188 lines
9.8 KiB
Markdown
188 lines
9.8 KiB
Markdown
# 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=激活
|