diff --git a/README.md b/README.md index a572f3e..267759f 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ Already a pro? Just edit this README.md and make it your own. Want to make it ea ``` cd existing_repo -git remote add origin http://192.168.1.100:18088/smart_bench/cmvr-es-cli.git +git remote add origin http://10.148.20.36:18088/smart_bench/cmvr-es-cli.git git branch -M main git push -uf origin main ``` ## Integrate with your tools -- [ ] [Set up project integrations](http://192.168.1.100:18088/smart_bench/cmvr-es-cli/-/settings/integrations) +- [ ] [Set up project integrations](http://10.148.20.36:18088/smart_bench/cmvr-es-cli/-/settings/integrations) ## Collaborate with your team diff --git a/cmvr/__init__.py b/cmvr/__init__.py new file mode 100644 index 0000000..4294e61 --- /dev/null +++ b/cmvr/__init__.py @@ -0,0 +1,15 @@ +from .client import CMVRGrpcClient +from .enums import CMVRErrorCode, DeviceState, FingerType +from .models import ( + MicState, SpeakerState, CameraState, + RH56DFTPDexHand, DexHandState, FacialExpressionState, + FingerTactileData, PalmTactileData, HandTactileSensors +) + +__all__ = [ + 'CMVRGrpcClient', + 'CMVRErrorCode', 'DeviceState', 'FingerType', + 'MicState', 'SpeakerState', 'CameraState', + 'RH56DFTPDexHand', 'DexHandState', 'FacialExpressionState', + 'FingerTactileData', 'PalmTactileData', 'HandTactileSensors' +] \ No newline at end of file diff --git a/cmvr/biohead_client.py b/cmvr/biohead_client.py new file mode 100644 index 0000000..5071a37 --- /dev/null +++ b/cmvr/biohead_client.py @@ -0,0 +1,222 @@ +import grpc +import time +from typing import Tuple +from datetime import datetime + +from .enums import CMVRErrorCode +from .models import FacialExpressionState + +class BioHeadClient: + """仿生头客户端""" + + def __init__(self, device_id: str, stub): + self.device_id = device_id + self.stub = stub + self.generated = None # 推迟导入 + self.request_queue = [] # 请求队列 + self.response_iterator = None # 响应迭代器 + self.stream_active = False # 流是否活跃 + + def _import_generated(self): + """推迟导入 generated""" + if self.generated is None: + try: + # 从正确的路径导入生成的模块 + from generated.cmvr.api import common_pb2, biohead_command_pb2 + self.generated = type('GeneratedModules', (), { + 'common_pb2': common_pb2, + 'biohead_command_pb2': biohead_command_pb2 + }) + except ImportError as e: + print(f"导入生成的模块失败: {e}") + print("请确保已生成 protobuf 代码") + raise + return self.generated + + def _create_command_header(self): + """创建命令头""" + generated = self._import_generated() + header = generated.common_pb2.CommandHeader.Request() + header.device_id = self.device_id + # 使用正确的方式设置时间戳 + from google.protobuf.timestamp_pb2 import Timestamp + timestamp = Timestamp() + timestamp.GetCurrentTime() + header.timestamp.CopyFrom(timestamp) + return header + + def set_expression(self, expression: FacialExpressionState) -> CMVRErrorCode: + """设置表情""" + try: + generated = self._import_generated() + # 创建请求 + request = generated.biohead_command_pb2.SetFacialExpression.Request() + request.header.CopyFrom(self._create_command_header()) + + # 填充表情数据 + expr = request.expression + expr.eyebrow.left_outside_y = expression.left_eyebrow_outside_y + expr.eyebrow.left_inside_y = expression.left_eyebrow_inside_y + expr.eyebrow.right_outside_y = expression.right_eyebrow_outside_y + expr.eyebrow.right_inside_y = expression.right_eyebrow_inside_y + + expr.eyelid.left_upper_y = expression.left_eye_upper_lid_y + expr.eyelid.left_lower_y = expression.left_eye_lower_lid_y + expr.eyelid.right_upper_y = expression.right_eye_upper_lid_y + expr.eyelid.right_lower_y = expression.right_eye_lower_lid_y + + expr.eyeball.left_x = expression.left_eye_ball_x + expr.eyeball.left_y = expression.left_eye_ball_y + expr.eyeball.right_x = expression.right_eye_ball_x + expr.eyeball.right_y = expression.right_eye_ball_y + + expr.nose.left_y = expression.left_nose_y + expr.nose.right_y = expression.right_nose_y + + expr.mouth.upper_lip_y = expression.upper_lip_y + expr.mouth.upper_lip_z = expression.upper_lip_z + expr.mouth.lower_lip_y = expression.lower_lip_y + expr.mouth.lower_lip_z = expression.lower_lip_z + + expr.mouth.left_lip.upper_x = expression.upper_left_lip_x + expr.mouth.left_lip.upper_y = expression.upper_left_lip_y + expr.mouth.left_lip.corner_x = expression.left_corner_lip_x + expr.mouth.left_lip.corner_y = expression.left_corner_lip_y + expr.mouth.left_lip.lower_x = expression.lower_left_lip_x + expr.mouth.left_lip.lower_y = expression.lower_left_lip_y + + expr.mouth.right_lip.upper_x = expression.upper_right_lip_x + expr.mouth.right_lip.upper_y = expression.upper_right_lip_y + expr.mouth.right_lip.corner_x = expression.right_corner_lip_x + expr.mouth.right_lip.corner_y = expression.right_corner_lip_y + expr.mouth.right_lip.lower_x = expression.lower_right_lip_x + expr.mouth.right_lip.lower_y = expression.lower_right_lip_y + + expr.jaw.x = expression.jaw_x + expr.jaw.y = expression.jaw_y + + # 发送请求 + response = self.stub.SetExpression(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"设置表情失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def start_stream(self): + """开始流式会话""" + try: + if not self.stream_active: + # 创建一个请求迭代器生成器 + self.request_queue = [] + self.response_iterator = self.stub.StreamExpression(self._stream_request_generator()) + self.stream_active = True + return CMVRErrorCode.CMVR_SUCCESS + except grpc.RpcError as e: + print(f"开始流式会话失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def _stream_request_generator(self): + """流式请求生成器""" + generated = self._import_generated() + while self.stream_active: + if self.request_queue: + request = self.request_queue.pop(0) + yield request + if request.eof: + break + else: + # 如果没有请求,等待一小段时间 + time.sleep(0.01) + + def stream_expression(self, expression, is_eof=False): + """流式控制表情""" + try: + if not self.stream_active: + result = self.start_stream() + if result != CMVRErrorCode.CMVR_SUCCESS: + return result + + generated = self._import_generated() + + # 创建请求 + request = generated.biohead_command_pb2.StreamFacialExpression.Request() + request.header.CopyFrom(self._create_command_header()) + request.expr.CopyFrom(expression) + request.eof = is_eof + + # 将请求添加到队列 + self.request_queue.append(request) + + # 读取最新的响应 + try: + feedback = next(self.response_iterator) + return CMVRErrorCode.CMVR_SUCCESS if feedback.header.success else CMVRErrorCode.CMVR_RPC_FAILED + except StopIteration: + return CMVRErrorCode.CMVR_SUCCESS + + except grpc.RpcError as e: + print(f"流式控制表情失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def end_stream(self): + """结束流式会话""" + try: + if self.stream_active: + # 发送结束信号 + generated = self._import_generated() + request = generated.biohead_command_pb2.StreamFacialExpression.Request() + request.header.CopyFrom(self._create_command_header()) + request.eof = True + + self.request_queue.append(request) + + # 等待所有响应 + for feedback in self.response_iterator: + if not feedback.header.success: + return CMVRErrorCode.CMVR_RPC_FAILED + + self.stream_active = False + self.request_queue = [] + self.response_iterator = None + + return CMVRErrorCode.CMVR_SUCCESS + except grpc.RpcError as e: + print(f"结束流式会话失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def get_system_status(self): + """获取系统状态""" + try: + generated = self._import_generated() + request = generated.biohead_command_pb2.GetStatus.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.GetSystemStatus(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"获取系统状态失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def emergency_stop(self): + """紧急停止""" + try: + generated = self._import_generated() + request = generated.biohead_command_pb2.EmergencyStop.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.EmergencyStop(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"紧急停止失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def close(self): + """关闭连接""" + if self.stream_active: + self.end_stream() \ No newline at end of file diff --git a/cmvr/camera_client.py b/cmvr/camera_client.py new file mode 100644 index 0000000..cf5e1b4 --- /dev/null +++ b/cmvr/camera_client.py @@ -0,0 +1,138 @@ +import grpc +import numpy as np +from PIL import Image +from typing import Tuple + +from . import generated +from .enums import CMVRErrorCode +from .models import CameraState + +class CameraClient: + """相机客户端""" + + def __init__(self, device_id: str, stub): + self.device_id = device_id + self.stub = stub + + def _create_command_header(self): + """创建命令头""" + header = generated.common_pb2.CommandHeader.Request() + header.device_id = self.device_id + header.timestamp.GetCurrentTime() + return header + + def get_status(self) -> Tuple[CMVRErrorCode, CameraState]: + """获取相机状态""" + try: + request = generated.camera_service_pb2.GetCameraStateCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.GetStatus(request) + + if not response.header.success: + return CMVRErrorCode.CMVR_RPC_FAILED, CameraState() + + state = CameraState( + is_initialized=response.state.is_initialized, + is_opened=response.state.is_opened, + is_streaming=response.state.is_streaming, + is_recording=response.state.is_recording, + width=response.state.width, + height=response.state.height, + fps=response.state.fps + ) + + return CMVRErrorCode.CMVR_SUCCESS, state + + except grpc.RpcError as e: + print(f"获取相机状态失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED, CameraState() + + def start_camera(self) -> CMVRErrorCode: + """启动相机""" + try: + request = generated.camera_service_pb2.StartCameraCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.StartCamera(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"启动相机失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def stop_camera(self) -> CMVRErrorCode: + """停止相机""" + try: + request = generated.camera_service_pb2.StopCameraCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.StopCamera(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"停止相机失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def get_rgb_image(self) -> Tuple[CMVRErrorCode, np.ndarray, int, int]: + """获取RGB图像""" + try: + request = generated.camera_service_pb2.GetRGBImageCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.GetRGBImage(request) + + if not response.header.success: + return CMVRErrorCode.CMVR_RPC_FAILED, np.array([]), 0, 0 + + width = response.color_frame.width + height = response.color_frame.height + size = len(response.color_frame.data) + + # 将字节数据转换为numpy数组 + img_data = np.frombuffer(response.color_frame.data, dtype=np.uint8) + + # 重塑为图像格式 (height, width, channels) + channels = 3 # 假设是RGB图像 + if size == width * height * channels: + img_array = img_data.reshape((height, width, channels)) + else: + # 可能是灰度图或其他格式 + img_array = img_data.reshape((height, width)) + + return CMVRErrorCode.CMVR_SUCCESS, img_array, width, height + + except grpc.RpcError as e: + print(f"获取图像失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED, np.array([]), 0, 0 + + def start_record(self, video_path: str) -> CMVRErrorCode: + """开始录像""" + try: + request = generated.camera_service_pb2.StartCameraRecordingCommand.Request() + request.header.CopyFrom(self._create_command_header()) + request.video_path = video_path + + response = self.stub.StartRecording(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"开始录像失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def stop_record(self) -> CMVRErrorCode: + """停止录像""" + try: + request = generated.camera_service_pb2.StopCameraRecordingCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.StopRecording(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"停止录像失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED \ No newline at end of file diff --git a/cmvr/client.py b/cmvr/client.py new file mode 100644 index 0000000..d4b7c5f --- /dev/null +++ b/cmvr/client.py @@ -0,0 +1,73 @@ +import grpc +from typing import Dict + +from .enums import CMVRErrorCode +from .biohead_client import BioHeadClient + + +class CMVRGrpcClient: + """CMVR gRPC 主客户端""" + + def __init__(self, server_address: str): + self.server_address = server_address + self.connected = False + + # 创建 gRPC 通道 + self.channel = grpc.insecure_channel(server_address) + + # 初始化各服务的存根 + self.generated = None # 推迟导入 + self.biohead_stub = None + self.biohead_map: Dict[str, BioHeadClient] = {} + + # 检查连接状态 + try: + grpc.channel_ready_future(self.channel).result(timeout=5) + self.connected = True + self._import_generated() # 在连接成功后才导入 generated + self.biohead_stub = self.generated.biohead_service_pb2_grpc.BioHeadServiceStub(self.channel) + except grpc.FutureTimeoutError: + print(f"连接服务器超时: {server_address}") + self.connected = False + + def _import_generated(self): + """推迟导入 generated""" + if self.generated is None: + try: + # 从正确的路径导入生成的模块 + from generated.cmvr.api import biohead_service_pb2_grpc + from generated.cmvr.api import common_pb2, biohead_command_pb2 + self.generated = type('GeneratedModules', (), { + 'biohead_service_pb2_grpc': biohead_service_pb2_grpc, + 'common_pb2': common_pb2, + 'biohead_command_pb2': biohead_command_pb2 + }) + except ImportError as e: + print(f"导入生成的模块失败: {e}") + print("请确保已生成 protobuf 代码") + raise + return self.generated + + def is_connected(self) -> bool: + """检查连接状态""" + return self.connected + + def get_biohead(self, device_id: str) -> BioHeadClient: + """获取仿生头客户端""" + if device_id not in self.biohead_map: + self.biohead_map[device_id] = BioHeadClient(device_id, self.biohead_stub) + return self.biohead_map[device_id] + + def close(self): + """关闭所有连接""" + # 关闭所有流式连接 + for biohead in self.biohead_map.values(): + biohead.close() + + # 关闭 gRPC 通道 + self.channel.close() + + # 清空所有映射 + self.biohead_map.clear() + + self.connected = False \ No newline at end of file diff --git a/cmvr/dexhand_client.py b/cmvr/dexhand_client.py new file mode 100644 index 0000000..72793b7 --- /dev/null +++ b/cmvr/dexhand_client.py @@ -0,0 +1,89 @@ +import grpc +from typing import Tuple + +from . import generated +from .enums import CMVRErrorCode +from .models import DexHandState, RH56DFTPDexHand + +class DexHandClient: + """灵巧手客户端""" + + def __init__(self, device_id: str, stub): + self.device_id = device_id + self.stub = stub + + def _create_command_header(self): + """创建命令头""" + 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: + request = generated.dexhand_service_pb2.GetDexHandStateCommand.Request() + 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): + state.hands[i] = RH56DFTPDexHand( + 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() + + def set_position(self, dof_id: int, value: float) -> CMVRErrorCode: + """设置位置""" + try: + request = generated.dexhand_service_pb2.SetDexHandPositionsCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + # 添加自由度值 + freedom_value = request.values.add() + freedom_value.id = dof_id + freedom_value.value = value + + response = self.stub.SetDexHandPos(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"设置位置失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def set_preset_act(self, preset_act_id: int) -> CMVRErrorCode: + """设置预设动作""" + try: + request = generated.dexhand_service_pb2.SetDexHandPresetActCommand.Request() + request.header.CopyFrom(self._create_command_header()) + request.presetactid = preset_act_id + + response = self.stub.SetDexHandPresetAct(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"设置预设动作失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED \ No newline at end of file diff --git a/cmvr/enums.py b/cmvr/enums.py new file mode 100644 index 0000000..6aee693 --- /dev/null +++ b/cmvr/enums.py @@ -0,0 +1,27 @@ +from enum import Enum + +class CMVRErrorCode(Enum): + """错误码定义""" + CMVR_SUCCESS = 0 + CMVR_CONNECT_FAILED = 1 + CMVR_INVALID_PARAM = 2 + CMVR_RPC_FAILED = 3 + CMVR_NOT_CONNECTED = 4 + CMVR_INTERNAL_ERROR = 5 + +class DeviceState(Enum): + """设备状态""" + STATE_INIT = 0 + STATE_READY = 1 + STATE_RUNNING = 2 + STATE_ERROR = 3 + STATE_ESTOP = 4 + STATE_STOP = 5 + +class FingerType(Enum): + """手指类型枚举""" + PINKY = 0 # 小拇指 + RING = 1 # 无名指 + MIDDLE = 2 # 中指 + INDEX = 3 # 食指 + THUMB = 4 # 大拇指 \ No newline at end of file diff --git a/cmvr/micphone_client.py b/cmvr/micphone_client.py new file mode 100644 index 0000000..2f2dab2 --- /dev/null +++ b/cmvr/micphone_client.py @@ -0,0 +1,74 @@ +import grpc +from typing import Tuple + +from . import generated +from .enums import CMVRErrorCode +from .models import MicState + +class MicphoneClient: + """麦克风客户端""" + + def __init__(self, device_id: str, stub): + self.device_id = device_id + self.stub = stub + + def _create_command_header(self): + """创建命令头""" + header = generated.common_pb2.CommandHeader.Request() + header.device_id = self.device_id + header.timestamp.GetCurrentTime() + return header + + def get_status(self) -> Tuple[CMVRErrorCode, MicState]: + """获取麦克风状态""" + try: + request = generated.microphone_service_pb2.GetMicStateCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.GetStatus(request) + + if not response.header.success: + return CMVRErrorCode.CMVR_RPC_FAILED, MicState() + + state = MicState( + is_initialized=response.state.is_initialized, + is_running=response.state.is_running, + is_recording=response.state.is_recording, + volume=response.state.volume, + error_message=response.state.error_message + ) + + return CMVRErrorCode.CMVR_SUCCESS, state + + except grpc.RpcError as e: + print(f"获取麦克风状态失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED, MicState() + + def start_record(self, audio_path: str) -> CMVRErrorCode: + """开始录音""" + try: + request = generated.microphone_service_pb2.StartMicRecordingCommand.Request() + request.header.CopyFrom(self._create_command_header()) + request.file_path = audio_path + + response = self.stub.StartRecord(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"开始录音失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def stop_record(self) -> CMVRErrorCode: + """停止录音""" + try: + request = generated.microphone_service_pb2.StopMicRecordingCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.StopRecord(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"停止录音失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED \ No newline at end of file diff --git a/cmvr/models.py b/cmvr/models.py new file mode 100644 index 0000000..5b7d1f4 --- /dev/null +++ b/cmvr/models.py @@ -0,0 +1,197 @@ +from dataclasses import dataclass, field +from typing import List +import numpy as np + +@dataclass +class MicState: + """麦克风状态""" + is_initialized: bool = False + is_running: bool = False + is_recording: bool = False + volume: int = 0 + error_message: str = "" + +@dataclass +class SpeakerState: + """扬声器状态""" + is_initialized: bool = False + is_running: bool = False + is_decoding: bool = False + is_paused: bool = False + volume: int = 0 + +@dataclass +class CameraState: + """相机状态""" + is_initialized: bool = False + is_opened: bool = False + is_streaming: bool = False + is_recording: bool = False + width: int = 0 + height: int = 0 + fps: int = 0 + +@dataclass +class RH56DFTPDexHand: + """灵巧手自由度状态""" + angle: int = 0 + speed: int = 0 + force: int = 0 + position: int = 0 + current: int = 0 + temperature: int = 0 + error: int = 0 + error_message: List[str] = field(default_factory=list) + +@dataclass +class DexHandState: + """灵巧手整体状态""" + is_initialized: bool = False + hands: List[RH56DFTPDexHand] = field(default_factory=lambda: [RH56DFTPDexHand() for _ in range(6)]) + +@dataclass +class FacialExpressionState: + """面部表情状态""" + # 眉毛 + left_eyebrow_outside_y: float = 0.0 + left_eyebrow_inside_y: float = 0.0 + right_eyebrow_outside_y: float = 0.0 + right_eyebrow_inside_y: float = 0.0 + + # 眼睑 + left_eye_upper_lid_y: float = 0.0 + left_eye_lower_lid_y: float = 0.0 + right_eye_upper_lid_y: float = 0.0 + right_eye_lower_lid_y: float = 0.0 + + # 眼球 + left_eye_ball_x: float = 0.0 + left_eye_ball_y: float = 0.0 + right_eye_ball_x: float = 0.0 + right_eye_ball_y: float = 0.0 + + # 鼻子 + left_nose_y: float = 0.0 + right_nose_y: float = 0.0 + + # 嘴巴 + upper_lip_y: float = 0.0 + upper_lip_z: float = 0.0 + lower_lip_y: float = 0.0 + lower_lip_z: float = 0.0 + + # 左唇角 + upper_left_lip_x: float = 0.0 + upper_left_lip_y: float = 0.0 + left_corner_lip_x: float = 0.0 + left_corner_lip_y: float = 0.0 + lower_left_lip_x: float = 0.0 + lower_left_lip_y: float = 0.0 + + # 右唇角 + upper_right_lip_x: float = 0.0 + upper_right_lip_y: float = 0.0 + right_corner_lip_x: float = 0.0 + right_corner_lip_y: float = 0.0 + lower_right_lip_x: float = 0.0 + lower_right_lip_y: float = 0.0 + + # 下巴 + jaw_x: float = 0.0 + jaw_y: float = 0.0 + +@dataclass +class FingerTactileData: + """手指触觉数据""" + data: np.ndarray = None + rows: int = 0 + cols: int = 0 + byteSize: int = 0 + name: str = "" + + def __post_init__(self): + if self.data is None and self.rows > 0 and self.cols > 0: + self.data = np.zeros((self.rows, self.cols), dtype=np.uint16) + +@dataclass +class PalmTactileData: + """手掌触觉数据""" + data: np.ndarray = None + rows: int = 8 + cols: int = 14 + byteSize: int = 224 + name: str = "掌心" + + def __post_init__(self): + if self.data is None: + self.data = np.zeros((self.rows, self.cols), dtype=np.uint16) + +@dataclass +class HandTactileSensors: + """整只手的触觉传感器数据""" + pinky_tip: FingerTactileData = None + pinky_finger: FingerTactileData = None + pinky_pad: FingerTactileData = None + + ring_tip: FingerTactileData = None + ring_finger: FingerTactileData = None + ring_pad: FingerTactileData = None + + middle_tip: FingerTactileData = None + middle_finger: FingerTactileData = None + middle_pad: FingerTactileData = None + + index_tip: FingerTactileData = None + index_finger: FingerTactileData = None + index_pad: FingerTactileData = None + + thumb_tip: FingerTactileData = None + thumb_finger: FingerTactileData = None + thumb_middle: FingerTactileData = None + thumb_pad: FingerTactileData = None + + palm: PalmTactileData = None + + def __post_init__(self): + """初始化所有传感器数据""" + # 小拇指 + self.pinky_tip = FingerTactileData(rows=3, cols=3, byteSize=18, name="小拇指指端") + self.pinky_finger = FingerTactileData(rows=12, cols=8, byteSize=192, name="小拇指指尖") + self.pinky_pad = FingerTactileData(rows=10, cols=8, byteSize=160, name="小拇指指腹") + + # 无名指 + self.ring_tip = FingerTactileData(rows=3, cols=3, byteSize=18, name="无名指指端") + self.ring_finger = FingerTactileData(rows=12, cols=8, byteSize=192, name="无名指指尖") + self.ring_pad = FingerTactileData(rows=10, cols=8, byteSize=160, name="无名指指腹") + + # 中指 + self.middle_tip = FingerTactileData(rows=3, cols=3, byteSize=18, name="中指指端") + self.middle_finger = FingerTactileData(rows=12, cols=8, byteSize=192, name="中指指尖") + self.middle_pad = FingerTactileData(rows=10, cols=8, byteSize=160, name="中指指腹") + + # 食指 + self.index_tip = FingerTactileData(rows=3, cols=3, byteSize=18, name="食指指端") + self.index_finger = FingerTactileData(rows=12, cols=8, byteSize=192, name="食指指尖") + self.index_pad = FingerTactileData(rows=10, cols=8, byteSize=160, name="食指指腹") + + # 大拇指 + self.thumb_tip = FingerTactileData(rows=3, cols=3, byteSize=18, name="大拇指指端") + self.thumb_finger = FingerTactileData(rows=12, cols=8, byteSize=192, name="大拇指尖") + self.thumb_middle = FingerTactileData(rows=3, cols=3, byteSize=18, name="大拇指指中") + self.thumb_pad = FingerTactileData(rows=12, cols=8, byteSize=192, name="大拇指指腹") + + # 掌心 + self.palm = PalmTactileData() + + def get_finger_name(self, finger_type): + """获取手指名称""" + from .enums import FingerType + + names = { + FingerType.PINKY: "小拇指", + FingerType.RING: "无名指", + FingerType.MIDDLE: "中指", + FingerType.INDEX: "食指", + FingerType.THUMB: "大拇指" + } + return names.get(finger_type, "未知") \ No newline at end of file diff --git a/cmvr/speaker_client.py b/cmvr/speaker_client.py new file mode 100644 index 0000000..b7aa18a --- /dev/null +++ b/cmvr/speaker_client.py @@ -0,0 +1,134 @@ +import grpc +from typing import Tuple + +from . import generated +from .enums import CMVRErrorCode +from .models import SpeakerState + +class SpeakerClient: + """扬声器客户端""" + + def __init__(self, device_id: str, stub): + self.device_id = device_id + self.stub = stub + + def _create_command_header(self): + """创建命令头""" + header = generated.common_pb2.CommandHeader.Request() + header.device_id = self.device_id + header.timestamp.GetCurrentTime() + return header + + def get_status(self) -> Tuple[CMVRErrorCode, SpeakerState]: + """获取扬声器状态""" + try: + request = generated.speaker_service_pb2.GetSpeakerStateCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.GetStatus(request) + + if not response.header.success: + return CMVRErrorCode.CMVR_RPC_FAILED, SpeakerState() + + state = SpeakerState( + is_initialized=response.state.is_initialized, + is_running=response.state.is_running, + is_decoding=response.state.is_decoding, + is_paused=response.state.is_paused, + volume=response.state.volume + ) + + return CMVRErrorCode.CMVR_SUCCESS, state + + except grpc.RpcError as e: + print(f"获取扬声器状态失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED, SpeakerState() + + def play_audio(self, audio_path: str) -> CMVRErrorCode: + """播放音频""" + try: + request = generated.speaker_service_pb2.PlayAudioCommand.Request() + request.header.CopyFrom(self._create_command_header()) + request.audio_path = audio_path + + response = self.stub.PlayAudio(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"播放音频失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def pause_audio(self) -> CMVRErrorCode: + """暂停播放""" + try: + request = generated.speaker_service_pb2.PauseSpeakerCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.PausePlayback(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"暂停播放失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def resume_audio(self) -> CMVRErrorCode: + """继续播放""" + try: + request = generated.speaker_service_pb2.ResumeSpeakerCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.ResumePlayback(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"继续播放失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def stop_audio(self) -> CMVRErrorCode: + """停止播放""" + try: + request = generated.speaker_service_pb2.StopSpeakerCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.StopPlayback(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"停止播放失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def set_volume(self, volume: float) -> CMVRErrorCode: + """设置音量""" + try: + request = generated.speaker_service_pb2.SetSpeakerVolumeCommand.Request() + request.header.CopyFrom(self._create_command_header()) + request.volume = volume + + response = self.stub.SetVolume(request) + + return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED + + except grpc.RpcError as e: + print(f"设置音量失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED + + def get_volume(self) -> Tuple[CMVRErrorCode, float]: + """获取音量""" + try: + request = generated.speaker_service_pb2.GetSpeakerVolumeCommand.Request() + request.header.CopyFrom(self._create_command_header()) + + response = self.stub.GetVolume(request) + + if not response.header.success: + return CMVRErrorCode.CMVR_RPC_FAILED, 0.0 + + return CMVRErrorCode.CMVR_SUCCESS, response.volume + + except grpc.RpcError as e: + print(f"获取音量失败: {e}") + return CMVRErrorCode.CMVR_RPC_FAILED, 0.0 \ No newline at end of file diff --git a/example_usage.py b/example_usage.py new file mode 100644 index 0000000..f6f0f67 --- /dev/null +++ b/example_usage.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 + +import sys +import os +import time +import random # 导入 random 模块 + +# 添加项目根目录到 Python 路径 +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +# 直接导入生成的模块 +from generated.cmvr.api import biohead_command_pb2 +from cmvr import CMVRGrpcClient, CMVRErrorCode, FacialExpressionState + +def test_biohead(): + """测试仿生头功能""" + print("=== 测试仿生头功能 ===") + + # 初始化客户端 + client = CMVRGrpcClient("192.168.1.6:50051") + if not client.is_connected(): + print("连接服务器失败") + return + + # 获取仿生头 + biohead = client.get_biohead("bio_head") + + # 创建表情 + expression = FacialExpressionState() + + # 设置眉毛,使用随机数并打印 + expression.left_eyebrow_outside_y = random.uniform(0, 1) + expression.left_eyebrow_inside_y = random.uniform(0, 1) + expression.right_eyebrow_outside_y = random.uniform(0, 1) + expression.right_eyebrow_inside_y = random.uniform(0, 1) + print(f"眉毛设置: 左外 {expression.left_eyebrow_outside_y}, 左内 {expression.left_eyebrow_inside_y}, 右外 {expression.right_eyebrow_outside_y}, 右内 {expression.right_eyebrow_inside_y}") + + # 设置眼睑,使用随机数并打印 + expression.left_eye_upper_lid_y = random.uniform(0, 1) + expression.left_eye_lower_lid_y = random.uniform(0, 1) + expression.right_eye_upper_lid_y = random.uniform(0, 1) + expression.right_eye_lower_lid_y = random.uniform(0, 1) + print(f"眼睑设置: 左上 {expression.left_eye_upper_lid_y}, 左下 {expression.left_eye_lower_lid_y}, 右上 {expression.right_eye_upper_lid_y}, 右下 {expression.right_eye_lower_lid_y}") + + # 设置眼球,使用随机数并打印 + expression.left_eye_ball_x = random.uniform(0, 1) + expression.left_eye_ball_y = random.uniform(0, 1) + expression.right_eye_ball_x = random.uniform(0, 1) + expression.right_eye_ball_y = random.uniform(0, 1) + print(f"眼球设置: 左X {expression.left_eye_ball_x}, 左Y {expression.left_eye_ball_y}, 右X {expression.right_eye_ball_x}, 右Y {expression.right_eye_ball_y}") + + # 设置嘴巴,使用随机数并打印 + expression.upper_lip_y = random.uniform(0, 1) + expression.lower_lip_y = random.uniform(0, 1) + print(f"嘴巴设置: 上唇 {expression.upper_lip_y}, 下唇 {expression.lower_lip_y}") + + # 设置下巴,使用随机数并打印 + expression.jaw_x = random.uniform(0, 1) + expression.jaw_y = random.uniform(0, 1) + print(f"下巴设置: X {expression.jaw_x}, Y {expression.jaw_y}") + + # 设置表情 + result = biohead.set_expression(expression) + if result == CMVRErrorCode.CMVR_SUCCESS: + print("设置表情成功") + else: + print(f"设置表情失败,错误码: {result}") + + # 测试流式控制 + print("开始流式控制表情 (5次眨眼)...") + result = biohead.start_stream() + if result != CMVRErrorCode.CMVR_SUCCESS: + print(f"开始流式会话失败,错误码: {result}") + else: + for i in range(5): + # 创建 protobuf 消息用于流式传输 + proto_expr = biohead_command_pb2.FacialExpression() + + # 睁眼,使用随机数并打印 + proto_expr.eyelid.left_upper_y = random.uniform(0, 1) + proto_expr.eyelid.left_lower_y = random.uniform(0, 1) + proto_expr.eyelid.right_upper_y = random.uniform(0, 1) + proto_expr.eyelid.right_lower_y = random.uniform(0, 1) + print(f"第{i+1}次流式睁眼设置: 左上 {proto_expr.eyelid.left_upper_y}, 左下 {proto_expr.eyelid.left_lower_y}, 右上 {proto_expr.eyelid.right_upper_y}, 右下 {proto_expr.eyelid.right_lower_y}") + + result = biohead.stream_expression(proto_expr) + if result == CMVRErrorCode.CMVR_SUCCESS: + print(f"第{i+1}次流式睁眼成功") + else: + print(f"第{i+1}次流式睁眼失败,错误码: {result}") + + time.sleep(0.5) + + # 闭眼,使用随机数并打印 + proto_expr.eyelid.left_upper_y = random.uniform(0, 1) + proto_expr.eyelid.left_lower_y = random.uniform(0, 1) + proto_expr.eyelid.right_upper_y = random.uniform(0, 1) + proto_expr.eyelid.right_lower_y = random.uniform(0, 1) + print(f"第{i+1}次流式闭眼设置: 左上 {proto_expr.eyelid.left_upper_y}, 左下 {proto_expr.eyelid.left_lower_y}, 右上 {proto_expr.eyelid.right_upper_y}, 右下 {proto_expr.eyelid.right_lower_y}") + + result = biohead.stream_expression(proto_expr) + if result == CMVRErrorCode.CMVR_SUCCESS: + print(f"第{i+1}次流式闭眼成功") + else: + print(f"第{i+1}次流式闭眼失败,错误码: {result}") + + time.sleep(0.2) + + # 结束流式会话 + result = biohead.end_stream() + if result == CMVRErrorCode.CMVR_SUCCESS: + print("结束流式会话成功") + else: + print(f"结束流式会话失败,错误码: {result}") + + print("流式控制完成") + + # 获取系统状态 + result = biohead.get_system_status() + if result == CMVRErrorCode.CMVR_SUCCESS: + print("获取系统状态成功") + else: + print(f"获取系统状态失败,错误码: {result}") + + # 测试紧急停止 + result = biohead.emergency_stop() + if result == CMVRErrorCode.CMVR_SUCCESS: + print("紧急停止成功") + else: + print(f"紧急停止失败,错误码: {result}") + + client.close() + +if __name__ == "__main__": + test_biohead() diff --git a/generate_proto.py b/generate_proto.py new file mode 100644 index 0000000..8a81a76 --- /dev/null +++ b/generate_proto.py @@ -0,0 +1,74 @@ +import os +import subprocess +from pathlib import Path + +def generate_proto(): + # 获取当前目录 + current_dir = Path(__file__).parent + proto_dir = current_dir / "protos" + generated_dir = current_dir / "generated" + + # 创建生成的目录 + generated_dir.mkdir(parents=True, exist_ok=True) + + # 编译所有 .proto 文件 + proto_files = [] + for root, _, files in os.walk(proto_dir): + for file in files: + if file.endswith(".proto"): + proto_files.append(os.path.join(root, file)) + + for proto_path in proto_files: + # 计算相对路径 + rel_path = os.path.relpath(proto_path, proto_dir) + output_dir = generated_dir + + cmd = [ + "python", "-m", "grpc_tools.protoc", + f"-I{proto_dir}", + f"--python_out={output_dir}", + f"--grpc_python_out={output_dir}", + proto_path + ] + + print(f"Generating code for {rel_path}...") + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + print(f"Error generating {rel_path}:") + print(result.stderr) + return False + + print("Proto files generated successfully!") + + # 修复生成的代码中的导入路径 + fix_imports(generated_dir) + + return True + +def fix_imports(generated_dir): + """修复生成的代码中的导入路径""" + for root, _, files in os.walk(generated_dir): + for file in files: + if file.endswith(".py"): + file_path = os.path.join(root, file) + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + + # 替换导入路径 + content = content.replace( + "from cmvr.api import", + "from generated.cmvr.api import" + ) + content = content.replace( + "import cmvr.api.", + "import generated.cmvr.api." + ) + + with open(file_path, 'w', encoding='utf-8') as f: + f.write(content) + + print(f"Fixed imports in {file_path}") + +if __name__ == "__main__": + generate_proto() \ No newline at end of file diff --git a/protos/cmvr/api/biohead_command.proto b/protos/cmvr/api/biohead_command.proto new file mode 100644 index 0000000..98527ce --- /dev/null +++ b/protos/cmvr/api/biohead_command.proto @@ -0,0 +1,177 @@ +syntax = "proto3"; + +import "cmvr/api/common.proto"; // 导入通用命令头定义,确保与其他服务接口一致 + +package cmvr.api; + +/** + * 面部表情控制参数定义 + * 包含生物头部机器人面部各部位的运动参数,用于精确控制面部表情 + */ +message FacialExpression { + /** + * 眉毛控制参数 + * 左右眉毛的内外侧垂直位置(0.0-1.0标准化值) + */ + message Eyebrow { + float left_outside_y = 1; // 左眉外侧垂直位置 + float left_inside_y = 2; // 左眉内侧垂直位置 + float right_outside_y = 3; // 右眉外侧垂直位置 + float right_inside_y = 4; // 右眉内侧垂直位置 + } + Eyebrow eyebrow = 3; // 眉毛参数封装 + + /** + * 眼睑控制参数 + * 左右眼睑的上下位置(0.0-1.0标准化值) + */ + message Eyelid { + float left_upper_y = 1; // 左上眼睑垂直位置 + float left_lower_y = 2; // 左下眼睑垂直位置 + float right_upper_y = 3; // 右上眼睑垂直位置 + float right_lower_y = 4; // 右下眼睑垂直位置 + } + Eyelid eyelid = 4; // 眼睑参数封装 + + /** + * 眼球控制参数 + * 左右眼球的二维坐标(-1.0到1.0标准化值,中心为0) + */ + message Eyeball { + float left_x = 1; // 左眼球水平位置 + float left_y = 2; // 左眼球垂直位置 + float right_x = 3; // 右眼球水平位置 + float right_y = 4; // 右眼球垂直位置 + } + Eyeball eyeball = 5; // 眼球参数封装 + + /** + * 鼻子控制参数 + * 鼻子左右侧的垂直位置(0.0-1.0标准化值) + */ + message Nose { + float left_y = 1; // 鼻子左侧垂直位置 + float right_y = 2; // 鼻子右侧垂直位置 + } + Nose nose = 6; // 鼻子参数封装 + + /** + * 嘴巴控制参数 + * 包含嘴唇整体位置和左右唇角细节 + */ + message Mouth { + float upper_lip_y = 1; // 上唇垂直位置 + float upper_lip_z = 2; // 上唇前后位置(Z轴) + float lower_lip_y = 3; // 下唇垂直位置 + float lower_lip_z = 4; // 下唇前后位置(Z轴) + + /** + * 左唇角控制参数 + * 包含上唇、唇角、下唇的坐标点 + */ + message LeftLip { + float upper_x = 1; // 左上唇水平位置 + float upper_y = 2; // 左上唇垂直位置 + float corner_x = 3; // 左唇角水平位置 + float corner_y = 4; // 左唇角垂直位置 + float lower_x = 5; // 左下唇水平位置 + float lower_y = 6; // 左下唇垂直位置 + } + LeftLip left_lip = 5; // 左唇角参数封装 + + /** + * 右唇角控制参数 + * 结构与左唇角对称 + */ + message RightLip { + float upper_x = 1; // 右上唇水平位置 + float upper_y = 2; // 右上唇垂直位置 + float corner_x = 3; // 右唇角水平位置 + float corner_y = 4; // 右唇角垂直位置 + float lower_x = 5; // 右下唇水平位置 + float lower_y = 6; // 右下唇垂直位置 + } + RightLip right_lip = 6; // 右唇角参数封装 + } + Mouth mouth = 7; // 嘴巴参数封装 + + /** + * 下巴控制参数 + * 下巴的二维移动坐标(X/Y轴) + */ + message Jaw { + float x = 1; // 下巴水平位置 + float y = 2; // 下巴垂直位置 + } + Jaw jaw = 8; // 下巴参数封装 +} + +/** + * 设置面部表情命令(集成通用命令头) + * 用于单次设置生物头部的面部表情 + */ +message SetFacialExpression { + message Request { + CommandHeader.Request header = 1; // 通用命令头,包含设备ID和时间戳 + FacialExpression expression = 2; // 具体面部表情参数 + } + + message Feedback { + CommandHeader.Feedback header = 1; // 通用反馈头,包含操作状态和时间戳 + string execution_id = 2; // 表情执行任务ID + float execution_time_ms = 3; // 表情执行耗时(毫秒) + } +} + +/** + * 流式面部表情命令(集成通用命令头) + * 用于连续发送多个面部表情,实现表情动画效果 + */ +message StreamFacialExpression { + message Request { + CommandHeader.Request header = 1; // 通用命令头 + FacialExpression expr = 2; // 执行 + bool eof = 3; // 标记是否为流结束 + } + + message Feedback { + CommandHeader.Feedback header = 1; // 通用反馈头 + FacialExpression expr_diff = 2; // 执行误差 + } +} + +/** + * 获取生物头部状态命令(集成通用命令头) + * 用于查询当前头部各部位的状态和位置 + */ +message GetStatus { + message Request { + CommandHeader.Request header = 1; // 通用命令头 + } + + message Feedback { + CommandHeader.Feedback header = 1; // 通用反馈头 + bool is_moving = 2; // 是否有部位在移动 + string last_request_id = 3; // 上一次请求的ID + repeated float current_positions = 4; // 当前各部位位置参数 + bool camera_recording = 5; // 相机是否在录制 + string active_recording_id = 6; // 活跃录制任务的ID + } +} + +/** + * 紧急停止命令(集成通用命令头) + * 用于立即停止所有面部动作 + */ +message EmergencyStop { + message Request { + CommandHeader.Request header = 1; // 通用命令头 + } + + message Feedback { + CommandHeader.Feedback header = 1; // 通用反馈头 + string stopped_processes = 2; // 已停止的进程列表 + } +} + + diff --git a/protos/cmvr/api/biohead_service.proto b/protos/cmvr/api/biohead_service.proto new file mode 100644 index 0000000..7ca4d83 --- /dev/null +++ b/protos/cmvr/api/biohead_service.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package cmvr.api; +import "cmvr/api/biohead_command.proto"; + +// 生物头部机器人服务接口 +service BioHeadService { + // 设置面部表情 + rpc SetExpression(SetFacialExpression.Request) returns (SetFacialExpression.Feedback){}; + + // 流式表情控制 + //rpc StreamExpression(StreamFacialExpression.Request) returns (StreamFacialExpression.Feedback){}; + + rpc StreamExpression (stream StreamFacialExpression.Request) returns (stream StreamFacialExpression.Feedback); + + + // 获取状态 + rpc GetSystemStatus(GetStatus.Request) returns (GetStatus.Feedback){}; + + // 紧急停止 + rpc EmergencyStop(EmergencyStop.Request) returns (EmergencyStop.Feedback){}; +} \ No newline at end of file diff --git a/protos/cmvr/api/camera_command.proto b/protos/cmvr/api/camera_command.proto new file mode 100644 index 0000000..36dfe9a --- /dev/null +++ b/protos/cmvr/api/camera_command.proto @@ -0,0 +1,161 @@ +syntax = "proto3"; + +import "cmvr/api/common.proto"; + +package cmvr.api; + +message FrameData { + enum FrameType { + U8C1 = 0; + U16C1 = 1; + U8C3 = 2; + U16C3 = 3; + F16C1 = 4; + F32C1 = 5; + } + bytes data = 1; + int32 width = 2; + int32 height = 3; + FrameType type = 4; + string codec = 5; + bool is_key_frame = 6; +} + +message CameraState { + bool is_initialized = 1; + bool is_opened = 2; + bool is_streaming = 3; + bool is_recording = 4; + bool is_error = 5; + string error_message = 6; + int32 fps = 7; + int32 width = 8; + int32 height = 9; +} + +message GetCameraStateCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + CameraState state = 2; + } +} + +message StartCameraCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message StopCameraCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message GetRGBImageCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + FrameData color_frame = 2; + } +} + +message GetDepthImageCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + FrameData depth_frame = 2; + } +} + +message GetRGBDImagesCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + FrameData color_frame = 2; + FrameData depth_frame = 3; + } +} + +message StartCameraRecordingCommand { + message Request { + CommandHeader.Request header = 1; + string video_path = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message StopCameraRecordingCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message GetRGBImageStreamCommand { + message Request { + CommandHeader.Request header = 1; + bool eof = 2; + } + + message Feedback { + CommandHeader.Feedback header = 1; + FrameData color_frame = 2; + int32 seq_no = 3; + } +} + +message GetDepthImageStreamCommand { + message Request { + CommandHeader.Request header = 1; + bool eof = 2; + } + + message Feedback { + CommandHeader.Feedback header = 1; + FrameData depth_frame = 2; + int32 seq_no = 3; + } +} + +message GetRGBDImagesStreamCommand { + message Request { + CommandHeader.Request header = 1; + bool eof = 2; + } + + message Feedback { + CommandHeader.Feedback header = 1; + FrameData color_frame = 2; + FrameData depth_frame = 3; + int32 seq_no = 4; + } +} + + + diff --git a/protos/cmvr/api/camera_service.proto b/protos/cmvr/api/camera_service.proto new file mode 100644 index 0000000..cb9b2ac --- /dev/null +++ b/protos/cmvr/api/camera_service.proto @@ -0,0 +1,21 @@ + +syntax = "proto3"; + +import "cmvr/api/camera_command.proto"; + +package cmvr.api; + +service CameraService { + rpc GetStatus(GetCameraStateCommand.Request) returns (GetCameraStateCommand.Feedback) {} + rpc StartCamera(StartCameraCommand.Request) returns (StartCameraCommand.Feedback) {} + rpc StopCamera(StopCameraCommand.Request) returns (StopCameraCommand.Feedback) {} + rpc GetRGBImage(GetRGBImageCommand.Request) returns (GetRGBImageCommand.Feedback) {} + rpc GetDepthImage(GetDepthImageCommand.Request) returns (GetDepthImageCommand.Feedback) {} + rpc GetRGBDImages(GetRGBDImagesCommand.Request) returns (GetRGBDImagesCommand.Feedback) {} + rpc StartRecording(StartCameraRecordingCommand.Request) returns (StartCameraRecordingCommand.Feedback) {} + rpc StopRecording(StopCameraRecordingCommand.Request) returns (StopCameraRecordingCommand.Feedback) {} + + rpc GetRGBImageStream(stream GetRGBImageStreamCommand.Request) returns (stream GetRGBImageStreamCommand.Feedback) {} + rpc GetDepthImageStream(stream GetDepthImageStreamCommand.Request) returns (stream GetDepthImageStreamCommand.Feedback) {} + rpc GetRGBDImagesStream(stream GetRGBDImagesStreamCommand.Request) returns (stream GetRGBDImagesStreamCommand.Feedback) {} +} \ No newline at end of file diff --git a/protos/cmvr/api/common.proto b/protos/cmvr/api/common.proto new file mode 100644 index 0000000..19c35bd --- /dev/null +++ b/protos/cmvr/api/common.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package cmvr.api; + +import "google/protobuf/timestamp.proto"; + +message DeviceLifecycle { + enum Lifecycle { + STATE_INIT = 0; + STATE_READY = 1; + STATE_RUNNING = 2; + STATE_ERROR = 3; + STATE_ESTOP = 4; + STATE_STOP = 5; + } + Lifecycle state = 1; +} + +message CommandHeader { + message Request { + string device_id = 1; // 目标设备名称 + google.protobuf.Timestamp timestamp = 2; // 请求时间 + } + + message Feedback { + bool success = 1; // 是否成功 + string error_message = 2; // 错误信息(成功时为空) + google.protobuf.Timestamp timestamp = 3; // 回复时间 + } +} diff --git a/protos/cmvr/api/dexhand_command.proto b/protos/cmvr/api/dexhand_command.proto new file mode 100644 index 0000000..5e89c97 --- /dev/null +++ b/protos/cmvr/api/dexhand_command.proto @@ -0,0 +1,149 @@ +syntax = "proto3"; + +import "cmvr/api/common.proto"; + +package cmvr.api; + +message FreedomValue { + int32 id = 1; // 自由度id 0-6 分别对应六个自由度 小拇指 无名指 中指 食指 大拇指弯曲 大拇指旋转 + float value = 2; // 值 0-1百分比 +} + +message RH56DFTPDexHand { + int32 dof_id = 1; // 自由度ID,唯一标识不同的关节或自由度 + int32 angle = 2; // 自由度角度 + int32 speed = 3; // 自由度速度 + int32 force = 4; // 实际受力 + int32 position = 5; // 执行器位置 + int32 current = 6; // 执行器实际电流 + int32 temperature = 7; // 执行器温度 + int32 error = 8; // 执行器故障信息 + repeated string error_message = 9; // 错误消息列表 +} +// 传感器数据 +// 传感器数据 +message SensorData { + // 手指类型(掌心作为特殊类型) + enum FingerType { + PINKY = 0; // 小拇指 + RING = 1; // 无名指 + MIDDLE_FINGER = 2; // 中指(避免与PartType.MIDDLE冲突) + INDEX = 3; // 食指 + THUMB = 4; // 大拇指 + PALM = 5; // 掌心 + } + + // 部位类型(指中改为THUMB_MIDDLE以避免冲突) + enum PartType { + TIP = 0; // 指端 + FINGER = 1; // 指尖 + PAD = 2; // 指腹 + THUMB_MIDDLE = 3; // 大拇指指中(仅大拇指有) + PALM_PAD = 4; // 掌心部位(对应PalmTactileData) + } + + FingerType finger_type = 4; // 手指类型(掌心使用PALM) + PartType part_type = 5; // 部位类型 + string sensor_name = 6; // 传感器名称(如"小拇指指端") + + message RowData { + repeated int32 values = 1 [packed = true]; // 一维数组,存放每行的浮点数值 + } + repeated RowData data = 1; // 二维数组,每行数据是 RowData 类型 + int32 rows = 2; // 行 + int32 cols = 3; // 列 +} + + +message DexHandState { + bool is_initialized = 1; // 是否初始化 + repeated RH56DFTPDexHand hands = 2; // 包含多个自由度状态 +} + +message GetDexHandStateCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + DexHandState state = 2; + } +} + +message SetDexHandPositionsCommand { + message Request { + CommandHeader.Request header = 1; + repeated FreedomValue values = 2; + } + + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message SetDexHandAnglesCommand { + message Request { + CommandHeader.Request header = 1; + repeated FreedomValue values = 2; + } + + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message SetDexHandForceCommand { + message Request { + CommandHeader.Request header = 1; + repeated FreedomValue values = 2; + } + + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message SetDexHandSpeedCommand { + message Request { + CommandHeader.Request header = 1; + repeated FreedomValue values = 2; + } + + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message SetDexHandPresetActCommand { + message Request { + CommandHeader.Request header = 1; + int32 presetActId = 2; + } + + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message GetSensorDataCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + repeated SensorData sensor = 2;//所有传感器的数据 + } +} + +message GetSensorDataStreamCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + repeated SensorData sensor = 2;//所有传感器的数据 + } +} \ No newline at end of file diff --git a/protos/cmvr/api/dexhand_service.proto b/protos/cmvr/api/dexhand_service.proto new file mode 100644 index 0000000..13593d3 --- /dev/null +++ b/protos/cmvr/api/dexhand_service.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +import "cmvr/api/dexhand_command.proto"; + +package cmvr.api; + + +service DexHandService { + // 基本控制 + rpc GetStatus(GetDexHandStateCommand.Request) returns (GetDexHandStateCommand.Feedback); + rpc SetDexHandPos(SetDexHandPositionsCommand.Request) returns (SetDexHandPositionsCommand.Feedback); + rpc SetDexHandAngle(SetDexHandAnglesCommand.Request) returns (SetDexHandAnglesCommand.Feedback); + rpc SetDexHandForce(SetDexHandForceCommand.Request) returns (SetDexHandForceCommand.Feedback); + rpc SetDexHandSpeed(SetDexHandSpeedCommand.Request) returns (SetDexHandSpeedCommand.Feedback); + rpc SetDexHandPresetAct(SetDexHandPresetActCommand.Request) returns (SetDexHandPresetActCommand.Feedback); + rpc GetSensorData(GetSensorDataCommand.Request) returns (GetSensorDataCommand.Feedback); + rpc GetSensorDataStream(stream GetSensorDataStreamCommand.Request) returns (stream GetSensorDataStreamCommand.Feedback); +} \ No newline at end of file diff --git a/protos/cmvr/api/geometry.proto b/protos/cmvr/api/geometry.proto new file mode 100644 index 0000000..a7ab001 --- /dev/null +++ b/protos/cmvr/api/geometry.proto @@ -0,0 +1,67 @@ +syntax = "proto3"; + +package cmvr.api; + + +message Vec2 { + double x = 1; + double y = 2; +} + +message Vec3 { + double x = 1; + double y = 2; + double z = 3; +} + +message SE2Pose { + Vec2 position = 1; // (m) + double angle = 2; // (rad) +} + +message SE2Velocity { + Vec2 linear = 1; // (m/s) + double angular = 2; // (rad/s) +} + +message Quaternion { + double x = 1; + double y = 2; + double z = 3; + double w = 4; +} + +message EulerAngleZYX { + double z = 1; + double y = 2; + double x = 3; +} + +message SE3Pose { + Vec3 position = 1; // (m) + oneof rotation { + Quaternion quaternion = 2; + EulerAngleZYX euler = 3; + } +} + +message Inertial { + // Mass (kg) + double mass = 1; + + // Center of mass (m) + Vec3 center_of_mass = 2; + + // Inertia tensor + Inertia inertia = 3; +} + +// Inertia tensor components (kg*m^2) +message Inertia { + double ixx = 1; + double iyy = 2; + double izz = 3; + double ixy = 4; + double ixz = 5; + double iyz = 6; +} diff --git a/protos/cmvr/api/humanoid_robot.proto b/protos/cmvr/api/humanoid_robot.proto new file mode 100644 index 0000000..e922115 --- /dev/null +++ b/protos/cmvr/api/humanoid_robot.proto @@ -0,0 +1,32 @@ + + +syntax = "proto3"; +package cmvr.api; +import "cmvr/api/common.proto"; + +message JointCmd { + string joint_name = 1; // 关节名称 + double rad = 2; // 弧度 + double vel = 3; // 角速度,rad/s +} + + +message MoveJ{ + message Request{ + CommandHeader.Request header = 1; + repeated JointCmd cmds = 2; + double vel = 3; + double acc = 4; + } + + message Response{ + CommandHeader.Feedback header= 1; + } + +} + +service HumanoidRobotService{ + rpc torqueOff(CommandHeader.Request) returns (CommandHeader.Feedback); + rpc torqueOn(CommandHeader.Request) returns (CommandHeader.Feedback); + rpc moveJ(MoveJ.Request) returns (MoveJ.Response); +} \ No newline at end of file diff --git a/protos/cmvr/api/microphone_command.proto b/protos/cmvr/api/microphone_command.proto new file mode 100644 index 0000000..4183415 --- /dev/null +++ b/protos/cmvr/api/microphone_command.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; + +import "cmvr/api/common.proto"; + +package cmvr.api; + +message MicState { + bool is_initialized = 1; + bool is_running = 2; + bool is_recording = 3; + int32 volume = 4; + string error_message = 5; +} +message GetMicStateCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + MicState state = 2; + } +} +message StartMicRecordingCommand { + message Request { + CommandHeader.Request header = 1; + string file_path = 2; + } + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message StopMicRecordingCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message PauseMicRecordingCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message ResumeMicRecordingCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message SetMicPhoneVolumeCommand { + message Request { + CommandHeader.Request header = 1; + int32 volume = 2; // 音量值(0 ~ 100) + } + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message GetMicPhoneVolumeCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + int32 volume = 2; // 当前音量值 + } +} + diff --git a/protos/cmvr/api/microphone_service.proto b/protos/cmvr/api/microphone_service.proto new file mode 100644 index 0000000..fcf31b7 --- /dev/null +++ b/protos/cmvr/api/microphone_service.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +import "cmvr/api/microphone_command.proto"; + +package cmvr.api; + + +service MicPhoneService { + // 基本控制 + rpc GetStatus(GetMicStateCommand.Request) returns (GetMicStateCommand.Feedback); + rpc StartRecord(StartMicRecordingCommand.Request) returns (StartMicRecordingCommand.Feedback); + rpc StopRecord(StopMicRecordingCommand.Request) returns (StopMicRecordingCommand.Feedback); + rpc PauseRecord(PauseMicRecordingCommand.Request) returns (PauseMicRecordingCommand.Feedback); + rpc ResumeRecord(ResumeMicRecordingCommand.Request) returns (ResumeMicRecordingCommand.Feedback); + + // 音量控制 + rpc SetVolume(SetMicPhoneVolumeCommand.Request) returns (SetMicPhoneVolumeCommand.Feedback); + rpc GetVolume(GetMicPhoneVolumeCommand.Request) returns (GetMicPhoneVolumeCommand.Feedback); + +} \ No newline at end of file diff --git a/protos/cmvr/api/speaker_command.proto b/protos/cmvr/api/speaker_command.proto new file mode 100644 index 0000000..f5a23b8 --- /dev/null +++ b/protos/cmvr/api/speaker_command.proto @@ -0,0 +1,99 @@ +syntax = "proto3"; + +import "cmvr/api/common.proto"; + +package cmvr.api; + + +// 音频数据格式(与麦克风类似,但用于输出) +message AudioData { + enum AudioFormat { + PCM = 0; + MP3 = 1; + AAC = 2; + WAV = 3; + } + bytes data = 1; // 音频二进制数据 + int32 sample_rate = 2; // 采样率(Hz) + int32 channels = 3; // 声道数 + AudioFormat format = 4; // 音频格式 + string codec = 5; // 编码方式 +} + +// 扬声器状态 +message SpeakerState { + bool is_initialized = 1; // 是否已初始化 + bool is_running = 2; // 是否正在播放 + bool is_decoding = 3; + bool is_paused = 5; + int32 volume = 6; // 当前音量(0 ~ 100) + string error_message = 7; // 错误信息 +} + +// 各种扬声器命令 +message GetSpeakerStateCommand { + message Request { + CommandHeader.Request header = 1; + } + + message Feedback { + CommandHeader.Feedback header = 1; + SpeakerState state = 2; + } +} + +message PlayAudioCommand { + message Request { + CommandHeader.Request header = 1; + string audio_path = 2; + } + message Feedback { CommandHeader.Feedback header = 1; } +} + +message StopSpeakerCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message PauseSpeakerCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message ResumeSpeakerCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message SetSpeakerVolumeCommand { + message Request { + CommandHeader.Request header = 1; + int32 volume = 2; // 音量值(0 ~ 100) + } + message Feedback { + CommandHeader.Feedback header = 1; + } +} + +message GetSpeakerVolumeCommand { + message Request { + CommandHeader.Request header = 1; + } + message Feedback { + CommandHeader.Feedback header = 1; + int32 volume = 2; // 当前音量值 + } +} + diff --git a/protos/cmvr/api/speaker_service.proto b/protos/cmvr/api/speaker_service.proto new file mode 100644 index 0000000..9cbc77c --- /dev/null +++ b/protos/cmvr/api/speaker_service.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +import "cmvr/api/speaker_command.proto"; + +package cmvr.api; + + +service SpeakerService { + // 基本控制 + rpc GetStatus(GetSpeakerStateCommand.Request) returns (GetSpeakerStateCommand.Feedback); + rpc PlayAudio(PlayAudioCommand.Request) returns (PlayAudioCommand.Feedback); + rpc StopPlayback(StopSpeakerCommand.Request) returns (StopSpeakerCommand.Feedback); + rpc PausePlayback(PauseSpeakerCommand.Request) returns (PauseSpeakerCommand.Feedback); + rpc ResumePlayback(ResumeSpeakerCommand.Request) returns (ResumeSpeakerCommand.Feedback); + + // 音量控制 + rpc SetVolume(SetSpeakerVolumeCommand.Request) returns (SetSpeakerVolumeCommand.Feedback); + rpc GetVolume(GetSpeakerVolumeCommand.Request) returns (GetSpeakerVolumeCommand.Feedback); + +} \ No newline at end of file diff --git a/protos/cmvr/api/system_command.proto b/protos/cmvr/api/system_command.proto new file mode 100644 index 0000000..7498dd6 --- /dev/null +++ b/protos/cmvr/api/system_command.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +import "cmvr/api/common.proto"; + +package cmvr.api; + +enum DeviceType { + AGV = 0; + Battery = 1; + Camera = 2; + DexHand = 3; + Gripper = 4; + Microphone = 5; + Robot = 6; + Speaker = 7; + Unknown = 20; +} + +message DeviceList { + string device_id = 1; + DeviceType device_type = 2; +} + +message GetSystemInfoCommand { + message Request {} + + message Feedback { + CommandHeader.Feedback header = 1; + string system_name = 2; + string version = 3; + string description = 4; + string os = 5; + string kernel_version = 6; + string architecture = 7; + } +} + +message GetSystemStatusCommand { + message Request {} + + message Feedback { + CommandHeader.Feedback header = 1; + float cpu_usage = 2; + float mem_total_mb = 3; + float mem_used_mb = 4; + float disk_total_gb = 5; + float disk_used_gb = 6; + repeated DeviceList device_list = 7; + } +} diff --git a/protos/cmvr/api/system_service.proto b/protos/cmvr/api/system_service.proto new file mode 100644 index 0000000..3ae6654 --- /dev/null +++ b/protos/cmvr/api/system_service.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +import "cmvr/api/system_command.proto"; + +package cmvr.api; + + +service SystemService { + rpc GetSystemInfo(GetSystemInfoCommand.Request) returns (GetSystemInfoCommand.Feedback) {} + rpc GetSystemStatus(GetSystemStatusCommand.Request) returns (GetSystemStatusCommand.Feedback) {} +} \ No newline at end of file diff --git a/protos/cmvr/api/test_service.proto b/protos/cmvr/api/test_service.proto new file mode 100644 index 0000000..708479d --- /dev/null +++ b/protos/cmvr/api/test_service.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package cmvr.api; + +message TestReqeust { + string data = 1; +} + +message TestResponse { + string data = 1; +} + +service TestService{ + rpc Call (TestReqeust) returns (TestResponse); +} + + diff --git a/protos/cmvr/msgs/can_card_parameter.proto b/protos/cmvr/msgs/can_card_parameter.proto new file mode 100644 index 0000000..5237042 --- /dev/null +++ b/protos/cmvr/msgs/can_card_parameter.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package cmvr.msgs; + +message CANCardParameter { + enum CANCardBrand { + FAKE_CAN = 0; + ESD_CAN = 1; + SOCKET_CAN_RAW = 2; + HERMES_CAN = 3; + } + + enum CANCardType { + PCI_CARD = 0; + USB_CARD = 1; + } + + enum CANChannelId { + CHANNEL_ID_ZERO = 0; + CHANNEL_ID_ONE = 1; + CHANNEL_ID_TWO = 2; + CHANNEL_ID_THREE = 3; + CHANNEL_ID_FOUR = 4; + CHANNEL_ID_FIVE = 5; + CHANNEL_ID_SIX = 6; + CHANNEL_ID_SEVEN = 7; + } + + enum CANInterface { + NATIVE = 0; + VIRTUAL = 1; + SLCAN = 2; + } + + enum BAUDRATE { + BCAN_BAUDRATE_1M = 0; + BCAN_BAUDRATE_500K = 1; + BCAN_BAUDRATE_250K = 2; + BCAN_BAUDRATE_150K = 3; + BCAN_BAUDRATE_NUM = 4; + } + + // CAN卡驱动类型配置 | 根据所用的CAN卡硬件型号或驱动类型配置 + optional CANCardBrand brand = 1; + // CAN卡硬件接口类型配置 | 根据所用的CAN卡硬件接口类型或驱动类型配置 + optional CANCardType type = 2; + // CAN卡端口号配置 | 根据所连接的CAN卡端口号配置 + optional CANChannelId channel_id = 3; + // CAN卡软件接口配置 + optional CANInterface interface = 4; + // CAN卡端口数量配置 + optional uint32 num_ports = 5; + // CAN卡波特率配置 + optional BAUDRATE baudrate = 6; +} diff --git a/protos/cmvr/msgs/canopen.proto b/protos/cmvr/msgs/canopen.proto new file mode 100644 index 0000000..23914e7 --- /dev/null +++ b/protos/cmvr/msgs/canopen.proto @@ -0,0 +1,202 @@ +syntax = "proto3"; + +package cmvr.msgs; + +message SdoFrame { + uint32 node_id = 1; // 节点ID + CommandSpecifier cs = 2; // SDO命令字 + ObIndex index = 3; // 对象字典索引 + ObSubIndex sub_index = 4; // 子索引 + uint32 data = 5; // 数据区 +} + +enum PdoBaseId{ + + PDO_BASE_ID_UNSPECIFIED = 0; + + RPDO1_BASE_ID_200 = 0x200; + RPDO2_BASE_ID_300 = 0x300; + RPDO3_BASE_ID_400 = 0x400; + RPDO4_BASE_ID_500 = 0x500; + + + TPDO1_BASE_ID_180 = 0x180; + TPDO2_BASE_ID_280 = 0x280; + TPDO3_BASE_ID_380 = 0x380; + TPDO4_BASE_ID_480 = 0x480; +} + +// PDO 传输类型 +enum TransmissionType { + + // 同步传输 + SYNC_EVENT_DRIVEN = 0x00; // 非循环同步 + SYNC_CYCLIC = 0x01; // 循环同步 + + // 远程请求触发 + REMOTE_SYNC = 0xFC; // 远程同步 + REMOTE_ASYNC = 0xFD; // 远程异步 + + // 异步传输 + ASYNC_MANUFACTURER_SPECIFIC = 0xFE; // 异步,制造商特定事件 + ASYNC_DEVICE_SPECIFIC = 0xFF; // 异步,设备子协议特定事件 +} + + + +enum CommandSpecifier { + CS_NO = 0; // 默认无效 + CS_WRITE_ONE_BYTE = 0x2F; // 写 1 字节 + CS_WRITE_TWO_BYTES = 0x2B; // 写 2 字节 + CS_WRITE_THREE_BYTES = 0x27; // 写 3 字节 + CS_WRITE_FOUR_BYTES = 0x23; // 写 4 字节 + CS_WRITE_SUCCESS_RESPONSE = 0x60; // 写成功响应 + + CS_READ_REQUEST = 0x40; // 发起读取请求 + CS_READ_RESPONSE_ONE_BYTE = 0x4F; // 响应 1 字节 + CS_READ_RESPONSE_TWO_BYTES = 0x4B;// 响应 2 字节 + CS_READ_RESPONSE_THREE_BYTES = 0x47; + CS_READ_RESPONSE_FOUR_BYTES = 0x43; + + CS_EXCEPTION_RESPONSE = 0x80; // 异常响应 +} + + + + +enum NmtState { + // 0x00 - 正在初始化(Initializing) + NMT_INITIALIZING = 0x00; + + // 0x01 - 未连接 / 应用复位(Reset Application) + NMT_RESET_APPLICATION = 0x01; + + // 0x02 - 正在连接(Connecting) + NMT_CONNECTING = 0x02; + + // 0x03 - 准备(Preparing) + NMT_PREPARING = 0x03; + + // 0x04 - 停止状态(Stopped),开启 SDO,关闭 PDO + NMT_STOPPED = 0x04; + + // 0x05 - 运行状态(Operational),开启 SDO、PDO、NMT + NMT_OPERATIONAL = 0x05; + + // 0x7F - 预运行状态(Pre-Operational),开启 SDO,关闭 PDO + NMT_PRE_OPERATIONAL = 0x7F; +} + + + +enum NmtCommand { + // 未指定命令 + NMT_COMMAND_UNSPECIFIED = 0x00; + + // 0x01 - 启动远程节点(Start Remote Node) + NMT_START_REMOTE_NODE = 0x01; + + // 0x02 - 停止远程节点(Stop Remote Node) + NMT_STOP_REMOTE_NODE = 0x02; + + // 0x80 - 进入预操作状态(Enter Pre-Operational) + NMT_ENTER_PRE_OPERATIONAL = 0x80; + + // 0x81 - 复位节点(Reset Node) + NMT_RESET_NODE = 0x81; + + // 0x82 - 复位通信(Reset Communication) + NMT_RESET_COMMUNICATION = 0x82; +} + + +// 索引 +enum ObIndex { + INDEX_ZERO = 0; + USER_SAVE_PARA_2000 = 0x2000; // 下发命令 1 保存参数 + POSITION_OFFSET_2008 = 0x2008; // 位置偏移,子索引 0x00,用于设置零点、起始位置 + // Error Codes + ERROR_CODE_6007 = 0x6007; + ERROR_CODE_603F = 0x603F; + + // Control and Status + CONTROL_WORD_6040 = 0x6040; + STATUS_WORD_6041 = 0x6041; + + // Operation Modes + OPERATION_MODE_6060 = 0x6060; + MODE_DISPLAY_6061 = 0x6061; + + // Actual Values + ACTUAL_POSITION_6064 = 0x6064; + ACTUAL_SPEED_606C = 0x606C; + ACTUAL_CURRENT_6078 = 0x6078; + + // Torque-related + TARGET_TORQUE_6071 = 0x6071; + MAX_TORQUE_6072 = 0x6072; + DEMAND_TORQUE_6074 = 0x6074; + + // Position-related + TARGET_POSITION_607A = 0x607A; + SOFTWARE_POSITION_LIMIT_607D = 0x607D; // Sub-indexes: 1, 2 + + // Speed-related + MAX_SPEED_607F = 0x607F; + PROFILE_SPEED_6081 = 0x6081; + PROFILE_ACCELERATION_6083 = 0x6083; + PROFILE_DECELERATION_6084 = 0x6084; + + // Same as DEMAND_TORQUE? Verify correctness. + // TORQUE_SLOPE_6074 = 0x6074; + + // PID Control + CURRENT_LOOP_PID_60F6 = 0x60F6; // Sub-indexes: 1, 2 + SPEED_LOOP_PID_60F9 = 0x60F9; // Sub-indexes: 1, 2 + POSITION_LOOP_PID_60FB = 0x60FB; // Sub-indexes: 1, 2, 3 + + // Target Speed + TARGET_SPEED_60FF = 0x60FF; + + QUICK_STOP_OPTION_605A = 0x605A; + QUICK_STOP_DECEL_6085 = 0x6085; + + // ------------------------- + // PDO 通信参数对象(Communication Object) + RPDO1_COMM_1400 = 0x1400; + RPDO2_COMM_1401 = 0x1401; + RPDO3_COMM_1402 = 0x1402; + RPDO4_COMM_1403 = 0x1403; + + TPDO1_COMM_1800 = 0x1800; + TPDO2_COMM_1801 = 0x1801; + TPDO3_COMM_1802 = 0x1802; + TPDO4_COMM_1803 = 0x1803; + + // PDO 映射对象(Mapping Object) + RPDO1_MAP_1600 = 0x1600; + RPDO2_MAP_1601 = 0x1601; + RPDO3_MAP_1602 = 0x1602; + RPDO4_MAP_1603 = 0x1603; + + TPDO1_MAP_1A00 = 0x1A00; + TPDO2_MAP_1A01 = 0x1A01; + TPDO3_MAP_1A02 = 0x1A02; + TPDO4_MAP_1A03 = 0x1A03; + + PRODUCER_HEARTBEAT_TIME = 0x1017; +} + +// 子索引 +enum ObSubIndex { + SUB_INDEX_0 = 0; + SUB_INDEX_1 = 1; + SUB_INDEX_2 = 2; + SUB_INDEX_3 = 3; + SUB_INDEX_4 = 4; + SUB_INDEX_5 = 5; + SUB_INDEX_6 = 6; + SUB_INDEX_7 = 7; +} + + diff --git a/protos/cmvr/msgs/error_code.proto b/protos/cmvr/msgs/error_code.proto new file mode 100644 index 0000000..66291a6 --- /dev/null +++ b/protos/cmvr/msgs/error_code.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package cmvr.msgs; + +// Error codes enum for API's categorized by modules. +enum ErrorCode { + // No error, returns on success. + OK = 0; + + // Canbus module error codes start from here. + CANBUS_ERROR = 2000; + CAN_CLIENT_ERROR_BASE = 2100; + CAN_CLIENT_ERROR_OPEN_DEVICE_FAILED = 2101; + CAN_CLIENT_ERROR_FRAME_NUM = 2102; + CAN_CLIENT_ERROR_SEND_FAILED = 2103; + CAN_CLIENT_ERROR_RECV_FAILED = 2104; + + // motor + MOTOR_ERROR = 3000; + MOTOR_ERROR_SET_ZERO = 3001; + + + +} + +message StatusPb { + ErrorCode error_code = 1; + string msg = 2; +} diff --git a/protos/cmvr/msgs/motor.proto b/protos/cmvr/msgs/motor.proto new file mode 100644 index 0000000..a0336de --- /dev/null +++ b/protos/cmvr/msgs/motor.proto @@ -0,0 +1,118 @@ +syntax = "proto3"; +import "cmvr/msgs/canopen.proto"; +package cmvr.msgs; + + + +/* -------------------------------------------------------- + * CAN OPEN 定义 + * --------------------------------------------------------*/ + + + + +// 电机控制模式(Operation Mode) +enum RunMode { + // 未指定 + RUN_MODE_UNSPECIFIED = 0; + + // Profile Position Mode - 位置模式 + RUN_MODE_PROFILE_POSITION = 1; + + // Velocity Mode - 简单速度模式 + RUN_MODE_VELOCITY = 2; + + // Profile Velocity Mode - 速度模式(带轮廓) + RUN_MODE_PROFILE_VELOCITY = 3; + + // Torque Mode - 转矩模式 + RUN_MODE_TORQUE = 4; + + // Homing Mode - 回零模式 + RUN_MODE_HOMING = 5; + + // Interpolation Mode - 位置插补模式 + RUN_MODE_INTERPOLATED_POSITION = 7; + + // Cyclic Synchronous Position Mode - 周期位置模式 + RUN_MODE_CYCLIC_SYNC_POSITION = 8; + + // Cyclic Synchronous Velocity Mode - 周期速度模式 + RUN_MODE_CYCLIC_SYNC_VELOCITY = 9; + + // Cyclic Synchronous Current Mode - 周期转矩模式 + RUN_MODE_CYCLIC_SYNC_CURRENT = 10; +} + + + + + +// 电机状态数据 +message MotorStatus { + RunMode run_mode = 1; // 当前运行模式 + + + int32 current = 2; // 当前电流(单位:mA) + int32 target_current = 3; // 目标电流(单位:mA) + int32 speed = 4; // 当前速度,转换为度每秒:(speed / 100 / 减速比) * 360 + int32 target_speed = 5; // 目标速度,单位同上 + int32 position = 6; // 当前电机位置(位置转角度:(position / 65536 / 减速比) * 360) + int32 target_position = 7; // 目标位置(非位置模式无效) + uint32 error_state = 8; // 错误状态位(见位说明) + + int32 speed_kp = 9; // 速度环Kp,含模式和速度信息(bit复用) + int32 speed_ki = 10; // 速度环Ki,含模式和速度信息(bit复用) + int32 speed_kd = 11; // 速度环Kd + + int32 position_kp = 12; // 位置环Kp + int32 position_ki = 13; // 位置环Ki + int32 position_kd = 14; // 位置环Kd + + int32 bus_voltage = 15; // 母线电压 + + int32 max_abs_current = 16; // 最大电流绝对值(单位:mA) + int32 max_pos_current = 17; // 最大正电流(单位:mA) + int32 min_neg_current = 18; // 最小负电流(单位:mA) + + int32 max_pos_accel = 19; // 最大正向加速度 + int32 min_neg_accel = 20; // 最小负向加速度 + int32 max_pos_velocity = 21; // 最大正向速度(转化为度/秒) + int32 min_neg_velocity = 22; // 最小负向速度(转化为度/秒) + + int32 max_pos_position = 23; // 最大正向位置(角度) + int32 min_neg_position = 24; // 最小负向位置(角度) + + int32 motor_temp = 25; // 电机温度(单位:摄氏度) + int32 board_temp = 26; // 电路板温度(单位:摄氏度) + + int32 current_kp = 27; // 电流P + int32 current_ki = 28; // 电流I + int32 current_kd = 29; // 电流D + + int32 motor_type = 30; // 电机型号(复合字段,包含刹车、ACTUATOR_TYPE等) + int32 motor_version = 31; // 电机版本号(软件+硬件,16进制格式) + int32 software_version = 32;// 软件版本号(16进制格式) + + int32 position_offset = 33; // 当前位置偏移(编码器位置 - 偏移值) + bytes csp_data = 34; // 获取CSP,8字节数据 + int32 encoder_voltage = 35; // 编码器电池电压(新版硬件支持) + int32 encoder_state = 36; // 编码器状态(双编码器=外圈位置,单编码器=状态) + + int32 overvoltage_limit = 37; // 过压阈值(单位:V) + int32 undervoltage_limit = 38; // 欠压阈值(单位:V) + int32 coil_over_temp = 39; // 电机线圈过温阈值(单位:℃) + int32 driver_over_temp = 40; // 驱动板过温阈值(单位:℃) + + /* --------------------- + * CANOPEN 特有的参数 + * --------------------*/ + SdoFrame sdo_response = 41; + NmtState nmt_state = 42; + // 只保留值,具体值在使用的时候使用联合体去解析 + uint32 ctrl_word = 43; + uint32 status_word = 44; + +} + + diff --git a/protos/cmvr/msgs/robot_detail.proto b/protos/cmvr/msgs/robot_detail.proto new file mode 100644 index 0000000..19a6595 --- /dev/null +++ b/protos/cmvr/msgs/robot_detail.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +import "cmvr/msgs/canopen.proto"; +import "cmvr/msgs/motor.proto"; +package cmvr.msgs; + + +// 包含机器人的所有信息 + +message RobotDetail{ + + // key 电机 nodeid + map motors = 1; // node_id => status +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f02eaba --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +grpcio==1.60.0 +grpcio-tools==1.60.0 +protobuf==4.25.3 +Pillow==10.1.0 +numpy==1.24.4 +opencv-python==4.9.0.80 \ No newline at end of file