From aa34e4010ba04e40f2633a71a62afce7a87ee204 Mon Sep 17 00:00:00 2001 From: SuLvXiangXin Date: Thu, 2 Jul 2026 10:54:53 +0800 Subject: [PATCH 1/2] Add R1 SDK support --- .../r1/high_level/r1_loco_client_example.py | 89 ++++++ .../r1/low_level/r1_ankle_swing_example.py | 278 ++++++++++++++++++ unitree_sdk2py/__init__.py | 3 +- unitree_sdk2py/r1/__init__.py | 1 + unitree_sdk2py/r1/loco/__init__.py | 1 + unitree_sdk2py/r1/loco/r1_loco_api.py | 19 ++ unitree_sdk2py/r1/loco/r1_loco_client.py | 72 +++++ 7 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 example/r1/high_level/r1_loco_client_example.py create mode 100644 example/r1/low_level/r1_ankle_swing_example.py create mode 100644 unitree_sdk2py/r1/__init__.py create mode 100644 unitree_sdk2py/r1/loco/__init__.py create mode 100644 unitree_sdk2py/r1/loco/r1_loco_api.py create mode 100644 unitree_sdk2py/r1/loco/r1_loco_client.py diff --git a/example/r1/high_level/r1_loco_client_example.py b/example/r1/high_level/r1_loco_client_example.py new file mode 100644 index 00000000..b98dc1f8 --- /dev/null +++ b/example/r1/high_level/r1_loco_client_example.py @@ -0,0 +1,89 @@ +import sys + +from unitree_sdk2py.core.channel import ChannelFactoryInitialize +from unitree_sdk2py.r1.loco.r1_loco_client import LocoClient + + +def string_to_float_list(value: str): + return [float(item) for item in value.replace(",", " ").split()] + + +def parse_args(argv): + args = {"network_interface": "lo"} + for arg in argv[1:]: + if arg.startswith("--"): + key_value = arg[2:].split("=", 1) + key = key_value[0] + value = key_value[1] if len(key_value) == 2 else "" + args[key] = value.strip('"') + elif args["network_interface"] == "lo": + args["network_interface"] = arg + else: + raise ValueError(f"unexpected argument: {arg}") + return args + + +def parse_bool(value: str): + if value == "true": + return True + if value == "false": + return False + raise ValueError(f"invalid bool argument: {value}") + + +def main(): + args = parse_args(sys.argv) + ChannelFactoryInitialize(0, args["network_interface"]) + + client = LocoClient() + client.Init() + client.SetTimeout(10.0) + + for key, value in args.items(): + if key == "network_interface": + continue + + print(f"Processing command: [{key}] with param: [{value}] ...") + + if key == "get_fsm_id": + code, fsm_id = client.GetFsmId() + print(f"ret: {code}, current fsm_id: {fsm_id}") + elif key == "get_fsm_mode": + code, fsm_mode = client.GetFsmMode() + print(f"ret: {code}, current fsm_mode: {fsm_mode}") + elif key == "set_fsm_id": + print(f"ret: {client.SetFsmId(int(value))}") + elif key == "set_velocity": + param = string_to_float_list(value) + if len(param) == 3: + param.append(1.0) + if len(param) != 4: + raise ValueError(f"invalid param size for set_velocity: {len(param)}") + print(f"ret: {client.SetVelocity(param[0], param[1], param[2], param[3])}") + elif key == "damp": + print(f"ret: {client.Damp()}") + elif key == "start": + print(f"ret: {client.Start()}") + elif key == "stand_up": + print(f"ret: {client.StandUp()}") + elif key == "zero_torque": + print(f"ret: {client.ZeroTorque()}") + elif key == "stop_move": + print(f"ret: {client.StopMove()}") + elif key == "switch_move_mode": + print(f"ret: {client.SwitchMoveMode(parse_bool(value))}") + elif key == "move": + param = string_to_float_list(value) + if len(param) != 3: + raise ValueError(f"invalid param size for move: {len(param)}") + print(f"ret: {client.Move(param[0], param[1], param[2])}") + elif key == "set_speed_mode": + print(f"ret: {client.SetSpeedMode(int(value))}") + else: + raise ValueError(f"unknown command: {key}") + + print("Done!") + + +if __name__ == "__main__": + main() diff --git a/example/r1/low_level/r1_ankle_swing_example.py b/example/r1/low_level/r1_ankle_swing_example.py new file mode 100644 index 00000000..76654076 --- /dev/null +++ b/example/r1/low_level/r1_ankle_swing_example.py @@ -0,0 +1,278 @@ +import struct +import sys +import time + +import numpy as np + +from unitree_sdk2py.comm.motion_switcher.motion_switcher_client import MotionSwitcherClient +from unitree_sdk2py.core.channel import ChannelFactoryInitialize, ChannelPublisher, ChannelSubscriber +from unitree_sdk2py.idl.default import unitree_hg_msg_dds__LowCmd_ +from unitree_sdk2py.idl.unitree_hg.msg.dds_ import IMUState_, LowCmd_, LowState_ +from unitree_sdk2py.utils.crc import CRC +from unitree_sdk2py.utils.thread import RecurrentThread + +HG_CMD_TOPIC = "rt/lowcmd" +HG_IMU_TORSO = "rt/secondary_imu" +HG_STATE_TOPIC = "rt/lowstate" + +R1_NUM_MOTOR = 26 + +Kp = [ + 200, 200, 200, 200, 200, 200, + 200, 200, 200, 200, 200, 200, + 300, 300, + 100, 100, 100, 100, 50, + 100, 100, 100, 100, 50, + 50, 10, +] + +Kd = [ + 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, + 5, 5, + 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, + 2, 0.1, +] + +JOINT_IDX_IN_IDL = [ + 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, + 12, 13, + 15, 16, 17, 18, 19, + 22, 23, 24, 25, 26, + 29, 30, +] + + +class Mode: + PR = 0 + AB = 1 + + +class R1JointIndex: + LeftHipPitch = 0 + LeftHipRoll = 1 + LeftHipYaw = 2 + LeftKnee = 3 + LeftAnklePitch = 4 + LeftAnkleB = 4 + LeftAnkleRoll = 5 + LeftAnkleA = 5 + RightHipPitch = 6 + RightHipRoll = 7 + RightHipYaw = 8 + RightKnee = 9 + RightAnklePitch = 10 + RightAnkleB = 10 + RightAnkleRoll = 11 + RightAnkleA = 11 + WaistRoll = 12 + WaistYaw = 13 + LeftShoulderPitch = 14 + LeftShoulderRoll = 15 + LeftShoulderYaw = 16 + LeftElbow = 17 + LeftWristRoll = 18 + RightShoulderPitch = 19 + RightShoulderRoll = 20 + RightShoulderYaw = 21 + RightElbow = 22 + RightWristRoll = 23 + HEAD_PITCH = 24 + HEAD_YAW = 25 + + +class Custom: + def __init__(self): + self.time_ = 0.0 + self.control_dt_ = 0.002 + self.duration_ = 3.0 + self.counter_ = 0 + self.mode_pr_ = Mode.PR + self.mode_machine_ = 0 + self.low_cmd = unitree_hg_msg_dds__LowCmd_() + self.low_state = None + self.motor_q = [0.0] * R1_NUM_MOTOR + self.motor_dq = [0.0] * R1_NUM_MOTOR + self.gamepad_A = False + self.gamepad_B = False + self.gamepad_X = False + self.gamepad_Y = False + self.crc = CRC() + + def Init(self): + self.msc = MotionSwitcherClient() + self.msc.SetTimeout(5.0) + self.msc.Init() + + status, result = self.msc.CheckMode() + while result and result.get("name"): + code, _ = self.msc.ReleaseMode() + if code != 0: + print("Failed to switch to Release Mode") + time.sleep(5) + status, result = self.msc.CheckMode() + + self.lowcmd_publisher_ = ChannelPublisher(HG_CMD_TOPIC, LowCmd_) + self.lowcmd_publisher_.Init() + + self.lowstate_subscriber = ChannelSubscriber(HG_STATE_TOPIC, LowState_) + self.lowstate_subscriber.Init(self.LowStateHandler, 1) + + self.imutorso_subscriber = ChannelSubscriber(HG_IMU_TORSO, IMUState_) + self.imutorso_subscriber.Init(self.ImuTorsoHandler, 1) + + def Start(self): + self.lowCmdWriteThreadPtr = RecurrentThread( + interval=self.control_dt_, target=self.LowCmdWrite, name="r1_lowcmd" + ) + self.lowCmdWriteThreadPtr.Start() + + def _update_gamepad(self, wireless_remote): + btn_value = struct.unpack_from(" Date: Thu, 2 Jul 2026 11:08:18 +0800 Subject: [PATCH 2/2] Add R1 camera examples --- example/r1/front_camera/camera_opencv.py | 35 ++++++++++++++++++++++++ example/r1/front_camera/capture_image.py | 30 ++++++++++++++++++++ unitree_sdk2py/r1/video/__init__.py | 0 unitree_sdk2py/r1/video/video_api.py | 9 ++++++ unitree_sdk2py/r1/video/video_client.py | 15 ++++++++++ 5 files changed, 89 insertions(+) create mode 100644 example/r1/front_camera/camera_opencv.py create mode 100644 example/r1/front_camera/capture_image.py create mode 100644 unitree_sdk2py/r1/video/__init__.py create mode 100644 unitree_sdk2py/r1/video/video_api.py create mode 100644 unitree_sdk2py/r1/video/video_client.py diff --git a/example/r1/front_camera/camera_opencv.py b/example/r1/front_camera/camera_opencv.py new file mode 100644 index 00000000..542d2d6a --- /dev/null +++ b/example/r1/front_camera/camera_opencv.py @@ -0,0 +1,35 @@ +from unitree_sdk2py.core.channel import ChannelFactoryInitialize +from unitree_sdk2py.r1.video.video_client import VideoClient +import cv2 +import numpy as np +import sys + + +if __name__ == "__main__": + if len(sys.argv) > 1: + ChannelFactoryInitialize(0, sys.argv[1]) + else: + ChannelFactoryInitialize(0) + + client = VideoClient() + client.SetTimeout(3.0) + client.Init() + + code, data = client.GetImageSample() + + while code == 0: + code, data = client.GetImageSample() + + image_data = np.frombuffer(bytes(data), dtype=np.uint8) + image = cv2.imdecode(image_data, cv2.IMREAD_COLOR) + + cv2.imshow("r1_front_camera", image) + if cv2.waitKey(20) == 27: + break + + if code != 0: + print("Get image sample error. code:", code) + else: + cv2.imwrite("r1_front_image.jpg", image) + + cv2.destroyWindow("r1_front_camera") diff --git a/example/r1/front_camera/capture_image.py b/example/r1/front_camera/capture_image.py new file mode 100644 index 00000000..6402d72a --- /dev/null +++ b/example/r1/front_camera/capture_image.py @@ -0,0 +1,30 @@ +import time +import sys + +from unitree_sdk2py.core.channel import ChannelFactoryInitialize +from unitree_sdk2py.r1.video.video_client import VideoClient + + +if __name__ == "__main__": + if len(sys.argv) > 1: + ChannelFactoryInitialize(0, sys.argv[1]) + else: + ChannelFactoryInitialize(0) + + client = VideoClient() + client.SetTimeout(3.0) + client.Init() + + print("##################GetImageSample###################") + code, data = client.GetImageSample() + + if code != 0: + print("get image sample error. code:", code) + else: + image_name = "./r1_front_image.jpg" + print("ImageName:", image_name) + + with open(image_name, "+wb") as f: + f.write(bytes(data)) + + time.sleep(1) diff --git a/unitree_sdk2py/r1/video/__init__.py b/unitree_sdk2py/r1/video/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/unitree_sdk2py/r1/video/video_api.py b/unitree_sdk2py/r1/video/video_api.py new file mode 100644 index 00000000..83e6134a --- /dev/null +++ b/unitree_sdk2py/r1/video/video_api.py @@ -0,0 +1,9 @@ +""" +R1 camera video service constants. +""" + +VIDEO_SERVICE_NAME = "videohub" +VIDEO_API_VERSION = "1.0.0.1" + +VIDEO_API_ID_GETIMAGESAMPLE = 1001 + diff --git a/unitree_sdk2py/r1/video/video_client.py b/unitree_sdk2py/r1/video/video_client.py new file mode 100644 index 00000000..4c52edd4 --- /dev/null +++ b/unitree_sdk2py/r1/video/video_client.py @@ -0,0 +1,15 @@ +from ...rpc.client import Client +from .video_api import * + + +class VideoClient(Client): + def __init__(self): + super().__init__(VIDEO_SERVICE_NAME, False) + + def Init(self): + self._SetApiVerson(VIDEO_API_VERSION) + self._RegistApi(VIDEO_API_ID_GETIMAGESAMPLE, 0) + + def GetImageSample(self): + return self._CallBinary(VIDEO_API_ID_GETIMAGESAMPLE, []) +