Skip to content

feat(sdk): 支持夹爪 IMU 分时交换与丢应答重试 - #29

Merged
Vertax42 merged 22 commits into
mainfrom
codex/gripper-imu-support
Nov 4, 2026
Merged

Vertax42 merged 22 commits into
mainfrom
codex/gripper-imu-support

Conversation

@xensexyq

@xensexyq xensexyq commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

摘要

从爪同时上报电机状态和 IMU 时,查询或目标下发可能与上报重叠,造成短帧;即使固件已经取出 IMU 样本,丢失一次交换应答也可能导致主机收不到这批数据。新增显式 start_exchange() 接口,将目标和遥测放进同一请求/应答;从爪交换重试保持原序号,让配套固件重放上次应答。草稿:旧送测固件 cbcc86d 的两台从爪长测均中止,本轮升版固件待真机复测,旧客户端兼容性仍需复测。

相对正式版 v0.4.4 的变化

正式标签 v0.4.4 和目标分支 main 均为 e595763;本次 SDK 为 3371b40,包版本仍为 0.4.4。配套固件 PR #23 的送测提交为 b587b2d,主爪上报 1.2.7.0,从爪上报 1.2.17.0。请在测试记录中登记两个仓库的提交号和实际刷写的 BIN 哈希。

  • 主爪:新增 C++ / Python start_exchange()。通过固件 0x5E 取回 IMU、编码器、按键和传感器事件,原订阅接口可继续使用。固件超过 1 秒未收到轮询会回到主动上报,SDK 恢复后可重新进入交换。普通 start_streaming() 默认协商 64 位 IMU 时间,也可指定 imu_v2_time=False 使用旧帧。主爪默认交换频率上限为 200 Hz,交换频率与采样频率分别设置。
  • 从爪:保留 start_streaming(motor_hz=100) 的电机上报,新增显式 imu_hz 及 IMU 校准接口。新 start_exchange() 通过 0x5D 随控制目标取回电机状态和 IMU;丢失应答时以原序号重试,由固件重放已缓存的应答。现有控制器可沿用原电机状态通道。
  • 时间与并发:SDK 识别 IMU 启动会话、旧时间戳回绕和重复样本。交换的一批样本可共享同一应答的主机到达时间,判断采样连续性应看 seq 和 mcu_timestamp_us。电机反馈与用户回调隔离,保留实际接收时间;Python 扫描设备时释放 GIL,减少后台扫描对控制线程的阻塞。
  • 超时与诊断:半双工请求排队计入超时,保护 ACK 等待序号并核对回显命令;交换线程和控制器看门狗使用单调时钟,避免系统校时干扰。新增交换统计与目标送达观测,分别记录底层 ACK 重试和整次交换失败。
  • 伪串口故障注入用例覆盖从爪丢应答后以同序号重试、交换最终成功且 IMU 序号无缺口。该用例未覆盖真实串口负载。

当前 CI

修复提交 3371b40 的本轮 GitHub Actions 三项全部通过:构建与单元测试、lint、协议镜像。云端协议作业实际检出固件 #23 的 b587b2d。上一轮 682eda6 构建和单测通过,lint 与协议镜像失败。

历史真机结果 · 固件 cbcc86d + SDK 682eda6

该轮固件上报主爪 1.2.6.0、从爪 1.2.16.0。以下结果仅归属这套历史组合;b587b2d 的真机结果待另行记录。

  • 两台从爪断电重启后,磁力计启停脚本各 21/21 通过;配置工具完整写入流程仍待验证。

11 月 3 日,两台从爪用固件 cbcc86d 和 SDK 682eda6 在空载往复、多路相机并发下实测,约 200 Hz 交换、100 Hz 电机状态与 IMU、500 Hz MCU 控制环。两份正式摘要均为 FAIL:

运行时长 原始坏段 末次监测:IMU 缺样 / 队列溢出 / 撞车 / 交换失败超时 停止原因
5.75 h 0 0 / 0 / 0 / 0 Python IMU 回调超过 200 ms 未到
4.30 h 0 0 / 0 / 0 / 0 同上

