Skip to content

autoware_agnocast_wrapper#

The purpose of this package is to integrate Agnocast, a zero-copy middleware, into each topic in Autoware with minimal side effects. Agnocast is a library designed to work alongside ROS 2, enabling true zero-copy publish/subscribe communication for all ROS 2 message types, including unsized message types.

This package provides macros that wrap functions for publish/subscribe operations and smart pointer types for handling ROS 2 messages. When Autoware is built using the default build command, Agnocast is not enabled. However, setting the environment variable ENABLE_AGNOCAST=1 enables Agnocast and results in a build that includes its integration. This design ensures backward compatibility for users who are unaware of Agnocast, minimizing disruption.

Two Integration Approaches#

This package provides two approaches for integrating Agnocast. Both will coexist for the foreseeable future.

1. Node Wrapper (agnocast_wrapper::Node)#

Use this when you want the entire node to transparently switch between rclcpp::Node and agnocast::Node at runtime. The node wrapper automatically selects the correct underlying implementation based on the ENABLE_AGNOCAST environment variable.

agnocast_wrapper::Node does not publicly derive from rclcpp::Node. It exposes a curated subset of the rclcpp::Node surface and forwards each member to the underlying implementation (rclcpp::Node or agnocast::Node). The member names and argument lists are identical in both builds (ENABLE_AGNOCAST=0 and =1), so a node written against it compiles unchanged either way — provided you spell the handle, options and message types with the AUTOWARE_* macros (see Type spellings). If you need an API that is not listed below, extend the wrapper, or reach the underlying node via get_rclcpp_node() (declared in both builds, but it throws when the node is in Agnocast mode — see the build-modes table).

Supported API surface#

The following members / free functions are provided. Unless noted, signatures mirror their rclcpp::Node counterparts.

Category Members
Construction Node(name, options), Node(name, namespace, options), virtual destructor, SharedPtr. Non-copyable and non-movable (copying would alias one backend behind two wrappers). Derives from std::enable_shared_from_this<Node>, so shared_from_this() is available when the node is owned by a shared_ptr
Basic info get_name(), get_namespace(), get_fully_qualified_name(), get_logger()
Time get_clock(), now()
Node interfaces get_node_base_interface(), get_node_topics_interface(), get_node_parameters_interface() (partial — only these three)
Callback groups create_callback_group()
Parameters declare_parameter() (typed + ParameterValue/ParameterType overloads), has_parameter(), undeclare_parameter(), get_parameter() / get_parameters() (typed + prefix overloads), set_parameter() / set_parameters() / set_parameters_atomically(), describe_parameter(s)(), get_parameter_types(), list_parameters(), add_on_set_parameters_callback(), remove_on_set_parameters_callback()
Publisher create_publisher<MessageT>() (QoS and depth overloads) — see Publisher API
Subscription create_subscription<MessageT>() (QoS and depth overloads)
Client create_client<ServiceT>() — takes rclcpp::QoS (the wrapper normalizes the Humble vs. Jazzy QoS-argument difference)
Service create_service<ServiceT>()message_ptr callback form and an rclcpp-style shared_ptr callback form
Timer create_wall_timer(); free create_timer(node, clock, period, cb, group) and free set_period(timer, period) (see Timer notes)
Underlying node get_rclcpp_node(); get_agnocast_node() (agnocast-enabled build only — not declared in an agnocast-disabled build, so calling it there is a compile error); free to_rclcpp_node(node)
Context free ok() — mode-agnostic replacement for rclcpp::ok() (see Context notes)

OnSetParametersCallbackType is aliased in this namespace and resolves to the correct rclcpp type for both Humble (rclcpp 16.x) and Jazzy (rclcpp 28+).

Polling subscribers are not a Node member. Use the free function polling::create_polling_subscriber<MessageT>(node, topic, qos) — see Polling Subscriber.

Type spellings#

The member names and argument lists above are the same in both builds, but the handle, options and message types they use are not the same C++ types. Always spell them with the AUTOWARE_* macros so the same source compiles in both builds:

