ROS Ecosystem
Orchestrate ROS 2, Nav2, MoveIt 2 and ros2_control by delegating to the real
ros2 CLI of your installed distro. Caasi never imports rclpy and never
speaks DDS itself — it finds your distro, builds ros2 command lines, and turns every
long-lived launch into a tracked run.
The GPU-accelerated half of the same graph — isaac-ros, perception,
slam, mapping, motion, nitros and
pipeline — lives on the
GPU-Accelerated Robotics page; the commands below are the
plain ROS 2 ones.
Distro discovery
Every command on this page resolves the distro the same way:
ROS_DISTROenvironment variable →/opt/ros/$ROS_DISTRO;- otherwise, scan
/opt/rosfor a subdirectory containingsetup.bash; - the
ros2binary:<distro>/bin/ros2first, thenros2onPATH.
You do not need to source setup.bash before using Caasi — the
distro is located directly. When no ros2 binary can be found, commands fail with:
shellcaasi ros list topics
Error: No ROS 2 installation found (no ros2 CLI). Install ROS 2 or set ROS_DISTRO — see 'caasi setup ros2'.
ros2 itself only works after setup.bash has been sourced, so every
foreground call Caasi makes obeys one rule: if the resolved root
(/opt/ros/<distro>) already appears in AMENT_PREFIX_PATH,
COLCON_PREFIX_PATH or PYTHONPATH, your shell counts as sourced and the
plain binary runs — no wrapper, no extra bash. Otherwise the call becomes
bash -c 'source "<root>/setup.bash" >/dev/null 2>&1 && exec "$@"'
-- <ros2> … for that child process only; your interactive shell is never modified,
and when no distro resolves there is nothing to source, so the failure above is what you get.
This covers ros status · doctor · list · topic · node · graph · service,
nav status · test, moveit status · plan · test,
control status · list and the rows the doctor commands below add of
their own. The one exception is the shared robotics section those doctors run: it
calls ros2 pkg prefix directly, with no wrapper, so run them from a sourced shell —
an unsourced one makes installed stacks look missing.
The tracked runs on this page (ros launch, nav launch,
moveit launch, ros topic --echo) are detached children and inherit
Caasi's own environment unchanged — there is no sourcing step for them, so start them from a
sourced shell or from caasi shell --ros. (scene capture and the
accelerated groups do capture a sourced environment for their
runs: bash -c 'source … && env', 5 s timeout, memoised per
setup.bash, and only when the shell is not already sourced.)
Caasi never sets ROS_DOMAIN_ID, ROS_LOCALHOST_ONLY or
RMW_IMPLEMENTATION — whatever you export reaches the child untouched. It only
reports them: RMW_IMPLEMENTATION in ros status and as the
DDS middleware row of every doctor below, and the sourcing decision
itself as the Environment sourced row.
caasi ros
caasi ros status
caasi ros status [--json]
The one-screen ROS overview: distro, root, binary, RMW implementation and how many topics are
currently live (a real ros2 topic list call — proves the graph works, not just that
files exist). Never fails: “not found” is reported as data.
shellcaasi ros status
ROS 2
Distro jazzy
Root /opt/ros/jazzy
Binary /opt/ros/jazzy/bin/ros2
RMW (default)
Topics 2
caasi ros status --json
{ "available": true, "distro": "jazzy", "root": "/opt/ros/jazzy",
"binary": "/opt/ros/jazzy/bin/ros2", "rmw": null, "topics": 2 }
caasi ros doctor
caasi ros doctor [--json]
Runs ros2 doctor (60 s timeout) and mirrors its output and exit code — the real
health check of your middleware, network and distro setup. JSON mode:
{"returncode": 0, "output": "…full report…"}.
caasi ros list
caasi ros list KIND [--json] # KIND: topics | nodes | services | actions
Thin, uniform wrapper over ros2 topic|node|service|action list:
shellcaasi ros list topics
Topics
/parameter_events
/rosout
/scan
/tf
caasi ros list nodes --json
["/talker", "/listener"]
caasi ros list topicz
Error: Unknown kind 'topicz'. Valid kinds: topics, nodes, services, actions.
JSON is the raw name array. Empty lists print a “no …” hint, exit 0; missing
ros2 exits 1.
caasi ros launch
caasi ros launch TARGET [--package|-p PKG] [--name NAME] [--dry-run] [--json] [-- LAUNCH_ARGS…]
| Parameter | Kind | Type | Default | Description |
|---|---|---|---|---|
TARGET | argument | str | required | Launch file path, or (with --package) the launch file name inside a package. |
--package | option (-p) | str | — | Launch from an installed package: ros2 launch <pkg> <target>. |
--name | option | str | target stem | Run name for the tracked run. |
--dry-run | option | flag | off | Show the command without starting. |
--json | option | flag | off | Print the run record. |
| trailing args | pass-through | Appended verbatim (e.g. use_sim_time:=true). |
The launch becomes a tracked run (backend: ros, kind: ros) — detached,
logged, stoppable, surviving your terminal. A path-like target that doesn't exist fails early:
Error: Launch file not found: <path>.
shellcaasi ros launch demo.launch.py -p demo_bringup --dry-run
Dry run — nothing was started:
command: /opt/ros/jazzy/bin/ros2 launch demo_bringup demo.launch.py
caasi ros launch demo.launch.py -p demo_bringup use_sim_time:=true
Run 20260905-190411-demo-launch started in the background.
Follow it with: caasi logs 20260905-190411-demo-launch -f
caasi run stop latest
caasi ros topic
caasi ros topic NAME [--echo] [--dry-run] [--json] [-- ECHO_ARGS…]
Two modes in one command:
- info (default): runs
ros2 topic info NAME— type, publisher and subscriber counts. Unknown topic →Error: Topic '/nope' not found (is the graph up?).exit 1. JSON:{"topic": "/scan", "info": "Type: sensor_msgs/msg/LaserScan\n…"}. - echo (
--echo): streaming would block forever, so it becomes a tracked run (kind: ros) namedecho-<topic>. Read the stream withcaasi logs, end it withcaasi run stop.
shellcaasi ros topic /scan
Type: sensor_msgs/msg/LaserScan
Publisher count: 1
Subscription count: 0
caasi ros topic /scan --echo
Run 20260905-191022-echo-scan started in the background.
caasi logs echo-scan -n 4
---
header:
stamp: {sec: 1757070622, nanosec: 412}
frame_id: laser_frame
range_min: 0.12
caasi run stop echo-scan
caasi ros node
caasi ros node NAME [--json]
Wraps ros2 node info NAME — subscribers, publishers, service clients/servers.
Unknown node → Error: Node '/nope' not found (is the graph up?). exit 1.
JSON: {"node": "/talker", "info": "…"}.
caasi ros graph
caasi ros graph [--json]
Quick whole-graph snapshot — all nodes and all topics in one view:
shellcaasi ros graph
ROS Graph
Nodes (2)
/listener
/talker
Topics (4)
/parameter_events
/rosout
/scan
/tf
caasi ros graph --json | jq '.nodes'
["/listener", "/talker"]
caasi ros service
caasi ros service list [--json]
caasi ros service info NAME [--json]
caasi ros service call NAME TYPE [--json] [-- CALL_ARGS…]
Inspect and call ROS 2 services — three thin verbs over ros2 service list|info|call.
Caasi builds no client, imports no rclpy and parses no request: the command line is
handed to the discovered ros2 binary, whose output and exit code are mirrored.
- list —
ros2 service list; the same call asros list services. JSON is the raw name array, an empty graph printsNo services found.and still exits0. - info —
ros2 service info NAME: type, client and server count. Unknown service →Error: Service '/nope' not found.exit1. JSON:{"service": "/toggle", "info": "Type: std_srvs/srv/SetBool\n…"}. - call —
ros2 service call NAME TYPE CALL_ARGS…. The request YAML and any otherros2 service callflag go after--and are appended verbatim. JSON:{"service", "type", "returncode", "stdout"}, and the delegated exit code is returned in both modes. A service that never appears is not an error Caasi invents:ros2 service callwaits for it, and the 20 s probe timeout ends the wait —timed out after 20.0s, exit1.
shell — this machine, no nodes runningcaasi ros service list
No services found.
caasi ros service list --json
[]
caasi ros service info /nope
Error: Service '/nope' not found.
caasi ros service call /nope std_srvs/srv/Nope --json
{
"service": "/nope",
"type": "std_srvs/srv/Nope",
"returncode": 1,
"stdout": ""
}
exit code: 1
shell — with a node runningcaasi ros service list
ROS 2 services
/talker/describe_parameters
/talker/get_parameters
/toggle
caasi ros service info /toggle
Type: std_srvs/srv/SetBool
Client count: 1
Server count: 1
caasi ros service call /toggle std_srvs/srv/SetBool -- "{data: true}"
# → 'ros2 service call /toggle std_srvs/srv/SetBool {data: true}' — output and exit code pass through
caasi nav — Nav2
Nav2 orchestration on top of the same delegation: Caasi checks that
nav2_bringup is installed (via ros2 pkg prefix nav2_bringup), starts the
standard bringup, and tells you whether the navigation stack is actually up — by looking for its
nodes in the live graph.
caasi nav status
caasi nav status [--json]
Reports the bringup install prefix and which Nav2 nodes are currently running. Recognized
nodes: bt_navigator, planner_server, controller_server,
behavior_server, recoveries_server, smoother_server,
waypoint_follower, amcl, velocity_smoother.
shellcaasi nav status
Nav2
Bringup /opt/ros/jazzy
Nodes /bt_navigator, /planner_server, /controller_server, /amcl
caasi nav status --json
{ "installed": true, "bringup": "/opt/ros/jazzy",
"nodes": ["/bt_navigator", "/planner_server", "/controller_server", "/amcl"] }
caasi nav launch
caasi nav launch [--params FILE] [--map FILE] [--dry-run] [--json] [-- LAUNCH_ARGS…]
| Option | Type | Default | Description |
|---|---|---|---|
--params | path | — | Params file → launch arg params_file:=<file>. |
--map | path | — | Map file → launch arg map:=<file>. |
--dry-run | flag | off | Show the command without starting. |
--json | flag | off | Print the run record. |
Starts ros2 launch nav2_bringup bringup_launch.py [map:=…] [params_file:=…] as a
tracked run named nav2-bringup (kind: nav). Requires
nav2_bringup to be installed:
shellcaasi nav launch --map maps/warehouse.yaml --dry-run
Dry run — nothing was started:
command: /opt/ros/jazzy/bin/ros2 launch nav2_bringup bringup_launch.py map:=maps/warehouse.yaml
caasi nav launch --map maps/warehouse.yaml --params my_nav2_params.yaml
Run 20260905-192233-nav2-bringup started in the background.
caasi logs nav2-bringup -f
# …wait for "[INFO] [bt_navigator]: bt_navigator started"…
Without Nav2: Error: Nav2 is not installed (no 'nav2_bringup' package). Install it
(e.g. sudo apt install ros-jazzy-navigation2 ros-jazzy-nav2-bringup) and try again.
caasi nav inspect
caasi nav inspect [PARAMS_FILE] [--json]
Lists the nodes configured in a Nav2 params file. With no argument, the distro default is
used: <bringup prefix>/share/nav2_bringup/params/nav2_params.yaml. Nav2 params
files are keyed by node name at the top level, so “which nodes will this bring up?” is one
yaml.safe_load away:
shellcaasi nav inspect
Nav2
Params /opt/ros/jazzy/share/nav2_bringup/params/nav2_params.yaml
Nodes 14
amcl
behavior_server
bt_navigator
controller_server
planner_server
…
caasi nav inspect my_params.yaml --json
{ "path": "my_params.yaml", "nodes": ["amcl", "bt_navigator", "planner_server"] }
Missing file → Error: Nav2 params file not found: <path> (exit 1).
caasi nav test
caasi nav test [--json]
Readiness gate with three checks — ros2 binary, nav2_bringup package,
running Nav2 nodes — and an exit code you can script against:
shellcaasi nav test
✓ ros2 /opt/ros/jazzy/bin/ros2
✓ nav2_bringup /opt/ros/jazzy
✓ nodes /bt_navigator, /planner_server, /controller_server, /amcl
Nav2 is ready.
caasi nav test --json | jq '.ok'
true
JSON: {"ok": bool, "checks": [{"check", "ok", "detail"}, …]}. Exit
0 when all checks pass, 1 otherwise.
caasi nav doctor
caasi nav doctor [--verbose] [--json]
The check-table version of nav test. It runs two shared sections — ros
(distro, setup file, ros2 binary, whether the environment is sourced,
RMW_IMPLEMENTATION) and robotics (one ros2 pkg prefix probe
per stack: nav2_bringup, moveit_core, controller_manager,
slam_toolbox) — then adds two Nav2 rows of its own: the default params file shipped
with nav2_bringup and the Nav2 nodes currently in the graph. Every row is
ok, warn, fail or skip; exit is 0
unless a row failed, so warnings alone never fail the gate. The robotics section on
its own is caasi doctor --component robotics. Each stack probe is a direct
ros2 pkg prefix call with an 8 s budget: a probe that times out is a warning
(probe timed out for '<package>'), any other non-zero exit is the
package '<package>' not found failure below.
shell — this machine, no nodes runningcaasi nav doctor
✓ ROS 2 distro — jazzy
✓ ROS 2 setup file — /opt/ros/jazzy/setup.bash
✓ ros2 CLI — /opt/ros/jazzy/bin/ros2
✓ Environment sourced — ROS 2 environment is present in this shell
• DDS middleware — RMW_IMPLEMENTATION not set (distro default will be used)
✓ Nav2 — nav2_bringup
✗ MoveIt 2 — package 'moveit_core' not found
↳ Install the moveit2 packages into your ROS 2 distro.
✗ ros2_control — package 'controller_manager' not found
↳ Install the ros2_control packages into your ROS 2 distro.
✓ SLAM Toolbox — slam_toolbox
✓ Nav2 params file — /opt/ros/jazzy/share/nav2_bringup/params/nav2_params.yaml
! Lifecycle nodes — No Nav2 nodes are running.
↳ Start the stack with 'caasi nav launch'.
exit code: 1
caasi nav doctor --json | jq '.group, .sections, .checks[-1], .exit_code'
"nav"
["ros", "robotics"]
{
"section": "robotics",
"name": "Lifecycle nodes",
"status": "warn",
"detail": "No Nav2 nodes are running.",
"hint": "Start the stack with 'caasi nav launch'."
}
1
JSON is {"group": "nav", "sections": ["ros", "robotics"], "checks": [{"section", "name",
"status", "detail", "hint"}], "exit_code"}. Hints print for failures and warnings;
--verbose prints them for passing rows too.
caasi moveit — MoveIt 2
Same pattern for manipulation: locate moveit_ros_move_group, launch your MoveIt
config, and verify the move_group node is up. Planning always happens inside MoveIt 2.
caasi moveit status
caasi moveit status [--json]
shellcaasi moveit status
MoveIt 2
Package /opt/ros/jazzy
move_group /move_group
caasi moveit status --json
{ "installed": true, "package": "/opt/ros/jazzy",
"move_group_nodes": ["/move_group"] }
Node detection works even when the package prefix lookup is unavailable — status answers
“is move_group running right now?” from the live graph.
caasi moveit launch
caasi moveit launch TARGET [--package|-p PKG] [--name NAME] [--dry-run] [--json] [-- LAUNCH_ARGS…]
Identical semantics to ros launch (path check, pass-through args, tracked run —
kind: moveit), pointed at your MoveIt config's demo launch:
shellcaasi moveit launch demo.launch.py -p panda_moveit_config
Run 20260905-193312-demo-launch started in the background.
Follow it with: caasi logs 20260905-193312-demo-launch -f
caasi moveit plan
caasi moveit plan [--group|-g NAME] [--json]
Answers “can I plan right now?” — i.e. is a move_group node in the graph? The
optional --group records which planning group you care about. Exit 0
ready / 1 not ready:
shellcaasi moveit plan --group panda_arm
move_group is up — planning available (group: panda_arm)
caasi moveit plan --json
{ "ready": true, "group": null, "move_group_nodes": ["/move_group"] }
caasi moveit plan; echo $?
Error: move_group node is not running. Launch your MoveIt config first (caasi moveit launch …).
1
caasi moveit test
caasi moveit test [--json]
Three-check readiness gate like nav test: ros2 binary,
moveit_ros_move_group package, live move_group node.
JSON {"ok", "checks"}, exit 0/1.
caasi moveit doctor
caasi moveit doctor [--verbose] [--json]
The same two shared sections as nav doctor — ros and
robotics, same exit semantics (1 only on a failed row) — plus three
MoveIt rows: the motion-planner capability behind caasi motion, the
moveit_ros_move_group package, and a live move_group node. JSON carries
"group": "moveit".
shell — this machine, no nodes runningcaasi moveit doctor
# …the same `ros` and `robotics` rows as `caasi nav doctor`, then:
! Motion planners — No motion capability is installed.
↳ See which planners are installed with 'caasi motion status'.
✗ moveit_ros_move_group — MoveIt 2 not found (package moveit_ros_move_group missing); install ros-$ROS_DISTRO-moveit.
↳ Install ros-$ROS_DISTRO-moveit.
! move_group — No move_group node is running.
↳ Start a move_group with 'caasi moveit launch <file>' or 'caasi motion serve'.
exit code: 1
The planner row lists whichever motion capabilities resolve; none resolving is a
warning, not a failure — only a missing moveit_ros_move_group package fails the
gate.
caasi control — ros2_control
Controller management via ros2 control (the ros2controlcli package),
plus offline validation of controller YAML — the #1 source of “why won't my hardware interface
load”.
caasi control status
caasi control status [--json]
shellcaasi control status
ros2_control
Package /opt/ros/jazzy
Controller managers /controller_manager
caasi control status --json
{ "installed": true, "package": "/opt/ros/jazzy",
"managers": ["/controller_manager"] }
managers are the live graph nodes containing controller_manager —
i.e. the robots/hardware interfaces currently serving controllers.
caasi control list
caasi control list [--json]
Runs ros2 control list_controllers and passes the output through:
shellcaasi control list
joint_state_broadcaster joint_state_broadcaster/JointStateBroadcaster active
diff_drive_controller diff_drive_controller/DiffDriveController active
caasi control list --json
["joint_state_broadcaster joint_state_broadcaster/JointStateBroadcaster active", …]
Failure (typically ros2controlcli not installed, or no manager reachable) →
Error: ros2_control CLI is not available (is 'ros2controlcli' installed?). exit 1.
caasi control check
caasi control check PARAMS_FILE [--json]
Static validation of a controller params YAML, offline — no ROS needed. It verifies:
- the file parses and its top level is a mapping;
- a
controller_manager:node with aros__parameters:block exists; update_rateis set on the controller manager;- every controller entry (dict-valued key under
ros__parameters) declares atype.
controllers.yamlcontroller_manager:
ros__parameters:
update_rate: 100
joint_state_broadcaster:
type: joint_state_broadcaster/JointStateBroadcaster
diff_drive_controller:
type: diff_drive_controller/DiffDriveController
shellcaasi control check controllers.yaml
✓ joint_state_broadcaster
✓ diff_drive_controller
Controller config looks good (2 controller(s)).
caasi control check broken.yaml --json
{
"path": "broken.yaml",
"controllers": ["diff_drive_controller", "broken_controller"],
"issues": [
"controller_manager.ros__parameters has no 'update_rate'",
"controller 'broken_controller' has no 'type'"
],
"ok": false
}
exit code: 1
Missing argument → Error: Pass a controller params YAML file…; missing file →
params-not-found error; exit 0 clean / 1 any issue (both modes).
caasi control doctor
caasi control doctor [--verbose] [--json]
Same shared ros + robotics sections and same exit rule as
nav doctor, plus two rows: the ros2controlcli package that
control list delegates to, and a live controller_manager node. JSON
carries "group": "control".
shell — this machine, no nodes runningcaasi control doctor
# …the same `ros` and `robotics` rows as `caasi nav doctor`, then:
✗ ros2controlcli — ros2_control CLI not found (package ros2controlcli missing); install ros-$ROS_DISTRO-ros2controlcli.
↳ Install ros-$ROS_DISTRO-ros2controlcli.
! controller_manager — No controller_manager node is running.
↳ Start the hardware interface that hosts controller_manager, then run 'caasi control list'.
exit code: 1
control check stays the offline answer for a params file; this is the online one —
it tells you whether ros2 control list_controllers can work at all right now.
The shared pattern
| You want… | Use |
|---|---|
| “what's on the graph?” | ros status · ros graph · ros list … |
| “call a service” | ros service list · ros service info · ros service call → ros2 service … |
| “start something long-lived” | ros launch · nav launch · moveit launch · ros topic --echo → all become tracked runs |
| “is the stack ready?” (scriptable) | nav test · moveit test · moveit plan · control check → exit 0/1 + --json |
| “what's missing on this machine?” | nav doctor · moveit doctor · control doctor → check table, exit 1 on any failure |
| “why is my config broken?” | nav inspect · control check · ros doctor |
| anything else ROS | caasi native ros … — raw ros2 pass-through |