Claude Cursor Skill

ros2

Build and debug robot software whose runtime interfaces use ROS 2.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download robium-ai-robium-skills_ros2-498ea4e.zip · 22 KB
Part of robium-ai/robium — 44 skills

Install

skills CLI npx skills add https://github.com/robium-ai/robium/tree/main/skills/ros2
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install robium-ai-robium@llmmart
Git git clone https://github.com/robium-ai/robium.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole robium-ai/robium collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

ROS 2

Treat ROS 2 as a live distributed graph. Inspect the graph that is actually running before changing source code.

Establish the environment

  • Identify the repository's ROS distro, RMW implementation, workspace, and ROS_DOMAIN_ID; do not substitute catalog-wide defaults.
  • Source the underlay before the workspace overlay. A fresh shell is a fresh environment.
  • Run rosdep after a fresh clone or dependency change, before interpreting a build failure as a source bug.
  • Prefer an overlay package or launch-time wiring over editing an installed third-party package.

Inspect the boundary that failed

  • Build: separate missing dependencies, package metadata, and stale build artifacts from compiler or application failures.
  • Launch: resolve the final arguments, parameters, namespaces, and remaps; do not reason only from a launch file's defaults.
  • Graph: confirm that the expected nodes and endpoints exist and that names include the namespace you expect.
  • Messages: compare publisher and subscriber types and QoS. Discovery without data is often an interface mismatch, especially for sensor topics.
  • Time and transforms: confirm a single time source and a connected, current TF path before debugging downstream behavior.
  • Lifecycle: distinguish an inactive node from a missing or crashed node.

Use topics for streams, services for bounded requests, and actions for long-running goals with feedback and cancellation. Choose based on semantics, not convenience.

Go deeper only when needed

  • For workspace layout, package anatomy, overlays, and rosdep, read workspace and packages.
  • For launch composition, parameters, includes, remaps, and relays, read launch patterns.
  • For topics, services, actions, QoS, and TF fundamentals, read interfaces and QoS.
  • When callbacks block or unexpectedly serialize, read concurrency before adding executor threads.
  • For a failing command or silent graph, read failures and then the fuller debugging guide if necessary.
  • For Create 3 and TurtleBot 4 evidence, read TurtleBot 4.
  • Use the bundled ament_python example only when a complete minimal package is useful; re-check its APIs against the project's distro.

Cross into navigation, gazebo, or a visualization skill only after ROS 2 evidence shows that their boundary is receiving valid data. Cross into integration when the failure is between processes, containers, or non-ROS systems; use environments when the runtime itself is not reproducible.

Done

  • The intended nodes and endpoints exist under the expected names.
  • QoS, time, and TF contracts are compatible at every changed boundary.
  • A message, service response, or action result proves the real interface, not merely that processes started.
  • Version-sensitive syntax was checked against the project's ROS distro and current ROS 2 documentation.
