CARLA
Loading...
Searching...
No Matches
ZenohSubscriberMiddleware.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 <cstddef>
16#include <cstdint>
17#include <string>
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 ISubscriberMiddleware.
28///
29/// Declares a subscriber on the rmw_zenoh-compatible keyexpr
30/// "<domain>/<topic>/<type>/<hash>". Each received sample is deserialized from CDR
31/// directly into the caller-supplied msg::* POD struct (no extra copy), and
32/// a liveliness token is declared so the endpoint appears in rmw_zenoh's
33/// graph cache.
34///
35/// Parameterized on a traits type S that provides:
36/// S::msg_type — the message type (a carla::ros2::msg::* POD struct)
37template<typename S>
38class ZenohSubscriberMiddleware : public ISubscriberMiddleware {
39 public:
40 using msg_type = typename S::msg_type;
41
42 ZenohSubscriberMiddleware() {
43 // Gravestone owned handles so destructor's z_drop is safe if Init never ran.
44 z_internal_null(&_subscriber);
45 z_internal_null(&_liveliness_token);
46 }
47 ZenohSubscriberMiddleware(const ZenohSubscriberMiddleware&) = delete;
48 ZenohSubscriberMiddleware& operator=(const ZenohSubscriberMiddleware&) = delete;
49
50 ~ZenohSubscriberMiddleware() override {
51 // zenoh-c's subscriber drop blocks until any in-flight callback returns,
52 // so it is safe to let `*_message_ptr` go out of scope after this.
53 z_drop(z_move(_liveliness_token));
54 z_drop(z_move(_subscriber));
55 }
56
57 bool Init(
58 const std::string& topic_name,
59 void* message_ptr,
60 bool* new_message_flag) override
61 {
62 return InitImpl(topic_name, message_ptr, new_message_flag, kZenohDefaultQos);
63 }
64
65 bool Init(
66 const std::string& topic_name,
67 void* message_ptr,
68 bool* new_message_flag,
69 const QosProfile& qos) override
70 {
71 // The profile is advertised in the liveliness token so rmw_zenoh peers see
72 // a compatible endpoint; zenoh delivery semantics are not differentiated
73 // (no querying-subscriber, so TRANSIENT_LOCAL late-join latching is not
74 // provided — see zenoh_qos_keyexpr).
75 return InitImpl(topic_name, message_ptr, new_message_flag,
76 zenoh_qos_keyexpr(qos));
77 }
78
79 private:
80 bool InitImpl(
81 const std::string& topic_name,
82 void* message_ptr,
83 bool* new_message_flag,
84 const std::string& qos_str)
85 {
86 _message_ptr = static_cast<msg_type*>(message_ptr);
87 _new_message_ptr = new_message_flag;
88
89 const z_loaned_session_t* session = zenoh_get_shared_session();
90
91 const std::string topic_no_rt = zenoh_strip_rt_prefix(topic_name);
92 const char* type_name = CdrTopicInfo<msg_type>::type_name();
93 const char* type_hash = CdrTopicInfo<msg_type>::type_hash();
94 if (type_hash == nullptr) {
95 log_error("ZenohSubscriberMiddleware: no type_hash for topic '",
96 topic_name, "'");
97 return false;
98 }
99
100 const std::string ke_str = zenoh_make_topic_keyexpr(
101 zenoh_ros_domain_id(), topic_no_rt, type_name, type_hash);
102 z_view_keyexpr_t ke;
103 if (z_view_keyexpr_from_str(&ke, ke_str.c_str()) != Z_OK) {
104 log_error("ZenohSubscriberMiddleware (", topic_name,
105 "): invalid keyexpr '", ke_str, "'");
106 return false;
107 }
108
109 z_owned_closure_sample_t closure;
110 z_closure_sample(&closure, &on_sample, nullptr, this);
111
112 z_subscriber_options_t sub_opts;
113 z_subscriber_options_default(&sub_opts);
114 if (z_declare_subscriber(session, &_subscriber, z_loan(ke),
115 z_move(closure), &sub_opts) != Z_OK) {
116 log_error("ZenohSubscriberMiddleware (", topic_name,
117 "): z_declare_subscriber failed");
118 return false;
119 }
120
121 const std::string lv_ke_str = zenoh_make_topic_liveliness_keyexpr(
122 zenoh_ros_domain_id(), zenoh_session_zid(), zenoh_next_entity_id(),
123 kZenohEntityKindSub, topic_no_rt, type_name, type_hash,
124 qos_str.c_str());
125 z_view_keyexpr_t lv_ke;
126 if (z_view_keyexpr_from_str(&lv_ke, lv_ke_str.c_str()) != Z_OK) {
127 log_error("ZenohSubscriberMiddleware (", topic_name,
128 "): invalid liveliness keyexpr");
129 return false;
130 }
131 if (z_liveliness_declare_token(session, &_liveliness_token,
132 z_loan(lv_ke), nullptr) != Z_OK) {
133 log_error("ZenohSubscriberMiddleware (", topic_name,
134 "): z_liveliness_declare_token failed");
135 return false;
136 }
137
138 // zenoh-c does not expose a publisher-matched signal on the subscriber
139 // side, so mark alive once declared. The publisher-side IsAlive() in
140 // ZenohPublisherMiddleware already gates wasted work.
141 _alive.store(true, std::memory_order_relaxed);
142 _topic_name = topic_name;
143 return true;
144 }
145
146 public:
147 bool IsAlive() const override {
148 return _alive.load(std::memory_order_relaxed);
149 }
150
151 std::string GetTopicName() const override {
152 return _topic_name;
153 }
154
155 private:
156 static void on_sample(z_loaned_sample_t* sample, void* context) {
157 auto* self = static_cast<ZenohSubscriberMiddleware*>(context);
158 const z_loaned_bytes_t* payload = z_sample_payload(sample);
159 z_owned_slice_t slice;
160 if (z_bytes_to_slice(payload, &slice) != Z_OK) {
161 log_error("ZenohSubscriberMiddleware (", self->_topic_name,
162 "): z_bytes_to_slice failed");
163 return;
164 }
165 const z_loaned_slice_t* ls = z_slice_loan(&slice);
166 const uint8_t* data = z_slice_data(ls);
167 const size_t len = z_slice_len(ls);
168 if (!deserialize_from_cdr(data, len, *self->_message_ptr)) {
169 log_error("ZenohSubscriberMiddleware (", self->_topic_name,
170 "): deserialization failed");
171 } else {
172 *self->_new_message_ptr = true;
173 }
174 z_drop(z_move(slice));
175 }
176
177 z_owned_subscriber_t _subscriber;
178 z_owned_liveliness_token_t _liveliness_token;
179
180 msg_type* _message_ptr { nullptr };
181 bool* _new_message_ptr { nullptr };
182
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
bool deserialize_from_cdr(const uint8_t *data, size_t size, T &msg)
Deserialize a msg::X from a CDR byte buffer that was produced by serialize_to_cdr() or by any ROS2-co...
This file contains definitions of common data structures used in traffic manager.