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 --pyi_out=src --python_out=src --proto_path=zrm/msgs=proto/msgs/ $(fd -e proto . proto/msgs/)

# Generate service modules
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.3.0.tar.gz (19.4 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.3.0-py3-none-any.whl (28.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for zrm-1.3.0.tar.gz
Algorithm Hash digest
SHA256 6341f3f57fa25e428d50f5fd6a729926197638f56ed08a4d021da6c311b9674e
MD5 132a964d39f23537c157cadb4e4c1902
BLAKE2b-256 3ef038b25b77bcbcbfd70afd0a6b324a04cb80742b5a3e33890f1a67107980f5

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for zrm-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 739d752e3dede5a1e41fbb760d4e915bb8df031a87f91f00d47b8bda4f1de282
MD5 4fca338c36c078d261e81580eafb8ae1
BLAKE2b-256 245fe7bfc1b27605a3b802a0b8282115aa5bfc42913b3c6f67e10a5474208a01

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