midit/triki
GitHub: midit/triki
一款将 Żabka Triki BLE 瓶盖玩具改造为健身房动作计数器的纯浏览器 Web Bluetooth 应用,采用带通滤波与积分相结合的信号处理方案实现可靠的重复次数检测。
Stars: 0 | Forks: 0
# IronCap — 一款由瓶盖 BLE 玩具改造而成的健身房动作计数器
Żabka 是一家波兰连锁便利店,他们曾赠送过一款名为
**Triki** 的小型 BLE 设备 —— 这是一个内置运动传感器的塑料瓶盖,最初用于
在其会员 App 中玩重力感应游戏。它内部包含了一个性能相当不错的 IMU。
本项目将其变成了一个 **健身房动作计数器**。在它的背面粘上一块钕磁铁,它就能
吸附在配重架、杠铃片或杠铃上。所有的操作都在手机上通过 Web Bluetooth 在浏览器中完成 ——
无需服务器,无需账号,也无需应用商店。
| App | 链接 | 功能 |
|---|---|---|
| **IronCap** | [`ironcap.html`](ironcap.html) | 计算任何锻炼的动作次数,追踪节奏,从你的纠正中学习,并保留锻炼历史记录 |
| **Cadence** | [`index.html`](index.html) | 早期实验:使用同一个传感器进行骑行踏频计算 |
**在线体验:** https://midit.github.io/triki/ironcap.html
## 新手入门
1. **iOS:** 安装 [Bluefy](https://apps.apple.com/app/bluefy-web-ble-browser/id1492822055)。
Safari 不支持 Web Bluetooth。**Android:** Chrome 可以正常使用。
2. **按下瓶盖上的按钮** 唤醒它,然后点击 *连接*。该设备
会进入休眠状态以节省 CR2032 纽扣电池电量,并且一次只能保持 **一个** 活动连接。
3. 将其吸附在配重上 —— 方向无所谓。
4. 按下 *开始一组*,完成你的动作,然后再按一次。确认计数。
瓶盖上的按钮是你唯一需要的物理控制方式:
| 动作 | 结果 |
|---|---|
| 单击 | 开始 / 完成一组 |
| 双击 | 取消该组 |
| 长按约 1.2 秒 | 结束本次锻炼 |
## 为什么计数逻辑是这样设计的
最朴素的方法 —— 将加速度积分得到速度,然后寻找
运动周期 —— **根本无法处理真实数据**。在一段
长达 888 秒的抓取记录中,由于帧间隔高达 423 ms 且加速度
达到 10.7 g,该积分器算出的结果达到了 **15.4 m/s**,而现实中杠铃的
峰值速度接近 2 m/s。这导致动作被成批计数,每秒能数出好几个。
真正有效的替代方案:
```
vertical acceleration
-> band-pass (0.12 s vs 1.2 s) remove noise and drift
-> integrate -> pseudo-velocity
-> band-pass again kill integrator drift
-> count zero-crossing cycles one cycle == one rep
```
积分之所以必要,有着一个微妙的原因:**垂直加速度在每次深蹲中会产生两个正向波峰**
(你在每个运动方向上都会加速然后再减速),因此直接在加速度上统计周期会导致
每个动作被重复计算。经过积分后,每个动作就只对应一个完整的周期。
根据带有标签的录音测量,这将总误差从 26 降低到了 10,并将步行期间的误报从 18 降低到了 2。
数据迫使我们修复的其他问题:
- **峰值速度时的瞬时加速度为零**,每个动作中会出现两次。因此,简单的
“设备是否静止?”检查会在动作进行中触发,从而破坏
滤波器。静止状态的判断必须基于一个时间窗口来进行。
- **重力估计必须是自适应的** —— 在静止时响应要快,以便立即锁定新的安装
朝向;在运动时响应要慢,这样滤波器就不会去
追踪动作本身并削弱其振幅。这正是实现安装朝向无关性的原因。
- **Off-by-one 偏差:** 最初的向上穿越点过去只是 *开启* 一个周期,所以十个动作
总是被报告为九个。
- **帧时间极其不规律**(大约 60 Hz,偶尔会出现 400 ms 的间隔),因此 `dt` 被进行了限幅处理,并且在遇到间隔时滤波器会重置,而不是去积分那些无意义的数据。
## 刻意设定显式的动作组
只有在 *开始* 和 *结束* 之间才会进行计数。在器械之间走动、
拿起重量以及坐回原位的过程都不会被计入 —— 这是
设计上决定的,而不是靠调节阈值实现的。在带有标签的记录数据中,那些
过渡动作正是 **每一个** 误报的根源。
## 从纠正中学习
每种锻炼项目都会存储自己的检测器参数,以及该锻炼项目带有标签的动作组的原始轨迹数据。当你纠正一次计数时,系统会执行一次网格搜索(grid search),寻找能够同时在 **所有** 该锻炼项目的已标记动作组中复现正确次数的参数。在准确度相同的候选参数中,它会选择最 **保守** 的一个,
因为用户纠正只能提供正向示例 —— 如果没有这个评判标准,搜索就会滑向过度敏感,并开始将噪声也计入动作。
这是参数拟合,而不是神经网络,这是刻意为之的:它
只需一个样本即可工作,在手机浏览器中运行大约只需 100 ms,而且你
可以确切地看到哪些数字发生了改变以及为什么改变。在一段真实的深蹲录音中,它
将结果从 9 纠正为了正确的 10。
每一组动作也会连同其原始轨迹和确认后的计数一起被写入研究日志中。你可以从 **数据** 标签页将其导出 —— 随着时间的推移,这些数据集将使计数器变得越来越准确。
## 硬件
| 部件 | 组件 |
|---|---|
| MCU | Nordic nRF52810 |
| IMU | ST LSM6DSL(加速度计 + 陀螺仪) |
| Flash | Macronix MX25R8035F, 1 MB |
| 电源 | CR2032 |
PCB 上暴露了 SWD 焊盘,但由于启用了 Nordic APPROTECT,因此读取
固件需要对芯片进行完全擦除。
### BLE 协议 — Nordic UART Service
| 角色 | UUID |
|---|---|
| Service | `6e400001-b5a3-f393-e0a9-e50e24dcca9e` |
| Write / RX | `6e400002-…` |
| Notify / TX | `6e400003-…` |
| LED (bit 0) | `6e400004-…` |
订阅通知,然后向 RX 写入以下内容以启动 IMU 数据流:
```
20 10 00 D0 07 68 00 03
```
字节 5 是输出数据速率:`0x68` = 104 Hz,`0x34` = 52 Hz。
### 帧格式 — 14 字节
```
[0] 0x22 header
[1] button: 0x00 up, 0x01 down
[2..7] gyro X, Y, Z int16 LE -> divide by 131.0 = deg/s
[8..13] accel X, Y, Z int16 LE -> divide by 2048.0 = g (±16 g)
```
**两个很容易犯的错误**(我们都犯过):
1. *假设加速度计的数据排在前面。* 事实并非如此。通过真实的
抓取记录进行验证:在静止状态下,字节 8–13 通道读取的值为 `-1186, -191, +1494`,
其向量大小为 1917 ≈ 2048 —— 这就是重力。
2. *仅仅匹配 `22 00` 头部。* 字节 1 代表按钮状态,因此 **当按住按钮时,每一帧的值都是 `22 01`**,如果进行严格匹配,就会在无形中丢弃所有这些帧。
通知是以 **任意偏移量的多帧突发形式** 到达的,因此
请扫描每个通知内部的数据头来寻找帧,而不是读取固定偏移量。
读取固定偏移量正是导致早期版本的踏频 App 在 iOS 上
有效帧率仅为 ~33% 的原因。
读取运动数据不需要身份验证。(在官方 App 中上传游戏分数受到 APPROTECT 背后密钥的挑战-响应机制限制 ——
在这里无关紧要,也不在讨论范围内。)
## 在本地运行
```
python -m http.server 8765
```
然后打开 `http://localhost:8765/ironcap.html`。Web Bluetooth 要求安全
上下文环境:`localhost` 和 `https` 符合条件,而局域网内普通的 `http` 则不行。
## 仓库结构
```
ironcap.html the gym app (self-contained, no dependencies)
index.html cycling cadence app
manifest.webmanifest PWA manifest
sw.js service worker, offline shell
triki_logger.py desktop BLE logger (bleak) for capturing raw data
```
## 现状与客观的局限性
- 检测器的默认参数是在 **一种** 带有标签的锻炼项目(深蹲)上拟合出来的。其他
锻炼项目在达到准确之前需要进行几次纠正。
- 尚未测量在推举、划船和器械训练上的准确性。
- 动作计数器是当前仍在维护的部分;踏频 App 是一个早期
实验,仅作为参考保留。
欢迎提交代码贡献和抓取的数据集 —— 尤其是深蹲以外其他
锻炼项目的已标注录音。
## 致谢
帧结构、按钮字节和缩放系数是由
[TrikiScope](https://github.com/Maku-hub/TrikiScope) 和
[zabka-triki-hardware](https://github.com/Piwencjusz/zabka-triki-hardware) 社区破解出来的。
## 许可证
MIT
标签:CMS安全, JavaScript, Web蓝牙, 传感器数据处理, 后端开发, 多模态安全, 数据可视化, 智能硬件, 物联网应用, 运动健身