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

Migration from v2.x

v3.0 replaces the Node-based API with a simpler session-centric architecture. If you prefer the old API, pin to zrm==2.1.0.

Installation

pip install zrm

Requires Python 3.10+.

Quick Start

import zrm
from zrm.msgs import geometry_pb2

with zrm.open(name="my_session") as session:
    # Publish
    pub = zrm.Publisher(session, "robot/pose", geometry_pb2.Pose2D)
    pub.publish(geometry_pb2.Pose2D(x=1.0, y=2.0, theta=0.5))

    # Subscribe
    sub = zrm.Subscriber(session, "robot/pose", geometry_pb2.Pose2D)
    if pose := sub.latest():
        print(f"Position: x={pose.x}, y={pose.y}")

    pub.close()
    sub.close()

Protobuf definition:

message Pose2D {
  double x = 1;
  double y = 2;
  double theta = 3;
}

CLI Tools

zrm-topic list                    # List topics
zrm-topic echo robot/pose         # Echo messages
zrm-topic pub robot/pose 'x: 1.0' # Publish to topic
zrm-topic hz robot/pose           # Measure frequency
zrm-service list                  # List services
zrm-service call add 'a: 1 b: 2'  # Call service
zrm-session list                  # List sessions

More Examples

See examples/ for complete working examples including services and graph discovery.


Configuration

Environment Variables

ZRM checks environment variables for Zenoh configuration (priority order):

  1. ZRM_CONFIG_FILE - path to a JSON5 config file
  2. ZRM_CONFIG - inline JSON5 config string
  3. ZENOH_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.json5

Programmatic Configuration

import zenoh
import zrm

config = zenoh.Config()
config.insert_json5("mode", "'peer'")
config.insert_json5("listen/endpoints", "['tcp/0.0.0.0:0#iface=enp8s0']")

with zrm.open(config) as session:
    pub = zrm.Publisher(session, "robot/pose", geometry_pb2.Pose2D)
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)

with zrm.open(name="service_example") as session:
    server = zrm.Server(session, "add_two_ints", examples_pb2.AddTwoInts, add_callback)
    client = zrm.Client(session, "add_two_ints", examples_pb2.AddTwoInts)

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

    server.close()
    client.close()

Protobuf definition:

message AddTwoInts {
  message Request { int32 a = 1; int32 b = 2; }
  message Response { int32 sum = 1; }
}
Graph Discovery

Discover topics, services, publishers, and subscribers in the network:

import zrm

with zrm.open(name="discovery") as session:
    graph = zrm.Graph(session)

    # Get all topics and services
    topics = graph.get_topics()      # [(name, type), ...]
    services = graph.get_services()  # [(name, type), ...]

    # Get publishers/subscribers for a topic
    pubs = graph.get_publishers("robot/pose")
    subs = graph.get_subscribers("robot/pose")

    # Get servers/clients for a service
    servers = graph.get_servers("add_two_ints")
    clients = graph.get_clients("add_two_ints")

    # Wait for entities to appear
    graph.wait_for_publisher("robot/pose", timeout=5.0)
    graph.wait_for_server("add_two_ints", timeout=5.0)

    graph.close()
Message Organization & Proto Generation

Directory Structure

src/<package>/
├── proto/                 # Proto definitions
│   ├── msgs/              # Message definitions
│   └── srvs/              # Service definitions
├── msgs/                  # Auto-generated *_pb2.py
└── srvs/                  # Auto-generated *_pb2.py

Generating Python Code

zrm-proto              # Generate from local protos
zrm-proto --dep zrm    # Include dependency protos

Standard Messages

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
Logging

ZRM initializes Zenoh logging from the RUST_LOG environment variable, defaulting to error. Set it to see more output:

# Show info-level logs
RUST_LOG=info python my_app.py

# Show debug-level logs
RUST_LOG=debug python my_app.py

# Filter to specific modules
RUST_LOG=zenoh=info python my_app.py

Valid levels: error, warn, info, debug, trace.

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

Acknowledgements

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-3.1.1.tar.gz (20.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-3.1.1-py3-none-any.whl (31.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: zrm-3.1.1.tar.gz
  • Upload date:
  • Size: 20.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for zrm-3.1.1.tar.gz
Algorithm Hash digest
SHA256 27f9ae1ca6e9301bddb13267386d81060ce90d99de6ac52a13ace240f054746b
MD5 185b6cd6c9d6d04dd383eb355cfbe803
BLAKE2b-256 80be2f316609d58c9a1023f7bd41aea65d44244ae28f22e1ffec38904914d773

See more details on using hashes here.

File details

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

File metadata

  • Download URL: zrm-3.1.1-py3-none-any.whl
  • Upload date:
  • Size: 31.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for zrm-3.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3096723239c09d7ba91f5052efbdde815e6b3e7a81365ef809a1faa2d5b3ecd5
MD5 2f75ed2613ed527555d4461a9744624f
BLAKE2b-256 037de05f7174ffcc6c1ba7f3cabdfb1086a75d276b9a8581d1793fd0ba19f828

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