Skip to content

PSI 手套 Python SDK 使用手册

ψ-SynGlove Air Python SDK 完整文档。

📋 目录

  1. 硬件安装方法说明
  2. SDK安装和配置方法
  3. 例程编译和调用方法
  4. SDK接口调用说明
  5. SDK打印内容说明
  6. 连接状态查询与故障排查

1. 硬件安装方法说明

本文适用于 ψ-SynGlove Air(Air Hand V2):22 路关节 ADC 采集,五指可选配 LRA 线性马达 触觉反馈。通信协议与 SDK Modbus 章节一致(读关节、写 0xB0 LRA)。

SDK 源码仓库路径:psi-air-glove-SDK/psi-air-glove-SDK/(含 python_sdkcpp_sdkros2_sdk)。

1.1 硬件清单

  • PSI 数据手套(左手/右手)
  • USB 数据线

1.2 硬件连接步骤

┌────────────────┐                        ┌─────────────┐
│  PSI 数据手套  │ ──────── USB ───────→ │   计算机    │
│  (左手/右手)   │   (虚拟串口通信)       │  (运行SDK)  │
└────────────────┘                        └─────────────┘

连接步骤:

  1. 连接手套:使用 USB 数据线将数据手套直接连接到计算机 USB 接口
  2. 手套供电:手套通过 USB 接口供电
  3. 虚拟串口:计算机会自动创建虚拟串口设备用于通信
  4. 确认设备
    • Linux: 运行 ls /dev/ttyUSB* /dev/ttyACM* 查看设备(通常为 /dev/ttyACM0
    • Windows: 打开设备管理器查看 COM 口(如 COM31)
    • macOS: 运行 ls /dev/tty.usbmodem*ls /dev/cu.usbmodem*

注意事项:

  • 手套使用 USB 供电,无需额外电源适配器
  • 首次连接时,操作系统会自动安装 USB CDC 驱动

1.3 串口权限设置(Linux)

bash
# 将当前用户添加到 dialout 组
sudo usermod -a -G dialout $USER

# 注销后重新登录,或使用以下命令立即生效
newgrp dialout

# 验证权限
ls -l /dev/ttyACM0

2. SDK安装和配置方法

本文档对应 ψ-SynGlove Air Python SDK,源码位于:

psi-air-glove-SDK/psi-air-glove-SDK/python_sdk/

安装后通过 import psi_glove_sdk 使用,当前版本 0.1.5(以 psi_glove_sdk/VERSION 为准)。

2.1 系统要求

项目要求
Python3.7+(离线 wheel 推荐 3.10)
操作系统Linux / Windows / macOS
基础依赖pyserial >= 3.5
可选依赖numpy(平滑加速)、mujoco / ruamel.yaml(指尖 FK 与可视化)

2.2 从源码安装

bash
# 进入 Python SDK 目录
cd psi-air-glove-SDK/psi-air-glove-SDK/python_sdk

# 可编辑安装(自动安装 requirements.txt 中的依赖)
pip install -e .

# 验证
python -c "import psi_glove_sdk; print('OK', psi_glove_sdk.__version__)"

开发态资源路径(不在 psi_glove_sdk/resources/ 源码副本中):

  • 默认配置:python_sdk/configs/
  • URDF 模型:仓库内 SynGlove_Air_URDF/(与 python_sdk 同级)

2.3 离线Wheel包安装

适用于目标机无法访问 PyPI、需一次性打包配置与 URDF 的场景。

bash
cd psi-air-glove-SDK/psi-air-glove-SDK/python_sdk

# 默认按本机架构生成 manylinux 标签(x86_64 / aarch64)
python scripts/package_offline_wheel.py

# 指定 Python 标签与平台(示例)
python scripts/package_offline_wheel.py --python-tag cp310 --plat-name manylinux_2_17_x86_64

# 安装
pip install ./build/dist/psi_air_glove-*.whl

说明:

  • Wheel 包名:psi-air-glove(import 名仍为 psi_glove_sdk)。
  • 构建时会把 configs/SynGlove_Air_URDF/ 打入 wheel 内的 psi_glove_sdk/resources/
  • 平台标签须与目标 Linux 匹配,否则 pip install 会拒绝安装。

2.3.1 使用已有离线包安装

若已拿到 psi_air_glove-<version>-cp<py>-none-linux_<arch>.whl(例如 psi_air_glove-0.1.5-cp310-none-linux_x86_64.whl),无需在目标机上重新执行 package_offline_wheel.py,按下列步骤安装即可。

1. 确认环境匹配

检查项说明
Python 版本文件名中的 cp310 表示需 Python 3.10cp311 对应 3.11,以此类推
操作系统当前 wheel 为 Linux x86_64 / aarch64 manylinux 标签,不支持 Windows / macOS 直接安装
架构linux_x86_64aarch64 不可混用,须与目标机 uname -m 一致

2. 安装

bash
# 进入 wheel 所在目录(路径按实际修改)
cd /path/to/dist

# 建议在虚拟环境中安装(可选)
python3 -m venv .venv
source .venv/bin/activate

# 安装离线包(将文件名换成你手里的 wheel)
python3 -m pip install ./psi_air_glove-0.1.5-cp310-none-linux_x86_64.whl

# 若曾装过旧版,可强制覆盖
# python3 -m pip install --force-reinstall ./psi_air_glove-0.1.5-cp310-none-linux_x86_64.whl

3. 验证安装

bash
python3 -c "import psi_glove_sdk; print('OK', psi_glove_sdk.__version__)"

python3 -c "
from psi_glove_sdk.config_loader import get_resources_root, get_synglove_urdf_root
print('resources:', get_resources_root())
print('urdf_root:', get_synglove_urdf_root())
"

get_resources_root()Noneurdf_root 下存在 SynGlove_Air_URDF/,说明配置与 URDF 已随 wheel 安装完整。

4. 指尖位姿 / MuJoCo 可视化(可选)

离线 wheel 仅包含 pyserial 与包内 resources;运行 test_fingertip_visualizer.pyFingertipPoseCalculator 还需额外安装:

bash
pip install mujoco numpy ruamel.yaml

安装完成后即可运行 examples/ 下示例(见 §3 例程)。

2.4 可配置参数

SerialInterface

参数类型默认说明
portstr必填串口路径,如/dev/ttyACM0COM3
baudrateint115200波特率,常用115200500000921600
timeoutfloat0.01读超时(秒)
auto_connectboolFalse构造时是否自动连接
mockboolFalse模拟模式(无硬件测试)

LRA 连续下发时,写超时默认放宽为 max(1s, 读超时×10),减轻 Write timeout

PSIGloveController

参数类型默认说明
communication_interfaceCommunicationInterface必填SerialInterface 实例
smoothing_window_sizeint10移动平均窗口;越大越平滑、延迟越高
adc_calibrationADCCalibrationParams内置默认可选 ADC 三次多项式校准

2.5 典型配置示例

python
from psi_glove_sdk import PSIGloveController, SerialInterface

serial = SerialInterface(
    port="/dev/ttyACM0",
    baudrate=115200,
    timeout=0.006,
)
controller = PSIGloveController(serial, smoothing_window_size=10)

if not controller.connect():
    raise RuntimeError("连接失败")

3. 例程编译和调用方法

SDK 提供四个示例程序,位于 psi-air-glove-SDK/psi-air-glove-SDK/python_sdk/examples/ 目录。

3.1 例程列表

例程名称文件功能说明
基础用法basic_usage.py演示连接、读取 22 路关节数据、断开连接
高级用法advanced_usage.py归一化与角度换算、统计信息、CSV 数据记录
LRA触觉反馈lra_play_example.py线性马达波形与振幅控制(Modbus0xB0
指尖可视化test_fingertip_visualizer.pyMuJoCo 实时显示指尖位姿与坐标系

3.2 例程运行方法

运行前建议已在 python_sdk 目录执行 pip install -e .

3.2.1 基础用法示例

bash
cd psi-air-glove-SDK/psi-air-glove-SDK/python_sdk/examples

# 运行基础示例(指定串口)
python3 basic_usage.py /dev/ttyACM0

# 自定义波特率
python3 basic_usage.py /dev/ttyACM0 500000

# Windows 系统
python basic_usage.py COM31

输出示例:

==================================================
PSI Glove SDK - 基础使用示例
==================================================
串口: /dev/ttyACM0
波特率: 115200
==================================================

[INFO] 正在连接到设备...
[INFO] 连接成功!

[INFO] 开始读取数据 (按Ctrl+C退出)...

--- 帧 #0 ---
拇指: 2048, 1856, 2304, 1792, 2176, 1920
食指: 1920, 2240, 1664, 2112
中指: 2304, 1856, 2048, 1792
无名指: 1920, 2176, 1664, 2048
小指: 2240, 1856, 2304, 1920

--- 帧 #10 ---
拇指: 2052, 1860, 2308, 1796, 2180, 1924
...

拇指为 6 路关节,其余四指各 4 路;数值为 12 位 ADC(0–4095)。按 Ctrl+C 退出后打印帧率统计。

3.2.2 高级用法示例

bash
# 运行高级示例
python3 advanced_usage.py /dev/ttyACM0

# 自定义波特率
python3 advanced_usage.py /dev/ttyACM0 500000

# 启用 CSV 数据记录(第三个参数为 --log)
python3 advanced_usage.py /dev/ttyACM0 115200 --log

输出示例:

==================================================
PSI Glove SDK - 高级使用示例
==================================================
串口: /dev/ttyACM0
波特率: 115200
数据记录: 启用
==================================================

[INFO] 正在连接...
[INFO] 连接成功!

--- 帧 #0 ---
拇指关节角度:
  关节 0:   45.2° (归一化: 0.512, 原始: 2048)
  关节 1:   38.1° (归一化: 0.431, 原始: 1856)
  ...

==================================================
统计信息 (拇指指尖关节):
  样本数: 1200 | 最小: 1800 | 最大: 2300 | 平均: 2050.3
==================================================

启用 --log 时,会在当前目录生成 psi_glove_data_YYYYMMDD_HHMMSS.csv

3.2.3 LRA 触觉示例

bash
python3 lra_play_example.py COM3 115200
python3 lra_play_example.py /dev/ttyACM0 921600

可选参数:--slave(Modbus 从机地址)、--step-sec(每档保持时间)、--amp-max(振幅上限,默认 64)。

示例逻辑:拇指出厂波形,振幅从 1 递增到 amp_maxmode=0 关闭该指马达。

3.2.4 指尖可视化示例

依赖:mujoconumpyruamel.yaml,以及 URDF(开发态在 SynGlove_Air_URDF/,离线 wheel 在 psi_glove_sdk/resources/)。

bash
pip install mujoco numpy ruamel.yaml

python3 test_fingertip_visualizer.py -p /dev/ttyACM0 -b 115200 -H right

启动 MuJoCo 窗口,实时显示五指指尖坐标系;终端周期性打印 URDF 关节角与指尖位姿矩阵。

效果展示:

指尖可视化示例运行效果

3.3 关节数据说明(22 路)

StatusMessage 包含 22 个关节 ADC 值(12 位,0–4095):

手指关节数joints 索引属性访问
拇指60–5status.thumb
食指46–9status.index
中指410–13status.middle
无名指414–17status.ring
小指418–21status.pinky

拇指 6 路顺序:tip, mid, back, back2, side, rotate。其余四指:tip, mid, back, side


4. SDK接口调用说明

4.1 核心类和方法

4.1.1 SerialInterface 类

串口通信接口类,处理底层 Modbus RTU 硬件通信。

python
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)
)
参数类型默认说明
portstr必填串口设备路径
baudrateint115200通信波特率
timeoutfloat0.01读超时(秒)
auto_connectboolFalse构造时是否自动connect()
mockboolFalse模拟模式
write_timeoutfloat| NoneNone写超时;LRA 连续下发时建议保持默认

