Files
qianbian/docs/protocol.md

188 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BLE 协议规范(4.2寸墨水屏设备 · 实现标准)
> 状态:**单一事实来源**。实现 `src/ppclock` 以本文件为准。
> 证据来源:`analysis/web/protocol-raw.md`(Web JS)+ `analysis/apk/apk-analysis.md`(APK 反编译互证)。
> **互证结论**:GATT UUID、NRF- 前缀、240B 分块、ff/00 平面码、dd 时间帧、e2 收尾、e100/e101/e102 模式族、倒计时 04/0n/990/ff00/ffff 帧、6 种抖动(Bayer 矩阵逐值一致)、红色判定、灰度系数 —— 三线一致。
> Web 独有(APK 未实现):WiFi 配置、EFEF 取设备 ID、2300/2301、14/15、e3/e4/e5、E6、FB、e901/EA000、EA0x —— 本规范以 Web 证据为准。
> APK 独有:`$IMG(0,0)`、8000/8001 图像起始帧(属扫地/轻舞固件家族分叉,本设备不适用)。
## 1. 设备与 GATT
| 项 | 值 |
|---|---|
| 广播名前缀 | `NRF-` |
| EPD Service | `13187b10-eba9-a3ba-044e-83d3217d9a38` |
| EPD Characteristic(图像+刷屏,write+notify) | `4b646063-6264-f3a7-8941-e65356ea82fe`(h=48) |
| RXTX Service | `00001f10-0000-1000-8000-00805f9b34fb` |
| **RXTX 命令通道(实机)** | **`0000331f-0000-1000-8000-00805f9b34fb`(h=57,notify+write+read,svc 0x221f)** |
| `00001f1f-...`(h=53,仅 write) | ⚠ 实机证据:写入仅 ATT-ACK **不执行**命令(2026-07-28);Web/App 文档中的"RXTX 特征",本设备弃用 |
| SUOTA Service | `0000fef5-...`(h=28-45,含自定义特征,见 §9) |
**实机 GATT 全表(2026-07-28 bridge 诊断,设备 NRF-5DBF28)**:
GAP/GATT/DIS(2A24/2A26/2A28/2A23/2A50)/BAS(2A19 notify) + fef5 SUOTA 六特征 + EPD + 1f1f(write-only) + 331f(notify+write+read)。
notify 订阅:设备 ID 应答案测经 **331f** 到达(EFEF 同样须写 331f);EPD/2A19 亦可订阅。
连接顺序:先 EPD service → 再 RXTX;写均用 write-with-response(`triggerRxTxCmd2` 例外,without-response)。
## 2. 屏幕参数
| 参数 | 值 |
|---|---|
| 分辨率 | 400 × 300 |
| 色彩 | 黑/白/红 三色(亦可切黑白模式) |
| 像素打包 | 1bpp,MSB first,行主序(y:0→300, x:0→400) |
| 平面 | bw 平面 15000B + 红平面 15000B(红仅三色模式) |
| bw 位值 | R,G,B 任一>0 → 1(白),全 0 → 0(黑)⚠即"非黑即白" |
| 红平面位值 | R>0,G=0,B=0 → 1(红) |
| 日历小图 | 280×100(3500B)或 400×180(9000B),同 1bpp |
## 3. 帧格式
### 3.1 图像数据块(EPD 通道)
```
[cod:1B][code:1B][offset:2B BE][payload:≤240B]
```
| 字段 | 值 |
|---|---|
| cod | `03`=整图;`04`=日历小图(数据总长 3500B 或 9000B 时) |
| code | `ff`=bw 平面;`00`=红平面 |
| offset | 本块在平面内的字节偏移,大端 |
| payload | ≤240 字节;按 offset 顺序连发,块间无应用层 ACK |
结束:全部块发完延时 ≥300ms → 整图发 `01`;日历小图发 `AA`。
### 3.2 刷屏(EPD 通道)
`00{param}` 后接 `01`。
### 3.3 时间设置(RXTX 通道)
```
dd [unix:4B BE][year:2B BE][month:1B][day:1B][week:1B] → 随后发 e2
```
- unix = 本地时区修正秒(`now_utc + tz`);week:周一=1 … 周日=7。
- 测试向量:2026-07-24 15:30:00 (UTC+8) 周五 → `dd 6982a7f0 07ea 07 18 05`(unix=1785475800=0x6982A7D8 ⚠按实现时实际值校验,此处仅示意格式)→ 待真机/互证校准。
### 3.4 倒计时(RXTX 通道)
- 日期:`ff00{YYYY}{MM}{DD}`(时钟界面)/ `ffff{YYYY}{MM}{DD}`(日历界面);YYYY=4 位十进制 ASCII。
- 前缀文字位图(时钟界面,16px 字格):≤4 字符(中文=1,英文=0.5)单包 `0{charCount}{位图hex}`;>4 拆两包:`04{前128B}` 然后 `0{charCount}{剩余}`。
- 前缀文字位图(日历界面,24px 字格):每字符一包 `990{索引}{72B}`,包间隔 1s。
- 位图打包:1bpp MSB first 行主序。
- 收尾 `E2`;关闭倒计时 `FA` → 1s → `E2`。
- 日历中文字设置:`990*` → `FA` → `e10103` → `E2`。
### 3.5 激活与设备 ID(RXTX 通道)
1. 请求 ID:发 `EFEF` → notify 收 **14 字节**(可打印即 ASCII,否则 hex 表示)。
2. 激活码:原流程 `GET http://4638e813e4.qicp.vip:8889/key_generator.php?id={用户ID}&key={设备ID}` → 返回 ≥32 hex(正则 `激活码1:([0-9A-F]{32,})`)。⚠算法在服务端,本地化方案见 §7。
3. 下发:`EF{激活码hex大写}` → 400ms → `e2`。
### 3.6 WiFi 配置(RXTX 通道)
ASCII UTF-8 直发:`WIFI_SSID:{ssid};WIFI_PASS:{pass};CITY:{城市代码}`(城市代码为天气代码,如 `101010100`)。
## 4. 命令 opcode 总表(RXTX 通道,除注明外)
| 命令 | 含义 |
|---|---|
| `e2` | 提交/刷新(多数命令收尾) |
| `dd…` | 设置时间(§3.3) |
| `2300` / `2301` | 三色模式 / 黑白模式 |
| `14` | 12/24 小时制切换 |
| `15` | 时钟颜色切换 |
| `e3` | 反色模式 |
| `e4` | 时钟字体切换 |
| `e5` | 旋转 180° |
| `e10000`–`e10003` | 图片模式:显示图一~图四 |
| `e10103` / `e10104` / `e10105` | 日历模式一/二/三 |
| `e10201` / `e10202` / `e10203` | 时钟模式一/二/三 |
| `E6{01-0f}` / `E6{10-f0}` | 红 / 黑 LUT 校准(越大越深;0 无效) |
| `FB01{HH}{MM}` / `FB00{HH}{MM}` | 开启 / 关闭休眠时段 |
| `e901{n}{01\|00}` | 轮播间隔 n(01=分钟,00=小时) |
| `EA0{0-3}` | 上传目标槽位(图一~图四) |
| `EA000{0-3}` | 轮播数量 1–4(值=n-1) |
| `EF{hex}` | 激活码下发 |
| `EF{数字逐位双写}` | 停车牌号码("12"→`EF1122`) |
| `EFEF` | 请求设备 ID |
| `FA` | 关闭倒计时 |
| `ff00…` / `ffff…` / `990…` / `04…` / `0…` | 倒计时(§3.4) |
| `00{xx}`+`01`(EPD) | 刷屏 |
| `01`(EPD) | 整图传输结束刷新 |
| `AA`(EPD) | 小图传输结束 |
模式切换命令(`e1xx`/`23xx`/`14`/`15`/`e3`-`e5`)发后 300ms 补 `e2`(setri 模式)。
## 5. 抖动算法参数(图像管线)
| 算法 | 参数 |
|---|---|
| none | 阈值化,threshold 默认 125 |
| floydsteinberg | 误差核 7/16,3/16,5/16,1/16 |
| atkinson | 6 邻域 ×1/8 |
| bayer | 4×4 矩阵 [[15,135,45,165],[195,75,225,105],[60,180,30,150],[240,120,210,90]] |
| stucki | /42 核 |
| jarvis | /48 核 |
| bwr_* | 同上 6 种三色版;红色判定 `r>g*1.5 && r>b*1.5 && r>threshold` |
预处理:亮度 ±255·v/100;对比度 (c+100)/100(128 中点);饱和度=灰度插值(0.299/0.587/0.114);diffusion 0–100/100 误差缩放。
## 6. 标准会话流程
1. 扫描(namePrefix `NRF-`)→ 连接 → 读取 DIS 固件版本 → 按固件选择 RXTX 特征。`PP_da14585_4.2` 使用 `331F`(notify/write/read);`HM42_AIO_*` 使用 `1F1F`(write-only,不订阅 notify)。首选特征不存在时才回退到另一特征。
2. (可选)`EFEF` 取设备 ID。
3. 对时:`dd…` + `e2`。
4. 操作:模式切换(+`e2`)/ 传图 / 倒计时 / WiFi。
5. 断线自动重连 ≤5 次。
### 6.1 图像协议的固件差异
| 固件家族 | RXTX | 400×300 单色平面 | EPD 图像会话 |
|---|---|---|---|
| `PP_da14585_4.2` / 未知 | `331F` | 黑白平面 | 旧序列:数据块 → `01`(小图 `AA`) |
| `HM42_AIO_*` | `1F1F` | 黑白 15,000B + 全零红色 15,000B | `8001` → `0000` → `020000` → 双平面数据 → `0101` → RXTX `e2` |
HM42 的双平面和会话帧均为必需条件:缺红色平面会出现红屏噪点,缺会话帧会出现黑白噪点。400×300 双平面在 Windows BLE 实机约需 8 分钟;调用方不得依赖跨网络的单个长 HTTP 连接,应在 BLE 主机本地执行并持久化结果后再轮询。
## 7. 本地化(去云)清单 —— 已全部解决
| 云依赖 | 本地化方案 | 状态 |
|---|---|---|
| key_generator.php 激活码 | **本地 keygen**:`code[i]=ror2(((MAC[i]<<1)&0xFF)^0xEF)%101`(firmware-analysis §4.2);`ppclock activate --auto` 经 EFEF 反解 MAC 自动完成;且固件侧 EF 不门禁任何命令 | ✅ 已实现 |
| file_down.php 固件下载 | 固件本地存档 `app/`;`ppclock ota` 本地升级(SUOTA 经 0x221F 服务,firmware-analysis §5) | ✅ 已实现 |
| qbsg.top 自助激活 | 同 keygen | ✅ |
| 天气(CITY 代码) | 原厂云功能,本地化范围外;`wifi` 命令仅透传。注意:**本固件 WIFI 命令为 no-op**(firmware-analysis §3.3) | ⚠ 固件未实现 |
| qrcodejs CDN(模板用) | CLI 本地 qrcode 库 | ✅ |
**固件侧 no-op 命令**(Web 端发送但本固件版本忽略,CLI 保留前向兼容):`E9xx` 轮播间隔、`EA0x` 槽位/`EA000x` 轮播数量、WIFI 配置(firmware-analysis §3.3 分发器跳表)。
## 9. SUOTA 固件升级(证据:APK §e)
| 项 | 值 |
|---|---|
| Service | `0000fef5-0000-1000-8000-00805f9b34fb` |
| SERV_STATUS(notify) | `64b4e8b5-0de5-401b-a21d-acc8db3b913a` |
| MEM_DEV | `8082caa8-41a6-4021-91c6-56f9b954cc34` |
| GPIO_MAP | `724249f0-5ec3-4b5f-8804-42345af08651` |
| PATCH_DATA(自定义映射) | `457871e8-d516-4ca1-9116-57d0b17b9cb2` |
| PATCH_LEN(自定义映射) | `9d84b9a3-000c-49d8-9183-855b673fda31` |
流程:MEM_DEV←`{00,00,00,13}`(SPI Flash)→ GPIO_MAP←`{00,00,06,05}` →
镜像+尾 1B XOR 校验 → 240B 块(PATCH_LEN 2B LE + 20B 无响应写块 + 等 SERV_STATUS==2)→
MEM_DEV←`{00,00,00,FE}` → 等 ==2 → `{00,00,00,FD}` 重启。
状态码:2=成功,3-9/17-22/0xFFFF/0xFFFE=各类错误(见 ota.py STATUS_ERRORS)。
## 8. 测试向量(实现前必须写入 tests/)
> 来源:analysis/web/protocol-raw.md 各节;标 ⚠ 的待 APK/固件互证确认。
1. setTime 布局(§3.3)⚠
2. 图像块帧头:400×300 bw 平面首块 → `03 ff 0000` + 240B ⚠
3. 小图判定:3500B → cod=`04`;15000B → cod=`03`
4. 停车牌 "12" → `EF1122`
5. 轮播 5 分钟 → `e9010501`
6. 槽位 3 上传前置 → `EA02`;轮播数量 4 → `EA0003`
7. 停车牌/激活/取 ID 的 `EF` 前缀区分:`EFEF`=取ID、双写数字=车牌、≥32hex=激活