cmvr-es-cli/cmvr/dexhand_client.py

340 lines
14 KiB
Python
Raw Permalink Normal View History

2025-08-21 14:05:27 +08:00
import grpc
2025-08-25 10:34:44 +08:00
import numpy as np
2025-08-21 14:05:27 +08:00
from typing import Tuple
2025-08-25 10:34:44 +08:00
from typing import Dict
2025-08-21 14:05:27 +08:00
2025-08-25 10:34:44 +08:00
from .enums import CMVRErrorCode, FingerType
2025-08-27 16:46:12 +08:00
from .models import DexHandState, FreedomState, HandTactileSensors
2025-08-21 14:05:27 +08:00
class DexHandClient:
"""灵巧手客户端"""
def __init__(self, device_id: str, stub):
self.device_id = device_id
self.stub = stub
2025-08-25 10:34:44 +08:00
self.generated = None # 推迟导入
def _import_generated(self):
"""推迟导入 generated"""
if self.generated is None:
try:
# 从正确的路径导入生成的模块
from generated.cmvr.api import common_pb2, dexhand_command_pb2
self.generated = type('GeneratedModules', (), {
'common_pb2': common_pb2,
'dexhand_command_pb2': dexhand_command_pb2
})
except ImportError as e:
print(f"导入生成的模块失败: {e}")
print("请确保已生成 protobuf 代码")
raise
return self.generated
2025-08-21 14:05:27 +08:00
def _create_command_header(self):
"""创建命令头"""
2025-08-25 10:34:44 +08:00
generated = self._import_generated()
2025-08-21 14:05:27 +08:00
header = generated.common_pb2.CommandHeader.Request()
header.device_id = self.device_id
header.timestamp.GetCurrentTime()
return header
def get_status(self) -> Tuple[CMVRErrorCode, DexHandState]:
"""获取灵巧手状态"""
try:
2025-08-25 10:34:44 +08:00
generated = self._import_generated()
request = generated.dexhand_command_pb2.GetDexHandStateCommand.Request()
2025-08-21 14:05:27 +08:00
request.header.CopyFrom(self._create_command_header())
response = self.stub.GetStatus(request)
if not response.header.success:
return CMVRErrorCode.CMVR_RPC_FAILED, DexHandState()
state = DexHandState(
is_initialized=response.state.is_initialized
)
# 填充手指状态
for i, hand_state in enumerate(response.state.hands):
if i < len(state.hands):
2025-08-27 16:46:12 +08:00
state.hands[i] = FreedomState(
2025-08-21 14:05:27 +08:00
angle=hand_state.angle,
speed=hand_state.speed,
force=hand_state.force,
position=hand_state.position,
current=hand_state.current,
temperature=hand_state.temperature,
error=hand_state.error,
error_message=list(hand_state.error_message)
)
return CMVRErrorCode.CMVR_SUCCESS, state
except grpc.RpcError as e:
print(f"获取灵巧手状态失败: {e}")
return CMVRErrorCode.CMVR_RPC_FAILED, DexHandState()
2025-10-21 10:38:31 +08:00
def set_angle(self, angle_map: Dict[str, float]) -> CMVRErrorCode:
2025-08-25 10:34:44 +08:00
"""
设置DexHand的角度
参数:
2025-10-21 10:38:31 +08:00
angle_map: 字典键为自由度ID字符串值为角度百分比 (0-1)
有效字符串ID"little_finger"小拇指"ring_finger"无名指
"middle_finger"中指"index_finger"食指
"thumb_bend"大拇指弯曲"thumb_rotate"大拇指旋转
对应proto中的FreedomValue (id和value)
2025-08-25 10:34:44 +08:00
返回:
CMVRErrorCode: 操作结果状态码
"""
2025-08-21 14:05:27 +08:00
try:
2025-08-25 10:34:44 +08:00
generated = self._import_generated()
2025-10-21 10:38:31 +08:00
# 有效自由度字符串ID列表与proto定义一致
valid_dof_ids = {
"little_finger", "ring_finger", "middle_finger",
"index_finger", "thumb_bend", "thumb_rotate"
}
2025-08-25 10:34:44 +08:00
# 创建角度设置请求对象
request = generated.dexhand_command_pb2.SetDexHandAnglesCommand.Request()
2025-08-21 14:05:27 +08:00
request.header.CopyFrom(self._create_command_header())
2025-08-25 10:34:44 +08:00
# 将字典转换为protobuf的FreedomValue列表
for dof_id, angle_value in angle_map.items():
2025-10-21 10:38:31 +08:00
# 验证自由度ID有效性字符串
if dof_id not in valid_dof_ids:
print(f"警告: 无效的自由度ID '{dof_id}'有效ID为{valid_dof_ids}")
2025-08-25 10:34:44 +08:00
continue
2025-10-21 10:38:31 +08:00
# 验证角度值范围0-1
2025-08-25 10:34:44 +08:00
if not (0 <= angle_value <= 1):
print(f"警告: 角度值 {angle_value} 超出范围必须在0-1之间")
2025-10-21 10:38:31 +08:00
angle_value = max(0, min(1, angle_value)) # 限制在有效范围
2025-08-21 14:05:27 +08:00
2025-10-21 10:38:31 +08:00
# 添加自由度角度设置直接使用字符串ID
2025-08-25 10:34:44 +08:00
freedom_value = request.values.add()
freedom_value.id = dof_id
freedom_value.value = angle_value
2025-08-21 14:05:27 +08:00
2025-08-25 10:34:44 +08:00
# 调用RPC接口
response = self.stub.SetDexHandAngle(request)
# 返回操作结果
2025-08-21 14:05:27 +08:00
return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED
except grpc.RpcError as e:
2025-08-25 10:34:44 +08:00
print(f"设置DexHand角度失败: {e}")
2025-08-21 14:05:27 +08:00
return CMVRErrorCode.CMVR_RPC_FAILED
2025-08-25 10:34:44 +08:00
def get_sensor_data(self) -> Tuple[CMVRErrorCode, HandTactileSensors]:
"""
获取灵巧手所有触觉传感器数据
将Protobuf的SensorData转换为HandTactileSensors对象
返回:
Tuple[CMVRErrorCode, HandTactileSensors]: 错误码和传感器数据对象
"""
2025-08-21 14:05:27 +08:00
try:
2025-08-25 10:34:44 +08:00
generated = self._import_generated()
# 创建传感器数据请求
request = generated.dexhand_command_pb2.GetSensorDataCommand.Request()
2025-08-21 14:05:27 +08:00
request.header.CopyFrom(self._create_command_header())
2025-08-25 10:34:44 +08:00
# 调用RPC接口获取数据
response = self.stub.GetSensorData(request)
2025-08-21 14:05:27 +08:00
2025-08-25 10:34:44 +08:00
if not response.header.success:
return CMVRErrorCode.CMVR_RPC_FAILED, HandTactileSensors()
# 初始化传感器数据容器
tactile_sensors = HandTactileSensors()
# 映射表FingerType + PartType -> HandTactileSensors属性
sensor_mapping = {
# 小拇指
(FingerType.PINKY, generated.dexhand_command_pb2.SensorData.PartType.TIP):
'pinky_tip',
(FingerType.PINKY, generated.dexhand_command_pb2.SensorData.PartType.FINGER):
'pinky_finger',
(FingerType.PINKY, generated.dexhand_command_pb2.SensorData.PartType.PAD):
'pinky_pad',
# 无名指
(FingerType.RING, generated.dexhand_command_pb2.SensorData.PartType.TIP):
'ring_tip',
(FingerType.RING, generated.dexhand_command_pb2.SensorData.PartType.FINGER):
'ring_finger',
(FingerType.RING, generated.dexhand_command_pb2.SensorData.PartType.PAD):
'ring_pad',
# 中指
(FingerType.MIDDLE, generated.dexhand_command_pb2.SensorData.PartType.TIP):
'middle_tip',
(FingerType.MIDDLE, generated.dexhand_command_pb2.SensorData.PartType.FINGER):
'middle_finger',
(FingerType.MIDDLE, generated.dexhand_command_pb2.SensorData.PartType.PAD):
'middle_pad',
# 食指
(FingerType.INDEX, generated.dexhand_command_pb2.SensorData.PartType.TIP):
'index_tip',
(FingerType.INDEX, generated.dexhand_command_pb2.SensorData.PartType.FINGER):
'index_finger',
(FingerType.INDEX, generated.dexhand_command_pb2.SensorData.PartType.PAD):
'index_pad',
# 大拇指
(FingerType.THUMB, generated.dexhand_command_pb2.SensorData.PartType.TIP):
'thumb_tip',
(FingerType.THUMB, generated.dexhand_command_pb2.SensorData.PartType.FINGER):
'thumb_finger',
(FingerType.THUMB, generated.dexhand_command_pb2.SensorData.PartType.THUMB_MIDDLE):
'thumb_middle',
(FingerType.THUMB, generated.dexhand_command_pb2.SensorData.PartType.PAD):
'thumb_pad',
# 掌心
(FingerType.PALM, generated.dexhand_command_pb2.SensorData.PartType.PALM_PAD):
'palm'
}
# 遍历所有传感器数据并填充到对应的结构中
for sensor in response.sensor:
# 获取映射的属性名
key = (FingerType(sensor.finger_type), sensor.part_type)
attr_name = sensor_mapping.get(key)
if not attr_name:
# 跳过未定义的传感器类型
print(f"警告: 未定义的传感器类型 - 手指: {sensor.finger_type}, 部位: {sensor.part_type}")
continue
# 获取对应的传感器对象
sensor_obj = getattr(tactile_sensors, attr_name)
# 更新传感器基本信息
sensor_obj.name = sensor.sensor_name
sensor_obj.rows = sensor.rows
sensor_obj.cols = sensor.cols
# 计算字节大小 (每个int32占2字节)
sensor_obj.byteSize = sensor.rows * sensor.cols * 2
# 转换数据为numpy数组
if sensor.rows > 0 and sensor.cols > 0 and len(sensor.data) > 0:
# 初始化数据数组
data_array = np.zeros((sensor.rows, sensor.cols), dtype=np.uint16)
# 填充数据
for row_idx, row_data in enumerate(sensor.data):
if row_idx < sensor.rows: # 防止数组越界
# 截取有效长度并转换
values = row_data.values[:sensor.cols]
data_array[row_idx, :len(values)] = values
sensor_obj.data = data_array
return CMVRErrorCode.CMVR_SUCCESS, tactile_sensors
2025-08-21 14:05:27 +08:00
except grpc.RpcError as e:
2025-08-25 10:34:44 +08:00
print(f"获取灵巧手传感器数据失败: {e}")
return CMVRErrorCode.CMVR_RPC_FAILED, HandTactileSensors()
def create_stream_request(self):
"""创建流请求(无参数)"""
generated = self._import_generated()
request = generated.dexhand_command_pb2.GetSensorDataStreamCommand.Request()
request.header.CopyFrom(self._create_command_header())
# 不需要设置其他参数
return request
def get_sensor_data_stream(self, request_generator):
"""
双向流获取传感器数据
参数:
request_generator: 请求生成器
返回:
传感器数据流迭代器
"""
generated = self._import_generated()
return self.stub.GetSensorDataStream(request_generator)
def parse_sensor_stream_data(self, feedback) -> Tuple[CMVRErrorCode, HandTactileSensors]:
"""
解析流反馈中的传感器数据
复用之前的传感器数据转换逻辑
"""
try:
generated = self._import_generated()
SensorData = generated.dexhand_command_pb2.SensorData
pb_finger_type = SensorData.FingerType
pb_part_type = SensorData.PartType
# 初始化传感器容器
tactile_sensors = HandTactileSensors()
# 传感器映射表与get_sensor_data保持一致
sensor_mapping = {
# 小拇指
(pb_finger_type.PINKY, pb_part_type.TIP): 'pinky_tip',
(pb_finger_type.PINKY, pb_part_type.FINGER): 'pinky_finger',
(pb_finger_type.PINKY, pb_part_type.PAD): 'pinky_pad',
# 无名指
(pb_finger_type.RING, pb_part_type.TIP): 'ring_tip',
(pb_finger_type.RING, pb_part_type.FINGER): 'ring_finger',
(pb_finger_type.RING, pb_part_type.PAD): 'ring_pad',
# 中指
(pb_finger_type.MIDDLE_FINGER, pb_part_type.TIP): 'middle_tip',
(pb_finger_type.MIDDLE_FINGER, pb_part_type.FINGER): 'middle_finger',
(pb_finger_type.MIDDLE_FINGER, pb_part_type.PAD): 'middle_pad',
# 食指
(pb_finger_type.INDEX, pb_part_type.TIP): 'index_tip',
(pb_finger_type.INDEX, pb_part_type.FINGER): 'index_finger',
(pb_finger_type.INDEX, pb_part_type.PAD): 'index_pad',
# 大拇指
(pb_finger_type.THUMB, pb_part_type.TIP): 'thumb_tip',
(pb_finger_type.THUMB, pb_part_type.FINGER): 'thumb_finger',
(pb_finger_type.THUMB, pb_part_type.THUMB_MIDDLE): 'thumb_middle',
(pb_finger_type.THUMB, pb_part_type.PAD): 'thumb_pad',
# 掌心
(pb_finger_type.PALM, pb_part_type.PALM_PAD): 'palm'
}
# 解析反馈中的传感器数据
for sensor in feedback.sensor: # 假设反馈中的传感器数据字段是sensor_data
key = (sensor.finger_type, sensor.part_type)
attr_name = sensor_mapping.get(key)
if not attr_name:
finger_name = pb_finger_type.Name(sensor.finger_type)
part_name = pb_part_type.Name(sensor.part_type)
print(f"警告: 未定义的传感器类型 - 手指: {finger_name}, 部位: {part_name}")
continue
# 填充传感器数据
sensor_obj = getattr(tactile_sensors, attr_name)
sensor_obj.name = sensor.sensor_name
sensor_obj.rows = sensor.rows
sensor_obj.cols = sensor.cols
sensor_obj.byteSize = sensor.rows * sensor.cols * 2
# 转换数据为numpy数组
if sensor.rows > 0 and sensor.cols > 0 and len(sensor.data) > 0:
data_array = np.zeros((sensor.rows, sensor.cols), dtype=np.uint16)
for row_idx, row_data in enumerate(sensor.data):
if row_idx < sensor.rows:
values = row_data.values[:sensor.cols]
data_array[row_idx, :len(values)] = values
sensor_obj.data = data_array
return CMVRErrorCode.CMVR_SUCCESS, tactile_sensors
except Exception as e:
print(f"解析流数据失败: {e}")
return CMVRErrorCode.CMVR_INTERNAL_ERROR, HandTactileSensors()