A minimal, single-file communication middleware built on Zenoh, providing a clean and simple API inspired by ROS2 patterns.
no-audio-zrm.mp4
- Minimalist: Single-file implementation
- Type-safe: Protobuf-based serialization with runtime type checking
- Ergonomic: Pythonic API with sensible defaults
pip install zrmimport zrm
from zrm.msgs import geometry_pb2
node = zrm.Node("my_node")
# Publish
pub = node.create_publisher("robot/pose", geometry_pb2.Pose2D)
pub.publish(geometry_pb2.Pose2D(x=1.0, y=2.0, theta=0.5))
# Subscribe
sub = node.create_subscriber("robot/pose", geometry_pb2.Pose2D)
if pose := sub.latest():
print(f"Position: x={pose.x}, y={pose.y}")
node.close()Protobuf definition:
message Pose2D {
double x = 1;
double y = 2;
double theta = 3;
}zrm-topic list # List topics
zrm-topic echo robot/pose # Echo messages
zrm-service list # List services
zrm-service call add 'a: 1 b: 2' # Call service
zrm-action list # List actions
zrm-action send fib 'order: 10' # Send action goal
zrm-node list # List nodesSee examples/ for complete working examples including services, actions with feedback/cancellation, and graph discovery.
Configuration
ZRM checks environment variables for Zenoh configuration (priority order):
ZRM_CONFIG_FILE- path to a JSON5 config fileZRM_CONFIG- inline JSON5 config stringZENOH_CONFIG- Zenoh's native config file path
# Inline config
export ZRM_CONFIG='{ mode: "peer", listen: { endpoints: ["tcp/0.0.0.0:0#iface=enp8s0"] } }'
# Or with multicast discovery
export ZRM_CONFIG='{
mode: "peer",
listen: { endpoints: ["tcp/0.0.0.0:0#iface=enp8s0"] },
scouting: { multicast: { enabled: true, interface: "enp8s0" } }
}'
# Or from a file
export ZRM_CONFIG_FILE=/path/to/config.json5import zenoh
import zrm
config = zenoh.Config()
config.insert_json5("mode", "'peer'")
config.insert_json5("listen/endpoints", "['tcp/0.0.0.0:0#iface=enp8s0']")
zrm.init(config)
node = zrm.Node("my_node")Services
Services use nested Request/Response messages:
import zrm
from zrm.srvs import examples_pb2
def add_callback(req):
return examples_pb2.AddTwoInts.Response(sum=req.a + req.b)
node = zrm.Node("service_node")
server = node.create_service("add_two_ints", examples_pb2.AddTwoInts, add_callback)
client = node.create_client("add_two_ints", examples_pb2.AddTwoInts)
response = client.call(examples_pb2.AddTwoInts.Request(a=5, b=3))
print(f"Sum: {response.sum}") # Output: 8
node.close()Protobuf definition:
message AddTwoInts {
message Request { int32 a = 1; int32 b = 2; }
message Response { int32 sum = 1; }
}Actions
Actions support long-running goals with feedback and cancellation:
import zrm
from zrm.actions import examples_pb2
def execute_fibonacci(goal_handle: zrm.ServerGoalHandle) -> None:
goal_handle.execute()
sequence = [0, 1]
for i in range(1, goal_handle.goal.order):
if goal_handle.cancel_requested:
goal_handle.cancel(examples_pb2.Fibonacci.Result(sequence=sequence))
return
sequence.append(sequence[i] + sequence[i - 1])
goal_handle.publish_feedback(examples_pb2.Fibonacci.Feedback(partial_sequence=sequence))
goal_handle.succeed(examples_pb2.Fibonacci.Result(sequence=sequence))
node = zrm.Node("action_node")
server = node.create_action_server("fibonacci", examples_pb2.Fibonacci, execute_fibonacci)
client = node.create_action_client("fibonacci", examples_pb2.Fibonacci)
goal_handle = client.send_goal(
examples_pb2.Fibonacci.Goal(order=10),
feedback_callback=lambda fb: print(f"Progress: {list(fb.partial_sequence)}")
)
result = goal_handle.get_result(timeout=30.0)
print(f"Result: {list(result.sequence)}")
node.close()Protobuf definition:
message Fibonacci {
message Goal { int32 order = 1; }
message Result { repeated int32 sequence = 1; }
message Feedback { repeated int32 partial_sequence = 1; }
}Message Organization & Proto Generation
src/<package>/
├── proto/ # Proto definitions
│ ├── msgs/ # Message definitions
│ ├── srvs/ # Service definitions
│ └── actions/ # Action definitions
├── msgs/ # Auto-generated *_pb2.py
├── srvs/ # Auto-generated *_pb2.py
└── actions/ # Auto-generated *_pb2.py
zrm-proto # Generate from local protos
zrm-proto --dep zrm # Include dependency protos| Category | Module | Types |
|---|---|---|
| Messages | zrm.msgs.header_pb2 |
Header |
| Messages | zrm.msgs.geometry_pb2 |
Point, Vector3, Quaternion, Pose, Pose2D, Twist, PoseStamped |
| Messages | zrm.msgs.sensor_pb2 |
Imu, Image, CompressedImage, CameraInfo, JointState |
| Messages | zrm.msgs.vision_pb2 |
Point2D, BoundingBox2D |
| Services | zrm.srvs.std_pb2 |
Trigger |
| Services | zrm.srvs.examples_pb2 |
AddTwoInts |
| Actions | zrm.actions.examples_pb2 |
Fibonacci |
Development
git clone https://github.com/JafarAbdi/zrm.git
cd zrm
uv sync
# Linting
uv run pre-commit run -a
# Testing
uv run pytest tests/ -v- Graph class inspired by ros-z
- Built on Eclipse Zenoh