Caasi v0.2.0 ROS Ecosystem: ros · nav · moveit · control

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:

  1. ROS_DISTRO environment variable → /opt/ros/$ROS_DISTRO;
  2. otherwise, scan /opt/ros for a subdirectory containing setup.bash;
  3. the ros2 binary: <distro>/bin/ros2 first, then ros2 on PATH.

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'.
Sourced ROS 2 environment

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…]
ParameterKindTypeDefaultDescription
TARGETargumentstrrequiredLaunch file path, or (with --package) the launch file name inside a package.
--packageoption (-p)strLaunch from an installed package: ros2 launch <pkg> <target>.
--nameoptionstrtarget stemRun name for the tracked run.
--dry-runoptionflagoffShow the command without starting.
--jsonoptionflagoffPrint the run record.
trailing argspass-throughAppended 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:

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.

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

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 [--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 [--params FILE] [--map FILE] [--dry-run] [--json] [-- LAUNCH_ARGS…]
OptionTypeDefaultDescription
--paramspathParams file → launch arg params_file:=<file>.
--mappathMap file → launch arg map:=<file>.
--dry-runflagoffShow the command without starting.
--jsonflagoffPrint 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 [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 [--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 [--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 doctorros 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:

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 callros2 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 ROScaasi native ros … — raw ros2 pass-through