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:
281
docs/ble_time_sync_pc_host.md
Normal file
281
docs/ble_time_sync_pc_host.md
Normal 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`
|
||||
|
||||
Reference in New Issue
Block a user