feat: 添加HID主机命令和主题颜色功能

- 添加了新的事件类型包括display_theme_event、hid_host_ack_event、
  hid_host_command_error_event和hid_host_command_event用于处理HID主机命令

- 在CMakeLists.txt中添加了新的源文件,包括显示主题事件和HID主机命令相关模块

- 实现了HID主机命令协议定义,包括主题颜色和时间同步命令

- 在BLE HID模块中添加了对供应商命令报告的支持,增加新的报告ID用于主机命令传输

- 扩展了HID传输路由机制,支持USB和BLE双通道传输

- 实现了显示模块的主题颜色存储功能,支持通过settings持久化保存主题颜色

- 添加了完整的BLE时间同步服务PC主机接入文档

- 修改了电池采样逻辑,增加2秒延迟以等待电池电压稳定
This commit is contained in:
2026-03-30 15:57:38 +08:00
parent 277462a8fe
commit 2c7eae4de1
25 changed files with 1157 additions and 94 deletions

View File

@@ -0,0 +1,281 @@
# BLE 时间同步服务 PC 上位机接入文档
## 1. 概述
当前项目实现了一套独立的 BLE 时间同步服务,供 PC 上位机在蓝牙连接后向键盘写入当前 UTC 时间、时区和时间精度。
这套服务的设计目标是:
- 不走 HID 报告通道,避免与键盘输入链路耦合。
- BLE 侧只负责协议适配,实际时间状态统一交给 `time_manager` 管理。
- 主机只负责“下发时间”,设备不通过此服务回读时间,也不通过 notify 返回确认。
结论上,这是一条:
- 自定义 GATT Service
- 单个可写 Characteristic
- 仅支持写入
- 需要加密连接
的单向时间同步通道。
## 2. GATT 定义
### 2.1 Service UUID
`0b7f5000-38d2-4f62-8f6f-36c4fd73a110`
### 2.2 Characteristic UUID
`0b7f5001-38d2-4f62-8f6f-36c4fd73a110`
### 2.3 Characteristic 属性
- `Write`
- `Write Without Response`
### 2.4 Characteristic 权限
- `Write Encrypted`
也就是说,上位机在写入前必须先完成配对/加密。未加密链路下,写入会被 GATT 层拒绝。
## 3. 设备端处理逻辑
### 3.1 依赖条件
设备端只有在以下两个条件同时满足时,才接受时间写入:
- BLE 栈已经 ready
- `time_manager` 已经 ready
如果模块尚未 ready写入会返回 ATT 错误。
### 3.2 写入成功后的行为
设备端收到合法 payload 后会:
1. 校验版本、长度和 flags。
2. 解析 `utc_ms``timezone_min``accuracy_ms`
3. 自动把同步来源标记为 `BLE`
4. 投递 `time_sync_event``time_manager`
5. `time_manager` 更新运行时钟状态,并延迟异步写入 settings。
注意:
- 当前 BLE 时间同步服务没有 `Read``Notify` 能力。
- 主机拿到 GATT 写成功,只能说明设备接受了这次写入。
- 设备不会通过这个服务主动回传“当前时间”。
## 4. 数据包格式
### 4.1 总长度
固定 `16` 字节。
### 4.2 字段布局
| 偏移 | 长度 | 字段名 | 类型 | 字节序 |
| --- | --- | --- | --- | --- |
| 0 | 1 | `version` | `uint8` | - |
| 1 | 1 | `flags` | `uint8` | - |
| 2 | 2 | `timezone_min` | `int16` | little-endian |
| 4 | 8 | `utc_ms` | `uint64` | little-endian |
| 12 | 4 | `accuracy_ms` | `uint32` | little-endian |
### 4.3 当前协议版本
- `version = 1`
### 4.4 flags 定义
当前只定义 1 个 bit
- `BIT(0)` = `TIMEZONE_VALID`
因此当前版本必须满足:
- `flags & 0x01 != 0`
推荐主机固定写:
- `flags = 0x01`
## 5. 字段语义
### 5.1 `timezone_min`
单位:分钟。
含义:本地时区相对 UTC 的偏移。
示例:
- 中国标准时间 UTC+8 -> `480`
- 印度 UTC+5:30 -> `330`
- UTC-5 -> `-300`
设备端当前接受范围:
- `-1440 ~ +1440`
### 5.2 `utc_ms`
单位:毫秒。
含义UTC 时间戳,不带本地时区偏移。
要求:
- 必须大于 `0`
### 5.3 `accuracy_ms`
单位:毫秒。
含义:本次时间来源的估计精度。
建议:
- 若上位机没有可靠精度信息,可直接写 `0`
- 若来自系统时间并已做网络对时,也可以给一个保守值,例如 `50``100`
## 6. 主机写入建议
### 6.1 推荐使用 Write Request
虽然设备同时支持:
- Write
- Write Without Response
但 PC 上位机侧建议优先使用 **Write Request带响应写**,原因是:
- 更容易拿到 ATT 层成功/失败结果
- 更方便在开发阶段定位协议错误
- 更适合作为配置/同步类命令
### 6.2 推荐时序
1. 扫描并连接设备。
2. 完成配对/加密。
3. 发现时间同步 Service 和 Characteristic。
4. 组包为固定 16 字节 payload。
5. 执行一次带响应写入。
6. 若写成功,可视为设备已接受此次同步请求。
## 7. Python 示例
下面示例基于 `bleak`,演示如何向设备写入当前系统时间。
```python
import asyncio
import struct
import time
from bleak import BleakClient
TIME_SYNC_CHAR_UUID = "0b7f5001-38d2-4f62-8f6f-36c4fd73a110"
def build_time_sync_payload(timezone_min: int, accuracy_ms: int = 0) -> bytes:
version = 1
flags = 0x01 # TIMEZONE_VALID
utc_ms = int(time.time() * 1000)
return struct.pack("<BBhQI", version, flags, timezone_min, utc_ms, accuracy_ms)
async def main(address: str):
payload = build_time_sync_payload(timezone_min=480, accuracy_ms=50)
async with BleakClient(address) as client:
await client.write_gatt_char(
TIME_SYNC_CHAR_UUID,
payload,
response=True,
)
print("time sync write done")
if __name__ == "__main__":
asyncio.run(main("XX:XX:XX:XX:XX:XX"))
```
说明:
- `struct.pack("<BBhQI", ...)` 对应协议定义的 little-endian 格式。
- `timezone_min=480` 表示 UTC+8。
- `response=True` 表示使用带响应写入。
## 8. 常见错误与排查
### 8.1 写入被拒绝
常见原因:
- 还没有完成配对/加密
- 写入长度不是 16 字节
- `version != 1`
- `flags` 未设置 `TIMEZONE_VALID`
- 使用了 prepare write / long write
### 8.2 写入成功但设备时间没更新
优先检查:
- 设备日志是否出现 `Accepted BLE time sync ...`
- 设备日志是否出现 `Time synchronized src=1 ...`
- 上位机是否错误地把本地时间直接当成 UTC 写入
### 8.3 时区显示错误
最常见原因:
- 主机把“本地毫秒时间戳”写进了 `utc_ms`
- 同时又传了 `timezone_min`
正确做法是:
- `utc_ms` 始终写 UTC 毫秒
- 本地时区单独放在 `timezone_min`
## 9. ATT 错误码语义
设备端当前会返回的典型 ATT 错误如下:
- `BT_ATT_ERR_UNLIKELY`
- 模块尚未 ready例如 BLE 栈或 `time_manager` 还未初始化完成
- `BT_ATT_ERR_INVALID_OFFSET`
- 非零 offset 写入
- `BT_ATT_ERR_ATTRIBUTE_NOT_LONG`
- 使用 prepare write / 长写流程
- `BT_ATT_ERR_VALUE_NOT_ALLOWED`
- payload 长度、版本、flags 或字段内容不合法
## 10. 当前服务边界
当前版本仅支持:
- 主机 -> 设备 单向校时
当前不支持:
- BLE 读回当前设备时间
- 校时结果通知
- 历史同步记录查询
- DST 单独字段
- 通过 payload 指定同步来源
同步来源在设备端固定记为:
- `TIME_SYNC_SOURCE_BLE`
## 11. 对接建议
对 PC 上位机实现建议如下:
- 首选带响应写入,不要默认用 write without response
- 时间戳统一使用 UTC 毫秒
- 时区单独使用分钟偏移
- 每次建立加密连接后可主动同步一次时间
- 若 PC 有系统授时状态,可把估计精度填入 `accuracy_ms`