4.1.2 PSIGloveController 类

主控制器类,管理连接、Modbus 读关节、移动平均平滑、LRA 触觉下发。

python
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)boolLRA 线性马达控制(Modbus 写0xB0

4.2 读取主手数据的完整流程

python
#!/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。StatusMessagejoints 为主字段,并通过属性访问各指数据:

python
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。tipmidbacksiderotateback2 是配置中使用的通道语义名;“模型关节”是返回角度数组在随 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_minADC_max 是当前手套的逐通道标定参数,而不是固定的 04095

对每个 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]。计算过程等价于:

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

即:

text
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 的关系为:

text
拇指:  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_Joint0ADC[5] (back2)1.05 -> 0.001.05 -> 0.00
angle[1] / Thumb_Joint1ADC[4] (rotate)0.26 -> -0.260.26 -> -0.26
angle[2] / Thumb_Joint2ADC[3] (side)1.57 -> 2.091.57 -> 2.09
angle[3] / Thumb_Joint3ADC[2] (back)0.52 -> 1.050.52 -> 1.05
angle[4] / Thumb_Joint4ADC[1] (mid)-1.57 -> -1.05-1.57 -> -1.05
angle[5] / Thumb_Joint5ADC[0] (tip)0.00 -> 1.570.00 -> 1.57
angle[6] / Index_Joint0ADC[9] (side)-0.17 -> 0.170.17 -> -0.17
angle[7] / Index_Joint1ADC[8] (back)0.35 -> 3.550.35 -> 3.55
angle[8] / Index_Joint2ADC[7] (mid)-0.97 -> -1.49-0.97 -> -1.49
angle[9] / Index_Joint3ADC[6] (tip)0.00 -> 1.570.00 -> 1.57
angle[10] / Middle_Joint0ADC[13] (side)-0.17 -> 0.17-0.17 -> 0.17
angle[11] / Middle_Joint1ADC[12] (back)0.35 -> 3.550.35 -> 3.55
angle[12] / Middle_Joint2ADC[11] (mid)-0.97 -> -1.49-0.97 -> -1.49
angle[13] / Middle_Joint3ADC[10] (tip)0.00 -> 1.570.00 -> 1.57
angle[14] / Ring_Joint0ADC[17] (side)-0.17 -> 0.17-0.17 -> 0.17
angle[15] / Ring_Joint1ADC[16] (back)0.35 -> 3.550.35 -> 3.55
angle[16] / Ring_Joint2ADC[15] (mid)-0.97 -> -1.49-0.97 -> -1.49
angle[17] / Ring_Joint3ADC[14] (tip)0.00 -> 1.570.00 -> 1.57
angle[18] / Little_Joint0ADC[21] (side)-0.17 -> 0.17-0.17 -> 0.17
angle[19] / Little_Joint1ADC[20] (back)0.55 -> 3.450.55 -> 3.45
angle[20] / Little_Joint2ADC[19] (mid)-1.57 -> -1.05-1.57 -> -1.05
angle[21] / Little_Joint3ADC[18] (tip)0.00 -> 1.570.00 -> 1.57

计算边界

当前 Python 实现不会把 normalized 截断到 [0, 1]。当 ADC 超出标定端点时,角度会继续线性外推;当 ADC_min == ADC_max 时,直接返回对应的 angle_minADC_min 也可以大于 ADC_max,公式仍按实际端点方向计算。

4.4 高级用法示例

4.4.1 批量数据采集

python
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 多手套同时使用

每只手套使用独立 SerialInterfacePSIGloveController

python
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 获取指尖位姿

通过 FingertipPoseCalculator22 路 ADC 映射为 URDF 关节角,再用 MuJoCo 正运动学得到五指指尖 4×4 齐次位姿(形状 (5, 4, 4),顺序:thumb → index → middle → ring → little)。

依赖:

bash
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 的世界位姿,再应用该偏移:

text
T_world_tip = T_world_link @ T_link_tip
  • pos:在对应末端 Link 局部坐标系中的平移 [x, y, z],单位为 m
  • rpy:构造 T_link_tipXYZ 欧拉角 [roll, pitch, yaw],单位为 rad
  • 某根手指未配置偏移时,T_link_tip 使用单位矩阵。
  • 该偏移只修正 poses 中返回的指尖参考系位姿,不修改 ADC→angle[0:22] 的关节角映射。

当前配置值如下:

手指fingertip_links 末端 Linkpos(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]

单手:实时读取

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

双手:同步获取指尖位姿

每只手一个串口、一个 FingertipPoseCalculatorhand="left" / "right"):

python
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)。

python
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–100 关闭该指;1–10 波形编号
finger_amplitudes长度 5,每元素 0–255播放增益;0 无驱动
slave_id1–247Modbus 从机地址,默认1

停止全部 LRA:

python
controller.play_lra([0, 0, 0, 0, 0], [0, 0, 0, 0, 0])

TIP

mode=1 表示「波形 1」,不是「振幅 1」。低强度请减小 amplitude(如 1–64)。

4.6 配置与 URDF 路径

python
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 指尖位姿,需额外安装 mujoconumpyruamel.yaml

python
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 寄存器 →StatusMessageRequestType.READ_JOINT_POSITION
LRA 播放0x100xB0,10 字节 [m0,a0,…,m4,a4]

通信格式:Modbus RTU,帧尾 CRC16。


5. SDK打印内容说明

本章说明运行官方示例时的典型终端输出,便于对照调试。

5.1 basic_usage.py

连接成功后周期性打印五指 ADC,例如:

==================================================
PSI Glove SDK - 基础使用示例
==================================================
串口: /dev/ttyACM0
波特率: 115200
==================================================

[INFO] 正在连接到设备...
[INFO] 连接成功!

--- 帧 #0 ---
拇指: 2048, 1856, 2304, 1792, 2176, 1920
食指: 1920, 2240, 1664, 2112
...
  • 拇指 6 个整数,其余四指各 4 个整数。
  • 数值范围约 0–4095(12 位 ADC,经 SDK 平滑与可选校准)。

5.2 advanced_usage.py

每 50 帧打印拇指各关节的角度、归一化值与原始 ADC;结束时输出拇指指尖统计信息。使用 --log 时生成 psi_glove_data_*.csv

5.3 lra_play_example.py

正常流程:

[INFO] 连接成功
[INFO] play_lra(thumb wave=1 amp=32) -> True

play_lra 返回 False 时检查:串口占用、固件是否支持 LRA、参数长度是否为 5。

5.4 test_fingertip_visualizer.py

启动 MuJoCo 窗口后,终端可能打印指尖坐标或关节映射日志;无手套连接时会报串口错误。

指尖可视化示例运行效果

需保证 URDF 路径正确:开发安装使用仓库 SynGlove_Air_URDF/;离线 wheel 使用包内 resources。

5.5 日志级别

示例均使用 logging

python
import logging
logging.basicConfig(level=logging.INFO)

调试通信可将级别设为 DEBUG(若 SDK 模块已配置相应 handler)。


6. 连接状态查询与故障排查

6.1 连接状态

python
if controller.is_connected():
    status = controller.loop()
else:
    print("未连接,请调用 connect()")

断开时调用 controller.disconnect(),并释放串口供其他程序使用。

6.2 串口与权限

现象处理
LinuxPermission deniedsudo usermod -aG dialout $USER 后重新登录
找不到设备ls /dev/ttyACM* / Windows 设备管理器查看 COM 口
Device or resource busy关闭 AirHandViewer、旧示例或其它占用串口的进程

6.3 Modbus 读数失败

  • 确认波特率与固件一致(常用 115200500000)。
  • 适当增大 SerialInterface.timeout(如 0.01)。
  • loop() 返回 None 时可用 get_last_status() 取上一帧有效数据。

6.4 LRA 相关问题

现象可能原因建议
play_lra 返回 True 但无振动固件 LRA 任务未就绪或消息队列未满刷写含lra_message_buffer_preinit 的 V2 固件
连接后第一次 LRA 无效早期固件 MessageBuffer 创建过晚同上,升级固件
停止后仍有微弱振动mode=0 未清零增益升级固件(PLAY_GAIN 归零修复)
Write timeoutMCU 忙于 I2C降低下发频率;使用 SDK 默认写超时
强度“很大”误将mode 当振幅mode 选波形,amplitude 用 1–64 试探

停止所有指马达:

python
ctrl.play_lra([0, 0, 0, 0, 0], [0, 0, 0, 0, 0])

6.5 离线 Wheel 安装失败

  • 错误 is not a supported wheel on this platform:在目标架构上重新执行 package_offline_wheel.py,或指定正确的 --plat-name
  • 缺少 URDF / 配置:确认安装的是离线构建产物,而非纯 pip install -e . 的开发树。

6.6 导入错误

ImportError: cannot import name 'play_lra'

说明加载了旧版已安装的 psi_glove_sdk。处理:

bash
pip uninstall psi-glove psi-air-glove psi_glove_sdk  # 视已安装包名而定
cd psi-air-glove-SDK/psi-air-glove-SDK/python_sdk && pip install -e .