C++ 控制器观测最大年龄分别为 16.3 ms 和 12.5 ms,两轮结束时电机均已失能。记录支持区分回调迟到与设备缺样,但旧脚本没有同期 GC 计时,不能断定 GC 是唯一原因。异常退出也没有 transport 的结束快照;交换最终超时为零,不能推断底层 ACK 从未超时或重试。

兼容性与合并前复测

本轮主验收使用固件 b587b2d(主爪 1.2.7.0、从爪 1.2.17.0)和 SDK 3371b40(包版本 0.4.4)。刷写后重新上电,先读回主板版本并核对角色;每轮记录设备 SN、所刷 BIN 的 SHA-256 和实际加载的 SDK 提交。

角色 本轮送测 BIN SHA-256
主爪 tc-gu-01-master-1.2.7-b587b2d.bin a2bfdca0217e12ef31a1634a70c5091c01ccb236330f796e181f3350e08165b7
从爪 tc-gu-01-slave-1.2.17-b587b2d.bin 0e72ce02f76e78a5a065d54cd978c403748d0b3db814afe276058a1c24893348

以下矩阵均为待执行项,请记录测试时长及最终判定。

组合 操作 重点判定
新 SDK + #23 主爪 显式调用 start_exchange(100, 100, 200),采集 IMU 和编码器、触发按键并穿插状态查询;中断轮询超过 1 秒后恢复。另用 start_streaming() 做主动上报对照。 交换启动返回 true;记录缺样、按键送达及坏帧;确认固件回退主动上报,恢复轮询后 SDK 重新进入交换。
新 SDK + #23 从爪 两台从爪分别固定、空载,接入相机后以 start_exchange(100, 100, 200) 做往复长测;分别记录电机状态、控制目标和 IMU。 按下方至少 6 h 的条件验收;区分主机回调停顿、底层重试、最终交换失败和 MCU 样本缺口。
新 SDK + 正式固件 在正式主爪调用 start_exchange(),在正式从爪尝试同一接口,再切回已支持的电机上报。 主爪默认回退到主动上报并返回 false,关闭回退时抛出 ProtocolError;从爪抛出 ProtocolError 且没有残留交换线程。
正式 SDK v0.4.4 + #23 固件 主爪使用原 IMU / 编码器上报;从爪先用正式版的电机上报,再按下方工况加查询。 验证旧接口、坏段及 ACK 超时。正式客户端不会自动使用交换;从爪 100 Hz IMU 加查询项需要能显式开流的协议测试工具,单独标记。
  • 用修改后的测试脚本在两台从爪各跑至少 6 h(覆盖此前最长 5.75 h 的触发点):新固件 + 新 SDK,显式 start_exchange(),相同上电条件、空载往复及相机负载。成功及异常退出时都保存启动/运行阶段的 ACK 超时与重试、交换最终失败、IMU 序号、队列溢出、控制器观测年龄、MCU 超期和 GC 停顿;保留最终 PASS/FAIL 和安全停机状态。
  • 用已发布的旧 SDK 0.4.4 + 新从爪固件跑 120 s 主动上报对照,覆盖无查询、IMU 关闭加查询、100 Hz IMU 加查询。分别统计原始坏段、重发后未恢复的数据和 ACK 超时;不要用交换模式的结果替代。
  • 用配置工具实际完成从爪磁力计会话(开始、采集、停止、写入);用工厂工具走完新固件验收流程。
  • 用最终 SDK 在主爪上短时复测普通上报、分时交换与回退。

较早配套版本在 120 s 空载往复 + 并发查询中验证过交换路径,不等同于最终长测通过。真实夹持、堵转和高温未由这两次运行验证。

对照(2026-11-04):main@e595763 ← codex/gripper-imu-support@3371b40,20 个提交、61 个文件。

