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.pyYou can switch AI Worker controllers via controller_type:
- default launch runs
ai_worker_movel_controller_node controller_type:=movelrunsai_worker_movel_controller_nodecontroller_type:=movejrunsai_worker_movej_controller_nodecontroller_type:=vrrunsvr_controller_nodeandreference_checker_node; whenhand:=true, it also runsretargeting_teleopcontroller_type:=leaderrunsleader_controller_nodetogether withvr_controller_node
In vr mode, arm retargeting is enabled by default (arm:=true) and starts arm_retargeting_teleop to map tracked arm motion into follower arm commands.
Example launch commands:
ros2 launch cyclo_motion_controller_ros ai_worker_controller.launch.py controller_type:=movel start_interactive_marker:=trueros2 launch cyclo_motion_controller_ros ai_worker_controller.launch.py controller_type:=movejros2 launch cyclo_motion_controller_ros ai_worker_controller.launch.py controller_type:=vrros2 launch cyclo_motion_controller_ros ai_worker_controller.launch.py controller_type:=vr hand:=trueros2 launch cyclo_motion_controller_ros ai_worker_controller.launch.py controller_type:=vr arm:=falseros2 launch cyclo_motion_controller_ros ai_worker_controller.launch.py controller_type:=leaderros2 launch cyclo_motion_controller_ros ai_worker_controller.launch.py disable_gripper_collisions:=trueWhen 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
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.
In vr mode, the hand launch argument enables hand teleoperation through the retargeting algorithm. It defaults to false.
The arm launch argument controls arm retargeting in vr mode and defaults to true.
Example movel commands:
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}
}"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.
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.