Orchestrator
The Orchestrator is the pub/sub bus. There is exactly one per robot — it owns the topic registry, the node registry, the action registry, and all four thread workers. You almost never construct one yourself: SafeOpMode.init() creates it as the orchestrator field before onSafeInit() runs.
@Override
protected void onSafeInit() {
// orchestrator, hardware, and safeMap are ready here.
orchestrator.publish("robot/init", true);
}
Outside an OpMode (unit tests, for example), create one directly:
Orchestrator orch = Orchestrator.create("test");
HardwareActions hw = orch.hardware();
// ... use it ...
orch.close();
FtcOrchestrator.create() is the FTC-side factory: same bus, but named "ftc" and logging to android.util.Log instead of stderr.
What it does
| Group | Methods | Notes |
|---|---|---|
| Topics | getOrCreateTopic(name, type), findTopic(name), findTopic(name, type) | Topics are created lazily; see Topics. |
| Publish | publish(name, value) | Non-blocking. Updates the latest-value cache, then dispatches to subscribers. |
| Subscribe | subscribe(name, type, handler) | Handler runs on the callback pool. Returns a Subscription handle. |
| Read | getLatestValue(name, type) | Optional of the most recent value; empty if none or type-incompatible. |
| Nodes | registerNode(name, node), unregisterNode(name), findNode(name) | Registration binds all annotations on the node. |
| Actions | runAction(name), cancelAllActions() | See @RunnableAction. |
| Scheduling | runPeriodically(task, hz) | The programmatic form of @RunPeriodically (non-hardware). |
| Hardware | runOnHardwareThread(task), hardware() | Direct submission, or the HardwareActions facade. |
| Logging | log, warn, error | Routed through the configured LogSink. |
Naming things
Both topic names and node names are plain strings. Two conventions used throughout the recipes:
- Topics are slash-separated paths rooted by their source:
g1/left_stick_y,drive/power/fl,intake/power. Producers own the first segment; consumers subscribe to the full path. - Node names identify the subsystem:
"drive","intake","shooter". Names are unique — registering a second node under an existing name is a no-op that logs a warning and returns the original node. If a subsystem silently does nothing, grep for a duplicateregisterNodecall.
Lifecycle
orchestrator.close(); // SafeOpMode.stop() does this for you
close() is idempotent. It unregisters every node (calling each node’s close() hook), cancels all scheduled loops and actions, and shuts down all four workers. After close, publishes are ignored (with a warning) and runAction returns a failed future.