|
nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
|
#include <publisher.hpp>
Classes | |
| class | Loan |
Public Types | |
| using | ConstSharedPtr = ::rclcpp::Owned<Publisher<M>> |
| using | SharedPtr = ::rclcpp::Owned<Publisher<M>> |
| using | UniquePtr = ::rclcpp::Owned<Publisher<M>> |
Public Member Functions | |
| Result | assert_liveliness () |
| ::rclcpp::QoS | get_actual_qos () const |
| Result | get_gid (uint8_t(&out_gid)[GID_SIZE]) const |
| Result | get_subscription_count (::rclcpp::Executor &executor, size_t *out_count) const |
| const char * | get_topic_name () const |
| Get the topic name. | |
| bool | is_durability_transient_local () const |
| bool | is_valid () const |
| Check if the publisher is initialized and valid. | |
| ::rclcpp::ResultOf< Loan > | loan (size_t requested_len) |
| Result | on_incompatible_type (IncompatibleTypeCallbackType cb, void *user_context=nullptr) |
| Result | on_liveliness_lost (nros_cpp_publisher_count_cb_t cb, void *user_context=nullptr) |
| Result | on_matched (PublisherMatchedCallbackType cb, void *user_context=nullptr) |
| Result | on_offered_deadline_missed (uint32_t deadline_ms, nros_cpp_publisher_count_cb_t cb, void *user_context=nullptr) |
| Publisher & | operator= (Publisher &&other) |
| Result | publish (const M &msg) |
| Result | publish_raw (const uint8_t *data, size_t len) |
| Renamed to publish_serialized (phase-515 C8); kept one release. | |
| Result | publish_serialized (const uint8_t *data, size_t len) |
| template<typename W> | |
| Result | publish_streamed (size_t total_len, W &&writer) |
| Publisher () | |
| Publisher (Publisher &&other) | |
| ~Publisher () | |
| Destructor — releases publisher resources. | |
Static Public Attributes | |
| static constexpr size_t | GID_SIZE = 24 |
Friends | |
| class | ::rclcpp::Node |
Typed publisher for a ROS 2 topic.
Mirrors upstream's rclcpp::Publisher<M>. The message type M must provide TYPE_NAME, TYPE_HASH, and ffi_publish() (generated by codegen).
Serialized (CDR) publishing is always available via publish_serialized().
Inline storage holds the runtime publisher handle directly (NROS_PUBLISHER_SIZE bytes; size auto-derived from the runtime) plus one pointer: the contracted endpoint's monitor cell (phase-462 W1, RFC-0052), which the runtime resolves at create time against the table the entry installed with nros_cpp_install_monitors and bumps on every publish, exactly as the Rust EmbeddedPublisher does. Null on an uncontracted publisher. Topic name metadata lives C++-side in topic_name_, avoiding a runtime hop for get_topic_name().
WHY THIS ONE IS NOT AN ARENA HANDLE — phase-456 W4
Every other entity in this API is moving to a two-word handle over the Rust executor arena, because the arena already owns the entity and the C++ object was a second copy of state. A publisher is the exception, decided rather than left over.
The arena holds only things the executor DISPATCHES to — its EntryKind is subscription, service, client, timer, action server, action client, guard condition — and a publisher is none of them. The Rust API says the same by construction: Node::create_publisher_with_qos hands back an EmbeddedPublisher<M> BY VALUE, a caller-owned value with a Drop. So the phase's principle, that entity lifetime is a Rust data structure's, is already satisfied here, and rclcpp::Owned<Publisher<M>> mirrors that structure exactly.
It was also argued that it could not be moved without losing something — the arena then had no removal path, so an arena publisher could never have been destroyed before its executor. 9768795b1d (issue 1496) retired that argument; the one above, that an arena slot would make C++ diverge from the Rust lifetime, is what carries the decision.
Measured, so the question does not get re-opened on a size argument: sizeof(Publisher<M>) is 872 bytes and INDEPENDENT of M — identical for a 512-byte-bound string, an 8-byte Int32 and a 65 552-byte image. It is storage_ 608 + topic_name_ 256 + initialized_ + padding. Nothing here is a transmit buffer sized from the type's bound: serialisation goes into the backend's outbound buffer, and publish_streamed stages on the stack. That is what separates this type from the 888-byte Subscription<M> whose storage_ went unused on the dispatch path, and from the 4 672-byte Client<int> that carried a reply buffer.
Usage:
| using rclcpp::Publisher< M >::ConstSharedPtr = ::rclcpp::Owned<Publisher<M>> |
rclcpp::Publisher<M>::ConstSharedPtr — the SAME type as SharedPtr.
Owned<const T> is refused by a static_assert in owned.hpp: it declares cleanly and is ill-formed on the first move or reset(), which is worse than a refusal. A const/mutable distinction drawn over a handle presupposes shared ownership, which a sole owner does not have; const SharedPtr& already yields the const Publisher<M>* view.
| using rclcpp::Publisher< M >::SharedPtr = ::rclcpp::Owned<Publisher<M>> |
rclcpp::Publisher<M>::SharedPtr — phase-456 W5.
rclcpp::Publisher<M>::SharedPtr member_; is close to universal in ported source, so this alias must exist on EVERY target — which std::shared_ptr does not. It names rclcpp::Owned<Publisher<M>>: the publisher BY VALUE, with operator-> so every ported pub_->publish(m) keeps working, and no allocator, control block or <memory> anywhere.
IT IS NOT AN ARENA HANDLE, and that is a decision rather than an omission — W4 measured the case and refused it. It was first argued from the arena having no removal path (EntryKind has no publisher at all), so that ~Publisher() and Owned<T>::reset() would become no-ops; that argument expired with 9768795b1d (issue 1496). The Rust side is what settles it: create_publisher_with_qos returns an EmbeddedPublisher<M> BY VALUE, with a Drop, and this phase's principle is that the C++ lifetime mirrors the Rust one. owned.hpp carries the long form.
WHAT CHANGED FOR A PORTED FILE: one reference, not a shared count. The entity is destroyed when the holder is, which is what upstream does when the holder was the last owner — and the W0 census found no entity handle copied anywhere in this tree or the porting corpus.
| using rclcpp::Publisher< M >::UniquePtr = ::rclcpp::Owned<Publisher<M>> |
rclcpp::Publisher<M>::UniquePtr — also the SAME type as SharedPtr.
Owned<T> IS unique ownership — move-only, one owner, destroys on scope exit — so a separate alias would be a second spelling of one type. Measured before the collapse (phase-456 W5): zero uses in the tree outside this definition and the one probe asserting its old shape.
|
inline |
Destructor — releases publisher resources.
|
inline |
|
inline |
Default constructor — creates an uninitialized publisher. Use Node::create_publisher() to initialize.
|
inline |
Phase 108.B.7 — manually assert liveliness. Required for publishers configured with LivelinessPolicy::ManualByTopic / ManualByNode; nothing to assert for Automatic / None, which answer Ok on every backend.
ErrorCode::Unsupported under a manual kind on a backend with no per-topic lease — zenoh-pico, XRCE-DDS, uORB (the phase-467 RMW gap-closure design study's Row 7). The entity is fine; the assertion simply did not reach a peer, which is the one thing a caller uses this for. cyclonedds performs it for real.
|
inline |
The QoS profile this publisher is ACTUALLY running — issue 1437.
rclcpp::Publisher::get_actual_qos: what the BACKEND GRANTED, which differs from what create_publisher was handed whenever the backend serves something else — zenoh clamps history depth to its receive ring, DDS negotiates, and a launch-lowered QoS override has already been folded in before either. That difference is what answers "why is nothing arriving": a RELIABLE reader does not match a BEST_EFFORT writer, and a clamped depth drops samples the caller believed kept.
Per policy, and a policy the backend cannot report is an ABSENCE — ReliabilityPolicy::Unknown, DurabilityPolicy::Unknown, ... — not your request echoed back. A backend with no read-back at all (XRCE) answers Unknown in every field. So compare field by field and check for the sentinel first; agreement you did not check for may be silence.
Free: read once at create and retained on the handle, so this never re-enters the transport and is safe from a callback. An uninitialized publisher answers the all-absent profile.
|
inline |
This publisher's own global identifier — rclcpp::PublisherBase::get_gid.
The phase-467 RMW gap-closure design study's Q1. Fills out_gid with [GID_SIZE](GID_SIZE) bytes on Ok, and writes nothing otherwise.
Upstream returns const rmw_gid_t & and cannot fail. Ours can — a backend may have no identity for this publisher — and RFC-0018 leaves no exceptions to say so with, so the value leaves through a reference to a caller-placed array and the verdict through the Result. An array REFERENCE rather than a pointer is what makes the width a compile-time fact at every call site.
Three bounds, each of which a caller will otherwise assume away:
|
inline |
How many subscriptions are on this publisher's topic, RIGHT NOW — rclcpp's Publisher::get_subscription_count. phase-444.
A WEAKER QUESTION THAN UPSTREAM'S, and it is stated here rather than discovered. Upstream counts the subscriptions MATCHED to THIS publisher — peers whose QoS is compatible with it. This counts every subscription DISCOVERED on the topic, matched or not, because that is what the graph can answer and a backend with no QoS negotiation cannot tell the two apart at all.
Non-inverting, and in the safe direction: a topic nobody subscribes to still reads 0, so the use this exists for — skip the work when nobody is listening — is answered exactly. An INCOMPATIBLE peer makes it read 1 where upstream reads 0, i.e. you do work nobody receives. Never the reverse.
Takes the executor and an out-parameter, neither of which upstream does. The executor because we open ONE transport session per image and it owns the session, so the graph is the executor's to read — the same receiver difference as every Executor::get_* graph verb (phase-381 W4). The out-parameter because RFC-0018 forbids exceptions, so the error has to be the return value. Both weakenings are ones the compiler makes a porter notice, which is RFC-0089's mechanical-edit test.
Reports what has been DISCOVERED and never blocks, so a small answer right after startup is "not seen yet", never "not there"; a backend that cannot read the graph returns an error, which is a DIFFERENT answer from 0 and leaves *out_count untouched.
|
inline |
Get the topic name.
|
inline |
rclcpp::PublisherBase::is_durability_transient_local() (Jazzy) – whether this publisher keeps samples for late-joining subscribers. issue 1975.
Read off get_actual_qos(), as upstream reads it off rcl_publisher_get_actual_qos: the GRANTED durability, not the requested one. Its envelope is that accessor's: a backend that cannot report durability (XRCE) answers DurabilityPolicy::Unknown, and Unknown is not TRANSIENT_LOCAL, so this reads false there – an absence, never your request echoed back. An uninitialized publisher answers false.
|
inline |
Check if the publisher is initialized and valid.
|
inline |
Loan a writable slot of at least requested_len bytes. The returned Loan is RAII: call commit(actual_len) to send, discard() to abandon, or let it drop (auto-discard).
Zero-copy only where the backend lends, which is zenoh alone; on Cyclone, XRCE and uORB this returns an error, permanently and in every build (issue 0814) — publish with publish() or publish_streamed() there.
|
inline |
Issue 1972 — rclcpp's PublisherEventCallbacks::incompatible_type_callback: a subscription on this topic NAME with a different type was seen.
|
inline |
Register a callback for liveliness-lost events on this publisher — rclcpp's PublisherEventCallbacks::liveliness_callback (issue 1993). cb(storage, count status, user_context), observed on this publisher's poll points (publish, assert_liveliness()). ErrorCode::Unsupported on a backend that does not generate the event (XRCE-DDS, uORB); ErrorCode::Full when the process-wide event-slot table (16 registrations, C and C++ together) is full.
|
inline |
Issue 1972 — rclcpp's PublisherEventCallbacks::matched_callback: cb(storage, MatchedInfo, user_context) when a subscription starts or stops matching this publisher.
Observed on this publisher's poll points — publish and assert_liveliness() — on both backends that carry it (Cyclone DDS, zenoh); a publisher waiting for a listener calls assert_liveliness() in its wait loop. ErrorCode::Unsupported on XRCE-DDS and uORB. Registering again replaces the earlier callback; the registration follows the publisher across a move.
|
inline |
Register a callback for offered-deadline-missed events on this publisher — rclcpp's PublisherEventCallbacks::deadline_callback. deadline_ms reaches the backend's registration: zenoh uses it when the QoS states no deadline, Cyclone DDS takes the QoS deadline. Same returns as on_liveliness_lost.
|
inline |
|
inline |
Publish a typed message.
Calls the codegen-generated M::ffi_publish() which serializes the message to CDR and publishes it.
phase-417 stage 3 — the initialized_ guard. publish was the ONLY entry point on this class without one, and the one that matters most: M::ffi_publish reaches nros_cpp_publish_raw, whose only check is storage.is_null(), and storage_ is an in-object array that is never null. So an uninitialised publisher reinterpreted zero-initialised bytes as an RmwPublisher — silently wrong, then UB, with no diagnostic at any warning level. That is reachable straight from create_publisher, which returns a Result a ported node may not read and leaves out untouched on failure.
Returns Result, where upstream returns void and throws: no exceptions here (RFC-0018). An uninitialised publisher answers NotInitialized and sends nothing. In freestanding code Result is [[nodiscard]], so a dropped one warns. On the std surface (ported code) the result may be dropped, as upstream's void is, and a failure is logged instead (detail::PublishResult, phase-488).
|
inline |
Renamed to publish_serialized (phase-515 C8); kept one release.
|
inline |
Publish CDR bytes — rclcpp's publish(const SerializedMessage&).
phase-515 C8: renamed from publish_raw, so the transmit verb reads as the receive side's take_serialized does. ADOPT-BOUNDED: the payload is a (data, len) pair, not an rclcpp::SerializedMessage, which owns a growable buffer through an allocator this API does not have.
|
inline |
Phase 124.E.1 — streamed publish.
total_len declares the full payload size; writer is invoked one or more times with a writable chunk and must return the bytes written. The runtime/backend allocates total_len bytes (in the outbound buffer when the backend natively streams, otherwise in a stack staging buffer capped at ~4 KiB) and drains the closure until full.
writer signature: size_t(uint8_t* chunk, size_t cap).
|
friend |
|
staticconstexpr |
Width of the buffer get_gid() fills — upstream's RMW_GID_STORAGE_SIZE, the phase-467 RMW gap-closure design study's Q1(a). Named here so a call site spells the size once and the compiler checks it, rather than copying a 24 that has already moved once.