Compare commits

...

12 Commits
main ... python

Author SHA1 Message Date
d18ccb33f2 update 2025-10-21 10:56:45 +08:00
891529a095 update 2025-10-21 10:54:48 +08:00
b428db1372 update 2025-10-21 10:38:31 +08:00
882a9d9133 update robot grpc 2025-10-21 09:33:47 +08:00
7d4e87f25e update 2025-08-27 16:47:47 +08:00
a6ab0f3790 update 2025-08-27 16:46:12 +08:00
bb6dbe5bdd update 2025-08-27 09:14:32 +08:00
350a5e8bec 更改 2025-08-27 08:56:33 +08:00
5735d899fb update 2025-08-26 17:19:45 +08:00
bd79b8fbd1 更改 2025-08-25 17:35:39 +08:00
49dd1c4248 update 2025-08-25 10:34:44 +08:00
8b9282c647 copy code file 2025-08-21 14:05:27 +08:00
44 changed files with 6498 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

557
biohead.py Normal file
View File

@ -0,0 +1,557 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
biohead.py 基于gRPC的仿生头控制脚本
功能
1. 控制单个表情参数
2. 控制多个表情参数
3. 退出
4. 自动增减模式从0到1再到0循环变化
5. 设置自动模式参数数量
6. 眨眼模式执行预设的眨眼动作
7. 打哈欠模式模拟打哈欠动作
8. 说话模式模拟嘴巴开合动作
"""
import sys
import time
import random
import os
import grpc
from typing import Generator
# 添加项目根目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# 导入gRPC相关模块
from generated.cmvr.api import biohead_command_pb2
from cmvr import CMVRGrpcClient, CMVRErrorCode, FacialExpressionState
def angle_to_normalized(angle):
"""将角度值(0-180)转换为归一化值(0-1)"""
return max(0.0, min(1.0, angle / 180.0))
def normalized_to_angle(normalized):
"""将归一化值(0-1)转换为角度值(0-180)"""
return int(normalized * 180)
def init_biohead_client(server_addr="192.168.1.222:50051"):
"""初始化仿生头gRPC客户端"""
client = CMVRGrpcClient(server_addr)
if not client.is_connected():
print("连接服务器失败")
return None
biohead = client.get_biohead("bio_head")
if not biohead:
print("获取仿生头客户端失败")
client.close()
return None
return client, biohead
def set_single_expression(biohead):
"""设置单个表情参数"""
print("\n--- 设置单个表情参数 ---")
# 表情参数映射表
param_map = {
1: "left_eyebrow_outside_y",
2: "left_eyebrow_inside_y",
3: "right_eyebrow_outside_y",
4: "right_eyebrow_inside_y",
5: "left_eye_upper_lid_y",
6: "left_eye_lower_lid_y",
7: "right_eye_upper_lid_y",
8: "right_eye_lower_lid_y",
9: "left_eye_ball_x",
10: "left_eye_ball_y",
11: "right_eye_ball_x",
12: "right_eye_ball_y",
13: "upper_lip_y",
14: "lower_lip_y",
15: "jaw_x",
16: "jaw_y"
}
print("可用表情参数:")
for idx, name in param_map.items():
print(f"{idx}: {name}")
try:
param_id = int(input("选择参数ID: "))
if param_id not in param_map:
print("无效的参数ID")
return
angle = int(input("输入角度(0-180): "))
if angle < 0 or angle > 180:
print("角度必须在0-180之间")
return
# 转换为归一化值
value = angle_to_normalized(angle)
# 创建表情对象
expression = FacialExpressionState()
# 设置对应的参数
setattr(expression, param_map[param_id], value)
# 发送表情设置
result = biohead.set_expression(expression)
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"设置 {param_map[param_id]} 成功,角度: {angle}°,归一化值: {value:.2f}")
else:
print(f"设置失败,错误码: {result}")
except ValueError:
print("输入无效,请输入数字")
def set_multiple_expressions(biohead):
"""设置多个表情参数"""
print("\n--- 设置多个表情参数 ---")
# 表情参数映射表
param_map = {
1: "left_eyebrow_outside_y",
2: "left_eyebrow_inside_y",
3: "right_eyebrow_outside_y",
4: "right_eyebrow_inside_y",
5: "left_eye_upper_lid_y",
6: "left_eye_lower_lid_y",
7: "right_eye_upper_lid_y",
8: "right_eye_lower_lid_y",
9: "left_eye_ball_x",
10: "left_eye_ball_y",
11: "right_eye_ball_x",
12: "right_eye_ball_y",
13: "upper_lip_y",
14: "lower_lip_y",
15: "jaw_x",
16: "jaw_y"
}
print("可用表情参数:")
for idx, name in param_map.items():
print(f"{idx}: {name}")
try:
count = int(input("输入要设置的参数数量: "))
if count <= 0:
print("数量必须大于0")
return
# 创建表情对象
expression = FacialExpressionState()
for i in range(count):
print(f"\n{i+1}个参数:")
param_id = int(input("选择参数ID: "))
if param_id not in param_map:
print("无效的参数ID跳过该参数")
continue
angle = int(input("输入角度(0-180): "))
if angle < 0 or angle > 180:
print("角度必须在0-180之间跳过该参数")
continue
# 转换为归一化值
value = angle_to_normalized(angle)
# 设置对应的参数
setattr(expression, param_map[param_id], value)
print(f"已添加: {param_map[param_id]} = {value:.2f} (对应角度: {angle}°)")
# 发送表情设置
result = biohead.set_expression(expression)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("设置多个表情参数成功")
else:
print(f"设置失败,错误码: {result}")
except ValueError:
print("输入无效,请输入数字")
def auto_mode(biohead, params):
"""自动增减模式0→1→0循环变化"""
print("\n--- 自动增减模式 ---")
try:
# 表情参数映射表
param_map = {
1: "left_eyebrow_outside_y",
2: "left_eyebrow_inside_y",
3: "right_eyebrow_outside_y",
4: "right_eyebrow_inside_y",
5: "left_eye_upper_lid_y",
6: "left_eye_lower_lid_y",
7: "right_eye_upper_lid_y",
8: "right_eye_lower_lid_y",
9: "left_eye_ball_x",
10: "left_eye_ball_y",
11: "right_eye_ball_x",
12: "right_eye_ball_y",
13: "upper_lip_y",
14: "lower_lip_y",
15: "jaw_x",
16: "jaw_y"
}
# 过滤有效的参数ID
valid_params = [p for p in params if p in param_map]
if not valid_params:
print("没有有效的参数,返回菜单")
return
print(f"将自动控制以下参数: {[param_map[p] for p in valid_params]}")
frequency = 50.0 # 50Hz
interval = 1.0 / frequency
value = 0.0
direction = 0.02 # 步长
# 启动流式控制
result = biohead.start_stream()
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"开始流式会话失败,错误码: {result}")
return
print("启动自动模式按Ctrl+C停止")
while True:
# 创建protobuf消息
proto_expr = biohead_command_pb2.FacialExpression()
# 设置所有参数
for param_id in valid_params:
param_name = param_map[param_id]
# 根据参数类型设置到不同的字段
if param_name in ["left_eyebrow_outside_y", "left_eyebrow_inside_y",
"right_eyebrow_outside_y", "right_eyebrow_inside_y"]:
setattr(proto_expr.eyebrow, param_name, value)
elif param_name in ["left_eye_upper_lid_y", "left_eye_lower_lid_y",
"right_eye_upper_lid_y", "right_eye_lower_lid_y"]:
setattr(proto_expr.eyelid, param_name, value)
elif param_name in ["left_eye_ball_x", "left_eye_ball_y",
"right_eye_ball_x", "right_eye_ball_y"]:
setattr(proto_expr.eye_ball, param_name, value)
elif param_name in ["upper_lip_y", "lower_lip_y"]:
setattr(proto_expr.mouth, param_name, value)
elif param_name in ["jaw_x", "jaw_y"]:
setattr(proto_expr.jaw, param_name, value)
# 发送流式表情
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"流式控制失败,错误码: {result}")
break
# 更新值
value += direction
if value >= 1.0:
value = 1.0
direction = -direction
elif value <= 0.0:
value = 0.0
direction = -direction
time.sleep(interval)
except KeyboardInterrupt:
print("\n自动模式已停止")
finally:
# 结束流式会话
biohead.end_stream()
def set_auto_params():
"""设置自动模式的参数列表"""
print("\n--- 设置自动模式参数 ---")
# 表情参数映射表
param_map = {
1: "left_eyebrow_outside_y",
2: "left_eyebrow_inside_y",
3: "right_eyebrow_outside_y",
4: "right_eyebrow_inside_y",
5: "left_eye_upper_lid_y",
6: "left_eye_lower_lid_y",
7: "right_eye_upper_lid_y",
8: "right_eye_lower_lid_y",
9: "left_eye_ball_x",
10: "left_eye_ball_y",
11: "right_eye_ball_x",
12: "right_eye_ball_y",
13: "upper_lip_y",
14: "lower_lip_y",
15: "jaw_x",
16: "jaw_y"
}
print("可用表情参数:")
for idx, name in param_map.items():
print(f"{idx}: {name}")
try:
count = int(input("输入要自动控制的参数数量: "))
if count <= 0:
print("数量必须大于0")
return None
params = []
for i in range(count):
param_id = int(input(f"{i+1}个参数ID: "))
if param_id not in param_map:
print("无效的参数ID跳过")
continue
params.append(param_id)
if not params:
print("未设置有效的参数")
return None
print(f"已设置自动模式参数: {[param_map[p] for p in params]}")
return params
except ValueError:
print("输入无效,请输入数字")
return None
def blink_mode(biohead):
"""眨眼模式:执行预设的眨眼动作"""
print("\n--- 眨眼模式 ---")
try:
blink_count = int(input("输入眨眼次数(默认10次): ") or "10")
interval = float(input("输入间隔时间(秒默认0.1): ") or "0.1")
# 启动流式控制
result = biohead.start_stream()
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"开始流式会话失败,错误码: {result}")
return
print(f"启动眨眼模式,共{blink_count}次,间隔{interval}")
for i in range(blink_count):
print(f"{i+1}次眨眼...")
# 闭眼动作
proto_expr = biohead_command_pb2.FacialExpression()
proto_expr.eyelid.left_upper_lid_y = angle_to_normalized(90)
proto_expr.eyelid.left_lower_lid_y = angle_to_normalized(90)
proto_expr.eyelid.right_upper_lid_y = angle_to_normalized(90)
proto_expr.eyelid.right_lower_lid_y = angle_to_normalized(90)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"闭眼动作失败,错误码: {result}")
break
time.sleep(interval)
# 睁眼动作
proto_expr = biohead_command_pb2.FacialExpression()
proto_expr.eyelid.left_upper_lid_y = angle_to_normalized(50)
proto_expr.eyelid.left_lower_lid_y = angle_to_normalized(130)
proto_expr.eyelid.right_upper_lid_y = angle_to_normalized(130)
proto_expr.eyelid.right_lower_lid_y = angle_to_normalized(60)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"睁眼动作失败,错误码: {result}")
break
time.sleep(interval)
print("眨眼模式结束")
except KeyboardInterrupt:
print("\n眨眼模式已停止")
except ValueError:
print("输入无效,请输入数字")
finally:
# 结束流式会话
biohead.end_stream()
def yawn_mode(biohead):
"""打哈欠模式:模拟打哈欠动作"""
print("\n--- 打哈欠模式 ---")
try:
yawn_count = int(input("输入打哈欠次数(默认5次): ") or "5")
interval = float(input("输入间隔时间(秒默认1): ") or "1")
# 启动流式控制
result = biohead.start_stream()
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"开始流式会话失败,错误码: {result}")
return
print(f"启动打哈欠模式,共{yawn_count}次,间隔{interval}")
for i in range(yawn_count):
print(f"{i+1}次打哈欠...")
# 打哈欠动作
proto_expr = biohead_command_pb2.FacialExpression()
# 眉毛
proto_expr.eyebrow.left_eyebrow_outside_y = angle_to_normalized(70)
proto_expr.eyebrow.right_eyebrow_outside_y = angle_to_normalized(110)
# 眼睑
proto_expr.eyelid.left_upper_lid_y = angle_to_normalized(60)
proto_expr.eyelid.left_lower_lid_y = angle_to_normalized(130)
proto_expr.eyelid.right_upper_lid_y = angle_to_normalized(120)
proto_expr.eyelid.right_lower_lid_y = angle_to_normalized(80)
# 嘴巴和下巴
proto_expr.mouth.upper_lip_y = angle_to_normalized(120)
proto_expr.mouth.lower_lip_y = angle_to_normalized(45)
proto_expr.jaw.x = angle_to_normalized(60)
proto_expr.jaw.y = angle_to_normalized(120)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"打哈欠动作失败,错误码: {result}")
break
time.sleep(interval)
# 恢复动作
proto_expr = biohead_command_pb2.FacialExpression()
# 恢复到正常状态
proto_expr.eyebrow.left_eyebrow_outside_y = angle_to_normalized(90)
proto_expr.eyebrow.right_eyebrow_outside_y = angle_to_normalized(90)
proto_expr.eyelid.left_upper_lid_y = angle_to_normalized(90)
proto_expr.eyelid.left_lower_lid_y = angle_to_normalized(90)
proto_expr.eyelid.right_upper_lid_y = angle_to_normalized(90)
proto_expr.eyelid.right_lower_lid_y = angle_to_normalized(90)
proto_expr.mouth.upper_lip_y = angle_to_normalized(90)
proto_expr.mouth.lower_lip_y = angle_to_normalized(90)
proto_expr.jaw.x = angle_to_normalized(90)
proto_expr.jaw.y = angle_to_normalized(90)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"恢复动作失败,错误码: {result}")
break
time.sleep(interval)
print("打哈欠模式结束")
except KeyboardInterrupt:
print("\n打哈欠模式已停止")
except ValueError:
print("输入无效,请输入数字")
finally:
# 结束流式会话
biohead.end_stream()
def talk_mode(biohead):
"""说话模式:模拟嘴巴开合动作"""
print("\n--- 说话模式 ---")
try:
talk_count = int(input("输入说话循环次数(默认50次): ") or "50")
# 启动流式控制
result = biohead.start_stream()
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"开始流式会话失败,错误码: {result}")
return
print(f"启动说话模式,共{talk_count}")
for i in range(talk_count):
print(f"{i+1}次说话...")
# 开口动作
proto_expr = biohead_command_pb2.FacialExpression()
proto_expr.mouth.upper_lip_y = angle_to_normalized(120)
proto_expr.mouth.lower_lip_y = angle_to_normalized(45)
proto_expr.jaw.x = angle_to_normalized(80)
proto_expr.jaw.y = angle_to_normalized(110)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"开口动作失败,错误码: {result}")
break
# 随机间隔
interval = random.uniform(0, 0.3)
time.sleep(interval)
# 闭口动作
proto_expr = biohead_command_pb2.FacialExpression()
proto_expr.mouth.upper_lip_y = angle_to_normalized(120)
proto_expr.mouth.lower_lip_y = angle_to_normalized(45)
proto_expr.jaw.x = angle_to_normalized(90)
proto_expr.jaw.y = angle_to_normalized(90)
result = biohead.stream_expression(proto_expr)
if result != CMVRErrorCode.CMVR_SUCCESS:
print(f"闭口动作失败,错误码: {result}")
break
# 随机间隔
interval = random.uniform(0, 0.3)
time.sleep(interval)
print("说话模式结束")
except KeyboardInterrupt:
print("\n说话模式已停止")
except ValueError:
print("输入无效,请输入数字")
finally:
# 结束流式会话
biohead.end_stream()
def main():
# 初始化gRPC客户端
server_addr = "192.168.1.222:50051"
if len(sys.argv) > 1:
server_addr = sys.argv[1]
print(f"连接到仿生头服务器: {server_addr}")
client, biohead = init_biohead_client(server_addr)
if not client or not biohead:
sys.exit(1)
# 默认自动模式参数
auto_params = [5, 6, 7, 8] # 默认控制眼睑参数
try:
while True:
print("\n=== 仿生头控制菜单 ===")
print("1. 控制单个表情参数")
print("2. 控制多个表情参数")
print("3. 退出")
print("4. 自动增减模式(50Hz)")
print("5. 设置自动模式参数")
print("6. 眨眼模式")
print("7. 打哈欠模式")
print("8. 说话模式")
opt = input("选择操作: ").strip()
if opt == '1':
set_single_expression(biohead)
elif opt == '2':
set_multiple_expressions(biohead)
elif opt == '3':
print("退出程序")
break
elif opt == '4':
auto_mode(biohead, auto_params)
elif opt == '5':
new_params = set_auto_params()
if new_params:
auto_params = new_params
elif opt == '6':
blink_mode(biohead)
elif opt == '7':
yawn_mode(biohead)
elif opt == '8':
talk_mode(biohead)
else:
print("无效选项,请重试")
except KeyboardInterrupt:
print("\n用户中断,退出程序")
finally:
client.close()
print("已关闭连接")
if __name__ == '__main__':
main()

344
biohead_test.py Normal file
View File

@ -0,0 +1,344 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
interactive_servo_test.py 基于 Python ESP32 舵机控制交互脚本
依赖pip install pyserial
用法
python3 interactive_servo_test.py /dev/ttyUSB0 [baudrate]
功能
1. 控制单个舵机
2. 控制多个舵机
3. 退出
4. 自动增减模式 0 180° 每步 +6° -6° 循环50Hz 频率发送
5. 设置自动模式舵机数量通道 1~N
6. 眨眼模式让五个舵机执行预设角度循环模拟眨眼8号舵机保持120度
7. 打哈欠模式模拟打哈欠动作
8. 说话模式模拟嘴巴开合动作
"""
import sys
import time
import serial
import random
FRAME_HEADER = 0xAA # 帧头
def wakeup_esp32(port, baud=115200):
"""通过 RTS/DTR 线复位并唤醒 ESP32"""
try:
ser = serial.Serial(port, baud, timeout=1)
ser.setDTR(False)
ser.setRTS(False)
time.sleep(0.01)
ser.setDTR(True)
ser.setRTS(True)
ser.write(b"\r\n")
time.sleep(1)
except Exception as e:
print(f"ESP32 唤醒失败: {e}")
finally:
try:
ser.close()
except:
pass
def open_serial(port, baud):
"""打开并配置串口"""
try:
ser = serial.Serial(port, baud, timeout=1)
print(f"已打开串口: {port} @ {baud}bps")
return ser
except Exception as e:
print(f"打开串口失败: {e}")
sys.exit(1)
def calc_checksum(data: bytearray) -> int:
"""计算 XOR 校验和"""
cs = 0
for b in data:
cs ^= b
return cs
def pack_commands(cmds):
"""
cmds: list of (addr, channel, angle, duration_ms)
返回完整帧字节数组
格式 [FRAME_HEADER][count] [addr, ch, ang, durL, durH]... [checksum]
"""
pkt = bytearray([FRAME_HEADER, len(cmds)])
for addr, ch, ang, dur in cmds:
pkt.extend([addr, ch, ang, dur & 0xFF, (dur >> 8) & 0xFF])
pkt.append(calc_checksum(pkt))
return pkt
def print_packet(pkt):
"""打印十六进制数据包"""
print("发送数据包:", ' '.join(f"0x{b:02X}" for b in pkt))
def input_hex(prompt):
"""输入 0x 开头或十进制整型"""
val = input(prompt).strip()
try:
return int(val, 0)
except ValueError:
print(f"无效数字: {val}")
return input_hex(prompt)
def input_cmd_single():
"""交互输入单条命令"""
addr = input_hex("输入 addr (hex or dec): ")
ch = input_hex("输入 channel (hex or dec): ")
ang = input_hex("输入 angle (0-180): ")
dur = input_hex("输入 duration(ms): ")
return [(addr, ch, ang, dur)]
def input_cmd_multiple():
"""交互输入多条命令"""
cnt = input_hex("输入命令数量: ")
cmds = []
for i in range(cnt):
print(f"{i+1} 条:")
cmds.extend(input_cmd_single())
return cmds
def auto_mode(ser, channels):
"""自动增减模式0→180→0步长650Hz多舵机"""
addr = input_hex("自动模式: 输入 addr (hex或dec): ")
interval = 1.0 / 50.0
dur_ms = int(interval * 1000)
angle = 0
direction = 1
step = 6
print(f"启动自动模式 @50Hz, addr=0x{addr:02X}, channels={channels}, step={step}°")
try:
while True:
cmds = [(addr, ch, angle, dur_ms) for ch in channels]
pkt = pack_commands(cmds)
print_packet(pkt)
ser.write(pkt)
# 更新角度
angle += direction * step
if angle >= 180:
angle = 180
direction = -1
elif angle <= 0:
angle = 0
direction = 1
time.sleep(interval)
except KeyboardInterrupt:
print("\n自动模式已停止,返回菜单")
def set_channels():
"""自定义舵机数量,返回通道列表 1~N"""
cnt = input_hex("输入自动模式舵机数量 N: ")
if cnt < 1:
print("数量必须 >= 1")
return None
channels = list(range(1, cnt+1))
print(f"已设置通道列表: {channels}")
return channels
def blink_mode(ser):
"""眨眼模式:根据输入的角度进行指定舵机的眨眼操作"""
addr = input_hex("眨眼模式: 输入 addr (hex或dec): ")
blink_count = 10 # 循环10次
interval = 0.1 # 每秒眨眼一次
# 定义五个舵机的通道
servo_channels = [4, 5, 6, 7, 8] # 控制五个舵机
blink_angles = [90, 90, 90, 90, 90] # 眨眼时的角度
open_angles = [50, 130, 130, 60, 90] # 睁开时的角度4-7号舵机回到90度8号保持120度
print(f"启动眨眼模式10次眨眼每次间隔 0.1 秒")
try:
for i in range(blink_count):
print(f"{i+1} 次眨眼...")
# 眨眼动作:同步发送所有舵机到眨眼角度
cmds = [(addr, ch, angle, 0) for ch, angle in zip(servo_channels, blink_angles)]
pkt = pack_commands(cmds)
print_packet(pkt)
ser.write(pkt)
time.sleep(interval)
# 睁开动作同步发送4-7号舵机回到90度8号保持120度
print("睁开动作舵机4-7回到90度")
cmds = [(addr, ch, angle, 0) for ch, angle in zip(servo_channels, open_angles)]
pkt = pack_commands(cmds)
print_packet(pkt)
ser.write(pkt)
time.sleep(interval)
print("眨眼模式结束,返回菜单")
except KeyboardInterrupt:
print("\n眨眼模式已停止,返回菜单")
def yawn_mode(ser):
"""打哈欠模式:控制多个舵机模拟打哈欠动作"""
addr = input_hex("打哈欠模式: 输入 addr (hex或dec): ")
yawn_count = 5 # 循环5次
interval = 1 # 每次打哈欠动作间隔1秒
# 定义控制的舵机通道
# 打哈欠时使用舵机1, 3, 4, 5, 6, 7, 8通道1~8
# 闭嘴时使用舵机8、965号舵机
yawning_channels = [1, 3, 4, 5, 6, 7, 8] # 控制通道1~8
yawning_angles = [70, 110, 60, 130, 120, 80, 90] # 打哈欠时的角度(根据需要调整)
closing_angles = [90, 90, 90, 90, 90, 90, 90] # 闭嘴时的角度(闭嘴动作)
# 65号舵机8、9号通道设置不同的角度模拟控制头部动作
yawning_65_angles = [120, 45, 60, 120] # 65号舵机8、9号通道在打哈欠时的角度
closing_65_angles = [120, 45, 90, 90] # 闭嘴时65号舵机8、9号通道的角度
print(f"启动打哈欠模式,循环 {yawn_count} 次,每次间隔 {interval}")
try:
for i in range(yawn_count):
print(f"{i+1} 次打哈欠...")
# 打哈欠动作:同步发送所有舵机到打哈欠角度
cmds = [(addr, ch, angle, 3000) for ch, angle in zip(yawning_channels, yawning_angles)]
cmds_65 = [(65, ch, angle, 3000) for ch, angle in zip([0,1,8, 9], yawning_65_angles)] # 控制65号舵机8、9号通道
cmds.extend(cmds_65) # 合并65号舵机的指令
pkt = pack_commands(cmds)
print_packet(pkt)
ser.write(pkt) # 一次性发送所有指令
time.sleep(interval)
# 闭嘴动作:同步发送舵机回到闭嘴角度
print("闭嘴动作:所有舵机回到闭嘴角度")
cmds = [(addr, ch, angle, 3000) for ch, angle in zip(yawning_channels, closing_angles)]
cmds_65 = [(65, ch, angle, 3000) for ch, angle in zip([0,1,8, 9], closing_65_angles)] # 65号舵机8、9号通道
cmds.extend(cmds_65) # 合并65号舵机的指令
pkt = pack_commands(cmds)
print_packet(pkt)
ser.write(pkt) # 一次性发送所有指令
time.sleep(interval)
print("打哈欠模式结束,返回菜单")
except KeyboardInterrupt:
print("\n打哈欠模式已停止,返回菜单")
def talk_mode(ser):
"""说话模式:模拟嘴巴开合动作"""
addr = input_hex("说话模式: 输入 addr (hex或dec): ")
talk_count = 50 # 循环5次
# 定义控制的舵机通道
mouth_channels = [0, 1, 8, 9] # 控制3个舵机口型
mouth_open_angles = [120, 45, 80, 110] # 嘴巴打开时的角度
mouth_close_angles = [120, 45, 90, 90] # 嘴巴关闭时的角度(正常闭合)
print(f"启动说话模式,循环 {talk_count}每次间隔随机生成在0~1秒之间")
try:
for i in range(talk_count):
print(f"{i + 1} 次说话...")
# 开口动作:同步发送所有舵机到嘴巴打开的角度
cmds = [(addr, ch, angle, 0) for ch, angle in zip(mouth_channels, mouth_open_angles)]
pkt = pack_commands(cmds)
print_packet(pkt)
ser.write(pkt) # 一次性发送所有指令
# 生成一个 0 到 1 之间的随机间隔时间
interval = random.uniform(0, 0.3)
print(f"随机间隔时间:{interval:.2f}")
time.sleep(interval)
# 闭口动作:同步发送所有舵机回到嘴巴关闭的角度
print("闭口动作:舵机回到闭口角度")
cmds = [(addr, ch, angle, 0) for ch, angle in zip(mouth_channels, mouth_close_angles)]
pkt = pack_commands(cmds)
print_packet(pkt)
ser.write(pkt) # 一次性发送所有指令
# 生成一个 0 到 1 之间的随机间隔时间
interval = random.uniform(0, 0.3)
print(f"随机间隔时间:{interval:.2f}")
time.sleep(interval)
print("说话模式结束,返回菜单")
except KeyboardInterrupt:
print("\n说话模式已停止,返回菜单")
def main():
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} /dev/ttyUSB0 [baudrate]")
sys.exit(1)
port = sys.argv[1]
baud = int(sys.argv[2]) if len(sys.argv) > 2 else 115200
print("唤醒 ESP32...")
wakeup_esp32(port, baud)
ser = open_serial(port, baud)
channels = list(range(1, 10)) # 默认通道 1-9
try:
while True:
print("\n=== 操作菜单 ===")
print("1. 控制单个舵机")
print("2. 控制多个舵机")
print("3. 退出")
print("4. 自动增减模式(50Hz)")
print("5. 设置自动模式舵机数量")
print("6. 眨眼模式")
print("7. 打哈欠模式")
print("8. 说话模式") # 新增选项
opt = input("选择: ").strip()
if opt == '1':
cmds = input_cmd_single()
elif opt == '2':
cmds = input_cmd_multiple()
elif opt == '4':
auto_mode(ser, channels)
continue
elif opt == '5':
new_ch = set_channels()
if new_ch:
channels = new_ch
continue
elif opt == '6':
blink_mode(ser)
continue
elif opt == '7': # 调用打哈欠模式
yawn_mode(ser)
continue
elif opt == '8': # 调用说话模式
talk_mode(ser)
continue
elif opt == '3':
break
else:
print("无效选项,重试")
continue
pkt = pack_commands(cmds)
print_packet(pkt)
ser.write(pkt)
print("✅ 指令已发送")
except KeyboardInterrupt:
print("\n用户中断,退出")
finally:
ser.close()
print("串口已关闭")
if __name__ == '__main__':
main()

