Skip to content

fix(rtps): interoperate with CycloneDDS peers (ROS 2 rmw_cyclonedds_cpp) - #862

Merged
alexchunlin merged 3 commits into
esp-cpp:mainfrom
alexchunlin:fix/rtps-cyclonedds-interop
Oct 30, 2026
Merged

alexchunlin merged 3 commits into
esp-cpp:mainfrom
alexchunlin:fix/rtps-cyclonedds-interop

Conversation

@alexchunlin

@alexchunlin alexchunlin commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Description

Four defects in components/rtps stop every sample between espp and a CycloneDDS participant, including ROS 2 on rmw_cyclonedds_cpp. FastDDS tolerates all four, so the existing interop matrix never caught them.

  • HEARTBEAT and GAP addressed to ENTITYID_UNKNOWN were dropped (MessageReceiver.cpp). CycloneDDS addresses its SEDP and user-data heartbeats to the participant, not to one reader (RTPS 2.5 8.3.7.5). espp looked up a reader by id 0, found none, and never NACKed, so it never received the SEDP data. The reader is now resolved through the matched writer, as the DATA path already does.
  • ACKNACKs had no INFO_DST and echoed the incoming readerId (StatefulReader.cpp). CycloneDDS resolves the writer by (destination prefix, writerId), so it ignored them. The ACKNACKs now carry INFO_DST and the reader's own entity id.
  • The pre-emptive ACKNACK used sequence base 0 (StatefulReader.cpp). RTPS 8.3.5.5 requires a base of at least 1. CycloneDDS's validate_AckNack repairs base 0 only for eProsima and RTI vendor ids, so for espp (vendor 13.37) it logs malformed packet received from vendor 13.37 and drops the message. The base is now 1.
  • SEDP samples without PID_UNICAST_LOCATOR kept a placeholder address (TopicData.cpp, SEDPAgent.cpp). CycloneDDS leaves the per-endpoint locator out, which RTPS 2.5 8.5.3.2 allows: the participant's default locator applies. TopicData's constructor pre-fills a placeholder address, and readFromBuffer() never reset it, so samples went to that address. The locators are now reset before parsing, and an endpoint without one falls back to its participant's default unicast locator.

The interop harness (components/rtps/interop) now installs rmw_cyclonedds_cpp and runs the ROS 2 pub/sub pairs a second time on CycloneDDS: reliable and best-effort, in both directions.

Motivation and Context

ROS 2 on CycloneDDS can't exchange any sample with espp. ros2 topic list shows the espp topics, but ros2 topic echo stays silent, and nothing published from ROS 2 arrives. We hit this between an ESP32-P4 board and a Jetson running ROS 2 Humble on rmw_cyclonedds_cpp.

This reproduces in the harness's container, where multicast always works, and the same container passes every FastDDS pair while failing in Cyclone

@ewang360 found and root-caused these four defects on the robot. This PR carries her patch unchanged, adds the harness cases, and adds the measurements below.

Not in this PR: services over rmw_cyclonedds_cpp. rmw_cyclonedds_cpp correlates requests with a {guid, seq} header at the start of the payload (cdds_request_header_t). It doesn't use the related_sample_identity inline QoS that espp's services and rmw_fastrtps_cpp use. With this PR, both service pairs still fail on CycloneDDS: ros2 service call never gets a reply, and the espp client gets 0 back. That needs its own change, so the harness leaves the service pairs out of the CycloneDDS run.

How has this been tested?

Interop harness (components/rtps/interop/run.sh, Docker on an Apple Silicon Mac, ros:jazzy-ros-base, CycloneDDS 0.10.5):

existing checks CycloneDDS pub/sub (4 new) malformed packet lines
main 43 of 43 pass 0 of 4 pass yes
this PR 43 of 43 pass 4 of 4 pass none

The golden wire-format test still passes.

Hardware:

  • ESP32-P4 (rev v1.3), ESP-IDF v6.0.1, espp/rtps 1.3.10. The changed files are the same in 1.3.10 and main.
  • Jetson AGX Orin, Ubuntu 22.04, ROS 2 Humble installed natively (not in Docker), rmw_cyclonedds_cpp 1.3.4 with CycloneDDS 0.10.5.
  • The two are connected through a Netgear GS308E switch.
  • The board publishes a best-effort status topic at 2 Hz and subscribes to a best-effort joystick topic.

