ROS2 Simulation Control
A complete ROS 2 workspace for the SO-ARM101 six-degree-of-freedom robotic arm, covering robot description, a built-in hardware driver, Gazebo simulation, and MoveIt 2 motion planning.
The SO-ARM101 is a second-generation open-source follower arm designed jointly by TheRobotStudio and the LeRobot community, using six STS3215 servos, a servo driver board, and 3D-printed PLA+ parts.
**Note: **the robotic arm requires center calibration; perform center calibration when all joints are at the middle of their rotatable range
Package Structure
Target platform: ROS 2 Humble / Jazzy.
ROS2 Environment Preparation
Before building this project, make sure ROS 2 and the related components are installed on your system.
System Requirements
Ubuntu 22.04 (recommended) or 24.04
At least 4 GB of memory
Real-hardware mode requires a USB serial port
0.1 Install ROS 2 Humble
# Set the locale
sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8
# Add the ROS 2 software source
sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
# Install ROS 2 Humble Desktop
sudo apt update
sudo apt install ros-humble-desktop0.2 Install Build Tools and Dependencies
# colcon build tool
sudo apt install python3-colcon-common-extensions
# MoveIt 2
sudo apt install ros-humble-moveit
# ros2_control
sudo apt install ros-humble-ros2-control \
ros-humble-ros2-controllers \
ros-humble-controller-manager \
ros-humble-joint-state-publisher-gui0.3 Set Environment Variables
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc0.4 Set Serial Port Permissions (Required for Real Hardware)
Permanent setup (recommended):
sudo usermod -a -G dialout $USER
# Takes effect after logging out and back inTemporary setup (must be re-executed after each reboot):
sudo chmod 666 /dev/ttyACM0Install the Workspace Environment
# Step 1 Create the workspace
mkdir -p ~/so101_ws/src
cd ~/so101_ws/src
# Step 2 Put the source code in
cp -r /path/to/SO-ARM101_ROS2 ./
# Step 3 Install system dependencies
cd ~/so101_ws
rosdep install --from-paths src --ignore-src -r -y
# Step 4 Build all packages
colcon build --symlink-install
# Step 5 Load the environment ← run in every new terminal
source install/setup.bashReal-hardware note — the so_arm_hardware package is built in. No additional driver installation is needed; it communicates directly with the STS3215 servos over the serial port using the SCS protocol.
Visualization Verification
This is the simplest place to start — no controller, no hardware needed.
# Terminal 1
source ~/so101_ws/install/setup.bash
ros2 launch so_arm101_description view_description.launch.py rviz:=trueRViz displays the complete robot model; drag the sliders to verify that each joint moves correctly.
Controller Test (Virtual Hardware / Mock Mode)
Still no real robot is needed; everything runs in memory.
# Terminal 1
source ~/so101_ws/install/setup.bash
ros2 launch so_arm101_description controllers_bringup.launch.pyIt is ready once the following appears in the log:
joint_state_broadcaster → active
joint_trajectory_controller → activeNote: simulation mode starts only two controllers (joint_state_broadcaster and joint_trajectory_controller). gripper_controller has been removed, and the gripper is uniformly controlled by joint_trajectory_controller along with all 6 joints.
Controller Responsibilities
MoveIt Motion Planning (Mock Hardware)
Only one terminal is needed — MoveIt automatically starts the controller stack internally.
# Terminal 1
source ~/so101_ws/install/setup.bash
ros2 launch so_arm101_moveit_config demo.launch.pyAfter the RViz window opens:
In the MotionPlanning panel, set Planning Group → manipulator
Start State → ****
<current>, Goal State → extendedClick Plan and then Execute
Available preset poses: open, zero, extended, rest.
4.1 MoveIt Interface in Detail
After RViz starts, the MotionPlanning panel is shown on the left, containing the following main tabs:
Planning Tab
Planning Parameters
Recommendation for the first test: set Velocity and Acceleration to 0.3 to reduce motion speed and ensure safety.
Scene Objects Tab
Add obstacles (Box / Sphere / Cylinder) for collision detection
Import / export the scene
MoveIt automatically plans around obstacles
Stored States Tab
Save commonly used robotic arm poses
Default poses:
open,zero,extended,rest
4.2 Basic Operation Workflow
Method A: Interactive Dragging (Recommended)
In the 3D view, find the interactive markers (colored arrows and rings) at the end of the robotic arm
Drag the arrows to translate the end position, and drag the rings to rotate the orientation
The system automatically solves IK and updates the joint angles in real time
Click Plan to view the planned trajectory (orange)
After confirming, click Execute to execute
If dragging is laggy, it is recommended to start from the
restpreset pose before dragging.
Method B: Preset Poses
Query Goal State dropdown menu → select
open/extended/rest, etc.Click Update
Click Plan
Click Execute
Method C: Manually Set Joint Angles
Query Goal State → Joints tab
Drag each joint slider to set the target angle
Joint range reference:
Click Update
Click Plan
Click Execute
Method D: Random Valid Goal
Click the Random Valid button to automatically generate a reachable random pose, then Plan → Execute.
4.3 Safety Notes
Reduce speed on first use: set Velocity / Acceleration to 0.1–0.3
Emergency stop: press Ctrl+C at any time to terminate the program, or cut the power
Joint limits: MoveIt will not plan beyond the range in
joint_limits.yaml, but you must ensure the configuration is correctReal hardware: before executing, ensure there is enough space around the robotic arm
MoveIt Configuration Overview
Gazebo Simulation
Gazebo simulation requires 4 terminals running simultaneously. Please execute them strictly in order.
5.1 Start Gazebo Simulation (Terminal 1)
# Terminal 1
source ~/so101_ws/install/setup.bash
ros2 launch so_arm_gz so_arm_gz_bringup.launch.pyWait for the Gazebo window to appear; the robot briefly remains in the air and then lands.
5.2 Load the Trajectory Controller (Terminal 2)
By default, Gazebo activates only forward_position_controller; you need to manually switch to joint_trajectory_controller:
# Terminal 2
source ~/so101_ws/install/setup.bash
# Step A — turn off forward_position_controller
ros2 control set_controller_state forward_position_controller inactive
# Step B — use spawner to load and activate joint_trajectory_controller
ros2 run controller_manager spawner joint_trajectory_controller
# Step C — verify
ros2 control list_controllersExpected output:
forward_position_controller inactive
joint_state_broadcaster active
joint_trajectory_controller active⚠️ Do not use ros2 control load_controller first! It puts the controller into the unconfigured state, preventing the spawner from activating it. If you have already done so, first unload_controller and start over.
5.3 Start move_group (Terminal 3)
# Terminal 3
source ~/so101_ws/install/setup.bash
ros2 launch so_arm101_moveit_config move_group.launch.py use_sim_time:=True5.4 Start RViz (Terminal 4)
# Terminal 4
source ~/so101_ws/install/setup.bash
ros2 launch so_arm101_moveit_config moveit_rviz.launch.pyAfter RViz is ready:
Planning Group → manipulator
Goal State → open (or
extended,rest)Click Plan and then Execute
The arm joints in Gazebo will follow the motion.
Note: Due to the PID gain limitation of the gz_ros2_control Humble version, the gripper may not physically open in Gazebo (the execution log still shows success). Mock mode and real hardware do not have this problem.
5.5 Headless Mode (No GUI)
ros2 launch so_arm_gz so_arm_gz_bringup.launch.py \
gazebo_gui:=false \
launch_rviz:=false5.6 Troubleshooting: When Loading Fails Repeatedly
If the spawner keeps reporting Failed to activate controller, perform the following steps for a complete reset:
# 1. Unload the stuck controller
ros2 control unload_controller joint_trajectory_controller
# 2. Turn off forward_position_controller
ros2 control set_controller_state forward_position_controller inactive
# 3. Spawn again
ros2 run controller_manager spawner joint_trajectory_controllerReal Hardware
Prerequisite: the SO-ARM101 robotic arm is assembled, and the servo driver board is connected to the computer via USB.
6.1 Start the Controller (Can Be Skipped)
# Terminal 1
source ~/so101_ws/install/setup.bash
ros2 launch so_arm101_description controllers_bringup.launch.py \
hardware_type:=real \
usb_port:=/dev/ttyACM0The so_arm_hardware plugin automatically performs the following:
Open the serial port
Scan the 6 servo IDs (1–6)
Verify that each servo responds
Enable torque and read the current position
Once the controller is ready, open two more terminals to start MoveIt:
# Terminal 2 — move_group
source ~/so101_ws/install/setup.bash
ros2 launch so_arm101_moveit_config move_group.launch.py# Terminal 3 — RViz
source ~/so101_ws/install/setup.bash
ros2 launch so_arm101_moveit_config moveit_rviz.launch.py6.2 MoveIt (One-Click Start)
The following command replaces 6.1 (do not run both at the same time; stop the command from 6.1) —
demo.launch.pyalready includes the controller stack internally.
# Terminal 1
source ~/so101_ws/install/setup.bash
ros2 launch so_arm101_moveit_config demo.launch.py \
hardware_type:=real \
usb_port:=/dev/ttyACM06.3 Serial Port Troubleshooting
6.4 RViz Display Does Not Match the Actual Pose
If the robotic arm pose in RViz does not match the real hardware (for example, joint offset or false collision reports):
Confirm that the servos have completed center calibration
Adjust the
position_offsetof each joint inso_arm101.ros2_control.xacroConversion formula:
new offset = current offset + (currently displayed rad / 0.00153398)After modifying, rebuild the
so_arm101_descriptionpackage
FAQ
Q1: "package not found" during build
A: Make sure all system dependencies are correctly installed and the ROS 2 environment is sourced:
source /opt/ros/humble/setup.bash
cd ~/so101_ws
rosdep install --from-paths src --ignore-src -r -y
colcon build --symlink-installQ2: "Permission denied" when accessing the serial port at startup
A: Check the serial port permissions:
# Temporary fix
sudo chmod 666 /dev/ttyACM0
# Permanent fix (takes effect after logout)
sudo usermod -a -G dialout $USERQ3: MoveIt planning fails with "Motion planning start tree could not be initialized"
A: There are usually two reasons:
Joint out of limits — Check the output of
FixStartStateBoundsin the log. The current tolerance is 0.3 rad; if the exceedance is within this range, it will pass. Otherwise, you need to adjuststart_state_max_bounds_erroror check the servo offsets.Start state collision — Check the output of
FixStartStateCollisionin the log. If it reports "Unable to find a valid state nearby", it means the current pose has self-collision. The robotic arm may be in a folded pose (for example, the gripper touching the shoulder), or the offset is incorrect. Adjustposition_offsetand try again.
Q4: The robotic arm does not move after Execute
A: Check the controller status:
ros2 control list_controllersMake sure joint_trajectory_controller is in the active state. If not, spawn it again:
ros2 run controller_manager spawner joint_trajectory_controllerQ5: RViz starts slowly or hangs
A: This is normal. When MoveIt starts, it loads the URDF model, the collision detection plugin, the kinematics solvers, etc.; the first startup takes about 10 seconds.
Q6: The planned path is not smooth or jitters
A: Try the following methods:
Switch to a different planner (select
RRTConnectfrom the Planner dropdown in RViz)Increase Planning Time to 10 seconds
Confirm that the goal is within the workspace (test with
Random Valid)
Q7: The gripper does not move in Gazebo
A: This is a hardcoded PID gain limitation of the gz_ros2_control Humble version (fixed at 0.1) and cannot be overridden via URDF parameters. The Execute in the log shows success, but the gripper will not open in the Gazebo physics simulation. Mock mode and real hardware do not have this problem.
Appendix: Launch Parameter Quick Reference
controllers_bringup.launch.py
so_arm_gz_bringup.launch.py
Directory Layout
SO-ARM101_ROS2/
├── so_arm_utils/ # Python utility library
├── so_arm101_description/ # URDF · controllers · meshes · RViz · MuJoCo
├── so_arm101_moveit_config/ # MoveIt 2 SRDF · planners · launch files
├── so_arm_gz/ # Gazebo simulation launch
├── so_arm_hardware/ # Built-in SCS serial driver (C++)
└── Simulation/ # Original CAD URDF (kept for reference)
