Skip to content

Commit b3261fe

Browse files
authored
Merge pull request #17 from woxue/feat/add_bot_unitree_g1_dev
add_unitree_g1
2 parents bc24c61 + 7927068 commit b3261fe

File tree

9 files changed

+3036
-0
lines changed

9 files changed

+3036
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .configuration_g1_29 import G1_29_RobotConfig
2+
from .g1_29_robot import G1_29_Robot
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from dataclasses import dataclass, field
2+
from typing import List, Optional
3+
from lerobot.robots import RobotConfig
4+
5+
# 主控制器:控制 G1 双臂(14 DoF),提供 EE 位姿接口
6+
from robot_control.robot_arm import G1_29_ArmController
7+
8+
# 手部控制器
9+
from robot_control.robot_hand_unitree import Dex3_1_Controller
10+
11+
12+
@RobotConfig.register_subclass("g1_29")
13+
@dataclass
14+
class G1_29_RobotConfig(RobotConfig):
15+
robot_type: str = "g1_29"
16+
robot_name: str = "Unitree G1 Dual Arms"
17+
18+
# === 关节相关 ===
19+
joint_names: List[str] = field(default_factory=lambda: [
20+
# Left arm
21+
"left_shoulder_pitch_joint", "left_shoulder_roll_joint", "left_shoulder_yaw_joint",
22+
"left_elbow_joint", "left_wrist_roll_joint", "left_wrist_pitch_joint", "left_wrist_yaw_joint",
23+
# Right arm
24+
"right_shoulder_pitch_joint", "right_shoulder_roll_joint", "right_shoulder_yaw_joint",
25+
"right_elbow_joint", "right_wrist_roll_joint", "right_wrist_pitch_joint", "right_wrist_yaw_joint",
26+
# Left hand
27+
"left_thumb_flex_joint", "left_index_flex_joint", "left_middle_flex_joint",
28+
"left_ring_flex_joint", "left_pinky_flex_joint", "left_thumb_abd_joint", "left_wrist_yaw_joint",
29+
# Right hand
30+
"right_thumb_flex_joint", "right_index_flex_joint", "right_middle_flex_joint",
31+
"right_ring_flex_joint", "right_pinky_flex_joint", "right_thumb_abd_joint", "right_wrist_yaw_joint",
32+
])
33+
num_joints: int = 14 + 14
34+
35+
# 关节控制单位:全部为弧度(Unitree SDK 要求)
36+
joint_units: List[str] = field(default_factory=lambda: [
37+
# Arm: 14 × "radian"
38+
"radian", "radian", "radian", "radian", "radian", "radian", "radian",
39+
"radian", "radian", "radian", "radian", "radian", "radian", "radian",
40+
# Hand: 14 × "radian" (left 7 + right 7)
41+
"radian", "radian", "radian", "radian", "radian", "radian", "radian",
42+
"radian", "radian", "radian", "radian", "radian", "radian", "radian",
43+
])
44+
45+
46+
# === 末端执行器相关 ===
47+
pose_units: List[str] = field(default_factory=lambda: [
48+
"m", "m", "m", "radian", "radian", "radian", # left EE (6D)
49+
"m", "m", "m", "radian", "radian", "radian" # right EE (6D)
50+
])
51+
52+
sdk_name: str = "unitree_sdk2py"
53+
control_frequency_hz: int = 100
54+
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
from multiprocessing import Array, Lock
2+
import numpy as np
3+
import logging
4+
from robot_control.robot_arm import G1_29_ArmController, G1_23_ArmController, H1_2_ArmController, H1_ArmController
5+
from robot_control.robot_arm_ik import G1_29_ArmIK, G1_23_ArmIK, H1_2_ArmIK, H1_ArmIK
6+
from robot_control.robot_hand_inspire_ftp import Inspire_Controller
7+
from robot_control.robot_arm_ik_fk import G1_29_ArmIK_fk
8+
from ..base_robot import BaseRobot
9+
# for simulation
10+
from unitree_sdk2py.core.channel import ChannelPublisher
11+
from unitree_sdk2py.idl.std_msgs.msg.dds_ import String_
12+
import pinocchio as pin
13+
from robot_control.robot_hand_dds import DexControl
14+
15+
from pinocchio import casadi as cpin
16+
from pinocchio.visualize import MeshcatVisualizer
17+
logger = logging.getLogger(__name__)
18+
19+
20+
class G1_29_Robot(BaseRobot):
21+
def __init__(self, config=None):
22+
self.config = config
23+
self.arm_controller = None
24+
self.hand_controller = None
25+
self.is_connected = False
26+
self.arm_ik_fk = G1_29_ArmIK_fk()
27+
28+
self.left_hand_pos_array = Array('d', 75, lock = True) # [input]
29+
self.right_hand_pos_array = Array('d', 75, lock = True) # [input]
30+
self.dual_hand_data_lock = Lock()
31+
self.dual_hand_state_array = Array('d', 12, lock = False) # [output] current left, right hand state(12) data.
32+
self.dual_hand_action_array = Array('d', 12, lock = False) # [output] current left, right hand action(12) data.
33+
self.dc = DexControl(interfacenet="enp129s0")
34+
def _check_dependencys(self):
35+
"""
36+
检查运行 G1 + Inspire 所需的依赖项
37+
"""
38+
logger.info("Checking dependencies for G1_29_Robot...")
39+
40+
# 检查 Unitree SDK 是否可导入
41+
try:
42+
import unitree_sdk2py
43+
from unitree_sdk2py.core.channel import ChannelFactoryInitialize
44+
except ImportError as e:
45+
raise RuntimeError("Unitree SDK 2.0 not installed or not in PYTHONPATH.") from e
46+
47+
48+
def _connect_arm(self, motion_mode=False, simulation_mode=False):
49+
"""
50+
连接 G1 双臂控制器。
51+
"""
52+
motion_mode = True
53+
logger.info("Connecting to G1 arm controller...")
54+
try:
55+
self.arm_controller = G1_29_ArmController(
56+
motion_mode=motion_mode,
57+
simulation_mode=simulation_mode
58+
)
59+
logger.info("G1 arm controller connected successfully.")
60+
except Exception as e:
61+
raise RuntimeError(f"Failed to connect arm controller: {e}") from e
62+
63+
def _connect_hand(self, left_hand_array_in, right_hand_array_in,
64+
dual_hand_data_lock=None,
65+
dual_hand_state_array_out=None,
66+
dual_hand_action_array_out=None,
67+
simulation_mode=False):
68+
"""
69+
连接 Inspire 手部控制器。
70+
"""
71+
logger.info("Connecting to Inspire hand controller...")
72+
try:
73+
self.hand_controller = Inspire_Controller(
74+
left_hand_array_in=left_hand_array_in,
75+
right_hand_array_in=right_hand_array_in,
76+
dual_hand_data_lock=dual_hand_data_lock,
77+
dual_hand_state_array_out=dual_hand_state_array_out,
78+
dual_hand_action_array_out=dual_hand_action_array_out,
79+
simulation_mode=simulation_mode
80+
)
81+
logger.info("Inspire hand controller connected successfully.")
82+
except Exception as e:
83+
raise RuntimeError(f"Failed to connect hand controller: {e}") from e
84+
85+
def _connect(self,
86+
motion_mode: bool = False,
87+
simulation_mode: bool = False,
88+
left_hand_array_in=None,
89+
right_hand_array_in=None,
90+
dual_hand_data_lock=None,
91+
dual_hand_state_array_out=None,
92+
dual_hand_action_array_out=None,
93+
) -> bool:
94+
"""
95+
完整连接流程:
96+
1. 检查依赖
97+
2. 连接手
98+
3. 连接臂
99+
"""
100+
try:
101+
self._check_dependencys()
102+
103+
# 连接手部
104+
if left_hand_array_in is None or right_hand_array_in is None:
105+
logger.warning("Hand arrays not provided. Skipping hand connection.")
106+
else:
107+
self._connect_hand(
108+
left_hand_array_in=left_hand_array_in,
109+
right_hand_array_in=right_hand_array_in,
110+
dual_hand_data_lock=dual_hand_data_lock,
111+
dual_hand_state_array_out=dual_hand_state_array_out,
112+
dual_hand_action_array_out=dual_hand_action_array_out,
113+
simulation_mode=simulation_mode
114+
)
115+
116+
# 连接手臂
117+
self._connect_arm(motion_mode=motion_mode, simulation_mode=simulation_mode)
118+
119+
self.is_connected = True
120+
logger.info("G1_29_Robot fully connected.")
121+
return True
122+
123+
except Exception as e:
124+
logger.error(f"Failed to connect G1_29_Robot: {e}")
125+
self.disconnect()
126+
return False
127+
128+
def _disconnect_arm(self):
129+
if self.arm_controller is not None:
130+
self.arm_controller.ctrl_dual_arm_go_home()
131+
logger.info("Arm controller disconnected.")
132+
133+
def _disconnect_hand(self):
134+
if self.hand_controller is not None:
135+
pass
136+
137+
def _disconnect(self):
138+
"""断开所有连接"""
139+
if not self.is_connected:
140+
return
141+
142+
self._disconnect_hand()
143+
self._disconnect_arm()
144+
145+
self.arm_controller = None
146+
self.hand_controller = None
147+
self.is_connected = False
148+
logger.info("G1_29_Robot disconnected.")
149+
150+
def _get_joint_state(self):
151+
if not self.is_connected or self.arm_controller is None:
152+
return np.zeros(14)
153+
q = self.arm_controller.get_current_dual_arm_q()
154+
return q
155+
156+
def _set_joint_state(self, joint_state: np.ndarray):
157+
"""
158+
设置机器人全部关节状态(臂 + 手),单位:弧度(按 joint_units 定义)。
159+
160+
Args:
161+
joint_state (np.ndarray): shape=(28,), order:
162+
[L_arm(7), R_arm(7), L_hand(7), R_hand(7)]
163+
单位全部为 rad(Unitree SDK & DexControl 要求)
164+
165+
Note:
166+
- 臂部:调用 self.arm_controller.ctrl_dual_arm(q, tau=0)
167+
- 手部:调用 self.dc.control_process(left_q, right_q)
168+
"""
169+
if not self.is_connected or self.arm_controller is None:
170+
logger.warning(" Robot not connected. Skipping _set_joint_state.")
171+
return
172+
173+
# 校验总维度
174+
if not isinstance(joint_state, np.ndarray) or joint_state.shape != (28,):
175+
logger.error(f"_set_joint_state: Expected (28,) array, got {joint_state.shape}")
176+
return
177+
178+
# 按 joint_names 顺序(你已定义:臂14 + 左手7 + 右手7)
179+
ARM_START, ARM_END = 0, 14
180+
L_HAND_START, L_HAND_END = 14, 21
181+
R_HAND_START, R_HAND_END = 21, 28
182+
183+
q_arm = joint_state[ARM_START:ARM_END] # (14,)
184+
q_lhand = joint_state[L_HAND_START:L_HAND_END] # (7,)
185+
q_rhand = joint_state[R_HAND_START:R_HAND_END] # (7,)
186+
187+
# 臂部控制(带平滑)
188+
try:
189+
current_arm_q = self.arm_controller.get_current_dual_arm_q()
190+
velocity_limit = self.arm_controller.arm_velocity_limit
191+
control_dt = self.arm_controller.control_dt
192+
delta = q_arm - current_arm_q
193+
motion_scale = np.max(np.abs(delta)) / (velocity_limit * control_dt)
194+
cliped_arm_q = current_arm_q + delta / max(motion_scale, 1.0)
195+
self.arm_controller.ctrl_dual_arm(cliped_arm_q, np.zeros_like(cliped_arm_q))
196+
except Exception as e:
197+
logger.error(f" Arm control failed: {e}")
198+
199+
# 手部控制(DexControl)
200+
if self.dc is not None:
201+
try:
202+
self.dc.control_process(q_lhand, q_rhand)
203+
logger.debug(" Hand control sent.")
204+
except Exception as e:
205+
logger.error(f" Hand control failed: {e}")
206+
else:
207+
logger.warning(" DexControl not initialized — skipping hand control.")
208+
209+
logger.info(" _set_joint_state: Arm + hand commands dispatched.")
210+
211+
def _get_ee_state(self):
212+
"""
213+
获取当前末端执行器状态(12维)。
214+
215+
Returns:
216+
np.ndarray: shape (12,)
217+
[L_x, L_y, L_z, L_roll, L_pitch, L_yaw,
218+
R_x, R_y, R_z, R_roll, R_pitch, R_yaw]
219+
单位: 位置 -> "m", 旋转 -> "radian"
220+
"""
221+
# 获取当前关节状态 (14D)
222+
q, _ = self._get_joint_state()
223+
224+
# 调用 IK 求解器的 FK 计算末端位姿 (4x4 齐次矩阵)
225+
L_pose, R_pose = self.arm_ik_fk.forward_kinematics(q)
226+
227+
# 提取位置 (3,)
228+
L_pos = L_pose[:3, 3]
229+
R_pos = R_pose[:3, 3]
230+
231+
# 提取旋转 → 转为欧拉角 (roll, pitch, yaw) in radians
232+
L_rpy = pin.rpy.matrixToRpy(L_pose[:3, :3]) # shape (3,) → [roll, pitch, yaw]
233+
R_rpy = pin.rpy.matrixToRpy(R_pose[:3, :3])
234+
235+
# 拼接成 12D 向量
236+
ee_state = np.concatenate([L_pos, L_rpy, R_pos, R_rpy])
237+
return ee_state
238+
239+
240+
def _set_ee_state(self, ee_state: np.ndarray):
241+
"""
242+
设置末端执行器目标位姿。
243+
244+
Args:
245+
ee_state (np.ndarray): shape (12,)
246+
[L_x, L_y, L_z, L_roll, L_pitch, L_yaw,
247+
R_x, R_y, R_z, R_roll, R_pitch, R_yaw]
248+
单位: 位置 -> meters, 旋转 -> radians (RPY, xyz order)
249+
"""
250+
if len(ee_state) != 12:
251+
raise ValueError(f"Expected ee_state of length 12, got {len(ee_state)}")
252+
253+
# --- 解析左手目标 ---
254+
L_pos = ee_state[0:3]
255+
L_rpy = ee_state[3:6]
256+
if self._is_rpy_singular(L_rpy):
257+
logger.info(f"[L] RPY near singularity (pitch={np.degrees(L_rpy[1]):.1f}°)")
258+
259+
L_rot = pin.rpy.rpyToMatrix(L_rpy[0], L_rpy[1], L_rpy[2])
260+
L_tf_se3 = pin.SE3(L_rot, L_pos)
261+
L_tf = L_tf_se3.homogeneous # 👈 转为 (4, 4) numpy.ndarray
262+
263+
# --- 解析右手目标 ---
264+
R_pos = ee_state[6:9]
265+
R_rpy = ee_state[9:12]
266+
if self._is_rpy_singular(R_rpy):
267+
logger.info(f"[R] RPY near singularity (pitch={np.degrees(R_rpy[1]):.1f}°)")
268+
269+
R_rot = pin.rpy.rpyToMatrix(R_rpy[0], R_rpy[1], R_rpy[2])
270+
R_tf_se3 = pin.SE3(R_rot, R_pos)
271+
R_tf = R_tf_se3.homogeneous # 👈 转为 (4, 4) numpy.ndarray
272+
273+
# --- 获取当前状态 ---
274+
current_q = self.arm_controller.get_current_dual_arm_q()
275+
current_dq = self.arm_controller.get_current_dual_arm_dq()
276+
277+
try:
278+
sol_q, sol_tauff = self.arm_ik_fk.solve_ik(
279+
left_wrist=L_tf,
280+
right_wrist=R_tf,
281+
current_lr_arm_motor_q=current_q,
282+
current_lr_arm_motor_dq=current_dq
283+
)
284+
self.arm_controller.ctrl_dual_arm(sol_q, sol_tauff)
285+
except Exception as e:
286+
logger.error(
287+
f"[_set_ee_state] IK failed for L{L_pos} R{R_pos}:\n"
288+
f" L_rpy={np.degrees(L_rpy)}°, R_rpy={np.degrees(R_rpy)}°\n"
289+
f" Error: {e}"
290+
)
291+
raise
292+
293+
def _is_rpy_singular(self, rpy):
294+
pitch = rpy[1]
295+
return abs(abs(pitch) - np.pi / 2) < 0.05

0 commit comments

Comments
 (0)