.unit.yaml file is the source of truth for a Basis unit. It defines what topics a unit subscribes to, how inputs are synchronized, and what topics it publishes. The code generator reads this file and produces the C++ boilerplate so you only write handler logic.
The file is validated against unit/schema.yaml before code generation runs.
Top-level fields
The concurrency model for all handlers in this unit.
single is the only currently supported value. All handlers run mutually exclusive from each other — only one handler executes at a time.Named arguments that are passed to the unit at launch time. Each key is the argument name. Each argument is itself an object with a
type field and optional metadata.Generated as --argument-name command-line flags. Accessible in topic name templates as {{args.argument_name}}.Argument sub-fields
Argument sub-fields
A C++ type name. Use primitive names:
bool, string, float, double, int, int32_t, etc. Avoid std:: prefixes. Vectors are not yet supported.A help string shown on errors and with
--help.When
true, wraps the generated field in std::optional<T>. Mutually exclusive with default.The default value when the argument is not supplied. Mutually exclusive with
optional.A list of additional
#include paths to inject into the generated unit_base.h. Use this when your handler types reference custom message headers or other C++ headers not pulled in by the serializer plugins.A map of handler names to handler definitions. Each handler describes a single callback function that fires when its synchronization condition is met. Handler names become C++ method names on the generated unit class.
Handler fields
Each entry underhandlers is an object with the following fields.
Controls when this handler fires. See sync block below.
A map of topic names to input definitions. Topic names are the full ROS-style topic path, e.g.
/camera_left. If omitted, the handler has no message inputs (useful with rate or external sync).A map of topic names to output definitions. Outputs are optional; a handler that only sends data to an external system may have none.
Sync block
The synchronization strategy for message inputs.
For
| Value | Behavior |
|---|---|
all | Fires when every non-optional input has at least one buffered message. |
equal | Fires when designated sync_field values are identical across all synced inputs. |
{ approximate: <ms> } | Fires when designated sync_field values are within the specified milliseconds of each other. |
external handlers, write sync: external (bare scalar, no type key).Fire the handler at a fixed rate (in Hz) regardless of message arrival. The value is a number in Hz.When
rate is set, inputs act as a cache — the most recently received message is forwarded to the handler each tick. Non-optional inputs must have been received at least once before the handler fires.rate and type are independent. You can combine a rate-based timer with required or optional topic inputs.Maximum number of messages to buffer per input channel for
equal and approximate synchronizers. When the buffer is full, the oldest message is dropped.Input fields
Each entry underhandlers.<name>.inputs is an object with the following fields.
The message type, in See Serialization for the full list of available serializer prefixes.
serializer:CppType format. The serializer prefix determines which serialization plugin is used. The C++ type name follows after the colon.The field or method to call on the message to extract the synchronization key. Used with
equal and approximate sync types.| Syntax | Generated code |
|---|---|
header.stamp() | msg->header.stamp() (method call chain) |
::time | &MyType::time (pointer-to-member; double-colon prefix) |
complex.access.to_string() | [](T* msg){ return msg->complex.access.to_string(); } (lambda) |
When
true, this input does not block the handler from firing. If the message has arrived by the time the handler is called, it is included; otherwise the field is nullptr.When
true, the most recently received message is not cleared after the handler fires. It is reused in every subsequent invocation until a newer message arrives. Useful for slow-changing configuration or calibration topics.Collect multiple messages between handler invocations. The generated input type becomes
std::vector<std::shared_ptr<const T_MSG>> instead of a single std::shared_ptr<const T_MSG>. Set to a positive integer to cap the buffer, or true for unlimited accumulation.Restricts this subscription to only the listed transport names. Mutually exclusive with
deny_transports.Excludes the listed transport names from this subscription. Mutually exclusive with
allow_transports.Quality-of-service settings for this subscription.
The subscriber queue depth — how many unprocessed messages are buffered before older ones are dropped.
An alternative C++ type to use when the message is delivered over the inproc transport. When set, the generated input type becomes
std::variant<std::monostate, std::shared_ptr<const NetworkType>, std::shared_ptr<const InprocType>>.Output fields
Each entry underhandlers.<name>.outputs supports the same type, inproc_type, allow_transports, deny_transports, and qos fields as inputs.
The published message type in
serializer:CppType format.When
true, the handler is allowed to return nullptr for this output without triggering an error diagnostic. Use this when a handler conditionally produces output.Restricts this publisher to only the listed transports.
Excludes the listed transports from this publisher.
Complete examples
The following examples are drawn directly fromunit/example/example.unit.yaml.
StereoMatch — exact timestamp sync
example.unit.yaml
header.stamp. Keeps at most two frames per channel in the buffer.
OnLidarSync — approximate sync with accumulation
example.unit.yaml
TenHertzWithTopic — rate-driven with transport restrictions
example.unit.yaml
/topic_a has been received at least once. /topic_a is restricted to inproc delivery; /topic_b forbids inproc. The output is optional — the handler can return nullptr for /topic_c.
HandleAll — fire on any input combination
example.unit.yaml
/topic_c and /topic_d have been received. No outputs — this handler sends data to an external system.
External — externally triggered output
example.unit.yaml
RunHandlerAndPublish() directly, for example from a camera SDK callback.
Topic name templating
Topic names support Jinja2{{ }} expressions that are evaluated against the unit’s args. This lets you parameterize topic names without recompiling.
--camera_name front resolves the topics to /front/rgb and /front/processed.
Next steps
Code generation
How to run the generator and what files it produces
Serialization
How the serializer prefix in the type field maps to a plugin
Synchronizers
Deep dive into how each sync type works
Launch files
How to wire units together into processes