This document defines the application-facing messaging interface used by OpenRoadCode producers and consumers. The bus is intentionally transport-light: domain contracts own schema and validation, while ZeroMQ only carries topic-prefixed JSON messages.
For copy/paste producer and subscriber examples, see messaging/README.md.
The broker is the only process that binds the TCP ports. Publishers and subscribers connect to it.
Default endpoints are defined in messaging/zeromq/endpoints.py:
tcp://0.0.0.0:5556tcp://0.0.0.0:5557tcp://127.0.0.1:5556tcp://127.0.0.1:5557Each bus message is a two-part ZeroMQ multipart message:
Example conceptual frame:
Payloads must be JSON objects. Every public contract owns its encoder, decoder, validator, typed decoded representation, schema version, and topic constant.
Public telemetry contracts use SI units unless a contract explicitly documents otherwise. Presentation layers may convert SI values for display, but producers must not publish UI-oriented units such as mph, PSI, Fahrenheit, or percent where the domain representation is m/s, Pa, K, or a 0.0..1.0 ratio.
Encoders validate their own output before publication. Decoders validate before constructing typed messages. Unknown or missing fields are rejected for strict versioned contracts.
Owns absolute position/fix information: latitude and longitude in radians, altitude and accuracy in meters, fix/satellite metadata, source, and cache state. Speed and course are excluded; see the Position IDD.
Owns motion information independent of the absolute position contract: heading and course in radians, ground and vertical speed in m/s, turn rate in rad/s, and cache state.
Owns fused vehicle orientation: heading, pitch, and roll in radians. These are angular coordinates, not an XYZ vector.
Owns inertial motion vectors: acceleration and linear acceleration in m/s², plus angular velocity in rad/s. Each quantity is represented as an XYZ vector.
Owns current automotive telemetry. Version 1 includes:
engine_speed_rad_svehicle_speed_m_sthrottle_positionaccelerator_pedal_positionengine_loadintake_manifold_pressure_pabarometric_pressure_paboost_pressure_pamass_air_flow_kg_scoolant_temperature_kintake_air_temperature_kfuel_levelcontrol_voltage_vApplications should normally create one MessageDispatcher for one subscriber connection and register multiple topics with it.
The dispatcher owns one receiver thread. Decoding occurs after receipt and handlers are submitted to an executor so slow handlers do not block bus reception.
A ZeroMQ socket must remain on the thread that owns it. ZeroMqSubscriber therefore creates its socket lazily on the thread that first calls receive() and releases that transport from the same owning thread during shutdown.
UI toolkit objects must not be mutated directly from dispatcher worker threads. Consumers should update a thread-safe state/cache from handlers, then marshal or poll that state on the UI toolkit thread.
Hardware and simulators should normalize data into domain state before publication. A typical automotive path is:
The navigation path fans one normalized NavigationState sample into public domain topics:
Attitude, IMU, and motion use the navigation sample timestamp. Position retains its GPS/source timestamp because fixes may update at a different cadence from inertial sampling.
Consumers must not know whether the source is simulated or physical hardware.
Recommended startup order for deterministic component testing:
ZeroMQ PUB/SUB subscriptions need time to propagate and early messages may be dropped. Continuous publishers should therefore tolerate slow joiners.
Recommended shutdown order is application-specific, but each dispatcher and publisher must be explicitly closed. MessageDispatcher.close() stops reception and drains/cancels executor work. ZeroMQ sockets should use zero linger for prompt application shutdown unless a future reliability requirement says otherwise.
Consumer state models may expose a monotonically increasing receive counter and latest message timestamp. These are diagnostic metadata only and are not part of the public telemetry contract. They distinguish:
Code must consume endpoint constants from messaging/zeromq/endpoints.py rather than duplicating port literals. Application-specific environment variables may override endpoints, but default topology semantics remain publisher ingress on 5556 and subscriber egress on 5557.
A new public message type should include, at minimum:
messaging/README.md updates documenting topic ownership and unitsDo not place hardware-specific parsing or UI-specific presentation logic in a public messaging contract.