What Spell it as ENABLE_AGNOCAST=0 ENABLE_AGNOCAST=1
create_publisher result AUTOWARE_PUBLISHER_PTR(M) rclcpp::Publisher<M>::SharedPtr agnocast_wrapper::Publisher<M>::SharedPtr
create_subscription result AUTOWARE_SUBSCRIPTION_PTR(M) rclcpp::Subscription<M>::SharedPtr agnocast_wrapper::Subscription<M>::SharedPtr
create_wall_timer result AUTOWARE_TIMER_PTR rclcpp::TimerBase::SharedPtr agnocast_wrapper::Timer::SharedPtr
create_publisher options arg AUTOWARE_PUBLISHER_OPTIONS rclcpp::PublisherOptions agnocast::PublisherOptions
create_subscription options AUTOWARE_SUBSCRIPTION_OPTIONS rclcpp::SubscriptionOptions agnocast::SubscriptionOptions
Owning subscription callback arg AUTOWARE_MESSAGE_CONST_SHARED_PTR(M) std::shared_ptr<const M> message_ptr<const M, Shared>

AUTOWARE_CLIENT_PTR(S) / AUTOWARE_SERVICE_PTR(S) and the AUTOWARE_CLIENT_*FUTURE* macros resolve to the wrapper's own Client<S> / Service<S> types in both builds, so client and service code needs no per-build spelling. See Key Macros for the full macro list.

Build modes: agnocast-disabled vs agnocast-enabled#

Which of the two Node class definitions is compiled is a build-time choice, selected by the USE_AGNOCAST_ENABLED preprocessor macro. The API surface above is identical in both, so this choice only affects the backend and the underlying-node accessors below.

This is a separate axis from the runtime backend selection: in the agnocast-enabled build, each node instance additionally picks rclcpp::Node vs agnocast::Node at construction from the ENABLE_AGNOCAST environment variable read at runtime (use_agnocast()), fixed for the node's lifetime. The runtime value selects the backend; it does not change which Node definition was compiled or which methods are declared — e.g. get_agnocast_node() is declared in every agnocast-enabled build and instead throws at runtime when the node is not in Agnocast mode.

Agnocast-disabled build
(USE_AGNOCAST_ENABLED undefined)
Agnocast-enabled build
(USE_AGNOCAST_ENABLED defined)
Backend Always an owned rclcpp::Node. rclcpp::Node or agnocast::Node, chosen at construction from the runtime ENABLE_AGNOCAST value and fixed for the node's lifetime.
get_rclcpp_node() Always returns the owned node. Declared; returns the rclcpp::Node, but throws std::runtime_error if the node is in Agnocast mode.
get_agnocast_node() Not declared — calling it is a compile error. Declared regardless of the runtime backend; returns the agnocast::Node, but throws if the node is not in Agnocast mode.
to_rclcpp_node(node) Always succeeds. Forwards to get_rclcpp_node() (same throw condition).

In both builds agnocast_wrapper::Node does not derive from rclcpp::Node, so hand it to an executor or utility via get_node_base_interface() (e.g. executor.add_node(node->get_node_base_interface())).

#include <autoware/agnocast_wrapper/node.hpp>

class MyNode : public autoware::agnocast_wrapper::Node
{
public:
  explicit MyNode(const rclcpp::NodeOptions & options)
  : Node("my_node", options)
  {
    pub_ = create_publisher<std_msgs::msg::String>("output", 10);
    sub_ = create_subscription<std_msgs::msg::String>(
      "input", 10,
      [this](AUTOWARE_MESSAGE_CONST_SHARED_PTR(std_msgs::msg::String) && msg) { /* ... */ });

    timer_ = create_wall_timer(
      std::chrono::milliseconds(100), [this]() { /* ... */ });
  }

private:
  AUTOWARE_PUBLISHER_PTR(std_msgs::msg::String) pub_;
  AUTOWARE_SUBSCRIPTION_PTR(std_msgs::msg::String) sub_;
  AUTOWARE_TIMER_PTR timer_;
};

Timer notes#

create_timer() is provided as a free function (not a member) because rclcpp::Node::create_timer was added in Jazzy and does not exist on Humble. The free form is portable across both:

timer_ = autoware::agnocast_wrapper::create_timer(
  this, this->get_clock(), rclcpp::Duration::from_seconds(0.1), [this]() { /* ... */ });

set_period() is likewise a free function. rclcpp::TimerBase has no set_period member, so the free form is the only portable spelling across both builds:

