Docs

Task Registration with addTask

Use addTask as the controlled entrypoint for periodic work. This page breaks down cadence, first-run behavior, naming, and the execution contract you attach to a task.

Why this page matters

This page explains how Task Registration with addTask fits into the wider ZeroKernel execution model, what problem it is meant to solve, and what trade-off you are actually accepting when you use it in production firmware. The goal is not to treat Task Registration with addTask as an isolated API call, but to understand where it sits inside bounded scheduling, queue discipline, fault visibility, and profile selection.

Read this topic as an operational contract. Start from the smallest working path, wire it into a lean profile first, and only expand into richer routing, diagnostics, or transport state after you can prove that the timing outcome is still worth the extra flash and RAM. That mindset is what keeps ZeroKernel useful on small boards instead of turning it into another bloated abstraction.

The safest pattern is always the same: define the runtime boundary, keep the hot path short, measure the effect with compare scripts, and only then scale complexity. The examples below are not filler; they show the smallest repeatable patterns you can lift into real firmware when you need clean integration instead of ad-hoc loops.

Three practical patterns

Core cadence pattern

Use one bounded task for the hot path, then let the scheduler keep the phase aligned over time.

C++
    ZeroKernel.begin(boardMillis);
ZeroKernel.addTask("Fast", fastTask, 10, 0, true);
ZeroKernel.tick();
  
Deferred work pattern

Move non-critical routing and transport out of the immediate task body so fast paths stay predictable.

C++
    const auto key = ZeroKernel.makeTopicKey("telemetry.sample");
ZeroKernel.publishDeferredFast(key, sampleValue);
ZeroKernel.flushEvents();
  
Runtime visibility pattern

Read the timing report and stats together so you can prove the cost of each abstraction layer.

C++
    const auto stats = ZeroKernel.getStats();
const auto timing = ZeroKernel.getTimingReport();
Serial.println(timing.maxTickMs);
  

What to verify while you use it

  • Validate timing before you validate aesthetics. A cleaner API is not a win if fast misses rise.
  • Prefer the smallest profile that still matches the workload, then add optional modules only when the measured payoff is obvious.
  • Keep callbacks and transport steps bounded so watchdog, panic flow, and queue limits remain meaningful.

Common mistakes that make results misleading

  • Do not copy a demo pattern into production firmware without measuring it on the real board and real build profile you plan to ship.
  • Do not read success counters without reading queue depth, timing, and workload label next to them.
  • Do not enable heavier diagnostics and compatibility flags in a lean target just because the defaults looked convenient.

Recommended working sequence

Start from the smallest valid path

Boot the runtime, register the minimum useful task set, and prove that the baseline timing is clean before adding optional layers.

Add one layer, then measure it

Introduce routing, diagnostics, or transport one layer at a time so the cost and payoff remain obvious.

Publish only repeatable results

Update docs, charts, or public claims only after the same workload survives the same validation path more than once.

What addTask really defines

addTask() is not just a convenience function for storing a callback. It is the point where you declare execution intent: how often something should run, whether it should start immediately, whether it should be visible to diagnostics, and how it should participate in the scheduler’s due-selection model.

The cleaner this declaration is, the easier the rest of the firmware becomes to maintain. When teams struggle with “too many tasks,” the problem is usually not the count itself. It is that tasks were registered without clear cadence boundaries, ownership, or follow-up supervision settings.

Basic shape

C++
    void sampleTask() {
  // bounded work
}

void setup() {
  ZeroKernel.begin(millis);
  ZeroKernel.addTask("Sample", sampleTask, 100, 0, true);
}
  

This is the minimum useful registration: a stable name, a bounded task body, a fixed interval, no startup delay, and immediate activation.

What each argument controls

ArgumentMeaning
nameHuman-readable identifier copied into runtime storage for diagnostics, snapshots, and signal messages.
callbackThe bounded cooperative task body that should finish quickly and return.
intervalCadence in the active scheduler clock domain; this is the contract for how often the task may become due.
startDelayOptional initial offset before first eligibility, useful when boot sequencing must be staggered.
enabledWhether the task enters the scheduler immediately or remains registered but paused.

Three registration patterns you will actually use

C++
    ZeroKernel.addTask("Sample", sampleTask, 100, 0, true);
  

Use this when the task is safe to start immediately at boot and has no dependency on another subsystem.

C++
    ZeroKernel.addTask("WiFiFlush", flushTask, 250, 1000, false);
ZeroKernel.resumeTask("WiFiFlush");
  

Use this when a task should exist early but not actually run until another stage in setup finishes.

C++
    ZeroKernel.addTask("FastLoop", fastTask, 10, 0, true);
ZeroKernel.setTaskPriority("FastLoop", zerokernel::kPriorityCritical);
ZeroKernel.setTaskHeartbeatTimeout("FastLoop", 50);
  

Use this for a high-value task that needs explicit scheduler priority and explicit supervision, not just a plain cadence.

Common follow-up calls

C++
    ZeroKernel.setTaskPriority("Sample", zerokernel::kPriorityHigh);
ZeroKernel.setTaskHeartbeatTimeout("Sample", 250);
ZeroKernel.setTaskExecutionContract("Sample", contract);
  

Most real tasks are not “done” at registration. They usually need at least one follow-up configuration call so the runtime knows how much it should care when the task misbehaves.

How to avoid task sprawl

Not every helper function deserves its own task. Use tasks to define cadence boundaries, not to mimic object hierarchies or source file boundaries. If two steps always run at the same rate and fail together, they often belong in the same task body with small helper functions.

Create a separate task only when one of these is true: the cadence is different, the failure meaning is different, or the work must be supervised independently.

addTask FAQ

Should every function become its own task?

No. Use tasks for cadence boundaries. Keep internal helper functions inside the task body if they share the same schedule.

Can a task name come from a temporary String?

The runtime now copies names into internal storage, but fixed string literals are still the safest and cleanest default.

What is the most common registration mistake?

Registering tasks without deciding whether they are truly time-sensitive, then compensating later with too many priorities and watchdog settings. Decide the cadence first, then layer supervision.