feat(rtps_embedded): Typed Publisher<T>/Subscriber<T> pub/sub (Phase 5) - #709
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
✅Static analysis result - no issues found! ✅ |
There was a problem hiding this comment.
Pull request overview
Adds a header-only typed pub/sub facade (espp::Publisher<T> / espp::Subscriber<T>) on top of espp::RtpsParticipant in components/rtps_embedded, enabling applications to publish/receive reflectable structs directly (no manual CDR byte handling), plus host-side and ESP32 example validation.
Changes:
- Introduces
components/rtps_embedded/include/rtps_pubsub.hppwithRtpsMessageconcept and typedPublisher<T>/Subscriber<T>wrappers. - Updates the
rtps_embeddedESP32 example to use the typed API instead of manual CDR serialization. - Adds a new host PC test
pc/tests/rtps_typed_pubsub.cppand exposes the new header vialib/include/espp.hpp.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| pc/tests/rtps_typed_pubsub.cpp | New host-side end-to-end typed pub/sub test validating round-trip of a multi-field struct. |
| lib/include/espp.hpp | Adds rtps_pubsub.hpp to the umbrella include set. |
| components/rtps_embedded/include/rtps_pubsub.hpp | New typed pub/sub layer (concept + Publisher/Subscriber + ROS 2 topic helper). |
| components/rtps_embedded/example/main/main.cpp | Migrates the ESP32 example from manual CDR byte handling to typed pub/sub. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- rtps_pubsub.hpp: include <concepts>/<cstddef>/<memory>/<mutex> (the concept and classes used them via transitive includes only) — Copilot. - Subscriber: capture a shared_ptr copy of the callback instead of `this`, so destroying the Subscriber while the participant still runs cannot dangle the engine-invoked callback (use-after-free) — Copilot. Documented the lifetime contract (stop the participant before tearing down callback-referenced state). - Publisher::publish: guard the reused serialization buffer with a mutex so concurrent publish() from multiple threads is not a data race — Copilot. - rtps_typed_pubsub.cpp: default-initialize Telemetry members (uninitMemberVarNoCtor) and use std::any_of instead of a raw loop (useStlAlgorithm) — the cppcheck findings failing static_analysis. Verified: cppcheck clean on both files (CI args); typed test PASS (sent=5 received=5 with field verification); golden byte-identical; host lib builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed all review comments + the static_analysis failure in 833bb2d:
Verified: cppcheck clean on both files with the CI args; typed test passes (sent=5 received=5 with field-level verification); golden byte-identical. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
Suppressed comments (3)
components/rtps_embedded/include/rtps_pubsub.hpp:28
- The
RtpsMessageconcept doesn’t currently constrain the parts of thecdrAPI thatPublisher<T>actually uses (cdr::serialize_into<cdr::xcdr1>(sample, buffer_)) nor does it constrain the deserialize return type. Tightening the concept to requireserialize_into(and to require thatdeserializereturns an optional/expected-like type holdingT) will produce clearer compile-time errors and prevent types that only partially satisfy the typed pub/sub contract from compiling untilpublish()is instantiated.
template <typename T>
concept RtpsMessage = requires(const T &value, std::span<const std::byte> bytes) {
{ cdr::serialized_size<cdr::xcdr1>(value) } -> std::convertible_to<std::size_t>;
{cdr::deserialize<T>(bytes)};
};
components/rtps_embedded/include/rtps_pubsub.hpp:185
- As written,
topic_name()will generatert//fooif callers pass a canonical ROS topic like/foo, and will double-prefix if callers pass an already-mangled DDS name likert/foo. If this helper is meant to be turnkey, consider normalizing input by stripping a leading/and returning the input unchanged when it already starts withrt/.
inline std::string topic_name(std::string_view ros_topic) {
std::string out = "rt/";
out += ros_topic;
return out;
}
pc/tests/rtps_typed_pubsub.cpp:72
- A fixed
sleep_for(2s)can make this test flaky (slow CI machines, loaded hosts, or different network stacks may need more/less than 2 seconds). Prefer folding discovery/wait time into the existing deadline loop (e.g., start publishing immediately and rely on reliable delivery once matched, or poll/wait on an explicit 'matched' signal if the participant exposes one) so the test outcome depends onkDeadlinerather than a hard-coded pre-delay.
// Let SEDP match before publishing.
std::this_thread::sleep_for(2s);
… review Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed the second review round + added the Python typed layer (f75157a): Review comments (round 2):
Python typed pub/sub (answering the "what's the Python equivalent?" discussion): since Verified: cppcheck clean; C++ + Python typed tests pass (fields verified); golden byte-identical; esp32 example builds. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.
Suppressed comments (4)
lib/python_bindings/espp/rtps.py:128
- Unlike
Publisher, this wrapper does not retainparticipant. If the caller releases its last participant reference while keeping the subscriber, the underlying participant is destroyed/stopped whilesub.validremains true. Store the participant onselfso the subscription keeps its transport alive.
deser = deserialize if deserialize is not None else (lambda data: message_type.deserialize(data))
components/rtps_embedded/include/rtps_pubsub.hpp:41
- The public example does not compile as written because
Reliabilityis nested underRtpsParticipantand no alias is introduced in this snippet. Qualify the enum so users can copy the example directly.
/// .reliability = Reliability::RELIABLE});
components/rtps_embedded/include/rtps_pubsub.hpp:30
- This new public API is not listed in
doc/Doxyfile'sINPUT, so its Doxygen documentation will not be generated or exposed with the rest of the library API. Addcomponents/rtps_embedded/include/rtps_pubsub.hppin alphabetical order and update the component documentation for the typed layer.
/// @brief Typed publisher: publish reflectable message structs on a topic.
lib/python_bindings/espp/rtps.py:67
- The PR description says Python remains on the byte API and that the typed layer is C++-only, but this adds and publicly exports a Python typed
Publisher/SubscriberAPI and converts the Python examples to it. Update the PR description so its stated scope and compatibility claims match the code being reviewed.
class Publisher:
…type name Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed the new comments in 68fbf9b:
The earlier aliasing / |
Phase 5 (typed pub/sub) of the
components/rtps_embeddedrefactor — seeREFACTOR_PLAN.md. Independent of and parallel to the Phase 4 (limits) work.What
A header-only typed layer over
espp::RtpsParticipantso applications publish/receive reflectable message structs directly — no manual CDR (de)serialization or byte-span handling.RtpsMessageconcept: any struct the reflectioncdrcan (de)serialize qualifies.Publisher<T>serializes withcdr::serialize_intointo a reused buffer → zero steady-state allocation.espp::ros2::topic_name()helper for ROS 2 naming.Additive by design
The untyped span/byte API (
add_writer/add_reader/publish/on_sample) is unchanged and is exactly what the typed layer wraps. Existing code is unaffected; the golden wire tests are byte-identical.The esp32 example is converted to the typed API (cleaner than the manual-
cdr::serializeversion, and it verifies the header compiles with reflect-cpp on target). Python keeps the byte API — templates don't bind generically, so the typed layer is a C++ ergonomic layer.Verification
pc/tests/rtps_typed_pubsub: two participants exchange a multi-field struct (uint32+float+string) end-to-end over real SEDP/DATA, with field-level verification of the typed round-trip — PASS.esp32p4on ESP-IDF 6.0; host lib builds.🤖 Generated with Claude Code