Files (robium)
  • examples
    • package-ament-python
      • launch
        • talker.launch.py 1.5 KB
          # status: unverified
          # source: https://github.com/ros2/ros2_documentation/blob/rolling/source/How-To-Guides/Launch-file-different-formats.rst
          #         (Python launch file: DeclareLaunchArgument, LaunchConfiguration,
          #         Node action with parameters/remappings). Fetched via ctx7 this
          #         session; docs.ros.org was blocked by an anti-bot page for direct
          #         fetch; re-verify there when reachable.
          #
          # Launches this package's `talker` executable (the console_scripts entry
          # point in ../setup.py, which resolves to ros2_example_pkg.talker_node:main).
          # Demonstrates a launch argument feeding a declared node parameter, and a
          # topic remap; see ../../../references/launch-patterns.md. Run with:
          #   ros2 launch ros2_example_pkg talker.launch.py message:="hi from launch"
          
          from launch import LaunchDescription
          from launch.actions import DeclareLaunchArgument
          from launch.substitutions import LaunchConfiguration
          from launch_ros.actions import Node
          
          
          def generate_launch_description():
              message_arg = DeclareLaunchArgument(
                  'message',
                  default_value='hello from ros2_example_pkg',
                  description='Text published on the chatter topic.',
              )
          
              talker_node = Node(
                  package='ros2_example_pkg',
                  executable='talker',
                  name='example_talker',
                  output='screen',
                  parameters=[{'message': LaunchConfiguration('message')}],
                  remappings=[('chatter', 'example_chatter')],
              )
          
              return LaunchDescription([message_arg, talker_node])
          
      • resource
        • ros2_example_pkg 0 B · in bundle
      • ros2_example_pkg
        • talker_node.py 2.7 KB
          # status: unverified
          # source: https://github.com/ros2/ros2_documentation/blob/rolling/source/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Publisher-And-Subscriber.rst
          #         (rclpy Node/Publisher/Timer pattern, including the current
          #         `with rclpy.init(): ...` + ExternalShutdownException idiom; this
          #         reflects the `rolling` branch of ros2_documentation via ctx7, not a
          #         distro-pinned page; re-confirm the idiom is current for whatever
          #         distro is actually installed before relying on it),
          #         https://github.com/ros2/examples/blob/rolling/rclpy/executors/examples_rclpy_executors/callback_group.py
          #         (independent official-code use of the same shutdown idiom), and
          #         https://github.com/ros2/ros2_documentation/blob/rolling/source/Tutorials/Beginner-Client-Libraries/Using-Parameters-In-A-Class-Python.rst
          #         (declare_parameter / get_parameter pattern).
          #
          # Minimal ament_python talker node for the ros2 skill's example package.
          # Publishes a parameterized std_msgs/String on 'chatter' at 1 Hz. The class
          # name and entry-point function name below are wired up in setup.py's
          # console_scripts ('talker = ros2_example_pkg.talker_node:main') and
          # referenced by package/executable in ../launch/talker.launch.py; keep all
          # three in sync if renaming. Re-verify against current ros2_documentation
          # before using in a real project.
          
          import rclpy
          from rclpy.executors import ExternalShutdownException
          from rclpy.node import Node
          from rclpy.qos import HistoryPolicy, QoSProfile, ReliabilityPolicy
          from std_msgs.msg import String
          
          
          class ExampleTalker(Node):
              """Minimal publisher node demonstrating a declared parameter and
              explicit QoS; see ../../../references/interfaces-and-qos.md.
              """
          
              def __init__(self):
                  super().__init__('example_talker')
          
                  self.declare_parameter('message', 'hello from ros2_example_pkg')
          
                  qos = QoSProfile(
                      reliability=ReliabilityPolicy.RELIABLE,
                      history=HistoryPolicy.KEEP_LAST,
                      depth=10,
                  )
                  self.publisher_ = self.create_publisher(String, 'chatter', qos)
                  self.timer_ = self.create_timer(1.0, self.on_timer)
                  self.count_ = 0
          
              def on_timer(self):
                  text = self.get_parameter('message').get_parameter_value().string_value
                  msg = String()
                  msg.data = f'{text} #{self.count_}'
                  self.publisher_.publish(msg)
                  self.get_logger().info(f'Publishing: "{msg.data}"')
                  self.count_ += 1
          
          
          def main(args=None):
              try:
                  with rclpy.init(args=args):
                      node = ExampleTalker()
                      rclpy.spin(node)
              except (KeyboardInterrupt, ExternalShutdownException):
                  pass
          
          
          if __name__ == '__main__':
              main()
          
        • __init__.py 0 B
      • package.xml 1.7 KB · in bundle
      • setup.cfg 679 B
        # status: unverified
        # source: https://github.com/ros2/ros2_documentation/blob/rolling/source/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.rst
        #         (standard ros2 pkg create ament_python scaffold). Fetched via ctx7
        #         on 2026-07-10; docs.ros.org was blocked by an anti-bot page for
        #         direct fetch; re-verify there when reachable.
        #
        # Points console-script installation at this package's lib dir so
        # `ros2 run ros2_example_pkg talker` finds the entry point defined in
        # setup.py. The path segment must match package_name in setup.py.
        [develop]
        script_dir=$base/lib/ros2_example_pkg
        [install]
        install_scripts=$base/lib/ros2_example_pkg
        
      • setup.py 1.7 KB
        # status: unverified
        # source: https://github.com/ros2/ros2_documentation/blob/rolling/source/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.rst
        #         (ament_python setup.py shape) and
        #         https://github.com/ros2/ros2_documentation/blob/rolling/source/Tutorials/Intermediate/Launch/Using-Substitutions.rst
        #         (data_files pattern for installing a package's launch directory).
        #         Fetched via ctx7 on 2026-07-10; docs.ros.org was blocked by an
        #         anti-bot page for direct fetch; re-verify there when reachable.
        #
        # package_name must match package.xml's <name>, the resource/ marker file
        # name, and the 'package=' argument in launch/talker.launch.py. The
        # console_scripts entry point name ('talker') must match the 'executable='
        # argument in that same launch file. Rename the package, entry point, and launch
        # references together.
        
        import os
        from glob import glob
        
        from setuptools import find_packages, setup
        
        package_name = 'ros2_example_pkg'
        
        setup(
            name=package_name,
            version='0.0.1',
            packages=find_packages(exclude=['test']),
            data_files=[
                ('share/ament_index/resource_index/packages',
                    ['resource/' + package_name]),
                ('share/' + package_name, ['package.xml']),
                (os.path.join('share', package_name, 'launch'),
                    glob('launch/*.launch.py')),
            ],
            install_requires=['setuptools'],
            zip_safe=True,
            maintainer='robium',
            maintainer_email='robium@example.invalid',
            description='Minimal ament_python example package for the robium ros2 skill.',
            license='Apache-2.0',
            tests_require=['pytest'],
            entry_points={
                'console_scripts': [
                    'talker = ros2_example_pkg.talker_node:main',
                ],
            },
        )
        
  • references
    • debugging.md 5.6 KB
      # Debugging
      
      The ROS 2 CLI introspection toolkit, and how to tell apart the handful of
      failure signatures that account for most "it doesn't work" ROS 2 reports.
      
      Sources: [ros2/ros2_documentation](https://github.com/ros2/ros2_documentation)
      (About-Command-Line-Tools.rst, About-Discovery.rst,
      Configuring-ROS2-Environment.rst, Installation-Troubleshooting.rst, the TF2
      tutorial set), fetched via ctx7 on 2026-07-10 (docs.ros.org was blocked by an
      anti-bot page for direct fetch; re-verify there when reachable).
      
      ## First: is the environment actually sourced and configured?
      
      Before touching application-level debugging, rule out environment problems;
      they produce error messages that look unrelated to the real cause:
      
      ```bash
      echo $ROS_DISTRO           # empty or wrong distro → underlay not sourced
      ros2 doctor --report       # broad environment/health report in one command
      ```
      
      - **Empty/wrong `$ROS_DISTRO`, "command not found: ros2"**: the underlay
        wasn't sourced, or was sourced in the wrong shell. `source
        /opt/ros/lyrical/setup.bash` (underlay) then `source install/setup.bash`
        (overlay), in that order, every new shell; see
        `references/workspace-and-packages.md`.
      - **Package builds but `ros2 run`/`ros2 pkg list` doesn't see it**: the
        overlay (`install/setup.bash`) wasn't re-sourced after the last build, or
        the package name in `package.xml` doesn't match what you're typing.
      - **Two unrelated ROS 2 systems interfering with each other** (nodes you
        didn't start showing up in `ros2 node list`, or traffic from another
        project): a `ROS_DOMAIN_ID` collision: every ROS 2 system defaults to
        domain `0`. `export ROS_DOMAIN_ID=<project-unique-int>` in every shell/
        container for this project.
      
      ## Node and topic introspection
      
      ```bash
      ros2 node list                 # every running node
      ros2 node info /my_node        # a node's subs/pubs/services/actions
      
      ros2 topic list                # every active topic
      ros2 topic info -v /my_topic   # publisher/subscriber count AND their QoS
      ros2 topic echo /my_topic      # print messages as they arrive
      ros2 topic hz /my_topic        # measured publish rate
      ros2 topic pub /my_topic <type> "<yaml>"   # publish one-off test messages
      ```
      
      `ros2 topic info -v` is the single most useful command for the QoS-mismatch
      failure mode: it prints each publisher's and subscriber's actual QoS profile
      side by side, so an incompatible pairing (e.g. one `RELIABLE`, one
      `BEST_EFFORT`) is visible directly instead of inferred from silence.
      
      ## Diagnosing "node not receiving messages"
      
      Work through these in order: this is the concrete version of the "QoS
      compatibility is the first suspect" directive:
      
      1. **Do both nodes appear in `ros2 node list`?** If not, it's a discovery
         problem (domain ID mismatch, network/multicast blocked; see the
         `integration` skill's DDS-across-containers gotchas if this is a
         multi-container setup), not a QoS problem.
      2. **Does the topic appear in `ros2 topic list` with both a publisher and a
         subscriber count ≥ 1 in `ros2 topic info -v`?** If either count is 0, the
         node isn't actually creating the publisher/subscription you think it is
         (check the topic name for a typo or an unintended remap); not a QoS
         problem yet.
      3. **Are the QoS profiles shown by `ros2 topic info -v` compatible?**
         Reliability: a subscriber set to `reliable` will not connect to a
         publisher set to `best_effort`. Durability: this only affects *late
         joiners*: a `volatile` publisher won't have delivered anything published
         before a late-joining subscriber connected. Mismatches here produce zero
         messages with no error on either side; this is usually the actual cause
         once steps 1–2 pass. See `references/interfaces-and-qos.md`.
      4. **Only after 1–3 check out**, look at application logic (callback errors,
         message filtering, executor not spinning).
      
      ## Build and dependency failures
      
      - **`colcon build` fails with a missing header or `ModuleNotFoundError`**:
        almost always an unresolved dependency; run `rosdep install --from-paths
        src -y --ignore-src` before assuming it's a source bug. See
        `references/workspace-and-packages.md`.
      - **A package isn't found by colcon at all**: confirm it's actually under
        `src/` and has a valid `package.xml`; a malformed or missing `package.xml`
        makes colcon skip the directory silently rather than erroring loudly.
      - **Rebuilding a Python-only change has no effect**: confirm the workspace
        was built with `--symlink-install`; without it, Python file edits require
        a rebuild to take effect, not just a re-source.
      
      ## TF2-specific debugging
      
      ```bash
      ros2 run tf2_ros tf2_echo <source_frame> <target_frame>   # live transform values
      ros2 run tf2_tools view_frames                             # renders the current TF tree to a PDF
      ```
      
      `tf2_echo` failing with "could not find a connection" almost always means
      either the broadcaster node isn't running, or the frame names don't match
      exactly (case-sensitive, no leading slash in modern TF2); `view_frames`
      shows the whole tree at once, which is faster than guessing frame names one
      `tf2_echo` at a time when the tree has more than two or three frames.
      
      ## rosdep troubleshooting
      
      - **`rosdep: command not found` / "no such key"**: `sudo rosdep init &&
        rosdep update` hasn't been run on this machine, or the rosdep index is
        stale relative to a newly-released package; `rosdep update` again.
      - **A specific key won't resolve**: check it's spelled exactly as it appears
        upstream (rosdep keys are case-sensitive and package-specific, not always
        the same as the apt package name) and that the OS/distro combination is
        actually supported by that package's rosdep rule.
      
    • interfaces-and-qos.md 6.9 KB
      # Interfaces and QoS
      
      A brief map of ROS 2's three communication primitives, then a deep dive on
      Quality of Service, the policy set that determines whether a publisher and
      subscriber (or client and server) can talk at all, and a short TF2 primer,
      since TF2 is itself built on top of these primitives.
      
      Sources: [ros2/ros2_documentation](https://github.com/ros2/ros2_documentation)
      (About-Quality-of-Service-Settings.rst, About-Discovery.rst,
      Overriding-QoS-Policies-For-Recording-And-Playback.rst, and the TF2 tutorial
      set), fetched via ctx7 on 2026-07-10 (docs.ros.org was blocked by an anti-bot
      page for direct fetch; re-verify there when reachable).
      
      ## Topics, services, actions: when to use which
      
      - **Topics** (publish/subscribe): continuous or event streams with no
        request/response coupling: sensor data, state, commands. Many-to-many.
      - **Services** (request/response): a single synchronous-feeling call that
        should complete quickly: "give me this value now". One request per call;
        the client blocks (or awaits) until the response arrives.
        `create_publisher`/`create_subscription` become `create_client`/
        `create_service` for services.
      - **Actions** (goal/feedback/result): a long-running, preemptible task with
        progress feedback: "navigate to this pose", "pick up this object". Built
        on top of topics and services internally; use when a service would either
        block too long or needs cancel/progress semantics a service can't express.
      
      Custom message/service/action definitions (`.msg`/`.srv`/`.action`) live in
      their own `ament_cmake`-typed interface package even in an otherwise
      `ament_python` project; Python packages cannot generate interfaces
      themselves. This skill doesn't cover writing custom interfaces in depth;
      start from an existing type (`std_msgs`, `geometry_msgs`, `sensor_msgs`) when
      one fits before reaching for a custom one.
      
      ## Quality of Service (QoS): why it exists
      
      ROS 2's transport is built on DDS, and QoS policies are largely inherited
      from it. Where ROS 1 had one implicit behavior per primitive, ROS 2 exposes
      the underlying knobs, which is more powerful but means two nodes can fail to
      connect even though discovery worked and no error was logged on either side.
      
      ## The policy set
      
      | Policy | Values | What it controls |
      |---|---|---|
      | **History** | `keep_last` (N), `keep_all` | How many samples the middleware buffers. `keep_last` + depth is the ROS 1 "queue size" equivalent. |
      | **Depth** | integer | The N in `keep_last`: how many samples are buffered before older ones are dropped. |
      | **Reliability** | `reliable`, `best_effort` | `reliable` guarantees delivery (retries under the hood); `best_effort` favors throughput/latency and can silently drop samples, the ROS 1 TCPROS vs UDPROS split. |
      | **Durability** | `volatile`, `transient_local` | `volatile` (default): late-joining subscribers get nothing published before they connected. `transient_local`: the publisher retains and delivers the last `depth` samples to late joiners, similar to ROS 1's "latched" publishers. |
      | **Deadline** | duration | Expected max time between samples; violations are reported, not enforced. No ROS 1 equivalent. |
      | **Lifespan** | duration | How long a sample stays valid after publishing; stale samples are dropped instead of delivered. No ROS 1 equivalent. |
      | **Liveliness** | `automatic`, `manual_by_topic` | How a node signals "I'm still alive" for this endpoint, paired with a lease duration. No ROS 1 equivalent. |
      
      Not every RMW implementation supports every policy; e.g. `rmw_zenoh_cpp`
      does not implement deadline/lifespan. Check the RMW implementation in use
      before relying on a less-common policy.
      
      ## Compatibility rule: the source of silent failures
      
      **Every QoS policy that affects compatibility must be compatible on both
      sides, or no connection is made at all, silently, with no error on either
      end.** Discovering each other (nodes see each other in `ros2 node list`) is
      not the same as being able to exchange messages. The classic break: a
      publisher set to `best_effort` and a subscriber set to `reliable` (a
      subscriber can't ask for a stronger guarantee than the publisher offers);
      they discover each other, log nothing, and zero messages cross.
      
      This is why "QoS compatibility is the first suspect" for a node that runs,
      discovers its peer, and receives nothing: check `ros2 topic info -v
      <topic>` (see `references/debugging.md`) before debugging application logic.
      
      ## Preset profiles
      
      Rather than hand-tuning every policy, ROS 2 ships presets for common shapes:
      
      - **Default profile**: `reliable`, `volatile`, `keep_last` depth 10; the
        right starting point for most topics.
      - **Sensor data profile** (`rclpy.qos.qos_profile_sensor_data` /
        `rmw_qos_profile_sensor_data`): `best_effort`, small `keep_last` depth;
        prioritizes the newest sample over guaranteed delivery, appropriate for
        high-rate sensor streams where a dropped frame doesn't matter but latency
        does.
      - **Services default profile**: `reliable`, `volatile`; a restarted service
        server shouldn't replay stale requests, so no `transient_local`.
      - **Parameters profile**: same shape as services but with a much larger
        queue depth, since parameter-related traffic can burst and shouldn't be
        dropped as readily.
      
      Explicit construction in Python:
      
      ```python
      from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy
      
      qos = QoSProfile(
          reliability=ReliabilityPolicy.RELIABLE,
          history=HistoryPolicy.KEEP_LAST,
          depth=10,
      )
      publisher = node.create_publisher(String, 'chatter', qos)
      ```
      
      ## Overriding QoS without touching code
      
      `ros2 bag record`/`play` and some tools accept a YAML QoS override file per
      topic (history, depth, reliability, durability, deadline, lifespan,
      liveliness); useful for adapting playback QoS to a subscriber's
      requirements without re-launching the original publisher. See
      `references/debugging.md` for the introspection commands that reveal what a
      running topic's actual QoS is, which is the input you need before writing an
      override.
      
      ## TF2 basics
      
      TF2 is ROS 2's coordinate-frame transform library, itself implemented as
      topics (`/tf`, `/tf_static`) under the hood; the same QoS/discovery rules
      apply, though TF2's own defaults are tuned for its use case (`/tf_static`
      uses `transient_local` so late joiners get static transforms immediately).
      
      Broadcasting a static transform from the CLI (no code needed):
      
      ```bash
      ros2 run tf2_ros static_transform_publisher \
        --x 0 --y 0 --z 1 --yaw 0 --pitch 0 --roll 0 \
        --frame-id world --child-frame-id sensor_link
      ```
      
      Broadcasting programmatically (`tf2_ros.TransformBroadcaster`) or looking up
      a transform (`tf2_ros.Buffer` + `TransformListener`, then
      `buffer.lookup_transform(target_frame, source_frame, rclpy.time.Time())`,
      wrapped in a try/except for `tf2_ros.TransformException`) follow the same
      publish/subscribe shape as any other topic; see
      `references/debugging.md` for `tf2_echo`/`view_frames` when a transform
      lookup is failing and it's unclear whether the frame exists at all.
      
    • launch-patterns.md 7 KB
      # Launch patterns
      
      How to write, install, and run ROS 2 launch files, and how to use
      remapping/relay to bridge two third-party packages without editing either.
      
      Sources: [ros2/ros2_documentation](https://github.com/ros2/ros2_documentation)
      (Launch-file-different-formats.rst, Launch-system.rst,
      Using-Substitutions.rst), [ros2/launch](https://github.com/ros2/launch)
      (architecture.md), and [ros-tooling/topic_tools
      README](https://github.com/ros-tooling/topic_tools/blob/main/README.md),
      all fetched via ctx7/direct GitHub raw fetch on 2026-07-10 (docs.ros.org was
      blocked by an anti-bot page for direct fetch; re-verify there when
      reachable).
      
      ## Python launch files are the default
      
      ROS 2 supports Python, XML, and YAML launch files with equivalent core
      functionality. Python is the default choice for anything beyond the
      simplest static node list: it's the only format with real conditionals,
      loops, and composability without a substitution-language workaround. Reserve
      XML/YAML for trivial cases or when matching an existing package's convention.
      
      ## Minimal launch file
      
      ```python
      from launch import LaunchDescription
      from launch_ros.actions import Node
      
      
      def generate_launch_description():
          return LaunchDescription([
              Node(
                  package='my_package',
                  executable='my_node',
                  name='my_node',
                  output='screen',
              ),
          ])
      ```
      
      `generate_launch_description()` is the required entry point: `ros2 launch`
      imports the file and calls this function. `output='screen'` sends the node's
      logging to the launching terminal instead of only to the log files.
      
      ## Launch arguments and parameters
      
      ```python
      from launch import LaunchDescription
      from launch.actions import DeclareLaunchArgument
      from launch.substitutions import LaunchConfiguration
      from launch_ros.actions import Node
      
      
      def generate_launch_description():
          return LaunchDescription([
              DeclareLaunchArgument(
                  'message',
                  default_value='hello',
                  description='Text this node publishes.',
              ),
              Node(
                  package='my_package',
                  executable='my_node',
                  name='my_node',
                  output='screen',
                  parameters=[{'message': LaunchConfiguration('message')}],
              ),
          ])
      ```
      
      Run with `ros2 launch my_package my_launch.py message:="custom text"`.
      `parameters=` also accepts a path to a YAML params file (or a
      `launch_ros.parameter_descriptions.ParameterFile` when the YAML itself needs
      launch substitutions resolved, e.g. `$(env HOME)`-style values); prefer a
      YAML file over a long inline dict once a node has more than a handful of
      parameters, so the values are reviewable outside the launch file.
      
      ## Remapping
      
      ```python
      Node(
          package='my_package',
          executable='my_node',
          remappings=[('input_topic', 'renamed_topic')],
      ),
      ```
      
      Equivalent XML: `<remap from="input_topic" to="renamed_topic"/>` nested
      inside a `<node>` tag. Remapping is the first tool to reach for when two
      packages almost line up but use different topic/service names; it requires
      no source changes in either package.
      
      ## Including other launch files
      
      ```python
      from launch.actions import IncludeLaunchDescription
      from launch.launch_description_sources import PythonLaunchDescriptionSource
      from launch.substitutions import PathJoinSubstitution
      from launch_ros.substitutions import FindPackageShare
      
      IncludeLaunchDescription(
          PythonLaunchDescriptionSource([
              PathJoinSubstitution([
                  FindPackageShare('other_package'), 'launch', 'other.launch.py',
              ])
          ]),
          launch_arguments={'some_arg': 'value'}.items(),
      ),
      ```
      
      `FindPackageShare` resolves to the installed `share/<package>` directory;
      use it instead of a hardcoded path so the include works regardless of where
      the workspace was built.
      
      ## Installing launch files (ament_python)
      
      Launch files live in a `launch/` directory at the package root and must be
      registered in `setup.py`'s `data_files` for `ros2 launch` to find them after
      install:
      
      ```python
      import os
      from glob import glob
      
      setup(
          # ...
          data_files=[
              ('share/ament_index/resource_index/packages', ['resource/' + package_name]),
              ('share/' + package_name, ['package.xml']),
              (os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
          ],
      )
      ```
      
      Also add `<exec_depend>launch</exec_depend>` and
      `<exec_depend>launch_ros</exec_depend>` to `package.xml`; a package that
      ships launch files but doesn't declare these depends will build fine and
      then fail at `ros2 launch` time on a machine that happens not to have them
      installed already. See `examples/package-ament-python/` for the full,
      working shape.
      
      ## Bridging two third-party packages: remap + relay
      
      Two common shapes for gluing packages you don't want to modify:
      
      **Rename at launch time (no extra running node).** Put the remap directly on
      whichever `Node` action you already control, as shown above. This is the
      default; reach for it first.
      
      **Republish as a standalone node (when neither package's launch file is a
      good place to put the remap, or the "bridge" needs to run independently).**
      `topic_tools`' `relay` node subscribes to one topic and republishes to
      another:
      
      ```bash
      ros2 run topic_tools relay <intopic> [outtopic]
      # e.g.: ros2 run topic_tools relay base_scan my_base_scan
      ```
      
      If the message *type* also needs to change (not just the topic name),
      `relay_field` republishes a field-level expression into a different message
      type instead of a straight pass-through:
      
      ```bash
      ros2 run topic_tools relay_field /chatter /header std_msgs/Header \
        "{stamp: {sec: 0, nanosec: 0}, frame_id: m.data}"
      ```
      
      Both are separate nodes from the `ros-tooling/topic_tools` package; add
      `topic_tools` as a dependency and launch the relay as its own `Node` action
      (or `ExecuteProcess`) alongside the two packages being bridged, rather than
      forking either package to add compatibility code. This pattern stays inside
      one ROS 2 system; if the "third-party package" is actually on the other side
      of a non-ROS system boundary, that's the `integration` skill's comms-choice
      table, not this one.
      
      ## Compose C++ nodes only after the standalone graph works
      
      Composition is a process-boundary choice, not automatic zero-copy. A composable
      C++ node accepts `rclcpp::NodeOptions` (commonly by `const&`) and registers its
      class once with `RCLCPP_COMPONENTS_REGISTER_NODE`. Dynamic loading also needs
      the matching `rclcpp_components_register_node(s)` CMake registration; building
      a shared library and using the C++ macro alone does not put it in the ament
      resource index.
      
      Manual composition can instantiate several such node classes in one executable
      without a component manager. Those nodes do not appear in `ros2 component list`.
      Intra-process communication is a separate `NodeOptions` choice, and zero-copy
      has additional publisher/subscriber ownership requirements. Keep nodes
      standalone while debugging discovery, parameters, QoS, or lifecycle; compose
      after the interfaces are already proven. Re-check the project's distro against
      the current [ROS 2 composition guide](https://docs.ros.org/en/rolling/Tutorials/Intermediate/Composition.html).
      
    • workspace-and-packages.md 7.2 KB
      # Workspace and packages
      
      How a ROS 2 workspace is laid out, how colcon builds it, how `ament_python`
      and `ament_cmake` package anatomy differ, and the rosdep workflow that has to
      run before any of it builds cleanly.
      
      Sources: [ros2/ros2_documentation](https://github.com/ros2/ros2_documentation)
      (Colcon-Tutorial.rst, Creating-Your-First-ROS2-Package.rst, Rosdep.rst,
      Migrating-Python-Package-Example.rst), fetched via the `ctx7` CLI (not a
      direct docs.ros.org fetch; that domain was blocked by an anti-bot page this
      session; re-verify against [docs.ros.org](https://docs.ros.org/) directly
      when it's reachable).
      
      ## Workspace layout
      
      A ROS 2 workspace is a directory with a `src/` folder containing package
      source; colcon creates the rest on first build:
      
      ```
      ros2_ws/
      ├── src/            # package source: the only directory you hand-write into
      ├── build/          # colcon's intermediate build artifacts, per package
      ├── install/        # what gets sourced: installed packages, one setup.bash
      └── log/            # build/test logs
      ```
      
      Never edit inside `build/` or `install/` by hand; they're regenerated by
      `colcon build` and any manual edit is silently lost on the next build.
      `.gitignore` all three; only `src/` is source-controlled.
      
      ## Underlay / overlay sourcing
      
      A "underlay" is a previously-sourced ROS 2 environment your workspace builds
      on top of; your own workspace is the "overlay". Source the underlay first,
      then the overlay; each `setup.bash` only *extends* whatever environment was
      already active, it doesn't replace it:
      
      ```bash
      source /opt/ros/lyrical/setup.bash   # underlay: the system ROS 2 install
      source install/setup.bash            # overlay: this workspace's packages
      ```
      
      Sourcing the overlay alone, or in the wrong order, is the single most common
      cause of "package not found" / "command not found: ros2" reports; see
      `references/debugging.md`.
      
      ## Building with colcon
      
      ```bash
      cd ros2_ws
      colcon build --symlink-install
      ```
      
      - `--symlink-install` symlinks Python files and non-compiled resources into
        `install/` instead of copying them, so edits to a Python node take effect
        without a rebuild (still rebuild for `setup.py`/entry-point changes or any
        C++ package).
      - `--packages-select <pkg> [<pkg> ...]` builds only the named packages; use
        this while iterating on one package in a larger workspace instead of a full
        rebuild every time.
      - `--packages-up-to <pkg>` builds a package and everything it depends on,
        skipping unrelated packages.
      - Colcon recognizes packages via `package.xml` (REP 149, format 3). Supported
        build types are `ament_python`, `ament_cmake`, and plain `cmake`, declared
        in `package.xml`'s `<export><build_type>...</build_type></export>` for
        Python packages, inferred from `CMakeLists.txt` for CMake ones.
      
      ## Creating a package
      
      ```bash
      ros2 pkg create --build-type ament_python --license Apache-2.0 \
        --node-name my_node my_package
      ```
      
      This scaffolds `package.xml`, `setup.py`, `setup.cfg`, a `resource/`
      marker file, the Python package directory with `__init__.py` and a starter
      node, and a `test/` directory with lint tests. Swap `--build-type
      ament_cmake` for a C++ package (scaffolds `CMakeLists.txt` and `src/`/
      `include/` instead). Add `--dependencies rclpy std_msgs ...` to pre-populate
      `package.xml`'s dependency tags.
      
      ## ament_python anatomy
      
      ```
      my_package/
      ├── package.xml              # metadata + dependencies (REP 149 format 3)
      ├── setup.py                 # setuptools config: name, entry points, data_files
      ├── setup.cfg                # installs console scripts to the right lib dir
      ├── resource/my_package       # empty marker file: registers the package with
      │                             #   the ament resource index; name must equal
      │                             #   the package name
      ├── my_package/               # the actual Python package (importable module)
      │   ├── __init__.py
      │   └── my_node.py
      └── test/                     # ament_copyright / ament_flake8 / ament_pep257
      ```
      
      `package.xml` must declare the Python build type:
      
      ```xml
      <export>
        <build_type>ament_python</build_type>
      </export>
      ```
      
      `setup.py`'s `entry_points` is what makes `ros2 run <package> <executable>`
      resolve to a Python function:
      
      ```python
      entry_points={
          'console_scripts': [
              'my_node = my_package.my_node:main',
          ],
      },
      ```
      
      The left side (`my_node`) is the executable name used by `ros2 run`/launch
      files; the right side is `<module_path>:<function>`. These two, plus
      `package.xml`'s `<name>` and `setup.py`'s `package_name` variable, all have
      to agree, or `ros2 run`/`ros2 pkg list` won't find what you expect. See
      `examples/package-ament-python/` for a full, internally-consistent example.
      
      ## ament_cmake anatomy (for contrast)
      
      Same `package.xml` shape, but `<build_type>ament_cmake</build_type>`, and the
      build/install pipeline lives in `CMakeLists.txt` instead of `setup.py`:
      
      ```cmake
      find_package(ament_cmake REQUIRED)
      find_package(rclcpp REQUIRED)
      
      add_executable(my_node src/my_node.cpp)
      ament_target_dependencies(my_node rclcpp)
      
      install(TARGETS my_node DESTINATION lib/${PROJECT_NAME})
      ament_package()
      ```
      
      The workspace, colcon, and rosdep mechanics in this file are identical
      regardless of build type; only how an individual package is laid out and
      built differs.
      
      ## rosdep: always run before building
      
      `rosdep` resolves each package's `package.xml` dependency keys to actual
      system packages (apt, pip, etc.) for the current OS and ROS distro, and
      installs them. Run it before every build that touches a workspace with new or
      changed dependencies, not just once at initial setup:
      
      ```bash
      sudo rosdep init      # one-time, per machine
      rosdep update          # refresh the dependency index
      rosdep install --from-paths src -y --ignore-src
      ```
      
      - `--from-paths src` scans every `package.xml` under `src/` for dependency
        keys.
      - `--ignore-src` skips keys that are themselves packages already present in
        the workspace (so it doesn't try to apt-install something you're building
        from source).
      - `-y` auto-confirms installs.
      
      Skipping this step is the most common cause of a colcon build failing with a
      missing-header or `ModuleNotFoundError` that looks like a source bug but is
      actually an unresolved dependency.
      
      ## Adding a dependency to an existing package
      
      1. Add the dependency tag to `package.xml`: `<exec_depend>some_pkg</exec_depend>`
         for a runtime-only Python/message dependency, `<build_depend>some_pkg</build_depend>`
         for a C++ build-time dependency (use `<depend>` if it's both).
      2. Re-run `rosdep install --from-paths src -y --ignore-src`; adding the tag
         alone does not install the underlying package.
      3. Rebuild: `colcon build --packages-select <your_package>`.
      
      ## Workspace overlays over third-party source edits
      
      When a third-party package's behavior needs to change, prefer building your
      change as an overlay (a new package, or a `COLCON_IGNORE`d fork rebuilt on
      top of the original) over hand-editing files inside a vendored or installed
      copy. In-place edits get silently clobbered by the next `rosdep update` /
      reinstall / container rebuild, and they hide the actual diff from anyone
      reading the workspace later. If you must fork, keep the fork as its own
      package with a clear name and a comment pointing at the upstream commit it
      diverged from, so the deviation is visible and re-mergeable.
      
  • CONCURRENCY.md 1.2 KB
    # ROS 2 callback concurrency
    
    An executor supplies threads; callback groups decide which callbacks may use
    them concurrently.
    
    - A multithreaded executor alone does not parallelize a node whose callbacks
      all use its implicit mutually exclusive callback group.
    - Put callbacks that may overlap in different mutually exclusive groups, or
      use a reentrant group only when the same callback may safely overlap itself.
    - Keep shared state, blocking calls, callback duration, and downstream thread
      safety visible when choosing a group. More threads do not make unsafe code
      safe.
    - Prove the intended overlap with timestamps or tracing. Do not infer it from
      the executor name.
    - Callback-group APIs and executor behavior vary across client libraries and
      ROS distributions. Check the project's installed API and current
      [ROS 2 executor documentation](https://docs.ros.org/en/rolling/Concepts/Intermediate/About-Executors.html)
      before copying exact syntax.
    
    Robium observed this failure mode in the Rolling `ros2/examples` repository on
    2026-08-02: callbacks left in the default group remained serialized under a
    `MultiThreadedExecutor`. Treat that as evidence for the model, not a frozen API
    example for another distribution.
    
  • FAILURES.md 2 KB
    # ROS 2 failures
    
    Use this router to identify the broken layer before applying a fix.
    
    - **`ros2` or a package is not found**
      - Check the underlay, then the overlay, were sourced in this shell.
      - Confirm the requested package exists in the selected distro and workspace.
      - A ROS setup script may read unset variables; source it before enabling
        shell `set -u`.
    
    - **The workspace does not build**
      - Run dependency resolution before changing code.
      - Identify the first package that fails; downstream `colcon` failures may be
        consequences.
      - Separate absent system dependencies from package metadata and compile
        errors.
    
    - **Nodes cannot see each other**
      - Compare `ROS_DOMAIN_ID`, RMW implementation, namespace, and network reach.
      - Domain ID `0` is shared by default. Unrelated stacks may silently cross-talk.
      - For containers or multiple hosts, move to `integration` once the local ROS
        graph is healthy; DDS discovery must be designed at that boundary.
    
    - **Endpoints exist but no messages arrive**
      - Compare message types and the offered/requested QoS policies on both ends.
      - Use best-effort when inspecting a best-effort publisher; a reliable CLI
        subscriber can otherwise appear healthy while receiving nothing.
      - Check that simulation time and `/clock` advance before changing callbacks.
    
    - **Transforms are unavailable or stale**
      - Find the first missing edge in the TF chain, not merely the final lookup.
      - Compare frame spelling, namespace, timestamp, and static versus dynamic
        ownership.
    
    - **A container ignores shutdown**
      - `ros2 launch` as PID 1 may not follow the expected SIGTERM path. Use an init
        shim or the shutdown signal the launch process handles, and verify an orderly
        exit rather than waiting for the container runtime's forced kill.
    
    Use [the detailed debugging guide](references/debugging.md) for concrete graph,
    topic, node, TF, and dependency probes. Confirm command flags with the current
    CLI help because distro surfaces change.
    
  • SKILL.md 3.1 KB
    ---
    name: ros2
    description: Build and debug robot software whose runtime interfaces use ROS 2.
    ---
    
    # ROS 2
    
    Treat ROS 2 as a live distributed graph. Inspect the graph that is actually
    running before changing source code.
    
    ## Establish the environment
    
    - Identify the repository's ROS distro, RMW implementation, workspace, and
      `ROS_DOMAIN_ID`; do not substitute catalog-wide defaults.
    - Source the underlay before the workspace overlay. A fresh shell is a fresh
      environment.
    - Run `rosdep` after a fresh clone or dependency change, before interpreting a
      build failure as a source bug.
    - Prefer an overlay package or launch-time wiring over editing an installed
      third-party package.
    
    ## Inspect the boundary that failed
    
    - **Build:** separate missing dependencies, package metadata, and stale build
      artifacts from compiler or application failures.
    - **Launch:** resolve the final arguments, parameters, namespaces, and remaps;
      do not reason only from a launch file's defaults.
    - **Graph:** confirm that the expected nodes and endpoints exist and that names
      include the namespace you expect.
    - **Messages:** compare publisher and subscriber types and QoS. Discovery
      without data is often an interface mismatch, especially for sensor topics.
    - **Time and transforms:** confirm a single time source and a connected,
      current TF path before debugging downstream behavior.
    - **Lifecycle:** distinguish an inactive node from a missing or crashed node.
    
    Use topics for streams, services for bounded requests, and actions for
    long-running goals with feedback and cancellation. Choose based on semantics,
    not convenience.
    
    ## Go deeper only when needed
    
    - For workspace layout, package anatomy, overlays, and `rosdep`, read
      [workspace and packages](references/workspace-and-packages.md).
    - For launch composition, parameters, includes, remaps, and relays, read
      [launch patterns](references/launch-patterns.md).
    - For topics, services, actions, QoS, and TF fundamentals, read
      [interfaces and QoS](references/interfaces-and-qos.md).
    - When callbacks block or unexpectedly serialize, read
      [concurrency](CONCURRENCY.md) before adding executor threads.
    - For a failing command or silent graph, read [failures](FAILURES.md) and then
      the fuller [debugging guide](references/debugging.md) if necessary.
    - For Create 3 and TurtleBot 4 evidence, read [TurtleBot 4](TURTLEBOT4.md).
    - Use the bundled `ament_python` example only when a complete minimal package
      is useful; re-check its APIs against the project's distro.
    
    Cross into `navigation`, `gazebo`, or a visualization skill only after ROS 2
    evidence shows that their boundary is receiving valid data. Cross into
    `integration` when the failure is between processes, containers, or non-ROS
    systems; use `environments` when the runtime itself is not reproducible.
    
    ## Done
    
    - The intended nodes and endpoints exist under the expected names.
    - QoS, time, and TF contracts are compatible at every changed boundary.
    - A message, service response, or action result proves the real interface, not
      merely that processes started.
    - Version-sensitive syntax was checked against the project's ROS distro and
      current [ROS 2 documentation](https://docs.ros.org/).
    
  • TURTLEBOT4.md 2.1 KB
    # TurtleBot 4 and Create 3 evidence
    
    This card records Robium observations from the `tb4-teleop` application. Treat
    them as signatures to compare, not as universal TurtleBot defaults.
    
    - **No ROS graph after boot:** on 2026-07-24, `turtlebot4.service` failed with
      `rcl node's rmw handle is invalid` while `wlan0` was down. The robot's
      CycloneDDS configuration bound to `wlan0`, so restoring Wi-Fi and restarting
      the service restored 24 topics. Establish network-interface health before
      rewriting DDS configuration.
    
    - **Lidar works but the base does not:** `/scan` is produced on the Pi and does
      not prove that the Create 3 application is connected. In the observed wedge,
      `/motion_control`, `/wheel_status`, and `/battery_state` disappeared while the
      base still answered at `192.168.186.2` over `usb0`.
    
    - **Recovering that base wedge:** the reproducible recovery was the Create 3
      application restart endpoint, with a JSON body:
    
      ```bash
      curl -X POST -H "Content-Type: application/json" -d '{}' \
        http://192.168.186.2/api/restart-app
      ```
    
      A bare POST hung in that setup. About 30 seconds after the JSON request,
      `/motion_control` returned and the `/cmd_vel` subscription count changed from
      0 to 1. Pi-side DDS edits, clock matching, base reboot, and a physical power
      cycle did not reproduce the recovery. Re-check the current Create 3 API before
      automating this endpoint.
    
    - **Inspecting base topics:** the observed Create 3 publishers were best-effort.
      CLI inspection needed `--qos-reliability best_effort`. For an alive check,
      fast topics such as `/imu` or `/wheel_status` were less ambiguous than the
      slowly published `/battery_state`.
    
    - **Reverse motion stops early:** this can be the base's safety behavior rather
      than teleop packet loss. The observed `motion_control` parameter supported
      `none`, `backup_only`, and `full`; `backup_only` retained cliff safety while
      removing the backup limit. Verify the current
      [Create 3 safety documentation](https://iroboteducation.github.io/create3_docs/api/safety/)
      before changing a physical robot's safety settings.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related