xensexyq and others added 21 commits October 22, 2026 12:52
保留原一参数 C++ 方法和 Python motor_hz 默认值,新增 imu_hz=0
可选参数。显式启用前查询能力,旧固件拒绝时不改原流;已开流时
拒绝静默新增源,避免 IMU-only 被控制器误当作电机流。
增加加速度/陀螺仪校准后保存接口,重试复用序号,补齐接口说明。
扩展 PTY 覆盖默认、旧固件、双流、参数错误和校准失败路径;修正
既有安全断言的异步等待,验证实际收到零力矩帧,而不只看 Fault 状态。

验证:C++ 477 项通过;Python 151 项通过、2 项需真机而跳过;
7 项针对性测试重复 10 轮通过;本次文件 pre-commit 检查通过。
仅隔离目录本地构建,未替换原 SDK 安装、未推送、未操作设备。
目的:保持旧固件可用,同时避免把时间回退或设备重启误当成连续采样。

- 增加 TimeSession/GetImuV2,只有明确不支持命令才降级,超时及错误响应继续报错。
- IMU 时间扩展为 64 位,提供会话、原生格式、时间有效性和不连续标志。
- 旧包保留原始值并按观测段展开;17 秒回退及不确定断流不伪造回绕。
- 各订阅和轮询独立维护时间线,主从爪开流自动选择格式,默认从爪仍只开电机流。
- Python 提供对应字段,新增串口仿真和协议边界回归,记录 ABI/ROS 接入限制。

验证:489 项 C++ 测试通过;Python 152 通过、2 项硬件测试跳过;
协议对照 75 命令、17 错误码、7 停止原因及 37 结构尺寸通过。
Python 扩展在本任务副本重建,未安装到业务环境;未改 ROS 仓库。
未进行真机长期验收,未发布、未推送。
配合主从爪统一时间分支:旧时间回退不因重复帧恢复有效;订阅过滤连续重复载荷,保留 V2 会话恢复与旧协议回退,补充恢复边界文档。

验证:491 项 C++/PTY 测试通过;152 项 Python 通过、2 跳过;固件协议镜像核对通过。未安装业务环境、未进行真机或 ROS 验收。
为评估从爪开启 IMU 上报对电机控制的影响,新增 5 项 PTY 回归,
覆盖两类控制器借用共流、阻塞回调时的超时零输出及退出资源释放。
增加硬件完整性与稳定性测试矩阵,明确设备身份、运动范围、A/B/A、
时间与帧率判据,以及分发积压下状态新鲜度判断的限制。

验证:C++ 全套 496 通过;Python 152 通过、2 项主爪硬件用例跳过;
新增用例重复 20 轮通过;适用 pre-commit 检查通过。
已完成 EL05 失能上报 15 段、450 秒采集,通信错误计数未增加,
首次 IMU 数组回调出现约 123 ms 延迟,保留记录并另行排查。
独立 PTY 诊断复现持续慢回调引起的控制降频和旧反馈新鲜度失真,
本提交仅增加测试和方案,不修改产品实现,不代表稳定性验收通过。
将真实电机、IMU、控制统计应答和缺字节帧固化为回放样本,覆盖逐字节、分块和损坏后恢复;说明 resync/CRC 计数不能单独定位故障层。

验证:C++ 全量 502 项通过;新增 6 项回放涵盖 621 个分割场景;git diff --check 通过。实物短帧在进入 SDK 解析前已存在,串口链路根因仍在验证。
说明冷启动记录、MCU重启与串口桥断电的证据区别,保留历史测量但撤除通用化结论。
补充USBmon与read全843445字节一致、131条接收坏记录的观测边界,以及uart_stats调用和发送/接收字节对账规则。

