copy code file

This commit is contained in:
linbo 2025-08-21 14:05:27 +08:00
parent 2bc6a24f94
commit 8b9282c647
34 changed files with 2579 additions and 2 deletions

View File

@ -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 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 branch -M main
git push -uf origin main git push -uf origin main
``` ```
## Integrate with your tools ## 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 ## Collaborate with your team

15
cmvr/__init__.py Normal file
View File

@ -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'
]

222
cmvr/biohead_client.py Normal file
View File

@ -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()

138
cmvr/camera_client.py Normal file
View File

@ -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

73
cmvr/client.py Normal file
View File

@ -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

89
cmvr/dexhand_client.py Normal file
View File

@ -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

27
cmvr/enums.py Normal file
View File

@ -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 # 大拇指

74
cmvr/micphone_client.py Normal file
View File

@ -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

197
cmvr/models.py Normal file
View File

@ -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, "未知")

134
cmvr/speaker_client.py Normal file
View File

@ -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

135
example_usage.py Normal file
View File

@ -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()

74
generate_proto.py Normal file
View File

@ -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()

View File

@ -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.01.00
*/
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; //
}
}

View File

@ -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){};
}

View File

@ -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;
}
}

View File

@ -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) {}
}

View File

@ -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; //
}
}

View File

@ -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;//
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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; //
}
}

View File

@ -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);
}

View File

@ -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; //
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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) {}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 SDOPDONMT
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;
}

View File

@ -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;
}

View File

@ -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; // Kpbit复用
int32 speed_ki = 10; // Kibit复用
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; // CSP8
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;
}

View File

@ -0,0 +1,14 @@
syntax = "proto3";
import "cmvr/msgs/canopen.proto";
import "cmvr/msgs/motor.proto";
package cmvr.msgs;
//
message RobotDetail{
// key nodeid
map<uint32, MotorStatus> motors = 1; // node_id => status
}

6
requirements.txt Normal file
View File

@ -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