autoware::agnocast_wrapper::set_period(timer_, std::chrono::milliseconds(200));

Context notes#

Use autoware::agnocast_wrapper::ok() instead of rclcpp::ok(). An AgnocastOnly executable never calls rclcpp::init(), so rclcpp::ok() reports false there even while the process is healthy; ok() checks both contexts.

Publisher API#

A wrapper publisher exposes three publish() overloads, all supported in both builds:

Call Behavior
ALLOCATE_OUTPUT_MESSAGE_UNIQUE(pub)pub->publish(std::move(msg)) Zero-copy: the message is built in place in shared memory.
ALLOCATE_OUTPUT_MESSAGE_SHARED(pub)pub->publish(std::move(msg)) Same, for the shared-ownership form.
pub->publish(msg) (const MessageT &) Copies msg into a freshly allocated message, then publishes.

Prefer the allocate-then-move form when you are constructing the outgoing message anyway; the const MessageT & overload suits a message you already hold and must keep.

CMake setup#

To use the Node wrapper in your package, add the following to your CMakeLists.txt:

find_package(autoware_agnocast_wrapper REQUIRED)
ament_target_dependencies(my_node_component autoware_agnocast_wrapper)
autoware_agnocast_wrapper_setup(my_node_component)

autoware_agnocast_wrapper_setup() is required: it defines USE_AGNOCAST_ENABLED on the target, which ament_target_dependencies() does not propagate. Apply it to every target that includes a wrapper header; autoware_agnocast_wrapper_register_node() does it for the targets it handles.

Registering a Node with autoware_agnocast_wrapper_register_node#

Instead of calling rclcpp_components_register_node directly, use the autoware_agnocast_wrapper_register_node macro to register your component node. This macro:

  1. Registers the component with rclcpp_components (for component container support)
  2. Creates a standalone executable that can switch between rclcpp::Node and agnocast::Node at runtime based on the ENABLE_AGNOCAST environment variable

When ENABLE_AGNOCAST is not set or set to 0, this macro falls back to standard rclcpp_components_register_node behavior.

find_package(autoware_agnocast_wrapper REQUIRED)

ament_auto_add_library(my_node_component SHARED src/my_node.cpp)
ament_target_dependencies(my_node_component autoware_agnocast_wrapper)

autoware_agnocast_wrapper_register_node(my_node_component
  PLUGIN "my_package::MyNode"
  EXECUTABLE my_node
)

Parameters:

Parameter Required Default Description
PLUGIN Yes - Fully qualified class name of the component
EXECUTABLE Yes - Executable name for the node
ROS2_EXECUTOR No SingleThreadedExecutor Executor to use when ENABLE_AGNOCAST=0 at runtime
AGNOCAST_EXECUTOR No SingleThreadedAgnocastExecutor Executor to use when ENABLE_AGNOCAST=1 at runtime

Valid executor values:

  • ROS2_EXECUTOR: SingleThreadedExecutor, MultiThreadedExecutor
  • AGNOCAST_EXECUTOR: SingleThreadedAgnocastExecutor, MultiThreadedAgnocastExecutor, CallbackIsolatedAgnocastExecutor, AgnocastOnlySingleThreadedExecutor, AgnocastOnlyMultiThreadedExecutor, AgnocastOnlyCallbackIsolatedExecutor

Node class requirements:

The required PLUGIN base class depends on the AGNOCAST_EXECUTOR type. The generated template enforces this via if constexpr at compile time:

AGNOCAST_EXECUTOR Required PLUGIN base class
AgnocastOnly* executors autoware::agnocast_wrapper::Node
Other agnocast executors Any rclcpp::Node-compatible class

Non-AgnocastOnly executors use NodeInstanceWrapper::get_node_base_interface() directly, which works with any node type (rclcpp::Node, agnocast_wrapper::Node, etc.) without requiring a cast. AgnocastOnly executors require get_agnocast_node(), which is only available on autoware::agnocast_wrapper::Node.

Behavior reference:

The tables below show the complete behavior for each configuration. When ENABLE_AGNOCAST=0 at build time, only ROS2_EXECUTOR matters. When ENABLE_AGNOCAST=1, the behavior depends on both ROS2_EXECUTOR and AGNOCAST_EXECUTOR, and can be switched at runtime via the ENABLE_AGNOCAST environment variable.

