Files
lerobot_piper/piper_scripts/piper_demo_endpose_joint.py
2025-05-07 11:29:56 +08:00

416 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*-coding:utf8-*-
from typing import Optional
import time
from piper_sdk import *
import pygame
import threading
from typing import Dict
def enable_fun(piper:C_PiperInterface_V2):
'''
使能机械臂并检测使能状态,尝试5s,如果使能超时则退出程序
'''
enable_flag = False
# 设置超时时间(秒)
timeout = 5
# 记录进入循环前的时间
start_time = time.time()
elapsed_time_flag = False
while not (enable_flag):
elapsed_time = time.time() - start_time
print("--------------------")
enable_flag = piper.GetArmLowSpdInfoMsgs().motor_1.foc_status.driver_enable_status and \
piper.GetArmLowSpdInfoMsgs().motor_2.foc_status.driver_enable_status and \
piper.GetArmLowSpdInfoMsgs().motor_3.foc_status.driver_enable_status and \
piper.GetArmLowSpdInfoMsgs().motor_4.foc_status.driver_enable_status and \
piper.GetArmLowSpdInfoMsgs().motor_5.foc_status.driver_enable_status and \
piper.GetArmLowSpdInfoMsgs().motor_6.foc_status.driver_enable_status
print("使能状态:",enable_flag)
piper.EnableArm(7)
piper.GripperCtrl(0,1000,0x01, 0)
print("--------------------")
# 检查是否超过超时时间
if elapsed_time > timeout:
print("超时....")
elapsed_time_flag = True
enable_flag = True
break
time.sleep(1)
pass
if(elapsed_time_flag):
print("程序自动使能超时,退出程序")
exit(0)
class UnifiedArmController:
def __init__(self):
# 初始化pygame和手柄
pygame.init()
pygame.joystick.init()
# 检查是否有连接的手柄
if pygame.joystick.get_count() == 0:
raise Exception("未检测到手柄")
# 初始化手柄
self.joystick = pygame.joystick.Joystick(0)
self.joystick.init()
# 控制模式标志 - True为末端位姿控制False为关节控制
self.end_pose_mode = False
# 摇杆死区
self.deadzone = 0.15
# 精细控制模式
self.fine_control_mode = False
# 初始化关节状态
self.joints = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] # 6个关节
self.joint_speeds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] # 6个关节的速度
# 关节弧度限制
self.joint_limits = [
(-92000 / 57324.840764, 92000 / 57324.840764), # joint1
( 0 / 57324.840764, 120000 / 57324.840764), # joint2
(-80000 / 57324.840764, 0 / 57324.840764), # joint3
(-90000 / 57324.840764, 90000 / 57324.840764), # joint4
(-65000 / 57324.840764, 65000 / 57324.840764), # joint5
(-90000 / 57324.840764, 90000 / 57324.840764) # joint6
]
# 初始化末端姿态 [X, Y, Z, RX, RY, RZ]
self.pose = [0.056127, 0, 0.213266, 0, 84.999, 0]
self.pose_speeds = [0.0] * 6
# 末端位姿限制
self.pose_limits = [
(-0.6, 0.6), # X (m)
(-0.6, 0.6), # Y (m)
(0.05, 0.6), # Z (m) - 设置最小高度防止碰撞
(-180, 180), # RX (deg)
(-180, 180), # RY (deg)
(-180, 180) # RZ (deg)
]
# 控制参数
self.linear_step = 0.0015 # 线性移动步长(m)
self.angular_step = 0.05 # 角度步长(deg)
self.joint_step = 0.015 # 关节步长(rad)
# 夹爪状态和速度
self.gripper = 0.0
self.gripper_speed = 0.0
# 启动更新线程
self.running = True
self.thread = threading.Thread(target=self.update_controller)
self.thread.start()
print("机械臂统一控制器已启动")
print("按下OPTIONS(Start)切换控制模式")
print("当前模式:", "末端位姿控制" if self.end_pose_mode else "关节控制")
def _apply_nonlinear_mapping(self, value):
"""应用非线性映射以提高控制精度"""
# 保持符号,但对数值应用平方映射以提高精度
sign = 1 if value >= 0 else -1
return sign * (abs(value) ** 2)
def _normalize_angle(self, angle):
"""将角度归一化到[-180, 180]范围内"""
while angle > 180:
angle -= 360
while angle < -180:
angle += 360
return angle
def update_controller(self):
while self.running:
try:
pygame.event.pump()
except Exception as e:
print(f"控制器错误: {e}")
self.stop()
continue
# 检查控制模式切换 (使用左摇杆按钮)
if self.joystick.get_button(9): # 左摇杆按钮
self.end_pose_mode = not self.end_pose_mode
print(f"切换到{'末端位姿控制' if self.end_pose_mode else '关节控制'}模式")
time.sleep(0.3) # 防止多次触发
# 检查精细控制模式切换 (使用L3按钮)
if self.joystick.get_button(10): # L3按钮
self.fine_control_mode = not self.fine_control_mode
print(f"切换到{'精细' if self.fine_control_mode else '普通'}控制模式")
time.sleep(0.3) # 防止多次触发
# 检查重置按钮 (7号按钮通常是Start按钮)
if self.joystick.get_button(7): # Start按钮
print("重置机械臂到0位...")
# 重置关节和位姿
self.joints = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
self.joint_speeds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
self.gripper = 0.0
self.gripper_speed = 0.0
# 临时切换到关节控制模式
self.end_pose_mode = False
print("机械臂已重置到0位")
time.sleep(0.3) # 防止多次触发
# 根据控制模式获取输入并更新状态
if self.end_pose_mode:
self.update_end_pose()
else:
self.update_joints()
# 夹爪控制(圈/叉)- 两种模式下都保持一致
circle = self.joystick.get_button(1) # 夹爪开
cross = self.joystick.get_button(0) # 夹爪关
self.gripper_speed = 0.01 if circle else (-0.01 if cross else 0.0)
# 更新夹爪
self.gripper += self.gripper_speed
self.gripper = max(0.0, min(0.08, self.gripper))
time.sleep(0.02)
def update_end_pose(self):
"""更新末端位姿控制"""
# 根据控制模式调整步长
current_linear_step = self.linear_step * (0.1 if self.fine_control_mode else 1.0)
current_angular_step = self.angular_step * (0.1 if self.fine_control_mode else 1.0)
# 方向键控制XY
hat = self.joystick.get_hat(0)
hat_up = hat[1] == 1 # Y+
hat_down = hat[1] == -1 # Y-
hat_left = hat[0] == -1 # X-
hat_right = hat[0] == 1 # X+
# 右摇杆控制Z
right_y = -self.joystick.get_axis(4) # Z控制取反使向上为正
# 左摇杆控制RZ
left_y = -self.joystick.get_axis(1) # RZ控制取反使向上为正
# 应用死区
right_y = 0.0 if abs(right_y) < self.deadzone else right_y
left_y = 0.0 if abs(left_y) < self.deadzone else left_y
self.pose_speeds[0] = current_linear_step if hat_up else (-current_linear_step if hat_down else 0.0) # X
self.pose_speeds[1] = current_linear_step if hat_left else (-current_linear_step if hat_right else 0.0) # Y
# 设置Z速度右摇杆Y轴控制
self.pose_speeds[2] = self._apply_nonlinear_mapping(right_y) * current_linear_step # Z
# L1/R1控制RX旋转
LB = self.joystick.get_button(4) # RX-
RB = self.joystick.get_button(5) # RX+
self.pose_speeds[3] = (-current_angular_step if LB else (current_angular_step if RB else 0.0))
# △/□控制RY旋转
triangle = self.joystick.get_button(2) # RY+
square = self.joystick.get_button(3) # RY-
self.pose_speeds[4] = (current_angular_step if triangle else (-current_angular_step if square else 0.0))
# 左摇杆Y轴控制RZ旋转
self.pose_speeds[5] = self._apply_nonlinear_mapping(left_y) * current_angular_step * 2 # RZ增加系数使旋转更明显
# 更新末端位姿
for i in range(6):
self.pose[i] += self.pose_speeds[i]
# 位置限制
for i in range(3):
min_val, max_val = self.pose_limits[i]
self.pose[i] = max(min_val, min(max_val, self.pose[i]))
# 角度归一化处理
for i in range(3, 6):
self.pose[i] = self._normalize_angle(self.pose[i])
def update_joints(self):
"""更新关节控制"""
# 根据控制模式调整步长
current_joint_step = self.joint_step * (0.1 if self.fine_control_mode else 1.0)
# 使用类似于末端位姿控制的映射,但直接控制关节
# 左摇杆控制关节1和2 (类似于末端位姿控制中的X和Y)
left_x = -self.joystick.get_axis(0) # 左摇杆x轴
left_y = -self.joystick.get_axis(1) # 左摇杆y轴
# 应用死区
left_x = 0.0 if abs(left_x) < self.deadzone else left_x
left_y = 0.0 if abs(left_y) < self.deadzone else left_y
# 右摇杆控制关节3和4
right_x = self.joystick.get_axis(3) # 右摇杆x轴
right_y = self.joystick.get_axis(4) # 右摇杆y轴
# 应用死区
right_x = 0.0 if abs(right_x) < self.deadzone else right_x
right_y = 0.0 if abs(right_y) < self.deadzone else right_y
# 方向键控制关节5和6
hat = self.joystick.get_hat(0)
up = hat[1] == 1
down = hat[1] == -1
left = hat[0] == -1
right = hat[0] == 1
# 映射输入到关节速度
self.joint_speeds[0] = left_x * current_joint_step # joint1速度
self.joint_speeds[1] = left_y * current_joint_step # joint2速度
self.joint_speeds[2] = right_y * current_joint_step # joint3速度
self.joint_speeds[3] = right_x * current_joint_step # joint4速度
self.joint_speeds[4] = -current_joint_step if up else (current_joint_step if down else 0.0) # joint5速度
self.joint_speeds[5] = current_joint_step if right else (-current_joint_step if left else 0.0) # joint6速度
# 积分速度到关节位置
for i in range(6):
self.joints[i] += self.joint_speeds[i]
# 关节范围保护
for i in range(6):
min_val, max_val = self.joint_limits[i]
self.joints[i] = max(min_val, min(max_val, self.joints[i]))
def update_state(self, ctrl_mode, end_pose, joint_state):
if ctrl_mode == 'end_pose':
_joint_state = [0] * 6
_joint_state[0] = joint_state.joint_1 / 57324.840764
_joint_state[1] = joint_state.joint_2 / 57324.840764
_joint_state[2] = joint_state.joint_3 / 57324.840764
_joint_state[3] = joint_state.joint_4 / 57324.840764
_joint_state[4] = joint_state.joint_5 / 57324.840764
_joint_state[5] = joint_state.joint_6 / 57324.840764
self.joints = _joint_state
else:
_end_pose = [0] * 6
_end_pose[0] = end_pose.X_axis / 1000 / 1000
_end_pose[1] = end_pose.Y_axis / 1000 / 1000
_end_pose[2] = end_pose.Z_axis / 1000 / 1000
_end_pose[3] = end_pose.RX_axis / 1000
_end_pose[4] = end_pose.RY_axis / 1000
_end_pose[5] = end_pose.RZ_axis / 1000
self.pose = _end_pose
def get_action(self) -> Dict:
"""获取当前控制命令"""
if self.end_pose_mode:
# 返回末端位姿
return {
'X': self.pose[0],
'Y': self.pose[1],
'Z': self.pose[2],
'RX': self.pose[3],
'RY': self.pose[4],
'RZ': self.pose[5],
'gripper': self.gripper
}
else:
# 返回关节角度
return {
'joint0': self.joints[0],
'joint1': self.joints[1],
'joint2': self.joints[2],
'joint3': self.joints[3],
'joint4': self.joints[4],
'joint5': self.joints[5],
'gripper': self.gripper
}
def get_control_mode(self):
"""返回当前控制模式"""
return "end_pose" if self.end_pose_mode else "joints"
def stop(self):
"""停止控制器"""
self.running = False
if self.thread.is_alive():
self.thread.join()
pygame.quit()
print("控制器已退出")
def reset(self):
"""重置到初始状态"""
self.joints = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
self.joint_speeds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
self.pose = [0.056127, 0, 0.213266, 0, 84.999, 0]
self.pose_speeds = [0.0] * 6
self.gripper = 0.0
self.gripper_speed = 0.0
print("已重置到初始状态")
if __name__ == "__main__":
piper = C_PiperInterface_V2("can0")
piper.ConnectPort()
piper.EnableArm(7)
enable_fun(piper=piper)
piper.GripperCtrl(0,1000,0x01, 0)
teleop = UnifiedArmController()
factor = 1000
while True:
# 获取当前控制命令
action = teleop.get_action()
control_mode = teleop.get_control_mode()
if control_mode == "end_pose":
# 末端位姿控制
position = list(action.values())
X = round(position[0]*factor*factor)
Y = round(position[1]*factor*factor)
Z = round(position[2]*factor*factor)
RX = round(position[3]*factor)
RY = round(position[4]*factor)
RZ = round(position[5]*factor)
joint_6 = round(position[6]*factor*factor)
piper.MotionCtrl_2(0x01, 0x00, 100, 0x00)
piper.EndPoseCtrl(X, Y, Z, RX, RY, RZ)
piper.GripperCtrl(abs(joint_6), 1000, 0x01, 0)
new_end_pose = piper.GetArmEndPoseMsgs().end_pose
new_joint_state = piper.GetArmJointMsgs().joint_state
teleop.update_state(control_mode, new_end_pose, new_joint_state)
print("控制模式: 末端控制")
print("末端位置", new_end_pose)
print("关节位置:", new_joint_state)
else:
# 关节控制
joints = list(action.values())
# 将关节角度转换为适合发送的格式
joint0 = round(joints[0] * 57324.840764) # 转换为机械臂期望的单位
joint1 = round(joints[1] * 57324.840764)
joint2 = round(joints[2] * 57324.840764)
joint3 = round(joints[3] * 57324.840764)
joint4 = round(joints[4] * 57324.840764)
joint5 = round(joints[5] * 57324.840764)
gripper = round(joints[6] * 1000 * 1000)
# 发送关节控制命令
piper.MotionCtrl_2(0x01, 0x01, 100, 0x00)
piper.JointCtrl(joint0, joint1, joint2, joint3, joint4, joint5)
piper.GripperCtrl(abs(gripper), 1000, 0x01, 0)
new_end_pose = piper.GetArmEndPoseMsgs().end_pose
new_joint_state = piper.GetArmJointMsgs().joint_state
teleop.update_state(control_mode, new_end_pose, new_joint_state)
print("控制模式: 关节控制")
print("末端位置", new_end_pose)
print("关节位置:", new_joint_state)
time.sleep(0.1)