验证:git diff --check通过;对照实际USBmon/strace原始数据和固件统计实现,由独立复核确认。
本提交只修改文档;此前502项C++回归通过。修复固件的主短帧硬件复测仍失败,未宣称验收通过。
为内部电机控制建立独立有界接收通道,用户IMU和motor慢回调不再阻塞控制反馈;控制通道过载锁存并由现有超时保护停机,重建Transport后才能恢复。
将完整帧read接收时间贯穿Frame/ACK、IMU/Motor/Encoder和两类控制器,拒绝过期、倒序及未来反馈,避免队列旧数据在分发时被重标为新鲜。
锁存现有判据下的严重故障、非有限反馈和过量程力矩,防止后续健康帧覆盖故障;包含启动ACK与显式reset接收边界,保持原阈值及线协议。
新增默认关闭的有界原始字节/拒帧诊断、分原因计数及Python绑定;更新验证文档和独立native测试加载入口,未覆盖原安装。

验证:最终C++512项通过;Python153项通过、2项缺硬件跳过;18项并发/退出用例重复10轮共180次通过;适用pre-commit检查通过。
旧实现2条慢回调回归先失败,修复后通过;重复测试发现并修正状态发布与PTY零输出接收之间的异步断言竞态,未放宽原截止时间,失败证据保留。
独立构建native与core实际加载路径已核对;未刷机、未安装、未推送、未发布。真实UART缺口定位与真机长稳仍待执行,精确过期故障队列时序注入未验证。
保留 ACK 请求序号至调用结束及 SameSeq 重试期间,防止回绕覆盖等待项;
校验应答命令,保留旧通用 ACK/NACK 支持。清除继承的 INPCK/IGNPAR。

增加本地目标写入次数/间隔、显式 MCU 接受数量快照和可选 UART 错误分类
查询,配套 C++/Python 接口与说明;不加入周期查询或重发控制目标。
纠正状态反馈后串口必然空闲及 HAL 计数可单独定责的说明。

验证:独立构建核心库、Python 绑定与示例;518 项 C++ 回归通过,含
ACK 回绕/错配、目标快照、旧/新诊断兼容性及现有 OTA/控制器测试;
155 项 Python 回归通过。未安装、刷写或访问真实夹爪/相机,未推送。
配合固件 STREAM_MODE_POLLED 与 CMD_SLAVE_EXCHANGE(0x5D),从结构上消除
主机下行与从爪上行在线上的重叠:
- FollowerGripper::start_exchange 以轮询模式开流并启动交换线程;每周期一次
  带遥测的交换,期间提交的目标立即以只带目标的交换发出,保持原有控制时延
- 应答拆成合成的 0x50/0x10/0x17 DATA 帧注入传输层原有分派(含控制通道),
  订阅者和 ForcePositionController/ImpedanceController 无需改动
- Motor::submit 在交换模式下改走交换;超时的目标未被新目标取代时下次重发
- 传输层半双工模式:每个请求从写出到应答或超时独占线路,用户查询也排队
- 新增 RetryMode::Once、ExchangeStats 与 Python 绑定;旧固件 NACK 时抛
  ProtocolError 且不遗留任何运行状态

新增 8 个 PTY 测试(轮询遥测、目标随交换、并发查询下无重叠、超时重发、
目标拒绝、旧固件、参数校验、力位控制器复用);去掉半双工锁的变异被检出。
全部 526 个测试四轮 -j16 并行通过。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
交换模式下每个请求独占线路直到应答或超时,原先等待轮次没有时限:一个
长耗时命令(如 IMU 加速度计/陀螺仪校准,应答超时 15 s)会让设了 100 ms
超时的查询被卡住数秒,超时语义失效。现在等待轮次与等待应答共用该请求的
超时,轮次等不到即抛 TimeoutError;交换线程据此记一次超时并保留目标。

新增 PTY 测试:持有线路的请求未应答时,50 ms 查询在 300 ms 内超时,释放后
查询恢复正常;改回无限等待的变异被检出。全部 527 个测试三轮 -j16 通过。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
配合主爪固件 0x5E 与 StreamMode::Polled,从结构上消除主机下行与主爪上行
重叠导致的上报缺字节:
- LeaderGripper::start_exchange(imu_hz, encoder_hz, exchange_hz,
  fallback_to_streaming):先用 0x5E 探测能力,旧固件回 InvalidCmd 时默认
  退回 start_streaming() 并返回 false,或按参数抛 ProtocolError