Build-time ENABLE_AGNOCAST=0 (or unset):

ROS2_EXECUTOR Runtime behavior
SingleThreadedExecutor SingleThreadedExecutor
MultiThreadedExecutor MultiThreadedExecutor

Runtime ENABLE_AGNOCAST has no effect in this mode — no switchable template is generated.

Build-time ENABLE_AGNOCAST=1:

ROS 2
_EXECUTOR
AGNOCAST
_EXECUTOR
Runtime
ENABLE_AGNOCAST=0
Runtime
ENABLE_AGNOCAST=1
SingleThreadedExecutor SingleThreadedAgnocastExecutor SingleThreadedExecutor SingleThreadedAgnocastExecutor
MultiThreadedExecutor MultiThreadedAgnocastExecutor MultiThreadedExecutor MultiThreadedAgnocastExecutor
MultiThreadedExecutor CallbackIsolatedAgnocastExecutor MultiThreadedExecutor CallbackIsolatedAgnocastExecutor
SingleThreadedExecutor AgnocastOnlySingleThreadedExecutor SingleThreadedExecutor AgnocastOnlySingleThreadedExecutor
MultiThreadedExecutor AgnocastOnlyMultiThreadedExecutor MultiThreadedExecutor AgnocastOnlyMultiThreadedExecutor
MultiThreadedExecutor AgnocastOnlyCallbackIsolatedExecutor MultiThreadedExecutor AgnocastOnlyCallbackIsolatedExecutor
SingleThreadedExecutor MultiThreadedAgnocastExecutor SingleThreadedExecutor MultiThreadedAgnocastExecutor
SingleThreadedExecutor CallbackIsolatedAgnocastExecutor SingleThreadedExecutor CallbackIsolatedAgnocastExecutor
SingleThreadedExecutor AgnocastOnlyMultiThreadedExecutor SingleThreadedExecutor AgnocastOnlyMultiThreadedExecutor
SingleThreadedExecutor AgnocastOnlyCallbackIsolatedExecutor SingleThreadedExecutor AgnocastOnlyCallbackIsolatedExecutor
MultiThreadedExecutor SingleThreadedAgnocastExecutor MultiThreadedExecutor SingleThreadedAgnocastExecutor
MultiThreadedExecutor AgnocastOnlySingleThreadedExecutor MultiThreadedExecutor AgnocastOnlySingleThreadedExecutor

Example with agnocast_wrapper::Node (AgnocastOnly executor):

autoware_agnocast_wrapper_register_node(my_node_component
  PLUGIN "my_package::MyNode"
  EXECUTABLE my_node
  AGNOCAST_EXECUTOR AgnocastOnlyCallbackIsolatedExecutor
)

Example with rclcpp::Node (no node changes required):

autoware_agnocast_wrapper_register_node(my_node_component
  PLUGIN "my_package::MyNode"
  EXECUTABLE my_node
  AGNOCAST_EXECUTOR CallbackIsolatedAgnocastExecutor
)

2. Macro + Free Function API#

Use this when only specific topics need Agnocast on an existing rclcpp::Node, without converting the entire node to agnocast_wrapper::Node.

You can immediately understand how to use the macros just by looking at autoware_agnocast_wrapper.hpp. A typical callback and publisher setup looks like this:

#include <autoware/agnocast_wrapper/autoware_agnocast_wrapper.hpp>

pub_output_ = AUTOWARE_CREATE_PUBLISHER3(
  PointCloud2,
  "output",
  rclcpp::SensorDataQoS().keep_last(max_queue_size_),
  pub_options
);

void onPointCloud(AUTOWARE_MESSAGE_UNIQUE_PTR(const PointCloud2) && input_msg) {
  auto output = ALLOCATE_OUTPUT_MESSAGE_UNIQUE(pub_output_);
  ...
  pub_output_->publish(std::move(output));
}

Subscription callbacks that only read the message inside the callback can also keep the plain rclcpp const MessageT & signature:

void onPointCloud(const PointCloud2 & input_msg) {
  ...
}

Zero-copy is preserved on the Agnocast path: the subscription dereferences the received pointer before invoking the callback, so the reference points directly into shared memory. The referenced entry is kept alive only while the callback runs: the reference is valid for the duration of the callback and must not be stored or used after the callback returns. Use AUTOWARE_MESSAGE_CONST_SHARED_PTR instead when the callback needs to keep the message alive beyond the callback without a copy.

