diff --git a/other/pages/rosdoc2.html b/other/pages/rosdoc2.html index 5937b4e..9aa308b 100644 --- a/other/pages/rosdoc2.html +++ b/other/pages/rosdoc2.html @@ -1069,6 +1069,15 @@ + + +
  • + + + Documenting Metapackages + + +
  • @@ -1120,6 +1129,15 @@ + + +
  • + + + Documenting Metapackages + + +
  • @@ -1160,6 +1178,46 @@

    Linking to Other Packages

    An example of this can be seen in the documentation for image_pipeline, where we want to link to the documentation for each of the packages within the metapackage.

    +

    Documenting Metapackages

    +

    A metapackage is one that contains no code and exists basically to bundle up a set of +dependencies. For instance image_pipeline is a repository containing several packages +for image processing - and the image_pipeline metapackage depends on every package +in the repository to make it easier to install with apt install ros-<distro>-image-pipeline +rather than specifying each package individually. This does lead to an issue with +rosdoc2, which will fail if there is no code to document. If you want to add +tutorials or documentation to a metapackage, you need to use a rosdoc2.yaml file +to properly build your documentation (which we assume is located in the doc +folder:

    +
    type: 'rosdoc2 config'
    +version: 1
    +
    +---
    +
    +settings:
    +    # Not generating any documentation of code
    +    generate_package_index: false
    +    always_run_doxygen: false
    +    enable_breathe: false
    +    enable_exhale: false
    +    always_run_sphinx_apidoc: false
    +    # Lie to rosdoc2, claim to be a python package
    +    override_build_type: 'ament_python'
    +    # Lie to rosdoc2 again, say your source is in doc folder
    +    python_source: 'doc'
    +
    +builders:
    +   # Configure Sphinx with the location of the docs:
    +    - sphinx: {
    +        name: 'image_pipeline',
    +        sphinx_sourcedir: 'doc',
    +        output_dir: ''
    +    }
    +
    +

    Don't forget to add your yaml file to the package.xml:

    +
    <export>
    +  <rosdoc2>rosdoc2.yaml</rosdoc2>
    +</export>
    +
    diff --git a/search/search_index.js b/search/search_index.js index 45ed88e..767b783 100644 --- a/search/search_index.js +++ b/search/search_index.js @@ -1 +1 @@ -var __index = {"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"index.html","title":"Home","text":""},{"location":"index.html#ros-2-cookbook","title":"ROS 2 Cookbook","text":"

    A collection of snippets ranging multiple topics in ROS 2.

    "},{"location":"literate-nav.html","title":"Literate nav","text":""},{"location":"client_libraries/index.html","title":"Client Libraries","text":""},{"location":"client_libraries/index.html#client-libraries","title":"Client libraries","text":""},{"location":"client_libraries/index.html#c-rclcpp","title":"C++ (rclcpp)","text":""},{"location":"client_libraries/index.html#python-rclpy","title":"Python (rclpy)","text":""},{"location":"client_libraries/rclcpp/index.html","title":"C++","text":""},{"location":"client_libraries/rclcpp/index.html#c-rclcpp","title":"C++ (rclcpp)","text":""},{"location":"client_libraries/rclcpp/index.html#nodes","title":"Nodes","text":""},{"location":"client_libraries/rclcpp/index.html#parameters","title":"Parameters","text":""},{"location":"client_libraries/rclcpp/index.html#logging","title":"Logging","text":""},{"location":"client_libraries/rclcpp/index.html#time","title":"Time","text":""},{"location":"client_libraries/rclcpp/index.html#tf2","title":"TF2","text":""},{"location":"client_libraries/rclcpp/index.html#point-clouds","title":"Point Clouds","text":""},{"location":"client_libraries/rclcpp/index.html#workarounds","title":"Workarounds","text":""},{"location":"client_libraries/rclcpp/index.html#initialization","title":"Initialization","text":""},{"location":"client_libraries/rclcpp/initialization.html","title":"rclcpp: Initialization","text":""},{"location":"client_libraries/rclcpp/initialization.html#shared_from_this-cannot-be-used-in-constructor","title":"shared_from_this Cannot Be Used in Constructor","text":"

    Sometime, you need to initialize some components outside of the constructor of your node. For example, you may want to initialize an object by passing a shared pointer to the node to it. This is not possible in the constructor, because the node is not fully initialized yet.

    The following pattern can be used to initialize components outside of the constructor of your node. This pattern was found through this comment.

    // Class declaration\n\nclass InitializedComponent\n{\npublic:\n  COMPOSITION_PUBLIC\n  explicit InitializedComponent(const rclcpp::NodeOptions & options);\n\n  COMPOSITION_PUBLIC\n  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr\n  get_node_base_interface() const;\n\nprivate:\n  rclcpp::Node::SharedPtr node_;\n};\n\n// Class implementation\n\nInitializedComponent::InitializedComponent(const rclcpp::NodeOptions & options)\n: node_(std::make_shared<rclcpp::Node>(\"node\", options))\n{\n  /* Do some initialization here */\n}\n\nrclcpp::node_interfaces::NodeBaseInterface::SharedPtr\nInitializedComponent::get_node_base_interface() const\n{\n  return this->node_->get_node_base_interface();\n}\n
    "},{"location":"client_libraries/rclcpp/initialization.html#overriding-default-parameters","title":"Overriding Default Parameters","text":"

    This is a pattern I find especially helpful when writing tests, where I want to create several tests with different parameters but would rather not maintain separate YAML or launch files with parameters:

    rclcpp::NodeOptions options;\noptions.parameter_overrides(\n  {\n    {\"my_namespace.my_parameter\", 5.2},\n    {\"another_parameter\", \"new value\"},\n  }\n);\nauto node = std::make_shared<rclcpp::Node>(\"node_name\", options);\n

    "},{"location":"client_libraries/rclcpp/logging.html","title":"rclcpp: Logging","text":""},{"location":"client_libraries/rclcpp/logging.html#changing-the-logging-level","title":"Changing the logging level","text":"
    #include <rclcpp/logger.hpp>\n\nrclcpp::get_logger(\"nav2_costmap_2d\").set_level(rclcpp::Logger::Level::Debug);\n
    "},{"location":"client_libraries/rclcpp/nodes.html","title":"rclcpp: Nodes and Components","text":""},{"location":"client_libraries/rclcpp/nodes.html#creating-a-component","title":"Creating a Component","text":"

    ROS 2 introduces components, which are nodes that can be run together in a single process. The benefits of using node compositions has been documented in a recent paper, Impact of ROS 2 Node Composition in Robotic Systems. Given the ease of use and the provided tooling, it really makes sense to make just about every node a component, and then let the rclcpp_components tooling create a node executable for you:

    #include <rclcpp/rclcpp.hpp>\n\nnamespace my_pkg\n{\n\nclass MyComponent : public rclcpp::Node\n{\npublic:\n  MyComponent(const rclcpp::NodeOptions& options)\n  : rclcpp::Node(\"node_name\", options)\n  {\n    // Do all your setup here - subscribers/publishers/timers\n    // After you return, an executor will be started\n\n    // Note: you cannot use shared_from_this()\n    //       here because the node is not fully\n    //       initialized.\n  }\n};\n\n}  // namespace my_pkg\n\n#include \"rclcpp_components/register_node_macro.hpp\"\nRCLCPP_COMPONENTS_REGISTER_NODE(my_pkg::MyComponent)\n

    The in the CMakeLists.txt:

    add_library(my_component SHARED\n  src/my_component.cpp\n)\nament_target_dependencies(my_component\n  rclcpp\n  rclcpp_components\n)\n\n# Also add a node executable which simply loads the component\nrclcpp_components_register_node(my_component\n  PLUGIN \"my_pkg::MyComponent\"\n  EXECUTABLE my_node\n)\n
    "},{"location":"client_libraries/rclcpp/nodes.html#executors","title":"Executors","text":"

    To run an executor in a thread:

    #include <thread>\n#include <rclcpp/rclcpp.hpp>\n#include <rclcpp/executors/single_threaded_executor.hpp>\n\nrclcpp::executors::SingleThreadedExecutor executor;\n\n// Node is rclcpp::Node::SharedPtr instance\nexecutor.add_node(node);\nstd::thread executor_thread(\n  std::bind(&rclcpp::executors::SingleThreadedExecutor::spin,\n            &executor));\n
    "},{"location":"client_libraries/rclcpp/parameters.html","title":"rclcpp: Parameters","text":"

    Parameters need to be declared. At the same time, you can get the value if you are not planning to update the value again later:

    // node is an instance of rclcpp::Node\n// 42 is a great default for a parameter\nint param = node.declare_parameter<int>(\"my_param_name\", 42);\n

    To get the value:

    int param;\nnode.get_parameter(\"my_param_name\", param);\n
    "},{"location":"client_libraries/rclcpp/parameters.html#dynamic-parameters","title":"Dynamic Parameters","text":"

    In ROS 2, all parameters can be dynamically updated through a ROS 2 service (there is no need to define duplicate stuff as with dynamic reconfigure).

    The example below works in Eloquent or later (earlier ROS 2 releases supported only a single callback and had a slightly different API). See the documentation for rclcpp::ParameterType for valid types.

    #include <vector>\n#include <rclcpp/rclcpp.hpp>\n\nclass MyNode : public rclcpp::Node\n{\npublic:\n  MyNode()\n  {\n    // Declare parameters first\n\n    // Then create callback\n    param_cb_ = this->add_on_set_parameters_callback(\n      std::bind(&MyNode::updateCallback, this, std::placeholders::_1));\n  }\n\nprivate:\n  // This will get called whenever a parameter gets updated\n  rcl_interfaces::msg::SetParametersResult updateCallback(\n    const std::vector<rclcpp::Parameter> & parameters)\n  {\n    rcl_interfaces::msg::SetParametersResult result;\n    result.successful = true;\n\n    for (const rclcpp::Parameter & param : parameters)\n    {\n      if (param.get_name() == \"my_param_name\")\n      {\n        if (param.get_type() != rclcpp::ParameterType::PARAMETER_STRING)\n        {\n          result.successful = false;\n          result.reason = \"my_param_name must be a string\";\n          break;\n        }\n\n        // Optionally do something with parameter\n      }\n    }\n\n    return result;\n  }\n\n  // Need to hold a pointer to the callback\n  rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr param_cb_;\n};\n
    "},{"location":"client_libraries/rclcpp/parameters.html#porting-xml-arrays-to-ros-2","title":"Porting XML Arrays to ROS 2","text":"

    Complex parameter blocks could be parsed as XML in ROS 1. For instance, the robot_calibration package used a block like:

    models:\n - name: arm\n   type: chain\n   frame: wrist_roll_link\n - name: camera\n   type: camera3d\n   frame: head_camera_rgb_optical_frame\n

    In ROS 2, the common pattern is to make the array a list of just names, then have each name be a block of parameters:

    models:\n- arm\n- camera\narm:\n  type: chain3d\n  frame: wrist_roll_link\ncamera:\n  type: camera3d\n  frame: head_camera_rgb_optical_frame\n

    To parse such a block:

    std::vector<ModelParams> models;\nauto model_names = node->declare_parameter<std::vector<std::string>>(\"models\", std::vector<std::string>());\nfor (auto name : model_names)\n{\n  RCLCPP_INFO(logger, \"Adding model: %s\", name.c_str());\n  ModelParams params;\n  params.name = name;\n  params.type = node->declare_parameter<std::string>(name + \".type\", std::string());\n  params.frame = node->declare_parameter<std::string>(name + \".frame\", std::string());\n  params.param_name = node->declare_parameter<std::string>(name + \".param_name\", std::string());\n  models.push_back(params);\n}\n
    "},{"location":"client_libraries/rclcpp/pcl.html","title":"rclcpp: Point Clouds","text":"

    The sensor_msgs/PointCloud2 is a very common type of ROS message for processing perception data in ROS. It is also one of the most complex messages to actually interpret.

    The complexity of the message derives from the fact that it holds arbitrary fields in a single giant data store. This allows the PointCloud2 message to work with any type of cloud (for instance, XYZ points only, XYZRGB, or even XYZI), but adds a bit of complexity in accessing the data in the cloud.

    In ROS 1, there was a simpler PointCloud message, but this has been deprecated and will be removed in ROS 2 Galactic.

    "},{"location":"client_libraries/rclcpp/pcl.html#using-the-pointcloud2iterator","title":"Using the PointCloud2Iterator","text":"

    The sensor_msgs package provides a C++ PointCloud2Iterator which can be used to create, modify and access PointCloud2 messages.

    To create a new message:

    #include \"sensor_msgs/msg/point_cloud2.hpp\"\n#include \"sensor_msgs/point_cloud2_iterator.hpp\"\n\nsensor_msgs::msg::PointCloud2 msg;\n\n// Fill in the size of the cloud\nmsg.height = 480;\nmsg.width = 640;\n\n// Create the modifier to setup the fields and memory\nsensor_msgs::PointCloud2Modifier mod(msg);\n\n// Set the fields that our cloud will have\nmod.setPointCloud2FieldsByString(2, \"xyz\", \"rgb\");\n\n// Set up memory for our points\nmod.resize(msg.height * msg.width);\n\n// Now create iterators for fields\nsensor_msgs::PointCloud2Iterator<float> iter_x(cloud_msg, \"x\");\nsensor_msgs::PointCloud2Iterator<float> iter_y(cloud_msg, \"y\");\nsensor_msgs::PointCloud2Iterator<float> iter_z(cloud_msg, \"z\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_r(cloud_msg, \"r\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_g(cloud_msg, \"g\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_b(cloud_msg, \"b\");\n\nfor (; iter_x != iter_x.end(); ++iter_x, ++iter_y, ++iter_z, ++iter_r, ++iter_g, ++iter_b)\n{\n  *iter_x = 0.0;\n  *iter_y = 0.0;\n  *iter_z = 0.0;\n  *iter_r = 0;\n  *iter_g = 255;\n  *iter_b = 0;\n}\n
    "},{"location":"client_libraries/rclcpp/pcl.html#using-pcl","title":"Using PCL","text":"

    For a number of operations, you might want to convert to a pcl::PointCloud in order to use the extensive API of the Point Cloud Library.

    In ROS 1, the pcl_ros package allowed you to write a subscriber whose callback took a pcl::PointCloud directly, however this package has not yet been ported to ROS 2. Regardless, it is pretty straight forward to do the conversion yourself with the pcl_conversions package:

    #include \"pcl_conversions/pcl_conversions.h\"\n\nvoid cloud_callback(const sensor_msgs::msg::PointCloud2::SharedPtr msg)\n{\n  // PCL still uses boost::shared_ptr internally\n  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud =\n    boost::make_shared<pcl::PointCloud<pcl::PointXYZRGB>>();\n\n  // This will convert the message into a pcl::PointCloud\n  pcl::fromROSMsg(*msg, *cloud);\n}\n

    You can also go in the opposite direction:

    #include \"pcl_conversions/pcl_conversions.h\"\n\npcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;\nsensor_msgs::msg::PointCloud2 msg;\n\npcl::toROSMsg(*cloud, msg);\ncloud_publisher->publish(msg);\n
    "},{"location":"client_libraries/rclcpp/tf2.html","title":"rclcpp: TF2","text":"

    The TF2 library provides easy access to transformations. All of the examples below require a dependency on the tf2_ros package.

    "},{"location":"client_libraries/rclcpp/tf2.html#broadcasting-transforms","title":"Broadcasting Transforms","text":"
    #include <tf2_ros/transform_broadcaster.h>\nstd::unique_ptr<tf2_ros::TransformBroadcaster> broadcaster;\n\nbroadcaster = std::make_unique<tf2_ros::TransformBroadcaster>(nodePtr);\n\ngeometry_msgs::msg::TransformStamped transform;\ntransform.header.stamp = node->now();\ntransform.header.frame_id = \"odom\";\ntransform.child_frame_id = \"base_link\";\n\n// Fill in transform.transform.translation\n// Fill in transform.transform.rotation\n\nbroadcaster->sendTransform(transform);\n
    "},{"location":"client_libraries/rclcpp/tf2.html#listening-for-transforms","title":"Listening for Transforms","text":"
    #include \"tf2_ros/transform_listener.h\"\n\nstd::shared_ptr<tf2_ros::Buffer> tf_buffer;\nstd::shared_ptr<tf2_ros::TransformListener> tf_listener;\n\nrclcpp::Node node(\"name_of_node\");\n\ntf_buffer.reset(new tf2_ros::Buffer(node.get_clock()));\ntf_listener.reset(new tf2_ros::TransformListener(*tf_buffer_));\n
    "},{"location":"client_libraries/rclcpp/tf2.html#applying-transforms","title":"Applying Transforms","text":"

    TF2 can be extended by packages that provide implementations of transform. The tf2_geometry_msgs package provides these for _geometry_msgs. The example below uses geometry_msgs::msg::PointStamped - but this should work for any data type in geometry_msgs:

    #include <tf2_geometry_msgs/tf2_geometry_msgs.h>\n\ngeometry_msg::msgs::PointStamped in, out;\nin.header.frame_id = \"source_frame\";\n\ntry\n{\n  tf_buffer->transform(in, out, \"target_frame\");\n}\ncatch (const tf2::TransformException& ex)\n{\n  RCLCPP_ERROR(rclcpp::get_logger(\"logger_name\"), \"Could not transform point.\");\n}\n

    The transform function can also take a timeout. It will then wait for the transform to be available up to that amount of time:

    tf_buffer->transform(in, out, \"target_frame\", tf2::durationFromSec(1.0));\n
    "},{"location":"client_libraries/rclcpp/tf2.html#get-latest-transform","title":"Get Latest Transform","text":"

    A common work flow is to get the \"latest\" transform. In ROS 2, this can be accomplished using tf2::TimePointZero, but requires using lookupTransform and then calling doTransform (which is basically what transform does internally):

    geometry_msgs::msg::PointStamped in, out;\n\ngeometry_msgs::msg::TransformStamped transform =\n   tf_buffer->lookupTransform(\"target_frame\",\n                              in.header.frame_id,\n                              tf2::TimePointZero);\n\ntf2::doTransform(in, out, transform);\n
    "},{"location":"client_libraries/rclcpp/tf2.html#constructing-transform-from-euler-angles","title":"Constructing Transform from Euler Angles","text":"

    There are numerous ways to do this (using Eigen, KDL, etc) - but it is also possible with only tf2 APIs:

    #include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>\n\ntf2::Quaternion quat;\n// While the documentation for tf2 orders the names of these as yaw, pitch, roll,\n// it specifies that yaw = rotation about Y, which is not what most of us expect\nquat.setEuler(pitch, roll, yaw);\n\ngeometry_msgs::msg::TransformStamped transform;\ntransform.transform.rotation = tf2::toMsg(quat);\n// Probably also fill in transform.header and transform.transform.translation\n\n// Can now use the transform with tf2::doTransform()\n
    "},{"location":"client_libraries/rclcpp/tf2.html#getting-yaw-angle-from-quaternion","title":"Getting Yaw Angle from Quaternion","text":"
    #include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>\n#include <tf2/utils.hpp>\n\ngeometry_msgs::msg::Pose pose;\ndouble yaw = tf2::getYaw(pose.orientation);\n

    [!NOTE] tf2::getYaw requires some pieces from tf2_geometery_msgs/tf2_geometry_msgs.hpp but cannot depend on them because it would create a circular dependency. This means you absolutely need to include tf2_geometry_msgs BEFORE tf2/utils or you will get an undefined reference to tf2::fromMsg

    "},{"location":"client_libraries/rclcpp/tf2.html#transform-from-eigenisometry3d","title":"Transform from Eigen::Isometry3d","text":"
    #include <tf2_eigen/tf2_eigen.hpp>\n\nEigen::Isometry3d map_to_odom;\ngeometry_msgs::msg::TransformStamped transform = tf2::eigenToTransform(map_to_odom);\n// Fill in header, child_frame_id\n
    "},{"location":"client_libraries/rclcpp/time.html","title":"rclcpp: Time","text":"

    The rclcpp::Time and rclcpp::Duration are a significant departure from their ROS 1 equivalents, but are more closely related to std::chrono. For an in-depth discussion comparing with std::chrono, see this discussion on ROS Discourse.

    When porting certain ROS 1 libraries, there may be significant usage of timestamps as floating-point seconds. To get floating point seconds from an _rclcpp::Time_:

    // node is instance of rclcpp::Node\nrclcpp::Time t = node.now();\ndouble seconds = t.seconds();\n

    There is no constructor for Time from seconds represented by a floating point, so you first need to convert to nanoseconds:

    rclcpp::Time t(static_cast<uin64_t>(seconds * 1e9));\n

    rclcpp::Duration does have functions to go both directions:

    rclcpp::Duration d = rclcpp::Duration::from_seconds(1.0);\ndouble seconds = d.seconds();\n

    Unlike ROS 1, sleeping does NOT occur from a Duration instance:

    rclcpp::sleep_for(std::chrono::milliseconds(50));\n
    "},{"location":"client_libraries/rclcpp/time.html#api-documentation-links","title":"API Documentation Links","text":""},{"location":"client_libraries/rclcpp/workarounds.html","title":"rclcpp: Workarounds","text":""},{"location":"client_libraries/rclcpp/workarounds.html#lazy-publishers","title":"Lazy Publishers","text":"

    Prior to the Iron release, ROS 2 did not support subscriber connect callbacks. This code creates a function which is called periodically to check if we need to start or stop subscribers:

    #include <rclcpp/rclcpp.hpp>\n#include <std_msgs/msg/float64.hpp>\n\nclass LazyPublisherEx : rclcpp::Node\n{\npublic:\n  LazyPublisherEx(const rclcpp::NodeOptions & options) :\n    Node(\"lazy_ex\", options)\n  {\n    // Setup timer\n    timer = this->create_wall_timer(\n      std::chrono::seconds(1),\n      std::bind(&LazyPublisher::periodic, this));\n  }\n\nprivate:\n  void periodic()\n  {\n    if (pub_.get_subscription_count() > 0)\n    {\n        // We have a subscriber, do any setup required\n    }\n    else\n    {\n        // Subscriber has disconnected, do any shutdown\n    }\n  }\n\n  rclcpp::Publisher<std_msgs::msg::Float64>::SharedPtr pub_;\n  rclcpp::TimerBase::SharedPtr timer_;\n};\n

    The same can be done when using image transport, you simply have to change from get_subscription_count to getNumSubscribers:

    #include <rclcpp/rclcpp.hpp>\n#include <image_transport/image_transport.h>\n\nclass LazyPublisherEx : rclcpp::Node\n{\npublic:\n  LazyPublisherEx(const rclcpp::NodeOptions & options) :\n    Node(\"lazy_ex\", options)\n  {\n    // Setup timer\n    timer = this->create_wall_timer(\n      std::chrono::seconds(1),\n      std::bind(&LazyPublisher::periodic, this));\n  }\n\nprivate:\n  void periodic()\n  {\n    if (pub_.getNumSubscribers() > 0)\n    {\n        // We have a subscriber, do any setup required\n    }\n    else\n    {\n        // Subscriber has disconnected, do any shutdown\n    }\n  }\n\n  image_transport::CameraPublisher pub_;\n  rclcpp::TimerBase::SharedPtr timer_;\n};\n
    "},{"location":"client_libraries/rclpy/index.html","title":"Python","text":""},{"location":"client_libraries/rclpy/index.html#python-rclpy","title":"Python (rclpy)","text":""},{"location":"client_libraries/rclpy/index.html#nodes","title":"Nodes","text":""},{"location":"client_libraries/rclpy/index.html#parameters","title":"Parameters","text":""},{"location":"client_libraries/rclpy/index.html#time","title":"Time","text":""},{"location":"client_libraries/rclpy/index.html#tf2","title":"TF2","text":""},{"location":"client_libraries/rclpy/nodes.html","title":"rclpy: Node Basics","text":"

    Most nodes have publishers and subscribers, both of which are creating by calling functions of the Node instance:

    import rclpy\nfrom rclpy.node import Node\n\nfrom std_msgs.msg import String\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__('my_node_name')\n\n        self.publisher = self.create_publisher(String, 'output_topic', 10)\n        self.subscription = self.create_subscription(\n            String,\n            'input_topic',\n            self.callback,\n            10)\n\n    def callback(self, msg):\n        self.get_logger().info(\"Recieved: %s\" % msg.data)\n        self.publisher.publish(msg)\n\nif __name___ == \"__main__\":\n    rclpy.init()\n    my_node = MyNode()\n    rclpy.spin(my_node)\n    my_node.destroy_node()  # cleans up pub-subs, etc\n    rclpy.shutdown()\n
    "},{"location":"client_libraries/rclpy/nodes.html#shutdown-hooks","title":"Shutdown Hooks","text":"

    ROS 1 had rospy.on_shutdown() - but there is not an equivalent in ROS 2. It really is not needed though, since we manually shut things down rather than was the case in rospy which used many global variables:

    try:\n    rclpy.spin(my_node)\nexcept KeyboardInterrupt:\n    pass\nfinally:\n    my_node.on_shutdown()  # do any custom cleanup\n    my_node.destroy_node()\n    rclpy.shutdown()\n
    "},{"location":"client_libraries/rclpy/parameters.html","title":"rclpy: Parameters","text":""},{"location":"client_libraries/rclpy/parameters.html#declaring-and-accessing-parameters","title":"Declaring and Accessing Parameters","text":"
    # node is rclpy.node.Node instance\n# 42 is a great default for a parameter\nnode.declare_parameter('my_param_name', 42)\n\n# To get the value:\nparam = node.get_parameter('my_param_name').value\n
    "},{"location":"client_libraries/rclpy/parameters.html#declaring-multiple-parameters-at-once","title":"Declaring Multiple Parameters at Once","text":"

    There seems to be a fairly undocumentated part of the rclpy API:

    node.declare_parameters(\n    namespace='',\n    parameters=[\n        ('my_param_name', 'default value'),\n        ('my_other_param', 42)\n    ]\n)\n
    "},{"location":"client_libraries/rclpy/parameters.html#dynamic-parameters","title":"Dynamic Parameters","text":"

    In ROS 2, all parameters can be dynamically updated through a ROS 2 service (there is no need to define duplicate stuff as with dynamic reconfigure).

    from rcl_interfaces.msg import SetParametersResult\n\nimport rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__('my_node_name')\n\n        # Declare a parameter\n        self.declare_parameter('my_param_name', 42)\n\n        # Then create callback\n        self.add_on_set_parameters_callback(self.callback)\n\n    def callback(self, parameters):\n        result = SetParametersResult(successful=True)\n\n        for p in parameters:\n            if p.name == 'my_param_name':\n                if p.type_ != p.Type.INTEGER:\n                    result.successful = False\n                    result.reason = 'my_param_name must be an Integer'\n                    return result\n                if p.value < 20:\n                    result.successful = False\n                    result.reason = 'my_param_name must be >= 20;\n                    return result\n\n        # Return success, so updates are seen via get_parameter()\n        return result\n

    For an example of calling the set_parameters service, see ROS Answers

    "},{"location":"client_libraries/rclpy/tf2.html","title":"rclpy: TF2","text":"

    The TF2 library provides easy access to transformations. All of the examples below require a dependency on the tf2_ros package.

    "},{"location":"client_libraries/rclpy/tf2.html#listening-for-transforms","title":"Listening for Transforms","text":"
    import rclpy\nfrom rclpy.node import Node\nfrom tf2_ros.buffer import Buffer\nfrom tf2_ros.transform_listener import TransformListener\n\nclass MyNode(Node):\n    def __init__(self):\n        super().__init__('my_node')\n\n        self.buffer = Buffer()\n        self.listener = TransformListener(self.buffer, self)\n
    "},{"location":"client_libraries/rclpy/tf2.html#applying-transforms","title":"Applying Transforms","text":"

    TF2 can be extended by packages that provide implementations of transform. The tf2_geometry_msgs package provides these for _geometry_msgs. The example below uses geometry_msgs.msg.PointStamped - but this should work for any data type in geometry_msgs:

    from geometry_msgs.msg import PointStamped\nfrom tf2_ros.buffer import Buffer\nfrom tf2_ros.transform_listener import TransformListener\nimport tf2_geometry_msgs\n\n# Setup buffer/listener as above\n\np1 = PointStamped()\np1.header.frame_id = 'source_frame'\n# fill in p1\n\np2 = buffer.transform(p1, 'target_frame')\n
    "},{"location":"client_libraries/rclpy/tf2.html#using-the-latest-transform","title":"Using the Latest Transform","text":"
    from rclpy.time import Time\n\n# Setting the stamp to a blank Time() instance will use the latest available data\np1 = PointStamped()\np1.header.frame_id = 'source_frame'\np1.header.stamp = Time().to_msg()\n\np2 = buffer.transform(p1, 'target_frame')\n
    "},{"location":"client_libraries/rclpy/tf2.html#transformations","title":"Transformations","text":"

    In ROS 1, tf included the transformations module. tf2 has no similar module. It is recommended to use transforms3d Python package, which is available through apt on Ubuntu 22.04:

    sudo apt-get install python3-transforms3d\n

    Or via pip on other operating systems:

    sudo pip3 install transforms3d\n

    For instance, to rotate a point:

    import numpy as np\nfrom transforms3d.quaternion import quat2mat\n\n# Create rotation matrix from quaternion\nR = quat2mat([w, x, y, z])\n# Create a vector to rotate\nV = np.array([x, y, z]).reshape((3, 1))\n# Rotate the vector\nM = np.dot(R, V)\n\np = PointStamped()\np.point.x = M[0, 0]\np.point.y = M[1, 0]\np.point.z = M[2, 0]\n

    Additionally, in ROS 2 Humble there is the tf_transformations package which should make it as easy as changing your imports from tf.transformations to tf_transformations.

    "},{"location":"client_libraries/rclpy/time.html","title":"rclpy: Time","text":"

    To get the equivalent of rospy.Time.now(), you now need a ROS 2 node:

    import rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def some_func(self):\n        t = self.get_clock().now()\n        msg.header.stamp = t.to_msg()\n

    Converting from Duration to messages is common:

    import rclpy\nfrom rclpy.duration import Duration\n\nmsg.duration = Duration(seconds=1).to_msg()\n
    "},{"location":"client_libraries/rclpy/time.html#timers","title":"Timers","text":"

    Timers are created from the Node:

    import rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__(\"my_node\")\n\n        # Create a timer that fires every quarter second\n        timer_period = 0.25\n        self.timer = self.create_timer(timer_period, self.callback)\n\n    def callback(self):\n        self.get_logger().info(\"timer has fired\")\n
    "},{"location":"client_libraries/rclpy/time.html#rates","title":"Rates","text":"

    Using Rate objects in ROS 2 is a bit more complex than in ROS 1. Due to implementation details, we need to spin() or the sleep() function will block. This is most easily accomplished using a thread:

    import threading\n\n# Run spin in a thread, make thread daemon so we don't have to join it to exit\nthread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True)\nthread.start()\n\nrate = node.create_rate(10)\nwhile rclpy.ok():\n    print('This prints at 10hz')\n    rate.sleep()\n
    "},{"location":"other/pages/index.html","title":"Other","text":""},{"location":"other/pages/index.html#other","title":"Other","text":""},{"location":"other/pages/index.html#rosbag2","title":"rosbag2","text":""},{"location":"other/pages/index.html#cmake","title":"CMake","text":""},{"location":"other/pages/index.html#colcon","title":"colcon","text":""},{"location":"other/pages/index.html#command-line-tools","title":"Command Line Tools","text":""},{"location":"other/pages/index.html#ros2launch","title":"ros2launch","text":""},{"location":"other/pages/index.html#networking-dds-cyclonedds","title":"Networking (DDS & CycloneDDS)","text":""},{"location":"other/pages/index.html#package-documentation","title":"Package Documentation","text":""},{"location":"other/pages/index.html#qos-quality-of-service","title":"QoS (Quality of Service)","text":""},{"location":"other/pages/index.html#documenting-with-rosdoc2","title":"Documenting with rosdoc2","text":""},{"location":"other/pages/index.html#rosdep","title":"rosdep","text":""},{"location":"other/pages/bag.html","title":"rosbag2","text":""},{"location":"other/pages/bag.html#common-command-line","title":"Common Command Line","text":""},{"location":"other/pages/bag.html#api-tutorials","title":"API Tutorials","text":"

    The ROS documentation has tutorials for recording and playback, which are linked below. There is no official tutorial for reading from a bagfile in Python, the linked one comes from an MCAP tutorial:

    "},{"location":"other/pages/bag.html#converting-bag-files-from-ros-1","title":"Converting Bag Files from ROS 1","text":"

    The following works, assuming you are mainly using standard topics. For instance, I have converted a number of bagfiles intended for SLAM testing, which mainly consist of sensor_msgs::LaserScan, TF, and nav_msgs::Odometry data.

    The easiest route for converting bagfiles is to use rosbags:

    sudo pip3 install rosbags\nrosbag-convert bagfile_name.bag\n

    This will create a new folder with the name bagfile_name containing the SQLite file and the index file. At this point, you can inspect the bagfile:

    ros2 bag info bagfile_name\n\nFiles:             bagfile_name.db3\nBag size:          65.7 MiB\nStorage id:        sqlite3\nROS Distro:        rosbags\nDuration:          122.298s\nStart:             Jun 15 2014 21:41:49.861 (1402882909.861)\nEnd:               Jun 15 2014 21:43:52.159 (1402883032.159)\nMessages:          35187\nTopic information: Topic: odom | Type: nav_msgs/msg/Odometry | Count: 12141 | Serialization Format: cdr\n                   Topic: tf | Type: tf2_msgs/msg/TFMessage | Count: 16939 | Serialization Format: cdr\n                   Topic: base_scan | Type: sensor_msgs/msg/LaserScan | Count: 4884 | Serialization Format: cdr\n                   Topic: odom_combined | Type: nav_msgs/msg/Odometry | Count: 1223 | Serialization Format: cdr\n

    This bagfile is now useable in ROS 2. However, you can also go a bit further by compressing the bagfile, and migrating it to the new MCAP file format. First, create a YAML file to define the output format:

    # output_format.yaml\noutput_bags:\n- uri: bagfile_name_compressed\n  all: true\n  compression_mode: file\n  compression_format: zstd\n

    Now, run the conversion:

    ros2 bag convert -i bagfile_name -o output_format.yaml\n

    Inspecting the new bag, we can see that compression is very nice - a 75% reduction in file size for my typical SLAM bag files:

    ros2 bag info bagfile_name_compressed\n\nFiles:             bagfile_name_compressed.mcap.zstd\nBag size:          16.7 MiB\nStorage id:        mcap\nROS Distro:        rolling\nDuration:          122.298s\nStart:             Jun 15 2014 21:41:49.861 (1402882909.861)\nEnd:               Jun 15 2014 21:43:52.159 (1402883032.159)\nMessages:          35187\nTopic information: Topic: base_scan | Type: sensor_msgs/msg/LaserScan | Count: 4884 | Serialization Format: cdr\n                   Topic: odom | Type: nav_msgs/msg/Odometry | Count: 12141 | Serialization Format: cdr\n                   Topic: odom_combined | Type: nav_msgs/msg/Odometry | Count: 1223 | Serialization Format: cdr\n                   Topic: tf | Type: tf2_msgs/msg/TFMessage | Count: 16939 | Serialization Format: cdr\n
    "},{"location":"other/pages/bag.html#removing-tf-from-a-bagfile","title":"Removing TF from a Bagfile","text":"

    I have often found that I needed to remove problematic TF data from a bagfile, usually filtering out the odom->base_link transform so that I can replace it with a more sophisticated filtered one. In this particular example we just toss the whole message out - but you could selectively remove the individual transform within the message and serialize the edited message back up.

    import rosbag2_py\nfrom rclpy.serialization import deserialize_message\nfrom rosidl_runtime_py.utilities import get_message\n\nreader = rosbag2_py.SequentialReader()\nreader.open(\n    rosbag2_py.StorageOptions(uri='input_bag', storage_id='mcap'),\n    rosbag2_py.ConverterOptions(input_serialization_format='cdr',\n                                output_serialization_format='cdr')\n)\n\nwriter = rosbag2_py.SequentialWriter()\nwriter.open(\n    rosbag2_py.StorageOptions(uri='output_bag', storage_id='mcap'),\n    rosbag2_py.ConverterOptions('', '')\n)\n\n# First preprocess the topics\ntopic_types = reader.get_all_topics_and_types()\nfor topic_type in topic_types:\n    # Setup output bagfile - same topics as input\n    writer.create_topic(topic_type)\n    # Note the type if this is our TF data\n    if topic_type.name == '/tf':\n        tf_typename = topic_type.type\n\n# Now iterate through messages, copying over the ones we don't filter\nwhile reader.has_next():\n    topic, data, timestamp = reader.read_next()\n    filter_out = False\n    # Filter out odom tf messages\n    if topic == '/tf':\n        # Deserialize the message so we can look at it\n        msg_type = get_message(tf_typename)\n        msg = deserialize_message(data, msg_type)\n        # Iterate through the transforms in the message\n        for transform in msg.transforms:\n            if transform.header.frame_id == 'odom':\n                # Toss this message\n                filter_out = True\n                break\n    # Copy over message if it isn't odom\n    if not filter_out:\n        writer.write(topic, data, timestamp)\n
    "},{"location":"other/pages/cmake.html","title":"CMake","text":"

    While you don't need to know everything about CMake to use ROS 2, knowing a bit will really be helpful. You might be interested in the CMake tutorial which explains the basics of CMake.

    "},{"location":"other/pages/cmake.html#ament","title":"Ament","text":"

    Ament is a set of CMake modules specifically designed for ROS 2 with the intent of making CMake easier to use. See also the Ament CMake documentation.

    The basic structure of an ament package:

    cmake_minimum_required(VERSION 3.5)\nproject(my_package_name)\n\n# Default to C++14\nif(NOT CMAKE_CXX_STANDARD)\n  set(CMAKE_CXX_STANDARD 14)\nendif()\n\nif(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n  add_compile_options(-Wall -Wextra -Wpedantic)\nendif()\n\n# Find packages\nfind_package(ament_cmake REQUIRED)\nfind_package(rclcpp REQUIRED)\n\n# Include our own headers\ninclude_directories(include)\n\n# Create a node\nadd_executable(my_node src/my_node.cpp)\nament_target_dependencies(my_node\n  rclcpp\n  # Other ament dependencies\n  # This sets up include and linker paths\n)\n\nadd_library(my_library src/my_library.cpp)\nament_target_dependencies(my_library\n  rclcpp\n)\n\n# Install our headers\ninstall(\n  DIRECTORY include/\n  DESTINATION include\n)\n\n# Install our node and library\ninstall(TARGETS my_node my_library\n  ARCHIVE DESTINATION lib\n  LIBRARY DESTINATION lib\n  RUNTIME DESTINATION lib/${PACKAGE_NAME}\n)\n\n# Install some Python scripts\ninstall(\n  PROGRAMS\n    scripts/my_script.py\n  DESTINATION\n    lib/${PROJECT_NAME}\n)\n\n# Tell downstream packages where to find our headers\nament_export_include_directories(include)\n# Tell downstream packages our libraries to link against\nament_export_libraries(my_library)\n# Help downstream packages to find transitive dependencies\nament_export_dependencies(\n  rclcpp\n)\nament_package()\n
    "},{"location":"other/pages/cmake.html#linting-configuration","title":"Linting Configuration","text":"

    I prefer a more ROS 1-style code style. To allow braces to be on their own lines:

    if(BUILD_TESTING)\n  find_package(ament_cmake_cpplint)\n  ament_cpplint(FILTERS \"-whitespace/braces\" \"-whitespace/newline\")\nendif()\n
    "},{"location":"other/pages/cmake.html#installing-python-scripts","title":"Installing Python Scripts","text":"
    install(\n  PROGRAMS\n    scripts/script1.py\n    scripts/script2.py\n  DESTINATION lib/${PROJECT_NAME}\n)\n
    "},{"location":"other/pages/cmake.html#depending-on-messages-in-same-package","title":"Depending on Messages in Same Package","text":"

    It is generally best practice to put messages in separate packages, but sometimes, especially for drivers, you want the messages in the same package.

    The following example worked in earlier versions of ROS 2 - but the syntax has changed See the Implementing custom interfaces tutorial for newer ROS 2 distributions.

    # Note: this WILL NOT work in ROS 2 Humble or later\nfind_package(rosidl_default_generators REQUIRED)\n\n# Generate some messages\nrosidl_generate_interfaces(${PROJECT_NAME}\n  \"msg/MyMessage.msg\"\n)\n\n# Add a node which uses the messages\nadd_executable(my_node my_node.cpp)\nrosidl_target_interfaces(my_node ${PROJECT_NAME} \"rosidl_typesupport_cpp\")\n
    "},{"location":"other/pages/cmake.html#removing-boost-from-pluginlib","title":"Removing Boost from Pluginlib","text":"

    Pluginlib supports both boost::shared_ptrs and std::shared_ptrs by default, if you want to avoid depending on Boost in your shiny new ROS 2 library, you need to specifically tell pluginlib not to include the Boost versions:

    target_compile_definitions(your_library PUBLIC \"PLUGINLIB__DISABLE_BOOST_FUNCTIONS\")\n
    "},{"location":"other/pages/cmake.html#using-eigen3","title":"Using Eigen3","text":"

    Add eigen to your package.xml as a dependency, and then:

    find_package(Eigen3 REQUIRED)\ninclude_directories(${EIGEN3_INCLUDE_DIRS})\n
    "},{"location":"other/pages/cmake.html#building-python-extensions-in-c","title":"Building Python Extensions in C++","text":"

    The example below is based on the etherbotix package.

    find_package(PythonLibs REQUIRED)\nfind_package(Boost REQUIRED python)\nfind_package(ament_cmake_python REQUIRED)\nfind_package(python_cmake_module REQUIRED)\n\nament_python_install_package(${PROJECT_NAME})\n\nadd_library(\n  my_python SHARED\n  ${SOURCE_FILES}\n)\nset_target_properties(\n  my_python PROPERTIES\n  LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}\n  PREFIX \"\"\n)\ntarget_link_libraries(my_python\n  ${Boost_LIBRARIES}\n  ${PYTHON_LIBRARIES}\n)\n\ninstall(\n  TARGETS my_python\n  DESTINATION \"${PYTHON_INSTALL_DIR}/${PROJECT_NAME}\"\n)\n
    "},{"location":"other/pages/colcon.html","title":"Command Line: Colcon","text":""},{"location":"other/pages/colcon.html#my-aliases","title":"My Aliases","text":"

    I hate typing - so these are the aliases in my ~/.bashrc for my most common workflow:

    alias build2=\"colcon build --symlink-install\"\nalias test2=\"colcon test --event-handlers console_direct+\"\n
    "},{"location":"other/pages/colcon.html#build","title":"Build","text":"

    colcon is used to build ROS 2 packages in a workspace.

    Build all packages:

    colcon build\n

    To avoid having to rebuild when tweaking Python scripts, config files, and launch files:

    colcon build --symlink-install\n

    To fix most build issues, especially if you have added or removed packages to your workspace or installed new RMW implementations, clean the CMake cache. See this ROS Answers post for more details.

    colcon build --cmake-clean-cache\n
    "},{"location":"other/pages/colcon.html#cmake-arguments","title":"CMake Arguments","text":"
    colcon build --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo\n
    "},{"location":"other/pages/colcon.html#test","title":"Test","text":"

    To test and get results to screen:

    colcon test\ncolcon test-result --verbose\n

    Build/test a single package:

    colcon <verb> --packages-select <package-name>\n
    "},{"location":"other/pages/colcon.html#formatting","title":"Formatting","text":"

    Get the output to the screen:

    colcon <verb> --event-handlers console_direct+\n
    "},{"location":"other/pages/colcon.html#be-careful-with-workspaces","title":"Be Careful With Workspaces","text":"

    You should probably build your workspace in a window where you have NOT sourced the setup.bash of that workspace. For more details on why, see this ticket.

    "},{"location":"other/pages/colcon.html#verb-documentation","title":"Verb Documentation","text":""},{"location":"other/pages/command_line.html","title":"Command Line Tools","text":""},{"location":"other/pages/launch.html","title":"roslaunch2","text":"

    See also rosetta_launch for a number of examples.

    "},{"location":"other/pages/launch.html#python-based-launch-files","title":"Python-Based Launch Files","text":"

    Python-based launch files all pretty much follow the same structure.

    [!NOTE] Prior to Foxy, the parameters name, namespace, and executable were prefixed with node_

    from launch import LaunchDescription\nfrom launch_ros.actions import Node\n\ndef generate_launch_description():\n    return LaunchDescription([\n        Node(\n            name='node_runtime_name',\n            package='ros2_package_name',\n            executable='name_of_executable',\n            parameters=[{'name_of_int_param': 1,\n                         'name_of_str_param': 'value'}],\n            remappings=[('from', 'to')],\n            output='screen',\n        ),\n        # More Nodes!\n    ])\n
    "},{"location":"other/pages/launch.html#making-a-launch-file-executable","title":"Making a Launch File Executable","text":"

    Normally, launch files are run with:

    ros2 launch pkg launch.py\n

    But, sometimes you want an executable launch file (for instance to put in a systemd job). Assuming you follow the default pattern shown above, all you need to add:

    #!/usr/bin/env python3\n\nimport sys\nfrom launch import LaunchService\n\n# define generate_launch_description() as above\n\nif __name__ == '__main__':\n    desc = generate_launch_description()\n    service = LaunchService(argv=sys.argv[1:])\n    service.include_launch_description(desc)\n    return service.run()\n
    "},{"location":"other/pages/launch.html#loading-parameters-from-a-file","title":"Loading Parameters From a File","text":"

    Some nodes have many parameters, it's easier to put them in a YAML file:

    node_name:\n  ros__parameters:\n      some_int_param: 1\n      some_str_param: \"the_value\"\n

    To load this:

    from ament_index_python.packages import get_package_share_directory\n\n# Assuming you have a file called package_name/config/params.yaml\nnode_params = os.path.join(\n    get_package_share_directory('package_name'),\n    'config',\n    'params.yaml'\n)\n\n# Add this to your LaunchDescription\nNode(\n    name='node_runtime_name',\n    package='ros2_package_name',\n    executable='name_of_executable',\n    parameters=[{'another_param': 42.0},\n                 node_params]\n)\n
    "},{"location":"other/pages/launch.html#including-python-launch-files","title":"Including Python Launch Files","text":"
    import os\nfrom ament_index_python.packages import get_package_share_directory\nfrom launch.actions import IncludeLaunchDescription\nfrom launch.launch_description_sources import PythonLaunchDescriptionSource\n\n# Assuming you have a file called package_name/launch/my_launch.launch.py\nmy_launch_file = os.path.join(\n    get_package_share_directory('package_name'),\n    'launch',\n    'my_launch.launch.py'\n)\n\n# Add this to your LaunchDescription\nIncludeLaunchDescription(\n    PythonLaunchDescriptionSource([my_launch_file])\n),\n
    "},{"location":"other/pages/launch.html#loading-a-urdf","title":"Loading a URDF","text":"

    Most robots need to load their URDF into the robot_state_publisher, and maybe other nodes as well:

    import os\nfrom ament_index_python.packages import get_package_share_directory\nfrom launch_ros.actions import Node\n\n# Load the URDF into a parameter\ndesc_dir = get_package_share_directory('robot_description_pkg')\nurdf_path = os.path.join(desc_dir, 'robots', 'my_urdf.urdf')\nurdf = open(urdf_path).read()\n\n# Add this to your LaunchDescription\nNode(\n    name='robot_state_publisher',\n    package='robot_state_publisher',\n    executable='robot_state_publisher',\n    parameters=[{'robot_description': urdf}],\n)\n
    "},{"location":"other/pages/launch.html#installing-launch-files","title":"Installing Launch Files","text":""},{"location":"other/pages/networking.html","title":"Networking","text":"

    ROS 2 uses DDS for message transport.

    Set the environment variable RMW_IMPLEMENTATION to select a DDS implementation (RMW = robotic middleware). For instance:

    export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp\n

    To check which RMW implementation is being used:

    ros2 doctor --report | grep middleware\n
    "},{"location":"other/pages/networking.html#dds-discovery","title":"DDS Discovery","text":"

    There is no rosmaster like in ROS 1. Node discovery is peer-to-peer with nodes announcing their topics on startup and periodically after that. By default, any machines on the same network will see each other if they have the same ROS_DOMAIN_ID.

    ROS_DOMAIN_ID can be any number between 0 and 253, although it is recommended to use numbers less than 128.

    In addition to the ROS_DOMAIN_ID, CycloneDDS supports a domain tag, which allows nearly infinite partitioning of the network (see below).

    If you want to limit communication to the localhost set ROS_LOCALHOST_ONLY, which is available since Eloquent.

    "},{"location":"other/pages/networking.html#cyclonedds","title":"CycloneDDS","text":"

    Cyclone can be configured with XML. This can be stored in a file or passed directly in the environment variable CYCLONEDDS_URI. A full list of supported options can be found in the eclipse-cyclonedds repo. See also the Guide to Configuration.

    "},{"location":"other/pages/networking.html#cyclonedds-multiple-interfaces","title":"CycloneDDS: Multiple Interfaces","text":"

    Cyclone currently only works with a single network interface. If you have multiple interfaces, specify which one to use in the NetworkInterfaceAddress:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>\n    </General>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#cyclonedds-disabling-multicast-except-discovery","title":"CycloneDDS: Disabling Multicast (Except Discovery)","text":"

    Some network hardware can perform poorly with multicast (especially with WIFI). You can limit multicast to just discovery:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <AllowMulticast>spdp</AllowMulticast>\n    </General>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#cyclonedds-domain-tag","title":"CycloneDDS: Domain Tag","text":"

    CycloneDDS also defines a \"Domain Tag\" which allows to drastically partition the network with a custom string:

    <CycloneDDS>\n  <Domain>\n    <Discovery>\n      <Tag>my_robot_name</Tag>\n    </Discovery>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#example","title":"Example","text":"

    The above tags can all be combined:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <!-- Explicitly set network interface -->\n      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>\n      <!-- Use multicast for discovery only -->\n      <AllowMulticast>spdp</AllowMulticast>\n    </General>\n    <Discovery>\n      <!-- This tag has to be the same on each machine -->\n      <Tag>my_robot_name</Tag>\n    </Discovery>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/packages.html","title":"Package Documentation","text":"

    Currently, documentation is a bit all over the place. This list is certainly not extensive, but I find it helpful to have quick links to the frequently used packages.

    "},{"location":"other/pages/packages.html#core-documentation","title":"Core Documentation","text":""},{"location":"other/pages/packages.html#higher-level-packages","title":"Higher Level Packages","text":""},{"location":"other/pages/qos.html","title":"QoS (Quality of Service)","text":""},{"location":"other/pages/qos.html#recommended-practices","title":"Recommended Practices","text":"

    Subscribing with SensorDataQoS means that we will get unreliable transport but be more immune to lossy networks. By publishing with SystemDefaultQoS we will still be able to connect to any subscriber as long as they are specifying volatile rather than transient local.

    "},{"location":"other/pages/qos.html#overrides","title":"Overrides","text":"

    rclcpp offers a consistent way to define QoS overrides as parameters. There is a (somewhat dated/inaccurate) design doc on this feature. In your code:

    // Allow overriding QoS settings (history, depth, reliability)\nrclcpp::PublisherOptions pub_options;\npub_options.qos_overriding_options = rclcpp::QosOverridingOptions::with_default_policies();\nnode->create_publisher<std_msgs::msg::String>(\"topic\", rclcpp::QoS(10), pub_options);\n

    Then, in your parameter file, you can define:

    node_name:\n  ros__parameters:\n    qos_overrides:\n      fully_resolved_topic_name:\n        publisher:\n          reliability: reliable\n          history_depth: 100\n          history: keep_last\n

    The same workflow works for subscribers, you just use rclcpp::SubscriptionOptions instance and change publisher to subscription in the YAML file.

    "},{"location":"other/pages/qos.html#magic-numbers","title":"Magic Numbers","text":"

    If you perform ros2 topic info -v /some/topic and you examine the QoS settings, you may note that several fields are set to the magic number 2147483651294967295 (or 2,147,483,651,294,967,295). e.g.

    QoS profile:\n    Reliability: RMW_QOS_POLICY_RELIABILITY_RELIABLE\n    Durability: RMW_QOS_POLICY_DURABILITY_VOLATILE\n    Lifespan: 2147483651294967295 nanoseconds\n    Deadline: 2147483651294967295 nanoseconds\n    Liveliness: RMW_QOS_POLICY_LIVELINESS_AUTOMATIC\n    Liveliness lease duration: 2147483651294967295 nanoseconds\n

    What is the significance of this number, which is approximately equal to 68 years? It's not \\(2^n - 1\\) as you might expect for a magic number. However, durations are defined as

    [builtin_interfaces/Duration]\n# Seconds component, range is valid over any possible int32 value.\nint32 sec\n\n# Nanoseconds component in the range of [0, 1e9).\nuint32 nanosec\n

    The max value for an int32 is \\(2^{31} - 1\\). The max value for an uint32 is \\(2^{32} - 1\\). (Note: According to the definition, any value for nanosec over 1e9 is invalid.)

    So the magic number 2147483651294967295 is the number of nanoseconds equivalent to \\(2^{31} -1\\) seconds (\\(2147483647\\) seconds) added to \\(2^{32} - 1\\) nanoseconds (\\(4.294967295\\) seconds).

    "},{"location":"other/pages/rosdep.html","title":"Rosdep","text":"

    rosdep install --from-paths src --ignore-src --rosdistro=humble -y

    "},{"location":"other/pages/rosdoc2.html","title":"rosdoc2","text":"

    Documentation for ROS 2 packages can be built with rosdoc2.

    When a doc job is configured in the rosdistro, the resulting documentation is uploaded to docs.ros.org. The design document for per package documentation lays out the directory structure.

    "},{"location":"other/pages/rosdoc2.html#linking-to-other-packages","title":"Linking to Other Packages","text":"

    This is a dirty hack, but appears to be the only way to have the table of contents link to another package's documentation without hard-coding the distro. The trailing http:// changes how Sphinx processes the link:

    .. toctree::\n   :maxdepth: 2\n\n   other_package <../other_package/index.html#http://>\n

    An example of this can be seen in the documentation for image_pipeline, where we want to link to the documentation for each of the packages within the metapackage.

    "}]} \ No newline at end of file +var __index = {"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"index.html","title":"Home","text":""},{"location":"index.html#ros-2-cookbook","title":"ROS 2 Cookbook","text":"

    A collection of snippets ranging multiple topics in ROS 2.

    "},{"location":"literate-nav.html","title":"Literate nav","text":""},{"location":"client_libraries/index.html","title":"Client Libraries","text":""},{"location":"client_libraries/index.html#client-libraries","title":"Client libraries","text":""},{"location":"client_libraries/index.html#c-rclcpp","title":"C++ (rclcpp)","text":""},{"location":"client_libraries/index.html#python-rclpy","title":"Python (rclpy)","text":""},{"location":"client_libraries/rclcpp/index.html","title":"C++","text":""},{"location":"client_libraries/rclcpp/index.html#c-rclcpp","title":"C++ (rclcpp)","text":""},{"location":"client_libraries/rclcpp/index.html#nodes","title":"Nodes","text":""},{"location":"client_libraries/rclcpp/index.html#parameters","title":"Parameters","text":""},{"location":"client_libraries/rclcpp/index.html#logging","title":"Logging","text":""},{"location":"client_libraries/rclcpp/index.html#time","title":"Time","text":""},{"location":"client_libraries/rclcpp/index.html#tf2","title":"TF2","text":""},{"location":"client_libraries/rclcpp/index.html#point-clouds","title":"Point Clouds","text":""},{"location":"client_libraries/rclcpp/index.html#workarounds","title":"Workarounds","text":""},{"location":"client_libraries/rclcpp/index.html#initialization","title":"Initialization","text":""},{"location":"client_libraries/rclcpp/initialization.html","title":"rclcpp: Initialization","text":""},{"location":"client_libraries/rclcpp/initialization.html#shared_from_this-cannot-be-used-in-constructor","title":"shared_from_this Cannot Be Used in Constructor","text":"

    Sometime, you need to initialize some components outside of the constructor of your node. For example, you may want to initialize an object by passing a shared pointer to the node to it. This is not possible in the constructor, because the node is not fully initialized yet.

    The following pattern can be used to initialize components outside of the constructor of your node. This pattern was found through this comment.

    // Class declaration\n\nclass InitializedComponent\n{\npublic:\n  COMPOSITION_PUBLIC\n  explicit InitializedComponent(const rclcpp::NodeOptions & options);\n\n  COMPOSITION_PUBLIC\n  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr\n  get_node_base_interface() const;\n\nprivate:\n  rclcpp::Node::SharedPtr node_;\n};\n\n// Class implementation\n\nInitializedComponent::InitializedComponent(const rclcpp::NodeOptions & options)\n: node_(std::make_shared<rclcpp::Node>(\"node\", options))\n{\n  /* Do some initialization here */\n}\n\nrclcpp::node_interfaces::NodeBaseInterface::SharedPtr\nInitializedComponent::get_node_base_interface() const\n{\n  return this->node_->get_node_base_interface();\n}\n
    "},{"location":"client_libraries/rclcpp/initialization.html#overriding-default-parameters","title":"Overriding Default Parameters","text":"

    This is a pattern I find especially helpful when writing tests, where I want to create several tests with different parameters but would rather not maintain separate YAML or launch files with parameters:

    rclcpp::NodeOptions options;\noptions.parameter_overrides(\n  {\n    {\"my_namespace.my_parameter\", 5.2},\n    {\"another_parameter\", \"new value\"},\n  }\n);\nauto node = std::make_shared<rclcpp::Node>(\"node_name\", options);\n

    "},{"location":"client_libraries/rclcpp/logging.html","title":"rclcpp: Logging","text":""},{"location":"client_libraries/rclcpp/logging.html#changing-the-logging-level","title":"Changing the logging level","text":"
    #include <rclcpp/logger.hpp>\n\nrclcpp::get_logger(\"nav2_costmap_2d\").set_level(rclcpp::Logger::Level::Debug);\n
    "},{"location":"client_libraries/rclcpp/nodes.html","title":"rclcpp: Nodes and Components","text":""},{"location":"client_libraries/rclcpp/nodes.html#creating-a-component","title":"Creating a Component","text":"

    ROS 2 introduces components, which are nodes that can be run together in a single process. The benefits of using node compositions has been documented in a recent paper, Impact of ROS 2 Node Composition in Robotic Systems. Given the ease of use and the provided tooling, it really makes sense to make just about every node a component, and then let the rclcpp_components tooling create a node executable for you:

    #include <rclcpp/rclcpp.hpp>\n\nnamespace my_pkg\n{\n\nclass MyComponent : public rclcpp::Node\n{\npublic:\n  MyComponent(const rclcpp::NodeOptions& options)\n  : rclcpp::Node(\"node_name\", options)\n  {\n    // Do all your setup here - subscribers/publishers/timers\n    // After you return, an executor will be started\n\n    // Note: you cannot use shared_from_this()\n    //       here because the node is not fully\n    //       initialized.\n  }\n};\n\n}  // namespace my_pkg\n\n#include \"rclcpp_components/register_node_macro.hpp\"\nRCLCPP_COMPONENTS_REGISTER_NODE(my_pkg::MyComponent)\n

    The in the CMakeLists.txt:

    add_library(my_component SHARED\n  src/my_component.cpp\n)\nament_target_dependencies(my_component\n  rclcpp\n  rclcpp_components\n)\n\n# Also add a node executable which simply loads the component\nrclcpp_components_register_node(my_component\n  PLUGIN \"my_pkg::MyComponent\"\n  EXECUTABLE my_node\n)\n
    "},{"location":"client_libraries/rclcpp/nodes.html#executors","title":"Executors","text":"

    To run an executor in a thread:

    #include <thread>\n#include <rclcpp/rclcpp.hpp>\n#include <rclcpp/executors/single_threaded_executor.hpp>\n\nrclcpp::executors::SingleThreadedExecutor executor;\n\n// Node is rclcpp::Node::SharedPtr instance\nexecutor.add_node(node);\nstd::thread executor_thread(\n  std::bind(&rclcpp::executors::SingleThreadedExecutor::spin,\n            &executor));\n
    "},{"location":"client_libraries/rclcpp/parameters.html","title":"rclcpp: Parameters","text":"

    Parameters need to be declared. At the same time, you can get the value if you are not planning to update the value again later:

    // node is an instance of rclcpp::Node\n// 42 is a great default for a parameter\nint param = node.declare_parameter<int>(\"my_param_name\", 42);\n

    To get the value:

    int param;\nnode.get_parameter(\"my_param_name\", param);\n
    "},{"location":"client_libraries/rclcpp/parameters.html#dynamic-parameters","title":"Dynamic Parameters","text":"

    In ROS 2, all parameters can be dynamically updated through a ROS 2 service (there is no need to define duplicate stuff as with dynamic reconfigure).

    The example below works in Eloquent or later (earlier ROS 2 releases supported only a single callback and had a slightly different API). See the documentation for rclcpp::ParameterType for valid types.

    #include <vector>\n#include <rclcpp/rclcpp.hpp>\n\nclass MyNode : public rclcpp::Node\n{\npublic:\n  MyNode()\n  {\n    // Declare parameters first\n\n    // Then create callback\n    param_cb_ = this->add_on_set_parameters_callback(\n      std::bind(&MyNode::updateCallback, this, std::placeholders::_1));\n  }\n\nprivate:\n  // This will get called whenever a parameter gets updated\n  rcl_interfaces::msg::SetParametersResult updateCallback(\n    const std::vector<rclcpp::Parameter> & parameters)\n  {\n    rcl_interfaces::msg::SetParametersResult result;\n    result.successful = true;\n\n    for (const rclcpp::Parameter & param : parameters)\n    {\n      if (param.get_name() == \"my_param_name\")\n      {\n        if (param.get_type() != rclcpp::ParameterType::PARAMETER_STRING)\n        {\n          result.successful = false;\n          result.reason = \"my_param_name must be a string\";\n          break;\n        }\n\n        // Optionally do something with parameter\n      }\n    }\n\n    return result;\n  }\n\n  // Need to hold a pointer to the callback\n  rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr param_cb_;\n};\n
    "},{"location":"client_libraries/rclcpp/parameters.html#porting-xml-arrays-to-ros-2","title":"Porting XML Arrays to ROS 2","text":"

    Complex parameter blocks could be parsed as XML in ROS 1. For instance, the robot_calibration package used a block like:

    models:\n - name: arm\n   type: chain\n   frame: wrist_roll_link\n - name: camera\n   type: camera3d\n   frame: head_camera_rgb_optical_frame\n

    In ROS 2, the common pattern is to make the array a list of just names, then have each name be a block of parameters:

    models:\n- arm\n- camera\narm:\n  type: chain3d\n  frame: wrist_roll_link\ncamera:\n  type: camera3d\n  frame: head_camera_rgb_optical_frame\n

    To parse such a block:

    std::vector<ModelParams> models;\nauto model_names = node->declare_parameter<std::vector<std::string>>(\"models\", std::vector<std::string>());\nfor (auto name : model_names)\n{\n  RCLCPP_INFO(logger, \"Adding model: %s\", name.c_str());\n  ModelParams params;\n  params.name = name;\n  params.type = node->declare_parameter<std::string>(name + \".type\", std::string());\n  params.frame = node->declare_parameter<std::string>(name + \".frame\", std::string());\n  params.param_name = node->declare_parameter<std::string>(name + \".param_name\", std::string());\n  models.push_back(params);\n}\n
    "},{"location":"client_libraries/rclcpp/pcl.html","title":"rclcpp: Point Clouds","text":"

    The sensor_msgs/PointCloud2 is a very common type of ROS message for processing perception data in ROS. It is also one of the most complex messages to actually interpret.

    The complexity of the message derives from the fact that it holds arbitrary fields in a single giant data store. This allows the PointCloud2 message to work with any type of cloud (for instance, XYZ points only, XYZRGB, or even XYZI), but adds a bit of complexity in accessing the data in the cloud.

    In ROS 1, there was a simpler PointCloud message, but this has been deprecated and will be removed in ROS 2 Galactic.

    "},{"location":"client_libraries/rclcpp/pcl.html#using-the-pointcloud2iterator","title":"Using the PointCloud2Iterator","text":"

    The sensor_msgs package provides a C++ PointCloud2Iterator which can be used to create, modify and access PointCloud2 messages.

    To create a new message:

    #include \"sensor_msgs/msg/point_cloud2.hpp\"\n#include \"sensor_msgs/point_cloud2_iterator.hpp\"\n\nsensor_msgs::msg::PointCloud2 msg;\n\n// Fill in the size of the cloud\nmsg.height = 480;\nmsg.width = 640;\n\n// Create the modifier to setup the fields and memory\nsensor_msgs::PointCloud2Modifier mod(msg);\n\n// Set the fields that our cloud will have\nmod.setPointCloud2FieldsByString(2, \"xyz\", \"rgb\");\n\n// Set up memory for our points\nmod.resize(msg.height * msg.width);\n\n// Now create iterators for fields\nsensor_msgs::PointCloud2Iterator<float> iter_x(cloud_msg, \"x\");\nsensor_msgs::PointCloud2Iterator<float> iter_y(cloud_msg, \"y\");\nsensor_msgs::PointCloud2Iterator<float> iter_z(cloud_msg, \"z\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_r(cloud_msg, \"r\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_g(cloud_msg, \"g\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_b(cloud_msg, \"b\");\n\nfor (; iter_x != iter_x.end(); ++iter_x, ++iter_y, ++iter_z, ++iter_r, ++iter_g, ++iter_b)\n{\n  *iter_x = 0.0;\n  *iter_y = 0.0;\n  *iter_z = 0.0;\n  *iter_r = 0;\n  *iter_g = 255;\n  *iter_b = 0;\n}\n
    "},{"location":"client_libraries/rclcpp/pcl.html#using-pcl","title":"Using PCL","text":"

    For a number of operations, you might want to convert to a pcl::PointCloud in order to use the extensive API of the Point Cloud Library.

    In ROS 1, the pcl_ros package allowed you to write a subscriber whose callback took a pcl::PointCloud directly, however this package has not yet been ported to ROS 2. Regardless, it is pretty straight forward to do the conversion yourself with the pcl_conversions package:

    #include \"pcl_conversions/pcl_conversions.h\"\n\nvoid cloud_callback(const sensor_msgs::msg::PointCloud2::SharedPtr msg)\n{\n  // PCL still uses boost::shared_ptr internally\n  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud =\n    boost::make_shared<pcl::PointCloud<pcl::PointXYZRGB>>();\n\n  // This will convert the message into a pcl::PointCloud\n  pcl::fromROSMsg(*msg, *cloud);\n}\n

    You can also go in the opposite direction:

    #include \"pcl_conversions/pcl_conversions.h\"\n\npcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;\nsensor_msgs::msg::PointCloud2 msg;\n\npcl::toROSMsg(*cloud, msg);\ncloud_publisher->publish(msg);\n
    "},{"location":"client_libraries/rclcpp/tf2.html","title":"rclcpp: TF2","text":"

    The TF2 library provides easy access to transformations. All of the examples below require a dependency on the tf2_ros package.

    "},{"location":"client_libraries/rclcpp/tf2.html#broadcasting-transforms","title":"Broadcasting Transforms","text":"
    #include <tf2_ros/transform_broadcaster.h>\nstd::unique_ptr<tf2_ros::TransformBroadcaster> broadcaster;\n\nbroadcaster = std::make_unique<tf2_ros::TransformBroadcaster>(nodePtr);\n\ngeometry_msgs::msg::TransformStamped transform;\ntransform.header.stamp = node->now();\ntransform.header.frame_id = \"odom\";\ntransform.child_frame_id = \"base_link\";\n\n// Fill in transform.transform.translation\n// Fill in transform.transform.rotation\n\nbroadcaster->sendTransform(transform);\n
    "},{"location":"client_libraries/rclcpp/tf2.html#listening-for-transforms","title":"Listening for Transforms","text":"
    #include \"tf2_ros/transform_listener.h\"\n\nstd::shared_ptr<tf2_ros::Buffer> tf_buffer;\nstd::shared_ptr<tf2_ros::TransformListener> tf_listener;\n\nrclcpp::Node node(\"name_of_node\");\n\ntf_buffer.reset(new tf2_ros::Buffer(node.get_clock()));\ntf_listener.reset(new tf2_ros::TransformListener(*tf_buffer_));\n
    "},{"location":"client_libraries/rclcpp/tf2.html#applying-transforms","title":"Applying Transforms","text":"

    TF2 can be extended by packages that provide implementations of transform. The tf2_geometry_msgs package provides these for _geometry_msgs. The example below uses geometry_msgs::msg::PointStamped - but this should work for any data type in geometry_msgs:

    #include <tf2_geometry_msgs/tf2_geometry_msgs.h>\n\ngeometry_msg::msgs::PointStamped in, out;\nin.header.frame_id = \"source_frame\";\n\ntry\n{\n  tf_buffer->transform(in, out, \"target_frame\");\n}\ncatch (const tf2::TransformException& ex)\n{\n  RCLCPP_ERROR(rclcpp::get_logger(\"logger_name\"), \"Could not transform point.\");\n}\n

    The transform function can also take a timeout. It will then wait for the transform to be available up to that amount of time:

    tf_buffer->transform(in, out, \"target_frame\", tf2::durationFromSec(1.0));\n
    "},{"location":"client_libraries/rclcpp/tf2.html#get-latest-transform","title":"Get Latest Transform","text":"

    A common work flow is to get the \"latest\" transform. In ROS 2, this can be accomplished using tf2::TimePointZero, but requires using lookupTransform and then calling doTransform (which is basically what transform does internally):

    geometry_msgs::msg::PointStamped in, out;\n\ngeometry_msgs::msg::TransformStamped transform =\n   tf_buffer->lookupTransform(\"target_frame\",\n                              in.header.frame_id,\n                              tf2::TimePointZero);\n\ntf2::doTransform(in, out, transform);\n
    "},{"location":"client_libraries/rclcpp/tf2.html#constructing-transform-from-euler-angles","title":"Constructing Transform from Euler Angles","text":"

    There are numerous ways to do this (using Eigen, KDL, etc) - but it is also possible with only tf2 APIs:

    #include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>\n\ntf2::Quaternion quat;\n// While the documentation for tf2 orders the names of these as yaw, pitch, roll,\n// it specifies that yaw = rotation about Y, which is not what most of us expect\nquat.setEuler(pitch, roll, yaw);\n\ngeometry_msgs::msg::TransformStamped transform;\ntransform.transform.rotation = tf2::toMsg(quat);\n// Probably also fill in transform.header and transform.transform.translation\n\n// Can now use the transform with tf2::doTransform()\n
    "},{"location":"client_libraries/rclcpp/tf2.html#getting-yaw-angle-from-quaternion","title":"Getting Yaw Angle from Quaternion","text":"
    #include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>\n#include <tf2/utils.hpp>\n\ngeometry_msgs::msg::Pose pose;\ndouble yaw = tf2::getYaw(pose.orientation);\n

    [!NOTE] tf2::getYaw requires some pieces from tf2_geometery_msgs/tf2_geometry_msgs.hpp but cannot depend on them because it would create a circular dependency. This means you absolutely need to include tf2_geometry_msgs BEFORE tf2/utils or you will get an undefined reference to tf2::fromMsg

    "},{"location":"client_libraries/rclcpp/tf2.html#transform-from-eigenisometry3d","title":"Transform from Eigen::Isometry3d","text":"
    #include <tf2_eigen/tf2_eigen.hpp>\n\nEigen::Isometry3d map_to_odom;\ngeometry_msgs::msg::TransformStamped transform = tf2::eigenToTransform(map_to_odom);\n// Fill in header, child_frame_id\n
    "},{"location":"client_libraries/rclcpp/time.html","title":"rclcpp: Time","text":"

    The rclcpp::Time and rclcpp::Duration are a significant departure from their ROS 1 equivalents, but are more closely related to std::chrono. For an in-depth discussion comparing with std::chrono, see this discussion on ROS Discourse.

    When porting certain ROS 1 libraries, there may be significant usage of timestamps as floating-point seconds. To get floating point seconds from an _rclcpp::Time_:

    // node is instance of rclcpp::Node\nrclcpp::Time t = node.now();\ndouble seconds = t.seconds();\n

    There is no constructor for Time from seconds represented by a floating point, so you first need to convert to nanoseconds:

    rclcpp::Time t(static_cast<uin64_t>(seconds * 1e9));\n

    rclcpp::Duration does have functions to go both directions:

    rclcpp::Duration d = rclcpp::Duration::from_seconds(1.0);\ndouble seconds = d.seconds();\n

    Unlike ROS 1, sleeping does NOT occur from a Duration instance:

    rclcpp::sleep_for(std::chrono::milliseconds(50));\n
    "},{"location":"client_libraries/rclcpp/time.html#api-documentation-links","title":"API Documentation Links","text":""},{"location":"client_libraries/rclcpp/workarounds.html","title":"rclcpp: Workarounds","text":""},{"location":"client_libraries/rclcpp/workarounds.html#lazy-publishers","title":"Lazy Publishers","text":"

    Prior to the Iron release, ROS 2 did not support subscriber connect callbacks. This code creates a function which is called periodically to check if we need to start or stop subscribers:

    #include <rclcpp/rclcpp.hpp>\n#include <std_msgs/msg/float64.hpp>\n\nclass LazyPublisherEx : rclcpp::Node\n{\npublic:\n  LazyPublisherEx(const rclcpp::NodeOptions & options) :\n    Node(\"lazy_ex\", options)\n  {\n    // Setup timer\n    timer = this->create_wall_timer(\n      std::chrono::seconds(1),\n      std::bind(&LazyPublisher::periodic, this));\n  }\n\nprivate:\n  void periodic()\n  {\n    if (pub_.get_subscription_count() > 0)\n    {\n        // We have a subscriber, do any setup required\n    }\n    else\n    {\n        // Subscriber has disconnected, do any shutdown\n    }\n  }\n\n  rclcpp::Publisher<std_msgs::msg::Float64>::SharedPtr pub_;\n  rclcpp::TimerBase::SharedPtr timer_;\n};\n

    The same can be done when using image transport, you simply have to change from get_subscription_count to getNumSubscribers:

    #include <rclcpp/rclcpp.hpp>\n#include <image_transport/image_transport.h>\n\nclass LazyPublisherEx : rclcpp::Node\n{\npublic:\n  LazyPublisherEx(const rclcpp::NodeOptions & options) :\n    Node(\"lazy_ex\", options)\n  {\n    // Setup timer\n    timer = this->create_wall_timer(\n      std::chrono::seconds(1),\n      std::bind(&LazyPublisher::periodic, this));\n  }\n\nprivate:\n  void periodic()\n  {\n    if (pub_.getNumSubscribers() > 0)\n    {\n        // We have a subscriber, do any setup required\n    }\n    else\n    {\n        // Subscriber has disconnected, do any shutdown\n    }\n  }\n\n  image_transport::CameraPublisher pub_;\n  rclcpp::TimerBase::SharedPtr timer_;\n};\n
    "},{"location":"client_libraries/rclpy/index.html","title":"Python","text":""},{"location":"client_libraries/rclpy/index.html#python-rclpy","title":"Python (rclpy)","text":""},{"location":"client_libraries/rclpy/index.html#nodes","title":"Nodes","text":""},{"location":"client_libraries/rclpy/index.html#parameters","title":"Parameters","text":""},{"location":"client_libraries/rclpy/index.html#time","title":"Time","text":""},{"location":"client_libraries/rclpy/index.html#tf2","title":"TF2","text":""},{"location":"client_libraries/rclpy/nodes.html","title":"rclpy: Node Basics","text":"

    Most nodes have publishers and subscribers, both of which are creating by calling functions of the Node instance:

    import rclpy\nfrom rclpy.node import Node\n\nfrom std_msgs.msg import String\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__('my_node_name')\n\n        self.publisher = self.create_publisher(String, 'output_topic', 10)\n        self.subscription = self.create_subscription(\n            String,\n            'input_topic',\n            self.callback,\n            10)\n\n    def callback(self, msg):\n        self.get_logger().info(\"Recieved: %s\" % msg.data)\n        self.publisher.publish(msg)\n\nif __name___ == \"__main__\":\n    rclpy.init()\n    my_node = MyNode()\n    rclpy.spin(my_node)\n    my_node.destroy_node()  # cleans up pub-subs, etc\n    rclpy.shutdown()\n
    "},{"location":"client_libraries/rclpy/nodes.html#shutdown-hooks","title":"Shutdown Hooks","text":"

    ROS 1 had rospy.on_shutdown() - but there is not an equivalent in ROS 2. It really is not needed though, since we manually shut things down rather than was the case in rospy which used many global variables:

    try:\n    rclpy.spin(my_node)\nexcept KeyboardInterrupt:\n    pass\nfinally:\n    my_node.on_shutdown()  # do any custom cleanup\n    my_node.destroy_node()\n    rclpy.shutdown()\n
    "},{"location":"client_libraries/rclpy/parameters.html","title":"rclpy: Parameters","text":""},{"location":"client_libraries/rclpy/parameters.html#declaring-and-accessing-parameters","title":"Declaring and Accessing Parameters","text":"
    # node is rclpy.node.Node instance\n# 42 is a great default for a parameter\nnode.declare_parameter('my_param_name', 42)\n\n# To get the value:\nparam = node.get_parameter('my_param_name').value\n
    "},{"location":"client_libraries/rclpy/parameters.html#declaring-multiple-parameters-at-once","title":"Declaring Multiple Parameters at Once","text":"

    There seems to be a fairly undocumentated part of the rclpy API:

    node.declare_parameters(\n    namespace='',\n    parameters=[\n        ('my_param_name', 'default value'),\n        ('my_other_param', 42)\n    ]\n)\n
    "},{"location":"client_libraries/rclpy/parameters.html#dynamic-parameters","title":"Dynamic Parameters","text":"

    In ROS 2, all parameters can be dynamically updated through a ROS 2 service (there is no need to define duplicate stuff as with dynamic reconfigure).

    from rcl_interfaces.msg import SetParametersResult\n\nimport rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__('my_node_name')\n\n        # Declare a parameter\n        self.declare_parameter('my_param_name', 42)\n\n        # Then create callback\n        self.add_on_set_parameters_callback(self.callback)\n\n    def callback(self, parameters):\n        result = SetParametersResult(successful=True)\n\n        for p in parameters:\n            if p.name == 'my_param_name':\n                if p.type_ != p.Type.INTEGER:\n                    result.successful = False\n                    result.reason = 'my_param_name must be an Integer'\n                    return result\n                if p.value < 20:\n                    result.successful = False\n                    result.reason = 'my_param_name must be >= 20;\n                    return result\n\n        # Return success, so updates are seen via get_parameter()\n        return result\n

    For an example of calling the set_parameters service, see ROS Answers

    "},{"location":"client_libraries/rclpy/tf2.html","title":"rclpy: TF2","text":"

    The TF2 library provides easy access to transformations. All of the examples below require a dependency on the tf2_ros package.

    "},{"location":"client_libraries/rclpy/tf2.html#listening-for-transforms","title":"Listening for Transforms","text":"
    import rclpy\nfrom rclpy.node import Node\nfrom tf2_ros.buffer import Buffer\nfrom tf2_ros.transform_listener import TransformListener\n\nclass MyNode(Node):\n    def __init__(self):\n        super().__init__('my_node')\n\n        self.buffer = Buffer()\n        self.listener = TransformListener(self.buffer, self)\n
    "},{"location":"client_libraries/rclpy/tf2.html#applying-transforms","title":"Applying Transforms","text":"

    TF2 can be extended by packages that provide implementations of transform. The tf2_geometry_msgs package provides these for _geometry_msgs. The example below uses geometry_msgs.msg.PointStamped - but this should work for any data type in geometry_msgs:

    from geometry_msgs.msg import PointStamped\nfrom tf2_ros.buffer import Buffer\nfrom tf2_ros.transform_listener import TransformListener\nimport tf2_geometry_msgs\n\n# Setup buffer/listener as above\n\np1 = PointStamped()\np1.header.frame_id = 'source_frame'\n# fill in p1\n\np2 = buffer.transform(p1, 'target_frame')\n
    "},{"location":"client_libraries/rclpy/tf2.html#using-the-latest-transform","title":"Using the Latest Transform","text":"
    from rclpy.time import Time\n\n# Setting the stamp to a blank Time() instance will use the latest available data\np1 = PointStamped()\np1.header.frame_id = 'source_frame'\np1.header.stamp = Time().to_msg()\n\np2 = buffer.transform(p1, 'target_frame')\n
    "},{"location":"client_libraries/rclpy/tf2.html#transformations","title":"Transformations","text":"

    In ROS 1, tf included the transformations module. tf2 has no similar module. It is recommended to use transforms3d Python package, which is available through apt on Ubuntu 22.04:

    sudo apt-get install python3-transforms3d\n

    Or via pip on other operating systems:

    sudo pip3 install transforms3d\n

    For instance, to rotate a point:

    import numpy as np\nfrom transforms3d.quaternion import quat2mat\n\n# Create rotation matrix from quaternion\nR = quat2mat([w, x, y, z])\n# Create a vector to rotate\nV = np.array([x, y, z]).reshape((3, 1))\n# Rotate the vector\nM = np.dot(R, V)\n\np = PointStamped()\np.point.x = M[0, 0]\np.point.y = M[1, 0]\np.point.z = M[2, 0]\n

    Additionally, in ROS 2 Humble there is the tf_transformations package which should make it as easy as changing your imports from tf.transformations to tf_transformations.

    "},{"location":"client_libraries/rclpy/time.html","title":"rclpy: Time","text":"

    To get the equivalent of rospy.Time.now(), you now need a ROS 2 node:

    import rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def some_func(self):\n        t = self.get_clock().now()\n        msg.header.stamp = t.to_msg()\n

    Converting from Duration to messages is common:

    import rclpy\nfrom rclpy.duration import Duration\n\nmsg.duration = Duration(seconds=1).to_msg()\n
    "},{"location":"client_libraries/rclpy/time.html#timers","title":"Timers","text":"

    Timers are created from the Node:

    import rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__(\"my_node\")\n\n        # Create a timer that fires every quarter second\n        timer_period = 0.25\n        self.timer = self.create_timer(timer_period, self.callback)\n\n    def callback(self):\n        self.get_logger().info(\"timer has fired\")\n
    "},{"location":"client_libraries/rclpy/time.html#rates","title":"Rates","text":"

    Using Rate objects in ROS 2 is a bit more complex than in ROS 1. Due to implementation details, we need to spin() or the sleep() function will block. This is most easily accomplished using a thread:

    import threading\n\n# Run spin in a thread, make thread daemon so we don't have to join it to exit\nthread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True)\nthread.start()\n\nrate = node.create_rate(10)\nwhile rclpy.ok():\n    print('This prints at 10hz')\n    rate.sleep()\n
    "},{"location":"other/pages/index.html","title":"Other","text":""},{"location":"other/pages/index.html#other","title":"Other","text":""},{"location":"other/pages/index.html#rosbag2","title":"rosbag2","text":""},{"location":"other/pages/index.html#cmake","title":"CMake","text":""},{"location":"other/pages/index.html#colcon","title":"colcon","text":""},{"location":"other/pages/index.html#command-line-tools","title":"Command Line Tools","text":""},{"location":"other/pages/index.html#ros2launch","title":"ros2launch","text":""},{"location":"other/pages/index.html#networking-dds-cyclonedds","title":"Networking (DDS & CycloneDDS)","text":""},{"location":"other/pages/index.html#package-documentation","title":"Package Documentation","text":""},{"location":"other/pages/index.html#qos-quality-of-service","title":"QoS (Quality of Service)","text":""},{"location":"other/pages/index.html#documenting-with-rosdoc2","title":"Documenting with rosdoc2","text":""},{"location":"other/pages/index.html#rosdep","title":"rosdep","text":""},{"location":"other/pages/bag.html","title":"rosbag2","text":""},{"location":"other/pages/bag.html#common-command-line","title":"Common Command Line","text":""},{"location":"other/pages/bag.html#api-tutorials","title":"API Tutorials","text":"

    The ROS documentation has tutorials for recording and playback, which are linked below. There is no official tutorial for reading from a bagfile in Python, the linked one comes from an MCAP tutorial:

    "},{"location":"other/pages/bag.html#converting-bag-files-from-ros-1","title":"Converting Bag Files from ROS 1","text":"

    The following works, assuming you are mainly using standard topics. For instance, I have converted a number of bagfiles intended for SLAM testing, which mainly consist of sensor_msgs::LaserScan, TF, and nav_msgs::Odometry data.

    The easiest route for converting bagfiles is to use rosbags:

    sudo pip3 install rosbags\nrosbag-convert bagfile_name.bag\n

    This will create a new folder with the name bagfile_name containing the SQLite file and the index file. At this point, you can inspect the bagfile:

    ros2 bag info bagfile_name\n\nFiles:             bagfile_name.db3\nBag size:          65.7 MiB\nStorage id:        sqlite3\nROS Distro:        rosbags\nDuration:          122.298s\nStart:             Jun 15 2014 21:41:49.861 (1402882909.861)\nEnd:               Jun 15 2014 21:43:52.159 (1402883032.159)\nMessages:          35187\nTopic information: Topic: odom | Type: nav_msgs/msg/Odometry | Count: 12141 | Serialization Format: cdr\n                   Topic: tf | Type: tf2_msgs/msg/TFMessage | Count: 16939 | Serialization Format: cdr\n                   Topic: base_scan | Type: sensor_msgs/msg/LaserScan | Count: 4884 | Serialization Format: cdr\n                   Topic: odom_combined | Type: nav_msgs/msg/Odometry | Count: 1223 | Serialization Format: cdr\n

    This bagfile is now useable in ROS 2. However, you can also go a bit further by compressing the bagfile, and migrating it to the new MCAP file format. First, create a YAML file to define the output format:

    # output_format.yaml\noutput_bags:\n- uri: bagfile_name_compressed\n  all: true\n  compression_mode: file\n  compression_format: zstd\n

    Now, run the conversion:

    ros2 bag convert -i bagfile_name -o output_format.yaml\n

    Inspecting the new bag, we can see that compression is very nice - a 75% reduction in file size for my typical SLAM bag files:

    ros2 bag info bagfile_name_compressed\n\nFiles:             bagfile_name_compressed.mcap.zstd\nBag size:          16.7 MiB\nStorage id:        mcap\nROS Distro:        rolling\nDuration:          122.298s\nStart:             Jun 15 2014 21:41:49.861 (1402882909.861)\nEnd:               Jun 15 2014 21:43:52.159 (1402883032.159)\nMessages:          35187\nTopic information: Topic: base_scan | Type: sensor_msgs/msg/LaserScan | Count: 4884 | Serialization Format: cdr\n                   Topic: odom | Type: nav_msgs/msg/Odometry | Count: 12141 | Serialization Format: cdr\n                   Topic: odom_combined | Type: nav_msgs/msg/Odometry | Count: 1223 | Serialization Format: cdr\n                   Topic: tf | Type: tf2_msgs/msg/TFMessage | Count: 16939 | Serialization Format: cdr\n
    "},{"location":"other/pages/bag.html#removing-tf-from-a-bagfile","title":"Removing TF from a Bagfile","text":"

    I have often found that I needed to remove problematic TF data from a bagfile, usually filtering out the odom->base_link transform so that I can replace it with a more sophisticated filtered one. In this particular example we just toss the whole message out - but you could selectively remove the individual transform within the message and serialize the edited message back up.

    import rosbag2_py\nfrom rclpy.serialization import deserialize_message\nfrom rosidl_runtime_py.utilities import get_message\n\nreader = rosbag2_py.SequentialReader()\nreader.open(\n    rosbag2_py.StorageOptions(uri='input_bag', storage_id='mcap'),\n    rosbag2_py.ConverterOptions(input_serialization_format='cdr',\n                                output_serialization_format='cdr')\n)\n\nwriter = rosbag2_py.SequentialWriter()\nwriter.open(\n    rosbag2_py.StorageOptions(uri='output_bag', storage_id='mcap'),\n    rosbag2_py.ConverterOptions('', '')\n)\n\n# First preprocess the topics\ntopic_types = reader.get_all_topics_and_types()\nfor topic_type in topic_types:\n    # Setup output bagfile - same topics as input\n    writer.create_topic(topic_type)\n    # Note the type if this is our TF data\n    if topic_type.name == '/tf':\n        tf_typename = topic_type.type\n\n# Now iterate through messages, copying over the ones we don't filter\nwhile reader.has_next():\n    topic, data, timestamp = reader.read_next()\n    filter_out = False\n    # Filter out odom tf messages\n    if topic == '/tf':\n        # Deserialize the message so we can look at it\n        msg_type = get_message(tf_typename)\n        msg = deserialize_message(data, msg_type)\n        # Iterate through the transforms in the message\n        for transform in msg.transforms:\n            if transform.header.frame_id == 'odom':\n                # Toss this message\n                filter_out = True\n                break\n    # Copy over message if it isn't odom\n    if not filter_out:\n        writer.write(topic, data, timestamp)\n
    "},{"location":"other/pages/cmake.html","title":"CMake","text":"

    While you don't need to know everything about CMake to use ROS 2, knowing a bit will really be helpful. You might be interested in the CMake tutorial which explains the basics of CMake.

    "},{"location":"other/pages/cmake.html#ament","title":"Ament","text":"

    Ament is a set of CMake modules specifically designed for ROS 2 with the intent of making CMake easier to use. See also the Ament CMake documentation.

    The basic structure of an ament package:

    cmake_minimum_required(VERSION 3.5)\nproject(my_package_name)\n\n# Default to C++14\nif(NOT CMAKE_CXX_STANDARD)\n  set(CMAKE_CXX_STANDARD 14)\nendif()\n\nif(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n  add_compile_options(-Wall -Wextra -Wpedantic)\nendif()\n\n# Find packages\nfind_package(ament_cmake REQUIRED)\nfind_package(rclcpp REQUIRED)\n\n# Include our own headers\ninclude_directories(include)\n\n# Create a node\nadd_executable(my_node src/my_node.cpp)\nament_target_dependencies(my_node\n  rclcpp\n  # Other ament dependencies\n  # This sets up include and linker paths\n)\n\nadd_library(my_library src/my_library.cpp)\nament_target_dependencies(my_library\n  rclcpp\n)\n\n# Install our headers\ninstall(\n  DIRECTORY include/\n  DESTINATION include\n)\n\n# Install our node and library\ninstall(TARGETS my_node my_library\n  ARCHIVE DESTINATION lib\n  LIBRARY DESTINATION lib\n  RUNTIME DESTINATION lib/${PACKAGE_NAME}\n)\n\n# Install some Python scripts\ninstall(\n  PROGRAMS\n    scripts/my_script.py\n  DESTINATION\n    lib/${PROJECT_NAME}\n)\n\n# Tell downstream packages where to find our headers\nament_export_include_directories(include)\n# Tell downstream packages our libraries to link against\nament_export_libraries(my_library)\n# Help downstream packages to find transitive dependencies\nament_export_dependencies(\n  rclcpp\n)\nament_package()\n
    "},{"location":"other/pages/cmake.html#linting-configuration","title":"Linting Configuration","text":"

    I prefer a more ROS 1-style code style. To allow braces to be on their own lines:

    if(BUILD_TESTING)\n  find_package(ament_cmake_cpplint)\n  ament_cpplint(FILTERS \"-whitespace/braces\" \"-whitespace/newline\")\nendif()\n
    "},{"location":"other/pages/cmake.html#installing-python-scripts","title":"Installing Python Scripts","text":"
    install(\n  PROGRAMS\n    scripts/script1.py\n    scripts/script2.py\n  DESTINATION lib/${PROJECT_NAME}\n)\n
    "},{"location":"other/pages/cmake.html#depending-on-messages-in-same-package","title":"Depending on Messages in Same Package","text":"

    It is generally best practice to put messages in separate packages, but sometimes, especially for drivers, you want the messages in the same package.

    The following example worked in earlier versions of ROS 2 - but the syntax has changed See the Implementing custom interfaces tutorial for newer ROS 2 distributions.

    # Note: this WILL NOT work in ROS 2 Humble or later\nfind_package(rosidl_default_generators REQUIRED)\n\n# Generate some messages\nrosidl_generate_interfaces(${PROJECT_NAME}\n  \"msg/MyMessage.msg\"\n)\n\n# Add a node which uses the messages\nadd_executable(my_node my_node.cpp)\nrosidl_target_interfaces(my_node ${PROJECT_NAME} \"rosidl_typesupport_cpp\")\n
    "},{"location":"other/pages/cmake.html#removing-boost-from-pluginlib","title":"Removing Boost from Pluginlib","text":"

    Pluginlib supports both boost::shared_ptrs and std::shared_ptrs by default, if you want to avoid depending on Boost in your shiny new ROS 2 library, you need to specifically tell pluginlib not to include the Boost versions:

    target_compile_definitions(your_library PUBLIC \"PLUGINLIB__DISABLE_BOOST_FUNCTIONS\")\n
    "},{"location":"other/pages/cmake.html#using-eigen3","title":"Using Eigen3","text":"

    Add eigen to your package.xml as a dependency, and then:

    find_package(Eigen3 REQUIRED)\ninclude_directories(${EIGEN3_INCLUDE_DIRS})\n
    "},{"location":"other/pages/cmake.html#building-python-extensions-in-c","title":"Building Python Extensions in C++","text":"

    The example below is based on the etherbotix package.

    find_package(PythonLibs REQUIRED)\nfind_package(Boost REQUIRED python)\nfind_package(ament_cmake_python REQUIRED)\nfind_package(python_cmake_module REQUIRED)\n\nament_python_install_package(${PROJECT_NAME})\n\nadd_library(\n  my_python SHARED\n  ${SOURCE_FILES}\n)\nset_target_properties(\n  my_python PROPERTIES\n  LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}\n  PREFIX \"\"\n)\ntarget_link_libraries(my_python\n  ${Boost_LIBRARIES}\n  ${PYTHON_LIBRARIES}\n)\n\ninstall(\n  TARGETS my_python\n  DESTINATION \"${PYTHON_INSTALL_DIR}/${PROJECT_NAME}\"\n)\n
    "},{"location":"other/pages/colcon.html","title":"Command Line: Colcon","text":""},{"location":"other/pages/colcon.html#my-aliases","title":"My Aliases","text":"

    I hate typing - so these are the aliases in my ~/.bashrc for my most common workflow:

    alias build2=\"colcon build --symlink-install\"\nalias test2=\"colcon test --event-handlers console_direct+\"\n
    "},{"location":"other/pages/colcon.html#build","title":"Build","text":"

    colcon is used to build ROS 2 packages in a workspace.

    Build all packages:

    colcon build\n

    To avoid having to rebuild when tweaking Python scripts, config files, and launch files:

    colcon build --symlink-install\n

    To fix most build issues, especially if you have added or removed packages to your workspace or installed new RMW implementations, clean the CMake cache. See this ROS Answers post for more details.

    colcon build --cmake-clean-cache\n
    "},{"location":"other/pages/colcon.html#cmake-arguments","title":"CMake Arguments","text":"
    colcon build --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo\n
    "},{"location":"other/pages/colcon.html#test","title":"Test","text":"

    To test and get results to screen:

    colcon test\ncolcon test-result --verbose\n

    Build/test a single package:

    colcon <verb> --packages-select <package-name>\n
    "},{"location":"other/pages/colcon.html#formatting","title":"Formatting","text":"

    Get the output to the screen:

    colcon <verb> --event-handlers console_direct+\n
    "},{"location":"other/pages/colcon.html#be-careful-with-workspaces","title":"Be Careful With Workspaces","text":"

    You should probably build your workspace in a window where you have NOT sourced the setup.bash of that workspace. For more details on why, see this ticket.

    "},{"location":"other/pages/colcon.html#verb-documentation","title":"Verb Documentation","text":""},{"location":"other/pages/command_line.html","title":"Command Line Tools","text":""},{"location":"other/pages/launch.html","title":"roslaunch2","text":"

    See also rosetta_launch for a number of examples.

    "},{"location":"other/pages/launch.html#python-based-launch-files","title":"Python-Based Launch Files","text":"

    Python-based launch files all pretty much follow the same structure.

    [!NOTE] Prior to Foxy, the parameters name, namespace, and executable were prefixed with node_

    from launch import LaunchDescription\nfrom launch_ros.actions import Node\n\ndef generate_launch_description():\n    return LaunchDescription([\n        Node(\n            name='node_runtime_name',\n            package='ros2_package_name',\n            executable='name_of_executable',\n            parameters=[{'name_of_int_param': 1,\n                         'name_of_str_param': 'value'}],\n            remappings=[('from', 'to')],\n            output='screen',\n        ),\n        # More Nodes!\n    ])\n
    "},{"location":"other/pages/launch.html#making-a-launch-file-executable","title":"Making a Launch File Executable","text":"

    Normally, launch files are run with:

    ros2 launch pkg launch.py\n

    But, sometimes you want an executable launch file (for instance to put in a systemd job). Assuming you follow the default pattern shown above, all you need to add:

    #!/usr/bin/env python3\n\nimport sys\nfrom launch import LaunchService\n\n# define generate_launch_description() as above\n\nif __name__ == '__main__':\n    desc = generate_launch_description()\n    service = LaunchService(argv=sys.argv[1:])\n    service.include_launch_description(desc)\n    return service.run()\n
    "},{"location":"other/pages/launch.html#loading-parameters-from-a-file","title":"Loading Parameters From a File","text":"

    Some nodes have many parameters, it's easier to put them in a YAML file:

    node_name:\n  ros__parameters:\n      some_int_param: 1\n      some_str_param: \"the_value\"\n

    To load this:

    from ament_index_python.packages import get_package_share_directory\n\n# Assuming you have a file called package_name/config/params.yaml\nnode_params = os.path.join(\n    get_package_share_directory('package_name'),\n    'config',\n    'params.yaml'\n)\n\n# Add this to your LaunchDescription\nNode(\n    name='node_runtime_name',\n    package='ros2_package_name',\n    executable='name_of_executable',\n    parameters=[{'another_param': 42.0},\n                 node_params]\n)\n
    "},{"location":"other/pages/launch.html#including-python-launch-files","title":"Including Python Launch Files","text":"
    import os\nfrom ament_index_python.packages import get_package_share_directory\nfrom launch.actions import IncludeLaunchDescription\nfrom launch.launch_description_sources import PythonLaunchDescriptionSource\n\n# Assuming you have a file called package_name/launch/my_launch.launch.py\nmy_launch_file = os.path.join(\n    get_package_share_directory('package_name'),\n    'launch',\n    'my_launch.launch.py'\n)\n\n# Add this to your LaunchDescription\nIncludeLaunchDescription(\n    PythonLaunchDescriptionSource([my_launch_file])\n),\n
    "},{"location":"other/pages/launch.html#loading-a-urdf","title":"Loading a URDF","text":"

    Most robots need to load their URDF into the robot_state_publisher, and maybe other nodes as well:

    import os\nfrom ament_index_python.packages import get_package_share_directory\nfrom launch_ros.actions import Node\n\n# Load the URDF into a parameter\ndesc_dir = get_package_share_directory('robot_description_pkg')\nurdf_path = os.path.join(desc_dir, 'robots', 'my_urdf.urdf')\nurdf = open(urdf_path).read()\n\n# Add this to your LaunchDescription\nNode(\n    name='robot_state_publisher',\n    package='robot_state_publisher',\n    executable='robot_state_publisher',\n    parameters=[{'robot_description': urdf}],\n)\n
    "},{"location":"other/pages/launch.html#installing-launch-files","title":"Installing Launch Files","text":""},{"location":"other/pages/networking.html","title":"Networking","text":"

    ROS 2 uses DDS for message transport.

    Set the environment variable RMW_IMPLEMENTATION to select a DDS implementation (RMW = robotic middleware). For instance:

    export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp\n

    To check which RMW implementation is being used:

    ros2 doctor --report | grep middleware\n
    "},{"location":"other/pages/networking.html#dds-discovery","title":"DDS Discovery","text":"

    There is no rosmaster like in ROS 1. Node discovery is peer-to-peer with nodes announcing their topics on startup and periodically after that. By default, any machines on the same network will see each other if they have the same ROS_DOMAIN_ID.

    ROS_DOMAIN_ID can be any number between 0 and 253, although it is recommended to use numbers less than 128.

    In addition to the ROS_DOMAIN_ID, CycloneDDS supports a domain tag, which allows nearly infinite partitioning of the network (see below).

    If you want to limit communication to the localhost set ROS_LOCALHOST_ONLY, which is available since Eloquent.

    "},{"location":"other/pages/networking.html#cyclonedds","title":"CycloneDDS","text":"

    Cyclone can be configured with XML. This can be stored in a file or passed directly in the environment variable CYCLONEDDS_URI. A full list of supported options can be found in the eclipse-cyclonedds repo. See also the Guide to Configuration.

    "},{"location":"other/pages/networking.html#cyclonedds-multiple-interfaces","title":"CycloneDDS: Multiple Interfaces","text":"

    Cyclone currently only works with a single network interface. If you have multiple interfaces, specify which one to use in the NetworkInterfaceAddress:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>\n    </General>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#cyclonedds-disabling-multicast-except-discovery","title":"CycloneDDS: Disabling Multicast (Except Discovery)","text":"

    Some network hardware can perform poorly with multicast (especially with WIFI). You can limit multicast to just discovery:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <AllowMulticast>spdp</AllowMulticast>\n    </General>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#cyclonedds-domain-tag","title":"CycloneDDS: Domain Tag","text":"

    CycloneDDS also defines a \"Domain Tag\" which allows to drastically partition the network with a custom string:

    <CycloneDDS>\n  <Domain>\n    <Discovery>\n      <Tag>my_robot_name</Tag>\n    </Discovery>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#example","title":"Example","text":"

    The above tags can all be combined:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <!-- Explicitly set network interface -->\n      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>\n      <!-- Use multicast for discovery only -->\n      <AllowMulticast>spdp</AllowMulticast>\n    </General>\n    <Discovery>\n      <!-- This tag has to be the same on each machine -->\n      <Tag>my_robot_name</Tag>\n    </Discovery>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/packages.html","title":"Package Documentation","text":"

    Currently, documentation is a bit all over the place. This list is certainly not extensive, but I find it helpful to have quick links to the frequently used packages.

    "},{"location":"other/pages/packages.html#core-documentation","title":"Core Documentation","text":""},{"location":"other/pages/packages.html#higher-level-packages","title":"Higher Level Packages","text":""},{"location":"other/pages/qos.html","title":"QoS (Quality of Service)","text":""},{"location":"other/pages/qos.html#recommended-practices","title":"Recommended Practices","text":"

    Subscribing with SensorDataQoS means that we will get unreliable transport but be more immune to lossy networks. By publishing with SystemDefaultQoS we will still be able to connect to any subscriber as long as they are specifying volatile rather than transient local.

    "},{"location":"other/pages/qos.html#overrides","title":"Overrides","text":"

    rclcpp offers a consistent way to define QoS overrides as parameters. There is a (somewhat dated/inaccurate) design doc on this feature. In your code:

    // Allow overriding QoS settings (history, depth, reliability)\nrclcpp::PublisherOptions pub_options;\npub_options.qos_overriding_options = rclcpp::QosOverridingOptions::with_default_policies();\nnode->create_publisher<std_msgs::msg::String>(\"topic\", rclcpp::QoS(10), pub_options);\n

    Then, in your parameter file, you can define:

    node_name:\n  ros__parameters:\n    qos_overrides:\n      fully_resolved_topic_name:\n        publisher:\n          reliability: reliable\n          history_depth: 100\n          history: keep_last\n

    The same workflow works for subscribers, you just use rclcpp::SubscriptionOptions instance and change publisher to subscription in the YAML file.

    "},{"location":"other/pages/qos.html#magic-numbers","title":"Magic Numbers","text":"

    If you perform ros2 topic info -v /some/topic and you examine the QoS settings, you may note that several fields are set to the magic number 2147483651294967295 (or 2,147,483,651,294,967,295). e.g.

    QoS profile:\n    Reliability: RMW_QOS_POLICY_RELIABILITY_RELIABLE\n    Durability: RMW_QOS_POLICY_DURABILITY_VOLATILE\n    Lifespan: 2147483651294967295 nanoseconds\n    Deadline: 2147483651294967295 nanoseconds\n    Liveliness: RMW_QOS_POLICY_LIVELINESS_AUTOMATIC\n    Liveliness lease duration: 2147483651294967295 nanoseconds\n

    What is the significance of this number, which is approximately equal to 68 years? It's not \\(2^n - 1\\) as you might expect for a magic number. However, durations are defined as

    [builtin_interfaces/Duration]\n# Seconds component, range is valid over any possible int32 value.\nint32 sec\n\n# Nanoseconds component in the range of [0, 1e9).\nuint32 nanosec\n

    The max value for an int32 is \\(2^{31} - 1\\). The max value for an uint32 is \\(2^{32} - 1\\). (Note: According to the definition, any value for nanosec over 1e9 is invalid.)

    So the magic number 2147483651294967295 is the number of nanoseconds equivalent to \\(2^{31} -1\\) seconds (\\(2147483647\\) seconds) added to \\(2^{32} - 1\\) nanoseconds (\\(4.294967295\\) seconds).

    "},{"location":"other/pages/rosdep.html","title":"Rosdep","text":"

    rosdep install --from-paths src --ignore-src --rosdistro=humble -y

    "},{"location":"other/pages/rosdoc2.html","title":"rosdoc2","text":"

    Documentation for ROS 2 packages can be built with rosdoc2.

    When a doc job is configured in the rosdistro, the resulting documentation is uploaded to docs.ros.org. The design document for per package documentation lays out the directory structure.

    "},{"location":"other/pages/rosdoc2.html#linking-to-other-packages","title":"Linking to Other Packages","text":"

    This is a dirty hack, but appears to be the only way to have the table of contents link to another package's documentation without hard-coding the distro. The trailing http:// changes how Sphinx processes the link:

    .. toctree::\n   :maxdepth: 2\n\n   other_package <../other_package/index.html#http://>\n

    An example of this can be seen in the documentation for image_pipeline, where we want to link to the documentation for each of the packages within the metapackage.

    "},{"location":"other/pages/rosdoc2.html#documenting-metapackages","title":"Documenting Metapackages","text":"

    A metapackage is one that contains no code and exists basically to bundle up a set of dependencies. For instance image_pipeline is a repository containing several packages for image processing - and the image_pipeline metapackage depends on every package in the repository to make it easier to install with apt install ros-<distro>-image-pipeline rather than specifying each package individually. This does lead to an issue with rosdoc2, which will fail if there is no code to document. If you want to add tutorials or documentation to a metapackage, you need to use a rosdoc2.yaml file to properly build your documentation (which we assume is located in the doc folder:

    type: 'rosdoc2 config'\nversion: 1\n\n---\n\nsettings:\n    # Not generating any documentation of code\n    generate_package_index: false\n    always_run_doxygen: false\n    enable_breathe: false\n    enable_exhale: false\n    always_run_sphinx_apidoc: false\n    # Lie to rosdoc2, claim to be a python package\n    override_build_type: 'ament_python'\n    # Lie to rosdoc2 again, say your source is in doc folder\n    python_source: 'doc'\n\nbuilders:\n   # Configure Sphinx with the location of the docs:\n    - sphinx: {\n        name: 'image_pipeline',\n        sphinx_sourcedir: 'doc',\n        output_dir: ''\n    }\n

    Don't forget to add your yaml file to the package.xml:

    <export>\n  <rosdoc2>rosdoc2.yaml</rosdoc2>\n</export>\n
    "}]} \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json index a8b0bb7..b301a00 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"index.html","title":"Home","text":""},{"location":"index.html#ros-2-cookbook","title":"ROS 2 Cookbook","text":"

    A collection of snippets ranging multiple topics in ROS 2.

    "},{"location":"literate-nav.html","title":"Literate nav","text":""},{"location":"client_libraries/index.html","title":"Client Libraries","text":""},{"location":"client_libraries/index.html#client-libraries","title":"Client libraries","text":""},{"location":"client_libraries/index.html#c-rclcpp","title":"C++ (rclcpp)","text":""},{"location":"client_libraries/index.html#python-rclpy","title":"Python (rclpy)","text":""},{"location":"client_libraries/rclcpp/index.html","title":"C++","text":""},{"location":"client_libraries/rclcpp/index.html#c-rclcpp","title":"C++ (rclcpp)","text":""},{"location":"client_libraries/rclcpp/index.html#nodes","title":"Nodes","text":""},{"location":"client_libraries/rclcpp/index.html#parameters","title":"Parameters","text":""},{"location":"client_libraries/rclcpp/index.html#logging","title":"Logging","text":""},{"location":"client_libraries/rclcpp/index.html#time","title":"Time","text":""},{"location":"client_libraries/rclcpp/index.html#tf2","title":"TF2","text":""},{"location":"client_libraries/rclcpp/index.html#point-clouds","title":"Point Clouds","text":""},{"location":"client_libraries/rclcpp/index.html#workarounds","title":"Workarounds","text":""},{"location":"client_libraries/rclcpp/index.html#initialization","title":"Initialization","text":""},{"location":"client_libraries/rclcpp/initialization.html","title":"rclcpp: Initialization","text":""},{"location":"client_libraries/rclcpp/initialization.html#shared_from_this-cannot-be-used-in-constructor","title":"shared_from_this Cannot Be Used in Constructor","text":"

    Sometime, you need to initialize some components outside of the constructor of your node. For example, you may want to initialize an object by passing a shared pointer to the node to it. This is not possible in the constructor, because the node is not fully initialized yet.

    The following pattern can be used to initialize components outside of the constructor of your node. This pattern was found through this comment.

    // Class declaration\n\nclass InitializedComponent\n{\npublic:\n  COMPOSITION_PUBLIC\n  explicit InitializedComponent(const rclcpp::NodeOptions & options);\n\n  COMPOSITION_PUBLIC\n  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr\n  get_node_base_interface() const;\n\nprivate:\n  rclcpp::Node::SharedPtr node_;\n};\n\n// Class implementation\n\nInitializedComponent::InitializedComponent(const rclcpp::NodeOptions & options)\n: node_(std::make_shared<rclcpp::Node>(\"node\", options))\n{\n  /* Do some initialization here */\n}\n\nrclcpp::node_interfaces::NodeBaseInterface::SharedPtr\nInitializedComponent::get_node_base_interface() const\n{\n  return this->node_->get_node_base_interface();\n}\n
    "},{"location":"client_libraries/rclcpp/initialization.html#overriding-default-parameters","title":"Overriding Default Parameters","text":"

    This is a pattern I find especially helpful when writing tests, where I want to create several tests with different parameters but would rather not maintain separate YAML or launch files with parameters:

    rclcpp::NodeOptions options;\noptions.parameter_overrides(\n  {\n    {\"my_namespace.my_parameter\", 5.2},\n    {\"another_parameter\", \"new value\"},\n  }\n);\nauto node = std::make_shared<rclcpp::Node>(\"node_name\", options);\n

    "},{"location":"client_libraries/rclcpp/logging.html","title":"rclcpp: Logging","text":""},{"location":"client_libraries/rclcpp/logging.html#changing-the-logging-level","title":"Changing the logging level","text":"
    #include <rclcpp/logger.hpp>\n\nrclcpp::get_logger(\"nav2_costmap_2d\").set_level(rclcpp::Logger::Level::Debug);\n
    "},{"location":"client_libraries/rclcpp/nodes.html","title":"rclcpp: Nodes and Components","text":""},{"location":"client_libraries/rclcpp/nodes.html#creating-a-component","title":"Creating a Component","text":"

    ROS 2 introduces components, which are nodes that can be run together in a single process. The benefits of using node compositions has been documented in a recent paper, Impact of ROS 2 Node Composition in Robotic Systems. Given the ease of use and the provided tooling, it really makes sense to make just about every node a component, and then let the rclcpp_components tooling create a node executable for you:

    #include <rclcpp/rclcpp.hpp>\n\nnamespace my_pkg\n{\n\nclass MyComponent : public rclcpp::Node\n{\npublic:\n  MyComponent(const rclcpp::NodeOptions& options)\n  : rclcpp::Node(\"node_name\", options)\n  {\n    // Do all your setup here - subscribers/publishers/timers\n    // After you return, an executor will be started\n\n    // Note: you cannot use shared_from_this()\n    //       here because the node is not fully\n    //       initialized.\n  }\n};\n\n}  // namespace my_pkg\n\n#include \"rclcpp_components/register_node_macro.hpp\"\nRCLCPP_COMPONENTS_REGISTER_NODE(my_pkg::MyComponent)\n

    The in the CMakeLists.txt:

    add_library(my_component SHARED\n  src/my_component.cpp\n)\nament_target_dependencies(my_component\n  rclcpp\n  rclcpp_components\n)\n\n# Also add a node executable which simply loads the component\nrclcpp_components_register_node(my_component\n  PLUGIN \"my_pkg::MyComponent\"\n  EXECUTABLE my_node\n)\n
    "},{"location":"client_libraries/rclcpp/nodes.html#executors","title":"Executors","text":"

    To run an executor in a thread:

    #include <thread>\n#include <rclcpp/rclcpp.hpp>\n#include <rclcpp/executors/single_threaded_executor.hpp>\n\nrclcpp::executors::SingleThreadedExecutor executor;\n\n// Node is rclcpp::Node::SharedPtr instance\nexecutor.add_node(node);\nstd::thread executor_thread(\n  std::bind(&rclcpp::executors::SingleThreadedExecutor::spin,\n            &executor));\n
    "},{"location":"client_libraries/rclcpp/parameters.html","title":"rclcpp: Parameters","text":"

    Parameters need to be declared. At the same time, you can get the value if you are not planning to update the value again later:

    // node is an instance of rclcpp::Node\n// 42 is a great default for a parameter\nint param = node.declare_parameter<int>(\"my_param_name\", 42);\n

    To get the value:

    int param;\nnode.get_parameter(\"my_param_name\", param);\n
    "},{"location":"client_libraries/rclcpp/parameters.html#dynamic-parameters","title":"Dynamic Parameters","text":"

    In ROS 2, all parameters can be dynamically updated through a ROS 2 service (there is no need to define duplicate stuff as with dynamic reconfigure).

    The example below works in Eloquent or later (earlier ROS 2 releases supported only a single callback and had a slightly different API). See the documentation for rclcpp::ParameterType for valid types.

    #include <vector>\n#include <rclcpp/rclcpp.hpp>\n\nclass MyNode : public rclcpp::Node\n{\npublic:\n  MyNode()\n  {\n    // Declare parameters first\n\n    // Then create callback\n    param_cb_ = this->add_on_set_parameters_callback(\n      std::bind(&MyNode::updateCallback, this, std::placeholders::_1));\n  }\n\nprivate:\n  // This will get called whenever a parameter gets updated\n  rcl_interfaces::msg::SetParametersResult updateCallback(\n    const std::vector<rclcpp::Parameter> & parameters)\n  {\n    rcl_interfaces::msg::SetParametersResult result;\n    result.successful = true;\n\n    for (const rclcpp::Parameter & param : parameters)\n    {\n      if (param.get_name() == \"my_param_name\")\n      {\n        if (param.get_type() != rclcpp::ParameterType::PARAMETER_STRING)\n        {\n          result.successful = false;\n          result.reason = \"my_param_name must be a string\";\n          break;\n        }\n\n        // Optionally do something with parameter\n      }\n    }\n\n    return result;\n  }\n\n  // Need to hold a pointer to the callback\n  rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr param_cb_;\n};\n
    "},{"location":"client_libraries/rclcpp/parameters.html#porting-xml-arrays-to-ros-2","title":"Porting XML Arrays to ROS 2","text":"

    Complex parameter blocks could be parsed as XML in ROS 1. For instance, the robot_calibration package used a block like:

    models:\n - name: arm\n   type: chain\n   frame: wrist_roll_link\n - name: camera\n   type: camera3d\n   frame: head_camera_rgb_optical_frame\n

    In ROS 2, the common pattern is to make the array a list of just names, then have each name be a block of parameters:

    models:\n- arm\n- camera\narm:\n  type: chain3d\n  frame: wrist_roll_link\ncamera:\n  type: camera3d\n  frame: head_camera_rgb_optical_frame\n

    To parse such a block:

    std::vector<ModelParams> models;\nauto model_names = node->declare_parameter<std::vector<std::string>>(\"models\", std::vector<std::string>());\nfor (auto name : model_names)\n{\n  RCLCPP_INFO(logger, \"Adding model: %s\", name.c_str());\n  ModelParams params;\n  params.name = name;\n  params.type = node->declare_parameter<std::string>(name + \".type\", std::string());\n  params.frame = node->declare_parameter<std::string>(name + \".frame\", std::string());\n  params.param_name = node->declare_parameter<std::string>(name + \".param_name\", std::string());\n  models.push_back(params);\n}\n
    "},{"location":"client_libraries/rclcpp/pcl.html","title":"rclcpp: Point Clouds","text":"

    The sensor_msgs/PointCloud2 is a very common type of ROS message for processing perception data in ROS. It is also one of the most complex messages to actually interpret.

    The complexity of the message derives from the fact that it holds arbitrary fields in a single giant data store. This allows the PointCloud2 message to work with any type of cloud (for instance, XYZ points only, XYZRGB, or even XYZI), but adds a bit of complexity in accessing the data in the cloud.

    In ROS 1, there was a simpler PointCloud message, but this has been deprecated and will be removed in ROS 2 Galactic.

    "},{"location":"client_libraries/rclcpp/pcl.html#using-the-pointcloud2iterator","title":"Using the PointCloud2Iterator","text":"

    The sensor_msgs package provides a C++ PointCloud2Iterator which can be used to create, modify and access PointCloud2 messages.

    To create a new message:

    #include \"sensor_msgs/msg/point_cloud2.hpp\"\n#include \"sensor_msgs/point_cloud2_iterator.hpp\"\n\nsensor_msgs::msg::PointCloud2 msg;\n\n// Fill in the size of the cloud\nmsg.height = 480;\nmsg.width = 640;\n\n// Create the modifier to setup the fields and memory\nsensor_msgs::PointCloud2Modifier mod(msg);\n\n// Set the fields that our cloud will have\nmod.setPointCloud2FieldsByString(2, \"xyz\", \"rgb\");\n\n// Set up memory for our points\nmod.resize(msg.height * msg.width);\n\n// Now create iterators for fields\nsensor_msgs::PointCloud2Iterator<float> iter_x(cloud_msg, \"x\");\nsensor_msgs::PointCloud2Iterator<float> iter_y(cloud_msg, \"y\");\nsensor_msgs::PointCloud2Iterator<float> iter_z(cloud_msg, \"z\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_r(cloud_msg, \"r\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_g(cloud_msg, \"g\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_b(cloud_msg, \"b\");\n\nfor (; iter_x != iter_x.end(); ++iter_x, ++iter_y, ++iter_z, ++iter_r, ++iter_g, ++iter_b)\n{\n  *iter_x = 0.0;\n  *iter_y = 0.0;\n  *iter_z = 0.0;\n  *iter_r = 0;\n  *iter_g = 255;\n  *iter_b = 0;\n}\n
    "},{"location":"client_libraries/rclcpp/pcl.html#using-pcl","title":"Using PCL","text":"

    For a number of operations, you might want to convert to a pcl::PointCloud in order to use the extensive API of the Point Cloud Library.

    In ROS 1, the pcl_ros package allowed you to write a subscriber whose callback took a pcl::PointCloud directly, however this package has not yet been ported to ROS 2. Regardless, it is pretty straight forward to do the conversion yourself with the pcl_conversions package:

    #include \"pcl_conversions/pcl_conversions.h\"\n\nvoid cloud_callback(const sensor_msgs::msg::PointCloud2::SharedPtr msg)\n{\n  // PCL still uses boost::shared_ptr internally\n  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud =\n    boost::make_shared<pcl::PointCloud<pcl::PointXYZRGB>>();\n\n  // This will convert the message into a pcl::PointCloud\n  pcl::fromROSMsg(*msg, *cloud);\n}\n

    You can also go in the opposite direction:

    #include \"pcl_conversions/pcl_conversions.h\"\n\npcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;\nsensor_msgs::msg::PointCloud2 msg;\n\npcl::toROSMsg(*cloud, msg);\ncloud_publisher->publish(msg);\n
    "},{"location":"client_libraries/rclcpp/tf2.html","title":"rclcpp: TF2","text":"

    The TF2 library provides easy access to transformations. All of the examples below require a dependency on the tf2_ros package.

    "},{"location":"client_libraries/rclcpp/tf2.html#broadcasting-transforms","title":"Broadcasting Transforms","text":"
    #include <tf2_ros/transform_broadcaster.h>\nstd::unique_ptr<tf2_ros::TransformBroadcaster> broadcaster;\n\nbroadcaster = std::make_unique<tf2_ros::TransformBroadcaster>(nodePtr);\n\ngeometry_msgs::msg::TransformStamped transform;\ntransform.header.stamp = node->now();\ntransform.header.frame_id = \"odom\";\ntransform.child_frame_id = \"base_link\";\n\n// Fill in transform.transform.translation\n// Fill in transform.transform.rotation\n\nbroadcaster->sendTransform(transform);\n
    "},{"location":"client_libraries/rclcpp/tf2.html#listening-for-transforms","title":"Listening for Transforms","text":"
    #include \"tf2_ros/transform_listener.h\"\n\nstd::shared_ptr<tf2_ros::Buffer> tf_buffer;\nstd::shared_ptr<tf2_ros::TransformListener> tf_listener;\n\nrclcpp::Node node(\"name_of_node\");\n\ntf_buffer.reset(new tf2_ros::Buffer(node.get_clock()));\ntf_listener.reset(new tf2_ros::TransformListener(*tf_buffer_));\n
    "},{"location":"client_libraries/rclcpp/tf2.html#applying-transforms","title":"Applying Transforms","text":"

    TF2 can be extended by packages that provide implementations of transform. The tf2_geometry_msgs package provides these for _geometry_msgs. The example below uses geometry_msgs::msg::PointStamped - but this should work for any data type in geometry_msgs:

    #include <tf2_geometry_msgs/tf2_geometry_msgs.h>\n\ngeometry_msg::msgs::PointStamped in, out;\nin.header.frame_id = \"source_frame\";\n\ntry\n{\n  tf_buffer->transform(in, out, \"target_frame\");\n}\ncatch (const tf2::TransformException& ex)\n{\n  RCLCPP_ERROR(rclcpp::get_logger(\"logger_name\"), \"Could not transform point.\");\n}\n

    The transform function can also take a timeout. It will then wait for the transform to be available up to that amount of time:

    tf_buffer->transform(in, out, \"target_frame\", tf2::durationFromSec(1.0));\n
    "},{"location":"client_libraries/rclcpp/tf2.html#get-latest-transform","title":"Get Latest Transform","text":"

    A common work flow is to get the \"latest\" transform. In ROS 2, this can be accomplished using tf2::TimePointZero, but requires using lookupTransform and then calling doTransform (which is basically what transform does internally):

    geometry_msgs::msg::PointStamped in, out;\n\ngeometry_msgs::msg::TransformStamped transform =\n   tf_buffer->lookupTransform(\"target_frame\",\n                              in.header.frame_id,\n                              tf2::TimePointZero);\n\ntf2::doTransform(in, out, transform);\n
    "},{"location":"client_libraries/rclcpp/tf2.html#constructing-transform-from-euler-angles","title":"Constructing Transform from Euler Angles","text":"

    There are numerous ways to do this (using Eigen, KDL, etc) - but it is also possible with only tf2 APIs:

    #include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>\n\ntf2::Quaternion quat;\n// While the documentation for tf2 orders the names of these as yaw, pitch, roll,\n// it specifies that yaw = rotation about Y, which is not what most of us expect\nquat.setEuler(pitch, roll, yaw);\n\ngeometry_msgs::msg::TransformStamped transform;\ntransform.transform.rotation = tf2::toMsg(quat);\n// Probably also fill in transform.header and transform.transform.translation\n\n// Can now use the transform with tf2::doTransform()\n
    "},{"location":"client_libraries/rclcpp/tf2.html#getting-yaw-angle-from-quaternion","title":"Getting Yaw Angle from Quaternion","text":"
    #include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>\n#include <tf2/utils.hpp>\n\ngeometry_msgs::msg::Pose pose;\ndouble yaw = tf2::getYaw(pose.orientation);\n

    [!NOTE] tf2::getYaw requires some pieces from tf2_geometery_msgs/tf2_geometry_msgs.hpp but cannot depend on them because it would create a circular dependency. This means you absolutely need to include tf2_geometry_msgs BEFORE tf2/utils or you will get an undefined reference to tf2::fromMsg

    "},{"location":"client_libraries/rclcpp/tf2.html#transform-from-eigenisometry3d","title":"Transform from Eigen::Isometry3d","text":"
    #include <tf2_eigen/tf2_eigen.hpp>\n\nEigen::Isometry3d map_to_odom;\ngeometry_msgs::msg::TransformStamped transform = tf2::eigenToTransform(map_to_odom);\n// Fill in header, child_frame_id\n
    "},{"location":"client_libraries/rclcpp/time.html","title":"rclcpp: Time","text":"

    The rclcpp::Time and rclcpp::Duration are a significant departure from their ROS 1 equivalents, but are more closely related to std::chrono. For an in-depth discussion comparing with std::chrono, see this discussion on ROS Discourse.

    When porting certain ROS 1 libraries, there may be significant usage of timestamps as floating-point seconds. To get floating point seconds from an _rclcpp::Time_:

    // node is instance of rclcpp::Node\nrclcpp::Time t = node.now();\ndouble seconds = t.seconds();\n

    There is no constructor for Time from seconds represented by a floating point, so you first need to convert to nanoseconds:

    rclcpp::Time t(static_cast<uin64_t>(seconds * 1e9));\n

    rclcpp::Duration does have functions to go both directions:

    rclcpp::Duration d = rclcpp::Duration::from_seconds(1.0);\ndouble seconds = d.seconds();\n

    Unlike ROS 1, sleeping does NOT occur from a Duration instance:

    rclcpp::sleep_for(std::chrono::milliseconds(50));\n
    "},{"location":"client_libraries/rclcpp/time.html#api-documentation-links","title":"API Documentation Links","text":""},{"location":"client_libraries/rclcpp/workarounds.html","title":"rclcpp: Workarounds","text":""},{"location":"client_libraries/rclcpp/workarounds.html#lazy-publishers","title":"Lazy Publishers","text":"

    Prior to the Iron release, ROS 2 did not support subscriber connect callbacks. This code creates a function which is called periodically to check if we need to start or stop subscribers:

    #include <rclcpp/rclcpp.hpp>\n#include <std_msgs/msg/float64.hpp>\n\nclass LazyPublisherEx : rclcpp::Node\n{\npublic:\n  LazyPublisherEx(const rclcpp::NodeOptions & options) :\n    Node(\"lazy_ex\", options)\n  {\n    // Setup timer\n    timer = this->create_wall_timer(\n      std::chrono::seconds(1),\n      std::bind(&LazyPublisher::periodic, this));\n  }\n\nprivate:\n  void periodic()\n  {\n    if (pub_.get_subscription_count() > 0)\n    {\n        // We have a subscriber, do any setup required\n    }\n    else\n    {\n        // Subscriber has disconnected, do any shutdown\n    }\n  }\n\n  rclcpp::Publisher<std_msgs::msg::Float64>::SharedPtr pub_;\n  rclcpp::TimerBase::SharedPtr timer_;\n};\n

    The same can be done when using image transport, you simply have to change from get_subscription_count to getNumSubscribers:

    #include <rclcpp/rclcpp.hpp>\n#include <image_transport/image_transport.h>\n\nclass LazyPublisherEx : rclcpp::Node\n{\npublic:\n  LazyPublisherEx(const rclcpp::NodeOptions & options) :\n    Node(\"lazy_ex\", options)\n  {\n    // Setup timer\n    timer = this->create_wall_timer(\n      std::chrono::seconds(1),\n      std::bind(&LazyPublisher::periodic, this));\n  }\n\nprivate:\n  void periodic()\n  {\n    if (pub_.getNumSubscribers() > 0)\n    {\n        // We have a subscriber, do any setup required\n    }\n    else\n    {\n        // Subscriber has disconnected, do any shutdown\n    }\n  }\n\n  image_transport::CameraPublisher pub_;\n  rclcpp::TimerBase::SharedPtr timer_;\n};\n
    "},{"location":"client_libraries/rclpy/index.html","title":"Python","text":""},{"location":"client_libraries/rclpy/index.html#python-rclpy","title":"Python (rclpy)","text":""},{"location":"client_libraries/rclpy/index.html#nodes","title":"Nodes","text":""},{"location":"client_libraries/rclpy/index.html#parameters","title":"Parameters","text":""},{"location":"client_libraries/rclpy/index.html#time","title":"Time","text":""},{"location":"client_libraries/rclpy/index.html#tf2","title":"TF2","text":""},{"location":"client_libraries/rclpy/nodes.html","title":"rclpy: Node Basics","text":"

    Most nodes have publishers and subscribers, both of which are creating by calling functions of the Node instance:

    import rclpy\nfrom rclpy.node import Node\n\nfrom std_msgs.msg import String\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__('my_node_name')\n\n        self.publisher = self.create_publisher(String, 'output_topic', 10)\n        self.subscription = self.create_subscription(\n            String,\n            'input_topic',\n            self.callback,\n            10)\n\n    def callback(self, msg):\n        self.get_logger().info(\"Recieved: %s\" % msg.data)\n        self.publisher.publish(msg)\n\nif __name___ == \"__main__\":\n    rclpy.init()\n    my_node = MyNode()\n    rclpy.spin(my_node)\n    my_node.destroy_node()  # cleans up pub-subs, etc\n    rclpy.shutdown()\n
    "},{"location":"client_libraries/rclpy/nodes.html#shutdown-hooks","title":"Shutdown Hooks","text":"

    ROS 1 had rospy.on_shutdown() - but there is not an equivalent in ROS 2. It really is not needed though, since we manually shut things down rather than was the case in rospy which used many global variables:

    try:\n    rclpy.spin(my_node)\nexcept KeyboardInterrupt:\n    pass\nfinally:\n    my_node.on_shutdown()  # do any custom cleanup\n    my_node.destroy_node()\n    rclpy.shutdown()\n
    "},{"location":"client_libraries/rclpy/parameters.html","title":"rclpy: Parameters","text":""},{"location":"client_libraries/rclpy/parameters.html#declaring-and-accessing-parameters","title":"Declaring and Accessing Parameters","text":"
    # node is rclpy.node.Node instance\n# 42 is a great default for a parameter\nnode.declare_parameter('my_param_name', 42)\n\n# To get the value:\nparam = node.get_parameter('my_param_name').value\n
    "},{"location":"client_libraries/rclpy/parameters.html#declaring-multiple-parameters-at-once","title":"Declaring Multiple Parameters at Once","text":"

    There seems to be a fairly undocumentated part of the rclpy API:

    node.declare_parameters(\n    namespace='',\n    parameters=[\n        ('my_param_name', 'default value'),\n        ('my_other_param', 42)\n    ]\n)\n
    "},{"location":"client_libraries/rclpy/parameters.html#dynamic-parameters","title":"Dynamic Parameters","text":"

    In ROS 2, all parameters can be dynamically updated through a ROS 2 service (there is no need to define duplicate stuff as with dynamic reconfigure).

    from rcl_interfaces.msg import SetParametersResult\n\nimport rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__('my_node_name')\n\n        # Declare a parameter\n        self.declare_parameter('my_param_name', 42)\n\n        # Then create callback\n        self.add_on_set_parameters_callback(self.callback)\n\n    def callback(self, parameters):\n        result = SetParametersResult(successful=True)\n\n        for p in parameters:\n            if p.name == 'my_param_name':\n                if p.type_ != p.Type.INTEGER:\n                    result.successful = False\n                    result.reason = 'my_param_name must be an Integer'\n                    return result\n                if p.value < 20:\n                    result.successful = False\n                    result.reason = 'my_param_name must be >= 20;\n                    return result\n\n        # Return success, so updates are seen via get_parameter()\n        return result\n

    For an example of calling the set_parameters service, see ROS Answers

    "},{"location":"client_libraries/rclpy/tf2.html","title":"rclpy: TF2","text":"

    The TF2 library provides easy access to transformations. All of the examples below require a dependency on the tf2_ros package.

    "},{"location":"client_libraries/rclpy/tf2.html#listening-for-transforms","title":"Listening for Transforms","text":"
    import rclpy\nfrom rclpy.node import Node\nfrom tf2_ros.buffer import Buffer\nfrom tf2_ros.transform_listener import TransformListener\n\nclass MyNode(Node):\n    def __init__(self):\n        super().__init__('my_node')\n\n        self.buffer = Buffer()\n        self.listener = TransformListener(self.buffer, self)\n
    "},{"location":"client_libraries/rclpy/tf2.html#applying-transforms","title":"Applying Transforms","text":"

    TF2 can be extended by packages that provide implementations of transform. The tf2_geometry_msgs package provides these for _geometry_msgs. The example below uses geometry_msgs.msg.PointStamped - but this should work for any data type in geometry_msgs:

    from geometry_msgs.msg import PointStamped\nfrom tf2_ros.buffer import Buffer\nfrom tf2_ros.transform_listener import TransformListener\nimport tf2_geometry_msgs\n\n# Setup buffer/listener as above\n\np1 = PointStamped()\np1.header.frame_id = 'source_frame'\n# fill in p1\n\np2 = buffer.transform(p1, 'target_frame')\n
    "},{"location":"client_libraries/rclpy/tf2.html#using-the-latest-transform","title":"Using the Latest Transform","text":"
    from rclpy.time import Time\n\n# Setting the stamp to a blank Time() instance will use the latest available data\np1 = PointStamped()\np1.header.frame_id = 'source_frame'\np1.header.stamp = Time().to_msg()\n\np2 = buffer.transform(p1, 'target_frame')\n
    "},{"location":"client_libraries/rclpy/tf2.html#transformations","title":"Transformations","text":"

    In ROS 1, tf included the transformations module. tf2 has no similar module. It is recommended to use transforms3d Python package, which is available through apt on Ubuntu 22.04:

    sudo apt-get install python3-transforms3d\n

    Or via pip on other operating systems:

    sudo pip3 install transforms3d\n

    For instance, to rotate a point:

    import numpy as np\nfrom transforms3d.quaternion import quat2mat\n\n# Create rotation matrix from quaternion\nR = quat2mat([w, x, y, z])\n# Create a vector to rotate\nV = np.array([x, y, z]).reshape((3, 1))\n# Rotate the vector\nM = np.dot(R, V)\n\np = PointStamped()\np.point.x = M[0, 0]\np.point.y = M[1, 0]\np.point.z = M[2, 0]\n

    Additionally, in ROS 2 Humble there is the tf_transformations package which should make it as easy as changing your imports from tf.transformations to tf_transformations.

    "},{"location":"client_libraries/rclpy/time.html","title":"rclpy: Time","text":"

    To get the equivalent of rospy.Time.now(), you now need a ROS 2 node:

    import rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def some_func(self):\n        t = self.get_clock().now()\n        msg.header.stamp = t.to_msg()\n

    Converting from Duration to messages is common:

    import rclpy\nfrom rclpy.duration import Duration\n\nmsg.duration = Duration(seconds=1).to_msg()\n
    "},{"location":"client_libraries/rclpy/time.html#timers","title":"Timers","text":"

    Timers are created from the Node:

    import rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__(\"my_node\")\n\n        # Create a timer that fires every quarter second\n        timer_period = 0.25\n        self.timer = self.create_timer(timer_period, self.callback)\n\n    def callback(self):\n        self.get_logger().info(\"timer has fired\")\n
    "},{"location":"client_libraries/rclpy/time.html#rates","title":"Rates","text":"

    Using Rate objects in ROS 2 is a bit more complex than in ROS 1. Due to implementation details, we need to spin() or the sleep() function will block. This is most easily accomplished using a thread:

    import threading\n\n# Run spin in a thread, make thread daemon so we don't have to join it to exit\nthread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True)\nthread.start()\n\nrate = node.create_rate(10)\nwhile rclpy.ok():\n    print('This prints at 10hz')\n    rate.sleep()\n
    "},{"location":"other/pages/index.html","title":"Other","text":""},{"location":"other/pages/index.html#other","title":"Other","text":""},{"location":"other/pages/index.html#rosbag2","title":"rosbag2","text":""},{"location":"other/pages/index.html#cmake","title":"CMake","text":""},{"location":"other/pages/index.html#colcon","title":"colcon","text":""},{"location":"other/pages/index.html#command-line-tools","title":"Command Line Tools","text":""},{"location":"other/pages/index.html#ros2launch","title":"ros2launch","text":""},{"location":"other/pages/index.html#networking-dds-cyclonedds","title":"Networking (DDS & CycloneDDS)","text":""},{"location":"other/pages/index.html#package-documentation","title":"Package Documentation","text":""},{"location":"other/pages/index.html#qos-quality-of-service","title":"QoS (Quality of Service)","text":""},{"location":"other/pages/index.html#documenting-with-rosdoc2","title":"Documenting with rosdoc2","text":""},{"location":"other/pages/index.html#rosdep","title":"rosdep","text":""},{"location":"other/pages/bag.html","title":"rosbag2","text":""},{"location":"other/pages/bag.html#common-command-line","title":"Common Command Line","text":""},{"location":"other/pages/bag.html#api-tutorials","title":"API Tutorials","text":"

    The ROS documentation has tutorials for recording and playback, which are linked below. There is no official tutorial for reading from a bagfile in Python, the linked one comes from an MCAP tutorial:

    "},{"location":"other/pages/bag.html#converting-bag-files-from-ros-1","title":"Converting Bag Files from ROS 1","text":"

    The following works, assuming you are mainly using standard topics. For instance, I have converted a number of bagfiles intended for SLAM testing, which mainly consist of sensor_msgs::LaserScan, TF, and nav_msgs::Odometry data.

    The easiest route for converting bagfiles is to use rosbags:

    sudo pip3 install rosbags\nrosbag-convert bagfile_name.bag\n

    This will create a new folder with the name bagfile_name containing the SQLite file and the index file. At this point, you can inspect the bagfile:

    ros2 bag info bagfile_name\n\nFiles:             bagfile_name.db3\nBag size:          65.7 MiB\nStorage id:        sqlite3\nROS Distro:        rosbags\nDuration:          122.298s\nStart:             Jun 15 2014 21:41:49.861 (1402882909.861)\nEnd:               Jun 15 2014 21:43:52.159 (1402883032.159)\nMessages:          35187\nTopic information: Topic: odom | Type: nav_msgs/msg/Odometry | Count: 12141 | Serialization Format: cdr\n                   Topic: tf | Type: tf2_msgs/msg/TFMessage | Count: 16939 | Serialization Format: cdr\n                   Topic: base_scan | Type: sensor_msgs/msg/LaserScan | Count: 4884 | Serialization Format: cdr\n                   Topic: odom_combined | Type: nav_msgs/msg/Odometry | Count: 1223 | Serialization Format: cdr\n

    This bagfile is now useable in ROS 2. However, you can also go a bit further by compressing the bagfile, and migrating it to the new MCAP file format. First, create a YAML file to define the output format:

    # output_format.yaml\noutput_bags:\n- uri: bagfile_name_compressed\n  all: true\n  compression_mode: file\n  compression_format: zstd\n

    Now, run the conversion:

    ros2 bag convert -i bagfile_name -o output_format.yaml\n

    Inspecting the new bag, we can see that compression is very nice - a 75% reduction in file size for my typical SLAM bag files:

    ros2 bag info bagfile_name_compressed\n\nFiles:             bagfile_name_compressed.mcap.zstd\nBag size:          16.7 MiB\nStorage id:        mcap\nROS Distro:        rolling\nDuration:          122.298s\nStart:             Jun 15 2014 21:41:49.861 (1402882909.861)\nEnd:               Jun 15 2014 21:43:52.159 (1402883032.159)\nMessages:          35187\nTopic information: Topic: base_scan | Type: sensor_msgs/msg/LaserScan | Count: 4884 | Serialization Format: cdr\n                   Topic: odom | Type: nav_msgs/msg/Odometry | Count: 12141 | Serialization Format: cdr\n                   Topic: odom_combined | Type: nav_msgs/msg/Odometry | Count: 1223 | Serialization Format: cdr\n                   Topic: tf | Type: tf2_msgs/msg/TFMessage | Count: 16939 | Serialization Format: cdr\n
    "},{"location":"other/pages/bag.html#removing-tf-from-a-bagfile","title":"Removing TF from a Bagfile","text":"

    I have often found that I needed to remove problematic TF data from a bagfile, usually filtering out the odom->base_link transform so that I can replace it with a more sophisticated filtered one. In this particular example we just toss the whole message out - but you could selectively remove the individual transform within the message and serialize the edited message back up.

    import rosbag2_py\nfrom rclpy.serialization import deserialize_message\nfrom rosidl_runtime_py.utilities import get_message\n\nreader = rosbag2_py.SequentialReader()\nreader.open(\n    rosbag2_py.StorageOptions(uri='input_bag', storage_id='mcap'),\n    rosbag2_py.ConverterOptions(input_serialization_format='cdr',\n                                output_serialization_format='cdr')\n)\n\nwriter = rosbag2_py.SequentialWriter()\nwriter.open(\n    rosbag2_py.StorageOptions(uri='output_bag', storage_id='mcap'),\n    rosbag2_py.ConverterOptions('', '')\n)\n\n# First preprocess the topics\ntopic_types = reader.get_all_topics_and_types()\nfor topic_type in topic_types:\n    # Setup output bagfile - same topics as input\n    writer.create_topic(topic_type)\n    # Note the type if this is our TF data\n    if topic_type.name == '/tf':\n        tf_typename = topic_type.type\n\n# Now iterate through messages, copying over the ones we don't filter\nwhile reader.has_next():\n    topic, data, timestamp = reader.read_next()\n    filter_out = False\n    # Filter out odom tf messages\n    if topic == '/tf':\n        # Deserialize the message so we can look at it\n        msg_type = get_message(tf_typename)\n        msg = deserialize_message(data, msg_type)\n        # Iterate through the transforms in the message\n        for transform in msg.transforms:\n            if transform.header.frame_id == 'odom':\n                # Toss this message\n                filter_out = True\n                break\n    # Copy over message if it isn't odom\n    if not filter_out:\n        writer.write(topic, data, timestamp)\n
    "},{"location":"other/pages/cmake.html","title":"CMake","text":"

    While you don't need to know everything about CMake to use ROS 2, knowing a bit will really be helpful. You might be interested in the CMake tutorial which explains the basics of CMake.

    "},{"location":"other/pages/cmake.html#ament","title":"Ament","text":"

    Ament is a set of CMake modules specifically designed for ROS 2 with the intent of making CMake easier to use. See also the Ament CMake documentation.

    The basic structure of an ament package:

    cmake_minimum_required(VERSION 3.5)\nproject(my_package_name)\n\n# Default to C++14\nif(NOT CMAKE_CXX_STANDARD)\n  set(CMAKE_CXX_STANDARD 14)\nendif()\n\nif(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n  add_compile_options(-Wall -Wextra -Wpedantic)\nendif()\n\n# Find packages\nfind_package(ament_cmake REQUIRED)\nfind_package(rclcpp REQUIRED)\n\n# Include our own headers\ninclude_directories(include)\n\n# Create a node\nadd_executable(my_node src/my_node.cpp)\nament_target_dependencies(my_node\n  rclcpp\n  # Other ament dependencies\n  # This sets up include and linker paths\n)\n\nadd_library(my_library src/my_library.cpp)\nament_target_dependencies(my_library\n  rclcpp\n)\n\n# Install our headers\ninstall(\n  DIRECTORY include/\n  DESTINATION include\n)\n\n# Install our node and library\ninstall(TARGETS my_node my_library\n  ARCHIVE DESTINATION lib\n  LIBRARY DESTINATION lib\n  RUNTIME DESTINATION lib/${PACKAGE_NAME}\n)\n\n# Install some Python scripts\ninstall(\n  PROGRAMS\n    scripts/my_script.py\n  DESTINATION\n    lib/${PROJECT_NAME}\n)\n\n# Tell downstream packages where to find our headers\nament_export_include_directories(include)\n# Tell downstream packages our libraries to link against\nament_export_libraries(my_library)\n# Help downstream packages to find transitive dependencies\nament_export_dependencies(\n  rclcpp\n)\nament_package()\n
    "},{"location":"other/pages/cmake.html#linting-configuration","title":"Linting Configuration","text":"

    I prefer a more ROS 1-style code style. To allow braces to be on their own lines:

    if(BUILD_TESTING)\n  find_package(ament_cmake_cpplint)\n  ament_cpplint(FILTERS \"-whitespace/braces\" \"-whitespace/newline\")\nendif()\n
    "},{"location":"other/pages/cmake.html#installing-python-scripts","title":"Installing Python Scripts","text":"
    install(\n  PROGRAMS\n    scripts/script1.py\n    scripts/script2.py\n  DESTINATION lib/${PROJECT_NAME}\n)\n
    "},{"location":"other/pages/cmake.html#depending-on-messages-in-same-package","title":"Depending on Messages in Same Package","text":"

    It is generally best practice to put messages in separate packages, but sometimes, especially for drivers, you want the messages in the same package.

    The following example worked in earlier versions of ROS 2 - but the syntax has changed See the Implementing custom interfaces tutorial for newer ROS 2 distributions.

    # Note: this WILL NOT work in ROS 2 Humble or later\nfind_package(rosidl_default_generators REQUIRED)\n\n# Generate some messages\nrosidl_generate_interfaces(${PROJECT_NAME}\n  \"msg/MyMessage.msg\"\n)\n\n# Add a node which uses the messages\nadd_executable(my_node my_node.cpp)\nrosidl_target_interfaces(my_node ${PROJECT_NAME} \"rosidl_typesupport_cpp\")\n
    "},{"location":"other/pages/cmake.html#removing-boost-from-pluginlib","title":"Removing Boost from Pluginlib","text":"

    Pluginlib supports both boost::shared_ptrs and std::shared_ptrs by default, if you want to avoid depending on Boost in your shiny new ROS 2 library, you need to specifically tell pluginlib not to include the Boost versions:

    target_compile_definitions(your_library PUBLIC \"PLUGINLIB__DISABLE_BOOST_FUNCTIONS\")\n
    "},{"location":"other/pages/cmake.html#using-eigen3","title":"Using Eigen3","text":"

    Add eigen to your package.xml as a dependency, and then:

    find_package(Eigen3 REQUIRED)\ninclude_directories(${EIGEN3_INCLUDE_DIRS})\n
    "},{"location":"other/pages/cmake.html#building-python-extensions-in-c","title":"Building Python Extensions in C++","text":"

    The example below is based on the etherbotix package.

    find_package(PythonLibs REQUIRED)\nfind_package(Boost REQUIRED python)\nfind_package(ament_cmake_python REQUIRED)\nfind_package(python_cmake_module REQUIRED)\n\nament_python_install_package(${PROJECT_NAME})\n\nadd_library(\n  my_python SHARED\n  ${SOURCE_FILES}\n)\nset_target_properties(\n  my_python PROPERTIES\n  LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}\n  PREFIX \"\"\n)\ntarget_link_libraries(my_python\n  ${Boost_LIBRARIES}\n  ${PYTHON_LIBRARIES}\n)\n\ninstall(\n  TARGETS my_python\n  DESTINATION \"${PYTHON_INSTALL_DIR}/${PROJECT_NAME}\"\n)\n
    "},{"location":"other/pages/colcon.html","title":"Command Line: Colcon","text":""},{"location":"other/pages/colcon.html#my-aliases","title":"My Aliases","text":"

    I hate typing - so these are the aliases in my ~/.bashrc for my most common workflow:

    alias build2=\"colcon build --symlink-install\"\nalias test2=\"colcon test --event-handlers console_direct+\"\n
    "},{"location":"other/pages/colcon.html#build","title":"Build","text":"

    colcon is used to build ROS 2 packages in a workspace.

    Build all packages:

    colcon build\n

    To avoid having to rebuild when tweaking Python scripts, config files, and launch files:

    colcon build --symlink-install\n

    To fix most build issues, especially if you have added or removed packages to your workspace or installed new RMW implementations, clean the CMake cache. See this ROS Answers post for more details.

    colcon build --cmake-clean-cache\n
    "},{"location":"other/pages/colcon.html#cmake-arguments","title":"CMake Arguments","text":"
    colcon build --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo\n
    "},{"location":"other/pages/colcon.html#test","title":"Test","text":"

    To test and get results to screen:

    colcon test\ncolcon test-result --verbose\n

    Build/test a single package:

    colcon <verb> --packages-select <package-name>\n
    "},{"location":"other/pages/colcon.html#formatting","title":"Formatting","text":"

    Get the output to the screen:

    colcon <verb> --event-handlers console_direct+\n
    "},{"location":"other/pages/colcon.html#be-careful-with-workspaces","title":"Be Careful With Workspaces","text":"

    You should probably build your workspace in a window where you have NOT sourced the setup.bash of that workspace. For more details on why, see this ticket.

    "},{"location":"other/pages/colcon.html#verb-documentation","title":"Verb Documentation","text":""},{"location":"other/pages/command_line.html","title":"Command Line Tools","text":""},{"location":"other/pages/launch.html","title":"roslaunch2","text":"

    See also rosetta_launch for a number of examples.

    "},{"location":"other/pages/launch.html#python-based-launch-files","title":"Python-Based Launch Files","text":"

    Python-based launch files all pretty much follow the same structure.

    [!NOTE] Prior to Foxy, the parameters name, namespace, and executable were prefixed with node_

    from launch import LaunchDescription\nfrom launch_ros.actions import Node\n\ndef generate_launch_description():\n    return LaunchDescription([\n        Node(\n            name='node_runtime_name',\n            package='ros2_package_name',\n            executable='name_of_executable',\n            parameters=[{'name_of_int_param': 1,\n                         'name_of_str_param': 'value'}],\n            remappings=[('from', 'to')],\n            output='screen',\n        ),\n        # More Nodes!\n    ])\n
    "},{"location":"other/pages/launch.html#making-a-launch-file-executable","title":"Making a Launch File Executable","text":"

    Normally, launch files are run with:

    ros2 launch pkg launch.py\n

    But, sometimes you want an executable launch file (for instance to put in a systemd job). Assuming you follow the default pattern shown above, all you need to add:

    #!/usr/bin/env python3\n\nimport sys\nfrom launch import LaunchService\n\n# define generate_launch_description() as above\n\nif __name__ == '__main__':\n    desc = generate_launch_description()\n    service = LaunchService(argv=sys.argv[1:])\n    service.include_launch_description(desc)\n    return service.run()\n
    "},{"location":"other/pages/launch.html#loading-parameters-from-a-file","title":"Loading Parameters From a File","text":"

    Some nodes have many parameters, it's easier to put them in a YAML file:

    node_name:\n  ros__parameters:\n      some_int_param: 1\n      some_str_param: \"the_value\"\n

    To load this:

    from ament_index_python.packages import get_package_share_directory\n\n# Assuming you have a file called package_name/config/params.yaml\nnode_params = os.path.join(\n    get_package_share_directory('package_name'),\n    'config',\n    'params.yaml'\n)\n\n# Add this to your LaunchDescription\nNode(\n    name='node_runtime_name',\n    package='ros2_package_name',\n    executable='name_of_executable',\n    parameters=[{'another_param': 42.0},\n                 node_params]\n)\n
    "},{"location":"other/pages/launch.html#including-python-launch-files","title":"Including Python Launch Files","text":"
    import os\nfrom ament_index_python.packages import get_package_share_directory\nfrom launch.actions import IncludeLaunchDescription\nfrom launch.launch_description_sources import PythonLaunchDescriptionSource\n\n# Assuming you have a file called package_name/launch/my_launch.launch.py\nmy_launch_file = os.path.join(\n    get_package_share_directory('package_name'),\n    'launch',\n    'my_launch.launch.py'\n)\n\n# Add this to your LaunchDescription\nIncludeLaunchDescription(\n    PythonLaunchDescriptionSource([my_launch_file])\n),\n
    "},{"location":"other/pages/launch.html#loading-a-urdf","title":"Loading a URDF","text":"

    Most robots need to load their URDF into the robot_state_publisher, and maybe other nodes as well:

    import os\nfrom ament_index_python.packages import get_package_share_directory\nfrom launch_ros.actions import Node\n\n# Load the URDF into a parameter\ndesc_dir = get_package_share_directory('robot_description_pkg')\nurdf_path = os.path.join(desc_dir, 'robots', 'my_urdf.urdf')\nurdf = open(urdf_path).read()\n\n# Add this to your LaunchDescription\nNode(\n    name='robot_state_publisher',\n    package='robot_state_publisher',\n    executable='robot_state_publisher',\n    parameters=[{'robot_description': urdf}],\n)\n
    "},{"location":"other/pages/launch.html#installing-launch-files","title":"Installing Launch Files","text":""},{"location":"other/pages/networking.html","title":"Networking","text":"

    ROS 2 uses DDS for message transport.

    Set the environment variable RMW_IMPLEMENTATION to select a DDS implementation (RMW = robotic middleware). For instance:

    export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp\n

    To check which RMW implementation is being used:

    ros2 doctor --report | grep middleware\n
    "},{"location":"other/pages/networking.html#dds-discovery","title":"DDS Discovery","text":"

    There is no rosmaster like in ROS 1. Node discovery is peer-to-peer with nodes announcing their topics on startup and periodically after that. By default, any machines on the same network will see each other if they have the same ROS_DOMAIN_ID.

    ROS_DOMAIN_ID can be any number between 0 and 253, although it is recommended to use numbers less than 128.

    In addition to the ROS_DOMAIN_ID, CycloneDDS supports a domain tag, which allows nearly infinite partitioning of the network (see below).

    If you want to limit communication to the localhost set ROS_LOCALHOST_ONLY, which is available since Eloquent.

    "},{"location":"other/pages/networking.html#cyclonedds","title":"CycloneDDS","text":"

    Cyclone can be configured with XML. This can be stored in a file or passed directly in the environment variable CYCLONEDDS_URI. A full list of supported options can be found in the eclipse-cyclonedds repo. See also the Guide to Configuration.

    "},{"location":"other/pages/networking.html#cyclonedds-multiple-interfaces","title":"CycloneDDS: Multiple Interfaces","text":"

    Cyclone currently only works with a single network interface. If you have multiple interfaces, specify which one to use in the NetworkInterfaceAddress:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>\n    </General>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#cyclonedds-disabling-multicast-except-discovery","title":"CycloneDDS: Disabling Multicast (Except Discovery)","text":"

    Some network hardware can perform poorly with multicast (especially with WIFI). You can limit multicast to just discovery:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <AllowMulticast>spdp</AllowMulticast>\n    </General>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#cyclonedds-domain-tag","title":"CycloneDDS: Domain Tag","text":"

    CycloneDDS also defines a \"Domain Tag\" which allows to drastically partition the network with a custom string:

    <CycloneDDS>\n  <Domain>\n    <Discovery>\n      <Tag>my_robot_name</Tag>\n    </Discovery>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#example","title":"Example","text":"

    The above tags can all be combined:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <!-- Explicitly set network interface -->\n      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>\n      <!-- Use multicast for discovery only -->\n      <AllowMulticast>spdp</AllowMulticast>\n    </General>\n    <Discovery>\n      <!-- This tag has to be the same on each machine -->\n      <Tag>my_robot_name</Tag>\n    </Discovery>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/packages.html","title":"Package Documentation","text":"

    Currently, documentation is a bit all over the place. This list is certainly not extensive, but I find it helpful to have quick links to the frequently used packages.

    "},{"location":"other/pages/packages.html#core-documentation","title":"Core Documentation","text":""},{"location":"other/pages/packages.html#higher-level-packages","title":"Higher Level Packages","text":""},{"location":"other/pages/qos.html","title":"QoS (Quality of Service)","text":""},{"location":"other/pages/qos.html#recommended-practices","title":"Recommended Practices","text":"

    Subscribing with SensorDataQoS means that we will get unreliable transport but be more immune to lossy networks. By publishing with SystemDefaultQoS we will still be able to connect to any subscriber as long as they are specifying volatile rather than transient local.

    "},{"location":"other/pages/qos.html#overrides","title":"Overrides","text":"

    rclcpp offers a consistent way to define QoS overrides as parameters. There is a (somewhat dated/inaccurate) design doc on this feature. In your code:

    // Allow overriding QoS settings (history, depth, reliability)\nrclcpp::PublisherOptions pub_options;\npub_options.qos_overriding_options = rclcpp::QosOverridingOptions::with_default_policies();\nnode->create_publisher<std_msgs::msg::String>(\"topic\", rclcpp::QoS(10), pub_options);\n

    Then, in your parameter file, you can define:

    node_name:\n  ros__parameters:\n    qos_overrides:\n      fully_resolved_topic_name:\n        publisher:\n          reliability: reliable\n          history_depth: 100\n          history: keep_last\n

    The same workflow works for subscribers, you just use rclcpp::SubscriptionOptions instance and change publisher to subscription in the YAML file.

    "},{"location":"other/pages/qos.html#magic-numbers","title":"Magic Numbers","text":"

    If you perform ros2 topic info -v /some/topic and you examine the QoS settings, you may note that several fields are set to the magic number 2147483651294967295 (or 2,147,483,651,294,967,295). e.g.

    QoS profile:\n    Reliability: RMW_QOS_POLICY_RELIABILITY_RELIABLE\n    Durability: RMW_QOS_POLICY_DURABILITY_VOLATILE\n    Lifespan: 2147483651294967295 nanoseconds\n    Deadline: 2147483651294967295 nanoseconds\n    Liveliness: RMW_QOS_POLICY_LIVELINESS_AUTOMATIC\n    Liveliness lease duration: 2147483651294967295 nanoseconds\n

    What is the significance of this number, which is approximately equal to 68 years? It's not \\(2^n - 1\\) as you might expect for a magic number. However, durations are defined as

    [builtin_interfaces/Duration]\n# Seconds component, range is valid over any possible int32 value.\nint32 sec\n\n# Nanoseconds component in the range of [0, 1e9).\nuint32 nanosec\n

    The max value for an int32 is \\(2^{31} - 1\\). The max value for an uint32 is \\(2^{32} - 1\\). (Note: According to the definition, any value for nanosec over 1e9 is invalid.)

    So the magic number 2147483651294967295 is the number of nanoseconds equivalent to \\(2^{31} -1\\) seconds (\\(2147483647\\) seconds) added to \\(2^{32} - 1\\) nanoseconds (\\(4.294967295\\) seconds).

    "},{"location":"other/pages/rosdep.html","title":"Rosdep","text":"

    rosdep install --from-paths src --ignore-src --rosdistro=humble -y

    "},{"location":"other/pages/rosdoc2.html","title":"rosdoc2","text":"

    Documentation for ROS 2 packages can be built with rosdoc2.

    When a doc job is configured in the rosdistro, the resulting documentation is uploaded to docs.ros.org. The design document for per package documentation lays out the directory structure.

    "},{"location":"other/pages/rosdoc2.html#linking-to-other-packages","title":"Linking to Other Packages","text":"

    This is a dirty hack, but appears to be the only way to have the table of contents link to another package's documentation without hard-coding the distro. The trailing http:// changes how Sphinx processes the link:

    .. toctree::\n   :maxdepth: 2\n\n   other_package <../other_package/index.html#http://>\n

    An example of this can be seen in the documentation for image_pipeline, where we want to link to the documentation for each of the packages within the metapackage.

    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"index.html","title":"Home","text":""},{"location":"index.html#ros-2-cookbook","title":"ROS 2 Cookbook","text":"

    A collection of snippets ranging multiple topics in ROS 2.

    "},{"location":"literate-nav.html","title":"Literate nav","text":""},{"location":"client_libraries/index.html","title":"Client Libraries","text":""},{"location":"client_libraries/index.html#client-libraries","title":"Client libraries","text":""},{"location":"client_libraries/index.html#c-rclcpp","title":"C++ (rclcpp)","text":""},{"location":"client_libraries/index.html#python-rclpy","title":"Python (rclpy)","text":""},{"location":"client_libraries/rclcpp/index.html","title":"C++","text":""},{"location":"client_libraries/rclcpp/index.html#c-rclcpp","title":"C++ (rclcpp)","text":""},{"location":"client_libraries/rclcpp/index.html#nodes","title":"Nodes","text":""},{"location":"client_libraries/rclcpp/index.html#parameters","title":"Parameters","text":""},{"location":"client_libraries/rclcpp/index.html#logging","title":"Logging","text":""},{"location":"client_libraries/rclcpp/index.html#time","title":"Time","text":""},{"location":"client_libraries/rclcpp/index.html#tf2","title":"TF2","text":""},{"location":"client_libraries/rclcpp/index.html#point-clouds","title":"Point Clouds","text":""},{"location":"client_libraries/rclcpp/index.html#workarounds","title":"Workarounds","text":""},{"location":"client_libraries/rclcpp/index.html#initialization","title":"Initialization","text":""},{"location":"client_libraries/rclcpp/initialization.html","title":"rclcpp: Initialization","text":""},{"location":"client_libraries/rclcpp/initialization.html#shared_from_this-cannot-be-used-in-constructor","title":"shared_from_this Cannot Be Used in Constructor","text":"

    Sometime, you need to initialize some components outside of the constructor of your node. For example, you may want to initialize an object by passing a shared pointer to the node to it. This is not possible in the constructor, because the node is not fully initialized yet.

    The following pattern can be used to initialize components outside of the constructor of your node. This pattern was found through this comment.

    // Class declaration\n\nclass InitializedComponent\n{\npublic:\n  COMPOSITION_PUBLIC\n  explicit InitializedComponent(const rclcpp::NodeOptions & options);\n\n  COMPOSITION_PUBLIC\n  rclcpp::node_interfaces::NodeBaseInterface::SharedPtr\n  get_node_base_interface() const;\n\nprivate:\n  rclcpp::Node::SharedPtr node_;\n};\n\n// Class implementation\n\nInitializedComponent::InitializedComponent(const rclcpp::NodeOptions & options)\n: node_(std::make_shared<rclcpp::Node>(\"node\", options))\n{\n  /* Do some initialization here */\n}\n\nrclcpp::node_interfaces::NodeBaseInterface::SharedPtr\nInitializedComponent::get_node_base_interface() const\n{\n  return this->node_->get_node_base_interface();\n}\n
    "},{"location":"client_libraries/rclcpp/initialization.html#overriding-default-parameters","title":"Overriding Default Parameters","text":"

    This is a pattern I find especially helpful when writing tests, where I want to create several tests with different parameters but would rather not maintain separate YAML or launch files with parameters:

    rclcpp::NodeOptions options;\noptions.parameter_overrides(\n  {\n    {\"my_namespace.my_parameter\", 5.2},\n    {\"another_parameter\", \"new value\"},\n  }\n);\nauto node = std::make_shared<rclcpp::Node>(\"node_name\", options);\n

    "},{"location":"client_libraries/rclcpp/logging.html","title":"rclcpp: Logging","text":""},{"location":"client_libraries/rclcpp/logging.html#changing-the-logging-level","title":"Changing the logging level","text":"
    #include <rclcpp/logger.hpp>\n\nrclcpp::get_logger(\"nav2_costmap_2d\").set_level(rclcpp::Logger::Level::Debug);\n
    "},{"location":"client_libraries/rclcpp/nodes.html","title":"rclcpp: Nodes and Components","text":""},{"location":"client_libraries/rclcpp/nodes.html#creating-a-component","title":"Creating a Component","text":"

    ROS 2 introduces components, which are nodes that can be run together in a single process. The benefits of using node compositions has been documented in a recent paper, Impact of ROS 2 Node Composition in Robotic Systems. Given the ease of use and the provided tooling, it really makes sense to make just about every node a component, and then let the rclcpp_components tooling create a node executable for you:

    #include <rclcpp/rclcpp.hpp>\n\nnamespace my_pkg\n{\n\nclass MyComponent : public rclcpp::Node\n{\npublic:\n  MyComponent(const rclcpp::NodeOptions& options)\n  : rclcpp::Node(\"node_name\", options)\n  {\n    // Do all your setup here - subscribers/publishers/timers\n    // After you return, an executor will be started\n\n    // Note: you cannot use shared_from_this()\n    //       here because the node is not fully\n    //       initialized.\n  }\n};\n\n}  // namespace my_pkg\n\n#include \"rclcpp_components/register_node_macro.hpp\"\nRCLCPP_COMPONENTS_REGISTER_NODE(my_pkg::MyComponent)\n

    The in the CMakeLists.txt:

    add_library(my_component SHARED\n  src/my_component.cpp\n)\nament_target_dependencies(my_component\n  rclcpp\n  rclcpp_components\n)\n\n# Also add a node executable which simply loads the component\nrclcpp_components_register_node(my_component\n  PLUGIN \"my_pkg::MyComponent\"\n  EXECUTABLE my_node\n)\n
    "},{"location":"client_libraries/rclcpp/nodes.html#executors","title":"Executors","text":"

    To run an executor in a thread:

    #include <thread>\n#include <rclcpp/rclcpp.hpp>\n#include <rclcpp/executors/single_threaded_executor.hpp>\n\nrclcpp::executors::SingleThreadedExecutor executor;\n\n// Node is rclcpp::Node::SharedPtr instance\nexecutor.add_node(node);\nstd::thread executor_thread(\n  std::bind(&rclcpp::executors::SingleThreadedExecutor::spin,\n            &executor));\n
    "},{"location":"client_libraries/rclcpp/parameters.html","title":"rclcpp: Parameters","text":"

    Parameters need to be declared. At the same time, you can get the value if you are not planning to update the value again later:

    // node is an instance of rclcpp::Node\n// 42 is a great default for a parameter\nint param = node.declare_parameter<int>(\"my_param_name\", 42);\n

    To get the value:

    int param;\nnode.get_parameter(\"my_param_name\", param);\n
    "},{"location":"client_libraries/rclcpp/parameters.html#dynamic-parameters","title":"Dynamic Parameters","text":"

    In ROS 2, all parameters can be dynamically updated through a ROS 2 service (there is no need to define duplicate stuff as with dynamic reconfigure).

    The example below works in Eloquent or later (earlier ROS 2 releases supported only a single callback and had a slightly different API). See the documentation for rclcpp::ParameterType for valid types.

    #include <vector>\n#include <rclcpp/rclcpp.hpp>\n\nclass MyNode : public rclcpp::Node\n{\npublic:\n  MyNode()\n  {\n    // Declare parameters first\n\n    // Then create callback\n    param_cb_ = this->add_on_set_parameters_callback(\n      std::bind(&MyNode::updateCallback, this, std::placeholders::_1));\n  }\n\nprivate:\n  // This will get called whenever a parameter gets updated\n  rcl_interfaces::msg::SetParametersResult updateCallback(\n    const std::vector<rclcpp::Parameter> & parameters)\n  {\n    rcl_interfaces::msg::SetParametersResult result;\n    result.successful = true;\n\n    for (const rclcpp::Parameter & param : parameters)\n    {\n      if (param.get_name() == \"my_param_name\")\n      {\n        if (param.get_type() != rclcpp::ParameterType::PARAMETER_STRING)\n        {\n          result.successful = false;\n          result.reason = \"my_param_name must be a string\";\n          break;\n        }\n\n        // Optionally do something with parameter\n      }\n    }\n\n    return result;\n  }\n\n  // Need to hold a pointer to the callback\n  rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr param_cb_;\n};\n
    "},{"location":"client_libraries/rclcpp/parameters.html#porting-xml-arrays-to-ros-2","title":"Porting XML Arrays to ROS 2","text":"

    Complex parameter blocks could be parsed as XML in ROS 1. For instance, the robot_calibration package used a block like:

    models:\n - name: arm\n   type: chain\n   frame: wrist_roll_link\n - name: camera\n   type: camera3d\n   frame: head_camera_rgb_optical_frame\n

    In ROS 2, the common pattern is to make the array a list of just names, then have each name be a block of parameters:

    models:\n- arm\n- camera\narm:\n  type: chain3d\n  frame: wrist_roll_link\ncamera:\n  type: camera3d\n  frame: head_camera_rgb_optical_frame\n

    To parse such a block:

    std::vector<ModelParams> models;\nauto model_names = node->declare_parameter<std::vector<std::string>>(\"models\", std::vector<std::string>());\nfor (auto name : model_names)\n{\n  RCLCPP_INFO(logger, \"Adding model: %s\", name.c_str());\n  ModelParams params;\n  params.name = name;\n  params.type = node->declare_parameter<std::string>(name + \".type\", std::string());\n  params.frame = node->declare_parameter<std::string>(name + \".frame\", std::string());\n  params.param_name = node->declare_parameter<std::string>(name + \".param_name\", std::string());\n  models.push_back(params);\n}\n
    "},{"location":"client_libraries/rclcpp/pcl.html","title":"rclcpp: Point Clouds","text":"

    The sensor_msgs/PointCloud2 is a very common type of ROS message for processing perception data in ROS. It is also one of the most complex messages to actually interpret.

    The complexity of the message derives from the fact that it holds arbitrary fields in a single giant data store. This allows the PointCloud2 message to work with any type of cloud (for instance, XYZ points only, XYZRGB, or even XYZI), but adds a bit of complexity in accessing the data in the cloud.

    In ROS 1, there was a simpler PointCloud message, but this has been deprecated and will be removed in ROS 2 Galactic.

    "},{"location":"client_libraries/rclcpp/pcl.html#using-the-pointcloud2iterator","title":"Using the PointCloud2Iterator","text":"

    The sensor_msgs package provides a C++ PointCloud2Iterator which can be used to create, modify and access PointCloud2 messages.

    To create a new message:

    #include \"sensor_msgs/msg/point_cloud2.hpp\"\n#include \"sensor_msgs/point_cloud2_iterator.hpp\"\n\nsensor_msgs::msg::PointCloud2 msg;\n\n// Fill in the size of the cloud\nmsg.height = 480;\nmsg.width = 640;\n\n// Create the modifier to setup the fields and memory\nsensor_msgs::PointCloud2Modifier mod(msg);\n\n// Set the fields that our cloud will have\nmod.setPointCloud2FieldsByString(2, \"xyz\", \"rgb\");\n\n// Set up memory for our points\nmod.resize(msg.height * msg.width);\n\n// Now create iterators for fields\nsensor_msgs::PointCloud2Iterator<float> iter_x(cloud_msg, \"x\");\nsensor_msgs::PointCloud2Iterator<float> iter_y(cloud_msg, \"y\");\nsensor_msgs::PointCloud2Iterator<float> iter_z(cloud_msg, \"z\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_r(cloud_msg, \"r\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_g(cloud_msg, \"g\");\nsensor_msgs::PointCloud2Iterator<uint8_t> iter_b(cloud_msg, \"b\");\n\nfor (; iter_x != iter_x.end(); ++iter_x, ++iter_y, ++iter_z, ++iter_r, ++iter_g, ++iter_b)\n{\n  *iter_x = 0.0;\n  *iter_y = 0.0;\n  *iter_z = 0.0;\n  *iter_r = 0;\n  *iter_g = 255;\n  *iter_b = 0;\n}\n
    "},{"location":"client_libraries/rclcpp/pcl.html#using-pcl","title":"Using PCL","text":"

    For a number of operations, you might want to convert to a pcl::PointCloud in order to use the extensive API of the Point Cloud Library.

    In ROS 1, the pcl_ros package allowed you to write a subscriber whose callback took a pcl::PointCloud directly, however this package has not yet been ported to ROS 2. Regardless, it is pretty straight forward to do the conversion yourself with the pcl_conversions package:

    #include \"pcl_conversions/pcl_conversions.h\"\n\nvoid cloud_callback(const sensor_msgs::msg::PointCloud2::SharedPtr msg)\n{\n  // PCL still uses boost::shared_ptr internally\n  pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud =\n    boost::make_shared<pcl::PointCloud<pcl::PointXYZRGB>>();\n\n  // This will convert the message into a pcl::PointCloud\n  pcl::fromROSMsg(*msg, *cloud);\n}\n

    You can also go in the opposite direction:

    #include \"pcl_conversions/pcl_conversions.h\"\n\npcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud;\nsensor_msgs::msg::PointCloud2 msg;\n\npcl::toROSMsg(*cloud, msg);\ncloud_publisher->publish(msg);\n
    "},{"location":"client_libraries/rclcpp/tf2.html","title":"rclcpp: TF2","text":"

    The TF2 library provides easy access to transformations. All of the examples below require a dependency on the tf2_ros package.

    "},{"location":"client_libraries/rclcpp/tf2.html#broadcasting-transforms","title":"Broadcasting Transforms","text":"
    #include <tf2_ros/transform_broadcaster.h>\nstd::unique_ptr<tf2_ros::TransformBroadcaster> broadcaster;\n\nbroadcaster = std::make_unique<tf2_ros::TransformBroadcaster>(nodePtr);\n\ngeometry_msgs::msg::TransformStamped transform;\ntransform.header.stamp = node->now();\ntransform.header.frame_id = \"odom\";\ntransform.child_frame_id = \"base_link\";\n\n// Fill in transform.transform.translation\n// Fill in transform.transform.rotation\n\nbroadcaster->sendTransform(transform);\n
    "},{"location":"client_libraries/rclcpp/tf2.html#listening-for-transforms","title":"Listening for Transforms","text":"
    #include \"tf2_ros/transform_listener.h\"\n\nstd::shared_ptr<tf2_ros::Buffer> tf_buffer;\nstd::shared_ptr<tf2_ros::TransformListener> tf_listener;\n\nrclcpp::Node node(\"name_of_node\");\n\ntf_buffer.reset(new tf2_ros::Buffer(node.get_clock()));\ntf_listener.reset(new tf2_ros::TransformListener(*tf_buffer_));\n
    "},{"location":"client_libraries/rclcpp/tf2.html#applying-transforms","title":"Applying Transforms","text":"

    TF2 can be extended by packages that provide implementations of transform. The tf2_geometry_msgs package provides these for _geometry_msgs. The example below uses geometry_msgs::msg::PointStamped - but this should work for any data type in geometry_msgs:

    #include <tf2_geometry_msgs/tf2_geometry_msgs.h>\n\ngeometry_msg::msgs::PointStamped in, out;\nin.header.frame_id = \"source_frame\";\n\ntry\n{\n  tf_buffer->transform(in, out, \"target_frame\");\n}\ncatch (const tf2::TransformException& ex)\n{\n  RCLCPP_ERROR(rclcpp::get_logger(\"logger_name\"), \"Could not transform point.\");\n}\n

    The transform function can also take a timeout. It will then wait for the transform to be available up to that amount of time:

    tf_buffer->transform(in, out, \"target_frame\", tf2::durationFromSec(1.0));\n
    "},{"location":"client_libraries/rclcpp/tf2.html#get-latest-transform","title":"Get Latest Transform","text":"

    A common work flow is to get the \"latest\" transform. In ROS 2, this can be accomplished using tf2::TimePointZero, but requires using lookupTransform and then calling doTransform (which is basically what transform does internally):

    geometry_msgs::msg::PointStamped in, out;\n\ngeometry_msgs::msg::TransformStamped transform =\n   tf_buffer->lookupTransform(\"target_frame\",\n                              in.header.frame_id,\n                              tf2::TimePointZero);\n\ntf2::doTransform(in, out, transform);\n
    "},{"location":"client_libraries/rclcpp/tf2.html#constructing-transform-from-euler-angles","title":"Constructing Transform from Euler Angles","text":"

    There are numerous ways to do this (using Eigen, KDL, etc) - but it is also possible with only tf2 APIs:

    #include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>\n\ntf2::Quaternion quat;\n// While the documentation for tf2 orders the names of these as yaw, pitch, roll,\n// it specifies that yaw = rotation about Y, which is not what most of us expect\nquat.setEuler(pitch, roll, yaw);\n\ngeometry_msgs::msg::TransformStamped transform;\ntransform.transform.rotation = tf2::toMsg(quat);\n// Probably also fill in transform.header and transform.transform.translation\n\n// Can now use the transform with tf2::doTransform()\n
    "},{"location":"client_libraries/rclcpp/tf2.html#getting-yaw-angle-from-quaternion","title":"Getting Yaw Angle from Quaternion","text":"
    #include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>\n#include <tf2/utils.hpp>\n\ngeometry_msgs::msg::Pose pose;\ndouble yaw = tf2::getYaw(pose.orientation);\n

    [!NOTE] tf2::getYaw requires some pieces from tf2_geometery_msgs/tf2_geometry_msgs.hpp but cannot depend on them because it would create a circular dependency. This means you absolutely need to include tf2_geometry_msgs BEFORE tf2/utils or you will get an undefined reference to tf2::fromMsg

    "},{"location":"client_libraries/rclcpp/tf2.html#transform-from-eigenisometry3d","title":"Transform from Eigen::Isometry3d","text":"
    #include <tf2_eigen/tf2_eigen.hpp>\n\nEigen::Isometry3d map_to_odom;\ngeometry_msgs::msg::TransformStamped transform = tf2::eigenToTransform(map_to_odom);\n// Fill in header, child_frame_id\n
    "},{"location":"client_libraries/rclcpp/time.html","title":"rclcpp: Time","text":"

    The rclcpp::Time and rclcpp::Duration are a significant departure from their ROS 1 equivalents, but are more closely related to std::chrono. For an in-depth discussion comparing with std::chrono, see this discussion on ROS Discourse.

    When porting certain ROS 1 libraries, there may be significant usage of timestamps as floating-point seconds. To get floating point seconds from an _rclcpp::Time_:

    // node is instance of rclcpp::Node\nrclcpp::Time t = node.now();\ndouble seconds = t.seconds();\n

    There is no constructor for Time from seconds represented by a floating point, so you first need to convert to nanoseconds:

    rclcpp::Time t(static_cast<uin64_t>(seconds * 1e9));\n

    rclcpp::Duration does have functions to go both directions:

    rclcpp::Duration d = rclcpp::Duration::from_seconds(1.0);\ndouble seconds = d.seconds();\n

    Unlike ROS 1, sleeping does NOT occur from a Duration instance:

    rclcpp::sleep_for(std::chrono::milliseconds(50));\n
    "},{"location":"client_libraries/rclcpp/time.html#api-documentation-links","title":"API Documentation Links","text":""},{"location":"client_libraries/rclcpp/workarounds.html","title":"rclcpp: Workarounds","text":""},{"location":"client_libraries/rclcpp/workarounds.html#lazy-publishers","title":"Lazy Publishers","text":"

    Prior to the Iron release, ROS 2 did not support subscriber connect callbacks. This code creates a function which is called periodically to check if we need to start or stop subscribers:

    #include <rclcpp/rclcpp.hpp>\n#include <std_msgs/msg/float64.hpp>\n\nclass LazyPublisherEx : rclcpp::Node\n{\npublic:\n  LazyPublisherEx(const rclcpp::NodeOptions & options) :\n    Node(\"lazy_ex\", options)\n  {\n    // Setup timer\n    timer = this->create_wall_timer(\n      std::chrono::seconds(1),\n      std::bind(&LazyPublisher::periodic, this));\n  }\n\nprivate:\n  void periodic()\n  {\n    if (pub_.get_subscription_count() > 0)\n    {\n        // We have a subscriber, do any setup required\n    }\n    else\n    {\n        // Subscriber has disconnected, do any shutdown\n    }\n  }\n\n  rclcpp::Publisher<std_msgs::msg::Float64>::SharedPtr pub_;\n  rclcpp::TimerBase::SharedPtr timer_;\n};\n

    The same can be done when using image transport, you simply have to change from get_subscription_count to getNumSubscribers:

    #include <rclcpp/rclcpp.hpp>\n#include <image_transport/image_transport.h>\n\nclass LazyPublisherEx : rclcpp::Node\n{\npublic:\n  LazyPublisherEx(const rclcpp::NodeOptions & options) :\n    Node(\"lazy_ex\", options)\n  {\n    // Setup timer\n    timer = this->create_wall_timer(\n      std::chrono::seconds(1),\n      std::bind(&LazyPublisher::periodic, this));\n  }\n\nprivate:\n  void periodic()\n  {\n    if (pub_.getNumSubscribers() > 0)\n    {\n        // We have a subscriber, do any setup required\n    }\n    else\n    {\n        // Subscriber has disconnected, do any shutdown\n    }\n  }\n\n  image_transport::CameraPublisher pub_;\n  rclcpp::TimerBase::SharedPtr timer_;\n};\n
    "},{"location":"client_libraries/rclpy/index.html","title":"Python","text":""},{"location":"client_libraries/rclpy/index.html#python-rclpy","title":"Python (rclpy)","text":""},{"location":"client_libraries/rclpy/index.html#nodes","title":"Nodes","text":""},{"location":"client_libraries/rclpy/index.html#parameters","title":"Parameters","text":""},{"location":"client_libraries/rclpy/index.html#time","title":"Time","text":""},{"location":"client_libraries/rclpy/index.html#tf2","title":"TF2","text":""},{"location":"client_libraries/rclpy/nodes.html","title":"rclpy: Node Basics","text":"

    Most nodes have publishers and subscribers, both of which are creating by calling functions of the Node instance:

    import rclpy\nfrom rclpy.node import Node\n\nfrom std_msgs.msg import String\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__('my_node_name')\n\n        self.publisher = self.create_publisher(String, 'output_topic', 10)\n        self.subscription = self.create_subscription(\n            String,\n            'input_topic',\n            self.callback,\n            10)\n\n    def callback(self, msg):\n        self.get_logger().info(\"Recieved: %s\" % msg.data)\n        self.publisher.publish(msg)\n\nif __name___ == \"__main__\":\n    rclpy.init()\n    my_node = MyNode()\n    rclpy.spin(my_node)\n    my_node.destroy_node()  # cleans up pub-subs, etc\n    rclpy.shutdown()\n
    "},{"location":"client_libraries/rclpy/nodes.html#shutdown-hooks","title":"Shutdown Hooks","text":"

    ROS 1 had rospy.on_shutdown() - but there is not an equivalent in ROS 2. It really is not needed though, since we manually shut things down rather than was the case in rospy which used many global variables:

    try:\n    rclpy.spin(my_node)\nexcept KeyboardInterrupt:\n    pass\nfinally:\n    my_node.on_shutdown()  # do any custom cleanup\n    my_node.destroy_node()\n    rclpy.shutdown()\n
    "},{"location":"client_libraries/rclpy/parameters.html","title":"rclpy: Parameters","text":""},{"location":"client_libraries/rclpy/parameters.html#declaring-and-accessing-parameters","title":"Declaring and Accessing Parameters","text":"
    # node is rclpy.node.Node instance\n# 42 is a great default for a parameter\nnode.declare_parameter('my_param_name', 42)\n\n# To get the value:\nparam = node.get_parameter('my_param_name').value\n
    "},{"location":"client_libraries/rclpy/parameters.html#declaring-multiple-parameters-at-once","title":"Declaring Multiple Parameters at Once","text":"

    There seems to be a fairly undocumentated part of the rclpy API:

    node.declare_parameters(\n    namespace='',\n    parameters=[\n        ('my_param_name', 'default value'),\n        ('my_other_param', 42)\n    ]\n)\n
    "},{"location":"client_libraries/rclpy/parameters.html#dynamic-parameters","title":"Dynamic Parameters","text":"

    In ROS 2, all parameters can be dynamically updated through a ROS 2 service (there is no need to define duplicate stuff as with dynamic reconfigure).

    from rcl_interfaces.msg import SetParametersResult\n\nimport rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__('my_node_name')\n\n        # Declare a parameter\n        self.declare_parameter('my_param_name', 42)\n\n        # Then create callback\n        self.add_on_set_parameters_callback(self.callback)\n\n    def callback(self, parameters):\n        result = SetParametersResult(successful=True)\n\n        for p in parameters:\n            if p.name == 'my_param_name':\n                if p.type_ != p.Type.INTEGER:\n                    result.successful = False\n                    result.reason = 'my_param_name must be an Integer'\n                    return result\n                if p.value < 20:\n                    result.successful = False\n                    result.reason = 'my_param_name must be >= 20;\n                    return result\n\n        # Return success, so updates are seen via get_parameter()\n        return result\n

    For an example of calling the set_parameters service, see ROS Answers

    "},{"location":"client_libraries/rclpy/tf2.html","title":"rclpy: TF2","text":"

    The TF2 library provides easy access to transformations. All of the examples below require a dependency on the tf2_ros package.

    "},{"location":"client_libraries/rclpy/tf2.html#listening-for-transforms","title":"Listening for Transforms","text":"
    import rclpy\nfrom rclpy.node import Node\nfrom tf2_ros.buffer import Buffer\nfrom tf2_ros.transform_listener import TransformListener\n\nclass MyNode(Node):\n    def __init__(self):\n        super().__init__('my_node')\n\n        self.buffer = Buffer()\n        self.listener = TransformListener(self.buffer, self)\n
    "},{"location":"client_libraries/rclpy/tf2.html#applying-transforms","title":"Applying Transforms","text":"

    TF2 can be extended by packages that provide implementations of transform. The tf2_geometry_msgs package provides these for _geometry_msgs. The example below uses geometry_msgs.msg.PointStamped - but this should work for any data type in geometry_msgs:

    from geometry_msgs.msg import PointStamped\nfrom tf2_ros.buffer import Buffer\nfrom tf2_ros.transform_listener import TransformListener\nimport tf2_geometry_msgs\n\n# Setup buffer/listener as above\n\np1 = PointStamped()\np1.header.frame_id = 'source_frame'\n# fill in p1\n\np2 = buffer.transform(p1, 'target_frame')\n
    "},{"location":"client_libraries/rclpy/tf2.html#using-the-latest-transform","title":"Using the Latest Transform","text":"
    from rclpy.time import Time\n\n# Setting the stamp to a blank Time() instance will use the latest available data\np1 = PointStamped()\np1.header.frame_id = 'source_frame'\np1.header.stamp = Time().to_msg()\n\np2 = buffer.transform(p1, 'target_frame')\n
    "},{"location":"client_libraries/rclpy/tf2.html#transformations","title":"Transformations","text":"

    In ROS 1, tf included the transformations module. tf2 has no similar module. It is recommended to use transforms3d Python package, which is available through apt on Ubuntu 22.04:

    sudo apt-get install python3-transforms3d\n

    Or via pip on other operating systems:

    sudo pip3 install transforms3d\n

    For instance, to rotate a point:

    import numpy as np\nfrom transforms3d.quaternion import quat2mat\n\n# Create rotation matrix from quaternion\nR = quat2mat([w, x, y, z])\n# Create a vector to rotate\nV = np.array([x, y, z]).reshape((3, 1))\n# Rotate the vector\nM = np.dot(R, V)\n\np = PointStamped()\np.point.x = M[0, 0]\np.point.y = M[1, 0]\np.point.z = M[2, 0]\n

    Additionally, in ROS 2 Humble there is the tf_transformations package which should make it as easy as changing your imports from tf.transformations to tf_transformations.

    "},{"location":"client_libraries/rclpy/time.html","title":"rclpy: Time","text":"

    To get the equivalent of rospy.Time.now(), you now need a ROS 2 node:

    import rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def some_func(self):\n        t = self.get_clock().now()\n        msg.header.stamp = t.to_msg()\n

    Converting from Duration to messages is common:

    import rclpy\nfrom rclpy.duration import Duration\n\nmsg.duration = Duration(seconds=1).to_msg()\n
    "},{"location":"client_libraries/rclpy/time.html#timers","title":"Timers","text":"

    Timers are created from the Node:

    import rclpy\nfrom rclpy.node import Node\n\nclass MyNode(Node):\n\n    def __init__(self):\n        super().__init__(\"my_node\")\n\n        # Create a timer that fires every quarter second\n        timer_period = 0.25\n        self.timer = self.create_timer(timer_period, self.callback)\n\n    def callback(self):\n        self.get_logger().info(\"timer has fired\")\n
    "},{"location":"client_libraries/rclpy/time.html#rates","title":"Rates","text":"

    Using Rate objects in ROS 2 is a bit more complex than in ROS 1. Due to implementation details, we need to spin() or the sleep() function will block. This is most easily accomplished using a thread:

    import threading\n\n# Run spin in a thread, make thread daemon so we don't have to join it to exit\nthread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True)\nthread.start()\n\nrate = node.create_rate(10)\nwhile rclpy.ok():\n    print('This prints at 10hz')\n    rate.sleep()\n
    "},{"location":"other/pages/index.html","title":"Other","text":""},{"location":"other/pages/index.html#other","title":"Other","text":""},{"location":"other/pages/index.html#rosbag2","title":"rosbag2","text":""},{"location":"other/pages/index.html#cmake","title":"CMake","text":""},{"location":"other/pages/index.html#colcon","title":"colcon","text":""},{"location":"other/pages/index.html#command-line-tools","title":"Command Line Tools","text":""},{"location":"other/pages/index.html#ros2launch","title":"ros2launch","text":""},{"location":"other/pages/index.html#networking-dds-cyclonedds","title":"Networking (DDS & CycloneDDS)","text":""},{"location":"other/pages/index.html#package-documentation","title":"Package Documentation","text":""},{"location":"other/pages/index.html#qos-quality-of-service","title":"QoS (Quality of Service)","text":""},{"location":"other/pages/index.html#documenting-with-rosdoc2","title":"Documenting with rosdoc2","text":""},{"location":"other/pages/index.html#rosdep","title":"rosdep","text":""},{"location":"other/pages/bag.html","title":"rosbag2","text":""},{"location":"other/pages/bag.html#common-command-line","title":"Common Command Line","text":""},{"location":"other/pages/bag.html#api-tutorials","title":"API Tutorials","text":"

    The ROS documentation has tutorials for recording and playback, which are linked below. There is no official tutorial for reading from a bagfile in Python, the linked one comes from an MCAP tutorial:

    "},{"location":"other/pages/bag.html#converting-bag-files-from-ros-1","title":"Converting Bag Files from ROS 1","text":"

    The following works, assuming you are mainly using standard topics. For instance, I have converted a number of bagfiles intended for SLAM testing, which mainly consist of sensor_msgs::LaserScan, TF, and nav_msgs::Odometry data.

    The easiest route for converting bagfiles is to use rosbags:

    sudo pip3 install rosbags\nrosbag-convert bagfile_name.bag\n

    This will create a new folder with the name bagfile_name containing the SQLite file and the index file. At this point, you can inspect the bagfile:

    ros2 bag info bagfile_name\n\nFiles:             bagfile_name.db3\nBag size:          65.7 MiB\nStorage id:        sqlite3\nROS Distro:        rosbags\nDuration:          122.298s\nStart:             Jun 15 2014 21:41:49.861 (1402882909.861)\nEnd:               Jun 15 2014 21:43:52.159 (1402883032.159)\nMessages:          35187\nTopic information: Topic: odom | Type: nav_msgs/msg/Odometry | Count: 12141 | Serialization Format: cdr\n                   Topic: tf | Type: tf2_msgs/msg/TFMessage | Count: 16939 | Serialization Format: cdr\n                   Topic: base_scan | Type: sensor_msgs/msg/LaserScan | Count: 4884 | Serialization Format: cdr\n                   Topic: odom_combined | Type: nav_msgs/msg/Odometry | Count: 1223 | Serialization Format: cdr\n

    This bagfile is now useable in ROS 2. However, you can also go a bit further by compressing the bagfile, and migrating it to the new MCAP file format. First, create a YAML file to define the output format:

    # output_format.yaml\noutput_bags:\n- uri: bagfile_name_compressed\n  all: true\n  compression_mode: file\n  compression_format: zstd\n

    Now, run the conversion:

    ros2 bag convert -i bagfile_name -o output_format.yaml\n

    Inspecting the new bag, we can see that compression is very nice - a 75% reduction in file size for my typical SLAM bag files:

    ros2 bag info bagfile_name_compressed\n\nFiles:             bagfile_name_compressed.mcap.zstd\nBag size:          16.7 MiB\nStorage id:        mcap\nROS Distro:        rolling\nDuration:          122.298s\nStart:             Jun 15 2014 21:41:49.861 (1402882909.861)\nEnd:               Jun 15 2014 21:43:52.159 (1402883032.159)\nMessages:          35187\nTopic information: Topic: base_scan | Type: sensor_msgs/msg/LaserScan | Count: 4884 | Serialization Format: cdr\n                   Topic: odom | Type: nav_msgs/msg/Odometry | Count: 12141 | Serialization Format: cdr\n                   Topic: odom_combined | Type: nav_msgs/msg/Odometry | Count: 1223 | Serialization Format: cdr\n                   Topic: tf | Type: tf2_msgs/msg/TFMessage | Count: 16939 | Serialization Format: cdr\n
    "},{"location":"other/pages/bag.html#removing-tf-from-a-bagfile","title":"Removing TF from a Bagfile","text":"

    I have often found that I needed to remove problematic TF data from a bagfile, usually filtering out the odom->base_link transform so that I can replace it with a more sophisticated filtered one. In this particular example we just toss the whole message out - but you could selectively remove the individual transform within the message and serialize the edited message back up.

    import rosbag2_py\nfrom rclpy.serialization import deserialize_message\nfrom rosidl_runtime_py.utilities import get_message\n\nreader = rosbag2_py.SequentialReader()\nreader.open(\n    rosbag2_py.StorageOptions(uri='input_bag', storage_id='mcap'),\n    rosbag2_py.ConverterOptions(input_serialization_format='cdr',\n                                output_serialization_format='cdr')\n)\n\nwriter = rosbag2_py.SequentialWriter()\nwriter.open(\n    rosbag2_py.StorageOptions(uri='output_bag', storage_id='mcap'),\n    rosbag2_py.ConverterOptions('', '')\n)\n\n# First preprocess the topics\ntopic_types = reader.get_all_topics_and_types()\nfor topic_type in topic_types:\n    # Setup output bagfile - same topics as input\n    writer.create_topic(topic_type)\n    # Note the type if this is our TF data\n    if topic_type.name == '/tf':\n        tf_typename = topic_type.type\n\n# Now iterate through messages, copying over the ones we don't filter\nwhile reader.has_next():\n    topic, data, timestamp = reader.read_next()\n    filter_out = False\n    # Filter out odom tf messages\n    if topic == '/tf':\n        # Deserialize the message so we can look at it\n        msg_type = get_message(tf_typename)\n        msg = deserialize_message(data, msg_type)\n        # Iterate through the transforms in the message\n        for transform in msg.transforms:\n            if transform.header.frame_id == 'odom':\n                # Toss this message\n                filter_out = True\n                break\n    # Copy over message if it isn't odom\n    if not filter_out:\n        writer.write(topic, data, timestamp)\n
    "},{"location":"other/pages/cmake.html","title":"CMake","text":"

    While you don't need to know everything about CMake to use ROS 2, knowing a bit will really be helpful. You might be interested in the CMake tutorial which explains the basics of CMake.

    "},{"location":"other/pages/cmake.html#ament","title":"Ament","text":"

    Ament is a set of CMake modules specifically designed for ROS 2 with the intent of making CMake easier to use. See also the Ament CMake documentation.

    The basic structure of an ament package:

    cmake_minimum_required(VERSION 3.5)\nproject(my_package_name)\n\n# Default to C++14\nif(NOT CMAKE_CXX_STANDARD)\n  set(CMAKE_CXX_STANDARD 14)\nendif()\n\nif(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n  add_compile_options(-Wall -Wextra -Wpedantic)\nendif()\n\n# Find packages\nfind_package(ament_cmake REQUIRED)\nfind_package(rclcpp REQUIRED)\n\n# Include our own headers\ninclude_directories(include)\n\n# Create a node\nadd_executable(my_node src/my_node.cpp)\nament_target_dependencies(my_node\n  rclcpp\n  # Other ament dependencies\n  # This sets up include and linker paths\n)\n\nadd_library(my_library src/my_library.cpp)\nament_target_dependencies(my_library\n  rclcpp\n)\n\n# Install our headers\ninstall(\n  DIRECTORY include/\n  DESTINATION include\n)\n\n# Install our node and library\ninstall(TARGETS my_node my_library\n  ARCHIVE DESTINATION lib\n  LIBRARY DESTINATION lib\n  RUNTIME DESTINATION lib/${PACKAGE_NAME}\n)\n\n# Install some Python scripts\ninstall(\n  PROGRAMS\n    scripts/my_script.py\n  DESTINATION\n    lib/${PROJECT_NAME}\n)\n\n# Tell downstream packages where to find our headers\nament_export_include_directories(include)\n# Tell downstream packages our libraries to link against\nament_export_libraries(my_library)\n# Help downstream packages to find transitive dependencies\nament_export_dependencies(\n  rclcpp\n)\nament_package()\n
    "},{"location":"other/pages/cmake.html#linting-configuration","title":"Linting Configuration","text":"

    I prefer a more ROS 1-style code style. To allow braces to be on their own lines:

    if(BUILD_TESTING)\n  find_package(ament_cmake_cpplint)\n  ament_cpplint(FILTERS \"-whitespace/braces\" \"-whitespace/newline\")\nendif()\n
    "},{"location":"other/pages/cmake.html#installing-python-scripts","title":"Installing Python Scripts","text":"
    install(\n  PROGRAMS\n    scripts/script1.py\n    scripts/script2.py\n  DESTINATION lib/${PROJECT_NAME}\n)\n
    "},{"location":"other/pages/cmake.html#depending-on-messages-in-same-package","title":"Depending on Messages in Same Package","text":"

    It is generally best practice to put messages in separate packages, but sometimes, especially for drivers, you want the messages in the same package.

    The following example worked in earlier versions of ROS 2 - but the syntax has changed See the Implementing custom interfaces tutorial for newer ROS 2 distributions.

    # Note: this WILL NOT work in ROS 2 Humble or later\nfind_package(rosidl_default_generators REQUIRED)\n\n# Generate some messages\nrosidl_generate_interfaces(${PROJECT_NAME}\n  \"msg/MyMessage.msg\"\n)\n\n# Add a node which uses the messages\nadd_executable(my_node my_node.cpp)\nrosidl_target_interfaces(my_node ${PROJECT_NAME} \"rosidl_typesupport_cpp\")\n
    "},{"location":"other/pages/cmake.html#removing-boost-from-pluginlib","title":"Removing Boost from Pluginlib","text":"

    Pluginlib supports both boost::shared_ptrs and std::shared_ptrs by default, if you want to avoid depending on Boost in your shiny new ROS 2 library, you need to specifically tell pluginlib not to include the Boost versions:

    target_compile_definitions(your_library PUBLIC \"PLUGINLIB__DISABLE_BOOST_FUNCTIONS\")\n
    "},{"location":"other/pages/cmake.html#using-eigen3","title":"Using Eigen3","text":"

    Add eigen to your package.xml as a dependency, and then:

    find_package(Eigen3 REQUIRED)\ninclude_directories(${EIGEN3_INCLUDE_DIRS})\n
    "},{"location":"other/pages/cmake.html#building-python-extensions-in-c","title":"Building Python Extensions in C++","text":"

    The example below is based on the etherbotix package.

    find_package(PythonLibs REQUIRED)\nfind_package(Boost REQUIRED python)\nfind_package(ament_cmake_python REQUIRED)\nfind_package(python_cmake_module REQUIRED)\n\nament_python_install_package(${PROJECT_NAME})\n\nadd_library(\n  my_python SHARED\n  ${SOURCE_FILES}\n)\nset_target_properties(\n  my_python PROPERTIES\n  LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}\n  PREFIX \"\"\n)\ntarget_link_libraries(my_python\n  ${Boost_LIBRARIES}\n  ${PYTHON_LIBRARIES}\n)\n\ninstall(\n  TARGETS my_python\n  DESTINATION \"${PYTHON_INSTALL_DIR}/${PROJECT_NAME}\"\n)\n
    "},{"location":"other/pages/colcon.html","title":"Command Line: Colcon","text":""},{"location":"other/pages/colcon.html#my-aliases","title":"My Aliases","text":"

    I hate typing - so these are the aliases in my ~/.bashrc for my most common workflow:

    alias build2=\"colcon build --symlink-install\"\nalias test2=\"colcon test --event-handlers console_direct+\"\n
    "},{"location":"other/pages/colcon.html#build","title":"Build","text":"

    colcon is used to build ROS 2 packages in a workspace.

    Build all packages:

    colcon build\n

    To avoid having to rebuild when tweaking Python scripts, config files, and launch files:

    colcon build --symlink-install\n

    To fix most build issues, especially if you have added or removed packages to your workspace or installed new RMW implementations, clean the CMake cache. See this ROS Answers post for more details.

    colcon build --cmake-clean-cache\n
    "},{"location":"other/pages/colcon.html#cmake-arguments","title":"CMake Arguments","text":"
    colcon build --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo\n
    "},{"location":"other/pages/colcon.html#test","title":"Test","text":"

    To test and get results to screen:

    colcon test\ncolcon test-result --verbose\n

    Build/test a single package:

    colcon <verb> --packages-select <package-name>\n
    "},{"location":"other/pages/colcon.html#formatting","title":"Formatting","text":"

    Get the output to the screen:

    colcon <verb> --event-handlers console_direct+\n
    "},{"location":"other/pages/colcon.html#be-careful-with-workspaces","title":"Be Careful With Workspaces","text":"

    You should probably build your workspace in a window where you have NOT sourced the setup.bash of that workspace. For more details on why, see this ticket.

    "},{"location":"other/pages/colcon.html#verb-documentation","title":"Verb Documentation","text":""},{"location":"other/pages/command_line.html","title":"Command Line Tools","text":""},{"location":"other/pages/launch.html","title":"roslaunch2","text":"

    See also rosetta_launch for a number of examples.

    "},{"location":"other/pages/launch.html#python-based-launch-files","title":"Python-Based Launch Files","text":"

    Python-based launch files all pretty much follow the same structure.

    [!NOTE] Prior to Foxy, the parameters name, namespace, and executable were prefixed with node_

    from launch import LaunchDescription\nfrom launch_ros.actions import Node\n\ndef generate_launch_description():\n    return LaunchDescription([\n        Node(\n            name='node_runtime_name',\n            package='ros2_package_name',\n            executable='name_of_executable',\n            parameters=[{'name_of_int_param': 1,\n                         'name_of_str_param': 'value'}],\n            remappings=[('from', 'to')],\n            output='screen',\n        ),\n        # More Nodes!\n    ])\n
    "},{"location":"other/pages/launch.html#making-a-launch-file-executable","title":"Making a Launch File Executable","text":"

    Normally, launch files are run with:

    ros2 launch pkg launch.py\n

    But, sometimes you want an executable launch file (for instance to put in a systemd job). Assuming you follow the default pattern shown above, all you need to add:

    #!/usr/bin/env python3\n\nimport sys\nfrom launch import LaunchService\n\n# define generate_launch_description() as above\n\nif __name__ == '__main__':\n    desc = generate_launch_description()\n    service = LaunchService(argv=sys.argv[1:])\n    service.include_launch_description(desc)\n    return service.run()\n
    "},{"location":"other/pages/launch.html#loading-parameters-from-a-file","title":"Loading Parameters From a File","text":"

    Some nodes have many parameters, it's easier to put them in a YAML file:

    node_name:\n  ros__parameters:\n      some_int_param: 1\n      some_str_param: \"the_value\"\n

    To load this:

    from ament_index_python.packages import get_package_share_directory\n\n# Assuming you have a file called package_name/config/params.yaml\nnode_params = os.path.join(\n    get_package_share_directory('package_name'),\n    'config',\n    'params.yaml'\n)\n\n# Add this to your LaunchDescription\nNode(\n    name='node_runtime_name',\n    package='ros2_package_name',\n    executable='name_of_executable',\n    parameters=[{'another_param': 42.0},\n                 node_params]\n)\n
    "},{"location":"other/pages/launch.html#including-python-launch-files","title":"Including Python Launch Files","text":"
    import os\nfrom ament_index_python.packages import get_package_share_directory\nfrom launch.actions import IncludeLaunchDescription\nfrom launch.launch_description_sources import PythonLaunchDescriptionSource\n\n# Assuming you have a file called package_name/launch/my_launch.launch.py\nmy_launch_file = os.path.join(\n    get_package_share_directory('package_name'),\n    'launch',\n    'my_launch.launch.py'\n)\n\n# Add this to your LaunchDescription\nIncludeLaunchDescription(\n    PythonLaunchDescriptionSource([my_launch_file])\n),\n
    "},{"location":"other/pages/launch.html#loading-a-urdf","title":"Loading a URDF","text":"

    Most robots need to load their URDF into the robot_state_publisher, and maybe other nodes as well:

    import os\nfrom ament_index_python.packages import get_package_share_directory\nfrom launch_ros.actions import Node\n\n# Load the URDF into a parameter\ndesc_dir = get_package_share_directory('robot_description_pkg')\nurdf_path = os.path.join(desc_dir, 'robots', 'my_urdf.urdf')\nurdf = open(urdf_path).read()\n\n# Add this to your LaunchDescription\nNode(\n    name='robot_state_publisher',\n    package='robot_state_publisher',\n    executable='robot_state_publisher',\n    parameters=[{'robot_description': urdf}],\n)\n
    "},{"location":"other/pages/launch.html#installing-launch-files","title":"Installing Launch Files","text":""},{"location":"other/pages/networking.html","title":"Networking","text":"

    ROS 2 uses DDS for message transport.

    Set the environment variable RMW_IMPLEMENTATION to select a DDS implementation (RMW = robotic middleware). For instance:

    export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp\n

    To check which RMW implementation is being used:

    ros2 doctor --report | grep middleware\n
    "},{"location":"other/pages/networking.html#dds-discovery","title":"DDS Discovery","text":"

    There is no rosmaster like in ROS 1. Node discovery is peer-to-peer with nodes announcing their topics on startup and periodically after that. By default, any machines on the same network will see each other if they have the same ROS_DOMAIN_ID.

    ROS_DOMAIN_ID can be any number between 0 and 253, although it is recommended to use numbers less than 128.

    In addition to the ROS_DOMAIN_ID, CycloneDDS supports a domain tag, which allows nearly infinite partitioning of the network (see below).

    If you want to limit communication to the localhost set ROS_LOCALHOST_ONLY, which is available since Eloquent.

    "},{"location":"other/pages/networking.html#cyclonedds","title":"CycloneDDS","text":"

    Cyclone can be configured with XML. This can be stored in a file or passed directly in the environment variable CYCLONEDDS_URI. A full list of supported options can be found in the eclipse-cyclonedds repo. See also the Guide to Configuration.

    "},{"location":"other/pages/networking.html#cyclonedds-multiple-interfaces","title":"CycloneDDS: Multiple Interfaces","text":"

    Cyclone currently only works with a single network interface. If you have multiple interfaces, specify which one to use in the NetworkInterfaceAddress:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>\n    </General>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#cyclonedds-disabling-multicast-except-discovery","title":"CycloneDDS: Disabling Multicast (Except Discovery)","text":"

    Some network hardware can perform poorly with multicast (especially with WIFI). You can limit multicast to just discovery:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <AllowMulticast>spdp</AllowMulticast>\n    </General>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#cyclonedds-domain-tag","title":"CycloneDDS: Domain Tag","text":"

    CycloneDDS also defines a \"Domain Tag\" which allows to drastically partition the network with a custom string:

    <CycloneDDS>\n  <Domain>\n    <Discovery>\n      <Tag>my_robot_name</Tag>\n    </Discovery>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/networking.html#example","title":"Example","text":"

    The above tags can all be combined:

    <CycloneDDS>\n  <Domain>\n    <General>\n      <!-- Explicitly set network interface -->\n      <NetworkInterfaceAddress>wlp2s0</NetworkInterfaceAddress>\n      <!-- Use multicast for discovery only -->\n      <AllowMulticast>spdp</AllowMulticast>\n    </General>\n    <Discovery>\n      <!-- This tag has to be the same on each machine -->\n      <Tag>my_robot_name</Tag>\n    </Discovery>\n  </Domain>\n</CycloneDDS>\n
    "},{"location":"other/pages/packages.html","title":"Package Documentation","text":"

    Currently, documentation is a bit all over the place. This list is certainly not extensive, but I find it helpful to have quick links to the frequently used packages.

    "},{"location":"other/pages/packages.html#core-documentation","title":"Core Documentation","text":""},{"location":"other/pages/packages.html#higher-level-packages","title":"Higher Level Packages","text":""},{"location":"other/pages/qos.html","title":"QoS (Quality of Service)","text":""},{"location":"other/pages/qos.html#recommended-practices","title":"Recommended Practices","text":"

    Subscribing with SensorDataQoS means that we will get unreliable transport but be more immune to lossy networks. By publishing with SystemDefaultQoS we will still be able to connect to any subscriber as long as they are specifying volatile rather than transient local.

    "},{"location":"other/pages/qos.html#overrides","title":"Overrides","text":"

    rclcpp offers a consistent way to define QoS overrides as parameters. There is a (somewhat dated/inaccurate) design doc on this feature. In your code:

    // Allow overriding QoS settings (history, depth, reliability)\nrclcpp::PublisherOptions pub_options;\npub_options.qos_overriding_options = rclcpp::QosOverridingOptions::with_default_policies();\nnode->create_publisher<std_msgs::msg::String>(\"topic\", rclcpp::QoS(10), pub_options);\n

    Then, in your parameter file, you can define:

    node_name:\n  ros__parameters:\n    qos_overrides:\n      fully_resolved_topic_name:\n        publisher:\n          reliability: reliable\n          history_depth: 100\n          history: keep_last\n

    The same workflow works for subscribers, you just use rclcpp::SubscriptionOptions instance and change publisher to subscription in the YAML file.

    "},{"location":"other/pages/qos.html#magic-numbers","title":"Magic Numbers","text":"

    If you perform ros2 topic info -v /some/topic and you examine the QoS settings, you may note that several fields are set to the magic number 2147483651294967295 (or 2,147,483,651,294,967,295). e.g.

    QoS profile:\n    Reliability: RMW_QOS_POLICY_RELIABILITY_RELIABLE\n    Durability: RMW_QOS_POLICY_DURABILITY_VOLATILE\n    Lifespan: 2147483651294967295 nanoseconds\n    Deadline: 2147483651294967295 nanoseconds\n    Liveliness: RMW_QOS_POLICY_LIVELINESS_AUTOMATIC\n    Liveliness lease duration: 2147483651294967295 nanoseconds\n

    What is the significance of this number, which is approximately equal to 68 years? It's not \\(2^n - 1\\) as you might expect for a magic number. However, durations are defined as

    [builtin_interfaces/Duration]\n# Seconds component, range is valid over any possible int32 value.\nint32 sec\n\n# Nanoseconds component in the range of [0, 1e9).\nuint32 nanosec\n

    The max value for an int32 is \\(2^{31} - 1\\). The max value for an uint32 is \\(2^{32} - 1\\). (Note: According to the definition, any value for nanosec over 1e9 is invalid.)

    So the magic number 2147483651294967295 is the number of nanoseconds equivalent to \\(2^{31} -1\\) seconds (\\(2147483647\\) seconds) added to \\(2^{32} - 1\\) nanoseconds (\\(4.294967295\\) seconds).

    "},{"location":"other/pages/rosdep.html","title":"Rosdep","text":"

    rosdep install --from-paths src --ignore-src --rosdistro=humble -y

    "},{"location":"other/pages/rosdoc2.html","title":"rosdoc2","text":"

    Documentation for ROS 2 packages can be built with rosdoc2.

    When a doc job is configured in the rosdistro, the resulting documentation is uploaded to docs.ros.org. The design document for per package documentation lays out the directory structure.

    "},{"location":"other/pages/rosdoc2.html#linking-to-other-packages","title":"Linking to Other Packages","text":"

    This is a dirty hack, but appears to be the only way to have the table of contents link to another package's documentation without hard-coding the distro. The trailing http:// changes how Sphinx processes the link:

    .. toctree::\n   :maxdepth: 2\n\n   other_package <../other_package/index.html#http://>\n

    An example of this can be seen in the documentation for image_pipeline, where we want to link to the documentation for each of the packages within the metapackage.

    "},{"location":"other/pages/rosdoc2.html#documenting-metapackages","title":"Documenting Metapackages","text":"

    A metapackage is one that contains no code and exists basically to bundle up a set of dependencies. For instance image_pipeline is a repository containing several packages for image processing - and the image_pipeline metapackage depends on every package in the repository to make it easier to install with apt install ros-<distro>-image-pipeline rather than specifying each package individually. This does lead to an issue with rosdoc2, which will fail if there is no code to document. If you want to add tutorials or documentation to a metapackage, you need to use a rosdoc2.yaml file to properly build your documentation (which we assume is located in the doc folder:

    type: 'rosdoc2 config'\nversion: 1\n\n---\n\nsettings:\n    # Not generating any documentation of code\n    generate_package_index: false\n    always_run_doxygen: false\n    enable_breathe: false\n    enable_exhale: false\n    always_run_sphinx_apidoc: false\n    # Lie to rosdoc2, claim to be a python package\n    override_build_type: 'ament_python'\n    # Lie to rosdoc2 again, say your source is in doc folder\n    python_source: 'doc'\n\nbuilders:\n   # Configure Sphinx with the location of the docs:\n    - sphinx: {\n        name: 'image_pipeline',\n        sphinx_sourcedir: 'doc',\n        output_dir: ''\n    }\n

    Don't forget to add your yaml file to the package.xml:

    <export>\n  <rosdoc2>rosdoc2.yaml</rosdoc2>\n</export>\n
    "}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index cc7fa86..ab26ff3 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,142 +2,142 @@ https://github.com/mikeferguson/ros2_cookbook/index.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/literate-nav.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/index.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclcpp/index.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclcpp/initialization.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclcpp/logging.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclcpp/nodes.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclcpp/parameters.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclcpp/pcl.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclcpp/tf2.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclcpp/time.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclcpp/workarounds.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclpy/index.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclpy/nodes.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclpy/parameters.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclpy/tf2.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/client_libraries/rclpy/time.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/index.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/bag.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/cmake.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/colcon.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/command_line.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/launch.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/networking.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/packages.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/qos.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/rosdep.html - 2024-02-15 + 2024-02-18 daily https://github.com/mikeferguson/ros2_cookbook/other/pages/rosdoc2.html - 2024-02-15 + 2024-02-18 daily \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index d586ce1..6897a8b 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