15
cmvr/__init__.py Normal file
View File

@ -0,0 +1,15 @@
from .client import CMVRGrpcClient
from .enums import CMVRErrorCode, DeviceState, FingerType, RobotCartesian, RobotJointIndexDirection
from .models import (
MicState, SpeakerState, CameraState,
FreedomState, DexHandState, FacialExpressionState,
FingerTactileData, PalmTactileData, HandTactileSensors,JointCmd,Pose3D,JointState
)
__all__ = [
'CMVRGrpcClient',
'CMVRErrorCode', 'DeviceState', 'FingerType',"RobotCartesian", "RobotJointIndexDirection",
'MicState', 'SpeakerState', 'CameraState',
'FreedomState', 'DexHandState', 'FacialExpressionState',
'FingerTactileData', 'PalmTactileData', 'HandTactileSensors',"JointCmd","Pose3D","JointState"
]

223
cmvr/biohead_client.py Normal file
View File

@ -0,0 +1,223 @@
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.stream_active = True
self.response_iterator = self.stub.StreamExpression(self._stream_request_generator())
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)
time.sleep(0.1)
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()

443
cmvr/camera_client.py Normal file
View File

@ -0,0 +1,443 @@
import grpc
import numpy as np
from PIL import Image
from typing import Tuple
from .enums import CMVRErrorCode
from .models import CameraState
from .models import CameraIntrinsics
class CameraClient:
"""相机客户端"""
def __init__(self, device_id: str, stub):
self.device_id = device_id
self.stub = stub
self.generated = None # 推迟导入
def _import_generated(self):
"""推迟导入 generated"""
if self.generated is None:
try:
# 从正确的路径导入生成的模块
from generated.cmvr.api import common_pb2, camera_command_pb2
self.generated = type('GeneratedModules', (), {
'common_pb2': common_pb2,
'camera_command_pb2': camera_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
header.timestamp.GetCurrentTime()
return header
def get_status(self) -> Tuple[CMVRErrorCode, CameraState]:
"""获取相机状态"""
try:
generated = self._import_generated()
request = generated.camera_command_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:
generated = self._import_generated()
request = generated.camera_command_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:
generated = self._import_generated()
request = generated.camera_command_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:
generated = self._import_generated()
request = generated.camera_command_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)
# 从 Protobuf 消息中读取 intrinsics 数据
protobuf_intrinsics = response.intrinsics
# 构造 Python 数据类对象(字段名称完全对应)
python_intrinsics = CameraIntrinsics(
cx=protobuf_intrinsics.cx,
cy=protobuf_intrinsics.cy,
fx=protobuf_intrinsics.fx,
fy=protobuf_intrinsics.fy,
coeffs=list(protobuf_intrinsics.coeffs) # Protobuf repeated 字段通常是列表类型,直接转换
)
# 示例:访问读取后的数据
print(f"主点坐标: ({python_intrinsics.cx}, {python_intrinsics.cy})")
print(f"焦距: ({python_intrinsics.fx}, {python_intrinsics.fy})")
print(f"畸变系数: {python_intrinsics.coeffs}")
# 重塑为图像格式 (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 get_depth_image(self) -> Tuple[CMVRErrorCode, np.ndarray, int, int, CameraIntrinsics]:
"""
获取深度图像及内参修复返回值类型
"""
# 创建默认内参实例(空值)用于错误返回
default_intrinsics = CameraIntrinsics(cx=0, cy=0, fx=0, fy=0, coeffs=[])
# 错误返回模板(类型与声明完全匹配)
error_return = (
CMVRErrorCode.CMVR_RPC_FAILED,
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
try:
generated = self._import_generated()
if not generated:
print("无法导入生成的protobuf模块")
return error_return
# 创建请求
request = generated.camera_command_pb2.GetDepthImageComand.Request()
request.header.CopyFrom(self._create_command_header())
# 调用RPC
response = self.stub.GetDepthImage(request)
if not response.header.success:
print(f"深度图像请求失败,错误码: {response.header.error_code}")
return (
CMVRErrorCode.CMVR_RPC_FAILED,
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
# 提取图像信息
width = response.depth_frame.width
height = response.depth_frame.height
depth_data = response.depth_frame.data
expected_size = width * height * 2
if len(depth_data) != expected_size:
print(f"深度图像数据尺寸不匹配: 预期 {expected_size}, 实际 {len(depth_data)}")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
# 转换为数组
depth_array = np.frombuffer(depth_data, dtype=np.uint16).reshape((height, width))
# 解析内参(确保始终返回有效实例)
intrinsics = CameraIntrinsics(
cx=response.intrinsics.cx,
cy=response.intrinsics.cy,
fx=response.intrinsics.fx,
fy=response.intrinsics.fy,
coeffs=list(response.intrinsics.coeffs) if hasattr(response.intrinsics, 'coeffs') else []
)
return (
CMVRErrorCode.CMVR_SUCCESS,
depth_array,
width,
height,
intrinsics
)
except grpc.RpcError as e:
print(f"深度图像RPC调用失败: {str(e)}")
return error_return
except Exception as e:
print(f"深度图像处理失败: {str(e)}")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
def get_rgbd_images(self) -> Tuple[CMVRErrorCode, np.ndarray, np.ndarray, int, int, CameraIntrinsics]:
"""
获取同步RGBD图像及内参修复返回值类型
"""
# 创建默认内参实例
default_intrinsics = CameraIntrinsics(cx=0, cy=0, fx=0, fy=0, coeffs=[])
# 错误返回模板
error_return = (
CMVRErrorCode.CMVR_RPC_FAILED,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
try:
generated = self._import_generated()
if not generated:
print("无法导入生成的protobuf模块")
return error_return
# 创建请求
request = generated.camera_command_pb2.GetRGBDImagesCommand.Request()
request.header.CopyFrom(self._create_command_header())
# 调用RPC
response = self.stub.GetRGBDImages(request)
if not response.header.success:
print(f"RGBD图像请求失败错误码: {response.header.error_code}")
return (
CMVRErrorCode.CMVR_RPC_FAILED,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
# 提取尺寸信息
rgb_width, rgb_height = response.color_frame.width, response.color_frame.height
depth_width, depth_height = response.depth_frame.width, response.depth_frame.height
if rgb_width != depth_width or rgb_height != depth_height:
print(f"RGBD尺寸不匹配: RGB({rgb_width}x{rgb_height}), Depth({depth_width}x{depth_height})")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
# 处理RGB图像
rgb_data = response.color_frame.data
rgb_expected = rgb_width * rgb_height * 3
if len(rgb_data) != rgb_expected:
print(f"RGB数据尺寸不匹配: 预期 {rgb_expected}, 实际 {len(rgb_data)}")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
rgb_array = np.frombuffer(rgb_data, dtype=np.uint8).reshape((rgb_height, rgb_width, 3))
# 处理深度图像
depth_data = response.depth_frame.data
depth_expected = depth_width * depth_height * 2
if len(depth_data) != depth_expected:
print(f"深度数据尺寸不匹配: 预期 {depth_expected}, 实际 {len(depth_data)}")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
depth_array = np.frombuffer(depth_data, dtype=np.uint16).reshape((depth_height, depth_width))
# 解析内参
intrinsics = CameraIntrinsics(
cx=response.intrinsics.cx,
cy=response.intrinsics.cy,
fx=response.intrinsics.fx,
fy=response.intrinsics.fy,
coeffs=list(response.intrinsics.coeffs) if hasattr(response.intrinsics, 'coeffs') else []
)
return (
CMVRErrorCode.CMVR_SUCCESS,
rgb_array,
depth_array,
rgb_width,
rgb_height,
intrinsics
)
except grpc.RpcError as e:
print(f"RGBD RPC调用失败: {str(e)}")
return error_return
except Exception as e:
print(f"RGBD图像处理失败: {str(e)}")
return (
CMVRErrorCode.CMVR_INTERNAL_ERROR,
np.array([], dtype=np.uint8),
np.array([], dtype=np.uint16),
0,
0,
default_intrinsics
)
def start_record(self, video_path: str) -> CMVRErrorCode:
"""开始录像"""
try:
generated = self._import_generated()
request = generated.camera_command_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:
generated = self._import_generated()
request = generated.camera_command_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
def create_rgb_stream_request(self):
"""创建流请求(无参数)"""
generated = self._import_generated()
request = generated.camera_command_pb2.GetRGBImageStreamCommand.Request()
request.header.CopyFrom(self._create_command_header())
# 不需要设置其他参数
return request
def get_rgb_stream(self, request_generator):
"""
双向流获取传感器数据
参数:
request_generator: 请求生成器
返回:
传感器数据流迭代器
"""
generated = self._import_generated()
return self.stub.GetRGBImageStream(request_generator)
def create_depth_stream_request(self):
"""创建流请求(无参数)"""
generated = self._import_generated()
request = generated.camera_command_pb2.GetDepthImageStreamCommand.Request()
request.header.CopyFrom(self._create_command_header())
# 不需要设置其他参数
return request
def get_depth_stream(self, request_generator):
"""
双向流获取传感器数据
参数:
request_generator: 请求生成器
返回:
传感器数据流迭代器
"""
generated = self._import_generated()
return self.stub.GetDepthImageStream(request_generator)
def create_rgbd_stream_request(self):
"""创建流请求(无参数)"""
generated = self._import_generated()
request = generated.camera_command_pb2.GetRGBDImageStreamCommand.Request()
request.header.CopyFrom(self._create_command_header())
# 不需要设置其他参数
return request
def get_rgbd_stream(self, request_generator):
"""
双向流获取传感器数据
参数:
request_generator: 请求生成器
返回:
传感器数据流迭代器
"""
generated = self._import_generated()
return self.stub.GetRGBDImageStream(request_generator)

156
cmvr/client.py Normal file
View File

@ -0,0 +1,156 @@
import grpc
from typing import Dict
from .enums import CMVRErrorCode
from .biohead_client import BioHeadClient
from .camera_client import CameraClient
from .dexhand_client import DexHandClient
from .micphone_client import MicphoneClient
from .speaker_client import SpeakerClient
from .humanoid_robot import HumanoidRobotClient
class CMVRGrpcClient:
"""CMVR gRPC 主客户端"""
def __init__(self, server_address: str):
self.server_address = server_address
self.connected = False
# 创建 gRPC 通道
# 设置通道选项,增加最大接收和发送消息大小
max_message_size: int = 20 * 1024 * 1024
options = [
('grpc.max_send_message_length', max_message_size),
('grpc.max_receive_message_length', max_message_size),
]
self.channel = grpc.insecure_channel(server_address,options=options)
# 初始化各服务的存根
self.generated = None # 推迟导入
self.biohead_stub = None
self.biohead_map: Dict[str, BioHeadClient] = {}
self.camera_stub = None
self.camera_map: Dict[str, CameraClient] = {}
self.dexhand_stub = None
self.dexhand_map: Dict[str, DexHandClient] = {}
self.micphone_stub = None
self.micphone_map: Dict[str, MicphoneClient] = {}
self.speaker_stub = None
self.speaker_map: Dict[str, SpeakerClient] = {}
self.humanoid_robot_stub = None
self.humanoid_robot_map: Dict[str, HumanoidRobotClient] = {}
# 检查连接状态
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)
self.camera_stub = self.generated.camera_service_pb2_grpc.CameraServiceStub(self.channel)
self.dexhand_stub = self.generated.dexhand_service_pb2_grpc.DexHandServiceStub(self.channel)
self.speaker_stub = self.generated.speaker_service_pb2_grpc.SpeakerServiceStub(self.channel)
self.micphone_stub = self.generated.microphone_service_pb2_grpc.MicPhoneServiceStub(self.channel)
self.humanoid_robot_stub = self.generated.humanoid_robot_sevice_pb2_grpc.HumanoidRobotServiceStub(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 camera_service_pb2_grpc
from generated.cmvr.api import common_pb2, biohead_command_pb2
from generated.cmvr.api import camera_command_pb2
from generated.cmvr.api import dexhand_command_pb2
from generated.cmvr.api import dexhand_service_pb2_grpc
from generated.cmvr.api import speaker_command_pb2
from generated.cmvr.api import speaker_service_pb2_grpc
from generated.cmvr.api import microphone_command_pb2
from generated.cmvr.api import microphone_service_pb2_grpc
from generated.cmvr.api import humanoid_robot_command_pb2
from generated.cmvr.api import humanoid_robot_service_pb2_grpc
self.generated = type('GeneratedModules', (), {
'biohead_service_pb2_grpc': biohead_service_pb2_grpc,
'common_pb2': common_pb2,
'biohead_command_pb2': biohead_command_pb2,
'camera_service_pb2_grpc': camera_service_pb2_grpc,
'camera_command_pb2': camera_command_pb2,
'dexhand_service_pb2_grpc': dexhand_service_pb2_grpc,
'dexhand_command_pb2': dexhand_command_pb2,
'speaker_service_pb2_grpc': speaker_service_pb2_grpc,
'speaker_command_pb2': speaker_command_pb2,
'microphone_service_pb2_grpc': microphone_service_pb2_grpc,
'microphone_command_pb2': microphone_command_pb2,
'humanoid_robot_sevice_pb2_grpc': humanoid_robot_service_pb2_grpc,
'humanoid_robot_command_pb2': humanoid_robot_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 get_camera(self, device_id: str) -> CameraClient:
"""获取摄像头客户端"""
if device_id not in self.camera_map:
self.camera_map[device_id] = CameraClient(device_id, self.camera_stub)
return self.camera_map[device_id]
def get_dexhand(self, device_id: str) -> DexHandClient:
if device_id not in self.dexhand_map:
self.dexhand_map[device_id] = DexHandClient(device_id, self.dexhand_stub)
return self.dexhand_map[device_id]
def get_speaker(self, device_id: str) -> SpeakerClient:
if device_id not in self.speaker_map:
self.speaker_map[device_id] = SpeakerClient(device_id, self.speaker_stub)
return self.speaker_map[device_id]
def get_micphone(self, device_id: str) -> MicphoneClient:
if device_id not in self.micphone_map:
self.micphone_map[device_id] = MicphoneClient(device_id, self.micphone_stub)
return self.micphone_map[device_id]
def get_humanoid_robot(self, device_id: str) -> HumanoidRobotClient:
if device_id not in self.micphone_map:
self.humanoid_robot_map[device_id] = HumanoidRobotClient(device_id, self.humanoid_robot_stub)
return self.humanoid_robot_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

339
cmvr/dexhand_client.py Normal file
View File

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

52
cmvr/enums.py Normal file
View File

@ -0,0 +1,52 @@
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 # 大拇指
PALM = 5 # 掌心
# 机器人相关枚举定义
class RobotCartesian(Enum):
"""机器人笛卡尔坐标系枚举"""
X = 0
Y = 1
Z = 2
RX = 3 # 绕X轴旋转
RY = 4 # 绕Y轴旋转
RZ = 5 # 绕Z轴旋转
class RobotJointIndexDirection(Enum):
"""机器人关节索引方向枚举"""
FORWARD = 0
BACKWARD = 1
X_POSITIVE = 2 # X轴正向
X_NEGATIVE = 3 # X轴负向
Y_POSITIVE = 4 # Y轴正向
Y_NEGATIVE = 5 # Y轴负向
Z_POSITIVE = 6 # Z轴正向
Z_NEGATIVE = 7 # Z轴负向
ROTATE_X = 8 # 绕X轴旋转
ROTATE_Y = 9 # 绕Y轴旋转
ROTATE_Z = 10 # 绕Z轴旋转

279
cmvr/humanoid_robot.py Normal file
View File

@ -0,0 +1,279 @@
import grpc
import numpy as np
from typing import List, Optional, Tuple
from typing import Dict
from .enums import CMVRErrorCode, RobotCartesian, RobotJointIndexDirection
from .models import JointCmd, Pose3D, JointState
class HumanoidRobotClient:
"""人形机器人客户端"""
def __init__(self, device_id: str, stub):
"""
初始化机器人客户端
参数:
device_id: 设备ID
stub: gRPC stub实例
"""
self.device_id = device_id
self.stub = stub
self.generated = None # 推迟导入
def _import_generated(self):
"""推迟导入 generated 模块"""
if self.generated is None:
try:
# 从正确的路径导入生成的模块
from generated.cmvr.api import common_pb2, humanoid_robot_command_pb2
self.generated = type('GeneratedModules', (), {
'common_pb2': common_pb2,
'humanoid_robot_command_pb2': humanoid_robot_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
header.timestamp.GetCurrentTime()
return header
# ==================== 基本控制接口 ====================
def torqueOn(self) -> CMVRErrorCode:
try:
generated = self._import_generated()
request = generated.common_pb2.CommandHeader.Request()
request.CopyFrom(self._create_command_header())
response = self.stub.torqueOn(request)
return CMVRErrorCode.CMVR_SUCCESS if response.success else CMVRErrorCode.CMVR_RPC_FAILED
except grpc.RpcError as e:
print(f"开启使能失败: {e}")
return CMVRErrorCode.CMVR_RPC_FAILED
def torqueOff(self) -> CMVRErrorCode:
try:
generated = self._import_generated()
request = generated.common_pb2.CommandHeader.Request()
request.CopyFrom(self._create_command_header())
response = self.stub.torqueOff(request)
return CMVRErrorCode.CMVR_SUCCESS if response.success else CMVRErrorCode.CMVR_RPC_FAILED
except grpc.RpcError as e:
print(f"关闭使能失败: {e}")
return CMVRErrorCode.CMVR_RPC_FAILED
# ==================== 关节空间运动 ====================
def moveJ(self, joint_commands: List[JointCmd],
overall_vel: float = 0.1, overall_acc: float = 0.5) -> Tuple[CMVRErrorCode, Optional[str]]:
try:
generated = self._import_generated()
# 创建MoveJ请求
request = generated.humanoid_robot_command_pb2.MoveJ.Request()
request.header.CopyFrom(self._create_command_header())
request.vel = overall_vel
request.acc = overall_acc
# 添加关节命令
for cmd in joint_commands:
joint_cmd = request.cmds.add()
joint_cmd.joint_name = cmd.joint_name
joint_cmd.rad = cmd.rad
joint_cmd.vel = cmd.vel
# 调用RPC接口
response = self.stub.moveJ(request)
return CMVRErrorCode.CMVR_SUCCESS if response.header.success else CMVRErrorCode.CMVR_RPC_FAILED
except grpc.RpcError as e:
return CMVRErrorCode.CMVR_RPC_FAILED
# ==================== 直线空间运动 ====================
def moveL(self, ee_link: str, target_pose: Pose3D,
vel: float = 0.1, acc: float = 0.5) -> Tuple[CMVRErrorCode, Optional[str]]:
"""
直线空间运动 (MoveL)
参数:
ee_link: 末端执行器连杆名称
target_pose: 目标位姿 (Pose3D)
vel: 速度 (m/s)
acc: 加速度 (m/)
返回:
Tuple[CMVRErrorCode, Optional[str]]: 错误码和错误消息
"""
try:
generated = self._import_generated()
# 创建MoveL请求
request = generated.humanoid_robot_command_pb2.MoveL.Request()
request.header.CopyFrom(self._create_command_header())
request.ee_link = ee_link
request.vel = vel
request.acc = acc
# 设置目标位姿
request.target_pose.x = target_pose.x
request.target_pose.y = target_pose.y
request.target_pose.z = target_pose.z
request.target_pose.rx = target_pose.rx
request.target_pose.ry = target_pose.ry
request.target_pose.rz = target_pose.rz
# 调用RPC接口
response = self.stub.moveL(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 speedJ(self, joint_name: str, velocity: float,
direction: RobotJointIndexDirection = RobotJointIndexDirection.FORWARD,
acc: float = 0.5) -> Tuple[CMVRErrorCode, Optional[str]]:
"""
关节速度控制 (SpeedJ)
参数:
joint_name: 关节名称
velocity: 速度 (rad/s)
direction: 运动方向
acc: 加速度 (rad/)
返回:
Tuple[CMVRErrorCode, Optional[str]]: 错误码和错误消息
"""
try:
generated = self._import_generated()
# 创建SpeedJ请求
request = generated.humanoid_robot_command_pb2.SpeedJ.Request()
request.header.CopyFrom(self._create_command_header())
request.joint_name = joint_name
request.vel = abs(velocity) # 确保速度为正值
request.acc = acc
request.direction = direction.value
# 调用RPC接口
response = self.stub.speedJ(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, str(e)
# ==================== 笛卡尔速度控制 ====================
def speedL(self, ee_link: str, velocity: float,
cartesian: RobotCartesian = RobotCartesian.X,
direction: RobotJointIndexDirection = RobotJointIndexDirection.FORWARD,
acc: float = 0.5) -> Tuple[CMVRErrorCode, Optional[str]]:
"""
笛卡尔速度控制 (SpeedL)
参数:
ee_link: 末端执行器连杆名称
velocity: 速度 (m/s rad/s取决于cartesian类型)
cartesian: 笛卡尔坐标轴
direction: 运动方向
acc: 加速度 (m/ rad/)
返回:
Tuple[CMVRErrorCode, Optional[str]]: 错误码和错误消息
"""
try:
generated = self._import_generated()
# 创建SpeedL请求
request = generated.humanoid_robot_command_pb2.SpeedL.Request()
request.header.CopyFrom(self._create_command_header())
request.ee_link = ee_link
request.vel = abs(velocity)
request.acc = acc
request.cartesian = cartesian.value
request.direction = direction.value
# 调用RPC接口
response = self.stub.speedL(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, str(e)
# ==================== 状态获取 ====================
def get_joint_states(self) -> Tuple[CMVRErrorCode, List[JointState]]:
"""
获取机器人所有关节状态
返回:
Tuple[CMVRErrorCode, List[JointState]]: 错误码和关节状态对象列表
"""
try:
generated = self._import_generated()
# 创建关节状态请求
request = generated.humanoid_robot_command_pb2.JointRequest()
request.header.CopyFrom(self._create_command_header())
# 调用RPC接口
response = self.stub.getJointState(request)
if not response.header.success:
print(f"RPC调用失败: {response.header}")
return CMVRErrorCode.CMVR_RPC_FAILED, []
# 检查是否有状态数据
if not response.state:
print("响应中没有关节状态数据")
return CMVRErrorCode.CMVR_NO_DATA, []
# 解析所有JointState
joint_states = []
for proto_joint_state in response.state:
# 解析响应数据到JointState对象
# 注意proto字段名和Python dataclass字段名的映射
joint_state = JointState(
name=list(proto_joint_state.name), # proto: name -> Python: name
position=list(proto_joint_state.position), # proto: position -> Python: position
velocity=list(proto_joint_state.velocity), # proto: velocity -> Python: velocity
effort=list(proto_joint_state.effort), # proto: effort -> Python: effort
timestamp=proto_joint_state.timestamp # proto: timestamp -> Python: timestamp
)
joint_states.append(joint_state)
if not joint_states:
print("解析后没有有效的关节状态数据")
return CMVRErrorCode.CMVR_NO_DATA, []
return CMVRErrorCode.CMVR_SUCCESS, joint_states
except grpc.RpcError as e:
print(f"gRPC错误 - 获取关节状态失败: {e}")
if hasattr(e, 'code'):
if e.code() == grpc.StatusCode.NOT_FOUND:
return CMVRErrorCode.CMVR_NOT_FOUND, []
elif e.code() == grpc.StatusCode.UNAVAILABLE:
return CMVRErrorCode.CMVR_CONNECTION_FAILED, []
return CMVRErrorCode.CMVR_RPC_FAILED, []
except Exception as e:
print(f"未知错误 - 获取关节状态失败: {e}")
import traceback
traceback.print_exc()
return CMVRErrorCode.CMVR_UNKNOWN_ERROR, []

94
cmvr/micphone_client.py Normal file
View File

@ -0,0 +1,94 @@
import grpc
from typing import Tuple
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
self.generated = None # 推迟导入
def _import_generated(self):
"""推迟导入 generated"""
if self.generated is None:
try:
# 从正确的路径导入生成的模块
from generated.cmvr.api import common_pb2, microphone_command_pb2
self.generated = type('GeneratedModules', (), {
'common_pb2': common_pb2,
'microphone_command_pb2': microphone_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
header.timestamp.GetCurrentTime()
return header
def get_status(self) -> Tuple[CMVRErrorCode, MicState]:
"""获取麦克风状态"""
try:
generated = self._import_generated()
request = generated.microphone_command_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:
generated = self._import_generated()
request = generated.microphone_command_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:
generated = self._import_generated()
request = generated.microphone_command_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

277
cmvr/models.py Normal file
View File

@ -0,0 +1,277 @@
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 CameraIntrinsics:
"""相机内参与Protobuf CameraIntrinsics对齐"""
cx: float = 0.0 # 对应Protobuf tag=1
cy: float = 0.0 # 对应Protobuf tag=2
fx: float = 0.0 # 对应Protobuf tag=3
fy: float = 0.0 # 对应Protobuf tag=4
# 畸变系数固定5个元素k1, k2, p1, p2, k3与Protobuf tag=5对齐
coeffs: List[float] = field(default_factory=lambda: [0.0]*5)
@dataclass
class FreedomState:
"""灵巧手自由度状态"""
dof_id: str = ""
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[FreedomState] = field(default_factory=lambda: [FreedomState() 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, "未知")
def print_summary(self):
"""打印传感器数据摘要信息"""
print(f" 小拇指指端: {self.pinky_tip.rows}x{self.pinky_tip.cols} (数据大小: {self.pinky_tip.byteSize} bytes)")
print(f" 无名指指端: {self.ring_tip.rows}x{self.ring_tip.cols} (数据大小: {self.ring_tip.byteSize} bytes)")
print(f" 中指指端: {self.middle_tip.rows}x{self.middle_tip.cols} (数据大小: {self.middle_tip.byteSize} bytes)")
print(f" 食指指端: {self.index_tip.rows}x{self.index_tip.cols} (数据大小: {self.index_tip.byteSize} bytes)")
print(f" 大拇指指端: {self.thumb_tip.rows}x{self.thumb_tip.cols} (数据大小: {self.thumb_tip.byteSize} bytes)")
print(f" 大拇指指中: {self.thumb_middle.rows}x{self.thumb_middle.cols} (数据大小: {self.thumb_middle.byteSize} bytes)")
print(f" 掌心: {self.palm.rows}x{self.palm.cols} (数据大小: {self.palm.byteSize} bytes)")
# 机器人相关数据结构
@dataclass
class JointCmd:
"""关节控制命令"""
joint_name: str = "" # 关节名称
rad: float = 0.0 # 弧度
vel: float = 0.0 # 角速度rad/s
@dataclass
class Pose3D:
"""3D位姿"""
x: float = 0.0 # X坐标
y: float = 0.0 # Y坐标
z: float = 0.0 # Z坐标
rx: float = 0.0 # 绕X轴旋转弧度
ry: float = 0.0 # 绕Y轴旋转弧度
rz: float = 0.0 # 绕Z轴旋转弧度
def to_array(self) -> np.ndarray:
"""转换为numpy数组"""
return np.array([self.x, self.y, self.z, self.rx, self.ry, self.rz])
@classmethod
def from_array(cls, arr: np.ndarray) -> 'Pose3D':
"""从numpy数组创建Pose3D"""
return cls(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5])
@dataclass
class JointState:
"""关节状态"""
name: List[str] = field(default_factory=list) # 关节名称列表
position: List[float] = field(default_factory=list) # 关节位置列表(弧度)
velocity: List[float] = field(default_factory=list) # 关节速度列表rad/s
effort: List[float] = field(default_factory=list) # 关节力矩列表
timestamp: float = 0.0 # 时间戳
def __post_init__(self):
"""确保列表长度一致"""
if len(self.name) > 0:
n_joints = len(self.name)
self.position = self.position[:n_joints] + [0.0] * (n_joints - len(self.position))
self.velocity = self.velocity[:n_joints] + [0.0] * (n_joints - len(self.velocity))
self.effort = self.effort[:n_joints] + [0.0] * (n_joints - len(self.effort))
def get_joint_by_name(self, joint_name: str) -> tuple:
"""根据关节名称获取状态"""
if joint_name in self.name:
idx = self.name.index(joint_name)
return (self.position[idx], self.velocity[idx], self.effort[idx])
return (0.0, 0.0, 0.0)
def to_dict(self) -> dict:
"""转换为字典格式"""
return {
'name': self.name,
'positions': self.position,
'velocities': self.velocity,
'efforts': self.effort,
'timestamp': self.timestamp
}

158
cmvr/speaker_client.py Normal file
View File

@ -0,0 +1,158 @@
import grpc
from typing import Tuple
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
self.generated = None # 推迟导入
def _import_generated(self):
"""推迟导入 generated"""
if self.generated is None:
try:
# 从正确的路径导入生成的模块
from generated.cmvr.api import common_pb2, speaker_command_pb2
self.generated = type('GeneratedModules', (), {
'common_pb2': common_pb2,
'speaker_command_pb2': speaker_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
header.timestamp.GetCurrentTime()
return header
def get_status(self) -> Tuple[CMVRErrorCode, SpeakerState]:
"""获取扬声器状态"""
try:
generated = self._import_generated()
request = generated.speaker_command_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:
generated = self._import_generated()
request = generated.speaker_command_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:
generated = self._import_generated()
request = generated.speaker_command_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:
generated = self._import_generated()
request = generated.speaker_command_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:
generated = self._import_generated()
request = generated.speaker_command_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:
generated = self._import_generated()
request = generated.speaker_command_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:
generated = self._import_generated()
request = generated.speaker_command_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

879
example_usage.py Normal file
View File

@ -0,0 +1,879 @@
#!/usr/bin/env python3
import sys
import os
import time
import random
import grpc # 新增grpc导入
import cv2
from typing import Generator # 新增Generator类型导入
import numpy as np # 确保导入numpy传感器数据处理需要
# 添加项目根目录到 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,JointCmd, Pose3D,RobotCartesian, RobotJointIndexDirection
def test_biohead():
"""测试仿生头功能"""
print("=== 测试仿生头功能 ===")
# 初始化客户端
client = CMVRGrpcClient("192.168.1.222: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.jaw_x = 0.5
expression.jaw_y = 0.5
# # 设置眉毛,使用随机数并打印
# 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}")
# 闭眼,使用随机数并打印
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}")
# 结束流式会话
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()
def test_camera():
"""测试相机功能"""
print("=== 测试相机功能 ===")
# 初始化客户端
client = CMVRGrpcClient("192.168.1.222:50060")
if not client.is_connected():
print("连接服务器失败")
return
# 获取相机客户端
camera = client.get_camera("cam4") # 假设设备ID为"cam4"
# 获取相机初始状态
error_code, state = camera.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print(f"相机初始状态: 已初始化={state.is_initialized}, 已打开={state.is_opened}, "
f"已流传输={state.is_streaming}, 已录制={state.is_recording}, "
f"分辨率={state.width}x{state.height}, FPS={state.fps}")
else:
print(f"获取相机状态失败,错误码: {error_code}")
client.close()
return
# 启动相机
start_result = camera.start_camera()
if start_result == CMVRErrorCode.CMVR_SUCCESS:
print("相机启动成功")
else:
print(f"相机启动失败,错误码: {start_result}")
client.close()
return
# 等待相机启动
time.sleep(1)
# 再次获取相机状态确认
error_code, state = camera.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print(f"启动后状态: 已打开={state.is_opened}, 已流传输={state.is_streaming}")
else:
print(f"获取相机状态失败,错误码: {error_code}")
# 测试获取RGB图像并保存
print("开始获取图像并保存 (3次)...")
for i in range(3):
error_code, img_array, width, height = camera.get_rgb_image()
if error_code == CMVRErrorCode.CMVR_SUCCESS and img_array.size > 0:
# 生成保存路径(当前目录)
img_filename = f"camera_test_image_{i+1}_{int(time.time())}.jpg"
# 转换颜色格式(如果需要)
if len(img_array.shape) == 3:
cv2_img = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
else:
cv2_img = img_array
# 保存图像
cv2.imwrite(img_filename, cv2_img)
print(f"{i+1}次获取图像成功,已保存至: {img_filename},分辨率: {width}x{height}")
else:
print(f"{i+1}次获取图像失败,错误码: {error_code}")
time.sleep(1)
# 测试录像功能
video_path = f"test_recording_{int(time.time())}.mp4"
print(f"开始录像,保存路径: {os.path.abspath(video_path)}") # 显示绝对路径
record_start_result = camera.start_record(video_path)
if record_start_result == CMVRErrorCode.CMVR_SUCCESS:
print("录像开始成功录制5秒...")
time.sleep(5)
# 停止录像
record_stop_result = camera.stop_record()
if record_stop_result == CMVRErrorCode.CMVR_SUCCESS:
print("录像停止成功")
else:
print(f"录像停止失败,错误码: {record_stop_result}")
else:
print(f"录像开始失败,错误码: {record_start_result}")
# 停止相机
stop_result = camera.stop_camera()
if stop_result == CMVRErrorCode.CMVR_SUCCESS:
print("相机停止成功")
else:
print(f"相机停止失败,错误码: {stop_result}")
# 最终状态确认
error_code, state = camera.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print(f"最终状态: 已打开={state.is_opened}, 已流传输={state.is_streaming}")
else:
print(f"获取相机状态失败,错误码: {error_code}")
client.close()
def test_rgb_image_stream():
"""测试双向流视频帧数据接口"""
print("=== 测试双向流视频帧数据接口 ===")
# 初始化GRPC客户端
client = CMVRGrpcClient("192.168.1.222:50060")
if not client.is_connected():
print("连接服务器失败")
return
# 获取camera客户端
camera = client.get_camera("cam4")
if not camera:
print("获取camera客户端失败")
client.close()
return
# 启动相机
start_result = camera.start_camera()
if start_result == CMVRErrorCode.CMVR_SUCCESS:
print("相机启动成功")
else:
print(f"相机启动失败,错误码: {start_result}")
client.close()
return
# 等待相机启动
time.sleep(1)
try:
# 1. 启动传感器数据流
print("\n--- 启动rgb数据流 ---")
# 创建请求生成器(双向流不需要参数,发送空请求)
def request_generator() -> Generator:
# 发送初始请求启动流
yield camera.create_rgb_stream_request()
# 保持流连接每5秒发送一次心跳可选
try:
while True:
time.sleep(5)
# 发送空请求保持连接
yield camera.create_rgb_stream_request()
except GeneratorExit:
print("请求生成器已关闭")
return
# 2. 接收并处理流数据
print("开始接收rgb流数据5秒后自动停止...")
start_time = time.time()
stream_duration = 15 # 接收15秒数据
keyframe_count = 0 # 关键帧计数器
total_frame_count = 0 # 总帧数计数器
# 存储流迭代器的变量
stream_iterator = camera.get_rgb_stream(request_generator())
# 调用双向流接口
for feedback in stream_iterator:
# 检查是否超时
if time.time() - start_time > stream_duration:
print("\n达到预设接收时间,停止接收")
break
total_frame_count += 1 # 累加总帧数
# 检查是否为关键帧并计数
if feedback.color_frame.is_key_frame:
keyframe_count += 1
print(f"[关键帧 #{keyframe_count}] 收到关键帧 (总帧数: {total_frame_count})")
stream_iterator.cancel()
print(f"\n流接收结束 - 总帧数: {total_frame_count}, 关键帧数: {keyframe_count}, 关键帧占比: {keyframe_count/total_frame_count:.2%}")
except grpc.RpcError as e:
print(f"双向流通信错误: {e}")
except Exception as e:
print(f"处理流数据时发生错误: {e}")
finally:
# 关闭连接
client.close()
print("\n=== 双向流传感器数据测试完成 ===")
def test_dexhand():
"""测试DexHand功能获取状态、设置角度和传感器数据"""
print("=== 测试DexHand功能 ===")
# 初始化GRPC客户端使用实际服务器地址
client = CMVRGrpcClient("192.168.0.222:50052")
if not client.is_connected():
print("连接服务器失败")
return
# 获取DexHand客户端假设设备ID为"hand1"根据实际设备ID修改
dexhand = client.get_dexhand("hand1")
if not dexhand:
print("获取DexHand客户端失败")
client.close()
return
# 定义字符串ID与手指名称的映射与proto/服务端一致)
dof_id_to_name = {
"little_finger": "小拇指",
"ring_finger": "无名指",
"middle_finger": "中指",
"index_finger": "食指",
"thumb_bend": "大拇指弯曲",
"thumb_rotate": "大拇指旋转"
}
# 有效字符串ID列表用于后续判断
valid_dof_ids = set(dof_id_to_name.keys())
# 1. 获取初始状态
print("\n--- 获取初始状态 ---")
error_code, state = dexhand.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print(f"初始化状态: 已初始化={state.is_initialized}")
print("各自由度状态:")
for hand in state.hands:
# 匹配字符串ID对应的手指名称未知ID显示原始值
finger_name = dof_id_to_name.get(hand.dof_id, f"未知自由度({hand.dof_id})")
print(f" {finger_name} (ID:{hand.dof_id}): 角度={hand.angle}, 速度={hand.speed}, "
f"受力={hand.force}, 位置={hand.position}, 温度={hand.temperature}°C")
if hand.error != 0:
print(f" 警告: {finger_name} 存在故障: {hand.error_message}")
else:
print(f"获取初始状态失败,错误码: {error_code}")
client.close()
return
# 1.1 获取初始传感器数据
print("\n--- 获取初始传感器数据 ---")
error_code, sensors = dexhand.get_sensor_data()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print("初始传感器数据摘要:")
sensors.print_summary() # 调用传感器数据摘要打印方法
# 打印掌心和食指指端的传感器数据示例
print(f" 掌心传感器数据形状: {sensors.palm.data.shape}")
print(f" 食指指端传感器数据形状: {sensors.index_tip.data.shape}")
else:
print(f"获取初始传感器数据失败,错误码: {error_code}")
# 2. 设置测试角度(小拇指、食指、大拇指弯曲等)
print("\n--- 设置测试角度 ---")
# 角度字典: 键为字符串类型自由度ID值为0-1百分比与set_angle函数参数要求一致
test_angles = {
"little_finger": 0.9, # 小拇指
"ring_finger": 0.9, # 无名指
"middle_finger": 0.9, # 中指
"index_finger": 0.9, # 食指
"thumb_bend": 0.9, # 大拇指弯曲
"thumb_rotate": 0.9 # 大拇指旋转
}
# 打印设置信息(通过映射表显示中文名称)
print("准备设置角度:")
for dof_id, value in test_angles.items():
finger_name = dof_id_to_name[dof_id] # 已确保ID有效直接获取名称
print(f" {finger_name} (ID:{dof_id}): {value*100}%")
# 执行角度设置直接传入字符串ID的字典
set_result = dexhand.set_angle(test_angles)
if set_result == CMVRErrorCode.CMVR_SUCCESS:
print("角度设置命令发送成功,等待执行器动作...")
time.sleep(2) # 等待执行器完成动作
else:
print(f"角度设置失败,错误码: {set_result}")
client.close()
return
# 2.1 获取角度变化后的传感器数据
print("\n--- 获取角度变化后传感器数据 ---")
error_code, sensors_after = dexhand.get_sensor_data()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print("角度变化后传感器数据摘要:")
# 对比食指指腹在动作前后的平均压力变化
if hasattr(sensors, 'index_pad') and hasattr(sensors_after, 'index_pad'):
avg_before = sensors.index_pad.data.mean() if sensors.index_pad.data.size > 0 else 0
avg_after = sensors_after.index_pad.data.mean() if sensors_after.index_pad.data.size > 0 else 0
print(f" 食指指腹平均压力变化: {avg_before:.2f}{avg_after:.2f}")
# 打印大拇指指端数据示例前3个值
if hasattr(sensors_after, 'thumb_tip') and sensors_after.thumb_tip.data.size > 0:
print(f" 大拇指指端部分数据: {sensors_after.thumb_tip.data.flatten()[:3]}...")
else:
print(f"获取角度变化后传感器数据失败,错误码: {error_code}")
# 3. 获取设置后的状态
print("\n--- 获取设置后状态 ---")
error_code, state = dexhand.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print("设置后各自由度状态(仅显示已设置的自由度):")
for hand in state.hands:
# 只打印我们设置过的自由度字符串ID对比
if hand.dof_id in test_angles:
finger_name = dof_id_to_name[hand.dof_id]
print(f" {finger_name} (ID:{hand.dof_id}): 新角度={hand.angle}, 当前位置={hand.position}")
else:
print(f"获取设置后状态失败,错误码: {error_code}")
# 4. 恢复默认角度(复位操作)
print("\n--- 恢复默认角度 ---")
# 复位字典键为字符串ID值为默认百分比
default_angles = {
"little_finger": 0.1,
"ring_finger": 0.1,
"middle_finger": 0.1,
"index_finger": 0.1,
"thumb_bend": 0.1,
"thumb_rotate": 0.8
}
set_result = dexhand.set_angle(default_angles)
if set_result == CMVRErrorCode.CMVR_SUCCESS:
print("默认角度恢复成功,等待复位完成...")
time.sleep(2)
else:
print(f"默认角度恢复失败,错误码: {set_result}")
# 4.1 获取复位后的传感器数据
print("\n--- 获取复位后传感器数据 ---")
error_code, sensors_reset = dexhand.get_sensor_data()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print("复位后传感器数据摘要:")
# 对比掌心压力在复位前后的变化
if hasattr(sensors_after, 'palm') and hasattr(sensors_reset, 'palm'):
avg_after = sensors_after.palm.data.mean() if sensors_after.palm.data.size > 0 else 0
avg_reset = sensors_reset.palm.data.mean() if sensors_reset.palm.data.size > 0 else 0
print(f" 掌心平均压力变化: {avg_after:.2f}{avg_reset:.2f}")
else:
print(f"获取复位后传感器数据失败,错误码: {error_code}")
# 5. 最终状态确认
print("\n--- 最终状态确认 ---")
error_code, state = dexhand.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print(f"最终初始化状态: {state.is_initialized}")
print("关键自由度最终位置:")
# 遍历需要确认的自由度字符串ID
for dof_id in test_angles.keys():
for hand in state.hands:
if hand.dof_id == dof_id:
finger_name = dof_id_to_name[dof_id]
print(f" {finger_name} (ID:{dof_id}): 角度={hand.angle}, 位置={hand.position}")
else:
print(f"获取最终状态失败,错误码: {error_code}")
# 关闭连接
client.close()
print("\n=== DexHand测试完成 ===")
def test_sensor_data_stream():
"""测试双向流传感器数据接口"""
print("=== 测试双向流传感器数据接口 ===")
# 初始化GRPC客户端
client = CMVRGrpcClient("192.168.1.222:50052")
if not client.is_connected():
print("连接服务器失败")
return
# 获取DexHand客户端
dexhand = client.get_dexhand("hand1")
if not dexhand:
print("获取DexHand客户端失败")
client.close()
return
try:
# 1. 启动传感器数据流
print("\n--- 启动传感器数据流 ---")
# 创建请求生成器(双向流不需要参数,发送空请求)
def request_generator() -> Generator:
# 发送初始请求启动流
yield dexhand.create_stream_request()
# 保持流连接每5秒发送一次心跳可选
try:
while True:
time.sleep(5)
# 发送空请求保持连接
yield dexhand.create_stream_request()
except GeneratorExit:
print("请求生成器已关闭")
return
# 2. 接收并处理流数据
print("开始接收传感器流数据5秒后自动停止...")
start_time = time.time()
stream_duration = 15 # 接收15秒数据
# 调用双向流接口
for feedback in dexhand.get_sensor_data_stream(request_generator()):
# 检查是否超时
if time.time() - start_time > stream_duration:
print("\n达到预设接收时间,停止接收")
break
# # 检查反馈状态
# if not feedback.header.success:
# print(f"数据流接收失败,错误码: {feedback.header.error_code}")
# continue
# 处理传感器数据转换为HandTactileSensors对象
error_code, sensors = dexhand.parse_sensor_stream_data(feedback)
if error_code != CMVRErrorCode.CMVR_SUCCESS:
print("传感器数据解析失败")
continue
# 打印传感器数据摘要(每秒打印一次)
if int(time.time()) % 1 == 0: # 控制打印频率
print(f"\n--- 传感器数据 (时间: {time.time() - start_time:.2f}s) ---")
sensors.print_summary()
# 打印食指指端的实时数据示例
if sensors.index_tip.data is not None and sensors.index_tip.data.size > 0:
print(f"食指指端平均压力: {sensors.index_tip.data.mean():.2f}")
if sensors.palm.data is not None and sensors.palm.data.size > 0:
print(f"掌心平均压力: {sensors.palm.data.mean():.2f}")
except grpc.RpcError as e:
print(f"双向流通信错误: {e}")
except Exception as e:
print(f"处理流数据时发生错误: {e}")
finally:
# 关闭连接
client.close()
print("\n=== 双向流传感器数据测试完成 ===")
def test_speaker():
"""测试扬声器功能"""
print("\n=== 测试扬声器功能 ===")
# 初始化GRPC客户端
client = CMVRGrpcClient("192.168.1.222:50060")
if not client.is_connected():
print("连接服务器失败")
return
speaker = client.get_speaker("spk1")
if speaker is None:
print("获取扬声器失败")
return
# 获取状态
result, status = speaker.get_status()
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"扬声器状态: 初始化={status.is_initialized}, 运行中={status.is_running}")
else:
print(f"获取扬声器状态失败,错误码: {result}")
# 设置音量
result = speaker.set_volume(80) # 80%音量
if result == CMVRErrorCode.CMVR_SUCCESS:
print("设置音量成功")
else:
print(f"设置音量失败,错误码: {result}")
# 获取音量
result, volume = speaker.get_volume()
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"当前音量: {volume}%")
else:
print(f"获取音量失败,错误码: {result}")
# 播放音频 (需要替换为实际存在的音频文件路径)
audio_path = "/home/share/assets/upload/员工女_车内温度有点低是否需要调高空调.wav"
print(f"尝试播放音频: {audio_path}")
result = speaker.play_audio(audio_path)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("开始播放音频")
# 等待3秒
time.sleep(3)
# 暂停播放
result = speaker.pause_audio()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("暂停播放")
# 等待1秒
time.sleep(1)
# 继续播放
result = speaker.resume_audio()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("继续播放")
# 等待2秒
time.sleep(2)
# 停止播放
result = speaker.stop_audio()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("停止播放")
else:
print(f"停止播放失败,错误码: {result}")
else:
print(f"继续播放失败,错误码: {result}")
else:
print(f"暂停播放失败,错误码: {result}")
else:
print(f"播放音频失败,错误码: {result}")
def test_microphone():
"""测试麦克风功能"""
print("\n=== 测试麦克风功能 ===")
# 初始化GRPC客户端
client = CMVRGrpcClient("192.168.1.222:50060")
if not client.is_connected():
print("连接服务器失败")
return
microphone = client.get_micphone("mic2")
if microphone is None:
print("获取麦克风失败")
return
# 获取状态
result, status = microphone.get_status()
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"麦克风状态: 初始化={status.is_initialized}, 录音中={status.is_recording}")
else:
print(f"获取麦克风状态失败,错误码: {result}")
# 开始录音
audio_path = "/home/linbo/Data/Audio/recorded_audio.mp3"
result = microphone.start_record(audio_path)
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"开始录音,保存到: {audio_path}")
# 录音5秒
time.sleep(5)
# 停止录音
result = microphone.stop_record()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("停止录音")
else:
print(f"停止录音失败,错误码: {result}")
else:
print(f"开始录音失败,错误码: {result}")
def test_humanoid_robot():
"""测试机器人功能"""
print("\n=== 测试机器人功能 ===")
# 初始化GRPC客户端
client = CMVRGrpcClient("192.168.0.222:50052")
if not client.is_connected():
print("连接服务器失败")
return
humanoidRobot = client.get_humanoid_robot("hc01")
if humanoidRobot is None:
print("获取机器人失败")
return
# 使能
# print("\n1. 开启机器人使能...")
# result = humanoidRobot.torqueOn()
# if result == CMVRErrorCode.CMVR_SUCCESS:
# print("✅ 使能开启成功")
# else:
# print(f"❌ 使能开启失败,错误码: {result}")
# return
# 等待使能生效
import time
time.sleep(1)
try:
# 获取初始状态
print("\n2. 获取初始关节状态...")
# 获取状态
result, joint_states = humanoidRobot.get_joint_states()
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"成功获取 {len(joint_states)} 个关节状态组")
for i, status in enumerate(joint_states):
print(f"\n=== 关节状态组 {i+1} ===")
print(f"时间戳: {status.timestamp:.3f}s")
print(f"关节数量: {len(status.name)}")
if len(status.name) > 0:
print("\n关节详情:")
print(f"{'关节名称':<20} {'位置(rad)':<12} {'速度(rad/s)':<12} {'力矩':<10}")
print("-" * 55)
for j, name in enumerate(status.name):
pos = status.position[j] if j < len(status.position) else 0.0
vel = status.velocity[j] if j < len(status.velocity) else 0.0
eff = status.effort[j] if j < len(status.effort) else 0.0
print(f"{name:<20} {pos:<12.4f} {vel:<12.4f} {eff:<10.4f}")
else:
print("该状态组没有关节数据")
# 打印所有状态的汇总信息
print(f"\n=== 汇总信息 ===")
total_joints = sum(len(state.name) for state in joint_states)
timestamps = [state.timestamp for state in joint_states]
print(f"总关节数: {total_joints}")
print(f"时间戳范围: {min(timestamps):.3f}s - {max(timestamps):.3f}s")
else:
print(f"获取关节状态失败,错误码: {result}")
return
# 测试moveJ - 关节空间运动
print("\n3. 测试关节空间运动 (moveJ)...")
# 创建关节命令列表,回到零位
joint_commands = [
JointCmd(joint_name="L_SHOULDER_P", rad=0, vel=0.8),
JointCmd(joint_name="L_SHOULDER_R", rad=0, vel=0.8),
JointCmd(joint_name="L_SHOULDER_Y", rad=0, vel=0.8),
JointCmd(joint_name="L_ELBOW_R", rad=0, vel=0.8),
JointCmd(joint_name="L_WRIST_P", rad=0, vel=0.8),
JointCmd(joint_name="L_WRIST_Y", rad=0, vel=0.8),
JointCmd(joint_name="L_WRIST_R", rad=0, vel=0.8),
JointCmd(joint_name="R_SHOULDER_P", rad=0, vel=0.8),
JointCmd(joint_name="R_SHOULDER_R", rad=0, vel=0.8),
JointCmd(joint_name="R_SHOULDER_Y", rad=0, vel=0.8),
JointCmd(joint_name="R_ELBOW_R", rad=0, vel=0.8),
JointCmd(joint_name="R_WRIST_P", rad=0, vel=0.8),
JointCmd(joint_name="R_WRIST_Y", rad=0, vel=0.8),
JointCmd(joint_name="R_WRIST_R", rad=0, vel=0.8),
JointCmd(joint_name="WAIST_P", rad=0, vel=0.8),
JointCmd(joint_name="WAIST_Y", rad=0, vel=0.8)
]
result, _ = humanoidRobot.moveJ(joint_commands, overall_vel=0.8, overall_acc=0.4)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ moveJ 命令发送成功")
time.sleep(3) # 等待运动完成
else:
print(f"❌ moveJ 命令发送失败,错误码: {result}")
# 获取运动后的状态
print("\n 获取moveJ后的关节状态...")
result, status = humanoidRobot.get_joint_state()
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f" 运动后时间戳: {status.timestamp:.3f}s")
print(f" 关节数量: {len(status.name)}")
# 测试moveL - 直线空间运动
print("\n4. 测试直线空间运动 (moveL)...")
# 创建目标位姿 (相对于当前位姿的小幅移动)
target_pose = Pose3D(x=0.05, y=0.0, z=0.0, rx=0.0, ry=0.0, rz=0.0)
result, _ = humanoidRobot.moveL(ee_link="R_WRIST_R", target_pose=target_pose,
vel=0.05, acc=0.2)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ moveL 命令发送成功")
time.sleep(3) # 等待运动完成
else:
print(f"❌ moveL 命令发送失败,错误码: {result}")
# 测试speedJ - 关节速度控制
print("\n5. 测试关节速度控制 (speedJ)...")
result, error_msg = humanoidRobot.speedJ(joint_name="R_SHOULDER_P",
velocity=0.1,
direction=RobotJointIndexDirection.FORWARD,
acc=0.3)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ speedJ 命令发送成功")
time.sleep(2) # 运行2秒
# 停止运动
stop_result, _ = humanoidRobot.speedJ(joint_name="R_SHOULDER_P",
velocity=0.0,
direction=RobotJointIndexDirection.FORWARD,
acc=0.3)
print(" 停止speedJ运动")
else:
print(f"❌ speedJ 命令发送失败,错误码: {result}")
if error_msg:
print(f" 错误信息: {error_msg}")
# 测试speedL - 笛卡尔速度控制
print("\n6. 测试笛卡尔速度控制 (speedL)...")
result, error_msg = humanoidRobot.speedL(ee_link="R_WRIST_R",
velocity=0.02,
cartesian=RobotCartesian.X,
direction=RobotJointIndexDirection.FORWARD,
acc=0.1)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ speedL 命令发送成功")
time.sleep(2) # 运行2秒
# 停止运动
stop_result, _ = humanoidRobot.speedL(ee_link="R_WRIST_R",
velocity=0.0,
cartesian=RobotCartesian.X,
direction=RobotJointIndexDirection.FORWARD,
acc=0.1)
print(" 停止speedL运动")
else:
print(f"❌ speedL 命令发送失败,错误码: {result}")
if error_msg:
print(f" 错误信息: {error_msg}")
# 获取最终状态
print("\n7. 获取最终关节状态...")
result, status = humanoidRobot.get_joint_state()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("\n=== 最终关节状态信息 ===")
print(f"时间戳: {status.timestamp:.3f}s")
print(f"关节数量: {len(status.name)}")
if len(status.name) > 0:
print("\n关节详情:")
print(f"{'关节名称':<15} {'位置(rad)':<12} {'速度(rad/s)':<12} {'力矩':<10}")
print("-" * 50)
for i, name in enumerate(status.name):
pos = status.position[i] if i < len(status.position) else 0.0
vel = status.velocity[i] if i < len(status.velocity) else 0.0
eff = status.effort[i] if i < len(status.effort) else 0.0
print(f"{name:<15} {pos:<12.4f} {vel:<12.4f} {eff:<10.4f}")
else:
print("没有关节数据")
else:
print(f"获取最终状态失败,错误码: {result}")
except Exception as e:
print(f"\n❌ 测试过程中发生异常: {e}")
import traceback
traceback.print_exc()
finally:
# 去使能
print("\n8. 关闭机器人使能...")
result = humanoidRobot.torqueOff()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ 使能关闭成功")
else:
print(f"❌ 使能关闭失败,错误码: {result}")
print("\n=== 机器人功能测试完成 ===")
if __name__ == "__main__":
# test_biohead()
# test_camera()
# test_rgb_image_stream()
test_dexhand()
# test_sensor_data_stream()
# test_speaker()
# test_microphone()
# test_humanoid_robot()

840
examples/example_usage.py Normal file
View File

@ -0,0 +1,840 @@
#!/usr/bin/env python3
import sys
import os
import time
import random
import grpc # 新增grpc导入
import cv2
from typing import Generator # 新增Generator类型导入
import numpy as np # 确保导入numpy传感器数据处理需要
# 添加项目根目录到 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,JointCmd, Pose3D,RobotCartesian, RobotJointIndexDirection
def test_biohead():
"""测试仿生头功能"""
print("=== 测试仿生头功能 ===")
# 初始化客户端
client = CMVRGrpcClient("192.168.1.222: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.jaw_x = 0.5
expression.jaw_y = 0.5
# # 设置眉毛,使用随机数并打印
# 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}")
# 闭眼,使用随机数并打印
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}")
# 结束流式会话
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()
def test_camera():
"""测试相机功能"""
print("=== 测试相机功能 ===")
# 初始化客户端
client = CMVRGrpcClient("192.168.1.222:50060")
if not client.is_connected():
print("连接服务器失败")
return
# 获取相机客户端
camera = client.get_camera("cam4") # 假设设备ID为"cam4"
# 获取相机初始状态
error_code, state = camera.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print(f"相机初始状态: 已初始化={state.is_initialized}, 已打开={state.is_opened}, "
f"已流传输={state.is_streaming}, 已录制={state.is_recording}, "
f"分辨率={state.width}x{state.height}, FPS={state.fps}")
else:
print(f"获取相机状态失败,错误码: {error_code}")
client.close()
return
# 启动相机
start_result = camera.start_camera()
if start_result == CMVRErrorCode.CMVR_SUCCESS:
print("相机启动成功")
else:
print(f"相机启动失败,错误码: {start_result}")
client.close()
return
# 等待相机启动
time.sleep(1)
# 再次获取相机状态确认
error_code, state = camera.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print(f"启动后状态: 已打开={state.is_opened}, 已流传输={state.is_streaming}")
else:
print(f"获取相机状态失败,错误码: {error_code}")
# 测试获取RGB图像并保存
print("开始获取图像并保存 (3次)...")
for i in range(3):
error_code, img_array, width, height = camera.get_rgb_image()
if error_code == CMVRErrorCode.CMVR_SUCCESS and img_array.size > 0:
# 生成保存路径(当前目录)
img_filename = f"camera_test_image_{i+1}_{int(time.time())}.jpg"
# 转换颜色格式(如果需要)
if len(img_array.shape) == 3:
cv2_img = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
else:
cv2_img = img_array
# 保存图像
cv2.imwrite(img_filename, cv2_img)
print(f"{i+1}次获取图像成功,已保存至: {img_filename},分辨率: {width}x{height}")
else:
print(f"{i+1}次获取图像失败,错误码: {error_code}")
time.sleep(1)
# 测试录像功能
video_path = f"test_recording_{int(time.time())}.mp4"
print(f"开始录像,保存路径: {os.path.abspath(video_path)}") # 显示绝对路径
record_start_result = camera.start_record(video_path)
if record_start_result == CMVRErrorCode.CMVR_SUCCESS:
print("录像开始成功录制5秒...")
time.sleep(5)
# 停止录像
record_stop_result = camera.stop_record()
if record_stop_result == CMVRErrorCode.CMVR_SUCCESS:
print("录像停止成功")
else:
print(f"录像停止失败,错误码: {record_stop_result}")
else:
print(f"录像开始失败,错误码: {record_start_result}")
# 停止相机
stop_result = camera.stop_camera()
if stop_result == CMVRErrorCode.CMVR_SUCCESS:
print("相机停止成功")
else:
print(f"相机停止失败,错误码: {stop_result}")
# 最终状态确认
error_code, state = camera.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print(f"最终状态: 已打开={state.is_opened}, 已流传输={state.is_streaming}")
else:
print(f"获取相机状态失败,错误码: {error_code}")
client.close()
def test_rgb_image_stream():
"""测试双向流视频帧数据接口"""
print("=== 测试双向流视频帧数据接口 ===")
# 初始化GRPC客户端
client = CMVRGrpcClient("192.168.1.222:50060")
if not client.is_connected():
print("连接服务器失败")
return
# 获取camera客户端
camera = client.get_camera("cam4")
if not camera:
print("获取camera客户端失败")
client.close()
return
# 启动相机
start_result = camera.start_camera()
if start_result == CMVRErrorCode.CMVR_SUCCESS:
print("相机启动成功")
else:
print(f"相机启动失败,错误码: {start_result}")
client.close()
return
# 等待相机启动
time.sleep(1)
try:
# 1. 启动传感器数据流
print("\n--- 启动rgb数据流 ---")
# 创建请求生成器(双向流不需要参数,发送空请求)
def request_generator() -> Generator:
# 发送初始请求启动流
yield camera.create_rgb_stream_request()
# 保持流连接每5秒发送一次心跳可选
try:
while True:
time.sleep(5)
# 发送空请求保持连接
yield camera.create_rgb_stream_request()
except GeneratorExit:
print("请求生成器已关闭")
return
# 2. 接收并处理流数据
print("开始接收rgb流数据5秒后自动停止...")
start_time = time.time()
stream_duration = 15 # 接收15秒数据
keyframe_count = 0 # 关键帧计数器
total_frame_count = 0 # 总帧数计数器
# 存储流迭代器的变量
stream_iterator = camera.get_rgb_stream(request_generator())
# 调用双向流接口
for feedback in stream_iterator:
# 检查是否超时
if time.time() - start_time > stream_duration:
print("\n达到预设接收时间,停止接收")
break
total_frame_count += 1 # 累加总帧数
# 检查是否为关键帧并计数
if feedback.color_frame.is_key_frame:
keyframe_count += 1
print(f"[关键帧 #{keyframe_count}] 收到关键帧 (总帧数: {total_frame_count})")
stream_iterator.cancel()
print(f"\n流接收结束 - 总帧数: {total_frame_count}, 关键帧数: {keyframe_count}, 关键帧占比: {keyframe_count/total_frame_count:.2%}")
except grpc.RpcError as e:
print(f"双向流通信错误: {e}")
except Exception as e:
print(f"处理流数据时发生错误: {e}")
finally:
# 关闭连接
client.close()
print("\n=== 双向流传感器数据测试完成 ===")
def test_dexhand():
"""测试DexHand功能获取状态、设置角度和传感器数据"""
print("=== 测试DexHand功能 ===")
# 初始化GRPC客户端使用实际服务器地址
client = CMVRGrpcClient("192.168.1.222:50060")
if not client.is_connected():
print("连接服务器失败")
return
# 获取DexHand客户端假设设备ID为"hand1"根据实际设备ID修改
dexhand = client.get_dexhand("hand1")
if not dexhand:
print("获取DexHand客户端失败")
client.close()
return
# 1. 获取初始状态
print("\n--- 获取初始状态 ---")
error_code, state = dexhand.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print(f"初始化状态: 已初始化={state.is_initialized}")
print("各自由度状态:")
for hand in state.hands:
print(f" 自由度ID: {hand.dof_id}, 角度: {hand.angle}, 速度: {hand.speed}, "
f"受力: {hand.force}, 位置: {hand.position}, 温度: {hand.temperature}°C")
if hand.error != 0:
print(f" 警告: 自由度ID {hand.dof_id} 存在故障: {hand.error_message}")
else:
print(f"获取初始状态失败,错误码: {error_code}")
client.close()
return
# 1.1 获取初始传感器数据
print("\n--- 获取初始传感器数据 ---")
error_code, sensors = dexhand.get_sensor_data()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print("初始传感器数据摘要:")
sensors.print_summary() # 调用传感器数据摘要打印方法
# 打印掌心和食指指端的传感器数据示例
print(f" 掌心传感器数据形状: {sensors.palm.data.shape}")
print(f" 食指指端传感器数据形状: {sensors.index_tip.data.shape}")
else:
print(f"获取初始传感器数据失败,错误码: {error_code}")
# 2. 设置测试角度(小拇指、食指、大拇指弯曲)
print("\n--- 设置测试角度 ---")
# 角度字典: 键为自由度ID (0-6)值为0-1百分比
test_angles = {
0: 0.9, # 小拇指
1: 0.9, # 无名指
2: 0.9, # 中指
3: 0.9, # 食指
4: 0.9, # 大拇指弯曲
5: 0.9 # 大拇指旋转
}
# 打印设置信息映射自由度ID到手指名称
finger_map = {
0: "小拇指",
1: "无名指",
2: "中指",
3: "食指",
4: "大拇指弯曲",
5: "大拇指旋转",
6: "预留自由度"
}
print("准备设置角度:")
for dof_id, value in test_angles.items():
finger_name = finger_map.get(dof_id, f"自由度{dof_id}")
print(f" {finger_name} (ID:{dof_id}): {value*100}%")
# 执行角度设置
set_result = dexhand.set_angle(test_angles)
if set_result == CMVRErrorCode.CMVR_SUCCESS:
print("角度设置命令发送成功,等待执行器动作...")
time.sleep(2) # 等待执行器完成动作
else:
print(f"角度设置失败,错误码: {set_result}")
client.close()
return
# 2.1 获取角度变化后的传感器数据
print("\n--- 获取角度变化后传感器数据 ---")
error_code, sensors_after = dexhand.get_sensor_data()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print("角度变化后传感器数据摘要:")
# 对比食指指腹在动作前后的平均压力变化
if hasattr(sensors, 'index_pad') and hasattr(sensors_after, 'index_pad'):
avg_before = sensors.index_pad.data.mean() if sensors.index_pad.data.size > 0 else 0
avg_after = sensors_after.index_pad.data.mean() if sensors_after.index_pad.data.size > 0 else 0
print(f" 食指指腹平均压力变化: {avg_before:.2f}{avg_after:.2f}")
# 打印大拇指指端数据示例前3个值
if sensors_after.thumb_tip.data.size > 0:
print(f" 大拇指指端部分数据: {sensors_after.thumb_tip.data.flatten()[:3]}...")
else:
print(f"获取角度变化后传感器数据失败,错误码: {error_code}")
# 3. 获取设置后的状态
print("\n--- 获取设置后状态 ---")
error_code, state = dexhand.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print("设置后各自由度状态:")
for hand in state.hands:
# 只打印我们设置过的自由度
if hand.dof_id in test_angles:
print(f" 自由度ID: {hand.dof_id}, 新角度: {hand.angle}, 当前位置: {hand.position}")
else:
print(f"获取设置后状态失败,错误码: {error_code}")
# 4. 恢复默认角度(复位操作)
print("\n--- 恢复默认角度 ---")
default_angles = {
0: 0.1,
1: 0.1,
2: 0.1,
3: 0.1,
4: 0.1,
5: 0.8
}
set_result = dexhand.set_angle(default_angles)
if set_result == CMVRErrorCode.CMVR_SUCCESS:
print("默认角度恢复成功,等待复位完成...")
time.sleep(2)
else:
print(f"默认角度恢复失败,错误码: {set_result}")
# 4.1 获取复位后的传感器数据
print("\n--- 获取复位后传感器数据 ---")
error_code, sensors_reset = dexhand.get_sensor_data()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print("复位后传感器数据摘要:")
# 对比掌心压力在复位前后的变化
if hasattr(sensors_after, 'palm') and hasattr(sensors_reset, 'palm'):
avg_after = sensors_after.palm.data.mean() if sensors_after.palm.data.size > 0 else 0
avg_reset = sensors_reset.palm.data.mean() if sensors_reset.palm.data.size > 0 else 0
print(f" 掌心平均压力变化: {avg_after:.2f}{avg_reset:.2f}")
else:
print(f"获取复位后传感器数据失败,错误码: {error_code}")
# 5. 最终状态确认
print("\n--- 最终状态确认 ---")
error_code, state = dexhand.get_status()
if error_code == CMVRErrorCode.CMVR_SUCCESS:
print(f"最终初始化状态: {state.is_initialized}")
print("关键自由度最终位置:")
for dof_id in test_angles.keys():
for hand in state.hands:
if hand.dof_id == dof_id:
print(f" 自由度ID {dof_id}: 角度={hand.angle}, 位置={hand.position}")
else:
print(f"获取最终状态失败,错误码: {error_code}")
# 关闭连接
client.close()
print("\n=== DexHand测试完成 ===")
def test_sensor_data_stream():
"""测试双向流传感器数据接口"""
print("=== 测试双向流传感器数据接口 ===")
# 初始化GRPC客户端
client = CMVRGrpcClient("192.168.1.222:50052")
if not client.is_connected():
print("连接服务器失败")
return
# 获取DexHand客户端
dexhand = client.get_dexhand("hand1")
if not dexhand:
print("获取DexHand客户端失败")
client.close()
return
try:
# 1. 启动传感器数据流
print("\n--- 启动传感器数据流 ---")
# 创建请求生成器(双向流不需要参数,发送空请求)
def request_generator() -> Generator:
# 发送初始请求启动流
yield dexhand.create_stream_request()
# 保持流连接每5秒发送一次心跳可选
try:
while True:
time.sleep(5)
# 发送空请求保持连接
yield dexhand.create_stream_request()
except GeneratorExit:
print("请求生成器已关闭")
return
# 2. 接收并处理流数据
print("开始接收传感器流数据5秒后自动停止...")
start_time = time.time()
stream_duration = 15 # 接收15秒数据
# 调用双向流接口
for feedback in dexhand.get_sensor_data_stream(request_generator()):
# 检查是否超时
if time.time() - start_time > stream_duration:
print("\n达到预设接收时间,停止接收")
break
# # 检查反馈状态
# if not feedback.header.success:
# print(f"数据流接收失败,错误码: {feedback.header.error_code}")
# continue
# 处理传感器数据转换为HandTactileSensors对象
error_code, sensors = dexhand.parse_sensor_stream_data(feedback)
if error_code != CMVRErrorCode.CMVR_SUCCESS:
print("传感器数据解析失败")
continue
# 打印传感器数据摘要(每秒打印一次)
if int(time.time()) % 1 == 0: # 控制打印频率
print(f"\n--- 传感器数据 (时间: {time.time() - start_time:.2f}s) ---")
sensors.print_summary()
# 打印食指指端的实时数据示例
if sensors.index_tip.data is not None and sensors.index_tip.data.size > 0:
print(f"食指指端平均压力: {sensors.index_tip.data.mean():.2f}")
if sensors.palm.data is not None and sensors.palm.data.size > 0:
print(f"掌心平均压力: {sensors.palm.data.mean():.2f}")
except grpc.RpcError as e:
print(f"双向流通信错误: {e}")
except Exception as e:
print(f"处理流数据时发生错误: {e}")
finally:
# 关闭连接
client.close()
print("\n=== 双向流传感器数据测试完成 ===")
def test_speaker():
"""测试扬声器功能"""
print("\n=== 测试扬声器功能 ===")
# 初始化GRPC客户端
client = CMVRGrpcClient("192.168.1.222:50060")
if not client.is_connected():
print("连接服务器失败")
return
speaker = client.get_speaker("spk1")
if speaker is None:
print("获取扬声器失败")
return
# 获取状态
result, status = speaker.get_status()
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"扬声器状态: 初始化={status.is_initialized}, 运行中={status.is_running}")
else:
print(f"获取扬声器状态失败,错误码: {result}")
# 设置音量
result = speaker.set_volume(80) # 80%音量
if result == CMVRErrorCode.CMVR_SUCCESS:
print("设置音量成功")
else:
print(f"设置音量失败,错误码: {result}")
# 获取音量
result, volume = speaker.get_volume()
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"当前音量: {volume}%")
else:
print(f"获取音量失败,错误码: {result}")
# 播放音频 (需要替换为实际存在的音频文件路径)
audio_path = "/home/share/assets/upload/员工女_车内温度有点低是否需要调高空调.wav"
print(f"尝试播放音频: {audio_path}")
result = speaker.play_audio(audio_path)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("开始播放音频")
# 等待3秒
time.sleep(3)
# 暂停播放
result = speaker.pause_audio()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("暂停播放")
# 等待1秒
time.sleep(1)
# 继续播放
result = speaker.resume_audio()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("继续播放")
# 等待2秒
time.sleep(2)
# 停止播放
result = speaker.stop_audio()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("停止播放")
else:
print(f"停止播放失败,错误码: {result}")
else:
print(f"继续播放失败,错误码: {result}")
else:
print(f"暂停播放失败,错误码: {result}")
else:
print(f"播放音频失败,错误码: {result}")
def test_microphone():
"""测试麦克风功能"""
print("\n=== 测试麦克风功能 ===")
# 初始化GRPC客户端
client = CMVRGrpcClient("192.168.1.222:50060")
if not client.is_connected():
print("连接服务器失败")
return
microphone = client.get_micphone("mic2")
if microphone is None:
print("获取麦克风失败")
return
# 获取状态
result, status = microphone.get_status()
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"麦克风状态: 初始化={status.is_initialized}, 录音中={status.is_recording}")
else:
print(f"获取麦克风状态失败,错误码: {result}")
# 开始录音
audio_path = "/home/linbo/Data/Audio/recorded_audio.mp3"
result = microphone.start_record(audio_path)
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f"开始录音,保存到: {audio_path}")
# 录音5秒
time.sleep(5)
# 停止录音
result = microphone.stop_record()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("停止录音")
else:
print(f"停止录音失败,错误码: {result}")
else:
print(f"开始录音失败,错误码: {result}")
def test_humanoid_robot():
"""测试机器人功能"""
print("\n=== 测试机器人功能 ===")
# 初始化GRPC客户端
client = CMVRGrpcClient("192.168.0.222:50052")
if not client.is_connected():
print("连接服务器失败")
return
humanoidRobot = client.get_humanoid_robot("hc01")
if humanoidRobot is None:
print("获取机器人失败")
return
# 获取初始状态
print("\n1. 获取初始关节状态...")
result, status = humanoidRobot.get_joint_state()
if result == CMVRErrorCode.CMVR_SUCCESS:
# 打印关节状态详细信息
print("\n=== 初始关节状态信息 ===")
print(f"时间戳: {status.timestamp:.3f}s")
print(f"关节数量: {len(status.name)}")
if len(status.name) > 0:
print("\n关节详情:")
print(f"{'关节名称':<15} {'位置(rad)':<12} {'速度(rad/s)':<12} {'力矩':<10}")
print("-" * 50)
for i, name in enumerate(status.name):
pos = status.position[i] if i < len(status.position) else 0.0
vel = status.velocity[i] if i < len(status.velocity) else 0.0
eff = status.effort[i] if i < len(status.effort) else 0.0
print(f"{name:<15} {pos:<12.4f} {vel:<12.4f} {eff:<10.4f}")
else:
print("没有关节数据")
else:
print(f"获取关节状态失败,错误码: {result}")
# 使能
print("\n2. 开启机器人使能...")
result = humanoidRobot.torqueOn()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ 使能开启成功")
else:
print(f"❌ 使能开启失败,错误码: {result}")
return
# 等待使能生效
import time
time.sleep(1)
try:
# 测试moveJ - 关节空间运动
print("\n3. 测试关节空间运动 (moveJ)...")
# 创建关节命令列表
joint_commands = [
JointCmd(joint_name="right_shoulder_pitch", rad=0.5, vel=0.3),
JointCmd(joint_name="right_shoulder_roll", rad=0.3, vel=0.3),
JointCmd(joint_name="right_elbow_pitch", rad=0.2, vel=0.3),
]
result, _ = humanoidRobot.moveJ(joint_commands, overall_vel=0.2, overall_acc=0.4)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ moveJ 命令发送成功")
time.sleep(3) # 等待运动完成
else:
print(f"❌ moveJ 命令发送失败,错误码: {result}")
# 获取运动后的状态
print("\n 获取moveJ后的关节状态...")
result, status = humanoidRobot.get_joint_state()
if result == CMVRErrorCode.CMVR_SUCCESS:
print(f" 运动后时间戳: {status.timestamp:.3f}s")
print(f" 关节数量: {len(status.name)}")
# 测试moveL - 直线空间运动
print("\n4. 测试直线空间运动 (moveL)...")
# 创建目标位姿 (相对于当前位姿的小幅移动)
target_pose = Pose3D(x=0.1, y=0.0, z=0.0, rx=0.0, ry=0.0, rz=0.0)
result, _ = humanoidRobot.moveL(ee_link="right_hand", target_pose=target_pose,
vel=0.05, acc=0.2)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ moveL 命令发送成功")
time.sleep(3) # 等待运动完成
else:
print(f"❌ moveL 命令发送失败,错误码: {result}")
# 测试speedJ - 关节速度控制
print("\n5. 测试关节速度控制 (speedJ)...")
result, error_msg = humanoidRobot.speedJ(joint_name="right_shoulder_pitch",
velocity=0.1,
direction=RobotJointIndexDirection.FORWARD,
acc=0.3)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ speedJ 命令发送成功")
time.sleep(2) # 运行2秒
# 停止运动
stop_result, _ = humanoidRobot.speedJ(joint_name="right_shoulder_pitch",
velocity=0.0,
direction=RobotJointIndexDirection.FORWARD,
acc=0.3)
print(" 停止speedJ运动")
else:
print(f"❌ speedJ 命令发送失败,错误码: {result}")
if error_msg:
print(f" 错误信息: {error_msg}")
# 测试speedL - 笛卡尔速度控制
print("\n6. 测试笛卡尔速度控制 (speedL)...")
result, error_msg = humanoidRobot.speedL(ee_link="right_hand",
velocity=0.02,
cartesian=RobotCartesian.X,
direction=RobotJointIndexDirection.FORWARD,
acc=0.1)
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ speedL 命令发送成功")
time.sleep(2) # 运行2秒
# 停止运动
stop_result, _ = humanoidRobot.speedL(ee_link="right_hand",
velocity=0.0,
cartesian=RobotCartesian.X,
direction=RobotJointIndexDirection.FORWARD,
acc=0.1)
print(" 停止speedL运动")
else:
print(f"❌ speedL 命令发送失败,错误码: {result}")
if error_msg:
print(f" 错误信息: {error_msg}")
# 获取最终状态
print("\n7. 获取最终关节状态...")
result, status = humanoidRobot.get_joint_state()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("\n=== 最终关节状态信息 ===")
print(f"时间戳: {status.timestamp:.3f}s")
print(f"关节数量: {len(status.name)}")
if len(status.name) > 0:
print("\n关节详情:")
print(f"{'关节名称':<15} {'位置(rad)':<12} {'速度(rad/s)':<12} {'力矩':<10}")
print("-" * 50)
for i, name in enumerate(status.name):
pos = status.position[i] if i < len(status.position) else 0.0
vel = status.velocity[i] if i < len(status.velocity) else 0.0
eff = status.effort[i] if i < len(status.effort) else 0.0
print(f"{name:<15} {pos:<12.4f} {vel:<12.4f} {eff:<10.4f}")
else:
print("没有关节数据")
else:
print(f"获取最终状态失败,错误码: {result}")
except Exception as e:
print(f"\n❌ 测试过程中发生异常: {e}")
import traceback
traceback.print_exc()
finally:
# 去使能
print("\n8. 关闭机器人使能...")
result = humanoidRobot.torqueOff()
if result == CMVRErrorCode.CMVR_SUCCESS:
print("✅ 使能关闭成功")
else:
print(f"❌ 使能关闭失败,错误码: {result}")
print("\n=== 机器人功能测试完成 ===")
if __name__ == "__main__":
# test_biohead()
# test_camera()
# test_rgb_image_stream()
# test_dexhand()
# test_sensor_data_stream()
# test_speaker()
# test_microphone()
test_humanoid_robot()

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

59
get_joint_state.py Normal file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env python3
import sys
import os
import time
import keyboard
import grpc
import numpy as np
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, JointCmd, Pose3D, RobotCartesian, RobotJointIndexDirection
def test_humanoid_robot():
"""循环获取所有电机状态按q退出每个电机信息单独一行并按指定格式打印"""
print("电机状态监控 (按q退出)\n")
client = CMVRGrpcClient("192.168.0.222:50052")
if not client.is_connected():
print("连接服务器失败")
return
humanoidRobot = client.get_humanoid_robot("hc01")
if humanoidRobot is None:
print("获取机器人失败")
return
try:
while True:
if keyboard.is_pressed('q'):
print("\n退出程序...")
break
# 获取所有关节状态
result, joint_states = humanoidRobot.get_joint_states()
if result == CMVRErrorCode.CMVR_SUCCESS and joint_states:
# 遍历所有状态组和电机
for status in joint_states:
for j, name in enumerate(status.name):
pos = status.position[j] if j < len(status.position) else 0.0
# 每个电机信息单独一行,按指定格式打印并以逗号结尾
print(f'{{"{name}", {pos:.6f}}},')
time.sleep(0.5)
# 如需每次刷新屏幕可取消下面一行注释
# os.system('cls' if os.name == 'nt' else 'clear')
except Exception as e:
print(f"\n错误: {e}")
import traceback
traceback.print_exc()
finally:
print("监控结束")
if __name__ == "__main__":
test_humanoid_robot()

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,175 @@
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 CameraIntrinsics {
float cx = 1; //
float cy = 2; //
float fx = 3; // x方向焦距
float fy = 4; // y方向焦距
repeated float coeffs = 5 [packed = true]; // 5
}
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;
CameraIntrinsics intrinsics = 3;
}
}
message GetDepthImageCommand {
message Request {
CommandHeader.Request header = 1;
}
message Feedback {
CommandHeader.Feedback header = 1;
FrameData depth_frame = 2;
CameraIntrinsics intrinsics = 3;
}
}
message GetRGBDImagesCommand {
message Request {
CommandHeader.Request header = 1;
}
message Feedback {
CommandHeader.Feedback header = 1;
FrameData color_frame = 2;
FrameData depth_frame = 3;
CameraIntrinsics intrinsics = 4;
}
}
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;
CameraIntrinsics intrinsics = 3;
int32 seq_no = 4;
}
}
message GetDepthImageStreamCommand {
message Request {
CommandHeader.Request header = 1;
bool eof = 2;
}
message Feedback {
CommandHeader.Feedback header = 1;
FrameData depth_frame = 2;
CameraIntrinsics intrinsics = 3;
int32 seq_no = 4;
}
}
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;
CameraIntrinsics intrinsics = 4;
int32 seq_no = 5;
}
}

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,43 @@
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; //
}
}
message ConfigParam {
string param_name = 1;
oneof param_value {
int32 int_value = 2; //
double double_value = 3; //
string string_value = 4; //
bool bool_value = 5; //
bytes bytes_value = 6; //
}
}

View File

@ -0,0 +1,172 @@
syntax = "proto3";
import "cmvr/api/common.proto";
package cmvr.api;
//
message FreedomValue {
string id = 1; // id
// little_finger
// ring_finger
// middle_finger
// index_finger
// thumb_bend
// thumb_rotate
float value = 2; // 0-1
}
//
message FreedomState {
string dof_id = 1; // idFreedomValue.id一致
// little_finger
// ring_finger
// middle_finger
// index_finger
// thumb_bend
// thumb_rotate
int32 angle = 2; // 0.1°/
int32 speed = 3; // 0.1°/s/
int32 force = 4; // 0.01N/
int32 position = 5; // /
int32 current = 6; // mA
int32 temperature = 7; //
int32 error = 8; // 00
repeated string error_message = 9; //
}
//
message SensorData {
//
enum FingerType {
PINKY = 0; //
RING = 1; //
MIDDLE_FINGER = 2; // PartType冲突
INDEX = 3; //
THUMB = 4; //
PALM = 5; //
}
//
enum PartType {
TIP = 0; //
FINGER = 1; //
PAD = 2; //
THUMB_MIDDLE = 3; //
PALM_PAD = 4; //
}
FingerType finger_type = 4; //
PartType part_type = 5; //
string sensor_name = 6; // "little_finger_tip"便
//
message RowData {
repeated int32 values = 1 [packed = true]; //
}
repeated RowData data = 1; //
int32 rows = 2; //
int32 cols = 3; //
}
//
message DexHandState {
bool is_initialized = 1; // truefalse
repeated FreedomState hands = 2; // FreedomValue.id一一对应
}
//
message GetDexHandStateCommand {
message Request {
CommandHeader.Request header = 1; // ID等
}
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; // ID01
}
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,24 @@
syntax = "proto3";
import "cmvr/api/common.proto";
package cmvr.api;
message Touch{
message Request{
CommandHeader.Request header = 1;
// unit: ms
int32 u = 2; //
int32 v = 3; //
// unit: N
double max_force = 4;
}
message Response{
CommandHeader.Feedback header= 1;
}
}

