This is the documentation for the latest (main) development branch of the Infuse-IoT platform. If you are looking for the documentation of previous releases, use the drop-down menu on the left and select the desired version.

Task Runner

The Infuse-IoT Task Runner is a library designed for high-level task scheduling based on the current application state.

The task runner consists of a core scheduling loop, which determines when tasks should be started or terminated, and individual tasks, which perform some action when scheduled.

In a typical Infuse-IoT application, the Task Runner is the driver of the majority of the application behaviour. Combined with builtin task implementations for the most common application actions, the Task Runner allows the basis of new applications to be created in extremely small amounts of code.

Task Scheduling

Tasks are scheduled based on the evaluation of individual task_schedule’s, which are evaluated once per second. In order for a task to be started, all of the start conditions must be met, while only a single termination condition must be met to trigger the task termination.

The current set of potential scheduling conditions are:

Combining these basic options together allows the construction of complex scheduling conditions in a compact form, for example:

Run this task once a minute while moving, as long as the battery is over 20% charged and the current global time is known. If the battery drops below 15%, or the task has been running for over 15 seconds, terminate it.

struct schedules schedule_list[] = {
  {
   .task_id = SOME_TASK_ID,
   .validity = TASK_VALID_ALWAYS,
   .periodicity_type = TASK_PERIODICITY_FIXED,
   .timeout_s = 15,
   .battery_start.lower = 20,
   .battery_terminate.lower = 15,
   .periodicity.fixed.period_s = 60,
   .states_start = TASK_STATES_DEFINE(TR_NOT | INFUSE_STATE_DEVICE_STATIONARY, INFUSE_STATE_TIME_KNOWN),
  },
};

Task Arguments

Each task schedule can also be assigned arguments related to the task itself. This allows the behaviour of the task to be customised as the application desires, without needing to modify the tasks source code. These arguments can also be updated without needing to perform a full firmware update, in case parameters need to be tweaked after deployment.

struct schedules schedule_list[] = {
  {
   .task_id = TASK_ID_IMU,
   .validity = TASK_VALID_ALWAYS,
   .task_args.infuse.imu =
     {
       .accelerometer =
         {
           .range_g = 4,
           .rate_hz = 50,
         },
       .gyroscope =
         {
           .range_dps = 500,
           .rate_hz = 50,
         },
       .fifo_sample_buffer = 100,
     },
  },
};

Arguments for custom task implementations can be inserted into task_schedule with the CONFIG_TASK_RUNNER_CUSTOM_TASK_DEFINITIONS option.

Updating Task Schedules

Task schedules can be updated at runtime without a full firmware update through the usage of the Key-Value Store. When CONFIG_KV_STORE_KEY_TASK_SCHEDULES is enabled, the schedules provided to task_runner_init() are treated as the default schedules distributed with the application.

Any writes to the underlying task schedule KV slots will replace the default schedule until a new set of default schedules are distributed. A new set of defaults are signified by incrementing the CONFIG_TASK_RUNNER_DEFAULT_SCHEDULES_ID option. This must be used if the default schedules are changing in a way that could be incompatible with previous definitions. One example of this is if a new schedule is inserted in the middle of the default schedule list.

When a new schedule is written to KV_KEY_TASK_SCHEDULES or a default schedule reset is triggered by a write to KV_KEY_TASK_SCHEDULES_DEFAULT_ID, all currently running tasks are terminated and all schedules are reloaded and revalidated.

Disabling schedule updates

If there is a particular task schedule that must never be updated for correct operation of a device, that can be controlled by adding the TASK_LOCKED flag to the task_schedule.validity field of the schedule like below:

struct schedules schedule_list[] = {
  {
   .task_id = TASK_ID_IMU,
   .validity = TASK_LOCKED | TASK_VALID_ALWAYS,
  },
};

This flag will prevent task_runner_schedules_load() from modifying the provided schedule, regardless of the value saved in the KV store.

Task Schedule vs Task Implementation

A task schedule is a description of when a task implementation should be run. A task schedule is linked to the implementation through the task_schedule.task_id field. A single application can have multiple schedules referring to the same task implementation, although only a single schedule per task implementation can be running at a given time.

Schedule Evaluation

All schedules in an application are evaluated at the same time by the task_runner_iterate() function, which is required to be run once a second. This task can be offloaded from the application by calling task_runner_start_auto_iterate(), which will automatically call the former function from the Infuse Workqueue context.

The application is able to receive notifications of when a schedule is started, requested to terminate, or stopped, by assigning a task_schedule_event_cb_t to the appropriate task_schedule_state.event_cb field AFTER the task runner is initialised with task_runner_init().

Schedule Event notifications

If required, applications can register to be notified of scheduling events for a given schedule. The available events are defined in task_schedule_event. To register for callbacks on these events, populate task_schedule_state.event_cb on the same index as the schedule of interest, after the call to task_runner_init(). For example to subscribe to scheduling callbacks for the battery task:

struct schedules schedules[] = {
  {
   .task_id = TASK_ID_IMU,
   .validity = TASK_VALID_ALWAYS,
  },
  {
   .task_id = TASK_ID_BATTERY,
   .validity = TASK_VALID_ALWAYS,
  },
};
TASK_SCHEDULE_STATES_DEFINE(states, schedules);

void my_callback(const struct task_schedule *schedule, enum task_schedule_event event)
{
   ...
}

int main(void)
{
   task_runner_schedules_load(0, schedules, ARRAY_SIZE(schedules));
   task_runner_init(schedules, states, ARRAY_SIZE(schedules), ...);
   states[1].event_cb = my_callback;
}

Task Implementations

Tasks can be implemented as running as either a dedicated thread or as a delayable workqueue item running on the Infuse Workqueue. The former allows for more flexibility in terms of blocking operations, while the latter is more lightweight in terms of RAM resources since there is no need for a dedicated thread stack per task.

Built-in Tasks

Infuse-IoT comes with a selection of builtin task implementations for a range of common application tasks. Each task uses the standard Zephyr or Infuse-IoT API, allowing each task to be re-used across any hardware driver that implements the API.

  • Battery state sampling

  • Environmental sensor sampling

  • GNSS location retrieval

  • IMU controller (3 or 6 axis)

  • Wi-Fi Access Point & LTE Cell scanning

  • Nearby Bluetooth device scanner

  • Tagged Data Format (TDF) logger

API Reference

Task Runner runner APIs
Task runner schedule APIs