ROSOrin Pro Explained: The Full ROS 2 AI-Agent Pipeline — OpenClaw, SLAM, YOLO, Nav2, and MoveIt2
Tell the robot "Bring me the red block on the table," and it drives to the table, finds the block, picks it up with the 6-DOF arm, and sets it down where you asked. That loop is not a single algorithm. Voice I/O, slam_toolbox laser SLAM, Nav2, OpenCV color detection, and arm kinematics each run as their own ROS 2 services. This article walks the official ROSOrin Pro (Jetson Orin NX) source tree and docs, one module at a time.
1. System function overview
Hiwonder ROSOrin Pro is a ROS 2 robot car built around NVIDIA Jetson Orin NX. The kit includes a depth camera, LiDAR, an AI voice box, and a 6-DOF robotic arm. A spoken command drives the full loop: understand -> navigate to the target -> recognize the block -> grasp -> place.
The main computer is a configuration option. Kits can ship with Jetson Orin Nano, Orin NX, or Orin AGX. This teardown uses the Jetson Orin NX 8 GB variant.
| Stage | Trigger | What runs |
|---|---|---|
| Wake word | wakeup_flag = True |
WonderEcho Pro microphone array |
| Command understanding | ASR text is non-empty | Text POST over HTTP to the OpenClaw agent |
| Start navigation | Command contains a place name / pose | Nav2 loads the map; AMCL localizes |
| Block detection | Robot arrives at the nav goal | Call /set_color to set the target color |
| Grasp | Depth reading is valid | Pixel coordinates -> arm base frame -> IK |
| Place | Grasp succeeds | Navigate to the drop-off pose and open the gripper |
Section takeaway. Workflow: voice command -> WonderEcho Pro ASR -> OpenClaw agent -> Nav2 -> OpenCV color detect -> arm grasp -> place. Each step is bounded by a ROS 2 service call. Failures fall back automatically.
2. Hardware architecture
ROSOrin Pro uses a dual-controller design: Jetson Orin NX + STM32. Orin NX runs the high-level stack (SLAM, YOLO, navigation, language). STM32 runs the real-time loop (motors, bus servos, raw sensors). Perception devices attach over USB to the Jetson. Actuators are driven by the STM32.
Jetson Orin NX takes the depth camera, LiDAR, and microphone array over USB, and talks to the STM32 through USB-UART (/dev/nrc, 1 Mbps). The STM32 drives chassis motors with PWM, arm servos on the dedicated servo bus, the OLED and IMU over I2C/SPI, and battery voltage on the onboard ADC. Wi-Fi comes from the Jetson radio. Splitting host + MCU keeps YOLO inference and SLAM optimization from blocking the real-time loop (motor PWM, servo bus updates).
Section takeaway. Hardware is Jetson Orin NX (host) + STM32 (real-time MCU). Sensors enter over USB. Chassis motors are STM32 PWM. Arm servos use a serial bus protocol. Host-MCU link is USB-UART at 1 Mbps.
3. Software framework
3.1 Layered architecture
Software is five layers: Interaction, Decision, Perception, Control, and Hardware Abstraction. Data flows sensors -> Perception -> Decision -> Control -> HAL -> actuators. A layer talks only to its neighbors. No skip-level calls.
3.2 How modules talk
| Mechanism | When to use | Examples |
|---|---|---|
| ROS 2 Topic (pub/sub) | Continuous streams and setpoints |
/depth_cam/rgb0/image_raw, /cmd_vel, /controller/...
|
| ROS 2 Service (req/resp) | One-shot calls and mode switches |
Trigger, /kinematics/set_pose_target, /set_color
|
| ROS 2 Action (feedback) | Long jobs |
navigate_to_pose, navigate_through_poses
|
| HTTP REST | OpenClaw LLM gateway | POST http://127.0.0.1:18789/v1/chat/completions |
| Serial bus | Low-level servo packets |
bus_servo_control -> HX-12H servos |
Section takeaway. Five layers, data only moves toward the actuators. Topics carry streams, services do one-shot work, actions cover long tasks. OpenClaw uses HTTP REST. Servos stay on the serial bus.
4. Core modules
4.1 Voice I/O and the OpenClaw agent
There are two stages. The front end is the WonderEcho Pro voice box (capture + ASR). The back end is the OpenClaw agent: language understanding and task decisions.
Voice front end (WonderEcho Pro): Implemented by the wonder_echo_pro_node in the xf_mic_asr_offline package. It talks to the microphone over serial and publishes /vocal_detect/wakeup (Bool) and /vocal_detect/asr_result (String).
Agent brain (OpenClaw): Implemented by the claw_voice node in the openclaw_controller package. It subscribes to WonderEcho Pro's ASR result, then sends an HTTP POST to the OpenClaw gateway at 127.0.0.1:18789 for language understanding. The API matches the OpenAI Chat Completions format.
# Source: claw_voice.py
payload = {
"model": "openclaw/default",
"messages": [{"role": "user", "content": text}],
"user": SESSION_KEY
}
resp = requests.post(
f"{GATEWAY_URL}/v1/chat/completions",
headers=headers,
json=payload
)
reply = result["choices"][0]["message"]["content"]
OpenClaw is more than speech understanding. Package openclaw_controller registers eight nodes: speech understanding, speech synthesis, target tracking, track-and-grasp, arm group control, chassis motion, navigation manager, and AprilTag localization. Together they are the robot "brain": they split a sentence into navigate / detect / grasp subtasks and dispatch them.
Cloud brain vs local brain. By default the gateway forwards to a remote API. If onboard compute is high enough (the article uses "100 TOPS+" as a rule of thumb), point the model field in ~/.openclaw/openclaw.json at a local checkpoint. Local inference cuts latency and works offline, but RAM is tight: SLAM + YOLO + an LLM on 8 GB is a squeeze.
| Setting | Meaning | Path / value |
|---|---|---|
| API token | Gateway auth token |
~/.openclaw/openclaw.json -> gateway.auth.token
|
| Gateway URL | OpenClaw agent endpoint | http://127.0.0.1:18789 |
| ASR mode | Online vs offline recognition |
mode=1 (on-device online ASR in this write-up) |
| Wake enable | Wake-word switch | Service /vocal_detect/enable_wakeup
|
Key points. WonderEcho Pro does ASR. OpenClaw understands the sentence and schedules modules. Default path is cloud; a local model is optional. The gateway speaks OpenAI-style JSON, so switching models is a config change.
4.2 SLAM mapping
ROSOrin Pro maps with slam_toolbox's sync_slam_toolbox_node (synchronous SLAM). The front end is scan matching. The back end is pose-graph optimization with Ceres Solver. Each laser scan is aligned to the map while the robot pose and the occupancy grid are updated together.
Core parameters in slam/config/slam.yaml:
| Parameter | Value | Meaning |
|---|---|---|
solver_plugin |
solver_plugins::CeresSolver |
Nonlinear back-end |
mode |
mapping |
mapping or localization |
resolution |
0.05 |
5 cm per grid cell |
max_laser_range |
12.0 |
Ignore returns beyond 12 m |
do_loop_closing |
true |
Enable loop closure |
Official mapping backends. Launch argument slam_method selects the stack. Pick the one that fits the room:
| Method | Traits | Best for |
|---|---|---|
slam_toolbox (default) |
Ceres sync SLAM, loop closure, live map-to-localize switch | Indoor rooms that need loop closure |
| Cartographer | Google pose-graph SLAM, submaps, usually cleaner large maps | Large spaces, longer runs |
| Gmapping | Classic particle filter, light CPU, ~30 particles | Small rooms, weak compute |
Switch at launch:
ros2 launch slam slam.launch.py slam_method:=cartographer
Configs live at:
-
slam/config/slam.yaml(slam_toolbox) -
slam/config/cartographer_2d.lua(Cartographer) -
slam/config/gmapping.yaml(Gmapping)
Mapping procedure:
# 1. Bring up chassis and sensors
ros2 launch bringup bringup.launch.py
# 2. Start SLAM
ros2 launch slam slam.launch.py
# 3. Drive slowly with a joystick or keyboard while the map fills in
# 4. Save the map
ros2 run nav2_map_server map_saver_cli -f ~/my_map
Map quality depends on three things: speed (keep under 0.3 m/s), scene texture (empty corridors need extra passes), and a completed loop (close the path at least once).
Key points. Official options: slam_toolbox (default), Cartographer (large, high-quality maps), Gmapping (small scenes, low CPU). Switch with slam_method.
4.3 Nav2 navigation
ROSOrin Pro uses Nav2 as a two-layer stack: global plan + local control.
| Component | Algorithm | Job |
|---|---|---|
Global planner (planner_server) |
Smac Planner Hybrid-A* | Plan on the static map |
Local controller (controller_server) |
TEB (Timed Elastic Band) | Track the path, dodge live obstacles, respect kinematics |
| Costmap | Global 5 cm + local 5 cm | Fuse LiDAR (and other sensors) into occupancy costs |
| Localization (AMCL) | Adaptive Monte Carlo, 500-2000 particles | Match live scans to the saved map |
Behavior tree (bt_navigator) |
BT + recoveries | On failure: Spin, Backup, Wait, then retry |
Nav2 is not only "go to one pose." Built-in patterns:
| Mode | How | What it does |
|---|---|---|
| Single-goal nav |
navigate_to_pose action |
One (x, y, yaw) goal |
| Multi-waypoint |
navigate_through_poses action |
Visit poses in order |
| Nav + haul |
navigation_transport node graph |
Drive, grasp, drive, place |
| Custom BT | Edit the BT XML in nav2_params.yaml
|
Change failure recoveries |
| Multi-robot | Separate robot_1_ekf.yaml / robot_2_ekf.yaml
|
Several bases sharing a map |
Local controller knobs in nav2_params.yaml:
controller_server:
ros__parameters:
controller_frequency: 20.0 # 20 Hz control loop
FollowPath:
plugin: "teb_local_planner::TEBController"
max_vel_x: 0.5 # max linear speed 0.5 m/s
max_vel_theta: 1.0 # max yaw rate 1.0 rad/s
xy_goal_tolerance: 0.1 # 10 cm XY tolerance
yaw_goal_tolerance: 0.1 # ~5.7 deg yaw tolerance
Key points. Nav2 covers single goals, waypoint tours, transport missions, custom behavior trees, and multi-robot setups. Global plan is Hybrid-A*. Local track is TEB at 20 Hz.
4.4 YOLO detection and OpenCV color tracking
Two vision stacks, two jobs:
-
YOLO - traffic-sign detection for the autonomous-driving demos (straight, right turn, stop, traffic light, crosswalk). Ultralytics stack. Default engine YOLOv26n; optional YOLOv11s. Exported to TensorRT
.enginefiles and run on the Jetson. -
OpenCV color detection - block finding for the arm. LAB threshold + contours, implemented by class
ColorTrackerin nodeclaw_track_and_grab.
YOLO node (yolo_node) - core loop
# Source: yolo_node.py - detection loop
results = self.yolo_detect(image, conf=self.conf, task=self.task)
for result in results:
for i in range(len(items)):
confidence = items.conf[i].item()
class_name = self.classes[class_id]
object_info = ObjectInfo()
object_info.class_name = class_name # class label
object_info.score = float(confidence) # 0-1 confidence
object_info.box = box_coords # [x1, y1, x2, y2]
objects_info.append(object_info)
object_msg = ObjectsInfo()
object_msg.objects = objects_info
self.object_pub.publish(object_msg) # ~/object_detect
YOLO interfaces:
| Interface | Kind | Name | Meaning |
|---|---|---|---|
| RGB subscribe | Topic (Image) |
/depth_cam/rgb0/image_raw |
Depth-camera RGB |
| Detections | Topic (ObjectsInfo) |
~/object_detect |
List of class / score / box |
| Overlay | Topic (Image) |
~/object_image |
Frame with drawn boxes |
| Start | Service (Trigger) |
/yolo/start |
Enable the detector |
| Stop | Service (Trigger) |
/yolo/stop |
Disable the detector |
ObjectsInfo fields (interfaces/msg/ObjectInfo.msg):
| Field | Type | Meaning |
|---|---|---|
class_name |
string |
Class label |
score |
float32 |
Confidence 0-1 |
box |
int32[4] |
Pixel box [x1, y1, x2, y2]
|
width |
int32 |
Source image width |
height |
int32 |
Source image height |
angle |
int32 |
Rotation; 0 in this detect mode |
OpenCV color tracker used by the gripper:
# Source: claw_track_and_grab.py - ColorTracker
img_lab = cv2.cvtColor(img_blur, cv2.COLOR_BGR2LAB) # BGR -> LAB
mask = cv2.inRange(img_lab, tuple(color['min']), tuple(color['max']))
contours = cv2.findContours(
dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE
)[-2]
# Keep the largest contour -> min enclosing circle -> block center
(center_x, center_y), radius = cv2.minEnclosingCircle(best_c[0])
Model choice: YOLO + OpenCV. YOLO is a TensorRT engine on Orin NX. With spare TOPS you can swap in Faster R-CNN or SSD by changing the engine:= parameter only.
Launch examples:
# Start YOLO (default YOLOv26n traffic weights)
ros2 run example yolo_node --ros-args -p engine:=best_traffic_26
# Switch to YOLOv11s
ros2 run example yolo_node --ros-args -p engine:=best_traffic_11
Key points. YOLO watches traffic signs. OpenCV ColorTracker finds colored blocks in LAB space. Detections go out as ObjectsInfo. Engines are swappable.
4.5 MoveIt2 and arm control
ROSOrin Pro ships a 6-DOF arm on a bus-servo link. The current official path is direct bus-servo control, in three layers.
Kinematic model: modified Denavit-Hartenberg. Five revolute joints plus a gripper = six DOF. Inverse kinematics takes target [x, y, z] and pitch, then solves the five arm joints. Gripper open/close is independent. If the requested pitch has no solution, the solver searches pitch_range (default ±180°) at resolution (default 1°).
When to add MoveIt2. Direct servo packets are enough for fixed pick-and-place. For live obstacle avoidance, trajectory smoothing, or coordinated multi-axis motion, put MoveIt2 in front of the same servo layer:
| MoveIt2 feature | What you get |
|---|---|
| Motion planning | OMPL RRT / PRM style planners, collision-free joint trajectories |
| Collision checking | URDF + FCL against the arm and the world |
| Trajectory execution |
FollowJointTrajectory action into servo_controller
|
| Visualization | RViz2 MotionPlanning plugin: drag a goal, preview the path |
Integration steps:
# 1. Install MoveIt2 (ROS 2 Humble)
sudo apt install ros-humble-moveit
# 2. Load the arm URDF in MoveIt Setup Assistant -> generate moveit_config
# 3. Map FollowJointTrajectory to servo_controller
# 4. Launch
ros2 launch moveit_config move_group.launch.py
MoveIt2 Python API sketch:
from moveit.planning import MoveItPy
from moveit.core.robot_state import RobotState
# Init
robot = MoveItPy(node_name="arm_planner")
arm = robot.get_planning_component("arm")
# Goal + plan
arm.set_start_state_to_current_state()
arm.set_goal_state(configuration_name="ready")
plan_result = arm.plan()
if plan_result:
robot.execute(plan_result.trajectory, controllers=[])
MoveItPy is the Humble-era Python binding. configuration_name="ready" is a named state from the SRDF produced by Setup Assistant. After plan() succeeds, execute() sends FollowJointTrajectory to the controller mapped onto the Hiwonder bus servos. Keep the existing kinematics service for simple scripted picks; use MoveIt when the path must weave around the camera mast or the chassis.
Key points. Today's kit talks to servos directly - fine for scripted grasps. Add MoveIt2 when you need OMPL planning, FCL collision checks, and RViz drag-to-plan. Install the moveit package, generate a config from the URDF, and map the trajectory action.
5. Summary
ROSOrin Pro covers the main teaching blocks of a mobile manipulator:
- WonderEcho Pro is the ASR front end. OpenClaw is the brain that parses language and schedules modules. Default brain is cloud; a local model is optional if memory allows.
- SLAM backends:
slam_toolbox(default), Cartographer (large, high quality), Gmapping (small, cheap). Switch withslam_method. - Nav2 is more than point-to-point: waypoints, transport missions, custom behavior trees, multi-robot.
- YOLO detects traffic signs. OpenCV color tracking finds blocks. Engines can be swapped (Faster R-CNN, SSD, etc.).
- MoveIt2 upgrades the arm from bus-servo setpoints to planned, collision-checked trajectories.
The design idea is layered decoupling: five software layers, each talking only next door; Jetson + STM32 on the metal; Topic / Service / Action covering stream vs one-shot vs long job.
Appendix A - Topic / service cheat sheet
| Name | Kind | Role |
|---|---|---|
/vocal_detect/wakeup |
Topic Bool
|
Wake word |
/vocal_detect/asr_result |
Topic String
|
ASR text |
/vocal_detect/enable_wakeup |
Service | Enable wake word |
127.0.0.1:18789 /v1/chat/completions |
HTTP | OpenClaw gateway |
/depth_cam/rgb0/image_raw |
Topic Image
|
RGB for YOLO / color |
~/object_detect |
Topic ObjectsInfo
|
YOLO boxes |
/yolo/start /yolo/stop
|
Service Trigger
|
YOLO on/off |
/set_color |
Service | Target block color |
/kinematics/set_pose_target |
Service | Arm IK goal |
/servo_controller |
Topic | Servo setpoints |
navigate_to_pose |
Action | Single Nav2 goal |
navigate_through_poses |
Action | Waypoint list |
Appendix B - Launch commands
ros2 launch bringup bringup.launch.py
ros2 launch slam slam.launch.py
ros2 launch slam slam.launch.py slam_method:=cartographer
ros2 run nav2_map_server map_saver_cli -f ~/my_map
ros2 run example yolo_node --ros-args -p engine:=best_traffic_26
ros2 run example yolo_node --ros-args -p engine:=best_traffic_11
sudo apt install ros-humble-moveit
ros2 launch moveit_config move_group.launch.py