1. Run a task#

pw_async2: Cooperative async tasks for embedded

The two most fundamental components of pw_async2 are tasks and dispatchers.

Log a welcome message in a task#

Task is the basic unit of execution in the pw_async2 framework. Tasks are objects that represent jobs to be done, like blinking an LED, processing sensor data, or running a vending machine.

  1. Study the code in vending_machine.h.

     1// The main task that drives the vending machine.
     2class VendingMachineTask : public pw::async2::Task {
     3 public:
     4  VendingMachineTask()
     5      : pw::async2::Task(PW_ASYNC_TASK_NAME("VendingMachineTask")) {}
     6
     7 private:
     8  // This is the core of the asynchronous task. The dispatcher calls this method
     9  // to give the task a chance to do work.
    10  pw::async2::Poll<> DoPend(pw::async2::Context& cx) override;
    11};
    

    VendingMachineTask inherits from Task.

    The DoPend() function is where your task will do work.

  2. Study the code in vending_machine.cc.

    Here you’ll find the incomplete implementation of DoPend.

    1pw::async2::Poll<> VendingMachineTask::DoPend(pw::async2::Context& cx) {
    2  // Fill in your implementation here.
    3  return pw::async2::Ready();
    4}
    

    The DoPend method returns a Poll<>. A Poll can be in one of two states:

    • Ready(): The task has finished its work.

    • Pending(): The task is not yet finished. The dispatcher should run it again later.

    Our current DoPend() immediately returns Ready(), meaning it exits without doing any work.

  3. Log a welcome message in vending_machine.cc:

    • Log the message at the start of the DoPend() implementation:

      PW_LOG_INFO("Welcome to the Pigweed Vending Machine!");
      

      This logging macro comes from pw_log. We’ve already included the header that provides this macro (pw_log/log.h).

    • Keep the Ready() return because this tells the dispatcher to complete the task after the message has been logged.

    Hint
    1pw::async2::Poll<> VendingMachineTask::DoPend(pw::async2::Context& cx) {
    2  PW_LOG_INFO("Welcome to the Pigweed Vending Machine!");
    3  return pw::async2::Ready();
    4}
    

Post the task to a dispatcher#

The Dispatcher is the engine that runs the tasks. It’s a simple, cooperative scheduler. You give it tasks by calling Post(). You run tasks with the dispatcher by calling RunUntilStalled() or RunToCompletion().

The dispatcher maintains a queue of tasks that are ready to be polled. When a run is triggered, it grabs a task from the queue and invokes the task’s DoPend() method. If the task returns Pending(), the task is put to sleep until it is woken by the operation that blocked it. If the task returns Ready(), the dispatcher considers it complete and will not run the task again.

  1. In main.cc, set up a task and run it with the dispatcher:

    • Create an instance of VendingMachineTask

    • Add the task to the dispatcher’s run queue by calling the dispatcher’s Post() method, passing the task as an arg

    • Tell the dispatcher to run all of its tasks until they return Ready() by calling its RunToCompletion() method

    Hint
     1int main() {
     2  pw::async2::Dispatcher dispatcher;
     3  codelab::HardwareInit(&dispatcher);
     4
     5  codelab::VendingMachineTask task;
     6  dispatcher.Post(task);
     7
     8  dispatcher.RunToCompletion();
     9
    10  return 0;
    11}
    

Run the app#

  1. Build and run the app again:

    bazelisk run //pw_async2/codelab
    

    You should see the same output as before, in addition to your new welcome message:

    INF  Welcome to the Pigweed Vending Machine!
    

Next steps#

You’ve written and run your first task with pw_async2. Continue to 2. Call an async function to learn how to run async operations in your task.

Check out Informed poll to learn more about the conceptual programming model of pw_async2.

Checkpoint#

At this point, your code should look similar to the files below.

#include "coin_slot.h"
#include "hardware.h"
#include "pw_async2/dispatcher.h"
#include "vending_machine.h"

namespace {

codelab::CoinSlot coin_slot;

}  // namespace

// Interrupt handler function invoked when the user inserts a coin into the
// vending machine.
void coin_inserted_isr() { coin_slot.Deposit(); }

// Interrupt handler function invoked when the user presses a key on the
// machine's keypad. Receives the value of the pressed key (0-9).
void key_press_isr(int key) {
  // In Step 3, implement your keypad handler here.
}

// Interrupt handler function invoked to simulate the item drop detector
// detecting confirmation that an item was successfully dispensed from the
// machine.
void item_drop_sensor_isr() {
  // In Step 5 you will uses this as part of a new Dispense task that runs
  // the dispenser motor until an item drops, or you time out on the vend
  // operation.
}

int main() {
  pw::async2::Dispatcher dispatcher;
  codelab::HardwareInit(&dispatcher);

  codelab::VendingMachineTask task;
  dispatcher.Post(task);

  dispatcher.RunToCompletion();

  return 0;
}
#include "vending_machine.h"

#include "pw_async2/try.h"
#include "pw_log/log.h"

namespace codelab {

pw::async2::Poll<> VendingMachineTask::DoPend(pw::async2::Context& cx) {
  PW_LOG_INFO("Welcome to the Pigweed Vending Machine!");
  return pw::async2::Ready();
}

}  // namespace codelab
#pragma once

#include "pw_async2/context.h"
#include "pw_async2/poll.h"
#include "pw_async2/task.h"

namespace codelab {

// The main task that drives the vending machine.
class VendingMachineTask : public pw::async2::Task {
 public:
  VendingMachineTask()
      : pw::async2::Task(PW_ASYNC_TASK_NAME("VendingMachineTask")) {}

 private:
  // This is the core of the asynchronous task. The dispatcher calls this method
  // to give the task a chance to do work.
  pw::async2::Poll<> DoPend(pw::async2::Context& cx) override;
};

}  // namespace codelab