ros2
Build and debug robot software whose runtime interfaces use ROS 2.
Install
npx skills add https://github.com/robium-ai/robium/tree/main/skills/ros2
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install robium-ai-robium@llmmart
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
rosdepafter 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_pythonexample 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.
Reviews (0)
No reviews yet.
No comments yet.