This README explains how to run and use xarm_rlds.py to teleoperate the xArm7 with either a PlayStation controller (via pygame) or a VR controller (via an OculusReader). It also documents the controller button/axis mappings and recommended setup steps.
- Script:
xarm_rlds.py— main control loop that reads a controller (PlayStation or VR), sends velocity/position/gripper commands to an xArm7, and records camera frames and HDF5 dataset entries. - Two controller modes:
playstation— standard gamepad usingpygame.joystick.vr— Oculus/VR controller usingoculus_reader.reader.OculusReader.
- This script talks to a real robot by default (xArm IP is configured in the script). Keep an emergency stop or power cut within reach. Use
mode: simin config to avoid sending commands to real hardware. - Verify the
robot_ipand network connectivity before running. - Ensure cameras are safe and scene is clear before enabling robot movement.
Make sure the following Python packages are installed (your environment may already provide some):
- python3
- numpy
- scipy
- opencv-python (cv2)
- pygame
- h5py
- pyrealsense2 (only if using RealSense cameras)
- xArm SDK (
xarmpackage providingxarm.wrapper.XArmAPI) - oculus_reader (provides
OculusReader) — project-local or pip package depending on your setup
Install with pip (example):
pip install numpy scipy opencv-python pygame h5py
# pyrealsense2, xarm SDK and oculus_reader may require platform-specific install stepsBy default the script uses the internal default_config() defined in the file and runs in real mode. To run the file directly:
python3 xarm_rlds.pyIf you prefer to use command-line arguments, uncomment the config = parse_args() line in the if __name__ == "__main__": block and run with options. Example (after enabling CLI):
python3 xarm_rlds.py --controller_type playstation --robot_ip 192.168.1.198 --save_dir ~/workspace/xarm-datasetNotes:
--controller_typechoices:playstationorvr.save_diris where dataset/hdf5 and camera images will be saved.modecan be set tosimto avoid sending commands to the real arm (requires ROS2 sim node configuration).
There are two distinct task-related settings in xarm_rlds.py you may want to change:
task_name— a human-readable name used when saving datasets. It appears in the default save path and HDF5 filename.task_type— alters starting poses and behavior. Current code supports at least"pick_and_place"and"pouring"(the latter enables special pouring logic in the VR controller).
How to change the task:
-
Edit
default_config()insidexarm_rlds.py(quickest for development):- Change the
task_namestring to a descriptive name (e.g.,"stack_cups"or"place_block"). - Change
task_typeto either"pick_and_place"or"pouring"depending on the behavior you want.
Example:
config = { "controller_type": "playstation", "task_name": "stack_the_cups", "task_type": "pick_and_place", ... }
- Change the
-
Use command-line arguments (recommended if you enable CLI parsing):
- Uncomment
config = parse_args()in the__main__block. Then run:
python3 xarm_rlds.py --task_name "stack the cups" --task_type pick_and_place- Supported
--task_typevalues:pick_and_place,pouring.
- Uncomment
-
Adjust task-specific starting poses and behavior (optional, advanced):
xArm7GripperEnvdefinesarm_starting_pose_pick_and_placeandarm_starting_pose_pouring. If you need a different start pose for a task, edit these tuples inside the class. Example:
self.arm_starting_pose_pick_and_place = (x, y, z, roll, pitch, yaw)
- For complex task changes (new task types), you may also need to add logic where
task_typeis checked (e.g., inmove_to_starting_position()or in controller code) to implement task-specific behavior.
Save path and naming notes:
- The script builds a save path using
save_dirandtask_name. By defaultdefault_config()will also create a uniquetrajectoryNfolder if previous runs exist. Changingtask_namewill change the folder and HDF5 file naming.
- The
ControlSysteminstantiates thexArm7GripperEnv, the chosen controller wrapper, camera manager(s), and anHDF5Recorder. - In a loop (robot update rate ~15 Hz):
- Read/update current robot/cartesian pose and gripper position.
- Call controller.get_action(state_dict) -> returns (velocity, gripper_command, pour_flag).
- Send velocity to the robot with arm.vc_set_cartesian_velocity(...) and handle gripper latch commands.
- Record camera frames and append a dataset record.
The PlayStationController class (pygame) reads axes/buttons and converts them to a 6-DOF velocity + gripper command.
-
Axes
- Left joystick vertical (axis 1): controls X linear velocity (forward/back). A nonzero axis moves along robot X. The code uses negative sign to map forward/back correctly.
- Left joystick horizontal (axis 0): controls Y linear velocity (left/right).
- Right joystick vertical (axis 4): controls Z linear velocity (up/down).
- D-Pad (hat 0) X (left/right): toggles rotation axis 0 (roll) by +/-
rot_step_sizedegrees/s. - D-Pad (hat 0) Y (up/down): toggles rotation axis 1 (pitch) by +/-
rot_step_sizedegrees/s. - Left trigger (axis 2) pressed > 0.5: rotates yaw positively by
rot_step_sizedeg/s. - Right trigger (axis 5) pressed > 0.5: rotates yaw negatively by
rot_step_sizedeg/s.
-
Buttons
- X (pygame button 0): Open gripper ->
gripper_command = 'open'. - SQUARE (pygame button 3): Close gripper ->
gripper_command = 'close'.
- X (pygame button 0): Open gripper ->
Notes:
pos_step_size(mm/s) androt_step_size(deg/s) are configurable in thedefault_config()or via CLI (when enabled).- A joystick deadzone threshold (
threshold) is used to ignore small axis noise.
The VRController uses OculusReader() producing transformation matrices and a buttons mapping. The code expects certain button keys and uses them as follows.
Primary concepts:
- The VR pipeline computes a VR controller pose (3D position + quaternion) transformed into the robot/environment frame. The difference between the VR pose and a stored
vr_originis used to compute a target offset. - When movement is enabled and the origin is set, the code runs two PID controllers to convert pose error into linear and angular velocities. These velocities are scaled and sent to the robot.
Buttons and their effects (as implemented in the code):
- movement toggle (controller-specific): controller ID +
G(e.g.,RGorLG) — togglesmovement_enabled. When movement is enabled the controller drives the robot. - reset orientation (controller-specific): controller ID +
J(e.g.,RJorLJ) — when pressed, the controller's current orientation is used to redefine "forward"; this affects the VR-to-global transform. - Triggers (
rightTrigorleftTrig): used for gripper control. The numeric trigger value is read and interpreted:- trigger < 0.1 ->
close - trigger > 0.9 ->
open - otherwise ->
none
- trigger < 0.1 ->
- Face buttons
A,B,X,Y(global keys in VR controller state):A(when using the right controller): zeros rotational velocity (sets rot_vel_list = [0,0,0]).B(right controller): zeros linear velocity (sets lin_vel_mm = [0,0,0]).X(any controller): whentask_type == 'pouring'this setspour = Trueand zeroes velocities (used to start pouring action).Y(any controller): whentask_type == 'pouring'this setspour = Falseand zeroes velocities (used to stop pouring action).
Other useful behaviors and notes:
- Reset origin: when the script detects
reset_originit stores the current robot pose and the current VR controller pose as the origin pair; subsequent motions are offsets from that origin.reset_originis toggled when the movement button is pressed/released. - Reset orientation: pressing the
Jbutton will reset how the VR orientation maps into the robot frame (useful to re-zero yaw/forward direction). get_info()returns a small status dict showing which face buttons are active and whether movement is currently enabled.
Be aware: the actual dictionary keys coming from your OculusReader implementation may have slightly different names. The code expects keys like A, B, X, Y, rightTrig, leftTrig, and controller-specific RG/RJ or LG/LJ for the movement/reset mapping. If your OculusReader.get_transformations_and_buttons() returns different names, update the code to match those names.
- PID controllers for VR are created inside
VRControllerwith initial gains. Modifypos_pidandrot_pidparameters there if motion is too slow or oscillatory. max_lin_vel,max_rot_vel, andmax_gripper_velinVRControllerlimit commanded velocities.- Workspace bounds and starting poses are defined in
xArm7GripperEnvand can be adjusted to fit your robot setup.
- No joystick detected for PlayStation mode: ensure your controller is paired/connected and visible to the OS; test with
jstestor a small pygame joystick listing script. - VR mode not responding: ensure
OculusReaderis running properly and returns pose/button data. Check theoculus_readermodule and run its sample reader if available. - Robot does not move: verify
robot_ip, network routing, and xArm SDK connectivity. Check for exceptions printed to the console.
- Connect PlayStation controller or start your VR runtime.
- Edit
default_config()to changerobot_ip,save_dir,controller_type, or desired camera IDs (or enable CLI parsing). - Run
python3 xarm_rlds.py. - Use the controller mappings above to drive the arm and open/close the gripper. Press movement toggle/zeroing buttons to calibrate origin and orientation as needed.
xarm_rlds.pycontains the following key classes and functions:xArm7GripperEnv— robot wrapper and gripper latch logicPlayStationController— pygame-based controller mappingVRController— OculusReader-based controller mapping and PID controllersCameraManager,RealSenseRecorder,WebcamRecorder— camera recording utilitiesHDF5Recorder— dataset creation and saving logic
Last updated: December 15, 2025