To use the macros provided by this package in your own package, include the following lines in your CMakeLists.txt:

find_package(autoware_agnocast_wrapper REQUIRED)
ament_target_dependencies(target autoware_agnocast_wrapper)
target_include_directories(target PRIVATE ${autoware_agnocast_wrapper_INCLUDE_DIRS})
autoware_agnocast_wrapper_setup(target)

Message Filters Support#

This package provides wrapper types for message_filters (Subscriber, Synchronizer) in the autoware::agnocast_wrapper::message_filters namespace. These wrappers transparently switch between ::message_filters and agnocast::message_filters at runtime.

Current limitations#

  • Only ApproximateTime and ExactTime synchronization policies are supported.
  • 2 to 8 message types per Synchronizer. Upstream message_filters supports up to 9, but the registration path this wrapper uses caps it at 8.
  • connectInput() is not supported; pass Subscriber references at construction time.
  • Subscriber::subscribe() (and the topic-taking constructor) takes an autoware::agnocast_wrapper::Node *, so this wrapper requires a Method 2 node. There is no overload for a plain rclcpp::Node.

Usage example#

#include <autoware/agnocast_wrapper/message_filters.hpp>

using namespace autoware::agnocast_wrapper::message_filters;

// 1. Create subscribers
Subscriber<sensor_msgs::msg::Image> image_sub;
Subscriber<sensor_msgs::msg::CameraInfo> info_sub;
image_sub.subscribe(node, "/camera/image", rmw_qos_profile_sensor_data);
info_sub.subscribe(node, "/camera/info", rmw_qos_profile_sensor_data);

// 2. Create synchronizer
using Policy = sync_policies::ApproximateTime<
    sensor_msgs::msg::Image, sensor_msgs::msg::CameraInfo>;
Synchronizer<Policy> sync(Policy(10), image_sub, info_sub);

// 3. Register callback. Mirrors `::message_filters::Synchronizer::registerCallback` —
//    pass a member-function pointer and `this`, or a `std::bind` result, or any other
//    callable convertible to `void(const AUTOWARE_MESSAGE_CONST_SHARED_PTR(M0) &,
//                                    const AUTOWARE_MESSAGE_CONST_SHARED_PTR(M1) &)`.
//    Returns a `::message_filters::Connection` for later `.disconnect()`.
auto conn = sync.registerCallback(&MyNode::onSynchronized, this);
// Note: `conn` going out of scope does NOT unregister the callback.
// Call conn.disconnect() explicitly if you need to remove it later.
// Equivalent form (still supported):
// sync.registerCallback(std::bind(
//   &MyNode::onSynchronized, this, std::placeholders::_1, std::placeholders::_2));

The callback method signature should use const references:

void onSynchronized(
  const AUTOWARE_MESSAGE_CONST_SHARED_PTR(sensor_msgs::msg::Image) & img,
  const AUTOWARE_MESSAGE_CONST_SHARED_PTR(sensor_msgs::msg::CameraInfo) & info);

Migration guide (from ::message_filters)#

Before After
#include <message_filters/subscriber.h> #include <autoware/agnocast_wrapper/message_filters.hpp>
message_filters::Subscriber<M> autoware::agnocast_wrapper::message_filters::Subscriber<M>
message_filters::Synchronizer<Policy> autoware::agnocast_wrapper::message_filters::Synchronizer<Policy>
message_filters::sync_policies::ApproximateTime<M0, M1> autoware::agnocast_wrapper::message_filters::sync_policies::ApproximateTime<M0, M1>
message_filters::sync_policies::ExactTime<M0, M1> autoware::agnocast_wrapper::message_filters::sync_policies::ExactTime<M0, M1>

tf2 Support#

This package provides wrapper types for tf2 (TransformListener, TransformBroadcaster, StaticTransformBroadcaster, Buffer) in the autoware::agnocast_wrapper namespace. The listener and broadcasters transparently switch between their tf2_ros and agnocast implementations at runtime, depending on whether the given node is running in Agnocast mode.

