Skip to content

Zenoh connector and ROS 2 topics through rmw_zenoh (design 053, native backend) - #316

Merged
lxsaah merged 28 commits into
mainfrom
feat/aimdb-zenoh-connector
Oct 12, 2026
Merged

lxsaah merged 28 commits into
mainfrom
feat/aimdb-zenoh-connector

Conversation

@lxsaah

@lxsaah lxsaah commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Design 053, the Zenoh connector with an rmw_zenoh-compatible ROS 2 profile (docs/design/053-zenoh-connector.md, rev 10), native backend. It lands stages s01–s12 and s16, each reviewed as its own PR into feat/aimdb-zenoh-connector (#303–#315). The plan is docs/design/053-implementation-plan.md.

What it adds

AimDB records become ROS 2 topics, with no ROS installation on the AimDB side, and Zenoh becomes a transport for AimDB in its own right.

#[derive(Clone, Debug, Serialize, Deserialize, RosMessage)]
#[ros(type = "sensor_msgs/msg/Temperature", hash = "RIHS01_…")]
struct Temperature { header: Header, temperature: f64, variance: f64 }

let zenoh = ZenohConnector::new("tcp/192.168.10.5:7447");
let ros2 = zenoh.ros2(Ros2Node::new("cell4_gateway")).register::<Temperature>();
builder.with_connector(zenoh).with_connector(ros2);

builder.configure::<Temperature>("cell4.temperature", |reg| {
    reg.buffer(BufferCfg::SpmcRing { capacity: 16 })
        .linked_from("zenoh://cell4/probe/temperature")   // devices on plain Zenoh
        .linked_to("ros2://cell4/temperature");           // a ROS 2 publisher
});
  • New crate aimdb-zenoh-connector (0.1.0, std):
    • ZenohConnector for zenoh:// links. Inbound links match Zenoh key expressions through ZenohGrammar, with 055's captures and keys. Matching is linear and allocation-free, and it is checked against zenoh-keyexpr as an oracle. A sample that several subscriptions receive is ingested once per route. Publications and subscriptions are remote-only.
    • Ros2Connector for ros2:// links. It speaks rmw_zenoh's data keys, liveliness tokens, attachments (sequence number, timestamp, GID) and CDR, in both directions, with a node, a namespace and a domain. Ros2LinkExt sets the advertised QoS.
    • zenoh.ros2(node): both schemes on one session. A gateway keeps one router connection, and the connector never hears its own publications.
  • New crate aimdb-cdr (0.1.0): no_std serde for OMG CDR (XCDR1).
  • aimdb-data-contracts:
    • WireFormat and the recorded aimdb.wire_format link key;
    • the ros2 feature: RosMessage and its name and hash checks;
    • the linkable-cdr feature: link_codecs::Cdr<N>.
  • aimdb-derive: #[derive(RosMessage)] with compile-time checks of the type and hash attributes; #[derive(Linkable)] now sets WIRE_FORMAT = Json.

Changes to existing crates

  • aimdb-core, breaking for the connector interface, in the same unreleased window as 054 and 055:

    • RouteInfo gains type_id;
    • InboundDispatch::routes() returns InboundRouteInfo (topic, type_id, config);
    • connector::WIRE_FORMAT_KEY is a reserved link config key, which the four (de)serializer setters clear;
    • a topic resolver now runs once per route.

    The user-facing link API does not change.

  • aimdb-mqtt-connector: a test fixture only (RouteInfo::type_id).

Verification

  • Tests:
    • unit and golden tests: the profile is rebuilt byte for byte from 28 tokens, 14 data keys and 14 attachments captured from live rmw_zenoh on Jazzy and Lyrical;
    • the grammar oracle against zenoh-keyexpr;
    • trybuild compile-fail tests;
    • in-process router tests for every link direction, the refusals, and the shared session, which counts one TCP connection through a proxy in either registration order.
  • Interop CI, a new ros2-interop job on Jazzy and Lyrical: ros2 node list, ros2 topic info -v, ros2 topic echo and ros2 topic pub against a real rmw_zenohd (make ros2-interop DISTRO=…).
  • Reviews folded in:
    • an in-process self-delivery loop (fixed with remote-only locality, regression-tested);
    • cubic-time matching on crafted keys (fixed, linear now);
    • ROS_DOMAIN_ID edge cases;
    • delete samples.
  • Dependency policy: make deny and the Security workflow now also check the Zenoh connector's std graph, which cargo deny's default features never saw. Accepted, with reasons in .cargo/audit.toml and deny.toml:
  • Verification pass at 9740561, fixed in 4d6e85f:
    • opening the session no longer stalls the runtime (zenoh's open blocks inside block_in_place; each attempt now runs on its own task), and the current-thread scheduler is refused at build;
    • a panicking deserializer drops its sample instead of silently killing all inbound traffic;
    • keys and filters over 65,535 bytes are refused (they abort the process inside zenoh-transport);
    • one ROS type per topic is checked across both directions, by DDS name and hash;
    • ROS_DOMAIN_ID is parsed like rcl's strtoul(…, 0), and a domain above 232 now warns instead of failing (this reverses the feat(zenoh): Ros2Connector for outbound ros2:// links (design 053 s09) #311 cap);
    • new auth-usrpwd feature (pubkey auth is left out: it pulls in rsa, RUSTSEC-2023-0071);
    • docs: new("") scouts by multicast, both views on one builder, the runtime requirement, limits.
  • Upstream: filed keyexpr: intersects recurses once per chunk under ** and overflows the stack on long keys eclipse-zenoh/zenoh#2836 (intersects overflows the stack on long keys). Records are in docs/upstream/, including the over-long key abort, which isn't reported upstream yet.

Not in this PR

  • The embedded backend (zenoh-nostd, plan stages s13–s15), deferred until zenoh-nostd is released on crates.io. A git dependency would make the crate unpublishable. The embedded feature builds the no_std parts, such as ZenohGrammar.
  • ros2:// directly from a microcontroller (053 §4.9), services, actions and TRANSIENT_LOCAL.
  • Follow-ups: refresh design 012, the connector guide, which predates 054; a BYOC tutorial; the manufacturing-cell demo.

Release notes

  • aimdb-zenoh-connector joins PUBLISH_ORDER, and phase 1 of publish-check (packaging) passes locally.
  • CHANGELOG entries are written for every touched crate, plus the global one.

Before or after merging

🤖 Generated with Claude Code

lxsaah and others added 25 commits September 26, 2026 01:32
- Introduced `Cargo.toml` and `README.md` for the new crate.
- Implemented a deserializer for XCDR1 format in `src/de.rs`.
- Created a custom error type in `src/error.rs` for handling encoding/decoding errors.
- Developed a serializer for XCDR1 format in `src/ser.rs`.
- Added main library functionality in `src/lib.rs` for encoding and decoding values.
- Implemented tests for various encoding and decoding scenarios in `tests/`.
- Ensured compatibility with hiroz-cdr for little-endian output.
- Added robustness tests to handle random and malformed input gracefully.
Bring in 054 (zero-allocation connector boundary) and 055 (wildcard
inbound links). RouteMeta and the _with_meta accessors are dropped:
the route lists they wrapped no longer exist. The reserved link keys
stay; the topic key follows with_topic_writer as aimdb.topic_writer,
and with_match_deserializer clears the recorded wire format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refresh AimDB citations to eefe2c2, correct the ExactGrammar refusal
row, require whole-chunk captures in ZenohGrammar, mark the inbound
allocation claim as measured, and add S6 (put beside session.run()).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sixteen stages, each a branch with a PR into feat/aimdb-zenoh-connector,
derived from 053 rev 9's sequencing and acceptance criteria.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No fork. The embedded backend (s13–s15) waits for zenoh-nostd on
crates.io; until then the connector ships native-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It ran the full matrix on the stage PRs; the branch is merging into main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Open the session on a task of its own, so zenoh's blocking open no longer
  stalls the runtime; refuse Tokio's current-thread scheduler at build.
- Catch a panicking deserializer; the sample is dropped and logged.
- Refuse keys and filters over 65,535 bytes, which abort zenoh-transport.
- Check one ROS type per topic across both directions, by DDS name and hash.
- Parse ROS_DOMAIN_ID as rcl does; a domain above 232 warns.
- Add the auth-usrpwd feature.
- Docs: multicast scouting with new(""), one builder for both views,
  runtime requirement, limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lxsaah
lxsaah merged commit 602cae1 into main Oct 12, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant