安装
从 PyPI 安装:
# pip
pip install linkerbot-py
# uv
uv add linkerbot-py
从 Git 仓库安装:
# pip
pip install git+https://github.com/linker-bot/linkerbot-python-sdk.git
# uv
uv add "linkerbot-py @ git+https://github.com/linker-bot/linkerbot-python-sdk"
可选依赖
Kinetix(运动学,机械臂必需)
机械臂(A7 / A7 Lite)依赖 Pinocchio 进行运动学计算,需要额外安装:
# pip (Linux / macOS)
pip install linkerbot-py[kinetix]
# uv
uv add linkerbot-py --extra kinetix
Windows 用户注意:Pinocchio 不支持在 Windows 上通过 pip 安装。请使用 Conda:
conda install pinocchio -c conda-forge
如果只使用灵巧手(L6 / L20 Lite / L25 等),无需安装此依赖。
快速开始
连接灵巧手
from linkerbot import L6
with L6(side="left", interface_name="can0") as hand:
# 控制和读取灵巧手
pass
使用 with 语句确保资源正确释放。
控制关节角度
设置 6 个关节的目标角度(范围 0-100):
from linkerbot import L6
with L6(side="left", interface_name="can0") as hand:
# 张开手掌
hand.angle.set_angles([100, 50, 100, 100, 100, 100])
# 握拳
hand.angle.set_angles([0, 0, 0, 0, 0, 0])
关节顺序:[拇指弯曲, 拇指侧摆, 食指, 中指, 无名指, 小指]
读取角度
with L6(side="left", interface_name="can0") as hand:
# 阻塞读取
data = hand.angle.get_blocking(timeout_ms=100)
print(f"当前角度:{data.angles.to_list()}")
# 访问单个关节
print(f"食指:{data.angles.index}")
读取力传感器
获取 5 个手指的力传感器数据:
with L6(side="left", interface_name="can0") as hand:
data = hand.force_sensor.get_blocking(timeout_ms=1000)
# 访问各手指数据
print(f"拇指:{data.thumb.values.shape}") # (12, 6)
print(f"食指:{data.index.values.shape}")
流式读取
通过统一事件流持续接收数据。初始化时会自动以默认间隔启动轮询(角度 60 Hz、力传感器 30 Hz),也可手动调用 start_polling() 覆盖:
from linkerbot.hand.l6 import SensorSource, AngleEvent
with L6(side="left", interface_name="can0") as hand:
# 初始化已自动启动默认轮询(角度 + 力传感器)
# 如需自定义,可再次调用 start_polling() 覆盖
hand.start_polling({SensorSource.ANGLE: 0.1})
for event in hand.stream():
match event:
case AngleEvent(data=data):
print(f"角度:{data.angles.to_list()}")
if should_stop():
break
hand.stop_polling()
hand.stop_stream()
完整示例
from linkerbot import L6
import time
with L6(side="left", interface_name="can0") as hand:
# 设置速度
hand.speed.set_speeds([50, 50, 50, 50, 50, 50])
# 张开
hand.angle.set_angles([100, 50, 100, 100, 100, 100])
time.sleep(1)
# 握拳
hand.angle.set_angles([0, 0, 0, 0, 0, 0])
time.sleep(1)
# 读取状态
angles = hand.angle.get_blocking()
temps = hand.temperature.get_blocking()
print(f"角度:{angles.angles.to_list()}")
print(f"温度:{temps.temperatures.to_list()} °C")
下一步
基础知识
架构概览
SDK 采用模块化设计,通过主类访问各功能模块:
L6
├── hand.angle # 角度控制
├── hand.speed # 速度控制
├── hand.torque # 扭矩控制
├── hand.current # 电流读取
├── hand.temperature # 温度读取
├── hand.force_sensor # 力传感器
├── hand.fault # 故障管理
└── hand.version # 版本信息
调用方式
所有功能通过 hand.模块.方法() 调用:
from linkerbot import L6
with L6(side="left", interface_name="can0") as hand:
hand.angle.set_angles([50, 50, 50, 50, 50, 50])
hand.speed.set_speeds([100, 100, 100, 100, 100, 100])
data = hand.temperature.get_blocking()
类型安全
SDK 提供类型化的数据类,支持 IDE 自动补全:
from linkerbot import L6
from linkerbot.hand.l6 import L6Angle
with L6(side="left", interface_name="can0") as hand:
# 使用数据类(推荐,有类型提示)
angle = L6Angle(thumb_flex=50, thumb_abd=30, index=60, middle=60, ring=60, pinky=60)
hand.angle.set_angles(angle)
# 或使用列表(简洁)
hand.angle.set_angles([50, 30, 60, 60, 60, 60])
数据类属性:
| 属性 | 说明 |
|---|---|
thumb_flex | 拇指弯曲 |
thumb_abd | 拇指侧摆 |
index | 食指 |
middle | 中指 |
ring | 无名指 |
pinky | 小指 |
数据读取模式
阻塞读取
发送请求并等待响应:
data = hand.angle.get_blocking(timeout_ms=100)
缓存读取
获取最近一次接收的数据(非阻塞):
data = hand.angle.get_snapshot()
if data is not None:
print(data.angles)
流式读取
通过统一事件流持续接收数据:
from linkerbot.hand.l6 import SensorSource, AngleEvent
hand.start_polling({SensorSource.ANGLE: 0.1})
for event in hand.stream():
match event:
case AngleEvent(data=data):
print(data.angles)
if should_stop():
break
hand.stop_polling()
hand.stop_stream()
快照
获取所有传感器的最新缓存数据:
snap = hand.get_snapshot()
print(snap.angle) # AngleData | None
print(snap.temperature) # TemperatureData | None
资源管理
使用 with 语句自动管理资源:
with L6(side="left", interface_name="can0") as hand:
# 使用灵巧手
pass
# 自动释放资源
或手动管理:
hand = L6(side="left", interface_name="can0")
try:
# 使用灵巧手
pass
finally:
hand.close()
异常处理
from linkerbot import L6
from linkerbot.exceptions import TimeoutError, ValidationError
with L6(side="left", interface_name="can0") as hand:
try:
data = hand.angle.get_blocking(timeout_ms=100)
except TimeoutError:
print("读取超时")
except ValidationError as e:
print(f"参数错误:{e}")
| 异常 | 说明 |
|---|---|
TimeoutError | 通信超时 |
ValidationError | 参数验证失败 |
StateError | 状态错误(如接口已关闭) |
关节索引
所有 6 关节模块使用相同的索引顺序:
| 索引 | 名称 | 标识 |
|---|---|---|
| 0 | 拇指弯曲 | thumb_flex |
| 1 | 拇指侧摆 | thumb_abd |
| 2 | 食指 | index |
| 3 | 中指 | middle |
| 4 | 无名指 | ring |
| 5 | 小指 | pinky |
CAN 总线
L6/O6 灵巧手通过 CAN 总线通信。构造函数中的 interface_type 参数指定 CAN 适配器类型。
构造参数
from linkerbot import L6
hand = L6(
side="left", # "left" 或 "right"
interface_name="can0", # 接口名称
interface_type="socketcan", # 适配器类型
)
| 参数 | 说明 |
|---|---|
side | 左手 "left" 或右手 "right" |
interface_name | 接口名称,取决于操作系统和适配器 |
interface_type | CAN 适配器类型,默认 "socketcan" |
Linux (SocketCAN)
Linux 默认使用 SocketCAN,无需指定 interface_type。
hand = L6(side="left", interface_name="can0")
配置 CAN 接口:
sudo ip link set can0 type can bitrate 1000000
sudo ip link set can0 up
连接多个 CAN 接口:Linux 系统会根据接口创建时间自动递增来命名接口,即根据 CAN 连接到主控板的顺序,依次命名为 can0、 can1、 can2,以此类推。可以串联依次执行以下命令启动多个 CAN 接口:
sudo ip link set can0 type can bitrate 1000000 && sudo ip link set can0 up && \
sudo ip link set can1 type can bitrate 1000000 && sudo ip link set can1 up && \
sudo ip link set can2 type can bitrate 1000000 && sudo ip link set can2 up
# ...
Windows (PCAN)
Windows 使用 PCAN 适配器:
hand = L6(side="left", interface_name="PCAN_USBBUS1", interface_type="pcan")
其他适配器类型参考 python-can 文档。
L6 灵巧手
快速开始
from linkerbot import L6
with L6(side="left", interface_name="can0") as hand:
# 设置角度
hand.angle.set_angles((10, 20, 30, 40, 50, 60))
# 读取角度
data = hand.angle.get_blocking(timeout_ms=500)
print(data.angles)
构造参数
| 参数 | 类型 | 说明 |
|---|---|---|
side | "left" | "right" | 左手或右手 |
interface_name | str | CAN 接口名,如 "can0" |
interface_type | str | CAN 接口类型,默认 "socketcan"。Windows 用法参考 CAN 总线 文档 |
关节说明
| 索引 | 名称 | 标识 |
|---|---|---|
| 0 | 拇指弯曲 | thumb_flex |
| 1 | 拇指侧摆 | thumb_abd |
| 2 | 食指 | index |
| 3 | 中指 | middle |
| 4 | 无名指 | ring |
| 5 | 小指 | pinky |
功能模块
| 模块 | 说明 | 文档 |
|---|---|---|
hand.angle | 关节角度控制与读取 | angle |
hand.speed | 运动速度控制 | speed |
hand.torque | 扭矩控制 | torque |
hand.force_sensor | 力传感器数据 | force-sensor |
hand.temperature | 温度监测 | temperature |
hand.current | 电流监测 | current |
hand.fault | 故障检测与清除 | fault |
hand.version | 设备版本信息 | version |
统一流式读取
L6 提供统一的事件流接口,通过 hand.stream() 和 hand.start_polling() 获取所有传感器数据。初始化时会自动以默认间隔启动轮询(角度 60 Hz、力传感器 30 Hz),无需手动调用 start_polling()。
from linkerbot import L6
from linkerbot.hand.l6 import SensorSource, AngleEvent, TemperatureEvent
with L6(side="left", interface_name="can0") as hand:
# 可同时指定多个传感器及各自的轮询间隔(秒)
# 再次调用 start_polling() 会覆盖之前的设置
hand.start_polling({SensorSource.ANGLE: 0.05, SensorSource.TEMPERATURE: 1.0})
for event in hand.stream():
match event:
case AngleEvent(data=ad):
print(f"角度:{ad.angles.to_list()}")
case TemperatureEvent(data=td):
print(f"温度:{td.temperatures.to_list()}")
hand.stop_polling()
hand.stop_stream()
SensorSource 可选值及默认频率
| 值 | 说明 | 默认频率 |
|---|---|---|
SensorSource.ANGLE | 角度 | 60 Hz |
SensorSource.TORQUE | 扭矩 | 不默认轮询 |
SensorSource.TEMPERATURE | 温度 | 不默认轮询 |
SensorSource.CURRENT | 电流 | 不默认轮询 |
SensorSource.FAULT | 故障 | 不默认轮询 |
SensorSource.FORCE_SENSOR | 力传感器 | 30 Hz |
快照
获取所有传感器最新缓存数据:
snap = hand.get_snapshot()
print(snap.angle) # AngleData | None
print(snap.temperature) # TemperatureData | None
角度控制
通过 hand.angle 控制和读取 L6 灵巧手的 6 个关节电机角度。
- 角度范围: 0-100
- 单位: 无量纲(映射到关节电机实际角度)
设置角度
from linkerbot import L6
from linkerbot.hand.l6 import L6Angle
# 使用列表
hand.angle.set_angles([50.0, 30.0, 60.0, 60.0, 60.0, 60.0])
# 使用 L6Angle 对象
angles = L6Angle(
thumb_flex=50.0, # 拇指屈曲
thumb_abd=30.0, # 拇指侧摆
index=60.0, # 食指
middle=60.0, # 中指
ring=60.0, # 无名指
pinky=60.0, # 小指
)
hand.angle.set_angles(angles)
读取角度
阻塞读取
from linkerbot import L6
from linkerbot.exceptions import TimeoutError
try:
data = hand.angle.get_blocking(timeout_ms=500)
print(f"拇指屈曲:{data.angles.thumb_flex}")
print(f"全部角度:{data.angles.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.angle.get_snapshot()
if data:
print(f"角度:{data.angles.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l6 import SensorSource, AngleEvent
hand.start_polling({SensorSource.ANGLE: 0.1})
for event in hand.stream():
match event:
case AngleEvent(data=data):
print(f"角度:{data.angles.to_list()}")
hand.stop_polling()
hand.stop_stream()
完整示例
from linkerbot import L6
with L6(side="left", interface_name="can0") as hand:
# 设置角度
hand.angle.set_angles([0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
# 读取当前角度
data = hand.angle.get_blocking(timeout_ms=500)
print(f"当前角度:{data.angles.to_list()}")
# 渐进移动
for i in range(0, 101, 10):
hand.angle.set_angles([float(i)] * 6)
速度控制
控制 L6 灵巧手各关节电机的运动速度。
设置速度
hand.speed.set_speeds(speeds)
设置 6 个关节电机的目标速度。
参数:
speeds:L6Speed实例或 6 元素列表,速度范围 0-100(无单位)
手指顺序: 拇指弯曲、拇指侧摆、食指、中指、无名指、小指
示例
列表方式
from linkerbot import L6
with L6(side="left", interface_name="can0") as hand:
# 所有手指设为中速
hand.speed.set_speeds([50.0, 50.0, 50.0, 50.0, 50.0, 50.0])
L6Speed 方式
from linkerbot import L6
from linkerbot.hand.l6 import L6Speed
with L6(side="left", interface_name="can0") as hand:
# 拇指慢速,其他手指快速
speed = L6Speed(
thumb_flex=30.0, thumb_abd=30.0, index=80.0, middle=80.0, ring=80.0, pinky=80.0
)
hand.speed.set_speeds(speed)
扭矩控制
概述
通过 hand.torque 访问扭矩控制功能,支持设置目标扭矩和读取当前扭矩。
扭矩值范围为 0-100(无量纲,表示关节电机的相对扭矩百分比),对应 L6 灵巧手的 6 个关节电机:
| 索引 | 属性 | 关节电机 |
|---|---|---|
| 0 | thumb_flex | 拇指弯曲 |
| 1 | thumb_abd | 拇指侧摆 |
| 2 | index | 食指 |
| 3 | middle | 中指 |
| 4 | ring | 无名指 |
| 5 | pinky | 小指 |
设置扭矩
# 使用列表
hand.torque.set_torques([50.0, 30.0, 60.0, 60.0, 60.0, 60.0])
# 使用 L6Torque 对象
from linkerbot.hand.l6 import L6Torque
target = L6Torque(
thumb_flex=50.0, thumb_abd=30.0, index=60.0, middle=60.0, ring=60.0, pinky=60.0
)
hand.torque.set_torques(target)
读取扭矩
阻塞读取
发送请求并等待响应:
from linkerbot.exceptions import TimeoutError
try:
data = hand.torque.get_blocking(timeout_ms=500)
print(f"拇指弯曲:{data.torques.thumb_flex}")
print(f"食指:{data.torques.index}")
except TimeoutError:
print("读取超时")
缓存读取
获取最近一次的缓存数据(非阻塞):
data = hand.torque.get_snapshot()
if data:
print(f"扭矩:{data.torques.to_list()}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l6 import SensorSource, TorqueEvent
hand.start_polling({SensorSource.TORQUE: 0.05})
for event in hand.stream():
match event:
case TorqueEvent(data=data):
print(f"扭矩:{data.torques.to_list()}")
hand.stop_polling()
hand.stop_stream()
示例
完整控制流程
from linkerbot import L6
from linkerbot.hand.l6 import L6Torque
with L6(side="left", interface_name="can0") as hand:
# 设置扭矩
hand.torque.set_torques([50.0, 30.0, 60.0, 60.0, 60.0, 60.0])
# 阻塞读取当前扭矩
data = hand.torque.get_blocking(timeout_ms=200)
print(f"当前扭矩:{data.torques.to_list()}")
实时监控
from linkerbot import L6
from linkerbot.hand.l6 import SensorSource, TorqueEvent
with L6(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.TORQUE: 0.1})
try:
for event in hand.stream():
match event:
case TorqueEvent(data=data):
if data.torques.index > 80:
print("食指关节电机扭矩过高")
break
finally:
hand.stop_polling()
hand.stop_stream()
电流读取
from linkerbot import L6
读取 L6 灵巧手六个关节电机的实时电流数据(单位:mA)。
概述
通过 hand.current 访问电流读取功能,支持三种模式:
| 模式 | 方法 | 用途 |
|---|---|---|
| 阻塞读取 | get_blocking() | 单次查询 |
| 流式读取 | hand.stream() | 持续监测 |
| 缓存读取 | get_snapshot() | 读取最近缓存 |
读取电流
阻塞读取
发送请求并等待响应:
data = hand.current.get_blocking(timeout_ms=500)
# 访问各手指电流 (单位:mA)
print(data.currents.thumb_flex) # 拇指弯曲
print(data.currents.thumb_abd) # 拇指侧摆
print(data.currents.index) # 食指
print(data.currents.middle) # 中指
print(data.currents.ring) # 无名指
print(data.currents.pinky) # 小指
# 索引访问
print(data.currents[0]) # thumb_flex
参数:
timeout_ms: 超时时间 (毫秒),默认 100
异常:
TimeoutError: 超时未收到响应
缓存读取
获取最近一次缓存的数据,不发送请求:
data = hand.current.get_snapshot()
if data:
print(f"电流:{data.currents.to_list()}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l6 import SensorSource, CurrentEvent
hand.start_polling({SensorSource.CURRENT: 0.05})
for event in hand.stream():
match event:
case CurrentEvent(data=data):
print(f"电流:{data.currents.to_list()}")
hand.stop_polling()
hand.stop_stream()
示例
检测过载电流
from linkerbot import L6
from linkerbot.hand.l6 import SensorSource, CurrentEvent
with L6(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.CURRENT: 0.05})
try:
for event in hand.stream():
match event:
case CurrentEvent(data=data):
for i, current in enumerate(data.currents.to_list()):
if current > 1000:
print(f"警告:关节 {i} 电流过高 ({current} mA)")
finally:
hand.stop_polling()
hand.stop_stream()
记录电流数据
import time
from linkerbot import L6
from linkerbot.hand.l6 import SensorSource, CurrentEvent
with L6(side="left", interface_name="can0") as hand:
records = []
start = time.time()
hand.start_polling({SensorSource.CURRENT: 0.1})
try:
for event in hand.stream():
match event:
case CurrentEvent(data=data):
records.append(
{
"time": data.timestamp - start,
"currents": data.currents.to_list(),
}
)
if time.time() - start > 5: # 记录 5 秒
break
finally:
hand.stop_polling()
hand.stop_stream()
print(f"采集 {len(records)} 条数据")
温度读取
概述
通过 hand.temperature 读取 L6 灵巧手 6 个关节电机的温度数据(单位:°C)。
温度属性
| 属性 | 说明 |
|---|---|
thumb_flex | 拇指屈曲关节电机 |
thumb_abd | 拇指侧摆关节电机 |
index | 食指关节电机 |
middle | 中指关节电机 |
ring | 无名指关节电机 |
pinky | 小指关节电机 |
读取温度
阻塞读取
data = hand.temperature.get_blocking(timeout_ms=500)
print(f"拇指温度:{data.temperatures.thumb_flex}°C")
参数
timeout_ms: 超时时间(毫秒),默认 100
返回值
TemperatureData: 包含temperatures和timestamp
异常
TimeoutError: 超时未响应
缓存读取
非阻塞读取最近一次缓存的温度数据。
data = hand.temperature.get_snapshot()
if data:
print(f"温度:{data.temperatures.to_list()}")
返回值
TemperatureData或None(无缓存数据时)
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l6 import SensorSource, TemperatureEvent
hand.start_polling({SensorSource.TEMPERATURE: 0.1})
for event in hand.stream():
match event:
case TemperatureEvent(data=data):
print(f"温度:{data.temperatures.to_list()}")
hand.stop_polling()
hand.stop_stream()
示例
读取所有关节电机温度
from linkerbot import L6
with L6(side="left", interface_name="can0") as hand:
data = hand.temperature.get_blocking(timeout_ms=500)
# 按属性访问
print(f"拇指屈曲:{data.temperatures.thumb_flex}°C")
print(f"食指:{data.temperatures.index}°C")
# 转换为列表
temps = data.temperatures.to_list()
print(f"全部温度:{temps}")
# 索引访问
print(f"第一个关节电机:{data.temperatures[0]}°C")
温度监控
from linkerbot import L6
from linkerbot.hand.l6 import SensorSource, TemperatureEvent
with L6(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.TEMPERATURE: 0.1})
try:
for event in hand.stream():
match event:
case TemperatureEvent(data=data):
for i, temp in enumerate(data.temperatures.to_list()):
if temp > 60.0:
print(f"警告:关节电机 {i} 过热 ({temp}°C)")
except KeyboardInterrupt:
pass
finally:
hand.stop_polling()
hand.stop_stream()
力传感器
L6 灵巧手配备 5 个手指的力传感器(拇指、食指、中指、无名指、小指),支持阻塞读取和缓存读取两种模式。
概述
通过 hand.force_sensor 访问力传感器功能:
from linkerbot import L6
with L6(side="left", interface_name="can0") as hand:
data = hand.force_sensor.get_blocking()
数据结构
ForceSensorData - 单个手指的传感器数据:
values: 形状 (12, 6) 的 NumPy 数组(uint8)timestamp: Unix 时间戳
AllFingersData - 全部 5 个手指的数据:
thumb,index,middle,ring,pinky: 各手指的ForceSensorData
读取数据
阻塞读取
data = hand.force_sensor.get_blocking(timeout_ms=1000)
print(data.thumb.values) # 拇指数据
print(data.index.values) # 食指数据
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
timeout_ms | float | 1000 | 超时时间(毫秒) |
异常: TimeoutError(超时)、ValidationError(参数无效)
缓存读取
获取最近一次接收的数据(非阻塞):
data = hand.force_sensor.get_snapshot()
if data:
print(f"拇指:{data.thumb.values[0]}")
print(f"食指:{data.index.values[0]}")
返回值: AllFingersData 或 None(任一手指无数据时)
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l6 import SensorSource, ForceSensorEvent
hand.start_polling({SensorSource.FORCE_SENSOR: 0.1})
for event in hand.stream():
match event:
case ForceSensorEvent(data=data):
print(data.thumb.values)
hand.stop_polling()
hand.stop_stream()
示例
阻塞读取所有手指
from linkerbot import L6
with L6(side="left", interface_name="can0") as hand:
data = hand.force_sensor.get_blocking(timeout_ms=1000)
print(f"拇指:{data.thumb.values.shape}") # (12, 6)
print(f"食指:{data.index.values.shape}")
流式采集指定时长
import time
from linkerbot import L6
from linkerbot.hand.l6 import SensorSource, ForceSensorEvent
with L6(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.FORCE_SENSOR: 0.05})
start = time.time()
try:
for event in hand.stream():
match event:
case ForceSensorEvent(data=data):
print(f"拇指:{data.thumb.values[0]}")
if time.time() - start > 5: # 采集 5 秒
break
finally:
hand.stop_polling()
hand.stop_stream()
故障管理
L6 灵巧手的故障检测与清除功能。
概述
通过 hand.fault 访问故障管理功能:
- 清除故障码
- 读取故障状态(阻塞式、缓存)
故障码表
| 故障码 | 值 | 说明 |
|---|---|---|
NONE | 0 | 无故障 |
PHASE_B_OVERCURRENT | 1 | B 相过流 |
PHASE_C_OVERCURRENT | 2 | C 相过流 |
PHASE_A_OVERCURRENT | 4 | A 相过流 |
OVERLOAD_1 | 8 | 过载等级 1 |
OVERLOAD_2 | 16 | 过载等级 2 |
MOTOR_OVERTEMP | 32 | 关节电机过温 |
MCU_OVERTEMP | 64 | MCU 过温 |
清除故障
hand.fault.clear_faults()
清除所有关节的故障码。
读取故障
阻塞式读取
data = hand.fault.get_blocking(timeout_ms=500)
参数:
timeout_ms:超时时间(毫秒),默认 100
返回值:FaultData 对象,包含:
faults:L6Fault故障数据timestamp:时间戳
异常:
TimeoutError:超时未收到响应
缓存读取
data = hand.fault.get_snapshot()
返回最近缓存的故障数据,无数据时返回 None。
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l6 import SensorSource, FaultEvent
hand.start_polling({SensorSource.FAULT: 0.2})
for event in hand.stream():
match event:
case FaultEvent(data=data):
if data.faults.has_any_fault():
for code in data.faults.to_list():
if code.has_fault():
print(code.get_fault_names())
hand.stop_polling()
hand.stop_stream()
故障数据
L6Fault 属性
| 属性 | 说明 |
|---|---|
thumb_flex | 拇指弯曲 |
thumb_abd | 拇指侧摆 |
index | 食指 |
middle | 中指 |
ring | 无名指 |
pinky | 小指 |
L6Fault 方法
# 检查是否有任何故障
faults.has_any_fault() # -> bool
# 转为列表
faults.to_list() # -> list[FaultCode]
# 索引访问
faults[0] # thumb_flex
FaultCode 方法
# 检查单个关节是否有故障
faults.thumb_flex.has_fault() # -> bool
# 获取故障名称
faults.thumb_flex.get_fault_names() # -> list[str]
示例
检查并清除故障
from linkerbot import L6
with L6(side="left", interface_name="can0") as hand:
# 读取故障状态
data = hand.fault.get_blocking(timeout_ms=500)
if data.faults.has_any_fault():
print("检测到故障:")
if data.faults.thumb_flex.has_fault():
print(f" 拇指弯曲:{data.faults.thumb_flex.get_fault_names()}")
if data.faults.index.has_fault():
print(f" 食指:{data.faults.index.get_fault_names()}")
# 清除故障
hand.fault.clear_faults()
else:
print("无故障")
持续监控
from linkerbot import L6
from linkerbot.hand.l6 import SensorSource, FaultEvent
with L6(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.FAULT: 0.2})
try:
for event in hand.stream():
match event:
case FaultEvent(data=data):
if data.faults.has_any_fault():
for code in data.faults.to_list():
if code.has_fault():
print(code.get_fault_names())
except KeyboardInterrupt:
pass
finally:
hand.stop_polling()
hand.stop_stream()
版本信息
获取 L6 灵巧手的版本信息。
概述
通过 hand.version 访问版本管理功能:
- 获取设备完整信息(序列号、PCB/固件/机械版本)
获取设备信息
hand.version.get_device_info() -> DeviceInfo
返回 DeviceInfo 对象,包含:
| 属性 | 类型 | 说明 |
|---|---|---|
serial_number | str | 设备序列号 |
pcb_version | Version | PCB 硬件版本 |
firmware_version | Version | 固件版本 |
mechanical_version | Version | 机械结构版本 |
timestamp | float | 获取时间(Unix 时间戳) |
Version 对象包含 major、minor、patch 属性,字符串格式为 V{major}.{minor}.{patch}。
异常:
TimeoutError:请求超时
示例
读取设备信息
from linkerbot import L6
hand = L6(channel="can0", hand_id=1)
info = hand.version.get_device_info()
print(f"序列号:{info.serial_number}")
print(f"固件版本:{info.firmware_version}")
hand.close()
L20Lite 灵巧手
快速开始
from linkerbot import L20lite
with L20lite(side="left", interface_name="can0") as hand:
# 设置角度
hand.angle.set_angles([50, 30, 60, 60, 60, 60, 20, 20, 20, 20])
# 读取角度
data = hand.angle.get_blocking(timeout_ms=500)
print(data.angles)
构造参数
| 参数 | 类型 | 说明 |
|---|---|---|
side | "left" | "right" | 左手或右手 |
interface_name | str | CAN 接口名,如 "can0" |
interface_type | str | CAN 接口类型,默认 "socketcan"。Windows 用法参考 CAN 总线 文档 |
关节说明
| 索引 | 名称 | 标识 |
|---|---|---|
| 0 | 拇指弯曲 | thumb_flex |
| 1 | 拇指侧摆 | thumb_abd |
| 2 | 食指弯曲 | index_flex |
| 3 | 中指弯曲 | middle_flex |
| 4 | 无名指弯曲 | ring_flex |
| 5 | 小指弯曲 | pinky_flex |
| 6 | 食指侧摆 | index_abd |
| 7 | 无名指侧摆 | ring_abd |
| 8 | 小指侧摆 | pinky_abd |
| 9 | 拇指旋转 | thumb_yaw |
功能模块
| 模块 | 说明 |
|---|---|
hand.angle | 关节角度控制与读取 |
hand.speed | 速度控制与读取 |
hand.torque | 扭矩控制与读取 |
hand.temperature | 温度读取 |
hand.force_sensor | 力传感器数据 |
hand.fault | 故障读取 |
hand.version | 设备版本信息 |
统一流式读取
L20Lite 提供统一的事件流接口,通过 hand.stream() 和 hand.start_polling() 获取所有传感器数据。初始化时会自动以默认间隔启动轮询(角度 60 Hz、力传感器 30 Hz),无需手动调用 start_polling()。
from linkerbot import L20lite
from linkerbot.hand.l20lite import SensorSource, AngleEvent, TemperatureEvent
with L20lite(side="left", interface_name="can0") as hand:
# 可同时指定多个传感器及各自的轮询间隔(秒)
# 再次调用 start_polling() 会覆盖之前的设置
hand.start_polling({SensorSource.ANGLE: 0.05, SensorSource.TEMPERATURE: 1.0})
for event in hand.stream():
match event:
case AngleEvent(data=ad):
print(f"角度:{ad.angles.to_list()}")
case TemperatureEvent(data=td):
print(f"温度:{td.temperatures.to_list()}")
hand.stop_polling()
hand.stop_stream()
SensorSource 可选值及默认频率
| 值 | 说明 | 默认频率 |
|---|---|---|
SensorSource.ANGLE | 角度 | 60 Hz |
SensorSource.SPEED | 速度 | 不默认轮询 |
SensorSource.TORQUE | 扭矩 | 不默认轮询 |
SensorSource.TEMPERATURE | 温度 | 不默认轮询 |
SensorSource.FORCE_SENSOR | 力传感器 | 30 Hz |
快照
获取所有传感器最新缓存数据:
snap = hand.get_snapshot()
print(snap.angle) # AngleData | None
print(snap.speed) # SpeedData | None
print(snap.torque) # TorqueData | None
print(snap.temperature) # TemperatureData | None
print(snap.force_sensor) # AllFingersData | None
角度控制
通过 hand.angle 控制和读取 L20Lite 灵巧手的 10 个关节电机角度。
- 角度范围: 0-100
- 单位: 无量纲(映射到关节电机实际角度)
设置角度
from linkerbot import L20lite
from linkerbot.hand.l20lite import L20liteAngle
# 使用列表
hand.angle.set_angles([50.0, 30.0, 60.0, 60.0, 60.0, 60.0, 20.0, 20.0, 20.0, 20.0])
# 使用 L20liteAngle 对象
angles = L20liteAngle(
thumb_flex=50.0, # 拇指弯曲
thumb_abd=30.0, # 拇指侧摆
index_flex=60.0, # 食指弯曲
middle_flex=60.0, # 中指弯曲
ring_flex=60.0, # 无名指弯曲
pinky_flex=60.0, # 小指弯曲
index_abd=20.0, # 食指侧摆
ring_abd=20.0, # 无名指侧摆
pinky_abd=20.0, # 小指侧摆
thumb_yaw=20.0, # 拇指旋转
)
hand.angle.set_angles(angles)
读取角度
阻塞读取
from linkerbot import L20lite
from linkerbot.exceptions import TimeoutError
try:
data = hand.angle.get_blocking(timeout_ms=500)
print(f"拇指弯曲:{data.angles.thumb_flex}")
print(f"全部角度:{data.angles.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.angle.get_snapshot()
if data:
print(f"角度:{data.angles.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l20lite import SensorSource, AngleEvent
hand.start_polling({SensorSource.ANGLE: 0.1})
try:
for event in hand.stream():
match event:
case AngleEvent(data=data):
print(f"角度:{data.angles.to_list()}")
finally:
hand.stop_polling()
hand.stop_stream()
完整示例
from linkerbot import L20lite
with L20lite(side="left", interface_name="can0") as hand:
# 设置角度
hand.angle.set_angles([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
# 读取当前角度
data = hand.angle.get_blocking(timeout_ms=500)
print(f"当前角度:{data.angles.to_list()}")
# 渐进移动
for i in range(0, 101, 10):
hand.angle.set_angles([float(i)] * 10)
速度控制
通过 hand.speed 控制和读取 L20Lite 灵巧手各关节电机的运动速度。
- 速度范围: 0-100
- 单位: 无量纲(映射到关节电机实际转速)
设置速度
from linkerbot import L20lite
from linkerbot.hand.l20lite import L20liteSpeed
# 使用列表
hand.speed.set_speeds([50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0])
# 使用 L20liteSpeed 对象
speeds = L20liteSpeed(
thumb_flex=30.0, # 拇指弯曲
thumb_abd=30.0, # 拇指侧摆
index_flex=80.0, # 食指弯曲
middle_flex=80.0, # 中指弯曲
ring_flex=80.0, # 无名指弯曲
pinky_flex=80.0, # 小指弯曲
index_abd=50.0, # 食指侧摆
ring_abd=50.0, # 无名指侧摆
pinky_abd=50.0, # 小指侧摆
thumb_yaw=50.0, # 拇指旋转
)
hand.speed.set_speeds(speeds)
读取速度
阻塞读取
from linkerbot.exceptions import TimeoutError
try:
data = hand.speed.get_blocking(timeout_ms=500)
print(f"拇指弯曲速度:{data.speeds.thumb_flex}")
print(f"全部速度:{data.speeds.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.speed.get_snapshot()
if data:
print(f"速度:{data.speeds.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l20lite import SensorSource, SpeedEvent
hand.start_polling({SensorSource.SPEED: 0.1})
try:
for event in hand.stream():
match event:
case SpeedEvent(data=data):
print(f"速度:{data.speeds.to_list()}")
finally:
hand.stop_polling()
hand.stop_stream()
完整示例
from linkerbot import L20lite
from linkerbot.hand.l20lite import L20liteSpeed
with L20lite(side="left", interface_name="can0") as hand:
# 拇指慢速,其他手指快速
speeds = L20liteSpeed(
thumb_flex=20.0,
thumb_abd=20.0,
index_flex=80.0,
middle_flex=80.0,
ring_flex=80.0,
pinky_flex=80.0,
index_abd=60.0,
ring_abd=60.0,
pinky_abd=60.0,
thumb_yaw=60.0,
)
hand.speed.set_speeds(speeds)
# 读取当前速度
data = hand.speed.get_blocking(timeout_ms=500)
print(f"当前速度:{data.speeds.to_list()}")
# 设置角度后观察速度效果
hand.angle.set_angles([100.0] * 10)
扭矩控制
通过 hand.torque 控制和读取 L20Lite 灵巧手的 10 个关节电机扭矩。
- 扭矩范围: 0-100
- 单位: 无量纲百分比
设置扭矩
from linkerbot import L20lite
from linkerbot.hand.l20lite import L20liteTorque
# 使用列表
hand.torque.set_torques([50.0, 50.0, 60.0, 60.0, 60.0, 60.0, 30.0, 30.0, 30.0, 30.0])
# 使用 L20liteTorque 对象
torques = L20liteTorque(
thumb_flex=50.0, # 拇指弯曲
thumb_abd=50.0, # 拇指侧摆
index_flex=60.0, # 食指弯曲
middle_flex=60.0, # 中指弯曲
ring_flex=60.0, # 无名指弯曲
pinky_flex=60.0, # 小指弯曲
index_abd=30.0, # 食指侧摆
ring_abd=30.0, # 无名指侧摆
pinky_abd=30.0, # 小指侧摆
thumb_yaw=30.0, # 拇指旋转
)
hand.torque.set_torques(torques)
读取扭矩
阻塞读取
from linkerbot.exceptions import TimeoutError
try:
data = hand.torque.get_blocking(timeout_ms=500)
print(f"拇指弯曲:{data.torques.thumb_flex}")
print(f"全部扭矩:{data.torques.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.torque.get_snapshot()
if data:
print(f"扭矩:{data.torques.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l20lite import SensorSource, TorqueEvent
hand.start_polling({SensorSource.TORQUE: 0.1})
try:
for event in hand.stream():
match event:
case TorqueEvent(data=data):
print(f"扭矩:{data.torques.to_list()}")
finally:
hand.stop_polling()
hand.stop_stream()
完整示例
from linkerbot import L20lite
from linkerbot.hand.l20lite import L20liteTorque
with L20lite(side="left", interface_name="can0") as hand:
# 设置扭矩
hand.torque.set_torques([50.0] * 10)
# 读取当前扭矩
data = hand.torque.get_blocking(timeout_ms=500)
print(f"当前扭矩:{data.torques.to_list()}")
# 使用数据类精细控制
torques = L20liteTorque(
thumb_flex=70.0,
thumb_abd=70.0,
index_flex=50.0,
middle_flex=50.0,
ring_flex=50.0,
pinky_flex=50.0,
index_abd=40.0,
ring_abd=40.0,
pinky_abd=40.0,
thumb_yaw=40.0,
)
hand.torque.set_torques(torques)
温度读取
通过 hand.temperature 读取 L20Lite 灵巧手 10 个关节电机的温度数据(单位:°C)。
温度属性
| 属性 | 说明 |
|---|---|
thumb_flex | 拇指弯曲关节电机 |
thumb_abd | 拇指侧摆关节电机 |
index_flex | 食指弯曲关节电机 |
middle_flex | 中指弯曲关节电机 |
ring_flex | 无名指弯曲关节电机 |
pinky_flex | 小指弯曲关节电机 |
index_abd | 食指侧摆关节电机 |
ring_abd | 无名指侧摆关节电机 |
pinky_abd | 小指侧摆关节电机 |
thumb_yaw | 拇指旋转关节电机 |
读取温度
阻塞读取
from linkerbot.exceptions import TimeoutError
try:
data = hand.temperature.get_blocking(timeout_ms=500)
print(f"拇指弯曲温度:{data.temperatures.thumb_flex}°C")
except TimeoutError:
print("读取超时")
参数
timeout_ms: 超时时间(毫秒),默认 100
返回值
TemperatureData: 包含temperatures(L20liteTemperature)和timestamp
异常
TimeoutError: 超时未响应
缓存读取
非阻塞读取最近一次缓存的温度数据。
data = hand.temperature.get_snapshot()
if data:
print(f"温度:{data.temperatures.to_list()}")
返回值
TemperatureData或None(无缓存数据时)
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l20lite import SensorSource, TemperatureEvent
hand.start_polling({SensorSource.TEMPERATURE: 0.1})
try:
for event in hand.stream():
match event:
case TemperatureEvent(data=data):
print(f"温度:{data.temperatures.to_list()}")
finally:
hand.stop_polling()
hand.stop_stream()
示例
读取所有关节电机温度
from linkerbot import L20lite
with L20lite(side="left", interface_name="can0") as hand:
data = hand.temperature.get_blocking(timeout_ms=500)
# 按属性访问
print(f"拇指弯曲:{data.temperatures.thumb_flex}°C")
print(f"食指弯曲:{data.temperatures.index_flex}°C")
# 转换为列表
temps = data.temperatures.to_list()
print(f"全部温度:{temps}")
# 索引访问
print(f"第一个关节电机:{data.temperatures[0]}°C")
温度监控
from linkerbot import L20lite
from linkerbot.hand.l20lite import SensorSource, TemperatureEvent
with L20lite(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.TEMPERATURE: 0.1})
try:
for event in hand.stream():
match event:
case TemperatureEvent(data=data):
for i, temp in enumerate(data.temperatures.to_list()):
if temp > 60.0:
print(f"警告:关节电机 {i} 过热 ({temp}°C)")
except KeyboardInterrupt:
pass
finally:
hand.stop_polling()
hand.stop_stream()
力传感器
L20Lite 灵巧手配备 5 个手指的力传感器(thumb, index, middle, ring, pinky),支持阻塞读取和缓存读取两种模式。
概述
通过 hand.force_sensor 访问力传感器功能:
from linkerbot import L20lite
with L20lite(side="left", interface_name="can0") as hand:
data = hand.force_sensor.get_blocking(timeout_ms=1000)
数据结构
ForceSensorData - 单个手指的传感器数据:
values: 形状 (12, 6) 的 NumPy 数组(uint8)timestamp: Unix 时间戳
AllFingersData - 全部 5 个手指的数据:
thumb,index,middle,ring,pinky: 各手指的ForceSensorData
读取数据
阻塞读取
from linkerbot.exceptions import TimeoutError
try:
data = hand.force_sensor.get_blocking(timeout_ms=1000)
print(data.thumb.values) # 拇指数据
print(data.index.values) # 食指数据
except TimeoutError:
print("读取超时")
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
timeout_ms | float | 1000 | 超时时间(毫秒),适用于整体操作 |
异常: TimeoutError(超时)、ValidationError(参数无效)
缓存读取
获取最近一次接收的数据(非阻塞):
data = hand.force_sensor.get_snapshot()
if data:
print(f"拇指:{data.thumb.values[0]}")
print(f"食指:{data.index.values[0]}")
返回值: AllFingersData 或 None(任一手指无数据时)
单手指读取
通过 get_finger() 获取单个手指的力传感器管理器:
# 获取拇指传感器
thumb_sensor = hand.force_sensor.get_finger("thumb")
thumb_data = thumb_sensor.get_blocking(timeout_ms=1000)
print(thumb_data.values.shape) # (12, 6)
可用手指名称:"thumb", "index", "middle", "ring", "pinky"
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l20lite import SensorSource, ForceSensorEvent
hand.start_polling({SensorSource.FORCE_SENSOR: 0.1})
try:
for event in hand.stream():
match event:
case ForceSensorEvent(data=data):
print(data.thumb.values)
finally:
hand.stop_polling()
hand.stop_stream()
示例
阻塞读取所有手指
from linkerbot import L20lite
with L20lite(side="left", interface_name="can0") as hand:
data = hand.force_sensor.get_blocking(timeout_ms=1000)
print(f"拇指:{data.thumb.values.shape}")
print(f"食指:{data.index.values.shape}")
print(f"中指:{data.middle.values.shape}")
print(f"无名指:{data.ring.values.shape}")
print(f"小指:{data.pinky.values.shape}")
流式采集指定时长
import time
from linkerbot import L20lite
from linkerbot.hand.l20lite import SensorSource, ForceSensorEvent
with L20lite(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.FORCE_SENSOR: 0.05})
start = time.time()
try:
for event in hand.stream():
match event:
case ForceSensorEvent(data=data):
print(f"拇指:{data.thumb.values[0]}")
if time.time() - start > 5: # 采集 5 秒
break
finally:
hand.stop_polling()
hand.stop_stream()
故障管理
L20Lite 灵巧手的故障检测功能。
概述
通过 hand.fault 访问故障管理功能:
- 读取故障状态(阻塞式、缓存)
故障码表
| 故障码 | 值 | 说明 |
|---|---|---|
NONE | 0 | 无故障 |
VOLTAGE_ABNORMAL | 1 | 过压/欠压 |
ENCODER_ABNORMAL | 2 | 磁编码器异常 |
OVERTEMPERATURE | 4 | 温度过热 |
OVERCURRENT | 8 | 电流过流 |
OVERLOAD | 32 | 负载过载 |
读取故障
阻塞式读取
from linkerbot.exceptions import TimeoutError
try:
data = hand.fault.get_blocking(timeout_ms=500)
except TimeoutError:
print("读取超时")
参数:
timeout_ms:超时时间(毫秒),默认 100
返回值:FaultData 对象,包含:
faults:L20liteFault故障数据timestamp:时间戳
异常:
TimeoutError:超时未收到响应
缓存读取
data = hand.fault.get_snapshot()
if data:
print(f"有故障:{data.faults.has_any_fault()}")
返回最近缓存的故障数据,无数据时返回 None。
故障数据
L20liteFault 属性
| 属性 | 说明 |
|---|---|
thumb_flex | 拇指弯曲 |
thumb_abd | 拇指侧摆 |
index_flex | 食指弯曲 |
middle_flex | 中指弯曲 |
ring_flex | 无名指弯曲 |
pinky_flex | 小指弯曲 |
index_abd | 食指侧摆 |
ring_abd | 无名指侧摆 |
pinky_abd | 小指侧摆 |
thumb_yaw | 拇指旋转 |
L20liteFault 方法
# 检查是否有任何故障
faults.has_any_fault() # -> bool
# 转为列表
faults.to_list() # -> list[FaultCode]
# 索引访问
faults[0] # thumb_flex
FaultCode 方法
# 检查单个关节电机是否有故障
faults.thumb_flex.has_fault() # -> bool
# 获取故障名称
faults.thumb_flex.get_fault_names() # -> list[str]
示例
检查故障状态
from linkerbot import L20lite
with L20lite(side="left", interface_name="can0") as hand:
# 读取故障状态
data = hand.fault.get_blocking(timeout_ms=500)
if data.faults.has_any_fault():
print("检测到故障:")
if data.faults.thumb_flex.has_fault():
print(f" 拇指弯曲:{data.faults.thumb_flex.get_fault_names()}")
if data.faults.index_flex.has_fault():
print(f" 食指弯曲:{data.faults.index_flex.get_fault_names()}")
else:
print("无故障")
遍历所有关节故障
from linkerbot import L20lite
with L20lite(side="left", interface_name="can0") as hand:
data = hand.fault.get_blocking(timeout_ms=500)
if data.faults.has_any_fault():
for i, code in enumerate(data.faults.to_list()):
if code.has_fault():
print(f"关节 {i} 故障:{code.get_fault_names()}")
版本信息
获取 L20Lite 灵巧手的版本信息。
概述
通过 hand.version 访问版本管理功能:
- 获取设备完整信息(序列号、PCB/固件/机械版本)
获取设备信息
hand.version.get_device_info() -> DeviceInfo
返回 DeviceInfo 对象,包含:
| 属性 | 类型 | 说明 |
|---|---|---|
serial_number | str | 设备序列号 |
pcb_version | Version | PCB 硬件版本 |
firmware_version | Version | 固件版本 |
mechanical_version | Version | 机械结构版本 |
timestamp | float | 获取时间(Unix 时间戳) |
Version 对象包含 major、minor、patch 属性,字符串格式为 V{major}.{minor}.{patch}。
异常:
TimeoutError:请求超时
示例
读取设备信息
from linkerbot import L20lite
with L20lite(side="left", interface_name="can0") as hand:
info = hand.version.get_device_info()
print(f"序列号:{info.serial_number}")
print(f"PCB 版本:{info.pcb_version}")
print(f"固件版本:{info.firmware_version}")
print(f"机械版本:{info.mechanical_version}")
L25 灵巧手
快速开始
from linkerbot import L25
with L25(side="left", interface_name="can0") as hand:
# 设置角度
hand.angle.set_angles([50.0] * 16)
# 读取角度
data = hand.angle.get_blocking(timeout_ms=500)
print(data.angles)
构造参数
| 参数 | 类型 | 说明 |
|---|---|---|
side | "left" | "right" | 左手或右手 |
interface_name | str | CAN 接口名,如 "can0" |
interface_type | str | CAN 接口类型,默认 "socketcan"。Windows 用法参考 CAN 总线 文档 |
关节说明
L25 灵巧手拥有 16 个自由度,分布在 5 根手指上。
| 索引 | 名称 | 标识 |
|---|---|---|
| 0 | 拇指侧摆 | thumb_abd |
| 1 | 拇指旋转 | thumb_yaw |
| 2 | 拇指根部 | thumb_root1 |
| 3 | 拇指指尖 | thumb_tip |
| 4 | 食指侧摆 | index_abd |
| 5 | 食指根部 | index_root1 |
| 6 | 食指指尖 | index_tip |
| 7 | 中指侧摆 | middle_abd |
| 8 | 中指根部 | middle_root1 |
| 9 | 中指指尖 | middle_tip |
| 10 | 无名指侧摆 | ring_abd |
| 11 | 无名指根部 | ring_root1 |
| 12 | 无名指指尖 | ring_tip |
| 13 | 小指侧摆 | pinky_abd |
| 14 | 小指根部 | pinky_root1 |
| 15 | 小指指尖 | pinky_tip |
功能模块
| 模块 | 说明 |
|---|---|
hand.angle | 关节角度控制与读取 |
hand.speed | 速度控制与读取 |
hand.torque | 扭矩控制与读取 |
hand.temperature | 温度读取 |
hand.force_sensor | 力传感器数据 |
hand.fault | 故障读取与清除 |
hand.version | 设备版本信息 |
统一流式读取
L25 提供统一的事件流接口,通过 hand.stream() 和 hand.start_polling() 获取所有传感器数据。初始化时会自动以默认间隔启动轮询(角度 60 Hz、力传感器 30 Hz),无需手动调用 start_polling()。
from linkerbot import L25
from linkerbot.hand.l25 import SensorSource, AngleEvent, TemperatureEvent
with L25(side="left", interface_name="can0") as hand:
# 可同时指定多个传感器及各自的轮询间隔(秒)
# 再次调用 start_polling() 会覆盖之前的设置
hand.start_polling({SensorSource.ANGLE: 0.05, SensorSource.TEMPERATURE: 1.0})
for event in hand.stream():
match event:
case AngleEvent(data=ad):
print(f"角度:{ad.angles.to_list()}")
case TemperatureEvent(data=td):
print(f"温度:{td.temperatures.to_list()}")
hand.stop_polling()
hand.stop_stream()
start_polling 参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
intervals | dict[SensorSource, float] | 全部默认值 | 每个传感器的轮询间隔(秒) |
SensorSource 可选值及默认频率
| 值 | 说明 | 默认频率 |
|---|---|---|
SensorSource.ANGLE | 角度 | 60 Hz |
SensorSource.SPEED | 速度 | 不默认轮询 |
SensorSource.TORQUE | 扭矩 | 不默认轮询 |
SensorSource.TEMPERATURE | 温度 | 不默认轮询 |
SensorSource.FAULT | 故障 | 不默认轮询 |
SensorSource.FORCE_SENSOR | 力传感器 | 30 Hz |
快照
获取所有传感器最新缓存数据:
snap = hand.get_snapshot()
print(snap.angle) # AngleData | None
print(snap.speed) # SpeedData | None
print(snap.torque) # TorqueData | None
print(snap.temperature) # TemperatureData | None
print(snap.fault) # FaultData | None
print(snap.force_sensor) # AllFingersData | None
角度控制
通过 hand.angle 控制和读取 L25 灵巧手的 16 个关节电机角度。
- 角度范围: 0-100
- 单位: 无量纲(映射到关节电机实际角度)
设置角度
from linkerbot import L25
from linkerbot.hand.l25 import L25Angle
# 使用列表(16 个关节)
hand.angle.set_angles([50.0] * 16)
# 使用 L25Angle 对象
angles = L25Angle(
thumb_abd=50.0, # 拇指侧摆
thumb_yaw=30.0, # 拇指旋转
thumb_root1=60.0, # 拇指根部
thumb_tip=40.0, # 拇指指尖
index_abd=50.0, # 食指侧摆
index_root1=60.0, # 食指根部
index_tip=40.0, # 食指指尖
middle_abd=50.0, # 中指侧摆
middle_root1=60.0, # 中指根部
middle_tip=40.0, # 中指指尖
ring_abd=50.0, # 无名指侧摆
ring_root1=60.0, # 无名指根部
ring_tip=40.0, # 无名指指尖
pinky_abd=50.0, # 小指侧摆
pinky_root1=60.0, # 小指根部
pinky_tip=40.0, # 小指指尖
)
hand.angle.set_angles(angles)
读取角度
阻塞读取
from linkerbot import L25
from linkerbot.exceptions import TimeoutError
try:
data = hand.angle.get_blocking(timeout_ms=500)
print(f"拇指侧摆:{data.angles.thumb_abd}")
print(f"全部角度:{data.angles.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.angle.get_snapshot()
if data:
print(f"角度:{data.angles.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l25 import SensorSource, AngleEvent
hand.start_polling({SensorSource.ANGLE: 0.1})
try:
for event in hand.stream():
match event:
case AngleEvent(data=data):
print(f"角度:{data.angles.to_list()}")
finally:
hand.stop_polling()
hand.stop_stream()
完整示例
from linkerbot import L25
with L25(side="left", interface_name="can0") as hand:
# 设置角度
hand.angle.set_angles([0.0] * 16)
# 读取当前角度
data = hand.angle.get_blocking(timeout_ms=500)
print(f"当前角度:{data.angles.to_list()}")
# 渐进移动
for i in range(0, 101, 10):
hand.angle.set_angles([float(i)] * 16)
速度控制
通过 hand.speed 控制和读取 L25 灵巧手各关节电机的运动速度。
- 速度范围: 0-100
- 单位: 无量纲
设置速度
from linkerbot import L25
from linkerbot.hand.l25 import L25Speed
# 使用列表(16 个关节)
hand.speed.set_speeds([50.0] * 16)
# 使用 L25Speed 对象
speed = L25Speed(
thumb_abd=30.0, # 拇指侧摆
thumb_yaw=30.0, # 拇指旋转
thumb_root1=30.0, # 拇指根部
thumb_tip=30.0, # 拇指指尖
index_abd=80.0, # 食指侧摆
index_root1=80.0, # 食指根部
index_tip=80.0, # 食指指尖
middle_abd=80.0, # 中指侧摆
middle_root1=80.0, # 中指根部
middle_tip=80.0, # 中指指尖
ring_abd=80.0, # 无名指侧摆
ring_root1=80.0, # 无名指根部
ring_tip=80.0, # 无名指指尖
pinky_abd=80.0, # 小指侧摆
pinky_root1=80.0, # 小指根部
pinky_tip=80.0, # 小指指尖
)
hand.speed.set_speeds(speed)
读取速度
阻塞读取
from linkerbot import L25
from linkerbot.exceptions import TimeoutError
try:
data = hand.speed.get_blocking(timeout_ms=500)
print(f"拇指侧摆速度:{data.speeds.thumb_abd}")
print(f"全部速度:{data.speeds.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.speed.get_snapshot()
if data:
print(f"速度:{data.speeds.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l25 import SensorSource, SpeedEvent
hand.start_polling({SensorSource.SPEED: 0.1})
try:
for event in hand.stream():
match event:
case SpeedEvent(data=data):
print(f"速度:{data.speeds.to_list()}")
finally:
hand.stop_polling()
hand.stop_stream()
完整示例
from linkerbot import L25
from linkerbot.hand.l25 import L25Speed
with L25(side="left", interface_name="can0") as hand:
# 设置速度
speed = L25Speed(
thumb_abd=30.0,
thumb_yaw=30.0,
thumb_root1=30.0,
thumb_tip=30.0,
index_abd=80.0,
index_root1=80.0,
index_tip=80.0,
middle_abd=80.0,
middle_root1=80.0,
middle_tip=80.0,
ring_abd=80.0,
ring_root1=80.0,
ring_tip=80.0,
pinky_abd=80.0,
pinky_root1=80.0,
pinky_tip=80.0,
)
hand.speed.set_speeds(speed)
# 读取当前速度
data = hand.speed.get_blocking(timeout_ms=500)
print(f"当前速度:{data.speeds.to_list()}")
扭矩控制
通过 hand.torque 控制和读取 L25 灵巧手的 16 个关节电机扭矩。
- 扭矩范围: 0-100
- 单位: 无量纲百分比
设置扭矩
from linkerbot import L25
from linkerbot.hand.l25 import L25Torque
# 使用列表(16 个关节)
hand.torque.set_torques([50.0] * 16)
# 使用 L25Torque 对象
torques = L25Torque(
thumb_abd=50.0, # 拇指侧摆
thumb_yaw=30.0, # 拇指旋转
thumb_root1=60.0, # 拇指根部
thumb_tip=40.0, # 拇指指尖
index_abd=50.0, # 食指侧摆
index_root1=60.0, # 食指根部
index_tip=40.0, # 食指指尖
middle_abd=50.0, # 中指侧摆
middle_root1=60.0, # 中指根部
middle_tip=40.0, # 中指指尖
ring_abd=50.0, # 无名指侧摆
ring_root1=60.0, # 无名指根部
ring_tip=40.0, # 无名指指尖
pinky_abd=50.0, # 小指侧摆
pinky_root1=60.0, # 小指根部
pinky_tip=40.0, # 小指指尖
)
hand.torque.set_torques(torques)
读取扭矩
阻塞读取
from linkerbot.exceptions import TimeoutError
try:
data = hand.torque.get_blocking(timeout_ms=500)
print(f"拇指侧摆:{data.torques.thumb_abd}")
print(f"全部扭矩:{data.torques.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.torque.get_snapshot()
if data:
print(f"扭矩:{data.torques.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l25 import SensorSource, TorqueEvent
hand.start_polling({SensorSource.TORQUE: 0.1})
try:
for event in hand.stream():
match event:
case TorqueEvent(data=data):
print(f"扭矩:{data.torques.to_list()}")
finally:
hand.stop_polling()
hand.stop_stream()
完整示例
from linkerbot import L25
with L25(side="left", interface_name="can0") as hand:
# 设置扭矩
hand.torque.set_torques([50.0] * 16)
# 读取当前扭矩
data = hand.torque.get_blocking(timeout_ms=500)
print(f"当前扭矩:{data.torques.to_list()}")
# 设置不同手指的扭矩
hand.torque.set_torques(
[
80.0,
80.0,
80.0,
80.0, # 拇指
60.0,
60.0,
60.0, # 食指
60.0,
60.0,
60.0, # 中指
60.0,
60.0,
60.0, # 无名指
60.0,
60.0,
60.0, # 小指
]
)
温度读取
通过 hand.temperature 读取 L25 灵巧手 16 个关节电机的温度数据(单位:°C)。
温度属性
| 属性 | 说明 |
|---|---|
thumb_abd | 拇指侧摆关节电机 |
thumb_yaw | 拇指旋转关节电机 |
thumb_root1 | 拇指根部关节电机 |
thumb_tip | 拇指指尖关节电机 |
index_abd | 食指侧摆关节电机 |
index_root1 | 食指根部关节电机 |
index_tip | 食指指尖关节电机 |
middle_abd | 中指侧摆关节电机 |
middle_root1 | 中指根部关节电机 |
middle_tip | 中指指尖关节电机 |
ring_abd | 无名指侧摆关节电机 |
ring_root1 | 无名指根部关节电机 |
ring_tip | 无名指指尖关节电机 |
pinky_abd | 小指侧摆关节电机 |
pinky_root1 | 小指根部关节电机 |
pinky_tip | 小指指尖关节电机 |
读取温度
阻塞读取
from linkerbot.exceptions import TimeoutError
try:
data = hand.temperature.get_blocking(timeout_ms=500)
print(f"拇指侧摆温度:{data.temperatures.thumb_abd}°C")
except TimeoutError:
print("读取超时")
参数
timeout_ms: 超时时间(毫秒),默认 100
返回值
TemperatureData: 包含temperatures(L25Temperature)和timestamp
异常
TimeoutError: 超时未响应
缓存读取
非阻塞读取最近一次缓存的温度数据。
data = hand.temperature.get_snapshot()
if data:
print(f"温度:{data.temperatures.to_list()}")
返回值
TemperatureData或None(无缓存数据时)
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l25 import SensorSource, TemperatureEvent
hand.start_polling({SensorSource.TEMPERATURE: 0.1})
try:
for event in hand.stream():
match event:
case TemperatureEvent(data=data):
print(f"温度:{data.temperatures.to_list()}")
finally:
hand.stop_polling()
hand.stop_stream()
示例
读取所有关节电机温度
from linkerbot import L25
with L25(side="left", interface_name="can0") as hand:
data = hand.temperature.get_blocking(timeout_ms=500)
# 按属性访问
print(f"拇指侧摆:{data.temperatures.thumb_abd}°C")
print(f"食指侧摆:{data.temperatures.index_abd}°C")
# 转换为列表
temps = data.temperatures.to_list()
print(f"全部温度:{temps}")
# 索引访问
print(f"第一个关节电机:{data.temperatures[0]}°C")
温度监控
from linkerbot import L25
from linkerbot.hand.l25 import SensorSource, TemperatureEvent
with L25(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.TEMPERATURE: 0.1})
try:
for event in hand.stream():
match event:
case TemperatureEvent(data=data):
for i, temp in enumerate(data.temperatures.to_list()):
if temp > 60.0:
print(f"警告:关节电机 {i} 过热 ({temp}°C)")
except KeyboardInterrupt:
pass
finally:
hand.stop_polling()
hand.stop_stream()
力传感器
L25 灵巧手配备 5 个手指的力传感器(thumb, index, middle, ring, pinky),支持阻塞读取和缓存读取两种模式。
概述
通过 hand.force_sensor 访问力传感器功能:
from linkerbot import L25
with L25(side="left", interface_name="can0") as hand:
data = hand.force_sensor.get_blocking()
数据结构
ForceSensorData - 单个手指的传感器数据:
values: 形状 (12, 6) 的 NumPy 数组(uint8)timestamp: Unix 时间戳
AllFingersData - 全部 5 个手指的数据:
thumb,index,middle,ring,pinky: 各手指的ForceSensorData
读取数据
阻塞读取
from linkerbot.exceptions import TimeoutError
try:
data = hand.force_sensor.get_blocking(timeout_ms=1000)
print(data.thumb.values) # 拇指数据
print(data.index.values) # 食指数据
except TimeoutError:
print("读取超时")
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
timeout_ms | float | 1000 | 超时时间(毫秒) |
异常: TimeoutError(超时)、ValidationError(参数无效)
缓存读取
获取最近一次接收的数据(非阻塞):
data = hand.force_sensor.get_snapshot()
if data:
print(f"拇指:{data.thumb.values[0]}")
print(f"食指:{data.index.values[0]}")
返回值: AllFingersData 或 None(任一手指无数据时)
单手指读取
通过 get_finger() 获取单个手指的传感器管理器,支持独立的阻塞和缓存读取:
# 获取拇指的力传感器管理器
thumb_sensor = hand.force_sensor.get_finger("thumb")
# 阻塞读取拇指数据
thumb_data = thumb_sensor.get_blocking(timeout_ms=1000)
print(f"拇指:{thumb_data.values.shape}")
# 缓存读取拇指数据
thumb_data = thumb_sensor.get_snapshot()
if thumb_data:
print(f"拇指:{thumb_data.values[0]}")
可用手指名称: "thumb", "index", "middle", "ring", "pinky"
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l25 import SensorSource, ForceSensorEvent
hand.start_polling({SensorSource.FORCE_SENSOR: 0.1})
try:
for event in hand.stream():
match event:
case ForceSensorEvent(data=data):
print(data.thumb.values)
finally:
hand.stop_polling()
hand.stop_stream()
示例
阻塞读取所有手指
from linkerbot import L25
with L25(side="left", interface_name="can0") as hand:
data = hand.force_sensor.get_blocking(timeout_ms=1000)
print(f"拇指:{data.thumb.values.shape}")
print(f"食指:{data.index.values.shape}")
print(f"中指:{data.middle.values.shape}")
print(f"无名指:{data.ring.values.shape}")
print(f"小指:{data.pinky.values.shape}")
流式采集指定时长
import time
from linkerbot import L25
from linkerbot.hand.l25 import SensorSource, ForceSensorEvent
with L25(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.FORCE_SENSOR: 0.05})
start = time.time()
try:
for event in hand.stream():
match event:
case ForceSensorEvent(data=data):
print(f"拇指:{data.thumb.values[0]}")
if time.time() - start > 5: # 采集 5 秒
break
finally:
hand.stop_polling()
hand.stop_stream()
故障管理
L25 灵巧手的故障检测与清除功能。
概述
通过 hand.fault 访问故障管理功能:
- 读取故障状态(阻塞式、缓存)
- 清除所有关节故障
故障码表
L25 使用位标志(Flag)表示故障状态,每个关节电机可同时存在多个故障。
| 故障码 | 值 | 说明 |
|---|---|---|
NONE | 0 | 无故障 |
MOTOR_ROTOR_LOCK | 1 | 电机转子锁死 |
MOTOR_OVER_CURRENT | 2 | 电机过流 |
MOTOR_STALL_FAULT | 4 | 电机堵转故障 |
VOLTAGE_ABNORMAL | 8 | 电压异常 |
SELF_CHECK_ABNORMAL | 16 | 自检异常 |
OVER_TEMPERATURE | 32 | 过温 |
SOFT_ROTOR_LOCK | 64 | 软件转子锁死 |
MOTOR_COMM_ABNORMAL | 128 | 电机通讯异常 |
读取故障
阻塞式读取
from linkerbot.exceptions import TimeoutError
try:
data = hand.fault.get_blocking(timeout_ms=500)
except TimeoutError:
print("读取超时")
参数:
timeout_ms:超时时间(毫秒),默认 100
返回值:FaultData 对象,包含:
faults:L25Fault故障数据timestamp:时间戳
异常:
TimeoutError:超时未收到响应
缓存读取
data = hand.fault.get_snapshot()
if data:
print(f"是否有故障:{data.faults.has_any_fault()}")
返回最近缓存的故障数据,无数据时返回 None。
清除故障
hand.fault.clear_faults()
向所有关节发送故障清除命令(fire-and-forget),不等待响应。
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.l25 import SensorSource, FaultEvent
hand.start_polling({SensorSource.FAULT: 0.2})
try:
for event in hand.stream():
match event:
case FaultEvent(data=data):
if data.faults.has_any_fault():
for code in data.faults.to_list():
if code.has_fault():
print(code.get_fault_names())
finally:
hand.stop_polling()
hand.stop_stream()
数据类说明
L25Fault 属性
| 属性 | 说明 |
|---|---|
thumb_abd | 拇指侧摆 |
thumb_yaw | 拇指旋转 |
thumb_root1 | 拇指根部 |
thumb_tip | 拇指指尖 |
index_abd | 食指侧摆 |
index_root1 | 食指根部 |
index_tip | 食指指尖 |
middle_abd | 中指侧摆 |
middle_root1 | 中指根部 |
middle_tip | 中指指尖 |
ring_abd | 无名指侧摆 |
ring_root1 | 无名指根部 |
ring_tip | 无名指指尖 |
pinky_abd | 小指侧摆 |
pinky_root1 | 小指根部 |
pinky_tip | 小指指尖 |
L25Fault 方法
# 检查是否有任何故障
faults.has_any_fault() # -> bool
# 转为列表
faults.to_list() # -> list[L25FaultCode]
# 索引访问
faults[0] # thumb_abd
L25FaultCode 方法
# 检查单个关节电机是否有故障
faults.thumb_abd.has_fault() # -> bool
# 获取故障名称列表
faults.thumb_abd.get_fault_names() # -> list[str]
示例
检查故障状态
from linkerbot import L25
with L25(side="left", interface_name="can0") as hand:
# 读取故障状态
data = hand.fault.get_blocking(timeout_ms=500)
if data.faults.has_any_fault():
print("检测到故障:")
if data.faults.thumb_abd.has_fault():
print(f" 拇指侧摆:{data.faults.thumb_abd.get_fault_names()}")
if data.faults.index_abd.has_fault():
print(f" 食指侧摆:{data.faults.index_abd.get_fault_names()}")
else:
print("无故障")
# 清除所有故障
hand.fault.clear_faults()
持续监控
from linkerbot import L25
from linkerbot.hand.l25 import SensorSource, FaultEvent
with L25(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.FAULT: 0.2})
try:
for event in hand.stream():
match event:
case FaultEvent(data=data):
if data.faults.has_any_fault():
for i, code in enumerate(data.faults.to_list()):
if code.has_fault():
print(f"关节 {i} 故障:{code.get_fault_names()}")
except KeyboardInterrupt:
pass
finally:
hand.stop_polling()
hand.stop_stream()
版本信息
获取 L25 灵巧手的版本信息。
概述
通过 hand.version 访问版本管理功能:
- 获取设备完整信息(序列号、PCB/固件/机械版本)
获取设备信息
hand.version.get_device_info() -> DeviceInfo
返回 DeviceInfo 对象,包含:
| 属性 | 类型 | 说明 |
|---|---|---|
serial_number | str | 设备序列号 |
pcb_version | Version | PCB 硬件版本 |
firmware_version | Version | 固件版本 |
mechanical_version | Version | 机械结构版本 |
timestamp | float | 获取时间(Unix 时间戳) |
Version 对象包含 major、minor、patch 属性,字符串格式为 V{major}.{minor}.{patch}。
异常:
TimeoutError:请求超时
示例
读取设备信息
from linkerbot import L25
with L25(side="left", interface_name="can0") as hand:
info = hand.version.get_device_info()
print(f"序列号:{info.serial_number}")
print(f"PCB 版本:{info.pcb_version}")
print(f"固件版本:{info.firmware_version}")
print(f"机械版本:{info.mechanical_version}")
O6 灵巧手
快速开始
from linkerbot import O6
with O6(side="left", interface_name="can0") as hand:
# 设置角度
hand.angle.set_angles((10, 20, 30, 40, 50, 60))
# 读取角度
data = hand.angle.get_blocking(timeout_ms=500)
print(data.angles)
构造参数
| 参数 | 类型 | 说明 |
|---|---|---|
side | "left" | "right" | 左手或右手 |
interface_name | str | CAN 接口名,如 "can0" |
interface_type | str | CAN 接口类型,默认 "socketcan"。Windows 用法参考 CAN 总线 文档 |
关节说明
| 索引 | 名称 | 标识 |
|---|---|---|
| 0 | 拇指弯曲 | thumb_flex |
| 1 | 拇指侧摆 | thumb_abd |
| 2 | 食指 | index |
| 3 | 中指 | middle |
| 4 | 无名指 | ring |
| 5 | 小指 | pinky |
功能模块
| 模块 | 说明 |
|---|---|
hand.angle | 关节角度控制与读取 |
hand.speed | 速度控制(支持读取和 RPM 单位) |
hand.acceleration | 加速度控制 |
hand.torque | 扭矩控制(支持 mA 单位) |
hand.temperature | 温度读取 |
hand.force_sensor | 力传感器数据 |
hand.fault | 故障读取 |
hand.version | 设备版本信息 |
统一流式读取
O6 提供统一的事件流接口,通过 hand.stream() 和 hand.start_polling() 获取所有传感器数据。初始化时会自动以默认间隔启动轮询(角度 60 Hz、力传感器 30 Hz),无需手动调用 start_polling()。
from linkerbot import O6
from linkerbot.hand.o6 import SensorSource, AngleEvent, TemperatureEvent
with O6(side="left", interface_name="can0") as hand:
# 可同时指定多个传感器及各自的轮询间隔(秒)
# 再次调用 start_polling() 会覆盖之前的设置
hand.start_polling({SensorSource.ANGLE: 0.05, SensorSource.TEMPERATURE: 1.0})
for event in hand.stream():
match event:
case AngleEvent(data=ad):
print(f"角度:{ad.angles.to_list()}")
case TemperatureEvent(data=td):
print(f"温度:{td.temperatures.to_list()}")
hand.stop_polling()
hand.stop_stream()
SensorSource 可选值及默认频率
| 值 | 说明 | 默认频率 |
|---|---|---|
SensorSource.ANGLE | 角度 | 60 Hz |
SensorSource.TORQUE | 扭矩 | 不默认轮询 |
SensorSource.SPEED | 速度 | 不默认轮询 |
SensorSource.ACCELERATION | 加速度 | 不默认轮询 |
SensorSource.TEMPERATURE | 温度 | 不默认轮询 |
SensorSource.FAULT | 故障 | 不默认轮询 |
SensorSource.FORCE_SENSOR | 力传感器 | 30 Hz |
快照
获取所有传感器最新缓存数据:
snap = hand.get_snapshot()
print(snap.angle) # AngleData | None
print(snap.temperature) # TemperatureData | None
角度控制
通过 hand.angle 控制和读取 O6 灵巧手的 6 个关节电机角度。
- 角度范围: 0-100
- 单位: 无量纲(映射到关节电机实际角度)
设置角度
from linkerbot import O6
from linkerbot.hand.o6 import O6Angle
# 使用列表
hand.angle.set_angles([50.0, 30.0, 60.0, 60.0, 60.0, 60.0])
# 使用 O6Angle 对象
angles = O6Angle(
thumb_flex=50.0, # 拇指屈曲
thumb_abd=30.0, # 拇指侧摆
index=60.0, # 食指
middle=60.0, # 中指
ring=60.0, # 无名指
pinky=60.0, # 小指
)
hand.angle.set_angles(angles)
读取角度
阻塞读取
from linkerbot import O6
from linkerbot.exceptions import TimeoutError
try:
data = hand.angle.get_blocking(timeout_ms=500)
print(f"拇指屈曲:{data.angles.thumb_flex}")
print(f"全部角度:{data.angles.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.angle.get_snapshot()
if data:
print(f"角度:{data.angles.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.o6 import SensorSource, AngleEvent
hand.start_polling({SensorSource.ANGLE: 0.1})
for event in hand.stream():
match event:
case AngleEvent(data=data):
print(f"角度:{data.angles.to_list()}")
hand.stop_polling()
hand.stop_stream()
完整示例
from linkerbot import O6
with O6(side="left", interface_name="can0") as hand:
# 设置角度
hand.angle.set_angles([0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
# 读取当前角度
data = hand.angle.get_blocking(timeout_ms=500)
print(f"当前角度:{data.angles.to_list()}")
# 渐进移动
for i in range(0, 101, 10):
hand.angle.set_angles([float(i)] * 6)
速度控制
通过 hand.speed 控制和读取 O6 灵巧手各关节电机的运动速度。
- 速度范围: 0-100
- 最大转速: 186.66 RPM(对应值 100)
设置速度
from linkerbot import O6
from linkerbot.hand.o6 import O6Speed
# 使用列表
hand.speed.set_speeds([50.0, 50.0, 50.0, 50.0, 50.0, 50.0])
# 使用 O6Speed 对象
speed = O6Speed(
thumb_flex=30.0, # 拇指弯曲
thumb_abd=30.0, # 拇指侧摆
index=80.0, # 食指
middle=80.0, # 中指
ring=80.0, # 无名指
pinky=80.0, # 小指
)
hand.speed.set_speeds(speed)
读取速度
阻塞读取
from linkerbot import O6
from linkerbot.exceptions import TimeoutError
try:
data = hand.speed.get_blocking(timeout_ms=500)
print(f"拇指弯曲速度:{data.speeds.thumb_flex}")
print(f"全部速度:{data.speeds.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.speed.get_snapshot()
if data:
print(f"速度:{data.speeds.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.o6 import SensorSource, SpeedEvent
hand.start_polling({SensorSource.SPEED: 0.1})
for event in hand.stream():
match event:
case SpeedEvent(data=data):
print(f"速度:{data.speeds.to_list()}")
hand.stop_polling()
hand.stop_stream()
RPM 单位转换
O6Speed 支持 RPM(转/分钟)单位的转换。
转换为 RPM
from linkerbot.hand.o6 import O6Speed
speed = O6Speed(50.0, 50.0, 50.0, 50.0, 50.0, 50.0)
rpm_values = speed.to_rpm()
print(rpm_values) # [93.33, 93.33, 93.33, 93.33, 93.33, 93.33]
从 RPM 创建
from linkerbot.hand.o6 import O6Speed
# 设置所有关节电机为 90 RPM
speed = O6Speed.from_rpm([90.0, 90.0, 90.0, 90.0, 90.0, 90.0])
hand.speed.set_speeds(speed)
# 拇指慢速,其他手指快速
speed = O6Speed.from_rpm([60.0, 60.0, 120.0, 120.0, 120.0, 120.0])
hand.speed.set_speeds(speed)
完整示例
from linkerbot import O6
from linkerbot.hand.o6 import O6Speed
with O6(side="left", interface_name="can0") as hand:
# 使用 RPM 设置速度
speed = O6Speed.from_rpm([90.0, 90.0, 120.0, 120.0, 120.0, 120.0])
hand.speed.set_speeds(speed)
# 读取当前速度
data = hand.speed.get_blocking(timeout_ms=500)
print(f"当前速度:{data.speeds.to_list()}")
print(f"RPM: {data.speeds.to_rpm()}")
加速度控制
通过 hand.acceleration 控制和读取 O6 灵巧手的 6 个关节电机加速度。
- 加速度范围: 0-100
- 最大加速度: 2209.8 deg/s²(对应值 100)
设置加速度
from linkerbot import O6
from linkerbot.hand.o6 import O6Acceleration
# 使用列表
hand.acceleration.set_accelerations([80.0, 80.0, 80.0, 80.0, 80.0, 80.0])
# 使用 O6Acceleration 对象
accel = O6Acceleration(
thumb_flex=80.0, # 拇指屈曲
thumb_abd=80.0, # 拇指侧摆
index=80.0, # 食指
middle=80.0, # 中指
ring=80.0, # 无名指
pinky=80.0, # 小指
)
hand.acceleration.set_accelerations(accel)
读取加速度
阻塞读取
from linkerbot import O6
from linkerbot.exceptions import TimeoutError
try:
data = hand.acceleration.get_blocking(timeout_ms=500)
print(f"拇指屈曲:{data.accelerations.thumb_flex}")
print(f"全部加速度:{data.accelerations.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.acceleration.get_snapshot()
if data:
print(f"加速度:{data.accelerations.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.o6 import SensorSource, AccelerationEvent
hand.start_polling({SensorSource.ACCELERATION: 0.1})
for event in hand.stream():
match event:
case AccelerationEvent(data=data):
print(f"加速度:{data.accelerations.to_list()}")
hand.stop_polling()
hand.stop_stream()
deg/s² 单位转换
O6Acceleration 支持与物理单位 deg/s²(度每秒平方)之间的转换。
转换为 deg/s²
from linkerbot.hand.o6 import O6Acceleration
accel = O6Acceleration(50.0, 50.0, 50.0, 50.0, 50.0, 50.0)
deg_s2_values = accel.to_deg_per_sec2()
print(deg_s2_values) # [1104.9, 1104.9, 1104.9, 1104.9, 1104.9, 1104.9]
从 deg/s² 创建
from linkerbot.hand.o6 import O6Acceleration
# 设置所有关节电机加速度为 1000 deg/s²
accel = O6Acceleration.from_deg_per_sec2([1000.0] * 6)
# 设置不同的加速度
accel = O6Acceleration.from_deg_per_sec2(
[
1500.0, # 拇指屈曲
1200.0, # 拇指侧摆
1800.0, # 食指
1800.0, # 中指
1800.0, # 无名指
1800.0, # 小指
]
)
完整示例
from linkerbot import O6
from linkerbot.hand.o6 import O6Acceleration
with O6(side="left", interface_name="can0") as hand:
# 设置较低加速度(平滑运动)
hand.acceleration.set_accelerations([30.0] * 6)
# 使用 deg/s² 单位设置
accel = O6Acceleration.from_deg_per_sec2([1000.0] * 6)
hand.acceleration.set_accelerations(accel)
# 读取当前加速度
data = hand.acceleration.get_blocking(timeout_ms=500)
print(f"当前加速度:{data.accelerations.to_list()}")
print(f"对应 deg/s²: {data.accelerations.to_deg_per_sec2()}")
扭矩控制
通过 hand.torque 控制和读取 O6 灵巧手的 6 个关节电机扭矩。
- 扭矩范围: 0-100(无量纲百分比)
- 最大电流: 1657.5 mA(对应 100)
设置扭矩
from linkerbot import O6
from linkerbot.hand.o6 import O6Torque
# 使用列表
hand.torque.set_torques([50.0, 30.0, 60.0, 60.0, 60.0, 60.0])
# 使用 O6Torque 对象
torques = O6Torque(
thumb_flex=50.0, # 拇指屈曲
thumb_abd=30.0, # 拇指侧摆
index=60.0, # 食指
middle=60.0, # 中指
ring=60.0, # 无名指
pinky=60.0, # 小指
)
hand.torque.set_torques(torques)
# 使用 mA 单位
torques = O6Torque.from_milliamps([800.0, 800.0, 1000.0, 1000.0, 1000.0, 1000.0])
hand.torque.set_torques(torques)
读取扭矩
阻塞读取
from linkerbot.exceptions import TimeoutError
try:
data = hand.torque.get_blocking(timeout_ms=500)
print(f"拇指屈曲:{data.torques.thumb_flex}")
print(f"全部扭矩:{data.torques.to_list()}")
except TimeoutError:
print("读取超时")
缓存读取
data = hand.torque.get_snapshot()
if data:
print(f"扭矩:{data.torques.to_list()}")
print(f"时间戳:{data.timestamp}")
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.o6 import SensorSource, TorqueEvent
hand.start_polling({SensorSource.TORQUE: 0.1})
for event in hand.stream():
match event:
case TorqueEvent(data=data):
print(f"扭矩:{data.torques.to_list()}")
hand.stop_polling()
hand.stop_stream()
mA 单位转换
O6 支持扭矩值与 mA 电流单位的相互转换。
从 mA 创建
from linkerbot.hand.o6 import O6Torque
# 从 mA 值创建(范围 0-1657.5 mA)
torques = O6Torque.from_milliamps([800.0, 800.0, 1000.0, 1000.0, 1200.0, 1200.0])
print(f"归一化值:{torques.to_list()}") # 转换为 0-100 范围
转换为 mA
torques = O6Torque(50.0, 50.0, 50.0, 50.0, 50.0, 50.0)
ma_values = torques.to_milliamps()
print(f"mA 值:{ma_values}") # [828.75, 828.75, ...]
完整示例
from linkerbot import O6
from linkerbot.hand.o6 import O6Torque
with O6(side="left", interface_name="can0") as hand:
# 设置扭矩
hand.torque.set_torques([50.0, 50.0, 50.0, 50.0, 50.0, 50.0])
# 读取当前扭矩
data = hand.torque.get_blocking(timeout_ms=500)
print(f"当前扭矩:{data.torques.to_list()}")
print(f"对应 mA: {data.torques.to_milliamps()}")
# 使用 mA 单位设置
torques = O6Torque.from_milliamps([1000.0] * 6)
hand.torque.set_torques(torques)
温度读取
通过 hand.temperature 读取 O6 灵巧手 6 个关节电机的温度数据(单位:°C)。
温度属性
| 属性 | 说明 |
|---|---|
thumb_flex | 拇指屈曲关节电机 |
thumb_abd | 拇指侧摆关节电机 |
index | 食指关节电机 |
middle | 中指关节电机 |
ring | 无名指关节电机 |
pinky | 小指关节电机 |
读取温度
阻塞读取
data = hand.temperature.get_blocking(timeout_ms=500)
print(f"拇指温度:{data.temperatures.thumb_flex}°C")
参数
timeout_ms: 超时时间(毫秒),默认 100
返回值
TemperatureData: 包含temperatures和timestamp
异常
TimeoutError: 超时未响应
缓存读取
非阻塞读取最近一次缓存的温度数据。
data = hand.temperature.get_snapshot()
if data:
print(f"温度:{data.temperatures.to_list()}")
返回值
TemperatureData或None(无缓存数据时)
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.o6 import SensorSource, TemperatureEvent
hand.start_polling({SensorSource.TEMPERATURE: 0.1})
for event in hand.stream():
match event:
case TemperatureEvent(data=data):
print(f"温度:{data.temperatures.to_list()}")
hand.stop_polling()
hand.stop_stream()
示例
读取所有关节电机温度
from linkerbot import O6
with O6(side="left", interface_name="can0") as hand:
data = hand.temperature.get_blocking(timeout_ms=500)
# 按属性访问
print(f"拇指屈曲:{data.temperatures.thumb_flex}°C")
print(f"食指:{data.temperatures.index}°C")
# 转换为列表
temps = data.temperatures.to_list()
print(f"全部温度:{temps}")
# 索引访问
print(f"第一个关节电机:{data.temperatures[0]}°C")
温度监控
from linkerbot import O6
from linkerbot.hand.o6 import SensorSource, TemperatureEvent
with O6(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.TEMPERATURE: 0.1})
try:
for event in hand.stream():
match event:
case TemperatureEvent(data=data):
for i, temp in enumerate(data.temperatures.to_list()):
if temp > 60.0:
print(f"警告:关节电机 {i} 过热 ({temp}°C)")
except KeyboardInterrupt:
pass
finally:
hand.stop_polling()
hand.stop_stream()
力传感器
O6 灵巧手配备 5 个手指的力传感器(thumb, index, middle, ring, pinky),支持阻塞读取和缓存读取两种模式。
概述
通过 hand.force_sensor 访问力传感器功能:
from linkerbot import O6
with O6(side="left", interface_name="can0") as hand:
data = hand.force_sensor.get_blocking()
数据结构
ForceSensorData - 单个手指的传感器数据:
values: 形状 (10, 4) 的 NumPy 数组(uint8)timestamp: Unix 时间戳
AllFingersData - 全部 5 个手指的数据:
thumb,index,middle,ring,pinky: 各手指的ForceSensorData
读取数据
阻塞读取
data = hand.force_sensor.get_blocking(timeout_ms=1000)
print(data.thumb.values) # 拇指数据
print(data.index.values) # 食指数据
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
timeout_ms | float | 1000 | 超时时间(毫秒) |
异常: TimeoutError(超时)、ValidationError(参数无效)
缓存读取
获取最近一次接收的数据(非阻塞):
data = hand.force_sensor.get_snapshot()
if data:
print(f"拇指:{data.thumb.values[0]}")
print(f"食指:{data.index.values[0]}")
返回值: AllFingersData 或 None(任一手指无数据时)
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.o6 import SensorSource, ForceSensorEvent
hand.start_polling({SensorSource.FORCE_SENSOR: 0.1})
for event in hand.stream():
match event:
case ForceSensorEvent(data=data):
print(data.thumb.values)
hand.stop_polling()
hand.stop_stream()
示例
阻塞读取所有手指
from linkerbot import O6
with O6(side="left", interface_name="can0") as hand:
data = hand.force_sensor.get_blocking(timeout_ms=1000)
print(f"拇指:{data.thumb.values.shape}")
print(f"食指:{data.index.values.shape}")
流式采集指定时长
import time
from linkerbot import O6
from linkerbot.hand.o6 import SensorSource, ForceSensorEvent
with O6(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.FORCE_SENSOR: 0.05})
start = time.time()
try:
for event in hand.stream():
match event:
case ForceSensorEvent(data=data):
print(f"拇指:{data.thumb.values[0]}")
if time.time() - start > 5: # 采集 5 秒
break
finally:
hand.stop_polling()
hand.stop_stream()
故障管理
O6 灵巧手的故障检测功能。
概述
通过 hand.fault 访问故障管理功能:
- 读取故障状态(阻塞式、缓存)
故障码表
| 故障码 | 值 | 说明 |
|---|---|---|
NONE | 0 | 无故障 |
VOLTAGE_ABNORMAL | 1 | 过压/欠压 |
ENCODER_ABNORMAL | 2 | 磁编码器异常 |
OVERTEMPERATURE | 4 | 温度过热 |
OVERCURRENT | 8 | 电流过流 |
OVERLOAD | 32 | 负载过载 |
读取故障
阻塞式读取
data = hand.fault.get_blocking(timeout_ms=500)
参数:
timeout_ms:超时时间(毫秒),默认 100
返回值:FaultData 对象,包含:
faults:O6Fault故障数据timestamp:时间戳
异常:
TimeoutError:超时未收到响应
缓存读取
data = hand.fault.get_snapshot()
返回最近缓存的故障数据,无数据时返回 None。
流式读取
通过顶层 hand.stream() 统一接收所有传感器事件:
from linkerbot.hand.o6 import SensorSource, FaultEvent
hand.start_polling({SensorSource.FAULT: 0.2})
for event in hand.stream():
match event:
case FaultEvent(data=data):
if data.faults.has_any_fault():
for code in data.faults.to_list():
if code.has_fault():
print(code.get_fault_names())
hand.stop_polling()
hand.stop_stream()
故障数据
O6Fault 属性
| 属性 | 说明 |
|---|---|
thumb_flex | 拇指弯曲 |
thumb_abd | 拇指侧摆 |
index | 食指 |
middle | 中指 |
ring | 无名指 |
pinky | 小指 |
O6Fault 方法
# 检查是否有任何故障
faults.has_any_fault() # -> bool
# 转为列表
faults.to_list() # -> list[FaultCode]
# 索引访问
faults[0] # thumb_flex
FaultCode 方法
# 检查单个关节电机是否有故障
faults.thumb_flex.has_fault() # -> bool
# 获取故障名称
faults.thumb_flex.get_fault_names() # -> list[str]
示例
检查故障状态
from linkerbot import O6
with O6(side="left", interface_name="can0") as hand:
# 读取故障状态
data = hand.fault.get_blocking(timeout_ms=500)
if data.faults.has_any_fault():
print("检测到故障:")
if data.faults.thumb_flex.has_fault():
print(f" 拇指弯曲:{data.faults.thumb_flex.get_fault_names()}")
if data.faults.index.has_fault():
print(f" 食指:{data.faults.index.get_fault_names()}")
else:
print("无故障")
持续监控
from linkerbot import O6
from linkerbot.hand.o6 import SensorSource, FaultEvent
with O6(side="left", interface_name="can0") as hand:
hand.start_polling({SensorSource.FAULT: 0.2})
try:
for event in hand.stream():
match event:
case FaultEvent(data=data):
if data.faults.has_any_fault():
for code in data.faults.to_list():
if code.has_fault():
print(code.get_fault_names())
except KeyboardInterrupt:
pass
finally:
hand.stop_polling()
hand.stop_stream()
版本信息
获取 O6 灵巧手的版本信息。
概述
通过 hand.version 访问版本管理功能:
- 获取设备完整信息(序列号、PCB/固件/机械版本)
获取设备信息
hand.version.get_device_info() -> DeviceInfo
返回 DeviceInfo 对象,包含:
| 属性 | 类型 | 说明 |
|---|---|---|
serial_number | str | 设备序列号 |
pcb_version | Version | PCB 硬件版本 |
firmware_version | Version | 固件版本 |
mechanical_version | Version | 机械结构版本 |
timestamp | float | 获取时间(Unix 时间戳) |
Version 对象包含 major、minor、patch 属性,字符串格式为 V{major}.{minor}.{patch}。
异常:
TimeoutError:请求超时
示例
读取设备信息
from linkerbot import O6
hand = O6(side="left", interface_name="can0")
info = hand.version.get_device_info()
print(f"序列号:{info.serial_number}")
print(f"PCB 版本:{info.pcb_version}")
print(f"固件版本:{info.firmware_version}")
print(f"机械版本:{info.mechanical_version}")
hand.close()
A7 Lite 机械臂
A7 Lite 机械臂文档。
快速开始
from linkerbot import A7lite
with A7lite(
side="left",
interface_name="can0",
) as arm:
arm.enable() # 使能机械臂所有 7 个关节电机
arm.home() # 将机械臂移动到零位
arm.disable() # 失能机械臂所有 7 个关节电机
构造参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
side | "left" | "right" | 必填 | 左臂或右臂 |
interface_name | str | 必填 | CAN 接口名,如 "can0" |
interface_type | str | "socketcan" | CAN 接口类型 |
tcp_offset | list[float] | [0.0, 0.0, 0.0] | TCP 偏移量 |
world_frame | "urdf" | "maestro" | "urdf" | 世界坐标系约定 |
构造时会自动检查所有 7 个电机是否在线。
前置依赖: A7 Lite 依赖 Pinocchio 进行运动学计算,需要安装 kinetix 可选依赖:
pip install linkerbot-py[kinetix]
Windows 用户:Pinocchio 不支持 pip 安装,请使用
conda install pinocchio -c conda-forge。
异常:
ImportError: 未安装kinetix可选依赖(Pinocchio)ValueError:side或world_frame参数非法StateError: 电机未响应 CAN 总线 / 上报数据超时
URDF 文件路径:
src/linkerbot/arm/kinetix/urdf/a7_lite__left.urdf和a7_lite__right.urdf
坐标系说明
A7 Lite 支持两种世界坐标系,通过 world_frame 参数选择。
URDF 坐标系(默认)
URDF 模型文件的坐标系,原点位于机器人左右臂肩关节连线中点处,如图所示:
- X 轴:机器人背后指向机器人前方为 X 轴正方向
- Y 轴:机器人视角左手边(面对机器人站立右手边)为 Y 轴正方向
- Z 轴:由右手定则确定上方为 Z 轴正方向
- Rx, Ry, Rz:表示按照 Z,Y,X 轴顺序,逆时针为正进行旋转
Maestro 坐标系
Maestro 坐标系,相对 URDF 坐标系进行了两步变换,如图所示:
- 原点平移:将世界坐标原点从基座中心平移到 Joint 2(肩部第二关节)中心处
- Z 轴旋转 90°:在新原点将 URDF 坐标轴绕 Z 轴逆时针旋转 +90°
变换后:
- X 轴:机器人视角右手边(面对机器人站立左手边)为 X 轴正方向
- Y 轴:机器人背后指向机器人前方,为 Y 轴正方向
- Z 轴:由右手定则确定上方为 Z 轴正方向
注意:这两种坐标的定义方式主要影响
move_p和move_l涉及到Pose的部分,不影响move_j控制
关节定义
零位定义
所有关节角度为 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 时,机械臂应当处于“自然下垂“姿态(关节相互垂直);调用 home() 可回到零位,如下图所示:
角度正方向定义
角度正方向遵从右手定则:以右手拇指指向旋转轴正方向,四指弯曲方向为正角度增大方向。
由于 URDF 中各关节轴可能为负方向(如 0 -1 0),实际正角度方向与直觉相反:
- 轴为
0 -1 0(Y 轴负方向):发送更大角度时,关节绕 Y 轴负方向旋转,即 Y 轴正方向指向自己时顺时针旋转。 - 轴为
-1 0 0(X 轴负方向):发送更大角度增大时,关节绕 X 轴负方向旋转,即 X 轴正方向指向自己时顺时针旋转。 - 轴为
0 0 1(Z 轴正方向):发送更大角度时,关节绕 Z 轴正方向旋转,即从上方看为逆时针。
A7 Lite 左臂关节一览(URDF 坐标系)
| 索引 | 关节名称 | 物理含义 | 旋转轴(URDF) | 角度范围(rad) |
|---|---|---|---|---|
| 0 | L1_JOINT | 肩部俯仰 | Y 轴负方向(0 -1 0) | [-2.18, 3.75] |
| 1 | L2_JOINT | 肩部横滚 | X 轴负方向(-1 0 0) | [-3.33, 0.19] |
| 2 | L3_JOINT | 肩部偏转(上臂扭转) | Z 轴正方向(0 0 1) | [-2.26, 2.26] |
| 3 | L4_JOINT | 肘部俯仰 | Y 轴负方向(0 -1 0) | [-0.78, 1.74] |
| 4 | L5_JOINT | 肘部偏转(前臂扭转) | Z 轴正方向(0 0 1) | [-2.96, 2.96] |
| 5 | L6_JOINT | 腕部俯仰 | Y 轴负方向(0 -1 0) | [-1.57, 1.57] |
| 6 | L7_JOINT | 腕部横滚 | X 轴负方向(-1 0 0) | [-1.57, 1.57] |
A7 Lite 右臂关节一览(URDF 坐标系)
| 索引 | 关节名称 | 物理含义 | 旋转轴(URDF) | 角度范围(rad) |
|---|---|---|---|---|
| 0 | R1_JOINT | 肩部俯仰 | Y 轴正方向(0 1 0) | [-3.75, 2.18] |
| 1 | R2_JOINT | 肩部横滚 | X 轴负方向(-1 0 0) | [-0.19, 3.33] |
| 2 | R3_JOINT | 肩部偏转(上臂扭转) | Z 轴正方向(0 0 1) | [-2.26, 2.26] |
| 3 | R4_JOINT | 肘部俯仰 | Y 轴正方向(0 1 0) | [-1.74, 0.78] |
| 4 | R5_JOINT | 肘部偏转(前臂扭转) | Z 轴正方向(0 0 1) | [-2.96, 2.96] |
| 5 | R6_JOINT | 腕部俯仰 | Y 轴正方向(0 1 0) | [-1.57, 1.57] |
| 6 | R7_JOINT | 腕部横滚 | X 轴负方向(-1 0 0) | [-1.57, 1.57] |
Pose(TCP 位姿)表示
Pose 表示 TCP(Tool Center Point,工具中心点)位姿,是一个 Pydantic 模型,包含 6 个字段:
from linkerbot import Pose
pose = Pose(x=0.3, y=0.1, z=0.5, rx=0.0, ry=0.5, rz=0.0)
| 字段 | 含义 | 单位 |
|---|---|---|
x, y, z | 空间坐标位置 | 米(m) |
rx | 绕 X 轴旋转(Roll),范围 [-π, π] | 弧度(rad) |
ry | 绕 Y 轴旋转(Pitch),范围 [-π/2, π/2] | 弧度(rad) |
rz | 绕 Z 轴旋转(Yaw),范围 [-π, π] | 弧度(rad) |
- 姿态使用外旋 ZYX 欧拉角表示:先绕世界坐标轴的 Z 轴旋转 rz,再绕世界坐标轴的 Y 轴旋转 ry,最后绕世界坐标轴的 X 轴旋转 rx。
生命周期管理
API 参考
| 方法 | 说明 |
|---|---|
enable() | 使能所有 7 个关节电机,会导致短暂失能。 |
disable() | 失能所有 7 个关节电机。 |
emergency_stop() | 紧急停止机械臂。此方法会阻塞约 2 秒。 |
calibrate_zero() | 将当前位置设为零位。需要机械臂处于失能状态。 |
reset_error() | 重置所有关节的错误状态。 |
close() | 关闭连接:等待运动完成后停止 CAN 调度器。使用 with 语句时会自动调用。 |
注意事项
- 调用
arm.enable()使能时会导致机械臂短暂失能。 - 设置零位需要机械臂处于失能状态。
emergency_stop()是阻塞调用,会等待约 2 秒。
控制模式
当前仅支持 PP(Profile Position)模式,调用 enable() 时会自动设置。
ControlMode 枚举可从 linkerbot 导入:
from linkerbot import ControlMode
CAN 总线
interface_name 指定 CAN 接口名称(如 "can0"),interface_type 指定接口类型(默认 "socketcan")。
更多信息(包括 Windows 用法)请参阅 CAN 总线配置。
功能模块
| 模块 | 文档 |
|---|---|
| 运动控制(move_j/move_p/move_l) | motion.md |
| 运动学(FK/IK/关节限位) | kinematics.md |
| 状态读取(角度/速度/扭矩/温度) | state.md |
| 参数配置(速度/加速度/PID) | config.md |
运动控制
回零
将所有关节移动到零位([0.0] * 7)。
from linkerbot import A7lite
with A7lite(side="left", interface_name="can0") as arm:
arm.enable()
arm.home() # 阻塞模式
arm.home(blocking=False) # 非阻塞模式
参数:
blocking: 是否阻塞等待运动完成,默认True。
move_j
通过发送每个关节的目标角度,控制机械臂运动。
from linkerbot import A7lite
with A7lite(side="left", interface_name="can0") as arm:
arm.enable()
arm.move_j([0.0, 0.0, 0.0, 1.57, 0.0, 0.0, 0.0], blocking=True)
参数:
target_joints: 7 个关节的目标角度(rad),类型list[float]blocking: 是否阻塞直到运动完成,默认True,类型bool
异常:
StateError: 机械臂运动中再次调用时ValidationError: 目标角度超出关节限位时
move_p
通过发送机械臂 TCP 6D 位姿(x, y, z, rx, ry, rz),控制机械臂运动。通过逆运动学求解目标关节角度后执行关节运动,末端路径不保证为直线。
from linkerbot import A7lite
from linkerbot import Pose
with A7lite(side="left", interface_name="can0") as arm:
arm.enable()
target = Pose(x=0.3, y=0.1, z=0.5, rx=0.0, ry=0.5, rz=0.0)
arm.move_p(target)
参数:
target_pose: 目标 TCP 位姿,类型Posecurrent_angles: IK 初始猜测关节角度,类型list[float] | None,None时从电机读取,blocking: 是否阻塞直到运动完成,类型bool,默认True,
异常:
StateError: 机械臂运动中再次调用时RuntimeError: IK 求解失败时
move_l
通过发送机械臂 TCP 6D 位姿(x, y, z, rx, ry, rz),控制机械臂直线运动。末端沿笛卡尔空间直线运动,位置使用梯形速度规划,姿态使用球面线性插值(Slerp)。move_l 始终阻塞直到运动完成,无 blocking 参数。
from linkerbot import A7lite, Pose
with A7lite(side="left", interface_name="can0") as arm:
arm.enable()
target = Pose(x=0.0, y=0.33, z=-0.25, rx=1.85, ry=0.0, rz=-1.57)
arm.move_l(target, max_velocity=0.1, acceleration=0.2)
参数:
target_pose: 目标 TCP 位姿,类型Posemax_velocity: TCP 线性运动梯形速度规划的最大速度(m/s),默认0.05,范围(0, 0.4]max_angular_velocity: TCP 旋转运动梯形速度规划的最大角速度(rad/s),默认0.3,范围(0, 3.0]acceleration: TCP 线性运动梯形速度规划的最大加速度(m/s²),默认0.1,范围(0, 1.0]angular_acceleration: TCP 旋转运动梯形速度规划的最大角加速度(rad/s²),默认0.1,范围(0, 1.0]current_pose: 起始 TCP 位姿,None时由current_angles或电机读取计算,类型Pose | Nonecurrent_angles: 起始关节角度,None时从电机读取,类型list[float] | None
异常:
StateError: 机械臂运动中再次调用时ValidationError: 参数超出范围时RuntimeError: 路径点 IK 求解失败时
运动状态检测
返回机械臂当前是否处于运动状态。
if arm.is_moving():
print("正在运动中...")
返回值:
bool: 是否在运动中
等待运动完成
阻塞当前线程,直到运动计时器归零。通常配合 blocking=False 使用。
arm.move_j([0.0, 0.0, 0.0, 1.57, 0.0, 0.0, 0.0], blocking=False)
# ... 执行其他操作 ...
arm.wait_motion_done() # 等待运动结束
示例
非阻塞运动与等待
from linkerbot import A7lite, Pose
with A7lite(side="left", interface_name="can0") as arm:
arm.enable()
arm.home()
# 非阻塞 move_j:发送后立即返回,可执行其他操作
arm.move_j([0.0, 0.0, 0.0, 1.57, 0.0, 0.0, 0.0], blocking=False)
print("move_j 已发送,正在运动中...")
arm.wait_motion_done() # 等待运动完成
# 阻塞 move_p:调用直接等到运动完成,无需 wait_motion_done
target = Pose(x=0.0, y=0.33, z=-0.25, rx=1.85, ry=0.0, rz=-1.57)
arm.move_p(target)
# move_l 始终阻塞
target = Pose(x=0.1, y=0.33, z=-0.2, rx=1.85, ry=0.0, rz=-1.57)
arm.move_l(target, max_velocity=0.1, acceleration=0.2)
arm.home()
运动学
正运动学
根据关节角度计算 TCP 位姿。
pose = arm.forward_kinematics([0.0, 0.0, 0.0, 1.57, 0.0, 0.0, 0.0])
print(f"TCP: ({pose.x:.3f}, {pose.y:.3f}, {pose.z:.3f})")
参数:
angles: 7 个关节角度(rad),类型list[float]
返回值:
Pose: TCP 位姿
逆运动学
根据 TCP 位姿求解关节角度。
from linkerbot import Pose
target = Pose(x=0.0, y=0.33, z=-0.25, rx=1.85, ry=0.0, rz=-1.57)
angles = arm.inverse_kinematics(target)
print(f"关节角度:{angles}")
参数:
pose: 目标 TCP 位姿,类型Posecurrent_angles: IK 初始猜测关节角度,类型list[float] | None,None时从电机读取
返回值:
list[float]: 7 个关节角度(rad)
关节限位
# 获取当前关节限位
limits = arm.get_joint_limits()
print(limits) # [(lower0, upper0), (lower1, upper1), ...]
# 设置自定义关节限位
arm.set_joint_limits([(-1.0, 1.0)] * 7)
状态读取
完整状态快照
一次性读取完整的机械臂状态,包含 TCP 位姿、所有关节的角度、速度、加速度、扭矩和温度。
from linkerbot import A7lite
with A7lite(side="left", interface_name="can0") as arm:
arm.enable()
state = arm.get_state()
print(state.pose)
print(f"第一个关节实际角度:{state.joint_angles[0].angle:.4f} rad")
返回值
State: 包含pose、joint_angles、joint_control_angles、joint_velocities、joint_control_velocities、joint_control_acceleration、joint_torques、joint_temperatures
State 数据模型
| 属性 | 说明 |
|---|---|
pose | TCP 位姿(Pose) |
joint_angles | 7 个关节的实际角度(list[AngleState]) |
joint_control_angles | 7 个关节的控制角度(list[AngleState]) |
joint_velocities | 7 个关节的实际速度(list[VelocityState]) |
joint_control_velocities | 7 个关节的控制速度(list[VelocityState]) |
joint_control_acceleration | 7 个关节的控制加速度(list[AccelerationState]) |
joint_torques | 7 个关节的实际扭矩(list[TorqueState]) |
joint_temperatures | 7 个关节的温度(list[TemperatureState]) |
各子状态模型(AngleState、VelocityState 等)均包含值字段和 timestamp(UNIX 时间戳,秒)。
子状态模型字段
| 模型 | 值字段 | 时间戳字段 |
|---|---|---|
AngleState | .angle | .timestamp |
VelocityState | .velocity | .timestamp |
AccelerationState | .acceleration | .timestamp |
TorqueState | .torque | .timestamp |
TemperatureState | .temperature | .timestamp |
单项状态读取
除 get_state() 外,以下方法分别返回单一类型的状态数据,均返回 7 个关节的列表(get_pose() 除外)。
from linkerbot import A7lite
with A7lite(side="left", interface_name="can0") as arm:
arm.enable()
angles = arm.get_angles()
velocities = arm.get_velocities()
temps = arm.get_temperatures()
pose = arm.get_pose()
print(f"第二个关节实际角度:{angles[1]:.4f} rad")
print(f"第三个关节实际速度:{velocities[2]:.4f} rad/s")
print(f"第四个关节实际温度:{temps[3]:.1f} °C")
print(f"TCP: ({pose.x:.3f}m, {pose.y:.3f}m, {pose.z:.3f}m)")
返回值
| 方法 | 返回值 | 单位 |
|---|---|---|
get_angles() | 7 个关节的实际角度 | rad |
get_control_angles() | 7 个关节的控制角度 | rad |
get_velocities() | 7 个关节的实际速度 | rad/s |
get_control_velocities() | 7 个关节的控制速度 | rad/s |
get_control_acceleration() | 7 个关节的控制加速度 | rad/s² |
get_torques() | 7 个关节的实际扭矩 | Nm |
get_temperatures() | 7 个关节的温度 | °C |
get_pose() | TCP 位姿 | m, rad |
传感器数据
A7 Lite 使用电机主动上报模式,构造时自动启动。
| 数据类型 | 单位 | 上报频率 |
|---|---|---|
| 角度 | rad | 由电机上报周期决定 |
| 速度 | rad/s | 同上 |
| 扭矩 | Nm | 同上 |
| 温度 | °C | 同上 |
参数配置
速度与加速度限制
设置 7 个关节的速度和加速度限制,影响 move_j、move_p 等运动。
from linkerbot import A7lite
with A7lite(side="left", interface_name="can0") as arm:
arm.enable()
# 将所有关节速度限制设为 2.0 rad/s
arm.set_velocities([2.0] * 7)
# 分别设置各关节加速度限制
arm.set_accelerations([10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0])
| 方法 | 参数 | 范围 | 说明 |
|---|---|---|---|
set_velocities(velocities) | 7 个关节的速度上限(rad/s) | [0.0, 50.0] | 默认 0.5 |
set_accelerations(accelerations) | 7 个关节的加速度上限(rad/s²) | [1.0, 50.0] | 默认 10.0 |
异常:ValidationError(必须提供恰好 7 个值,或值超出范围时)
PID 参数设置
设置 7 个关节的 PID 参数。
from linkerbot import A7lite
with A7lite(side="left", interface_name="can0") as arm:
arm.set_position_kps([100.0] * 7)
arm.set_velocity_kps([0.5] * 7)
arm.set_velocity_kis([0.01] * 7)
arm.set_velocity_filt_gains([0.8] * 7)
| 方法 | 参数 | 说明 |
|---|---|---|
set_position_kps(kps) | 7 个关节的位置 Kp 值 | 位置环比例增益 |
set_velocity_kps(kps) | 7 个关节的速度 Kp 值 | 速度环比例增益 |
set_velocity_kis(kis) | 7 个关节的速度 Ki 值 | 速度环积分增益 |
set_velocity_filt_gains(gains) | 7 个关节的速度滤波器增益 | 增益越大滤波越强,响应越滞后 |
参数速查
| 参数类型 | 默认值 | 范围 |
|---|---|---|
| 速度限制 | 0.5 rad/s | [0.0, 50.0] rad/s |
| 加速度限制 | 10.0 rad/s² | [1.0, 50.0] rad/s² |
move_l 最大平移速度 | 0.05 m/s | (0, 0.4] m/s |
move_l 最大角速度 | 0.3 rad/s | (0, 3.0] rad/s |
move_l 平移加速度 | 0.1 m/s² | (0, 1.0] m/s² |
move_l 角加速度 | 0.1 rad/s² | (0, 1.0] rad/s² |
示例
设置 PID 参数、速度和加速度限制
from linkerbot import A7lite
with A7lite(side="left", interface_name="can0") as arm:
arm.set_position_kps([80.0] * 7)
arm.set_velocity_kps([0.4] * 7)
arm.set_velocity_kis([0.008] * 7)
arm.set_velocity_filt_gains([0.9] * 7)
arm.set_velocities([1.0] * 7)
arm.set_accelerations([5.0] * 7)
A7 机械臂
A7 是一款自研 7 自由度机械臂。
快速开始
from linkerbot import A7
with A7(
side="left",
interface_name="can0",
) as arm:
arm.enable() # 使能机械臂所有 7 个关节电机
arm.home() # 将机械臂移动到零位
arm.disable() # 失能机械臂所有 7 个关节电机
构造参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
side | "left" | "right" | 必填 | 左臂或右臂 |
interface_name | str | 必填 | CAN 接口名,如 "can0" |
interface_type | str | "socketcan" | CAN 接口类型 |
tcp_offset | list[float] | [0.0, 0.0, 0.0] | TCP 偏移量 |
world_frame | "urdf" | "maestro" | "urdf" | 世界坐标系约定 |
构造时会自动检查所有 7 个电机是否在线。
前置依赖: A7 依赖 Pinocchio 进行运动学计算,需要安装 kinetix 可选依赖:
pip install linkerbot-py[kinetix]
Windows 用户:Pinocchio 不支持 pip 安装,请使用
conda install pinocchio -c conda-forge。
异常:
ImportError: 未安装kinetix可选依赖(Pinocchio)ValueError:side或world_frame参数非法StateError: 电机未响应 CAN 总线 / 初始数据读取失败
URDF 文件路径:
src/linkerbot/arm/kinetix/urdf/a7__left.urdf和a7__right.urdf
坐标系说明
A7 支持两种世界坐标系,通过 world_frame 参数选择。
URDF 坐标系(默认)
URDF 模型文件的坐标系,原点位于机械臂基座(Base_Link)中心,如图所示:
- X 轴:机器人背后指向机器人前方为 X 轴正方向
- Y 轴:机器人视角左手边(面对机器人站立右手边)为 Y 轴正方向
- Z 轴:由右手定则确定上方为 Z 轴正方向
- Rx, Ry, Rz:表示按照 Z,Y,X 轴顺序,逆时针为正进行旋转
Maestro 坐标系
Maestro 坐标系,相对 URDF 坐标系进行了两步变换,如图所示:
- 原点平移:将世界坐标原点从基座中心平移到 Joint 2(肩部第二关节)中心处
- Z 轴旋转 90°:在新原点将 URDF 坐标轴绕 Z 轴逆时针旋转 +90°
变换后:
- X 轴:机器人视角右手边(面对机器人站立左手边)为 X 轴正方向
- Y 轴:机器人背后指向机器人前方,为 Y 轴正方向
- Z 轴:由右手定则确定上方为 Z 轴正方向
注意:这两种坐标的定义方式主要影响
move_p和move_l涉及到Pose的部分,不影响move_j控制
关节定义
零位定义
所有关节角度为 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 时,机械臂应当处于“自然下垂“姿态(关节相互垂直);调用 home() 可回到零位,如下图所示:
角度正方向定义
角度正方向遵从右手定则:以右手拇指指向旋转轴正方向,四指弯曲方向为正角度增大方向。
- 轴为
0 1 0(Y 轴正方向):角度增大时,从正 Y 轴看为逆时针旋转 - 轴为
1 0 0(X 轴正方向):角度增大时,从正 X 轴看为逆时针旋转 - 轴为
0 0 -1(Z 轴负方向):角度增大时,从上方看为顺时针旋转 - 轴为
0 -1 0(Y 轴负方向):角度增大时,从正 Y 轴看为顺时针旋转
A7 左臂关节一览(URDF 坐标系)
| 索引 | 关节名称 | 物理含义 | 旋转轴(URDF) | 角度范围(rad) |
|---|---|---|---|---|
| 0 | Left_Shoulder_Pitch_Joint | 肩部俯仰 | Y 轴正方向(0 1 0) | [-2.97, 1.05] |
| 1 | Left_Shoulder_Roll_Joint | 肩部横滚 | X 轴正方向(1 0 0) | [-0.35, 3.49] |
| 2 | Left_Shoulder_Yaw_Joint | 肩部偏转(上臂扭转) | Z 轴负方向(0 0 -1) | [-2.79, 2.79] |
| 3 | Left_Elbow_Pitch_Joint | 肘部俯仰 | Y 轴负方向(0 -1 0) | [-0.21, 2.355] |
| 4 | Left_Wrist_Yaw_Joint | 腕部偏转(前臂扭转) | Z 轴负方向(0 0 -1) | [-2.79, 2.79] |
| 5 | Left_Wrist_Pitch_Joint | 腕部俯仰 | Y 轴负方向(0 -1 0) | [-1.57, 1.57] |
| 6 | Left_Wrist_Roll_Joint | 腕部横滚 | X 轴正方向(1 0 0) | [-1.57, 1.57] |
A7 右臂关节一览(URDF 坐标系)
| 索引 | 关节名称 | 物理含义 | 旋转轴(URDF) | 角度范围(rad) |
|---|---|---|---|---|
| 0 | Right_Shoulder_Pitch_Joint | 肩部俯仰 | Y 轴负方向(0 -1 0) | [-1.05, 2.97] |
| 1 | Right_Shoulder_Roll_Joint | 肩部横滚 | X 轴正方向(1 0 0) | [-3.49, 0.35] |
| 2 | Right_Shoulder_Yaw_Joint | 肩部偏转(上臂扭转) | Z 轴负方向(0 0 -1) | [-2.79, 2.79] |
| 3 | Right_Elbow_Pitch_Joint | 肘部俯仰 | Y 轴负方向(0 -1 0) | [-0.21, 2.355] |
| 4 | Right_Wrist_Yaw_Joint | 腕部偏转(前臂扭转) | Z 轴负方向(0 0 -1) | [-2.79, 2.79] |
| 5 | Right_Wrist_Pitch_Joint | 腕部俯仰 | Y 轴正方向(0 1 0) | [-1.57, 1.57] |
| 6 | Right_Wrist_Roll_Joint | 腕部横滚 | X 轴正方向(1 0 0) | [-1.57, 1.57] |
Pose(TCP 位姿)表示
Pose 表示 TCP(Tool Center Point,工具中心点)位姿,是一个 Pydantic 模型,包含 6 个字段:
from linkerbot import Pose
pose = Pose(x=0.3, y=0.1, z=0.5, rx=0.0, ry=0.5, rz=0.0)
| 字段 | 含义 | 单位 |
|---|---|---|
x, y, z | 空间坐标位置 | 米(m) |
rx | 绕 X 轴旋转(Roll),范围 [-π, π] | 弧度(rad) |
ry | 绕 Y 轴旋转(Pitch),范围 [-π/2, π/2] | 弧度(rad) |
rz | 绕 Z 轴旋转(Yaw),范围 [-π, π] | 弧度(rad) |
- 姿态使用外旋 ZYX 欧拉角表示:先绕世界坐标轴的 Z 轴旋转 rz,再绕世界坐标轴的 Y 轴旋转 ry,最后绕世界坐标轴的 X 轴旋转 rx。
生命周期管理
API 参考
| 方法 | 说明 |
|---|---|
enable() | 使能所有 7 个关节电机,会导致短暂失能。 |
disable() | 失能所有 7 个关节电机。 |
emergency_stop() | 紧急停止机械臂,使机械臂锁定在当前位姿。 |
calibrate_zero() | 将当前位置设为零位。需要机械臂处于失能状态。 |
reset_error() | 重置所有关节的错误状态。 |
close() | 关闭连接:停止轮询、等待运动完成后停止 CAN 调度器。使用 with 语句时会自动调用。 |
save_params() | 将当前 PID 等参数保存到电机闪存,断电后仍生效。 |
注意事项
- 调用
arm.enable()使能时会导致机械臂短暂失能。 - 设置零位需要机械臂处于失能状态。
控制模式
当前仅支持 PP(Profile Position)模式,调用 enable() 时会自动设置。
ControlMode 枚举可从 linkerbot 导入:
from linkerbot import ControlMode
CAN 总线
interface_name 指定 CAN 接口名称(如 "can0"),interface_type 指定接口类型(默认 "socketcan")。
更多信息(包括 Windows 用法)请参阅 CAN 总线配置。
功能模块
| 模块 | 文档 |
|---|---|
| 运动控制(move_j/move_p/move_l) | motion.md |
| 运动学(FK/IK/关节限位) | kinematics.md |
| 状态读取(角度/速度/扭矩/温度) | state.md |
| 参数配置(速度/加速度/PID) | config.md |
运动控制
回零
将所有关节移动到零位。
from linkerbot import A7
with A7(side="left", interface_name="can0") as arm:
arm.enable()
arm.home() # 阻塞模式
arm.home(blocking=False) # 非阻塞模式
参数:
blocking: 是否阻塞等待运动完成,默认True。
move_j
通过发送每个关节的目标角度,控制机械臂运动。
from linkerbot import A7
with A7(side="left", interface_name="can0") as arm:
arm.enable()
arm.move_j([0.0, 0.0, 0.0, 1.57, 0.0, 0.0, 0.0])
参数:
target_joints: 7 个关节的目标角度(rad),类型list[float]blocking: 是否阻塞直到运动完成,默认True,类型bool
异常:
StateError: 机械臂运动中再次调用时ValidationError: 目标角度超出关节限位时
move_p
通过发送机械臂 TCP 6D 位姿(x, y, z, rx, ry, rz),控制机械臂运动。通过逆运动学求解目标关节角度后执行关节运动,末端路径不保证为直线。
from linkerbot import A7, Pose
with A7(side="left", interface_name="can0") as arm:
arm.enable()
target = Pose(x=0.0, y=0.33, z=-0.25, rx=1.85, ry=0.0, rz=-1.57)
arm.move_p(target)
参数:
target_pose: 目标 TCP 位姿,类型linkerbot.arm.Posecurrent_angles: IK 初始猜测关节角度,类型list[float] | None,None时从电机读取,blocking: 是否阻塞直到运动完成,类型bool,默认True,
异常:
StateError: 机械臂运动中再次调用时RuntimeError: IK 求解失败时
move_l
通过发送机械臂 TCP 6D 位姿(x, y, z, rx, ry, rz),控制机械臂直线运动。末端沿笛卡尔空间直线运动,位置使用梯形速度规划,姿态使用球面线性插值(Slerp)。move_l 始终阻塞直到运动完成,无 blocking 参数。
from linkerbot import A7, Pose
with A7(side="left", interface_name="can0") as arm:
arm.enable()
target = Pose(x=0.0, y=0.33, z=-0.25, rx=1.85, ry=0.0, rz=-1.57)
arm.move_l(target)
参数:
target_pose: 目标 TCP 位姿,类型Posemax_velocity: TCP 线性运动梯形速度规划的最大速度(m/s),默认0.05,范围(0, 0.4]max_angular_velocity: TCP 旋转运动梯形速度规划的最大角速度(rad/s),默认0.3,范围(0, 3.0]acceleration: TCP 线性运动梯形速度规划的最大加速度(m/s²),默认0.1,范围(0, 1.0]angular_acceleration: TCP 旋转运动梯形速度规划的最大角加速度(rad/s²),默认0.1,范围(0, 1.0]current_pose: 起始 TCP 位姿,None时由current_angles或电机读取计算,类型Pose | Nonecurrent_angles: 起始关节角度,None时从电机读取,类型list[float] | None
异常:
StateError: 机械臂运动中再次调用时ValidationError: 参数超出范围时RuntimeError: 路径点 IK 求解失败时
运动状态检测
返回机械臂当前是否处于运动状态。
if arm.is_moving():
print("正在运动中...")
返回值:
bool: 是否在运动中
等待运动完成
阻塞当前线程,直到运动计时器归零。通常配合 blocking=False 使用。
arm.move_j([0.0, 0.0, 0.0, 1.57, 0.0, 0.0, 0.0], blocking=False)
# ... 执行其他操作 ...
arm.wait_motion_done() # 等待运动结束
示例
非阻塞运动与等待
from linkerbot import A7, Pose
with A7(side="left", interface_name="can0") as arm:
arm.enable()
arm.home()
# 设置速度和加速度
arm.set_velocities([1.0] * 7)
arm.set_accelerations([10.0] * 7)
# 非阻塞 move_j:发送后立即返回,可执行其他操作
arm.move_j([0.0, 0.0, 0.0, 1.57, 0.0, 0.0, 0.0], blocking=False)
print("move_j 已发送,正在运动中...")
arm.wait_motion_done() # 等待运动完成
# 阻塞 move_p:调用直接等到运动完成,无需 wait_motion_done
target = Pose(x=0.0, y=0.33, z=-0.25, rx=1.85, ry=0.0, rz=-1.57)
arm.move_p(target)
# move_l 始终阻塞
target = Pose(x=0.1, y=0.33, z=-0.2, rx=1.85, ry=0.0, rz=-1.57)
arm.move_l(target)
arm.home()
运动学
正运动学
根据关节角度计算 TCP 位姿。
pose = arm.forward_kinematics([0.0, 0.0, 0.0, 1.57, 0.0, 0.0, 0.0])
print(f"TCP: ({pose.x:.3f}, {pose.y:.3f}, {pose.z:.3f})")
参数:
angles: 7 个关节角度(rad),类型list[float]
返回值:
Pose: TCP 位姿
逆运动学
根据 TCP 位姿求解关节角度。
from linkerbot import Pose
target = Pose(x=0.0, y=0.33, z=-0.25, rx=1.85, ry=0.0, rz=-1.57)
angles = arm.inverse_kinematics(target)
print(f"关节角度:{angles}")
参数:
pose: 目标 TCP 位姿,类型Posecurrent_angles: IK 初始猜测关节角度,类型list[float] | None,None时从电机读取
返回值:
list[float]: 7 个关节角度(rad)
关节限位
# 获取当前关节限位
limits = arm.get_joint_limits()
print(limits) # [(lower0, upper0), (lower1, upper1), ...]
# 设置自定义关节限位
arm.set_joint_limits([(-1.0, 1.0)] * 7)
状态读取
完整状态快照
一次性读取完整的机械臂状态,包含 TCP 位姿、所有关节的角度、速度、加速度、扭矩和温度。
from linkerbot import A7
with A7(side="left", interface_name="can0") as arm:
arm.enable()
state = arm.get_state()
print(state.pose)
print(f"第一个关节实际角度:{state.joint_angles[0].angle:.4f} rad")
返回值
State: 包含pose、joint_angles、joint_control_angles、joint_velocities、joint_control_velocities、joint_control_acceleration、joint_torques、joint_temperatures
State 数据模型
| 属性 | 说明 |
|---|---|
pose | TCP 位姿(Pose) |
joint_angles | 7 个关节的实际角度(list[AngleState]) |
joint_control_angles | 7 个关节的控制角度(list[AngleState]) |
joint_velocities | 7 个关节的实际速度(list[VelocityState]) |
joint_control_velocities | 7 个关节的控制速度(list[VelocityState]) |
joint_control_acceleration | 7 个关节的控制加速度(list[AccelerationState]) |
joint_torques | 7 个关节的实际扭矩(list[TorqueState]) |
joint_temperatures | 7 个关节的温度(list[TemperatureState]) |
各子状态模型均包含值字段和 timestamp(UNIX 时间戳,秒)。
子状态模型字段
| 模型 | 值字段 | 时间戳字段 |
|---|---|---|
AngleState | .angle | .timestamp |
VelocityState | .velocity | .timestamp |
AccelerationState | .acceleration | .timestamp |
TorqueState | .torque | .timestamp |
TemperatureState | .temperature | .timestamp |
单项状态读取
除 get_state() 外,以下方法分别返回单一类型的状态数据,均返回 7 个关节的列表(get_pose() 除外)。
from linkerbot import A7
with A7(side="left", interface_name="can0") as arm:
arm.enable()
angles = arm.get_angles()
velocities = arm.get_velocities()
temps = arm.get_temperatures()
pose = arm.get_pose()
print(f"第二个关节实际角度:{angles[1]:.4f} rad")
print(f"第三个关节实际速度:{velocities[2]:.4f} rad/s")
print(f"第四个关节实际温度:{temps[3]:.1f} °C")
print(f"TCP: ({pose.x:.3f}m, {pose.y:.3f}m, {pose.z:.3f}m)")
返回值
| 方法 | 返回值 | 单位 |
|---|---|---|
get_angles() | 7 个关节的实际角度 | rad |
get_control_angles() | 7 个关节的控制角度 | rad |
get_velocities() | 7 个关节的实际速度 | rad/s |
get_control_velocities() | 7 个关节的控制速度 | rad/s |
get_control_acceleration() | 7 个关节的控制加速度 | rad/s² |
get_torques() | 7 个关节的实际扭矩 | Nm |
get_temperatures() | 7 个关节的温度 | °C |
get_pose() | TCP 位姿 | m, rad |
传感器数据
A7 使用轮询模式采集传感器数据,get_*() 返回的是上一次轮询时的值。
| 数据类型 | 单位 | 默认轮询间隔 | 最大延迟 |
|---|---|---|---|
| 角度 | rad | 10 ms | ≈ 10 ms |
| 速度 | rad/s | 10 ms | ≈ 10 ms |
| 扭矩 | Nm | 50 ms | ≈ 50 ms |
| 温度 | °C | 1000 ms | ≈ 1000 ms |
如需更低延迟,可通过 start_polling() 方法自定义不同传感器的轮询间隔:
from linkerbot import A7
from linkerbot.arm.a7 import SensorType
with A7(side="left", interface_name="can0") as arm:
arm.start_polling(
{
SensorType.POSITION: 0.005, # 5 ms = 200 Hz
SensorType.VELOCITY: 0.005,
SensorType.TORQUE: 0.1,
SensorType.TEMPERATURE: 2.0,
}
)
arm.enable()
arm.home()
参数配置
速度与加速度限制
设置 7 个关节的速度和加速度限制。
from linkerbot import A7
with A7(side="left", interface_name="can0") as arm:
arm.enable()
# 将所有关节速度限制设为 1.0 rad/s
arm.set_velocities([1.0] * 7)
# 分别设置各关节加速度限制
arm.set_accelerations([10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0])
| 方法 | 参数 | 范围 | 说明 |
|---|---|---|---|
set_velocities(velocities) | 7 个关节的速度上限(rad/s) | [0.0, 50.0] | 默认 0.5 |
set_accelerations(accelerations) | 7 个关节的加速度上限(rad/s²) | [1.0, 50.0] | 默认 10.0,同时设置加速度和减速度 |
异常:ValidationError(必须提供恰好 7 个值,或值超出范围时)
PID 参数设置
设置 7 个关节的 PID 参数。
from linkerbot import A7
with A7(side="left", interface_name="can0") as arm:
arm.set_position_kps([100.0] * 7)
arm.set_velocity_kps([0.5] * 7)
arm.set_velocity_kis([0.01] * 7)
| 方法 | 参数 | 说明 |
|---|---|---|
set_position_kps(kps) | 7 个关节的位置 Kp 值 | 位置环比例增益 |
set_velocity_kps(kps) | 7 个关节的速度 Kp 值 | 速度环比例增益 |
set_velocity_kis(kis) | 7 个关节的速度 Ki 值 | 速度环积分增益 |
参数速查
| 参数类型 | 默认值 | 范围 |
|---|---|---|
| 速度限制 | 0.5 rad/s | [0.0, 50.0] rad/s |
| 加速度限制 | 10.0 rad/s² | [1.0, 50.0] rad/s² |
move_l 最大平移速度 | 0.05 m/s | (0, 0.4] m/s |
move_l 最大角速度 | 0.3 rad/s | (0, 3.0] rad/s |
move_l 平移加速度 | 0.1 m/s² | (0, 1.0] m/s² |
move_l 角加速度 | 0.1 rad/s² | (0, 1.0] rad/s² |
示例
设置 PID 参数和速度与加速度限制后运动
from linkerbot import A7
with A7(side="left", interface_name="can0") as arm:
arm.set_position_kps([80.0] * 7)
arm.set_velocity_kps([0.4] * 7)
arm.set_velocity_kis([0.008] * 7)
arm.enable()
arm.set_velocities([1.0] * 7)
arm.set_accelerations([10.0] * 7)
arm.home(blocking=True)
arm.move_j([0.0, 0.0, 0.0, 1.57, 0.0, 0.0, 0.0], blocking=True)
参数持久化
调用 save_params() 将当前 PID、速度、加速度等参数写入电机闪存,断电后仍然生效。
from linkerbot import A7
with A7(side="left", interface_name="can0") as arm:
arm.set_position_kps([80.0] * 7)
arm.set_velocity_kps([0.4] * 7)
arm.save_params() # 保存到闪存
注意:每次写入耗时约 1 ms,避免在实时控制循环中调用。