The node-taking constructors require a Method 2 node (autoware::agnocast_wrapper::Node). This is needed because an AgnocastOnly executor does not spin a plain tf2_ros::TransformListener (a ROS 2 subscription); routing /tf through Agnocast keeps tf callbacks firing.

All four wrapper types are non-copyable and non-movable (the backend is chosen at construction and bound by reference), so hold them by value or in a unique_ptr.

Buffer aliases to agnocast::Buffer in Agnocast-enabled builds and tf2_ros::Buffer otherwise. The agnocast variant intentionally omits APIs that would silently break under an AgnocastOnly executor (currently waitForTransform / setCreateTimerInterface and the /tf2_frames debug service), so misuse is caught at compile time.

Usage example#

#include <autoware/agnocast_wrapper/node.hpp>
#include <autoware/agnocast_wrapper/tf2.hpp>

class MyNode : public autoware::agnocast_wrapper::Node
{
public:
  MyNode()
  : autoware::agnocast_wrapper::Node("my_node"), tf_buffer_(this->get_clock())
  {
    // `*this` is a node derived from autoware::agnocast_wrapper::Node.
    tf_listener_ = std::make_unique<autoware::agnocast_wrapper::TransformListener>(
      tf_buffer_, *this);
    tf_broadcaster_ = std::make_unique<autoware::agnocast_wrapper::TransformBroadcaster>(*this);
  }

private:
  autoware::agnocast_wrapper::Buffer tf_buffer_;
  std::unique_ptr<autoware::agnocast_wrapper::TransformListener> tf_listener_;
  std::unique_ptr<autoware::agnocast_wrapper::TransformBroadcaster> tf_broadcaster_;
};

Migration guide (from tf2_ros)#

Before After
#include <tf2_ros/transform_listener.hpp> #include <autoware/agnocast_wrapper/tf2.hpp>
#include <tf2_ros/buffer.hpp> #include <autoware/agnocast_wrapper/tf2.hpp>
#include <tf2_ros/transform_broadcaster.hpp> #include <autoware/agnocast_wrapper/tf2.hpp>
#include <tf2_ros/static_transform_broadcaster.hpp> #include <autoware/agnocast_wrapper/tf2.hpp>
tf2_ros::TransformListener autoware::agnocast_wrapper::TransformListener
tf2_ros::Buffer autoware::agnocast_wrapper::Buffer
tf2_ros::TransformBroadcaster autoware::agnocast_wrapper::TransformBroadcaster
tf2_ros::StaticTransformBroadcaster autoware::agnocast_wrapper::StaticTransformBroadcaster

Diagnostic Updater Support#

This package provides a wrapper autoware::agnocast_wrapper::diagnostic_updater::Updater for diagnostic_updater::Updater. The wrapper transparently switches between diagnostic_updater::Updater and agnocast::Updater at runtime, so nodes inheriting from autoware::agnocast_wrapper::Node can use the same idiom in both modes.

The diagnostic_updater.period and diagnostic_updater.use_fqn parameters are declared identically in both modes, so behavior remains consistent.

Current limitations#

  • Only the Updater(autoware::agnocast_wrapper::Node*, double) constructor is supported. The upstream interface-pointer constructor and Updater(NodeT, double) template overload are intentionally hidden in both modes, so source code stays portable between agnocast-enabled and disabled builds.
  • The wrapper does not inherit from DiagnosticTaskVector, so getTasks() is not available.
  • The wrapper is non-copyable and non-movable; verbose_ is bound by reference to the underlying impl.

Usage example#

#include <autoware/agnocast_wrapper/diagnostic_updater.hpp>

class MyNode : public autoware::agnocast_wrapper::Node
{
public:
  explicit MyNode(const rclcpp::NodeOptions & options)
  : Node("my_node", options), updater_(this)
  {
    updater_.setHardwareID("my_hardware");
    updater_.add("status", this, &MyNode::diagnose);
  }

private:
  void diagnose(diagnostic_updater::DiagnosticStatusWrapper & stat) {
    stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "running");
  }

  autoware::agnocast_wrapper::diagnostic_updater::Updater updater_;
};

Migration guide (from diagnostic_updater::Updater)#

Before After
#include <diagnostic_updater/diagnostic_updater.hpp> #include <autoware/agnocast_wrapper/diagnostic_updater.hpp>
diagnostic_updater::Updater updater_{this}; autoware::agnocast_wrapper::diagnostic_updater::Updater updater_{this};

