Skip to main content

A minimal, single-file communication middleware built on Zenoh

Project description

ZRM (Zenoh ROS-like Middleware)

CI

A minimal, single-file communication middleware built on Zenoh, providing a clean and simple API inspired by ROS2 patterns.

https://github.com/user-attachments/assets/3e41d9a7-f553-457b-a879-cae2af45bf63

Features

  • Minimalist: Single-file implementation
  • Type-safe: Protobuf-based serialization with runtime type checking
  • Ergonomic: Pythonic API with sensible defaults

Installation

# Install from PyPI
pip install zrm

For development:

# Clone the repository and install dependencies
git clone https://github.com/JafarAbdi/zrm.git
cd zrm
uv sync

Development

Linting

Run linting and formatting checks using pre-commit:

uv run pre-commit run -a

This runs all configured linters and formatters on all files in the repository.

Testing

Run the test suite with pytest:

uv run pytest tests/ -v

Quick Start

Publisher/Subscriber

from zrm import Node
from zrm.msgs import geometry_pb2

# Create a node
node = Node("my_node")

# Create publisher and subscriber via node factory methods
pub = node.create_publisher("robot/pose", geometry_pb2.Pose2D)
sub = node.create_subscriber("robot/pose", geometry_pb2.Pose2D)

# Publish a message
pose = geometry_pb2.Pose2D(x=1.0, y=2.0, theta=0.5)
pub.publish(pose)

# Get latest message
current_pose = sub.latest()
if current_pose:
    print(f"Position: x={current_pose.x}, y={current_pose.y}")

# Clean up
pub.close()
sub.close()
node.close()

Subscriber with Callback

def handle_pose(pose):
    print(f"Received: x={pose.x}, y={pose.y}")

node = Node("listener_node")
sub = node.create_subscriber(
    topic="robot/pose",
    msg_type=geometry_pb2.Pose2D,
    callback=handle_pose,
)

Service Server/Client

Services use namespaced Request/Response messages for better organization:

from zrm import Node
from zrm.srvs import examples_pb2

# Define service handler
def add_callback(req):
    return examples_pb2.AddTwoInts.Response(sum=req.a + req.b)

# Create node
node = Node("service_node")

# Create service server via node factory method
server = node.create_service(
    service="add_two_ints",
    service_type=examples_pb2.AddTwoInts,
    callback=add_callback,
)

# Create service client via node factory method
client = node.create_client(
    service="add_two_ints",
    service_type=examples_pb2.AddTwoInts,
)

# Call service
request = examples_pb2.AddTwoInts.Request(a=5, b=3)
response = client.call(request)
print(f"Sum: {response.sum}")  # Output: 8

# Clean up
client.close()
server.close()
node.close()

Service Definition Pattern:

// Services must have nested Request and Response messages
message AddTwoInts {
  message Request {
    int32 a = 1;
    int32 b = 2;
  }

  message Response {
    int32 sum = 1;
  }
}

Message Organization

ZRM uses a convention-based message organization:

Directory Structure

proto/
├── msgs/              # Message definitions
│   ├── geometry.proto
│   ├── sensor.proto
│   └── header.proto
└── srvs/              # Service definitions
    ├── std.proto
    └── examples.proto
src/zrm/
├── msgs/                  # Generated message modules
│   ├── geometry_pb2.py
│   ├── sensor_pb2.py
│   └── header_pb2.py
└── srvs/                  # Generated service modules
    ├── std_pb2.py
    └── examples_pb2.py

Generating Python Code

# Generate message modules
../protoc-33.0-linux-x86_64/bin/protoc --pyi_out=src --python_out=src --proto_path=zrm/msgs=proto/msgs/ $(fd -e proto . proto/msgs/)

# Generate service modules
../protoc-33.0-linux-x86_64/bin/protoc --pyi_out=src --python_out=src --proto_path=zrm/srvs=proto/srvs/ $(fd -e proto . proto/srvs/)

Standard Messages

Messages (zrm.msgs):

  • geometry: Point, Vector3, Quaternion, Pose, Pose2D, Twist, PoseStamped
  • sensor: Image, LaserScan, PointCloud2
  • header: Header

Services (zrm.srvs):

  • std: Trigger

CLI Tools

ZRM provides command-line tools for inspecting and interacting with the network:

Topic Commands

# List all topics and their publishers/subscribers
zrm-topic list

# Echo messages from a topic (auto-discovers type)
zrm-topic echo robot/pose

# Echo with explicit type
zrm-topic echo robot/pose -t zrm/msgs/geometry/Pose2D

# Publish to a topic
zrm-topic pub robot/pose "x: 1.0 y: 2.0 theta: 0.5" -t zrm/msgs/geometry/Pose2D -r 10

# Measure topic frequency
zrm-topic hz robot/pose

Node Commands

# List all nodes in the network
zrm-node list

Service Commands

# List all services in the network
zrm-service list

# Call a service (auto-discovers type)
zrm-service call add_two_ints 'a: 1 b: 2'

# Call with explicit type
zrm-service call add_two_ints 'a: 1 b: 2' -t zrm/srvs/examples/AddTwoInts

Examples

See examples/ directory for complete working examples:

  • talker.py / listener.py: Basic publisher/subscriber pattern
  • service_server.py / service_client.py: Service request/response pattern
  • Graph discovery and introspection

Acknowledgements

  • The Graph class is inspired by ros-z
  • Built on Eclipse Zenoh for efficient pub/sub and query/reply patterns

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zrm-1.2.0.tar.gz (18.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zrm-1.2.0-py3-none-any.whl (26.8 kB view details)

Uploaded Python 3

File details

Details for the file zrm-1.2.0.tar.gz.

File metadata

  • Download URL: zrm-1.2.0.tar.gz
  • Upload date:
  • Size: 18.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.14

File hashes

Hashes for zrm-1.2.0.tar.gz
Algorithm Hash digest
SHA256 f5314a13a5fedc5104ab1043f53acdcb7d1bf77c261d1a2546fb00bf2a27a235
MD5 4252e552f5651c58f77d1733e0e18f5e
BLAKE2b-256 585954c9deada2f4fa7591d9aea0ee3eee4f505fae7f6efd4c4c9cb471494c2e

See more details on using hashes here.

File details

Details for the file zrm-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: zrm-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 26.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.14

File hashes

Hashes for zrm-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bc471475419ff48c8145ea707a964c8df97f3051ae258f83d0f41ef4f3b6e037
MD5 5c27ea01d9af7d8b4c8d051d83757327
BLAKE2b-256 101b9973a8ec9974427308517d1ef82ec7af6eade0b9959d0e59995cbe84c084

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page