- 交换线程按周期取回暂存记录,积压时立即续取(每周期最多 8 次),记录按原
  DATA 帧注入分派,IMU/编码器/按键/传感器错误订阅者无需改动
- 请求超时以同一 seq 重试,固件重放缓存应答,已取出的记录不丢
- 半双工期间所有请求轮流占线;stop_streaming()/close()/析构结束交换
- 新增 LeaderExchangeStats 与 Python 绑定

新增假主爪与 9 个 PTY 测试;五种变异(改为单次发送、去掉半双工、去掉续取、
去掉回退、停止时不结束交换)均被检出。C++ 536 项三轮 -j16 通过,Python 155
项通过。start_streaming() 与已有命令线格式不变。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
主爪固件 1 秒未收到 0x5E 会自行退回主动上报(主机崩溃或卡顿)。
- 应答 flags 不含 POLLED 时,交换线程重发同一 POLLED StartStream
  (最多每 200 ms 一次),卡顿恢复后回到交换模式,期间数据经主动上报
  照常分发不丢;LeaderExchangeStats 新增 rearms / rearm_failures
- start_exchange 文档说明 exchange_hz 取数据频率 2 倍可消除偶发多等
  一个周期,以及同一应答内样本共用到达时间作为 host_time
- 测试替身模拟固件租约回退;并发查询测试改为 1 kHz 实际频率并在
  CHANGELOG 记录半双工轮次锁不公平的已知限制(该测试在 4b3d5c2 上
  已约 1/3 偶发失败)

新增租约回退 PTY 测试,两种变异均被检出;C++ 537 项三轮 -j16 通过,
Python 155 项通过。线格式与已有接口不变。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scan_grippers() 与 find_one/find_left/find_right/find_leader/find_follower
在整个扫描期间持有 GIL(两台空闲夹爪约 280 ms,遇到无响应端口可达
ACK 超时)。上位机在后台线程扫描时,同进程 100 Hz 目标推送线程被同步
冻结;上位机在某一角色被其他进程占用时每 3 秒重扫,导致开主爪测试时
从爪往复每 3 秒卡顿一次。

绑定改用 py::call_guard<py::gil_scoped_release>,与其他阻塞调用一致。
实测扫描期间推送线程最大延迟由 263–280 ms 降为 0 ms;Python 153 项
通过(2 项因主爪被占用跳过)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main 0.4.4 只含示例(视触觉、Rerun 全链路查看、gripper_console --rerun)、
文档与环境(xensesdk 2.2.2、rerun-sdk 0.41.0、numpy 2.2.4)和版本号,
未改 cpp/ 与 python/bindings。

CHANGELOG 冲突:保留 main 的 [0.4.4] 发布段原样;本分支未发布的主从爪
分时交换、租约重入与发现释放 GIL 条目留在 [Unreleased]。

验证:合并树 C++ 537 项 -j16 共 8 轮,7 轮全过,1 轮
LeaderExchangePty.NeverWrites... 在满载下交换次数低于阈值偶发失败(单独
重复 30 次全过,合并未触及相关代码);Python 155 项通过,示例可编译。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main 新增 3cac181、a73a1ae:tactile_sensor.py 与 Rerun 视图按
Sensor.drawMarkerMove 绘制标记流,并同步文档。只涉及 python/examples
与 docs,无冲突,未改 cpp/ 与 python/bindings。

验证:C++ 537 项 -j16 通过,Python 155 项通过,全部示例可编译;
核心库与上次合并 e147e30 的构建逐字节相同。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main 新增 e595763:CHANGELOG 记录视触觉标记流修正并入 0.4.4、v0.4.4
tag 已移动、版本号不变。只改 CHANGELOG.md,自动合并无冲突;本分支
[Unreleased] 条目不变。代码与构建产物不变,无需复测。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- start_streaming(imu_hz, encoder_hz, imu_v2_time=True):传 False 时
  不协商时间会话、上报 28 字节旧版 IMU 帧(同 SDK <= 0.4.4)。普通上报
  下主机写出与上行重叠会撞坏帧,帧越长越易中:主爪 a9bd2c3、请求 IMU
  1000 Hz + 编码器 100 Hz + 每秒 100 次查询、120 s ×3,IMU 坏帧 V2 868
  个、旧版 1 个。默认不变;交换模式可消除重叠并保留 V2
