Why this page matters
This page explains how Topic Key Routing 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 Topic Key Routing 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
Use one bounded task for the hot path, then let the scheduler keep the phase aligned over time.
ZeroKernel.begin(boardMillis);
ZeroKernel.addTask("Fast", fastTask, 10, 0, true);
ZeroKernel.tick();
Move non-critical routing and transport out of the immediate task body so fast paths stay predictable.
const auto key = ZeroKernel.makeTopicKey("telemetry.sample");
ZeroKernel.publishDeferredFast(key, sampleValue);
ZeroKernel.flushEvents();
Read the timing report and stats together so you can prove the cost of each abstraction layer.
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
Boot the runtime, register the minimum useful task set, and prove that the baseline timing is clean before adding optional layers.
Introduce routing, diagnostics, or transport one layer at a time so the cost and payoff remain obvious.
Update docs, charts, or public claims only after the same workload survives the same validation path more than once.
Why key-first routing exists
Topic keys exist because repeated string comparisons are one of the easiest costs to leave behind in embedded firmware long after the project has become performance-sensitive. A single string route is harmless. A route that fires continuously inside a telemetry node, a command-driven system, or a queue-backed transport path becomes a small but constant tax. Topic keys move that repeated cost into a one-time translation step.
The practical effect is simple: routing becomes easier to keep predictable. You still pay for callbacks, queues, and diagnostics, but you stop paying for repeated label work on every hot-path publish or subscribe. That is why ZeroKernel treats key-first routing as the preferred path and legacy labels as a compatibility bridge rather than the long-term direction.
The more frequently a route executes, the more important this becomes. A route that triggers once a minute is not the same as a route that triggers every 10 milliseconds. Topic keys matter most on the routes that carry the rhythm of the node.
Preferred pattern
const auto sensorKey = ZeroKernel.makeTopicKey("sensor.reading");
ZeroKernel.subscribeFast(sensorKey, onSensorReading);
ZeroKernel.publishFast(sensorKey, 17);
Create the key once, keep it in a stable scope, and reuse it. The common mistake is to build the same key inside a hot task repeatedly. That throws away part of the benefit. The cleanest pattern is to declare keys near the top of the file, then pass them into the pieces of code that need to publish or subscribe.
What improves when you use keys
- Less string work in repeated runtime dispatch.
- Smaller and clearer hot path for constrained boards.
- Easier to keep routing consistent across modules and examples.
Topic keys do not remove all routing overhead. They remove one important class of avoidable repeated work. The remaining costs still matter, but the runtime becomes easier to scale when one of the easiest-to-fix costs is no longer attached to every publish and subscribe on the most active routes.
How to migrate from legacy labels
The safest migration is incremental. Create the key, move subscribers first, then move publishers, then lock lean builds into key-first mode. This avoids a giant one-shot change and keeps the route readable while the code base is transitioning. Once the route is fully converted, disable the legacy label path in the profiles where you care about size and speed the most.
// Step 1: create a stable key
const auto telemetryKey = ZeroKernel.makeTopicKey("telemetry.temperature");
// Step 2: move subscribers
ZeroKernel.subscribeFast(telemetryKey, onTemperature);
// Step 3: move publishers
ZeroKernel.publishDeferredFast(telemetryKey, currentTemperature);
# Step 4: strip the legacy path in lean builds
-DZEROKERNEL_ENABLE_LEGACY_LABEL_API=0
-DZEROKERNEL_ENABLE_TOPIC_KEY_ONLY=1
Three useful routing examples
// Example 1: direct state publish
const auto stateKey = ZeroKernel.makeTopicKey("node.state");
ZeroKernel.publishFast(stateKey, 1);
// Example 2: deferred telemetry
const auto telemetryKey = ZeroKernel.makeTopicKey("telemetry.sample");
ZeroKernel.publishDeferredFast(telemetryKey, readSensor());
// Example 3: queued command work
const auto commandKey = ZeroKernel.makeTopicKey("pump.flush");
ZeroKernel.enqueueCommandFast(commandKey, 0);
Topic key FAQ
Should new firmware start with string labels?
No. New firmware should start key-first unless you are intentionally writing temporary migration code.
Do topic keys remove all routing overhead?
No. They remove a meaningful class of repeated label work, but queues, callbacks, and diagnostics still have a cost.
Should I keep labels around for readability?
Keep the human-readable label at the key declaration point, then use the key everywhere else. That preserves readability without paying repeated string costs.
When is legacy routing still acceptable?
During migration, debugging, or tiny prototypes. For anything long-lived or performance-sensitive, move to key-first routing early.