The add() / removeByName() / setHardwareID() / setHardwareIDf() / broadcast() / force_update() / setPeriod() / getPeriod() APIs and the verbose_ field behave the same as the upstream diagnostic_updater::Updater.

Note: DiagnosticTask subclasses (e.g. FrequencyStatus, TimeStampStatus, Heartbeat) defined in diagnostic_updater can be added via updater_.add(task) unchanged.

Polling Subscriber (polling:: namespace)#

autoware::agnocast_wrapper::polling::create_polling_subscriber<MessageT>(node, topic, qos) creates a polling (take-based) subscriber whose take_data() returns a plain std::shared_ptr<const MessageT> in both ENABLE_AGNOCAST modes. In agnocast mode the message stays in shared memory and is aliased into the returned shared_ptr (zero-copy, no payload copy); in rclcpp mode it reuses autoware_utils_rclcpp::InterProcessPollingSubscriber.

take_data() contract#

  • Returns the latest message, or nullptr when none is available.
  • Re-delivery is governed by the policy tag and is identical across backends:
    • polling_policy::Latest (default): re-delivers the cached message.
    • polling_policy::Newest: returns nullptr until a new message arrives.
  • polling_policy::All is rejected at compile time (take_data() returns a single message, not a vector).
  • The returned std::shared_ptr has the same lifetime semantics in both modes.

Usage example#

#include <autoware/agnocast_wrapper/polling_subscriber.hpp>

class MyNode : public autoware::agnocast_wrapper::Node
{
public:
  explicit MyNode(const rclcpp::NodeOptions & options) : Node("my_node", options)
  {
    namespace polling = autoware::agnocast_wrapper::polling;
    sub_ = polling::create_polling_subscriber<nav_msgs::msg::Odometry>(this, "~/input/odometry", 1);
  }

  void on_timer()
  {
    const std::shared_ptr<const nav_msgs::msg::Odometry> msg = sub_->take_data();
    if (!msg) {
      return;
    }
    // use msg->...
  }

private:
  autoware::agnocast_wrapper::polling::PollingSubscriber<nav_msgs::msg::Odometry>::SharedPtr sub_;
};

How to Enable/Disable Agnocast on Build#

To build Autoware with Agnocast:

export ENABLE_AGNOCAST=1
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release

To build Autoware without Agnocast (default behavior):

colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release

To explicitly disable Agnocast when it has been previously enabled:

unset ENABLE_AGNOCAST
# or
export ENABLE_AGNOCAST=0

To rebuild a specific package without Agnocast after it was previously built with Agnocast:

rm -Rf ./install/<package_name> ./build/<package_name>
export ENABLE_AGNOCAST=0
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release --packages-select <package_name>

To rebuild a specific package with Agnocast after it was previously built without it:

rm -Rf ./install/<package_name> ./build/<package_name>
export ENABLE_AGNOCAST=1
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release --packages-select <package_name>

Please note that the ENABLE_AGNOCAST environment variable may not behave as expected in the following scenario:

  • Package A depends on build artifacts from Package B
  • Both A and B were previously built with Agnocast enabled
  • Rebuilding only Package A with ENABLE_AGNOCAST=0 will not be sufficient, as compile options enabling Agnocast may propagate from Package B

Example:

  • A = autoware_occupancy_grid_map_outlier_filter
  • B = autoware_pointcloud_preprocessor

In such cases, rebuild both A and B with Agnocast disabled to ensure consistency. As a best practice, we recommend keeping the value of ENABLE_AGNOCAST consistent within a workspace to avoid unintentional mismatches and simplify build management.

How to Enable Agnocast at Runtime#

When Agnocast is enabled at build time, the heaphook shared library must be preloaded at runtime via LD_PRELOAD, and component containers must be replaced with their Agnocast equivalents. This package provides agnocast_env.launch.xml (and its Python equivalent agnocast_env.launch.py) which handles both of these concerns based on the ENABLE_AGNOCAST environment variable.

Discovery agent#

Including agnocast_env.launch.xml / .py also launches the agnocast_discovery_agent node automatically, once per launch tree and only when ENABLE_AGNOCAST=1. It publishes the local Agnocast state on /_agnocast_discovery, which the ros2 topic list_agnocast / info_agnocast / hz_agnocast commands rely on.