- start_exchange 未指定 exchange_hz 时取数据频率并封顶 200 Hz:主爪
  无论请求多少都按 100 Hz 采样,start_exchange(1000, 100) 原先会因
  1000 > 500 抛错;显式 exchange_hz 上限仍为 500

新增 3 项测试,两种变异均被检出;C++ 540 项 -j16 四轮通过,Python 155 项
通过;主爪实测见上。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
conda-forge/manylinux 等旧 sysroot 工具链下,libstdc++ 的
condition_variable::wait_until(steady_clock) 与 timed_mutex::try_lock_for
退化为 CLOCK_REALTIME 的 pthread_cond_timedwait/pthread_mutex_timedlock:
NTP 把墙钟往回调多少,等待就多等多少。实测从爪交换在 "Clock change
detected" 后 0.2 s 停顿超过 status_timeout_ms,力位控制器进入 Fault。

新增 detail::MonotonicCondition(pthread_condattr_setclock(CLOCK_MONOTONIC))
与 MonotonicTimedLock,用于主从爪交换线程、半双工轮次锁、力位与阻抗控制器
的状态看门狗;与工具链无关。新增 5 项单元测试;C++ 545 项 -j16 九轮中 8 轮
全过、1 轮为既有定时敏感用例偶发(修改前同条件 6 轮失败 2 轮);Python 155
项通过;核心库不再引用 pthread_mutex_timedlock。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
从爪交换原先每个请求只发一次(RetryMode::Once):主机卡顿超过超时
导致应答丢失时,下一次交换取走新样本,丢失应答里的 IMU 样本随之丢失
(高负载主机上每小时 5–9 个)。改为与主爪相同的 RetryMode::SameSeq,
固件命令层对逐字节相同的重复请求原样重放缓存应答(遥测一致、目标
不会执行两次)。

测试替身模拟固件重放缓存应答,新增“应答丢失由同 seq 重试找回、IMU
不缺”测试;改回 Once 的变异被检出。并发查询测试改为 1 kHz(与主爪测试
一致,原无节流循环在不公平轮次锁下偶发失败,改前单独 30 次失败 3 次)。
C++ 546 项三轮通过,Python 153 项通过(2 项需主爪硬件跳过)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SDK PR #29 使用固件 PR #23 的头文件,其余受信运行仍校验 hw_v1.1.0。补齐诊断命令 TxTrace 和四种交换报文头的尺寸检查,按锁定的 Ruff 版本格式化两处 Python 文件。

验证:全量 pre-commit、Ruff 和严格协议对照通过;旧基线与注入的报文尺寸错误均被检出。构建通过,C++ 546 项通过,Python 153 项通过、2 项跳过。
Replace the bundled images with leader 1.2.7 and follower 1.2.17 built from
tc-gu-01 8c059e9 (tc-gu-01#24 on top of #23) with arm-none-eabi-gcc 10.3.1;
manifest size/CRC32 verified against the files. Leader image is the one verified
on the taccap-collector backpack. Docs, CHANGELOG and OTA example names updated;
host-breaking timestamp timebase change called out (this SDK already unwraps it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chore(firmware): 随包固件升级到主爪 1.2.7 / 从爪 1.2.17(协议 V2.9)
@Vertax42
Vertax42 marked this pull request as ready for review November 4, 2026 13:28
@Vertax42
Vertax42 merged commit f0624c8 into main Nov 4, 2026
3 checks passed
@Vertax42
Vertax42 deleted the codex/gripper-imu-support branch November 4, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants