4. SDK接口调用说明
📋 目录
4.1 核心类和方法
4.1.1 SerialInterface 类
串口通信接口类,处理底层 Modbus RTU 硬件通信。
from psi_glove_sdk import SerialInterface
# 创建串口接口
serial = SerialInterface(
port="/dev/ttyACM0", # 串口路径(Linux: /dev/ttyACM0,Windows: COM3)
baudrate=115200, # 波特率,常用 115200 / 500000 / 921600
timeout=0.01, # 读超时(秒)
auto_connect=False, # 是否在构造时自动连接
mock=False, # 模拟模式(无真实硬件,用于测试)
write_timeout=None, # 写超时;默认 None 表示 max(1.0, timeout×10)
)| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
port | str | 必填 | 串口设备路径 |
baudrate | int | 115200 | 通信波特率 |
timeout | float | 0.01 | 读超时(秒) |
auto_connect | bool | False | 构造时是否自动connect() |
mock | bool | False | 模拟模式 |
write_timeout | float| None | None | 写超时;LRA 连续下发时建议保持默认 |
4.1.2 PSIGloveController 类
主控制器类,管理连接、Modbus 读关节、移动平均平滑、LRA 触觉下发。
from psi_glove_sdk import PSIGloveController, ADCCalibrationParams
# 创建控制器
controller = PSIGloveController(
communication_interface=serial, # 通信接口实例
smoothing_window_size=10, # 平滑窗口(样本数)
adc_calibration=ADCCalibrationParams() # 可选 ADC 校准,默认内置参数
)核心方法:
| 方法名称 | 返回类型 | 功能说明 |
|---|---|---|
connect() | bool | 连接设备,成功返回True |
disconnect() | None | 断开设备连接 |
is_connected() | bool | 检查是否已连接 |
loop() | Optional[StatusMessage] | 读取关节数据并更新缓存,失败返回None |
read_joint_positions() | Optional[StatusMessage] | 仅读取一帧,不经过loop() 的缓存语义 |
get_last_status() | Optional[StatusMessage] | 获取最后一次成功读取的数据 |
play_lra(modes, amplitudes, slave_id=1) | bool | LRA 线性马达控制(Modbus 写0xB0) |
4.2 读取主手数据的完整流程
#!/usr/bin/env python3
from psi_glove_sdk import PSIGloveController, SerialInterface, StatusMessage
import time
# 步骤 1: 创建串口接口
serial = SerialInterface(
port="/dev/ttyACM0",
baudrate=115200,
timeout=0.006,
)
# 步骤 2: 创建控制器
controller = PSIGloveController(
communication_interface=serial,
smoothing_window_size=10,
)
# 步骤 3: 连接设备
if not controller.connect():
print("错误: 无法连接到设备")
exit(1)
print("连接成功!")
# 步骤 4: 循环读取数据
try:
while True:
# 读取数据(自动 Modbus 解析 + 平滑)
status: StatusMessage = controller.loop()
if status:
print(f"拇指关节 (6): {status.thumb}")
print(f"食指关节 (4): {status.index}")
print(f"中指关节 (4): {status.middle}")
print(f"无名指关节 (4): {status.ring}")
print(f"小指关节 (4): {status.pinky}")
# 22 路关节扁平列表
all_joints = status.to_list()
print(f"所有关节 (22个): {all_joints}")
else:
last_status = controller.get_last_status()
if last_status:
print("读取失败,使用缓存数据")
time.sleep(0.01) # 约 100 Hz
except KeyboardInterrupt:
print("\n用户中断")
finally:
controller.disconnect()
print("已断开连接")4.3 数据格式和单位
StatusMessage 数据结构
Air Hand V2 提供 22 路关节 ADC。StatusMessage 以 joints 为主字段,并通过属性访问各指数据:
from dataclasses import dataclass
from typing import List
@dataclass
class StatusMessage:
joints: List[int] # 22 个关节 ADC,顺序见下表
@property
def thumb(self) -> List[int]: ... # joints[0:6],6 路
@property
def index(self) -> List[int]: ... # joints[6:10]
@property
def middle(self) -> List[int]: ... # joints[10:14]
@property
def ring(self) -> List[int]: ... # joints[14:18]
@property
def pinky(self) -> List[int]: ... # joints[18:22]
def to_list(self) -> List[int]:
"""返回 22 个关节值的列表"""
return list(self.joints)
def to_dict(self) -> dict:
"""返回含 joints 与各指属性的字典"""
...22 路 ADC 通道定义与关节映射
StatusMessage.joints 是 ADC 输入顺序;JointAngleCalculator.get_joint_angles() 返回的 angle[0:22] 是模型关节角顺序。实际转换会在每根手指内部反序,因此 ADC[0] 不写入 angle[0]。
下表直接按 SDK 的实际索引运算列出每路 ADC。tip、mid、back、side、rotate、back2 是配置中使用的通道语义名;“模型关节”是返回角度数组在随 SDK 提供的手套模型中的对应关节。
| ADC 通道 | StatusMessage 访问方式 | 手指 | 通道定义 | 关节/自由度 | 输出角度 | 模型关节 |
|---|---|---|---|---|---|---|
ADC[0] | status.thumb[0] | 拇指 | tip | 末端屈伸 | angle[5] | Thumb_Joint5 |
ADC[1] | status.thumb[1] | 拇指 | mid | 中部屈伸 | angle[4] | Thumb_Joint4 |
ADC[2] | status.thumb[2] | 拇指 | back | 根部屈伸 | angle[3] | Thumb_Joint3 |
ADC[3] | status.thumb[3] | 拇指 | side | 侧摆 | angle[2] | Thumb_Joint2 |
ADC[4] | status.thumb[4] | 拇指 | rotate | 旋转 | angle[1] | Thumb_Joint1 |
ADC[5] | status.thumb[5] | 拇指 | back2 | 第二根部自由度 | angle[0] | Thumb_Joint0 |
ADC[6] | status.index[0] | 食指 | tip | 远端屈伸 | angle[9] | Index_Joint3 |
ADC[7] | status.index[1] | 食指 | mid | 中部屈伸 | angle[8] | Index_Joint2 |
ADC[8] | status.index[2] | 食指 | back | 根部屈伸 | angle[7] | Index_Joint1 |
ADC[9] | status.index[3] | 食指 | side | 根部侧摆(内收/外展) | angle[6] | Index_Joint0 |
ADC[10] | status.middle[0] | 中指 | tip | 远端屈伸 | angle[13] | Middle_Joint3 |
ADC[11] | status.middle[1] | 中指 | mid | 中部屈伸 | angle[12] | Middle_Joint2 |
ADC[12] | status.middle[2] | 中指 | back | 根部屈伸 | angle[11] | Middle_Joint1 |
ADC[13] | status.middle[3] | 中指 | side | 根部侧摆(内收/外展) | angle[10] | Middle_Joint0 |
ADC[14] | status.ring[0] | 无名指 | tip | 远端屈伸 | angle[17] | Ring_Joint3 |
ADC[15] | status.ring[1] | 无名指 | mid | 中部屈伸 | angle[16] | Ring_Joint2 |
ADC[16] | status.ring[2] | 无名指 | back | 根部屈伸 | angle[15] | Ring_Joint1 |
ADC[17] | status.ring[3] | 无名指 | side | 根部侧摆(内收/外展) | angle[14] | Ring_Joint0 |
ADC[18] | status.pinky[0] | 小指 | tip | 远端屈伸 | angle[21] | Little_Joint3 |
ADC[19] | status.pinky[1] | 小指 | mid | 中部屈伸 | angle[20] | Little_Joint2 |
ADC[20] | status.pinky[2] | 小指 | back | 根部屈伸 | angle[19] | Little_Joint1 |
ADC[21] | status.pinky[3] | 小指 | side | 根部侧摆(内收/外展) | angle[18] | Little_Joint0 |
ADC 到角度的实际计算
ADC 转角度依赖每路传感器的标定上下限。默认使用的标定与关节映射文件为:
- 源码目录:
python_sdk/configs/master_slave_config URDF.yaml - 安装 wheel 后:
<site-packages>/psi_glove_sdk/resources/configs/master_slave_config URDF.yaml
也可以通过 JointAngleCalculator(config_path=...) 指定其他文件;相对路径按上述 configs 目录解析,绝对路径直接使用。update_calibration=True(默认值)时,计算器初始化会从手套读取 22 路硬件标定值,并更新该文件中的 master_hand.{left|right}_hand_limits;因此 ADC_min、ADC_max 是当前手套的逐通道标定参数,而不是固定的 0 和 4095。
对每个 ADC 通道 i,SDK 先从 master_hand.{left|right}_hand_limits 取得该通道的标定端点 ADC_min[i]、ADC_max[i],再从 synglove_air.{left|right}_hand_limits 取得目标关节角端点 angle_min[k]、angle_max[k]。计算过程等价于:
def adc_to_angle(adc, adc_min, adc_max, angle_min, angle_max):
adc_range = adc_max - adc_min
if abs(adc_range) < 1e-9:
normalized = 0.0
else:
normalized = (adc - adc_min) / adc_range
return angle_min + normalized * (angle_max - angle_min)即:
normalized[i] = (ADC[i] - ADC_min[i]) / (ADC_max[i] - ADC_min[i])
angle[k] = angle_min[k] + normalized[i] × (angle_max[k] - angle_min[k])输入通道 i 与输出角度 k 的关系为:
拇指: ADC[0..5] -> angle[5..0]
食指: ADC[6..9] -> angle[9..6]
中指: ADC[10..13] -> angle[13..10]
无名指:ADC[14..17] -> angle[17..14]
小指: ADC[18..21] -> angle[21..18]目标角度端点如下。箭头左侧对应 ADC_min,右侧对应 ADC_max;单位均为 rad。
| 输出角度/模型关节 | ADC 来源 | 左手目标端点(rad) | 右手目标端点(rad) |
|---|---|---|---|
angle[0] / Thumb_Joint0 | ADC[5] (back2) | 1.05 -> 0.00 | 1.05 -> 0.00 |
angle[1] / Thumb_Joint1 | ADC[4] (rotate) | 0.26 -> -0.26 | 0.26 -> -0.26 |
angle[2] / Thumb_Joint2 | ADC[3] (side) | 1.57 -> 2.09 | 1.57 -> 2.09 |
angle[3] / Thumb_Joint3 | ADC[2] (back) | 0.52 -> 1.05 | 0.52 -> 1.05 |
angle[4] / Thumb_Joint4 | ADC[1] (mid) | -1.57 -> -1.05 | -1.57 -> -1.05 |
angle[5] / Thumb_Joint5 | ADC[0] (tip) | 0.00 -> 1.57 | 0.00 -> 1.57 |
angle[6] / Index_Joint0 | ADC[9] (side) | -0.17 -> 0.17 | 0.17 -> -0.17 |
angle[7] / Index_Joint1 | ADC[8] (back) | 0.35 -> 3.55 | 0.35 -> 3.55 |
angle[8] / Index_Joint2 | ADC[7] (mid) | -0.97 -> -1.49 | -0.97 -> -1.49 |
angle[9] / Index_Joint3 | ADC[6] (tip) | 0.00 -> 1.57 | 0.00 -> 1.57 |
angle[10] / Middle_Joint0 | ADC[13] (side) | -0.17 -> 0.17 | -0.17 -> 0.17 |
angle[11] / Middle_Joint1 | ADC[12] (back) | 0.35 -> 3.55 | 0.35 -> 3.55 |
angle[12] / Middle_Joint2 | ADC[11] (mid) | -0.97 -> -1.49 | -0.97 -> -1.49 |
angle[13] / Middle_Joint3 | ADC[10] (tip) | 0.00 -> 1.57 | 0.00 -> 1.57 |
angle[14] / Ring_Joint0 | ADC[17] (side) | -0.17 -> 0.17 | -0.17 -> 0.17 |
angle[15] / Ring_Joint1 | ADC[16] (back) | 0.35 -> 3.55 | 0.35 -> 3.55 |
angle[16] / Ring_Joint2 | ADC[15] (mid) | -0.97 -> -1.49 | -0.97 -> -1.49 |
angle[17] / Ring_Joint3 | ADC[14] (tip) | 0.00 -> 1.57 | 0.00 -> 1.57 |
angle[18] / Little_Joint0 | ADC[21] (side) | -0.17 -> 0.17 | -0.17 -> 0.17 |
angle[19] / Little_Joint1 | ADC[20] (back) | 0.55 -> 3.45 | 0.55 -> 3.45 |
angle[20] / Little_Joint2 | ADC[19] (mid) | -1.57 -> -1.05 | -1.57 -> -1.05 |
angle[21] / Little_Joint3 | ADC[18] (tip) | 0.00 -> 1.57 | 0.00 -> 1.57 |
计算边界
当前 Python 实现不会把 normalized 截断到 [0, 1]。当 ADC 超出标定端点时,角度会继续线性外推;当 ADC_min == ADC_max 时,直接返回对应的 angle_min。ADC_min 也可以大于 ADC_max,公式仍按实际端点方向计算。
4.4 高级用法示例
4.4.1 批量数据采集
import time
import csv
from psi_glove_sdk import PSIGloveController, SerialInterface
serial = SerialInterface("/dev/ttyACM0", 115200)
controller = PSIGloveController(serial, smoothing_window_size=10)
controller.connect()
data_buffer = []
for _ in range(1000):
status = controller.loop()
if status:
data_buffer.append(status.to_list()) # 每行 22 个关节
time.sleep(0.01)
with open("glove_data.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow([f"joint_{i}" for i in range(22)])
writer.writerows(data_buffer)
print(f"已保存 {len(data_buffer)} 帧到 glove_data.csv")
controller.disconnect()也可使用官方 advanced_usage.py,第三个参数传入 --log 自动生成带时间戳的 CSV。
4.4.2 多手套同时使用
每只手套使用独立 SerialInterface 与 PSIGloveController:
from psi_glove_sdk import PSIGloveController, SerialInterface
import time
left_serial = SerialInterface("/dev/ttyACM0", 115200)
left_controller = PSIGloveController(left_serial)
left_controller.connect()
right_serial = SerialInterface("/dev/ttyACM1", 115200)
right_controller = PSIGloveController(right_serial)
right_controller.connect()
try:
while True:
left_status = left_controller.loop()
right_status = right_controller.loop()
if left_status and right_status:
print(
f"左手拇指尖: {left_status.thumb[0]}, "
f"右手拇指尖: {right_status.thumb[0]}"
)
time.sleep(0.01)
finally:
left_controller.disconnect()
right_controller.disconnect()4.4.3 获取指尖位姿
通过 FingertipPoseCalculator 将 22 路 ADC 映射为 URDF 关节角,再用 MuJoCo 正运动学得到五指指尖 4×4 齐次位姿(形状 (5, 4, 4),顺序:thumb → index → middle → ring → little)。
依赖:
pip install mujoco numpy ruamel.yaml
# 或:pip install -e ".[fingertip]"| 方法 | 说明 |
|---|---|
connect() / disconnect() | 连接/断开内部PSIGloveController |
get_fingertip_poses(joint_data_22) | 由 22 路 ADC 仅做 FK,返回(5, 4, 4) |
update_and_get_poses() | 读一帧 ADC 并 FK,返回(poses, urdf_angles, adc) 或全 None |
指尖参考系末端偏移
同一份 master_slave_config URDF.yaml 还在 synglove_air.fingertip_frame_offsets.{left|right}.{finger} 中记录了每根手指从末端 Link 到指尖参考系的单独偏移。FingertipPoseCalculator 先用 fingertip_links 找到末端 body/site 的世界位姿,再应用该偏移:
T_world_tip = T_world_link @ T_link_tippos:在对应末端 Link 局部坐标系中的平移[x, y, z],单位为 m。rpy:构造T_link_tip的XYZ欧拉角[roll, pitch, yaw],单位为 rad。- 某根手指未配置偏移时,
T_link_tip使用单位矩阵。 - 该偏移只修正
poses中返回的指尖参考系位姿,不修改 ADC→angle[0:22]的关节角映射。
当前配置值如下:
| 手 | 手指 | fingertip_links 末端 Link | pos(m) | rpy(rad,XYZ) |
|---|---|---|---|---|
| 左手 | 拇指 | Thumb_Link5 | [0.014392, 0.0, 0.0] | [-1.5708, 0.0, -1.1974] |
| 左手 | 食指 | Index_Link3 | [0.014392, 0.0, 0.0] | [1.5708, 0.0, 1.1974] |
| 左手 | 中指 | Middle_Link3 | [0.014392, 0.0, 0.0] | [-1.5708, 0.0, -1.1974] |
| 左手 | 无名指 | Ring_Link3 | [0.014392, 0.0, 0.0] | [-1.5708, 0.0, -1.1974] |
| 左手 | 小指 | Little_Link3 | [0.014392, 0.0, 0.0] | [-1.5708, 0.0, -1.1974] |
| 右手 | 拇指 | Thumb_Link5 | [0.014392, 0.0, 0.0] | [1.5708, 0.0, 1.9442] |
| 右手 | 食指 | Index_Link3 | [0.014392, 0.0, 0.0] | [-1.5708, 0.0, -1.1974] |
| 右手 | 中指 | Middle_Link3 | [0.014392, 0.0, 0.0] | [-1.5708, 0.0, -1.1974] |
| 右手 | 无名指 | Ring_Link3 | [0.014392, 0.0, 0.0] | [-1.5708, 0.0, -1.1974] |
| 右手 | 小指 | Little_Link3 | [0.014392, 0.0, 0.0] | [-1.5708, 0.0, -1.1974] |
单手:实时读取
import time
from psi_glove_sdk import FingertipPoseCalculator
calc = FingertipPoseCalculator(port="/dev/ttyACM0", baudrate=115200, hand="right")
if not calc.connect():
raise RuntimeError("连接失败")
try:
while True:
poses, urdf_angles, adc = calc.update_and_get_poses()
if poses is not None:
print(f"拇指尖 xyz (m): {poses[0][:3, 3]}")
time.sleep(0.01)
finally:
calc.disconnect()双手:同步获取指尖位姿
每只手一个串口、一个 FingertipPoseCalculator(hand="left" / "right"):
import time
from psi_glove_sdk import FingertipPoseCalculator
left_calc = FingertipPoseCalculator(
port="/dev/ttyACM0", baudrate=115200, hand="left",
)
right_calc = FingertipPoseCalculator(
port="/dev/ttyACM1", baudrate=115200, hand="right",
)
if not left_calc.connect() or not right_calc.connect():
raise RuntimeError("双手连接失败")
try:
while True:
left_poses, _, _ = left_calc.update_and_get_poses()
right_poses, _, _ = right_calc.update_and_get_poses()
if left_poses is not None and right_poses is not None:
print("左拇指尖:", left_poses[0][:3, 3])
print("右拇指尖:", right_poses[0][:3, 3])
time.sleep(0.01)
except KeyboardInterrupt:
pass
finally:
left_calc.disconnect()
right_calc.disconnect()说明: 构造时会尝试从设备读校准并更新 master_slave_config URDF.yaml。可视化见 test_fingertip_visualizer.py 与 例程 3.2.4。
4.5 LRA 线性马达 play_lra
通过 Modbus 功能码 16 写寄存器 0xB0,载荷 10 字节:[m0,a0, m1,a1, …, m4,a4](拇指→小指,均为 uint8)。
from psi_glove_sdk import REG_LRA_CTRL, LRA_AMPLITUDE_MAX
modes = [5, 0, 0, 0, 0] # 拇指波形 5,其余指关闭
amps = [32, 0, 0, 0, 0] # 振幅 0–255
ok = controller.play_lra(modes, amps, slave_id=0x01)| 参数 | 范围 | 说明 |
|---|---|---|
finger_modes | 长度 5,每元素 0–10 | 0 关闭该指;1–10 波形编号 |
finger_amplitudes | 长度 5,每元素 0–255 | 播放增益;0 无驱动 |
slave_id | 1–247 | Modbus 从机地址,默认1 |
停止全部 LRA:
controller.play_lra([0, 0, 0, 0, 0], [0, 0, 0, 0, 0])TIP
mode=1 表示「波形 1」,不是「振幅 1」。低强度请减小 amplitude(如 1–64)。
4.6 配置与 URDF 路径
from psi_glove_sdk import (
load_config,
save_config,
get_default_config_path,
get_configs_dir,
get_synglove_urdf_root,
get_resources_root,
)
cfg = load_config()
urdf_root = get_synglove_urdf_root()- 开发安装:配置在
python_sdk/configs/,URDF 在仓库SynGlove_Air_URDF/。 - 离线 wheel:资源在
psi_glove_sdk/resources/内。
4.7 FingertipPoseCalculator
将手套 ADC 映射为 URDF/MuJoCo 指尖位姿,需额外安装 mujoco、numpy、ruamel.yaml:
from psi_glove_sdk import FingertipPoseCalculator
calc = FingertipPoseCalculator(
port="/dev/ttyACM0",
baudrate=115200,
hand="right",
)详见 test_fingertip_visualizer.py 与 例程 3.2.4。
4.8 Modbus 协议摘要
| 功能 | 功能码 | 地址/说明 |
|---|---|---|
| 读关节 | 0x03 | 读 22 寄存器 →StatusMessage(RequestType.READ_JOINT_POSITION) |
| LRA 播放 | 0x10 | 写0xB0,10 字节 [m0,a0,…,m4,a4] |
通信格式:Modbus RTU,帧尾 CRC16。
