Skip to content

feat(zenoh): Ros2Connector for outbound ros2:// links (design 053 s09) - #311

Merged
lxsaah merged 4 commits into
feat/aimdb-zenoh-connectorfrom
feat/053-s09-ros2-outbound
Oct 9, 2026
Merged

lxsaah merged 4 commits into
feat/aimdb-zenoh-connectorfrom
feat/053-s09-ros2-outbound

Conversation

@lxsaah

@lxsaah lxsaah commented Oct 9, 2026

Copy link
Copy Markdown
Member

Stage s09 of docs/design/053-implementation-plan.md: outbound ros2:// (053 §4.2–4.4, §5.1, §6).

The connector

let ros2 = Ros2Connector::new("tcp/192.168.10.5:7447", Ros2Node::new("cell4_gateway").namespace("/cell4"))
    .domain_id(7)
    .register::<Temperature>();
builder.configure::<Temperature>("cell4.temperature", |reg| {
    reg.buffer(BufferCfg::SpmcRing { capacity: 16 }).linked_to("ros2://cell4/temperature");
});
  • Ros2Node: a name and a namespace. The namespace is part of the node's identity, not its topics.
  • Ros2Connector: domain_id(n) takes precedence; without it the connector reads ROS_DOMAIN_ID, and without that it uses 0. register::<T: RosMessage>() records T's ROS type name, hash and wire format. with_zenoh_config(..) is available, and the connector owns the ros2 scheme. It opens its own session for now; sharing a session with ZenohConnector comes in s11.
  • Ros2LinkExt on both link builders: with_depth(n), and with_reliability(Reliability::BestEffort), which is advertised only (see below). Both are parsed once at build time, so an invalid value fails the build.
  • What each outbound link does on the wire:
    • its data key is <domain>/<topic>/<type>/<hash>;
    • the node token and one publisher token per link (entity ids 1, 2, …) are declared before the first put;
    • every put carries the 33-byte attachment: a sequence number per publisher starting at 1, a wall-clock timestamp, and the GID of its token;
    • congestion control is Drop, and publications are remote-only, as in s08.

Build-time refusals (criterion 9, outbound rows)

Mistake Result
register::<T>() on a non-RosMessage type compile error (trybuild): "NotRos is not a ROS 2 message type", with a hint to derive it. The new #[diagnostic::on_unimplemented] on RosMessage in data-contracts produces this message
Hand-written RosMessage whose Linkable isn't CDR build() error naming the type
Hand-written malformed hash build() error naming the type and the hash
Record type never registered build() error
Non-CDR link codec (linked_to_with(url, Json)) build() error
Topic writer on a ros2:// link build() error
Bad topic, node name or namespace build() error, using the profile's rmw rules
ROS_DOMAIN_ID that isn't an integer build() error. The precedence is tested as a pure function, so tests don't race on the environment
ros2:// with no Ros2Connector core's existing unregistered-scheme error
Custom serializer warning, not an error: the documented escape hatch

Inbound ros2:// links fail the build with "not supported yet" until s10.

Design notes

  • Reliability isn't sent: reliability() on Zenoh publishers is #[unstable] in 1.10.1, and I didn't enable zenoh/unstable for it. A BEST_EFFORT link advertises best-effort in its token and travels on Zenoh's default reliable path. Zenoh never refuses a match on QoS (053 §6). 053 §5.1 now says this.
  • Token building is lean now: mangle and the QoS field are Display adapters, so each token is a single allocation. The golden tests from s07 are unchanged and pass.

Tests

  • tests/ros2_outbound.rs, against an in-process router, with a node in domain 7 under /cell4. It checks the node token, publisher tokens for the default QoS (::,10:…) and depth 1 (::,1:…), the data key, that the payload decodes as CDR, and attachments with sequence numbers n then n+1, a sane timestamp, and the GID of the publisher's token. It also covers every refusal above.
  • tests/compile_fail.rs (trybuild): the register bound.
  • Unit tests: domain precedence and QoS option parsing.
  • Live ROS, by hand, with examples/ros2_talker.rs against Jazzy's and Lyrical's rmw_zenohd:
    • ros2 node list shows /aimdb/talker;
    • ros2 topic info -v shows std_msgs/msg/String, the right hash, KEEP_LAST (10), RELIABLE, VOLATILE, and a GID;
    • ros2 topic echo decodes data: aimdb N.

CI

The second commit (7ee775b) adds feat/aimdb-zenoh-connector to ci.yml's pull_request branches, so this PR and later stage PRs run the full matrix instead of a local make check. It stays until you want it reverted.

Run locally: cargo fmt --all -- --check. Before the target-directory cleanup, these had passed: the ros2_outbound tests, the trybuild case and the library tests, plus the live ROS check. CI covers everything else, including clippy on the new code, which I have not run locally.

🤖 Generated with Claude Code

lxsaah and others added 4 commits October 9, 2026 16:39
`Ros2Connector::new(endpoint, Ros2Node::new(name).namespace(ns))` with
`.domain_id(n)` (else ROS_DOMAIN_ID, else 0) and `.register::<T:
RosMessage>()`. Each outbound link becomes a ROS publisher: rmw_zenoh
data key, node and publisher liveliness tokens declared before the
first put, and the 33-byte attachment (sequence from 1, wall-clock
timestamp, GID). `Ros2LinkExt` sets depth and advertised reliability.

build() refuses: an unregistered type, a type whose Linkable is not CDR
or whose names are malformed, a non-CDR link codec, a topic writer, ROS
name violations, a bad ROS_DOMAIN_ID, and inbound ros2:// links (s10).
A custom serializer warns. `register` on a non-RosMessage type is a
compile error, now with an `on_unimplemented` message.

The profile's tokens are built in one allocation each (Display adapters).
Verified live with `examples/ros2_talker.rs` against Jazzy and Lyrical.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Stage PRs are checked in CI instead of a local `make check`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
clippy::for_unbounded_range.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ability

From the #311 review:
- An empty or blank ROS_DOMAIN_ID means unset, as in rcl: domain 0.
- A domain above 232, from domain_id(..) or ROS_DOMAIN_ID, fails the
  build and names its source.
- `Reliability` is the profile's own enum, public in every build; the
  duplicate in ros2.rs and its string round trip are gone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@lxsaah
lxsaah merged commit ebfcd8e into feat/aimdb-zenoh-connector Oct 9, 2026
9 checks passed
@lxsaah
lxsaah deleted the feat/053-s09-ros2-outbound branch October 9, 2026 19:06
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