ZeroMQ message bus: broker routing and participant SDK
Project description
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.
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 / gRPC-Web 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/ |
Web monitoring console (product UI; build output synced to assets/console/ locally / in CI, not committed) |
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 / gRPC-Web
▼
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-cpp-ros2-humble or …-jazzy (does not vendor rcl). See the ROS 2 bridge section.
Quick start
1. Start the broker
Rust:
cargo run --bin robot_bus_broker
# discovery / domain: robot_bus_broker --domain-id 0 --advertise-host 10.0.0.5
# disable announce: robot_bus_broker --no-discovery
Introspection CLI (rbus)
Query the broker console HTTP API (default http://127.0.0.1:15771; override with --url or ROBOT_BUS_BROKER_URL):
cargo run --bin rbus -- topic list
cargo run --bin rbus -- service list
cargo run --bin rbus -- action list
cargo run --bin rbus -- status
Topic list shows names with recent forwarded traffic (a live subscriber is required for metrics). Services / actions appear after a worker READY.
Broker discovery (UDP multicast)
Brokers periodically announce on 239.255.76.67:15550 (away from ROS 2 / DDS 7400 / 239.255.0.1). The UDP payload is a pure protobuf BrokerAnnounce (magic must be RBUS). Invalid packets are dropped.
Clients still choose the transport (tcp / ipc / inproc / grpc); discovery only fills host / paths / gRPC URL:
use robot_bus::{DiscoverOpts, Node, NodeOptions};
let opts = NodeOptions::tcp().discover(DiscoverOpts {
domain_id: 0,
..Default::default()
})?;
let mut node = Node::with_options("talker", opts);
Same API shape in bindings: Node.discover(...) (Python / C++ / Java / Android / TypeScript Node.js). Browser gRPC-Web has no UDP discovery.
Python (ships a CLI entry after pip install robot-bus):
robot-bus-broker
Or start in-process:
import robot_bus
with robot_bus.RobotBusBroker.start() as broker:
# ... application code ...
pass
# leaves the with-block and stops automatically
# Or block like the CLI (Ctrl+C to exit)
# robot_bus.run_broker()
Python
pip install robot-bus
Local development (requires maturin; just optional):
just python-dev
# 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.)
import robot_bus
from robot_bus.sensor_msgs.msg.v1 import Imu
from robot_bus.geometry_msgs.msg.v1 import Vector3
def on_imu(topic, imu: Imu):
print(topic, imu.linear_acceleration)
node = robot_bus.Node("pilot")
imu_pub = node.create_publisher("/robot1/imu", Imu)
node.create_subscription("/robot1/imu", on_imu, msg_type=Imu)
imu_pub.publish(Imu(linear_acceleration=Vector3(x=0.0, y=0.0, z=9.8)))
# 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
npm install robot-bus
Local development:
just ts-dev
# 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 gRPC-Web (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 gRPC-Web 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).
just java-dev # JVM
just android-dev # AAR (needs Android SDK + NDK 26 + cargo-ndk)
// 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-cpp_*_linux_*.deb (also MSI / PKG) |
Core SDK + broker, no ROS 2 bridge |
robot-bus-cpp-ros2-humble_*_linux_*.deb |
Same + bridge linked for Humble (Linux only; needs system Humble; does not vendor rcl) |
robot-bus-cpp-ros2-jazzy_*_linux_*.deb |
Same + bridge linked for Jazzy (Linux only) |
Install only one of the three (they conflict). See docs/cpp-api.md.
#include <robot_bus/Node.hpp>
#include <robot_bus/sensor_msgs/msg/v1/imu.pb.h>
robot_bus::Broker broker;
robot_bus::Node node("pilot");
auto pub = node.create_publisher("/imu");
Rust (Node + spin)
Add to Cargo.toml:
robot-bus = { path = "../robot-bus" }
# or from crates.io: robot-bus = "0.1.1"
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 std::sync::Arc;
use std::time::Duration;
use robot_bus::geometry_msgs::msg::v1::Vector3;
use robot_bus::sensor_msgs::msg::v1::Imu;
use robot_bus::Node;
let mut node = Node::new("pilot");
let imu_pub = node.create_publisher::<Imu>("/robot1/imu")?;
node.create_subscription::<Imu, _>(
"/robot1/imu",
|topic, imu| {
println!("{topic}: {:?}", imu.linear_acceleration);
},
None,
)?;
let imu = Imu {
linear_acceleration: Some(Vector3 { x: 0.0, y: 0.0, z: 9.8 }),
..Default::default()
};
imu_pub.publish(&imu)?;
node.create_timer(
Duration::from_millis(100),
Arc::new(|| {
// control period / heartbeat
}),
None,
)?;
let handle = node.shutdown_handle()?;
std::thread::spawn(move || { /* ... */ handle.shutdown(); });
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 robot_bus::{Publisher, HighWaterMark};
let pub_ = Publisher::with_hwm(None, HighWaterMark::new(10, 10))?;
pub_.set_high_water_mark(HighWaterMark { snd: 10, rcv: 10 })?;
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 / gRPC-Web gateway |
Web console (console/)
Optional monitoring UI for broker status, topic traffic, and event logs. With the console feature (default), the broker serves an embedded static UI on 0.0.0.0:15771 after you build assets once.
Development (hot reload — preferred):
# terminal 1
cargo run --bin robot_bus_broker
# terminal 2
cd console && pnpm install && pnpm dev
# open http://localhost:3000 (/api is proxied to the broker; override with ROBOT_BUS_BROKER_URL)
Embedded in the broker binary:
just console # pnpm build + sync → assets/console/ (gitignored)
cargo run --bin robot_bus_broker
# open http://localhost:15771
# 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's same-port monitoring API: GET /api/v1/status, GET /api/v1/topics, GET /api/v1/services, GET /api/v1/actions, SSE /api/v1/events. The frontend source lives in console/; only the generated static files are compiled into binaries with the console feature.
gRPC / gRPC-Web gateway
Started with robot_bus_broker / RobotBusBroker::start. Standard gRPC and gRPC-Web share the same port (default 0.0.0.0:15770).
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.Run |
Bidirectional stream: client sends GOAL / CANCEL; server pushes ActionEvent (kind distinguishes FEEDBACK / RESULT) |
cargo run --bin robot_bus_broker
# config: cargo run --bin robot_bus_broker -- --help
# gRPC: http://0.0.0.0:15770
In-process:
use robot_bus::{GrpcBrokerConfig, RobotBusBroker, RobotBusConfig};
let broker = RobotBusBroker::start(RobotBusConfig {
grpc: GrpcBrokerConfig {
listen: "0.0.0.0:15770".parse()?,
..Default::default()
},
..RobotBusConfig::default()
})?;
let grpc = format!("http://{}", broker.grpc_listen());
Proto (package robot_bus_interface.grpc.v1, distinct from ROS *.msg.v1 / *.srv.v1):
UDP discovery (robot_bus_interface.msg.v1):
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-cpp (no bridge) vs robot-bus-cpp-ros2-humble / robot-bus-cpp-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) |
| MVP topic types | std_msgs/msg/String, sensor_msgs/msg/Imu |
| MVP service types | std_srvs/srv/Trigger, std_srvs/srv/SetBool (directions ros_to_bus / bus_to_ros only; default call timeout 5s) |
| MVP action types | example_interfaces/action/Fibonacci (directions ros_to_bus / bus_to_ros only; default goal timeout 30s) |
use robot_bus::ros2::{Direction, Ros2Bridge};
let mut bridge = Ros2Bridge::new("ros_bridge")
.bus_tcp("localhost")
.route("/chatter", "/chatter")
.string()
.direction(Direction::Both)
.add()
.service("/reset", "/reset")
.trigger()
.direction(Direction::RosToBus)
.add()?
.service("/enable", "/enable")
.set_bool()
.direction(Direction::BusToRos)
.add()?
.action("/fibonacci", "/fibonacci")
.fibonacci()
.direction(Direction::RosToBus)
.add()?
.build()?;
bridge.spin()?;
// or: Ros2Bridge::from_yaml("bridge.yaml")?.spin()?;
C++ (after installing the matching Linux robot-bus-cpp-ros2-* package and sourcing ROS):
#include <robot_bus/Ros2Bridge.hpp>
auto bridge = robot_bus::Ros2Bridge::New("ros_bridge")
.bus_tcp("localhost")
.route("/chatter", "/chatter")
.string()
.direction(robot_bus::Ros2Direction::Both)
.add()
.service("/reset", "/reset")
.trigger()
.direction(robot_bus::Ros2Direction::RosToBus)
.add()
.action("/fibonacci", "/fibonacci")
.fibonacci()
.direction(robot_bus::Ros2Direction::RosToBus)
.add()
.build();
bridge.spin();
// or: robot_bus::Ros2Bridge::from_yaml("bridge.yaml").spin();
See docs/cpp-api.md for package selection and local just cpp-dev-ros2.
Testing
just test-rust
just test-python
just test-typescript
just test-interop # cross-language matrix under tests/interop/
just perf # robot-bus → docs/perf-report.md (benches/robot_bus_perf/)
just perf-ros2 # ROS 2 comparison under benches/ros2_perf/
# 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 - 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, foxglove_msgs (ported from Foxglove schemas, package foxglove_msgs.msg.v1).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file robot_bus-0.1.1-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: robot_bus-0.1.1-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 3.2 MB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
717573716a6f7d0ab2f784836d5198dd7ba78cc7710461865256c2212cb638aa
|
|
| MD5 |
5421330853151434f2d25f0c95505a94
|
|
| BLAKE2b-256 |
1b60bc4ec88545bdbef759c63f84f804bb6a0449126b2c755d79b33f24aa3bc1
|
File details
Details for the file robot_bus-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: robot_bus-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 3.7 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9631b37c4f4d7a7701b614e02fb49081379d1bcddea527b8c41d7be7c5514add
|
|
| MD5 |
b8ec10f000e9c3e0ecd768cd5b8d0b57
|
|
| BLAKE2b-256 |
41f0323900d97268be7b10fd4e86581a57daccd37d330dd73f639861353fe6cc
|
File details
Details for the file robot_bus-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: robot_bus-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 3.8 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79017f1c87f0365af4827ccdf268817771a2aec9764d6b8eb0898610c5778626
|
|
| MD5 |
91694f6a2230f6edfad470dbbd3fc23d
|
|
| BLAKE2b-256 |
44faaf89c93aa5a1a7e7f042bd17094d64bfb044a19813efe8dc863f0d87516a
|
File details
Details for the file robot_bus-0.1.1-cp39-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: robot_bus-0.1.1-cp39-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 3.4 MB
- Tags: CPython 3.9+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: maturin/1.14.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d5fabe99ae171a59de71438b0081a1cc7eaa89b54a153f18e9780d4fc0f9087
|
|
| MD5 |
fd64e3bdfeb78a9abee28cd350f52c33
|
|
| BLAKE2b-256 |
61aad24990c49de7cf718bc945fefdc36f8e8bfba686f9d0b83f3c82d9358a14
|