CARLA
Loading...
Searching...
No Matches
ZenohPublisherMiddleware.h
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB).
2// This work is licensed under the terms of the MIT license.
3// For a copy, see <https://opensource.org/licenses/MIT>.
4
5#pragma once
6
12#include "carla/Logging.h"
13
14#include <atomic>
15#include <mutex>
16#include <string>
17#include <vector>
18
19#ifndef CARLA_ROS2_MIDDLEWARE_TESTING
20#ifdef CARLA_ROS2_MIDDLEWARE_ZENOH
21
22#include <zenoh.h>
23
24namespace carla {
25namespace ros2 {
26
27/// Zenoh implementation of IPublisherMiddleware, compatible with rmw_zenoh.
28///
29/// Serializes msg::* POD structs to CDR via CdrSerialization.h and publishes
30/// them on a keyexpr formatted per the rmw_zenoh design doc:
31/// "<domain>/<topic>/<type>/<hash>". A liveliness token is declared so the
32/// endpoint shows up in rmw_zenoh's graph cache.
33///
34/// Parameterized on a traits type T that provides:
35/// T::msg_type — the message type (a carla::ros2::msg::* POD struct)
36template<typename T>
37class ZenohPublisherMiddleware : public IPublisherMiddleware {
38 public:
39 using msg_type = typename T::msg_type;
40
41 ZenohPublisherMiddleware() {
42 // Gravestone owned handles so destructor's z_drop is safe if Init never ran.
43 z_internal_null(&_publisher);
44 z_internal_null(&_liveliness_token);
45 z_internal_null(&_matching_listener);
46 }
47 ZenohPublisherMiddleware(const ZenohPublisherMiddleware&) = delete;
48 ZenohPublisherMiddleware& operator=(const ZenohPublisherMiddleware&) = delete;
49
50 ~ZenohPublisherMiddleware() override {
51 // zenoh-c's z_drop on a publisher does not wait for in-flight z_publisher_put
52 // calls (unlike FastDDS/CycloneDDS writers, which synchronize internally), so
53 // serialize against Publish() to avoid a use-after-free when a render-thread
54 // publish races with sensor teardown on the main thread.
55 std::lock_guard<std::mutex> lock(_publish_mutex);
56 z_drop(z_move(_matching_listener));
57 z_drop(z_move(_liveliness_token));
58 z_drop(z_move(_publisher));
59 }
60
61 bool Init(const std::string& topic_name) override {
62 return InitImpl(topic_name, kZenohDefaultQos);
63 }
64
65 bool Init(const std::string& topic_name, const QosProfile& qos) override {
66 // The profile is advertised in the liveliness token so rmw_zenoh peers see
67 // a compatible endpoint; zenoh delivery semantics are not differentiated
68 // (see zenoh_qos_keyexpr).
69 return InitImpl(topic_name, zenoh_qos_keyexpr(qos));
70 }
71
72 private:
73 bool InitImpl(const std::string& topic_name, const std::string& qos_str) {
74 const z_loaned_session_t* session = zenoh_get_shared_session();
75
76 const std::string topic_no_rt = zenoh_strip_rt_prefix(topic_name);
77 const char* type_name = CdrTopicInfo<msg_type>::type_name();
78 const char* type_hash = CdrTopicInfo<msg_type>::type_hash();
79 if (type_hash == nullptr) {
80 log_error("ZenohPublisherMiddleware: no type_hash for topic '",
81 topic_name, "'");
82 return false;
83 }
84
85 const std::string ke_str = zenoh_make_topic_keyexpr(
86 zenoh_ros_domain_id(), topic_no_rt, type_name, type_hash);
87 z_view_keyexpr_t ke;
88 if (z_view_keyexpr_from_str(&ke, ke_str.c_str()) != Z_OK) {
89 log_error("ZenohPublisherMiddleware (", topic_name,
90 "): invalid keyexpr '", ke_str, "'");
91 return false;
92 }
93
94 z_publisher_options_t pub_opts;
95 z_publisher_options_default(&pub_opts);
96 if (z_declare_publisher(session, &_publisher, z_loan(ke), &pub_opts)
97 != Z_OK) {
98 log_error("ZenohPublisherMiddleware (", topic_name,
99 "): z_declare_publisher failed");
100 return false;
101 }
102
103 const std::string lv_ke_str = zenoh_make_topic_liveliness_keyexpr(
104 zenoh_ros_domain_id(), zenoh_session_zid(), zenoh_next_entity_id(),
105 kZenohEntityKindPub, topic_no_rt, type_name, type_hash,
106 qos_str.c_str());
107 z_view_keyexpr_t lv_ke;
108 if (z_view_keyexpr_from_str(&lv_ke, lv_ke_str.c_str()) != Z_OK) {
109 log_error("ZenohPublisherMiddleware (", topic_name,
110 "): invalid liveliness keyexpr");
111 return false;
112 }
113 if (z_liveliness_declare_token(session, &_liveliness_token,
114 z_loan(lv_ke), nullptr) != Z_OK) {
115 log_error("ZenohPublisherMiddleware (", topic_name,
116 "): z_liveliness_declare_token failed");
117 return false;
118 }
119
120 z_owned_closure_matching_status_t closure;
121 z_closure_matching_status(&closure, &on_matching_status, nullptr, this);
122 if (z_publisher_declare_matching_listener(
123 z_loan(_publisher), &_matching_listener, z_move(closure))
124 != Z_OK) {
125 log_error("ZenohPublisherMiddleware (", topic_name,
126 "): z_publisher_declare_matching_listener failed");
127 return false;
128 }
129
130 _topic_name = topic_name;
131 return true;
132 }
133
134 public:
135 bool Publish(void* message_data) override {
136 std::lock_guard<std::mutex> lock(_publish_mutex);
137 if (!z_internal_check(_publisher)) {
138 return false;
139 }
140
141 const msg_type* msg = static_cast<const msg_type*>(message_data);
142 std::vector<uint8_t> cdr = serialize_to_cdr(*msg);
143
144 z_owned_bytes_t payload;
145 z_bytes_copy_from_buf(&payload, cdr.data(), cdr.size());
146
147 const std::vector<uint8_t> attach = zenoh_attachment();
148 z_owned_bytes_t attachment;
149 z_bytes_copy_from_buf(&attachment, attach.data(), attach.size());
150
151 z_publisher_put_options_t opts;
152 z_publisher_put_options_default(&opts);
153 opts.attachment = z_move(attachment);
154
155 if (z_publisher_put(z_loan(_publisher), z_move(payload), &opts) != Z_OK) {
156 log_error("ZenohPublisherMiddleware::Publish (", _topic_name,
157 "): z_publisher_put failed");
158 return false;
159 }
160 return true;
161 }
162
163 bool IsAlive() const override {
164 return _alive.load(std::memory_order_relaxed);
165 }
166
167 std::string GetTopicName() const override {
168 return _topic_name;
169 }
170
171 private:
172 static void on_matching_status(const z_matching_status_t* status,
173 void* context) {
174 auto* self = static_cast<ZenohPublisherMiddleware*>(context);
175 self->_alive.store(status->matching, std::memory_order_relaxed);
176 }
177
178 z_owned_publisher_t _publisher;
179 z_owned_liveliness_token_t _liveliness_token;
180 z_owned_matching_listener_t _matching_listener;
181
182 mutable std::mutex _publish_mutex;
183 std::atomic<bool> _alive { false };
184 std::string _topic_name;
185};
186
187} // namespace ros2
188} // namespace carla
189
190#endif // CARLA_ROS2_MIDDLEWARE_ZENOH
191#endif // !CARLA_ROS2_MIDDLEWARE_TESTING
std::vector< uint8_t > serialize_to_cdr(const T &msg)
Serialize a msg::X to a CDR byte buffer including the DDS encapsulation header (Classic CDR,...
This file contains definitions of common data structures used in traffic manager.