View File

@ -0,0 +1,9 @@
syntax = "proto3";
import "cmvr/api/hlc_command.proto";
package cmvr.api;
service HlcService{
rpc touch(Touch.Request) returns (Touch.Response);
}

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,122 @@
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 Pose3D{
double x = 1;
double y = 2;
double z = 3;
double rx = 4;
double ry = 5;
double rz = 6;
}
enum RobotCartesian{
X = 0;
Y = 1;
Z = 2;
RX = 3;
RY = 4;
RZ = 5;
} ;
enum RobotJointIndexDirection{
FORWARD = 0;
BACKWARD = 1;
X_POSITIVE = 2; // X轴正向
X_NEGATIVE = 3; // X轴负向
Y_POSITIVE = 4; // Y轴正向
Y_NEGATIVE = 5; // Y轴负向
Z_POSITIVE = 6; // Z轴正向
Z_NEGATIVE = 7; // Z轴负向
ROTATE_X = 8; // X轴旋转
ROTATE_Y = 9; // Y轴旋转
ROTATE_Z = 10; // Z轴旋转
}
message MoveJ{
message Request{
CommandHeader.Request header = 1;
repeated JointCmd cmds = 2;
double vel = 3;
double acc = 4;
}
message Response{
CommandHeader.Feedback header= 1;
}
}
message MoveL{
message Request{
CommandHeader.Request header = 1;
string ee_link = 2; //
Pose3D targetPose = 3;
double vel = 4;
double acc = 5;
}
message Response{
CommandHeader.Feedback header= 1;
}
}
message SpeedJ{
message Request{
CommandHeader.Request header = 1;
string joint_name = 2;
double vel = 3;
double acc = 4;
RobotJointIndexDirection dir = 5;
}
message Response{
CommandHeader.Feedback header= 1;
}
}
message SpeedL{
message Request{
CommandHeader.Request header = 1;
string ee_link = 2;
double vel = 3;
double acc = 4;
RobotJointIndexDirection dir = 5;
RobotCartesian cart = 6;
}
message Response{
CommandHeader.Feedback header= 1;
}
}
//
message JointState {
repeated string name = 1; //
repeated double position = 2; //
repeated double velocity = 3; //
repeated double effort = 4; //
double timestamp = 5; //
}
//
message JointResponse {
CommandHeader.Feedback header= 1;
repeated JointState state = 2;
}
//
message JointRequest {
CommandHeader.Request header = 1;
}

View File

@ -0,0 +1,16 @@
syntax = "proto3";
package cmvr.api;
import "cmvr/api/common.proto";
import "cmvr/api/humanoid_robot_command.proto";
service HumanoidRobotService{
rpc torqueOff(CommandHeader.Request) returns (CommandHeader.Feedback);
rpc torqueOn(CommandHeader.Request) returns (CommandHeader.Feedback);
rpc moveJ(MoveJ.Request) returns (MoveJ.Response);
rpc moveL(MoveL.Request) returns (MoveL.Response);
rpc speedJ(SpeedJ.Request) returns (SpeedJ.Response);
rpc speedL(SpeedL.Request) returns (SpeedL.Response);
rpc getJointState(JointRequest) returns (JointResponse);
}

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,61 @@
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;
}
}
message UpdateParamsCommand {
message Request {
CommandHeader.Request header = 1;
repeated ConfigParam params = 2;
}
message Feedback {
CommandHeader.Feedback header = 1;
}
}

View File

@ -0,0 +1,13 @@
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) {}
rpc UpdateParams(UpdateParamsCommand.Request) returns (UpdateParamsCommand.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
}

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

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