This repository provides control packages for the ROBOTIS Physical AI lineup.
├── cyclo_motion_controller/
│ ├── CMakeLists.txt
│ └── package.xml
├── cyclo_motion_controller_core/
│ ├── include/cyclo_motion_controller_core/
│ │ ├── common/
│ │ │ └── ...
│ │ ├── controllers/
│ │ │ └── ...
│ │ ├── kinematics/
│ │ │ └── ...
│ │ └── optimization/
│ │ └── ...
│ ├── src/
│ │ ├── controllers/
│ │ │ └── ...
│ │ ├── kinematics/
│ │ │ └── ...
│ │ └── retargeting/
│ │ └── ...
│ ├── CMakeLists.txt
│ └── package.xml
├── cyclo_motion_controller_ros/
│ ├── config/
│ │ └── ...
│ ├── include/cyclo_motion_controller_ros/
│ │ ├── nodes/
│ │ │ └── ...
│ │ └── utils/
│ │ └── ...
│ ├── launch/
│ │ └── ...
│ ├── src/
│ │ ├── nodes/
│ │ │ └── ...
│ │ └── utils/
│ │ └── ...
│ ├── CMakeLists.txt
│ └── package.xml
├── cyclo_motion_controller_ros_py/
│ ├── resource/
│ │ └── ...
│ ├── scripts/
│ │ └── ...
│ ├── package.xml
│ ├── setup.cfg
│ └── setup.py
├── cyclo_motion_controller_models/
│ ├── launch/
│ │ └── ...
│ ├── models/
│ │ └── ...
│ ├── CMakeLists.txt
│ └── package.xml
└── osqp_eigen_vendor/
├── cmake/
├── third_party/
│ └── osqp-eigen/
├── CMakeLists.txt
├── THIRD_PARTY_NOTICES.md
└── package.xml
cyclo_motion_controller/
- Meta package containing package related to motion control.
cyclo_motion_controller_core/
- Core package containing kinematics solver, controllers, and retargeting utilities.
include/cyclo_motion_controller_core/common/: Shared types and utility functions.include/cyclo_motion_controller_core/optimization/: QP definitions and solver interfaces.src/controllers/: Controller implementations for AI Worker and OpenManipulator.src/kinematics/: Kinematics solver implementation.src/retargeting/: Python retargeting utilities.
cyclo_motion_controller_ros/
- ROS 2 package containing controller nodes, launch files, and runtime configs.
config/: YAML configuration files for AI Worker, OMX, and OMY controllers.launch/: Launch files for running the controller nodes.src/nodes/: ROS 2 node executables organized by robot family.src/utils/: Utility nodes such as interactive markers and reference checking.
cyclo_motion_controller_ros_py/
- ROS 2 Python package containing retargeting-related scripts and tests.
scripts/: Python entrypoints.
cyclo_motion_controller_models/
- Robot model descriptions and RViz resources package.
launch/: Launch files for visualizing robot models.models/: URDF/SRDF robot models used by the controller.
osqp_eigen_vendor/
- Vendor package that wraps the upstream
osqp-eigensource tree for this repository. third_party/osqp-eigen/: Vendored upstream source.
- ROS 2 Jazzy installed
- vcs is used to import workspace dependencies, and rosdep is used to install system dependencies
- numpy<2 is required
Clone the repository and import workspace dependencies:
cd ~/ros2_ws/src
git clone https://github.com/ROBOTIS-GIT/cyclo_control.git
vcs import . < cyclo_control/cyclo_control_ci.reposInstall dependencies via rosdep, then build:
cd ~/ros2_ws
sudo apt update
rosdep install --from-paths src --ignore-src -r -y --rosdistro jazzy
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release
source install/setup.bashLaunch AI Worker controllers:
ros2 launch cyclo_motion_controller_ros ai_worker_controller.launch.pyLaunch arguments:
| Argument | Default | Description |
|---|---|---|
controller_type |
movel |
Selects the controller mode. Available values are listed below. |
config_file |
package default | Controller YAML file. |
follower_urdf_path |
package default | Follower robot URDF. |
default_srdf_path |
package default | Default follower robot SRDF. |
modified_srdf_path |
package default | Follower robot SRDF with gripper-to-gripper collision disabled. |
disable_gripper_collisions |
false |
Uses modified_srdf_path instead of default_srdf_path. |
controller_type values:
| Value | Started nodes |
|---|---|
movel |
ai_worker_movel_controller_node |
movej |
ai_worker_movej_controller_node |
bimanual_movel |
ai_worker_bimanual_movel_controller_node |
bimanual_movej |
ai_worker_bimanual_movej_controller_node |
vr |
vr_controller_node, reference_checker_node |
leader |
leader_controller_node, vr_controller_node |
Interactive marker arguments:
| Argument | Default | Description |
|---|---|---|
start_interactive_marker |
false |
Starts MoveL interactive markers for movel and bimanual_movel. |
base_frame |
base_link |
Frame used by interactive markers and MoveL goals. |
marker_scale |
0.2 |
Interactive marker scale. |
right_controlled_link |
end_effector_r_link |
Follower link tracked by the right marker. |
left_controlled_link |
end_effector_l_link |
Follower link tracked by the left marker. |
right_movel_topic |
/r_goal_move |
MoveL command topic for the right marker. |
left_movel_topic |
/l_goal_move |
MoveL command topic for the left marker. |
right_goal_pose_topic |
/r_goal_pose |
PoseStamped mirror topic for the right marker. |
left_goal_pose_topic |
/l_goal_pose |
PoseStamped mirror topic for the left marker. |
Bimanual MoveL arguments:
| Argument | Default | Description |
|---|---|---|
grasp_capture_topic |
/capture_grasp |
Bool topic used to enable or disable bimanual rigid grasp mode. |
virtual_object_movel_topic |
/virtual_object_goal_move |
MoveL command topic for the bimanual virtual object marker. |
virtual_object_pose_topic |
/virtual_object_goal_pose |
PoseStamped mirror topic for the virtual object marker. |
VR and leader arguments:
| Argument | Default | Description |
|---|---|---|
reactivate_topic |
/reactivate |
Bool topic used to toggle the VR controller. |
leader_urdf_path |
package default | Leader robot URDF used by controller_type:=leader. |
arm |
true |
Enables arm retargeting in vr mode. |
hand |
false |
Enables hand retargeting in vr mode. |
When controller_type:=movel and start_interactive_marker:=true, ai_worker_controller.launch.py starts two configurable interactive markers:
- right marker uses
right_controlled_linkand publishes MoveL commands toright_movel_topic - left marker uses
left_controlled_linkand publishes MoveL commands toleft_movel_topic
When controller_type:=bimanual_movel and start_interactive_marker:=true, the launch file starts right and left hand markers while grasp mode is inactive. These markers publish robotis_interfaces/msg/MoveL commands to right_movel_topic and left_movel_topic. The marker commands use time_from_start: 0, so the bimanual MoveL controller treats them as direct servo goals, matching the normal MoveL controller behavior.
The bimanual MoveL controller also supports rigid grasp control:
- publish
truetograsp_capture_topicto capture the current relative grasp between both hands - while grasp mode is active, the right and left hand markers are disabled
- a virtual object marker is enabled and publishes MoveL commands to
virtual_object_movel_topic - virtual-object MoveL commands with
time_from_start: 0act as direct marker goals; positive durations are interpolated inside the controller - publish
falsetograsp_capture_topicto release rigid grasp control and return to independent hand MoveL goals
To disable collision checking only between the two grippers, set disable_gripper_collisions:=true. This helps maintain smooth handover-style motions when the grippers come into contact.
Example movel commands for normal and bimanual MoveL:
ros2 topic pub --once /r_goal_move robotis_interfaces/msg/MoveL "{
pose: {
header: {frame_id: 'base_link'},
pose: {
position: {x: 0.35, y: -0.20, z: 0.85},
orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
}
},
time_from_start: {sec: 2, nanosec: 0}
}"ros2 topic pub --once /l_goal_move robotis_interfaces/msg/MoveL "{
pose: {
header: {frame_id: 'base_link'},
pose: {
position: {x: 0.35, y: 0.20, z: 0.85},
orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
}
},
time_from_start: {sec: 2, nanosec: 0}
}"For marker-style direct servo goals, set time_from_start to zero:
ros2 topic pub --once /r_goal_move robotis_interfaces/msg/MoveL "{
pose: {
header: {frame_id: 'base_link'},
pose: {
position: {x: 0.35, y: -0.20, z: 0.85},
orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
}
},
time_from_start: {sec: 0, nanosec: 0}
}"For bimanual rigid grasp mode, command the virtual object with:
ros2 topic pub --once /capture_grasp std_msgs/msg/Bool "{data: true}"ros2 topic pub --once /virtual_object_goal_move robotis_interfaces/msg/MoveL "{
pose: {
header: {frame_id: 'base_link'},
pose: {
position: {x: 0.35, y: 0.00, z: 0.85},
orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
}
},
time_from_start: {sec: 0, nanosec: 0}
}"movel interpolation duration is supplied per command via time_from_start. A value of zero is intended for marker-based control and makes the controller track the latest goal directly.
Example movej input commands:
ros2 topic pub --once /leader/joint_trajectory_command_broadcaster_right/raw_joint_trajectory trajectory_msgs/msg/JointTrajectory "{
joint_names: ['arm_r_joint1', 'arm_r_joint2', 'arm_r_joint3', 'arm_r_joint4', 'arm_r_joint5', 'arm_r_joint6', 'arm_r_joint7', 'gripper_r_joint1'],
points: [
{
positions: [0.3, -0.2, 0.1, 0.0, 0.2, -0.1, 0.0, 0.02],
time_from_start: {sec: 3, nanosec: 0}
}
]
}"ros2 topic pub --once /leader/joint_trajectory_command_broadcaster_left/raw_joint_trajectory trajectory_msgs/msg/JointTrajectory "{
joint_names: ['arm_l_joint1', 'arm_l_joint2', 'arm_l_joint3', 'arm_l_joint4', 'arm_l_joint5', 'arm_l_joint6', 'arm_l_joint7', 'gripper_l_joint1'],
points: [
{
positions: [-0.3, 0.2, -0.1, 0.0, -0.2, 0.1, 0.0, 0.02],
time_from_start: {sec: 3, nanosec: 0}
}
]
}"ai_worker_movej_controller subscribes to the raw trajectory topics and republishes filtered trajectories while preserving gripper values from the input message. ai_worker_bimanual_movej_controller uses the same raw trajectory topics, adds bimanual rigid-grasp filtering when enabled through grasp_capture_topic, and republishes filtered arm trajectories.
Launch the OMX follower controller:
ros2 launch cyclo_motion_controller_ros omx_controller.launch.py start_interactive_marker:=trueYou can switch OMX controllers via controller_type:
- default launch runs
omx_movel_controller_node controller_type:=movejrunsomx_movej_controller_nodecontroller_type:=movelrunsomx_movel_controller_node
When controller_type:=movel and start_interactive_marker:=true, omx_controller.launch.py starts one configurable interactive marker that publishes to marker_goal_topic.
Example movel command:
ros2 topic pub --once /omx_movel_controller/movel robotis_interfaces/msg/MoveL "{
pose: {
pose: {
position: {x: 0.20, y: 0.00, z: 0.18},
orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
}
},
time_from_start: {sec: 3, nanosec: 0}
}"movel interpolation duration is supplied per command via time_from_start.
Example movej command:
ros2 topic pub --once /omx_movej_controller/movej trajectory_msgs/msg/JointTrajectory "{
joint_names: ['joint1', 'joint2', 'joint3', 'joint4', 'joint5', 'gripper_joint_1'],
points: [
{
positions: [0.0, -0.5, 0.8, 0.0, 0.3, 0.02],
time_from_start: {sec: 3, nanosec: 0}
}
]
}"omx_movej_controller_node republishes a patched copy of the input movej message, so gripper commands included in the input remain in the published trajectory.
Launch the OMY follower controller:
ros2 launch cyclo_motion_controller_ros omy_controller.launch.py start_interactive_marker:=trueYou can switch OMY controllers via controller_type:
- default launch runs
omy_movel_controller_node controller_type:=movejrunsomy_movej_controller_nodecontroller_type:=movelrunsomy_movel_controller_node
When controller_type:=movel and start_interactive_marker:=true, omy_controller.launch.py starts one configurable interactive marker that publishes to marker_goal_topic.
Example movel command:
ros2 topic pub --once /omy_movel_controller/movel robotis_interfaces/msg/MoveL "{
pose: {
pose: {
position: {x: 0.30, y: -0.20, z: 0.5},
orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
}
},
time_from_start: {sec: 3, nanosec: 0}
}"movel interpolation duration is supplied per command via time_from_start.
Example movej command:
ros2 topic pub --once /omy_movej_controller/movej trajectory_msgs/msg/JointTrajectory "{
joint_names: ['joint1', 'joint2', 'joint3', 'joint4', 'joint5', 'joint6', 'rh_r1_joint'],
points: [
{
positions: [0.0, -0.5, 0.8, 0.0, 0.3, 0.0, 1.0],
time_from_start: {sec: 3, nanosec: 0}
}
]
}"omy_movej_controller_node also republishes a patched copy of the input movej message, preserving gripper values when the gripper joint is included in the input.
You can visualize the robot models used by the controllers with the launch files below.
Examples:
ros2 launch cyclo_motion_controller_models view_ffw_sg2_follower.launch.pyros2 launch cyclo_motion_controller_models view_omx_f.launch.pyros2 launch cyclo_motion_controller_models view_omy_f3m.launch.pyThis repository builds on several excellent open-source projects. The core motion controller implementations are derived from and informed by dyros_robot_controller, a project researched at Seoul National University. The retargeting modules in this repository are derived from and informed by dex-retargeting. The robot kinematics used throughout the controller stack are based on pinocchio, and the optimization layer uses osqp-eigen for the Eigen-based OSQP solver interface.