Teleoperation for the Franka FR3 as a single pure-Python ROS 2 (Humble) package, with
two switchable input sources — GELLO (joint-space leader) and Quest (VR hand,
Cartesian → IK). Both drive the stock fr3_arm_controller (JointTrajectoryController);
shared robot bring-up, gripper control, SpaceMouse, and error recovery.
| Node | What it is |
|---|---|
gello_publisher |
Reads the GELLO dynamixel chain on ttyUSB0, publishes /gello/joint_states + gripper width. |
gello_traj_bridge |
Repipes /gello/joint_states → /fr3_arm_controller/joint_trajectory: slow ease-in on startup (and after recovery), then responsive, uncapped tracking. |
gripper_client |
Franka Hand from the GELLO trigger via Grasp goals (no homing). Modes hold/toggle. |
quest_listener |
TCP server → /right_hand_pose + /right_hand_inputs. |
quest_franka_joints_commander |
Placo IK: Quest pose → /fr3_arm_controller/joint_trajectory (+ gripper grasp). |
franka_error_recovery |
Clears FR3 reflex errors + reloads controllers (SpaceMouse btn 22 / Quest btn). Always on. |
franka_move_to_start |
Smooth move to home pose (SpaceMouse btn 4 / Quest btn). |
get_offsets |
GELLO calibration helper. |
cd ~/ros2_ws
colcon build --packages-select fr3_teleop
source install/setup.bashExternal deps (already installed, not rosdep-able): placo (Quest IK), scipy.
ros2 launch fr3_teleop bringup.launch.py teleop:=gello # GELLO (default)
ros2 launch fr3_teleop bringup.launch.py teleop:=quest # Quest
ros2 launch fr3_teleop bringup.launch.py teleop:=rollout # policy playbackAll modes always start: robot + fr3_arm_controller, spacenav_node, franka_error_recovery,
franka_move_to_start.
- gello →
gello_publisher+gello_traj_bridge+gripper_client - quest →
quest_listener+quest_franka_joints_commander - rollout → no teleop input; a separately-run policy commands
/fr3_arm_controller/joint_trajectory- the gripper. Reset the arm to home with
move_to_start(SpaceMouse btn 4, or publishstd_msgs/Emptyon/franka_reset).
- the gripper. Reset the arm to home with
The robot comes up automatically and will move. Keep the e-stop within reach.
ros2 launch fr3_teleop robot.launch.py # robot + fr3_arm_controller + gripper driver
ros2 launch fr3_teleop gello.launch.py # (gello) or: ros2 launch fr3_teleop quest.launch.py
ros2 launch fr3_teleop gripper.launch.py # (gello) then run the bridge: ros2 run fr3_teleop gello_traj_bridgeros2 run fr3_teleop get_offsets --start-joints 0 0 0 -1.57 0 1.57 0 \
--joint-signs 1 1 1 -1 1 1 1 \
--port /dev/serial/by-id/usb-FTDI_USB__-__Serial_Converter_FTB8HM10-if00-port0gello.yaml— GELLO leader (com_port, joint_signs, assembly_offsets, gripper range, dynamixel).robot.yaml— FR3 (robot_ip 172.16.0.2, load_gripper, fake hardware).gripper.yaml— gripper behaviour shared by gello + quest:gripper_mode(hold/toggle), thresholds, grasp force/speed.invert_triggeris GELLO-only (Quest's trigger polarity is opposite and fixed in the node).controllers.yaml—fr3_arm_controller(P/D gains) @ 1 kHz +franka_robot_state_broadcasterwithlock_log_error:false(suppresses the RT-publisher-lock spam).
Bridge tuning (gello_traj_bridge params): sync_speed (0.5 rad/s — startup/recovery ease-in speed), sync_tol (0.05 rad), command_dt (0.1 s — tracking horizon), max_time (8 s). Tracking after sync is uncapped, so these don't add teleop lag.
/gello/joint_states— GELLO → bridge (gello mode)/right_hand_pose,/right_hand_inputs— Quest → commander (quest mode)/fr3_arm_controller/joint_trajectory— bridge/commander → arm/gripper/gripper_client/target_gripper_width_percent— GELLO → gripper (gello mode)/spacenav/joy— SpaceMouse (error recovery btn 22, move-to-start btn 4)/franka_recovered—franka_error_recovery→ bridge (re-sync/ease-in signal after a recovery)
- Requires the workspace's
franka_ros2(built);franka_hardwareprovides the/action_server/error_recoveryaction used byfranka_error_recovery. - Quest teleop needs the Meta Quest app streaming to
quest_listener's TCP port (set itshost/portto your machine).