vvlasov-dev/redmi-watch-live

GitHub: vvlasov-dev/redmi-watch-live

一个将小米蓝牙协议移植到 Windows/Python 的项目,把 Redmi Watch 5 Active 变成本地实时健康数据仪表板,并附带睡眠引擎与智能闹钟。

Stars: 0 | Forks: 0

# Redmi Watch 5 Active → PC:实时仪表板、睡眠引擎与智能闹钟 一个独立的**将 Gadgetbridge 中的 Xiaomi 协议移植到 Windows/Python** 的项目。程序通过 Bluetooth Classic (SPP) 连接手表,完成加密认证,并将这款廉价手表转变为自定义仪表板的数据源:实时心率、带有睡眠分期的睡眠历史记录、月度及年度趋势——以及**在 REM 窗口内提供温和唤醒信号并保证智能闹钟唤醒的睡眠引擎**。 这些手表收集的数据通常只存在于 Xiaomi 的云端。本项目将数据掌握在自己手中:手表作为传感器,而本地仪表板成为唯一事实来源。 ## 截图 仪表板根据使用频率划分为不同的标签页。以下截图为展示模式(`/demo`,合成的“完美一天”),采用深色主题。 **当前** — 实时心率、每日目标圆环、任务: ![仪表板 — “当前”标签页](https://raw.githubusercontent.com/vvlasov-dev/redmi-watch-live/main/assets/dashboard-now.png) **睡眠** — 带有睡眠评分的睡眠分期图、睡眠助手(REM 信号)及智能闹钟: ![仪表板 — “睡眠”标签页](https://raw.githubusercontent.com/vvlasov-dev/redmi-watch-live/main/assets/dashboard-sleep.png) **健康** — 每小时心率、心率区间、步数、压力、SpO₂ 及每周趋势: ![仪表板 — “健康”标签页](https://raw.githubusercontent.com/vvlasov-dev/redmi-watch-live/main/assets/dashboard-health.png) ## 功能特性 - **实时仪表板** (`http://127.0.0.1:8765`) — 实时心率、目标圆环、按分钟绘制的心率/SpO₂/压力曲线、心率区间、每小时步数。单页应用,支持深色/浅色主题,图表支持缩放和悬停查看。 - **带有睡眠分期的睡眠** — 顶级睡眠应用般的睡眠分期图(平滑的深度曲线)、夜晚睡眠评分、真实的深度/REM/浅度睡眠比例(如果手表未读取到分期,则显示“无数据”——参见“数据真实性”)。 - **睡眠引擎 / lucid** — 使用手表提供的 REM 阶段(当手表支持时),否则会根据统计出的 REM 时间窗口发送微弱的震动提醒——因为在该硬件上,心率无法区分 REM 阶段;在睡眠约 6 小时后提供**保证生效的智能闹钟**,并采用逐级唤醒机制(温和信号 → 蜂鸣 → 手表端硬件兜底闹钟)。 - **永久保存的趋势数据** — 7 天 / 30 天 / 12 个月;聚合数据永久保存。 - **与手表双向同步的任务** — 仪表板上的待办事项列表会镜像同步到手表的**原生“提醒”应用**中(不是通知!):在 PC 上添加 — 出现在手腕上;在手表上勾选 — 在 PC 上显示为已完成。协议中的 Reminder 子类型已成功逆向并经过了硬件验证。 - **PC → 手表通知** 支持自定义图标(自定义位图,移植了像素格式转换),PC 端控制闹钟和震动,系统托盘支持。 手头没有硬件?打开 **`/demo`** — 这是一个基于合成的“完美一天”(逼真的心率、全满的圆环、极佳的睡眠数据)的演示页面,不会与真实数据混淆。 ## 架构 垂直切片设计:通用底层模块位于 `core/` 目录,每个功能是 `features/` 中的独立文件夹(代码结构映射详见 [`FEATURES.md`](FEATURES.md),开发约定详见 [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md)): | 层级 | 功能说明 | |---|---| | `core/client.py` | BT SPP 状态机:握手、数据帧、realtime、reconnect | | `spp.py` / `xcrypto.py` / `miniproto.py` | v1/v2 数据帧封装、miwear-auth + AES-CTR、微型 protobuf | | `activity.py` | 活动文件解析器(日/详情/睡眠/分期)— 与 Gadgetbridge **逐字节** 对齐 | | `core/store.py` | SQLite(日/睡眠数据 — 永久保存,分钟级数据保存 90 天,采样数据保存 14 天,任务) | | `core/router.py` | HTTP 路由器:各功能注册自己的路由 | | `core/watch_io.py` | 单一 BT 通道仲裁器:优先级调度(闹钟 > lucid > 任务),去重 | | `features/sleep/` | 睡眠引擎:纯逻辑核心 `decide()` + tick 循环(lucid、智能闹钟、自动夜间模式) | | `features/todos/` | 任务:CRUD + 与手表原生提醒的双向同步 | | `dashboard.py` | 状态管理 + `/state` 模型 + 应用构建 | | `index.dc.html` | 前端(design-canvas HTML,`{{ }}` 数据绑定,标签页) | | `service.py` | 监控程序:健壮启动、reconnect 循环、watchdog | ## 引以为傲的工程细节 - **真实蓝牙协议的逆向工程**,而非简单教程:加密握手、每包使用 IV=密钥的 AES-CTR、CRC-16/ARC、无外部依赖的 protobuf。所有内容都由模拟手表行为的离线测试覆盖。 - **逐字节对齐的活动文件解析器**,参考了 Gadgetbridge 的源代码——偏移量并非凭空猜测,而是与手表的真实 dump 进行了核对(dump 属于私人生物识别数据,未在仓库中公开;测试均运行合成测试用例)。 - **“寂静之夜”——从数据中发现的洞察。** 结果发现,每次我们轮询睡眠文件时,手表都会将其记录为一分钟的清醒状态(14 次轮询与同步时刻完全吻合),从而破坏了 REM 检测。解决方案:当用户睡觉时,完全不轮询手表——引擎依靠按秒计算的心率运行,在用户醒来后再进行一次性同步拉取整晚的数据。 - **手表原生“提醒”中的任务。** Reminder 子类型的 schedule 服务已从 Gadgetbridge 的 proto 文件中还原并在硬件上验证:通过 id 创建/删除,将手表的 ack 响应绑定到具体任务(基于单一通道的 FIFO 机制)。此外还捕捉到了一个固件 Bug——手表有时会在数据流中途返回**空列表**;简单的同步机制会将所有任务标记为“已完成”,因此 reconcile 操作要求满足“之前存在 + 连续两次轮询缺失”。 ## 开发方式 开发规则固化在仓库的文件中,而不是依靠记忆: - **[`docs/CONVENTIONS.md`](docs/CONVENTIONS.md)** — 事实来源:先计划后编码、层级划分、数据真实性、Definition of Done 检查清单。 - **5 个测试套件** (`run_tests.py`) — 通过纯逻辑核心 `decide()` 回放真实的睡眠记录,解析器合成字节测试用例,加密/帧封装,任务,通道仲裁器;`deploy.ps1` 在测试未通过时拒绝部署,`hooks/pre-commit` 阻止未通过的代码提交。 ### 通过不变量而非肉眼捕获的 Bug 1. **睡眠解析器重复累加数据** — 手表在每次同步时都会追加数据包的副本;导致 REM 膨胀了 4 到 15 倍。通过校验“分期总和 == 总时长”发现。 2. **将零视为事实** — `rem = 0` 被显示为“无 REM”,尽管字节标志位表明“手表未测量分期”。通过检查原始字节发现 → 修正为“无数据”。 3. **闹钟在白天触发** — 发生在测试期间,通过提前预置的引擎决策日志捕获。修复:添加了“仅在夜间”的限制条件。 ## 安装与运行 (Windows) ``` cd redmi_watch5_live py -m pip install -r requirements.txt # 1) 复制示例 config 并填入你的 COM 端口 + auth 密钥: copy config.example.json config.json py client.py --list # найти COM-порт спаренных часов # 2) 启动服务(dashboard + 引擎): powershell -ExecutionPolicy Bypass -File deploy.ps1 # прогоняет тесты, стартует сервис # dashboard: http://127.0.0.1:8765 · 无硬件 demo: http://127.0.0.1:8765/demo # 测试: py run_tests.py ``` ### 两个必要条件 1. **Auth 密钥 (16 字节)。** 如果没有在 Mi Fitness 中配对获取的密钥,手表将拒绝连接。无法直接在 Windows 下提取——需要**使用一次 Android 设备**(方法见 Gadgetbridge wiki 的 “Pairing types and Auth Key”:配对时使用 `adb logcat`,或读取应用数据库)。密钥需放入 `config.json`(该文件已在 `.gitignore` 中 — 仓库仅包含 `config.example.json`)。 2. **手表的数据通道是单一的。** 当 PC 正在读取数据时,手机无法获取数据 — 请关闭 Mi Fitness / 将手表与手机解绑,否则 PC 无法获取 SPP 通道。 ## 隐私 这些属于个人生物识别数据。已从仓库中**排除** (`.gitignore`):`config.json`(手表密钥)、`history.db`(所有心率/睡眠历史记录)、日志,以及 `captures/*.bin`(真实的手册 dump — 解析器的本地 fixture,不包含在仓库中;`test_activity.py` 在文件不存在时会自动跳过并运行合成测试用例)。 ## 测试与许可证 - `run_tests.py` = `selftest.py`(加密/帧封装/protobuf)+ `test_activity.py`(解析器:始终运行合成数据,如有本地真实 dump 则一并运行)+ `test_sleep_engine.py`(通过 `decide()` 回放真实睡眠记录)+ `test_todos.py` + `test_watch_io.py`。 - 协议来源为 Gadgetbridge 的 `service/devices/xiaomi` 包 (**AGPLv3**):
标签:Python, 云资产清单, 健康数据, 无后门, 智能穿戴, 蓝牙通信, 逆向工具, 逆向工程