English | 中文
Robot Bus
Lightweight ROS 2–style messaging over ZeroMQ — topics, services & actions, no ROS install. SDKs for Rust, Python, TypeScript, C++, Java, and Android. Tool nodes, TF, and Studio UI live in robot-bus-tools.
No ROS distro, no source setup.bash, no workspace. One broker process plus an SDK in any supported language is enough.
Design principles: APIs stay close to ROS 2 naming and usage (Node, SingleThreadedExecutor / MultiThreadedExecutor, add_node, create_publisher / create_subscription, spin) to ease migration; the transport is ZeroMQ and is not tied to any ROS distribution.
Pre-release notice: This project is still in pre-release. APIs may change substantially and runtime stability is not production-ready yet — use caution in production.
More API examples live under docs/.
Crate API
| Module | Role |
|---|---|
broker:: |
Routing process (message / service / action) |
| Top-level API | Publisher / Subscriber / Client / Worker |
runtime::Executor |
Low-level poll loop (usually wrapped by the executors below) |
runtime::SingleThreadedExecutor / MultiThreadedExecutor |
Explicit executors (multi-node / parallel); a single node can Node::spin directly |
runtime::Node / TopicPublisher / CallbackGroup |
Nodes, publishers, callback groups (mutually exclusive / reentrant) |
grpc:: (default feature) |
gRPC + browser WebSocket RPC gateway (started with the broker) |
ros2:: (ros2 feature) |
In-process ROS 2 topic/service bridge (Ros2Bridge) |
Repository layout
Rust core stays at the repo root (Cargo.toml + src/). Language SDKs live under bindings/; do not flatten them to peer top-level folders.
| Path | Role |
|---|---|
src/, Cargo.toml |
Rust core (crates.io / maturin entry) |
proto/ |
Contract source: ROS-style Protobuf → generated code for Rust / bindings |
bindings/ |
Language SDKs (Python, TypeScript, C++, Java, Android) |
console/ |
Broker console + BOT SIM viz/ops panel (build → assets/console/); in-process sim in src/bot_sim/ |
sibling robot-bus-tools |
rbus_* nodes, TF library + language extensions, Robot Bus Studio |
benches/ |
Perf harnesses: robot_bus_perf/ (just perf), ros2_perf/ (just perf-ros2) |
tests/ |
Rust integration tests + cross-language interop (just test-interop) |
docs/ |
API guides and generated perf reports |
scripts/, tools/, justfile |
Codegen, packaging, and task orchestration |
Architecture
Application code (Rust / Python / TypeScript / C++ / Java / Android)
└── robot-bus SDK
│
│ ZMQ (tcp / ipc / inproc) or gRPC / WebSocket RPC
▼
robot_bus_broker process
Optional ROS 2 bridge (Rust feature)
Everyday robot-bus development does not install ROS 2. To interconnect with a ROS 2 graph in-process, enable Cargo feature ros2 and use robot_bus::ros2::Ros2Bridge (chained API or YAML). Official support: Humble and Jazzy (source that distro + rclrs). C++: install robot-bus-ros2-humble or …-jazzy (does not vendor rcl). See the ROS 2 bridge section.
Quick start
1. Start the broker
Rust:
# API listen: robot_bus_broker --api-listen 0.0.0.0:15770
# advertise host: robot_bus_broker --advertise-host 10.0.0.5
# federation peer: robot_bus_broker --peer 10.0.0.2:15770
Introspection CLI (rbus)
Query the broker console HTTP API (default http://127.0.0.1:15770; override with --url or ROBOT_BUS_BROKER_URL):
topic list prints name and registered protobuf type (or -). Types appear after a typed create_publisher::<M> registers with the console (before any traffic). Topics with only raw traffic and no registration still list with type -. Services / actions appear after a worker READY.
Broker discovery (HTTP API)
Brokers expose GET /api/v1/discover on the API listen port (default 15770, shared with gRPC / WS / console). The JSON lists connectable message/service/action endpoints (OS-assigned ports after bind :0) plus brokerId / apiUrl.
Clients still choose the transport (tcp / ipc / inproc / grpc); discovery only fills host / paths / gRPC URL:
use ;
let opts = tcp.discover?;
let mut node = with_options;
Same API shape in bindings: Node.discover(...) (Python / C++ / Java / Android / TypeScript Node.js). Browser clients use the API URL / WebSocket directly.
Federation: pass --peer HOST:PORT (peer API listen) so the local broker fetches the peer's discover map and wires ZMQ peers.
Python (ships a CLI entry after pip install robot-bus):
Or start in-process:
# ... application code ...
pass
# leaves the with-block and stops automatically
# Or block like the CLI (Ctrl+C to exit)
# robot_bus.run_broker()
Python
Local development (requires maturin; just optional):
# equivalent: cd bindings/python && maturin develop --features extension-module,grpc
(grpc is a default feature; spelling it out avoids missing the gateway when default-features = false.)
=
=
# node.spin() # blocks; call node.shutdown() / shutdown_handle().shutdown() from another thread
(Omit the message type for raw bytes. Use SingleThreadedExecutor / MultiThreadedExecutor + add_node when sharing nodes or needing multi-threaded handlers.)
gRPC-only gateway clients: Node.grpc("name") / Node.grpc_at("name", "http://…") (subscribe / publish / call service / action). See docs/python-api.md.
TypeScript
Local development:
# equivalent: cd bindings/typescript && npm install && npm run build:native && npm run build:ts
One npm package: Node.js uses napi-rs (full ZMQ API); browsers use WebSocket RPC on /ws (subscribe / publish / service / action client). Bundlers pick the entry via exports. See docs/typescript-api.md.
import { Node } from "robot-bus";
import { Imu } from "robot-bus/sensor_msgs/msg/v1/imu.js";
const node = new Node("pilot");
const pub = node.createPublisher("/robot1/imu", Imu);
node.createSubscription("/robot1/imu", (_t, imu) => console.log(imu), Imu);
Browser / gRPC-only: Node.grpc("client") (the browser entry's Node is the WebSocket RPC facade).
Java / Android (Maven Central)
| Artifact | Directory | Coordinates |
|---|---|---|
| JVM JAR (Java 11+, Maven) | bindings/java/ |
org.indunet:robot-bus |
| Android AAR (minSdk 24, Kotlin SDK) | bindings/android/ |
org.indunet:robot-bus-android |
Package name is org.indunet.robot.bus for both. Android is a standalone Kotlin SDK (does not depend on the Java JAR). After you write release notes and Publish on GitHub, CI publishes to Maven Central (or run the Actions workflows manually).
// Android (Kotlin)
RobotBusAndroid.init(this)
val pub = node.createPublisher("/imu", Imu::class.java)
See docs/java-api.md / docs/android-api.md, bindings/java/README.md / bindings/android/README.md.
C++ (DEB / MSI)
No central package registry for C++: download from GitHub Releases (CI attaches assets after you Publish):
| Package | Contents |
|---|---|
robot-bus_*_linux_*.deb (also MSI / PKG) |
Core SDK + broker, no ROS 2 bridge |
robot-bus-ros2-humble_*_linux_*.deb |
Same + bridge linked for Humble (Linux only; needs system Humble; does not vendor rcl) |
robot-bus-ros2-jazzy_*_linux_*.deb |
Same + bridge linked for Jazzy (Linux only) |
Install only one of the three (they conflict). See docs/cpp-api.md.
robot_bus::Broker broker;
robot_bus::Node ;
auto pub = node.;
Rust (Node + spin)
Add to Cargo.toml:
= { = "../robot-bus" }
# or from crates.io: robot-bus = "0.1.7"
Semantics mirror ROS 2: Node::new → typed create_publisher / create_subscription → node.spin() (auto-attaches a SingleThreadedExecutor).
gRPC-only (no ZMQ): Node::grpc / Node::grpc_at — subscribe, publish, and call service / action, but cannot act as a server; see docs/rust-api.md.
use Arc;
use Duration;
use Vector3;
use Imu;
use Node;
let mut node = new;
let imu_pub = node.?;
node.?;
let imu = Imu ;
imu_pub.publish?;
node.create_timer?;
let handle = node.shutdown_handle?;
spawn;
node.spin?;
- Single-node default:
node.spin()(internalSingleThreadedExecutor) SingleThreadedExecutor/MultiThreadedExecutor+add_node: shared multi-node or parallel handlers- Callback groups:
MutuallyExclusive/Reentrant(create_callback_group; default is mutually exclusive) - Service / action: typed
create_service/create_client,create_action_server/create_action_client(on the Node like topics;*_rawvariants also available) - Timer:
create_timer(also on the Node, driven byspin) - Raw bytes:
create_publisher_raw/create_subscription_raw - Low-level escape hatch:
Executor(advanced)
Send / receive high-water marks (ZMQ HWM, not full QoS) can be set at create time or at runtime:
use ;
let pub_ = with_hwm?;
pub_.set_high_water_mark?;
Defaults: message STREAM(2/2), service RPC(4/4), action ACTION(8/8). Broker flags: --snd-hwm / --rcv-hwm.
Binaries
| Binary | Description |
|---|---|
robot_bus_broker |
Starts all three buses plus the gRPC / WebSocket RPC gateway |
Web console (console/)
Optional broker monitoring UI: Overview, Topics, Services, Actions, Topology, and event logs. With the console feature (default), the broker serves an embedded static UI on 0.0.0.0:15770 after you build assets once.
Development (hot reload — preferred):
# terminal 1
# terminal 2
&& &&
# open http://localhost:3000 (/api is proxied to the broker; override with ROBOT_BUS_BROKER_URL)
Embedded in the broker binary:
# open http://localhost:15770
# disable: cargo run --bin robot_bus_broker -- --no-console
assets/console/ is build output (not committed). CI and release jobs run just console (or equivalent) before compiling with the console feature.
Wired to the broker on the same port as native gRPC / WebSocket RPC (0.0.0.0:15770): the Dashboard is a TypeScript GrpcNode that subscribes to /robot_bus/* system topics over /ws. A thin REST shim (GET /api/v1/...) remains for rbus / tooling. Topology and topic-type registration use reliable control-plane services (/robot_bus/topology/register, /robot_bus/topic_type/register) through the existing service bus. Domain visualizers, Flow, and LIVE / WHEP live in robot-bus-tools Studio. The frontend source lives in console/; only the generated static files are compiled into binaries with the console feature.
Bot demo (2 nodes): in-process src/bot_sim/ owns physics (SUB /robot_bus/bot/cmd_vel → PUB /robot_bus/bot/pose) and starts when the console opens a BOT SIM session; the BOT SIM panel (bot_viz) renders pose and dispatches capabilities (keyboard teleop first). Shared world — multiple viewers, last-writer-wins teleop.
gRPC + browser WebSocket gateway
Started with robot_bus_broker / RobotBusBroker::start. Native gRPC (HTTP/2) and browser WebSocket RPC (/ws, one connection per RPC) share the same port (default 0.0.0.0:15770). There is no gRPC-Web layer.
You can also attach via the Node API with Node::grpc / Node::grpc_at (client: subscribe / publish / call service / call action; see docs/rust-api.md).
| RPC | Semantics |
|---|---|
MessageGateway.Subscribe |
Subscribe by topic prefix; server streams binary payloads |
MessageGateway.Publish |
Unary publish: topic + binary payload onto the message bus |
ServiceGateway.Call |
Unary: service_name + request bytes → response bytes |
ActionGateway.SendGoal |
Unary goal request followed by a server stream of ActionEvent values (FEEDBACK, then RESULT) |
Action clients use a ROS 2–style GoalHandle in every language: send_goal / sendGoal returns the handle immediately, feedback is delivered to a callback as it arrives, and the result is awaited separately. handle.cancel() is best-effort and does not imply server confirmation: WebSocket RPC sends an explicit CANCEL frame (connection stays open for RESULT; a true disconnect still cancels), native gRPC cancels the goal stream, and ZMQ sends an explicit CANCEL frame.
# config: cargo run --bin robot_bus_broker -- --help
# gRPC: http://0.0.0.0:15770
In-process:
use ;
let broker = start?;
let grpc = format!;
Proto (package robot_bus_interface.grpc.v1, distinct from ROS *.msg.v1 / *.srv.v1):
HTTP discovery (GET /api/v1/discover on the API listen port). Legacy protobuf schema:
announce.proto(UDP multicast path removed)
Tool nodes, TF, and Studio
Hardware / multimedia nodes (rbus_*), the TF coordinate-frame library, and the domain visualizer / Flow / LIVE UI now live in the sibling repository robot-bus-tools.
# After cloning both repos as siblings:
# Studio (visualizers + Flow + LIVE):
&& &&
robot-bus keeps the broker, multi-language communication SDKs, ROS 2 bridge, protobuf contracts (tf2_msgs included), and the broker monitoring console (Overview / Topics / Topology / Logs).
ROS 2 bridge (feature = "ros2")
In-process topic, service, and action bridge via robot_bus::ros2::Ros2Bridge (chained API or YAML). Not enabled by default — core SDK, crates.io, and maturin builds stay ROS-free.
Supported ROS 2 distributions (official): Humble and Jazzy. Other distros: build from source after sourcing that distro (best-effort).
| Need | Notes |
|---|---|
| Cargo (Rust) | --features ros2 (pulls optional rclrs) |
| Environment | Source Humble or Jazzy so rcl / type support libs link; main CI does not enable this feature |
| C++ packages | robot-bus (no bridge) vs robot-bus-ros2-humble / robot-bus-ros2-jazzy (mutually exclusive, Linux DEBs only — Windows MSI / macOS PKG ship the core stub). Packages do not vendor rcl/RMW/DDS — install system ROS and source /opt/ros/<distro>/setup.bash |
| Broker | Running robot_bus_broker reachable over tcp/ipc (or bus_discover) |
| Topic types | Registry — configure any registered type by string (not a hardcoded enum). Built-in: std_msgs/msg/String, sensor_msgs/msg/Imu, sensor_msgs/msg/Image, foxglove_msgs/msg/CompressedVideo. Extend by implementing TopicCodec + registering it. |
| Service types | std_srvs/srv/Trigger, std_srvs/srv/SetBool (directions ros_to_bus / bus_to_ros only; default call timeout 5s) |
| Action types | example_interfaces/action/Fibonacci (directions ros_to_bus / bus_to_ros only; default goal timeout 30s) |
use ;
let mut bridge = new
.bus_tcp
.route
.string
.direction
.add?
.route
.type_name
.direction
.add?
.service
.trigger
.direction
.add?
.service
.set_bool
.direction
.add?
.action
.fibonacci
.direction
.add?
.build?;
bridge.spin?;
// or: Ros2Bridge::from_yaml("bridge.yaml")?.spin()?;
// Camera→H264 example YAML: src/ros2/example_camera_h264.yaml
foxglove_msgs/msg/CompressedVideo requires the foxglove_msgs ROS package on the system (DynamicMessage type support).
C++ (after installing the matching Linux robot-bus-ros2-* package and sourcing ROS):
auto bridge =
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.;
bridge.;
// or: robot_bus::Ros2Bridge::from_yaml("bridge.yaml").spin();
// Camera→H264 example: src/ros2/example_camera_h264.yaml
See docs/cpp-api.md for package selection and local just cpp-dev-ros2.
Testing
# equivalent:
# cargo test
# PYTHONPATH=bindings/python python3 bindings/python/tests/test_msgs_roundtrip.py
# PYTHONPATH=bindings/python python3 bindings/python/tests/test_typed_api.py
# cd bindings/typescript && npm test
Protobuf messages
proto/ follows ROS package layout: proto/<pkg>/{msg|srv|grpc}/v1/*.proto.
Generated stubs are not checked into git; run just gen-* after changing protos or before local tests (requires protoc 35.1). CI / release pipelines generate and ship them inside wheels, crates.io crates, npm packages, DEB/MSI, and Maven JAR/AAR — consumers of published packages do not need protoc.
| Language | Path | Notes |
|---|---|---|
| Rust | robot_bus::<pkg>::{msg|srv}::v1 |
just gen-rust → src/generated/<pkg>/{msg|srv}/v1/<stem>.rs |
| Python | robot_bus.<pkg>.{msg|srv}.v1 |
just gen-python; packed into the wheel |
| TypeScript | robot-bus/<pkg>/{msg|srv}/v1/… |
just gen-typescript; packed into the npm package |
| Java / Android | org.indunet.robot.bus.<pkg>.{msg|srv|action}.v1 |
just gen-java; packed into JAR / AAR |
| C++ | #include <robot_bus/…> |
just gen-cpp; packed into DEB/MSI |
- Transport body remains opaque bytes (including the gRPC gateway); the Rust Node SDK binds types at create time and auto encode/decode (
create_publisher::<M>, etc.), or use*_raw; Python / TypeScript / Java pass a protobuf type for typed APIs (thin wrappers), or omit the type for raw bytes - Typed
create_publisher::<M>also best-effort registerstopic → M::full_name()(e.g.sensor_msgs.msg.v1.Imu) with the broker console HTTP API sorbus topic list/topic infocan show types without putting type metadata on the wire - srv is a pair of
*Request/*Responsemessages, not gRPC - grpc (
robot_bus) is the gateway RPC contract, started with the broker (default featuregrpc) - Messages live under the
robot_busnamespace and do not claim top-level ROS package names likesensor_msgs; encoding is protobuf and is not interoperable with ROS CDR - One-shot:
just gen-all
Covered packages: builtin_interfaces, std_msgs, std_srvs, geometry_msgs, sensor_msgs, nav_msgs, tf2_msgs, trajectory_msgs, diagnostic_msgs, unique_identifier_msgs, shape_msgs, visualization_msgs, control_msgs, nav2_msgs, apriltag_msgs, foxglove_msgs (ported from Foxglove schemas, package foxglove_msgs.msg.v1).