Provided Variables#

After including agnocast_env.launch.xml (or agnocast_env.launch.py), the following variables are available (in Python launch files, reference them via LaunchConfiguration):

Variable Description
ld_preload_value LD_PRELOAD value with the heaphook library prepended (when Agnocast is enabled)
container_package Resolved component container package name (rclcpp_components or agnocast_components)
container_executable Resolved component container executable name

Launch Arguments#

Argument Default Description
agnocast_heaphook_path /opt/ros/$ROS_DISTRO/lib/libagnocast_heaphook.so (falls back to humble) Path to the heaphook shared library
use_multithread false Use the multi-threaded component container (component_container_mt)
use_agnocast $(env ENABLE_AGNOCAST 0) Per-node override (1/0). Usually left unset; defaults to the ENABLE_AGNOCAST environment variable

The container_executable is resolved as follows:

use_multithread ENABLE_AGNOCAST=0 ENABLE_AGNOCAST=1
false component_container agnocast_component_container
true component_container_mt agnocast_component_container_cie

Examples (XML)#

Basic usage with a single node:

<include file="$(find-pkg-share autoware_agnocast_wrapper)/launch/agnocast_env.launch.xml"/>

<node pkg="my_package" exec="my_node" name="my_node">
  <env name="LD_PRELOAD" value="$(var ld_preload_value)"/>
</node>

Using a component container with multi-threading:

<include file="$(find-pkg-share autoware_agnocast_wrapper)/launch/agnocast_env.launch.xml">
  <arg name="use_multithread" value="true"/>
</include>

<node_container pkg="$(var container_package)" exec="$(var container_executable)" name="my_container">
  <env name="LD_PRELOAD" value="$(var ld_preload_value)"/>
</node_container>

Disabling Agnocast for a single include (debugging / emergency fallback):

<include file="$(find-pkg-share autoware_agnocast_wrapper)/launch/agnocast_env.launch.xml">
  <arg name="use_agnocast" value="0"/>
</include>

<node pkg="my_package" exec="my_node" name="my_node">
  <env name="LD_PRELOAD" value="$(var ld_preload_value)"/>
</node>

Even when the workspace is built with ENABLE_AGNOCAST=1, passing use_agnocast to a single include forces that node (or container) back to the plain rclcpp path without touching the rest of the launch tree. Use it to temporarily disable Agnocast for one node while debugging, or as an emergency fallback when a specific node misbehaves under Agnocast.

Examples (Python)#

A Python launch file (agnocast_env.launch.py) is also provided with the same functionality. It accepts the same use_agnocast argument and sets the same launch configurations (ld_preload_value, container_package, container_executable) that can be referenced via LaunchConfiguration.

Basic usage with a single node:

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch.substitutions import PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare

def generate_launch_description():
    agnocast_env = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            PathJoinSubstitution([
                FindPackageShare("autoware_agnocast_wrapper"),
                "launch",
                "agnocast_env.launch.py",
            ])
        ),
    )

    my_node = Node(
        package="my_package",
        executable="my_node",
        name="my_node",
        additional_env={"LD_PRELOAD": LaunchConfiguration("ld_preload_value")},
    )

    return LaunchDescription([agnocast_env, my_node])

Using a component container with multi-threading:

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
from launch.substitutions import PathJoinSubstitution
from launch_ros.actions import ComposableNodeContainer
from launch_ros.substitutions import FindPackageShare

def generate_launch_description():
    agnocast_env = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            PathJoinSubstitution([
                FindPackageShare("autoware_agnocast_wrapper"),
                "launch",
                "agnocast_env.launch.py",
            ])
        ),
        launch_arguments={"use_multithread": "true"}.items(),
    )

    container = ComposableNodeContainer(
        name="my_container",
        namespace="",
        package=LaunchConfiguration("container_package"),
        executable=LaunchConfiguration("container_executable"),
        additional_env={"LD_PRELOAD": LaunchConfiguration("ld_preload_value")},
        composable_node_descriptions=[],
    )

    return LaunchDescription([agnocast_env, container])

The same use_agnocast override works here too, via launch_arguments={"use_agnocast": "0"}.items().

This ensures that only the intended nodes receive the heaphook, rather than all nodes in the launch tree.