Each row is one flash of the board. The full patch and stock were each run 3 times, with the same result each time.

espp/rtps board → ROS 2 (ros2 topic echo) ROS 2 → board (status lines showing the published value, of ~84) malformed packet on the Jetson
1.3.10 stock nothing (3 of 3 runs) 0 (3 of 3) yes
this PR samples arrive (3 of 3) 47 (3 of 3) none
this PR without the ENTITYID_UNKNOWN fix nothing 0 none
this PR without INFO_DST and the reader id nothing 0 none
this PR without the locator fallback nothing 24 none
this PR without base 1 samples arrive 48 yes

So the first, second and fourth fixes are each needed for delivery. Base 1 is what removes the malformed packet warnings, and it's what the spec requires.

Screenshots (if appropriate, e.g. schematic, board, console logs, lab pictures):

On the Jetson, against stock 1.3.10:

recvUC: malformed packet received from vendor 13.37 length 48 state parse:shortmsg <52545053 02020d25 e00af6e8 0d00c4bc 5aa50025 06011800 c7030000 c2030000 @48 > (note: maybe partially bswap'd)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update
  • Hardware (schematic, board, system design) change
  • Software change

Checklist:

  • My change requires a change to the documentation.
  • I have added / updated the documentation related to this change via either README or WIKI

Software

  • I have added tests to cover my changes.
  • I have updated the .github/workflows/build.yml file to add my new test to the automated cloud build github action. (Not needed: the new cases are in the interop harness, which rtps_interop.yml already runs on every components/rtps/** change.)
  • All new and existing tests passed.
  • My code follows the code style of this project. (Checked with clang-format 15: the pinned 14.0.6 has no arm64 wheel.)

🤖 Generated with Claude Code

alexchunlin and others added 2 commits October 29, 2026 18:17
The interop matrix only ran the ROS 2 pairs on rmw_fastrtps_cpp. Add
rmw_cyclonedds_cpp to the harness image and run the pub/sub pairs (reliable and
best-effort, both directions) again on CycloneDDS. Services are left out:
rmw_cyclonedds_cpp correlates requests with a {guid, seq} header in the
payload, not the related_sample_identity inline QoS espp uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four defects stopped samples between espp and a CycloneDDS participant
(ROS 2 rmw_cyclonedds_cpp). FastDDS tolerates all four.

- HEARTBEAT and GAP addressed to readerId ENTITYID_UNKNOWN were dropped.
  CycloneDDS addresses its SEDP and user-data heartbeats that way, so espp
  never NACKed and never received SEDP data. Resolve the reader through the
  matched writer, as the DATA path already does.
- ACKNACKs had no INFO_DST and echoed the incoming readerId. CycloneDDS
  resolves the writer by (destination prefix, writerId), so it ignored them.
  Add INFO_DST and use the reader's own entity id.
- The pre-emptive ACKNACK used sequence base 0. RTPS 8.3.5.5 requires >= 1;
  CycloneDDS patches base 0 only for eProsima and RTI vendor ids and logs
  "malformed packet received from vendor 13.37" for espp.
- SEDP samples without PID_UNICAST_LOCATOR (CycloneDDS omits it) kept the
  TopicData constructor's placeholder address. Reset the locators before
  parsing and fall back to the participant's default unicast locator.

Co-authored-by: ewang360 <61071212+ewang360@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

✅Static analysis result - no issues found! ✅

@alexchunlin
alexchunlin requested a review from finger563 October 29, 2026 22:32
cppcheck useStlAlgorithm on SEDPAgent.cpp:114.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alexchunlin
alexchunlin merged commit 6fe3c4f into esp-cpp:main Oct 30, 2026
182 checks passed
alexchunlin added a commit to rammp-org/RAMMP-mib-fw that referenced this pull request Oct 30, 2026
esp-cpp/espp#862 is in 1.3.11. With 1.3.10 and older the Jetson (ROS 2 on
rmw_cyclonedds_cpp) and the MIB exchange no samples. docs/ros2.md follows, and lists
PUSH_ASSIST with the states reported as ENABLED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants