DCode

Covers Engine 2.0.0

Writing Periodic Tasks

Run scheduled logic with persistent state and missed-release diagnostics.

Use task_periodic for work that repeats at a requested frequency. It samples its inputs when its iteration runs; it does not run once per input update.

A stateful filter

Assume another source publishes demo_noise:

task_periodic tasks_noise_filter:
    start:
        state.last = |demo_noise|

    task elapsed_seconds:
        local raw = |demo_noise|
        state.last = state.last + (raw - state.last) * 0.2
        |demo_noise_filtered| = state.last

    missed:
        print("Skipped releases", missed_count, "lateness ms", lateness_ms)

    end:
        print("Noise filter stopped")

start: runs at task start. task elapsed_seconds: repeats while running and not held. The elapsed value excludes held time. end: runs on stop. The filter’s coefficient is applied once per iteration, so changing task frequency changes its time response.

Operate the task

Channel suffixPurpose
_runningWrite 1 to start or 0 to stop.
_holdWrite 1 to hold, 0 to resume.
_target_frequencyRequested Hz; defaults to 10.
_actual_frequencyMeasured Hz; observe-only.

For this example, the start channel is tasks_noise_filter_running. Task Lifecycle explains ownership and stopping.

Missed releases

CAESAR can skip overdue releases. missed: runs once for the skipped group with missed_count and lateness_ms. Keep this handler short; it cannot recover physical samples that were never acquired. Inspect execution time and missed-cycle diagnostics when the requested frequency is not being achieved.

A 10 Hz loop has a nominal 100 ms interval. It is not an immediate limit detector. Use a dependency-driven event when the condition should be evaluated in the input-update path.

Dynamic channel names

This section requires the Lua backend. Native C++ requires static pipe names and supports only single-argument print statements, so the earlier multi-argument missed-release log also needs simplifying before native compilation.

{expression} inside a pipe reference builds a name at runtime:

task_periodic tasks_state_copy:
    task elapsed_seconds:
        for name in {"alpha", "beta", "gamma"}:
            |demo_{name}_state| = |demo_{name}_command|

With name equal to alpha, the target is demo_alpha_state. Dynamic names work in calculations too, but require explicit trigger dependencies there. See Channel Calculations.