CARLA
Loading...
Searching...
No Matches
ROS2.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
7#include "carla/Logging.h"
8#include "carla/ros2/ROS2.h"
10#include "carla/geom/Vector3D.h"
19
21
42
48#if defined(WITH_ROS2_DEMO)
50#endif
51
52#include <cmath>
53#include <memory>
54#include <string>
55#include <utility>
56#include <vector>
57
58namespace carla {
59namespace ros2 {
60
61// static fields
62std::shared_ptr<ROS2> ROS2::_instance;
63
64// list of sensors (should be equal to the list of SensorsRegistry)
83 // Keep these in lock-step with the SensorRegistry tuple order: the fisheye /
84 // wide-angle-lens cameras (ported in #9741) were added to SensorRegistry but
85 // were missing here, which shifted every following value out of sync with the
86 // registry index.
94 // Autoware vehicle interface (appended at the END, matching the
95 // SensorRegistry append; V2X sensors have no ROS2 dispatch and therefore
96 // no enum entry, mirroring the pre-existing convention).
99};
100
101bool ROS2::Enable(bool enable, Middleware middleware, int domain_id) {
102 // Select the ROS 2 middleware before any publisher or subscriber is created.
103 // SetActiveMiddleware is the DDS-free bridge that resolves availability inside
104 // the shared library (the CARLA_ROS2_MIDDLEWARE_* macros are not visible here
105 // in carla-server) and keeps vendor headers out of carla-server.
106 if (enable && !SetActiveMiddleware(middleware)) {
107 log_error("ROS2: requested middleware '", MiddlewareToString(middleware),
108 "' is not available. Compiled in: ", GetAvailableMiddleware(), ".");
109 _enabled = false;
110 return false;
111 }
112 if (enable) {
113 // Configure the domain id before any transport context is created (the
114 // shared participants/sessions are created lazily on the first publisher,
115 // i.e. the clock publisher below). SetActiveDomainId is the DDS-free bridge
116 // so the value lands in the shared library's MiddlewareConfig, which the
117 // middlewares read.
118 const ResolvedDomainId resolved = SetActiveDomainId(domain_id);
119 const char* domain_source =
120 (resolved.source == DomainIdSource::CommandLine) ? "--ros-domain-id"
121 : (resolved.source == DomainIdSource::Environment) ? "ROS_DOMAIN_ID"
122 : "default";
123 log_info("ROS2: using middleware '", MiddlewareToString(middleware),
124 "', domain id: ", resolved.id, " (", domain_source, ")");
125 }
126 _enabled = enable;
127 log_info("ROS2 enabled: ", _enabled.load());
128 {
129 std::lock_guard<std::mutex> lock(_mutex);
130 _clock_publisher = std::make_shared<CarlaClockPublisher>();
131 }
132#if defined(WITH_ROS2_DEMO)
133 _basic_publisher = std::make_shared<BasicPublisher>();
134 _basic_publisher->Init();
135#endif
136 return true;
137}
138
139void ROS2::SetFrame(uint64_t frame) {
140 _frame = frame;
141 // The control callbacks run outside the lock so applying vehicle control
142 // never stalls the camera deliveries.
143 std::vector<std::pair<std::shared_ptr<BaseSubscriber>, ActorCallback>> pending;
144 {
145 std::lock_guard<std::mutex> lock(_mutex);
146 for (auto &element : _subscribers) {
147 auto callback_it = _actor_callbacks.find(element.first);
148 if (callback_it != _actor_callbacks.end()) {
149 pending.emplace_back(element.second, callback_it->second);
150 }
151 }
152 }
153 for (auto &element : pending) {
154 element.first->ProcessMessages(element.second);
155 }
156#if defined(WITH_ROS2_DEMO)
157 if (_basic_subscriber) {
158 void *actor = _basic_subscriber->GetActor();
159 if (!_basic_subscriber->IsAlive()) {
161 }
162 if (actor && _basic_subscriber->HasNewMessage()) {
163 auto it = _actor_message_callbacks.find(actor);
164 if (it != _actor_message_callbacks.end()) {
165 MessageControl control;
166 control.message = _basic_subscriber->GetMessage();
167 it->second(actor, control);
168 }
169 }
170 }
171#endif
172}
173
174void ROS2::SetTimestamp(double timestamp) {
175 double integral;
176 const double fractional = std::modf(timestamp, &integral);
177 const double multiplier = 1000000000.0;
178 const Stamp stamp{
179 static_cast<int32_t>(integral),
180 static_cast<uint32_t>(fractional * multiplier)};
181 std::shared_ptr<CarlaClockPublisher> clock_publisher;
182 {
183 std::lock_guard<std::mutex> lock(_mutex);
184 _stamp = stamp;
185 clock_publisher = _clock_publisher;
186 }
187 if (clock_publisher) {
188 clock_publisher->Write(stamp.seconds, stamp.nanoseconds);
189 clock_publisher->Publish();
190 }
191#if defined(WITH_ROS2_DEMO)
192 _basic_publisher->SetData("Hello from Carla!");
193 _basic_publisher->Publish();
194#endif
195}
196
198 std::lock_guard<std::mutex> lock(_mutex);
199 return _stamp;
200}
201
202void ROS2::DropPublishersLocked(void *actor) {
203 _publishers.erase(actor);
204 _camera_publishers.erase(actor);
205 _transforms.erase(actor);
206}
207
208bool ROS2::IsKnownActorLocked(void *actor) const {
209 return _registrations.count(actor) > 0 || HasTopicOverride(actor);
210}
211
213 void *actor, std::string ros_name, std::string frame_id, bool publish_tf) {
214 std::lock_guard<std::mutex> lock(_mutex);
215 // A new actor can reuse a destroyed one's address; never inherit its
216 // cached publishers.
218 // insert_or_assign so re-registering an actor with a new ros_name actually
219 // updates the entry; unordered_map::insert would silently keep the stale
220 // one.
221 _registrations.insert_or_assign(
222 actor, ActorRegistration{std::move(ros_name), std::move(frame_id), publish_tf});
223}
224
225void ROS2::UnregisterSensor(void *actor) {
226 std::lock_guard<std::mutex> lock(_mutex);
228}
229
232 _actor_parents.erase(actor);
233 _registrations.erase(actor);
234}
235
237 void *actor, std::string ros_name, std::string frame_id, ActorCallback callback,
238 bool enable_ackermann_control,
239 bool enable_autoware_control) {
240 std::lock_guard<std::mutex> lock(_mutex);
242 _registrations.insert_or_assign(
243 actor, ActorRegistration{ros_name, frame_id, true});
244
245 // Idempotency: drop any prior subscribers / callbacks bound to this actor
246 // so a re-registration does not accumulate duplicate DataReaders nor leave
247 // the previous callback wired.
248 _subscribers.erase(actor);
249 _autoware_vehicles.erase(actor);
250 _actor_callbacks.insert_or_assign(actor, std::move(callback));
251
252 // The legacy CarlaEgoVehicleControlSubscriber::Init built its topic as
253 // "rt/carla/" + [parent + "/"] + name + "/vehicle_control_cmd". With the
254 // new template constructors the suffix is appended inside each subscriber,
255 // so we hand them the base path only. BuildBaseTopicName also honors an
256 // exact-topic override registered via AddActorRosTopicName.
257 const std::string base_topic_name = BuildBaseTopicName(actor);
258
259 // The control modes are mutually exclusive: a vehicle listens on exactly one
260 // command surface, so two topics can never contend frame to frame. The
261 // Autoware command set wins over Ackermann (tier4 gave it priority over the
262 // ego control subscriber); Ackermann stays opt-in per youtalk's review.
263 if (enable_autoware_control) {
264 _autoware_vehicles.insert(actor);
265 _subscribers.insert({actor, std::make_shared<AutowareControlSubscriber>(actor, std::move(frame_id))});
266 } else if (enable_ackermann_control) {
267 _subscribers.insert({actor, std::make_shared<AckermannControlSubscriber>(actor, base_topic_name, std::move(frame_id))});
268 } else {
269 _subscribers.insert({actor, std::make_shared<CarlaEgoVehicleControlSubscriber>(actor, base_topic_name, std::move(frame_id))});
270 }
271}
272
273void ROS2::UnregisterVehicle(void *actor) {
274 std::lock_guard<std::mutex> lock(_mutex);
275 _subscribers.erase(actor);
276 _actor_callbacks.erase(actor);
277 _autoware_vehicles.erase(actor);
278 _autoware_status_publishers.erase(actor);
280}
281
282void ROS2::AddActorRosTopicName(void *actor, std::string ros_topic_name) {
283 std::lock_guard<std::mutex> lock(_mutex);
285 _actor_ros_topic_names.insert_or_assign(actor, std::move(ros_topic_name));
286}
287
289 std::lock_guard<std::mutex> lock(_mutex);
290 _actor_ros_topic_names.erase(actor);
291 // Drop the cached publishers bound to the overridden topic so the next data
292 // tick re-creates them under the default naming (tier4 semantics).
294}
295
296std::string ROS2::GetActorRosTopicName(void *actor) const {
297 std::lock_guard<std::mutex> lock(_mutex);
298 auto it = _actor_ros_topic_names.find(actor);
299 return it != _actor_ros_topic_names.end() ? it->second : std::string{};
300}
301
302bool ROS2::HasTopicOverride(void *actor) const {
303 auto it = _actor_ros_topic_names.find(actor);
304 return it != _actor_ros_topic_names.end() && !it->second.empty();
305}
306
307void ROS2::AddActorParentRosName(void *actor, void *parent) {
308 std::lock_guard<std::mutex> lock(_mutex);
309 auto it = _actor_parents.find(actor);
310 if (it != _actor_parents.end()) {
311 it->second.push_back(parent);
312 } else {
313 _actor_parents.insert({actor, {parent}});
314 }
315}
316
318 [[maybe_unused]] void *actor,
319 [[maybe_unused]] std::string ros_name,
320 [[maybe_unused]] ActorMessageCallback callback) {
321#if defined(WITH_ROS2_DEMO)
322 _actor_message_callbacks.insert_or_assign(actor, std::move(callback));
323 _basic_subscriber.reset();
324 _basic_subscriber = std::make_shared<BasicSubscriber>(actor, ros_name.c_str());
325 _basic_subscriber->Init();
326#endif
327}
328
329void ROS2::RemoveBasicSubscriberCallback([[maybe_unused]] void *actor) {
330#if defined(WITH_ROS2_DEMO)
331 _basic_subscriber.reset();
332 _actor_message_callbacks.erase(actor);
333#endif
334}
335
336std::string ROS2::LookupRosName(void *actor) const {
337 auto it = _registrations.find(actor);
338 return it != _registrations.end() ? it->second.ros_name : std::string{};
339}
340
341std::string ROS2::LookupFrameId(void *actor) const {
342 auto it = _registrations.find(actor);
343 return it != _registrations.end() ? it->second.frame_id : std::string{};
344}
345
346std::string ROS2::BuildParentChain(void *actor) const {
347 auto it = _actor_parents.find(actor);
348 if (it == _actor_parents.end()) {
349 return std::string{};
350 }
351 const std::string current_actor_name = LookupRosName(actor);
352 std::string parent_name;
353 for (auto *parent : it->second) {
354 const std::string name = LookupRosName(parent);
355 if (name.empty() || name == current_actor_name) {
356 continue;
357 }
358 parent_name = name + '/' + parent_name;
359 }
360 if (!parent_name.empty() && parent_name.back() == '/') {
361 parent_name.pop_back();
362 }
363 return parent_name;
364}
365
366std::string ROS2::BuildBaseTopicName(void *actor) const {
367 // Exact-topic override (ros_topic_name attribute flow): a non-empty custom
368 // name replaces the whole "rt/carla/[parent/]ros_name" convention. Suffixes
369 // for multi-topic sensors are appended by the publishers on top of this.
370 auto override_it = _actor_ros_topic_names.find(actor);
371 if (override_it != _actor_ros_topic_names.end() && !override_it->second.empty()) {
372 const std::string &custom = override_it->second;
373 std::string base_topic_name = "rt";
374 if (custom.front() != '/') {
375 base_topic_name += '/';
376 }
377 base_topic_name += custom;
378 return base_topic_name;
379 }
380
381 const std::string ros_name = LookupRosName(actor);
382 if (ros_name.empty()) {
383 return std::string{};
384 }
385 const std::string parent_chain = BuildParentChain(actor);
386 std::string base_topic_name = "rt/carla/";
387 if (!parent_chain.empty()) {
388 base_topic_name += parent_chain + "/";
389 }
390 base_topic_name += ros_name;
391 return base_topic_name;
392}
393
395 void *actor,
396 const std::string &prefix,
398 auto it = _registrations.find(actor);
399 if (it == _registrations.end()) {
400 return;
401 }
402 const std::string placeholder = prefix + "__";
403 if (it->second.ros_name != placeholder) {
404 return;
405 }
406 std::string resolved = prefix + std::to_string(id);
407 it->second.ros_name = resolved;
408 if (it->second.frame_id == placeholder) {
409 it->second.frame_id = std::move(resolved);
410 }
411}
412
413template <typename CameraT>
414std::shared_ptr<CarlaCameraPublisher> ROS2::GetOrCreateCameraSensor(
416 void *actor,
417 const std::string &default_prefix) {
418 std::lock_guard<std::mutex> lock(_mutex);
419 auto it_camera = _camera_publishers.find(actor);
420 if (it_camera != _camera_publishers.end()) {
421 // Enforce the one-actor-one-camera-type invariant on the cache-hit path.
422 // RGB/Depth/SS/IS/Normals all alias CarlaRGBCameraPublisher (shared BGRA
423 // passthrough), so a hit across those types casts cleanly and is expected.
424 // Only an RGB <-> OpticalFlow mismatch fails the cast: that would route
425 // optical-flow float bytes through an RGB publisher (or vice versa). Surface
426 // it and skip the sample instead of letting the first-created type silently
427 // win and corrupt the published image.
428 auto typed = std::dynamic_pointer_cast<CameraT>(it_camera->second);
429 if (typed == nullptr) {
430 log_error(
431 "ROS2 camera publisher type mismatch for actor", actor,
432 "- the actor was dispatched as two different camera types; ignoring this sample.");
433 return nullptr;
434 }
435 return typed;
436 }
437 if (!IsKnownActorLocked(actor)) {
438 return nullptr;
439 }
440
441 ResolveAutoStreamSuffix(actor, default_prefix, id);
442 const std::string base_topic_name = BuildBaseTopicName(actor);
443 const std::string frame_id = LookupFrameId(actor);
444
445 auto new_publisher = std::make_shared<CameraT>(base_topic_name, frame_id);
446 _camera_publishers.insert({actor, new_publisher});
447 return new_publisher;
448}
449
450std::shared_ptr<BasePublisher> ROS2::GetOrCreateSensor(
451 int type, carla::streaming::detail::stream_id_type id, void *actor) {
452 std::lock_guard<std::mutex> lock(_mutex);
453 auto it_publishers = _publishers.find(actor);
454 if (it_publishers != _publishers.end()) {
455 return it_publishers->second;
456 }
457 if (!IsKnownActorLocked(actor)) {
458 return nullptr;
459 }
460
461 // Resolve auto-naming "prefix__" -> "prefix<stream_id>" before computing the
462 // topic name. Each enum case names its own prefix so the resolved ros_name
463 // stays stable across ticks.
464 auto resolve = [this, actor, id](const std::string &prefix) {
465 ResolveAutoStreamSuffix(actor, prefix, id);
466 };
467
468 std::shared_ptr<BasePublisher> publisher;
469 switch (type) {
471 resolve("collision");
472 publisher = std::make_shared<CarlaCollisionPublisher>(
473 BuildBaseTopicName(actor), LookupFrameId(actor));
474 break;
475 }
476 case ESensors::DVSCamera: {
477 resolve("dvs");
478 publisher = std::make_shared<CarlaDVSCameraPublisher>(
479 BuildBaseTopicName(actor), LookupFrameId(actor));
480 break;
481 }
483 resolve("gnss");
484 publisher = std::make_shared<CarlaGNSSPublisher>(
485 BuildBaseTopicName(actor), LookupFrameId(actor));
486 break;
487 }
489 resolve("imu");
490 publisher = std::make_shared<CarlaIMUPublisher>(
491 BuildBaseTopicName(actor), LookupFrameId(actor));
492 break;
493 }
494 case ESensors::Radar: {
495 resolve("radar");
496 publisher = std::make_shared<CarlaRadarPublisher>(
497 BuildBaseTopicName(actor), LookupFrameId(actor), HasTopicOverride(actor));
498 break;
499 }
501 resolve("ray_cast_semantic");
502 publisher = std::make_shared<CarlaSemanticLidarPublisher>(
503 BuildBaseTopicName(actor), LookupFrameId(actor), HasTopicOverride(actor));
504 break;
505 }
507 // Both ray-cast and HSS lidars dispatch here; resolve either placeholder.
508 resolve("ray_cast");
509 resolve("hss_lidar");
510 publisher = std::make_shared<CarlaLidarPublisher>(
511 BuildBaseTopicName(actor), LookupFrameId(actor), HasTopicOverride(actor));
512 break;
513 }
515 resolve("autoware_gnss");
516 // frame_id "map" (not the sensor's own frame): the published poses are
517 // world/map-frame quantities including the MGRS offset. Architecture
518 // decision — tier4's AutowarePublisherBase used the sensor ros_name as
519 // frame_id, which was accidental; triage confirmed "map" is the intent.
520 publisher = std::make_shared<AutowareGNSSPublisher>(
521 BuildBaseTopicName(actor), "map");
522 break;
523 }
530 // Sensors without a publisher on ue5-dev today; the dispatch in
531 // ProcessDataFrom* logs and exits cleanly.
532 return nullptr;
533 default:
534 log_error("ROS2::GetOrCreateSensor: unknown sensor type", type);
535 return nullptr;
536 }
537
538 if (publisher) {
539 _publishers.insert({actor, publisher});
540 }
541 return publisher;
542}
543
544std::shared_ptr<CarlaTransformPublisher> ROS2::GetOrCreateTransformPublisherLocked(void *actor) {
545 // Global TF gate (tier4's _publish_tf): checked before the cache so
546 // toggling at runtime silences every sensor immediately.
547 if (!_publish_tf) {
548 return nullptr;
549 }
550 auto it = _transforms.find(actor);
551 if (it != _transforms.end()) {
552 return it->second;
553 }
554 auto registration_it = _registrations.find(actor);
555 if (registration_it == _registrations.end() || !registration_it->second.publish_tf) {
556 return nullptr;
557 }
558 auto transform = std::make_shared<CarlaTransformPublisher>();
559 _transforms.insert({actor, transform});
560 return transform;
561}
562
563namespace {
564
565// Builds the parent_frame_id for TF: top-level actors broadcast against
566// "map"; child actors broadcast against their direct parent's frame_id.
567std::string ParentFrameOrMap(const std::string &parent_chain) {
568 return parent_chain.empty() ? std::string{"map"} : parent_chain;
569}
570
571} // namespace
572
574 void *actor, const Stamp &stamp, const carla::geom::Transform &sensor_transform) {
575 std::shared_ptr<CarlaTransformPublisher> transform_publisher;
576 std::string parent_frame;
577 std::string frame_id;
578 {
579 std::lock_guard<std::mutex> lock(_mutex);
580 transform_publisher = GetOrCreateTransformPublisherLocked(actor);
581 if (!transform_publisher) {
582 return;
583 }
584 parent_frame = ParentFrameOrMap(BuildParentChain(actor));
585 frame_id = LookupFrameId(actor);
586 }
587 transform_publisher->Write(
588 stamp.seconds, stamp.nanoseconds, parent_frame, frame_id,
589 sensor_transform.location.x, sensor_transform.location.y, sensor_transform.location.z,
590 sensor_transform.rotation.pitch, sensor_transform.rotation.yaw, sensor_transform.rotation.roll);
591 transform_publisher->Publish();
592}
593
595 uint64_t sensor_type,
597 const carla::geom::Transform sensor_transform,
598 int W, int H, float Fov,
599 const carla::SharedBufferView buffer,
600 void *actor) {
601 const Stamp stamp = GetStamp();
602 // Image dimensions + FOV are now read straight from ImageSerializer's
603 // per-frame header inside the camera publisher's WriteCameraInfo call;
604 // the W/H/Fov arguments survive for ABI compatibility with the
605 // Unreal-side dispatcher.
606 (void)W;
607 (void)H;
608 (void)Fov;
609
610 std::shared_ptr<CarlaCameraPublisher> publisher;
611 switch (sensor_type) {
613 publisher = GetOrCreateCameraSensor<CarlaRGBCameraPublisher>(stream_id, actor, "rgb");
614 break;
616 publisher = GetOrCreateCameraSensor<CarlaDepthCameraPublisher>(stream_id, actor, "depth");
617 break;
619 publisher = GetOrCreateCameraSensor<CarlaNormalsCameraPublisher>(stream_id, actor, "normals");
620 break;
622 publisher = GetOrCreateCameraSensor<CarlaSSCameraPublisher>(stream_id, actor, "semantic_segmentation");
623 break;
625 publisher = GetOrCreateCameraSensor<CarlaISCameraPublisher>(stream_id, actor, "instance_segmentation");
626 break;
628 publisher = GetOrCreateCameraSensor<CarlaOpticalFlowCameraPublisher>(
629 stream_id, actor, "optical_flow");
630 break;
636 default:
637 log_info(
638 "Sensor to ROS data: frame.", _frame.load(), "sensor.", sensor_type, "stream.", stream_id,
639 "buffer.", buffer->size());
640 return;
641 }
642
643 if (publisher) {
644 const auto *header_ptr = buffer->data();
645 if (!header_ptr) {
646 return;
647 }
648 if (sensor_type == ESensors::OpticalFlowCamera) {
649 const auto *header = reinterpret_cast<
651 publisher->WriteCameraInfo(
652 stamp.seconds, stamp.nanoseconds, 0, 0, header->height, header->width, header->fov_angle, true);
653 publisher->WriteImage(
654 stamp.seconds, stamp.nanoseconds, header->height, header->width,
656 } else {
657 const auto *header = reinterpret_cast<
659 publisher->WriteCameraInfo(
660 stamp.seconds, stamp.nanoseconds, 0, 0, header->height, header->width, header->fov_angle, true);
661 publisher->WriteImage(
662 stamp.seconds, stamp.nanoseconds, header->height, header->width,
664 }
665 publisher->Publish();
666 }
667
668 PublishTransform(actor, stamp, sensor_transform);
669}
670
672 uint64_t /*sensor_type*/,
674 const carla::geom::Transform sensor_transform,
675 const carla::geom::GeoLocation &data,
676 void *actor) {
677 const Stamp stamp = GetStamp();
678 if (auto base = GetOrCreateSensor(ESensors::GnssSensor, stream_id, actor)) {
679 auto publisher = std::dynamic_pointer_cast<CarlaGNSSPublisher>(base);
680 publisher->Write(stamp.seconds, stamp.nanoseconds, data.latitude, data.longitude, data.altitude);
681 publisher->Publish();
682 }
683 PublishTransform(actor, stamp, sensor_transform);
684}
685
687 uint64_t /*sensor_type*/,
689 const carla::geom::Transform sensor_transform,
690 carla::geom::Vector3D accelerometer,
691 carla::geom::Vector3D gyroscope,
692 float compass,
693 void *actor) {
694 const Stamp stamp = GetStamp();
695 if (auto base = GetOrCreateSensor(ESensors::InertialMeasurementUnit, stream_id, actor)) {
696 auto publisher = std::dynamic_pointer_cast<CarlaIMUPublisher>(base);
697 publisher->Write(
698 stamp.seconds, stamp.nanoseconds,
699 accelerometer.x, accelerometer.y, accelerometer.z,
700 gyroscope.x, gyroscope.y, gyroscope.z,
701 compass);
702 publisher->Publish();
703 }
704 PublishTransform(actor, stamp, sensor_transform);
705}
706
708 uint64_t /*sensor_type*/,
710 const carla::geom::Transform sensor_transform,
711 const carla::SharedBufferView buffer,
712 int /*W*/, int /*H*/, float /*Fov*/,
713 void *actor) {
714 const Stamp stamp = GetStamp();
715 if (auto base = GetOrCreateSensor(ESensors::DVSCamera, stream_id, actor)) {
716 auto publisher = std::dynamic_pointer_cast<CarlaDVSCameraPublisher>(base);
717 const auto *header = reinterpret_cast<
719 if (!header) {
720 return;
721 }
722 constexpr std::size_t header_offset =
724 constexpr std::size_t event_size = sizeof(carla::sensor::data::DVSEvent);
725 const std::size_t event_count = (buffer->size() - header_offset) / event_size;
726 const std::uint8_t *event_bytes = buffer->data() + header_offset;
727
728 publisher->WriteCameraInfo(
729 stamp.seconds, stamp.nanoseconds, 0, 0, header->height, header->width, header->fov_angle, true);
730 publisher->WriteImage(
731 stamp.seconds, stamp.nanoseconds, header->height, header->width,
732 event_count, event_bytes, event_size);
733 publisher->WritePointCloud(
734 stamp.seconds, stamp.nanoseconds, 1, static_cast<std::uint32_t>(event_count), event_bytes);
735 publisher->Publish();
736 }
737 PublishTransform(actor, stamp, sensor_transform);
738}
739
741 uint64_t sensor_type,
743 const carla::geom::Transform sensor_transform,
745 void *actor) {
746 // No sensor description available: plain XYZI layout (channel_count = 0
747 // routes the extended path off).
749 sensor_type, stream_id, sensor_transform,
750 0u, 0.0f, 0.0f, data, actor);
751}
752
754 uint64_t /*sensor_type*/,
756 const carla::geom::Transform sensor_transform,
757 uint32_t channel_count,
758 float upper_fov_limit,
759 float lower_fov_limit,
761 void *actor) {
762 const Stamp stamp = GetStamp();
763 if (auto base = GetOrCreateSensor(ESensors::RayCastLidar, stream_id, actor)) {
764 auto publisher = std::dynamic_pointer_cast<CarlaLidarPublisher>(base);
765 // The lidar returns a flat list of floats rather than structured detection
766 // points. Each detection is 4 floats: x, y, z, intensity. Divide the total
767 // float count by 4 to recover the number of detections.
768 const auto width = static_cast<std::uint32_t>(data._points.size() / 4u);
769 if (channel_count >= 2u) {
770 // tier4 routing: every raycast lidar whose description is known goes
771 // through the extended Autoware XYZIRCAEDT layout. Per-channel
772 // elevations span [upper_fov, lower_fov] (degrees in, radians out),
773 // uniformly spaced, top channel first — same as tier4's ROS2.cpp.
774 const float upper_rad = upper_fov_limit * static_cast<float>(M_PI) / 180.0f;
775 const float lower_rad = lower_fov_limit * static_cast<float>(M_PI) / 180.0f;
776 std::vector<float> vertical_angles;
777 vertical_angles.reserve(channel_count);
778 const float vertical_step =
779 (upper_rad - lower_rad) / static_cast<float>(channel_count - 1u);
780 for (uint32_t i = 0u; i < channel_count; ++i) {
781 vertical_angles.push_back(upper_rad - static_cast<float>(i) * vertical_step);
782 }
783 publisher->WriteExtendedPointCloud(
784 stamp.seconds, stamp.nanoseconds, 1u, width,
785 reinterpret_cast<const std::uint8_t *>(data._points.data()),
788 vertical_angles);
789 } else {
790 publisher->WritePointCloud(
791 stamp.seconds, stamp.nanoseconds, 1u, width,
792 reinterpret_cast<const std::uint8_t *>(data._points.data()));
793 }
794 publisher->Publish();
795 }
796 PublishTransform(actor, stamp, sensor_transform);
797}
798
800 uint64_t /*sensor_type*/,
802 const carla::geom::Transform sensor_transform,
804 void *actor) {
805 const Stamp stamp = GetStamp();
806 if (auto base = GetOrCreateSensor(ESensors::RayCastSemanticLidar, stream_id, actor)) {
807 auto publisher = std::dynamic_pointer_cast<CarlaSemanticLidarPublisher>(base);
808 const auto width = static_cast<std::uint32_t>(data._ser_points.size());
809 publisher->WritePointCloud(
810 stamp.seconds, stamp.nanoseconds, 1u, width,
811 reinterpret_cast<const std::uint8_t *>(data._ser_points.data()));
812 publisher->Publish();
813 }
814 PublishTransform(actor, stamp, sensor_transform);
815}
816
818 uint64_t /*sensor_type*/,
820 const carla::geom::Transform sensor_transform,
822 void *actor) {
823 const Stamp stamp = GetStamp();
824 if (auto base = GetOrCreateSensor(ESensors::Radar, stream_id, actor)) {
825 auto publisher = std::dynamic_pointer_cast<CarlaRadarPublisher>(base);
826 const auto width = static_cast<std::uint32_t>(data.GetDetectionCount());
827 publisher->WritePointCloud(
828 stamp.seconds, stamp.nanoseconds, 1u, width,
829 reinterpret_cast<const std::uint8_t *>(data._detections.data()));
830 publisher->Publish();
831 }
832 PublishTransform(actor, stamp, sensor_transform);
833}
834
836 uint64_t sensor_type,
838 const carla::geom::Transform /*sensor_transform*/,
839 AActor * /*first_actor*/,
840 AActor * /*second_actor*/,
841 float distance,
842 void * /*actor*/) {
843 log_info(
844 "Sensor ObstacleDetector to ROS data: frame.", _frame.load(), "sensor.", sensor_type,
845 "stream.", stream_id, "distance.", distance);
846}
847
849 uint64_t /*sensor_type*/,
851 const carla::geom::Transform sensor_transform,
852 uint32_t other_actor,
853 carla::geom::Vector3D impulse,
854 void *actor) {
855 const Stamp stamp = GetStamp();
856 if (auto base = GetOrCreateSensor(ESensors::CollisionSensor, stream_id, actor)) {
857 auto publisher = std::dynamic_pointer_cast<CarlaCollisionPublisher>(base);
858 publisher->Write(stamp.seconds, stamp.nanoseconds, other_actor, impulse.x, impulse.y, impulse.z);
859 publisher->Publish();
860 }
861 PublishTransform(actor, stamp, sensor_transform);
862}
863
865 uint64_t /*sensor_type*/,
867 const carla::geom::Transform /*sensor_transform*/,
869 void *vehicle_actor,
870 void * /*actor*/) {
871 const Stamp stamp = GetStamp();
872 // Only vehicles registered with enable_autoware_control publish reports:
873 // the report topics are fixed absolute names shared by the whole vehicle
874 // interface (tier4 restricted this to the ego for the same reason).
875 std::shared_ptr<AutowareVehicleStatusPublisher> publisher;
876 {
877 std::lock_guard<std::mutex> lock(_mutex);
878 if (_autoware_vehicles.count(vehicle_actor) == 0) {
880 "ROS2::ProcessDataFromStatusSensor: the Vehicle Status Sensor is attached to a "
881 "vehicle that is not registered for Autoware control - not publishing the data.");
882 return;
883 }
884 auto it = _autoware_status_publishers.find(vehicle_actor);
885 if (it != _autoware_status_publishers.end()) {
886 publisher = it->second;
887 } else {
888 publisher = std::make_shared<AutowareVehicleStatusPublisher>();
889 _autoware_status_publishers.insert({vehicle_actor, publisher});
890 }
891 }
892
893 constexpr uint8_t reverse_mask = 0b00000001;
894 constexpr uint8_t manual_gear_mask = 0b00000010;
895
896 constexpr uint8_t left_blinker_mask = 0b00000001;
897 constexpr uint8_t right_blinker_mask = 0b00000010;
898 constexpr uint8_t hazard_lights_mask = 0b00000100;
899
900 // Decode data (control_flags b0=reverse b1=manual; turn_mask b0=L b1=R b2=hazard).
901 const bool is_reverse = (data.control_flags & reverse_mask) != 0;
902 const bool is_manual_gear = (data.control_flags & manual_gear_mask) != 0;
903 (void)is_reverse; // reserved: tier4 TODO — gear/reverse interplay
904 (void)is_manual_gear; // reserved: tier4 TODO
905
906 const bool is_left_blinker_on = (data.turn_mask & left_blinker_mask) != 0;
907 const bool is_right_blinker_on = (data.turn_mask & right_blinker_mask) != 0;
908 const bool is_hazard_lights_on = (data.turn_mask & hazard_lights_mask) != 0;
909
910 // Positive values mean forward and left (when going forward), so the local
911 // Y velocity and Z angular velocity flip sign from CARLA's convention.
912 publisher->SetVelocity(data.vel_x_mps, -data.vel_y_mps, -data.angVel_z_mps);
913
914 // Steering is reported negated, matching the negation applied when the
915 // Autoware steering command is translated to CARLA (tier4 parity).
916 publisher->SetSteering(-data.steer);
917
918 // Control mode command is a ROS service in Autoware, so there is no easy
919 // way to reflect it yet; AUTONOMOUS is reported unconditionally (tier4).
920 publisher->SetControlMode(ControlMode::AUTONOMOUS);
921
922 publisher->SetGear(Gear::NONE);
923 switch (data.gear) {
924#define CASE(GEAR_VALUE, GEAR_ENUM) \
925 case GEAR_VALUE: \
926 publisher->SetGear(GEAR_ENUM); \
927 break; \
928 static_assert(true, "")
929
931 CASE(-1, Gear::REVERSE );
932 CASE( 0, Gear::NEUTRAL );
933 CASE( 1, Gear::DRIVE );
934 CASE( 2, Gear::DRIVE_2 );
935 CASE( 3, Gear::DRIVE_3 );
936 CASE( 4, Gear::DRIVE_4 );
937 CASE( 5, Gear::DRIVE_5 );
938 CASE( 6, Gear::DRIVE_6 );
939 CASE( 7, Gear::DRIVE_7 );
940 CASE( 8, Gear::DRIVE_8 );
941 CASE( 9, Gear::DRIVE_9 );
942 CASE( 10, Gear::DRIVE_10 );
943 CASE( 11, Gear::DRIVE_11 );
944 CASE( 12, Gear::DRIVE_12 );
945 CASE( 13, Gear::DRIVE_13 );
946 CASE( 14, Gear::DRIVE_14 );
947 CASE( 15, Gear::DRIVE_15 );
948 CASE( 16, Gear::DRIVE_16 );
949 CASE( 17, Gear::DRIVE_17 );
950 CASE( 18, Gear::DRIVE_18 );
951
952#undef CASE
953 }
954
955 // Turn indicators.
956 if (!is_left_blinker_on && !is_right_blinker_on) {
957 publisher->SetTurnIndicators(TurnIndicatorsStatus::OFF);
958 } else if (is_left_blinker_on && !is_right_blinker_on) {
959 publisher->SetTurnIndicators(TurnIndicatorsStatus::LEFT);
960 } else if (is_right_blinker_on && !is_left_blinker_on) {
961 publisher->SetTurnIndicators(TurnIndicatorsStatus::RIGHT);
962 } else {
963 log_error("ROS2::ProcessDataFromStatusSensor: both blinkers are on; this should not happen.");
964 }
965
966 publisher->SetHazardLights(is_hazard_lights_on);
967
968 publisher->Publish(stamp.seconds, stamp.nanoseconds);
969}
970
972 uint64_t /*sensor_type*/,
974 const carla::geom::Transform sensor_transform,
975 const carla::geom::GeoLocation & /*data*/,
976 const carla::geom::Transform &sensor_world_transform,
977 const double mgrs_offset_position[3],
978 void *actor) {
979 const Stamp stamp = GetStamp();
980 if (auto base = GetOrCreateSensor(ESensors::AutowareGnssSensor, stream_id, actor)) {
981 if (auto publisher = std::dynamic_pointer_cast<AutowareGNSSPublisher>(base)) {
982 publisher->Write(
983 stamp.seconds, stamp.nanoseconds,
984 sensor_world_transform.location.x,
985 sensor_world_transform.location.y,
986 sensor_world_transform.location.z,
987 sensor_world_transform.rotation.pitch,
988 sensor_world_transform.rotation.yaw,
989 sensor_world_transform.rotation.roll,
990 mgrs_offset_position[0], mgrs_offset_position[1], mgrs_offset_position[2]);
991 publisher->Publish();
992 } else {
993 log_error(
994 "ROS2::ProcessDataFromAutowareGNSS: publisher type mismatch for actor", actor,
995 "- the actor was dispatched as both regular and Autoware GNSS; ignoring this sample.");
996 }
997 }
998 PublishTransform(actor, stamp, sensor_transform);
999}
1000
1002 std::lock_guard<std::mutex> lock(_mutex);
1003 _publishers.clear();
1004 _transforms.clear();
1005 _camera_publishers.clear();
1006 _subscribers.clear();
1007 _actor_callbacks.clear();
1008 _registrations.clear();
1009 _actor_parents.clear();
1010 _actor_ros_topic_names.clear();
1011 _autoware_vehicles.clear();
1013 _clock_publisher.reset();
1014 _enabled = false;
1015#if defined(WITH_ROS2_DEMO)
1016 _basic_publisher.reset();
1017 _basic_subscriber.reset();
1018#endif
1019}
1020
1021} // namespace ros2
1022} // namespace carla
#define CASE(DATUM)
float distance
A rigid transform in CARLA's / Unreal's left-handed frame: x forward, y right, z up,...
std::unordered_map< void *, std::shared_ptr< CarlaTransformPublisher > > _transforms
Definition ROS2.h:342
void ProcessDataFromRadar(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, const carla::sensor::data::RadarData &data, void *actor=nullptr)
Definition ROS2.cpp:817
void ProcessDataFromDVS(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, const carla::SharedBufferView buffer, int W, int H, float Fov, void *actor=nullptr)
Definition ROS2.cpp:707
static std::shared_ptr< ROS2 > _instance
Definition ROS2.h:315
std::string GetActorRosTopicName(void *actor) const
Definition ROS2.cpp:296
std::atomic< bool > _enabled
Definition ROS2.h:322
std::string LookupRosName(void *actor) const
Definition ROS2.cpp:336
void ProcessDataFromStatusSensor(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, const carla::sensor::s11n::VehicleStatusData &data, void *vehicle_actor, void *actor)
Definition ROS2.cpp:864
void UnregisterVehicle(void *actor)
Definition ROS2.cpp:273
void RemoveBasicSubscriberCallback(void *actor)
Definition ROS2.cpp:329
std::string BuildParentChain(void *actor) const
Definition ROS2.cpp:346
void AddActorRosTopicName(void *actor, std::string ros_topic_name)
Definition ROS2.cpp:282
void RemoveActorRosTopicName(void *actor)
Definition ROS2.cpp:288
void RegisterSensor(void *actor, std::string ros_name, std::string frame_id, bool publish_tf)
Definition ROS2.cpp:212
std::unordered_set< void * > _autoware_vehicles
Definition ROS2.h:332
std::unordered_map< void *, std::shared_ptr< BasePublisher > > _publishers
Definition ROS2.h:340
std::unordered_map< void *, std::vector< void * > > _actor_parents
Definition ROS2.h:338
std::shared_ptr< BasePublisher > GetOrCreateSensor(int type, carla::streaming::detail::stream_id_type id, void *actor)
Definition ROS2.cpp:450
std::unordered_map< void *, std::string > _actor_ros_topic_names
Definition ROS2.h:329
void ProcessDataFromIMU(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, carla::geom::Vector3D accelerometer, carla::geom::Vector3D gyroscope, float compass, void *actor=nullptr)
Definition ROS2.cpp:686
bool IsKnownActorLocked(void *actor) const
Definition ROS2.cpp:208
void ProcessDataFromObstacleDetection(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, AActor *first_actor, AActor *second_actor, float distance, void *actor=nullptr)
Definition ROS2.cpp:835
std::unordered_map< void *, std::shared_ptr< AutowareVehicleStatusPublisher > > _autoware_status_publishers
Definition ROS2.h:337
void ProcessDataFromAutowareGNSS(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, const carla::geom::GeoLocation &data, const carla::geom::Transform &sensor_world_transform, const double mgrs_offset_position[3], void *actor=nullptr)
Definition ROS2.cpp:971
void ProcessDataFromCamera(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, int W, int H, float Fov, const carla::SharedBufferView buffer, void *actor=nullptr)
Definition ROS2.cpp:594
bool HasTopicOverride(void *actor) const
Definition ROS2.cpp:302
std::unordered_map< void *, ActorCallback > _actor_callbacks
Definition ROS2.h:344
std::string LookupFrameId(void *actor) const
Definition ROS2.cpp:341
void ProcessDataFromSemanticLidar(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, carla::sensor::data::SemanticLidarData &data, void *actor=nullptr)
Definition ROS2.cpp:799
std::unordered_multimap< void *, std::shared_ptr< BaseSubscriber > > _subscribers
Definition ROS2.h:345
std::mutex _mutex
Definition ROS2.h:320
std::shared_ptr< CarlaClockPublisher > _clock_publisher
Definition ROS2.h:339
void ProcessDataFromCollisionSensor(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, uint32_t other_actor, carla::geom::Vector3D impulse, void *actor)
Definition ROS2.cpp:848
void RegisterVehicle(void *actor, std::string ros_name, std::string frame_id, ActorCallback callback, bool enable_ackermann_control=false, bool enable_autoware_control=false)
Definition ROS2.cpp:236
std::atomic< bool > _publish_tf
Definition ROS2.h:323
std::atomic< uint64_t > _frame
Definition ROS2.h:324
std::shared_ptr< CarlaTransformPublisher > GetOrCreateTransformPublisherLocked(void *actor)
Definition ROS2.cpp:544
bool Enable(bool enable, Middleware middleware=Middleware::FastDDS, int domain_id=kUnsetDomainId)
Enable or disable ROS 2 publishing.
Definition ROS2.cpp:101
void AddActorParentRosName(void *actor, void *parent)
Definition ROS2.cpp:307
Stamp GetStamp() const
Definition ROS2.cpp:197
std::unordered_map< void *, ActorRegistration > _registrations
Definition ROS2.h:327
void AddBasicSubscriberCallback(void *actor, std::string ros_name, ActorMessageCallback callback)
Definition ROS2.cpp:317
void SetTimestamp(double timestamp)
Definition ROS2.cpp:174
void UnregisterSensorLocked(void *actor)
Definition ROS2.cpp:230
std::shared_ptr< CarlaCameraPublisher > GetOrCreateCameraSensor(carla::streaming::detail::stream_id_type id, void *actor, const std::string &default_prefix)
Definition ROS2.cpp:414
void ProcessDataFromLidar(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, carla::sensor::data::LidarData &data, void *actor=nullptr)
Definition ROS2.cpp:740
void SetFrame(uint64_t frame)
Definition ROS2.cpp:139
std::string BuildBaseTopicName(void *actor) const
Definition ROS2.cpp:366
void ResolveAutoStreamSuffix(void *actor, const std::string &prefix, carla::streaming::detail::stream_id_type id)
Definition ROS2.cpp:394
void PublishTransform(void *actor, const Stamp &stamp, const carla::geom::Transform &sensor_transform)
Definition ROS2.cpp:573
void DropPublishersLocked(void *actor)
Definition ROS2.cpp:202
std::unordered_map< void *, std::shared_ptr< CarlaCameraPublisher > > _camera_publishers
Definition ROS2.h:341
void UnregisterSensor(void *actor)
Definition ROS2.cpp:225
void ProcessDataFromGNSS(uint64_t sensor_type, carla::streaming::detail::stream_id_type stream_id, const carla::geom::Transform sensor_transform, const carla::geom::GeoLocation &data, void *actor=nullptr)
Definition ROS2.cpp:671
std::vector< float > _points
Definition LidarData.h:120
size_t GetDetectionCount() const
Returns the number of current detections.
Definition RadarData.h:58
std::vector< RadarDetection > _detections
Definition RadarData.h:78
std::vector< SemanticLidarDetection > _ser_points
std::string GetAvailableMiddleware()
const char * MiddlewareToString(Middleware middleware)
Convert a Middleware enum value to a readable string.
Definition Middleware.h:22
std::function< void(void *actor, ROS2CallbackData data)> ActorCallback
Middleware
Enumeration of available middleware implementations.
Definition Middleware.h:15
std::function< void(void *actor, ROS2MessageCallbackData data)> ActorMessageCallback
bool SetActiveMiddleware(Middleware requested)
DDS-free bridge for selecting the active middleware across the shared-library boundary.
@ InstanceSegmentationCamera_WideAngleLens
Definition ROS2.cpp:89
@ InstanceSegmentationCamera
Definition ROS2.cpp:81
@ RssSensor
Definition ROS2.cpp:78
@ NormalsCamera
Definition ROS2.cpp:68
@ DepthCamera_WideAngleLens
Definition ROS2.cpp:88
@ RayCastSemanticLidar
Definition ROS2.cpp:76
@ GnssSensor
Definition ROS2.cpp:70
@ SemanticSegmentationCamera_WideAngleLens
Definition ROS2.cpp:90
@ SceneCaptureCamera
Definition ROS2.cpp:79
@ CameraGBufferUint8
Definition ROS2.cpp:91
@ DVSCamera
Definition ROS2.cpp:69
@ RayCastLidar
Definition ROS2.cpp:77
@ SceneCaptureCamera_WideAngleLens
Definition ROS2.cpp:87
@ AutowareGnssSensor
Definition ROS2.cpp:98
@ SemanticSegmentationCamera
Definition ROS2.cpp:80
@ DepthCamera
Definition ROS2.cpp:67
@ CameraGBufferFloat
Definition ROS2.cpp:92
@ WorldObserver
Definition ROS2.cpp:82
@ VehicleStatusSensor
Definition ROS2.cpp:97
@ InertialMeasurementUnit
Definition ROS2.cpp:71
@ OpticalFlowCamera
Definition ROS2.cpp:74
@ CollisionSensor
Definition ROS2.cpp:66
@ ObstacleDetectionSensor
Definition ROS2.cpp:73
@ LaneInvasionSensor
Definition ROS2.cpp:72
@ Environment
From the ROS_DOMAIN_ID environment variable.
@ CommandLine
From the –ros-domain-id command line option.
ResolvedDomainId SetActiveDomainId(int domain_id)
Store the domain id in the shared library's MiddlewareConfig and return the effective domain id resol...
This file contains definitions of common data structures used in traffic manager.
std::shared_ptr< BufferView > SharedBufferView
Definition BufferView.h:151
static void log_error(Args &&... args)
Definition Logging.h:110
static void log_info(Args &&... args)
Definition Logging.h:82
static void log_warning(Args &&... args)
Definition Logging.h:96
The resolved domain id together with the source it came from.
Snapshot of a vehicle's kinematic + control state, streamed each tick by the Autoware VehicleStatusSe...