docs: protocol.md 协议规范(实现标准)

This commit is contained in:
agent committed 2026-07-24 10:37:58 +00:00
1 parent cb7eee312f
commit 190182fb2e
7 files changed
+35996

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
Lqbsg/top/MainActivity;
Lqbsg/top/MainApp;
Lqbsg/top/activation/ActivationManager;
Lqbsg/top/ble/AndroidRawGattConnector$connect$callback$1;
Lqbsg/top/ble/AndroidRawGattConnector;
Lqbsg/top/ble/AndroidRawGattHandle;
Lqbsg/top/ble/BleManager$AndroidGattOtaChannel;
Lqbsg/top/ble/BleManager$DescriptorWriteRequest;
Lqbsg/top/ble/BleManager$btStateReceiver$1;
Lqbsg/top/ble/BleManager$gattCallback$1;
Lqbsg/top/ble/BleManager$readDeviceInfoString$1;
Lqbsg/top/ble/BleManager$readDeviceInfoString$result$1;
Lqbsg/top/ble/BleManager$scanCallback$1;
Lqbsg/top/ble/BleManager$startDeviceInfoRead$1;
Lqbsg/top/ble/BleManager$startScan$1;
Lqbsg/top/ble/BleManager;
Lqbsg/top/ble/BleManagerFacade;
Lqbsg/top/ble/BleManagerKt;
Lqbsg/top/ble/BleSession$executeWrite$2$1;
Lqbsg/top/ble/BleSession$sendCommand$1;
Lqbsg/top/ble/BleSession$sendCommand$2;
Lqbsg/top/ble/BleSession$sendEpdBytes$1;
Lqbsg/top/ble/BleSession$sendEpdBytes$2;
Lqbsg/top/ble/BleSession;
Lqbsg/top/ble/GattOtaChannel;
Lqbsg/top/ble/RawGattConnector;
Lqbsg/top/ble/RawGattHandle;
Lqbsg/top/ble/RawGattListener;
Lqbsg/top/ble/SimulatedBleDeviceProfile;
Lqbsg/top/ble/SimulatedBleKt;
Lqbsg/top/ble/SimulatedBleManager$connect$2;
Lqbsg/top/ble/SimulatedBleManager$startScan$$inlined$sortedBy$1;
Lqbsg/top/ble/SimulatedBleManager$startScan$1;
Lqbsg/top/ble/SimulatedBleManager;
Lqbsg/top/ble/SimulatedGattOtaChannel$readCharacteristic$1;
Lqbsg/top/ble/SimulatedGattOtaChannel$requestMtu$1;
Lqbsg/top/ble/SimulatedGattOtaChannel$writeCharacteristic$1;
Lqbsg/top/ble/SimulatedGattOtaChannel$writeDescriptor$1;
Lqbsg/top/ble/SimulatedGattOtaChannel;
Lqbsg/top/ble/SimulatedRawGattConnector;
Lqbsg/top/ble/SimulatedRawGattHandle$connect$1;
Lqbsg/top/ble/SimulatedRawGattHandle$discoverServices$1;
Lqbsg/top/ble/SimulatedRawGattHandle$readCharacteristic$1;
Lqbsg/top/ble/SimulatedRawGattHandle$requestMtu$1;
Lqbsg/top/ble/SimulatedRawGattHandle$writeCharacteristic$1;
Lqbsg/top/ble/SimulatedRawGattHandle$writeDescriptor$1;
Lqbsg/top/ble/SimulatedRawGattHandle;
Lqbsg/top/ble/SimulatedSession$Companion;
Lqbsg/top/ble/SimulatedSession$sendCommand$1;
Lqbsg/top/ble/SimulatedSession$sendCommand$2$1;
Lqbsg/top/ble/SimulatedSession$sendEpdBytes$1;
Lqbsg/top/ble/SimulatedSession;
Lqbsg/top/ble/UnsupportedBleManager;
Lqbsg/top/ble/UnsupportedRawGattConnector;
Lqbsg/top/ble/UnsupportedRawGattHandle;
Lqbsg/top/ble/a;
Lqbsg/top/ble/b;
Lqbsg/top/ble/c;
Lqbsg/top/ble/data/BleConnectionInfo;
Lqbsg/top/ble/data/BleDeviceInfo;
Lqbsg/top/ble/data/BleException$BluetoothDisabled;
Lqbsg/top/ble/data/BleException$LocationDisabled;
Lqbsg/top/ble/data/BleException$PermissionDenied;
Lqbsg/top/ble/data/BleException;
Lqbsg/top/ble/data/BleScanRecord;
Lqbsg/top/ble/data/BleScanState;
Lqbsg/top/ble/data/BleSessionFacade;
Lqbsg/top/ble/data/DeviceConnectionState$Connected;
Lqbsg/top/ble/data/DeviceConnectionState$Connecting;
Lqbsg/top/ble/data/DeviceConnectionState;
Lqbsg/top/ble/data/DisconnectReason$BluetoothDisabled;
Lqbsg/top/ble/data/DisconnectReason$ConnectFailed;
Lqbsg/top/ble/data/DisconnectReason$DeviceDisconnected;
Lqbsg/top/ble/data/DisconnectReason$GattError;
Lqbsg/top/ble/data/DisconnectReason$OutOfRange;
Lqbsg/top/ble/data/DisconnectReason;
Lqbsg/top/template/editor/LibEditor;
Lqbsg/top/template/editor/TemplateEditorRenderCallback;
Lqbsg/top/template/editor/TemplateEditorResourceBridge;
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Whitespace-only changes.
Binary file not shown.
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Parse and verify DA14585 firmware image header (Dialog SUOTA/multi-image format).
Image header layout (64 bytes):
+0x00 u8[2] magic 0x70 0x51 ('pQ' LE word 0x5170) = IMAGE_VALID
+0x02 u8[2] signature version/application id (here 0xAA 0x01)
+0x04 u32 code_size size of code region (= file_size - 64)
+0x08 u32 crc32 CRC over code region
+0x0C 52B reserved 0xFF
"""
import struct, sys, zlib, binascii
IMG = "/mnt/documents/Works/Eink/qianbian/app/firmware-PP_da14585_4.2_CH.img"
def crc32_mpeg(data, init=0xFFFFFFFF, xorout=0, poly=0x04C11DB7):
crc = init
for b in data:
crc ^= b << 24
for _ in range(8):
crc = ((crc << 1) ^ poly) & 0xFFFFFFFF if (crc & 0x80000000) else (crc << 1) & 0xFFFFFFFF
return crc ^ xorout
def main():
data = open(IMG, "rb").read()
print(f"file size: {len(data)} (0x{len(data):x})")
magic = data[0:2]
sig = data[2:4]
code_size, crc_hdr = struct.unpack_from("<II", data, 4)
reserved = data[12:64]
print(f"magic : {magic.hex()} ({magic!r})")
print(f"signature : {sig.hex()}")
print(f"code_size : {code_size} (0x{code_size:x}) file-64 = {len(data)-64}")
print(f"header CRC : 0x{crc_hdr:08x}")
print(f"reserved : all 0xff = {all(b==0xff for b in reserved)}")
print(f"size match : {code_size == len(data)-64}")
code = data[64:64+code_size]
# try CRC variants
c_zlib = zlib.crc32(code) & 0xFFFFFFFF # CRC-32 (reflected, init/xor FF)
c_bzip2 = crc32_mpeg(code, 0xFFFFFFFF, 0xFFFFFFFF) # CRC-32/BZIP2
c_mpeg = crc32_mpeg(code, 0xFFFFFFFF, 0) # CRC-32/MPEG-2
c_posix = crc32_mpeg(code, 0, 0xFFFFFFFF) # CRC-32/POSIX (cksum-ish)
c_jam = zlib.crc32(code, 0xFFFFFFFF) ^ 0 ^ 0xFFFFFFFF # jamcrc style check
print(f"CRC32 zlib : 0x{c_zlib:08x} match={c_zlib==crc_hdr}")
print(f"CRC32 bzip2 : 0x{c_bzip2:08x} match={c_bzip2==crc_hdr}")
print(f"CRC32 mpeg2 : 0x{c_mpeg:08x} match={c_mpeg==crc_hdr}")
print(f"CRC32 posix : 0x{c_posix:08x} match={c_posix==crc_hdr}")
# also try crc over code with initial vector table included (whole thing)
c_zlib_all = zlib.crc32(data[0:64+code_size]) & 0xFFFFFFFF
print(f"CRC32 zlib(all incl hdr): 0x{c_zlib_all:08x}")
if __name__ == "__main__":
main()
+152
View File
@@ -0,0 +1,152 @@
# BLE 协议规范(4.2寸墨水屏设备 · 实现标准)
> 状态:**单一事实来源**。实现 `src/ppclock` 以本文件为准。
> 证据来源:`analysis/web/protocol-raw.md`(Web JS),APK/固件互证结果以"互证"标注;未互证项以 ⚠ 标注。
## 1. 设备与 GATT
| 项 | 值 |
|---|---|
| 广播名前缀 | `NRF-` |
| EPD Service | `13187b10-eba9-a3ba-044e-83d3217d9a38` |
| EPD Characteristic(图像+刷屏,write+notify) | `4b646063-6264-f3a7-8941-e65356ea82fe` |
| RXTX Service | `00001f10-0000-1000-8000-00805f9b34fb` |
| RXTX Characteristic(命令,write+read+notify) | `00001f1f-0000-1000-8000-00805f9b34fb` |
| `0000221f-...` | 预留,未使用 |
连接顺序:先 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-`)→ 连接 → EPD service → RXTX service → 订阅两通道 notify。
2. (可选)`EFEF` 取设备 ID。
3. 对时:`dd…` + `e2`。
4. 操作:模式切换(+`e2`)/ 传图(`EA0{slot}` → 数据块×N → `01`/`AA`)/ 倒计时 / WiFi。
5. 断线自动重连 ≤5 次。
## 7. 本地化(去云)清单
| 云依赖 | 本地化方案 |
|---|---|
| key_generator.php 激活码 | 分析固件校验逻辑(见 docs/firmware-analysis.md),本地实现算法或固件补丁绕过 ⚠待固件分析 |
| file_down.php 固件下载 | 固件已本地存档 app/ |
| qbsg.top 自助激活 | 同上激活方案 |
| 天气(CITY 代码) | 本地化=不依赖天气;CLI 提供 `wifi-config` 透传 |
| qrcodejs CDN(模板用) | CLI 用本地 qrcode 库 |
## 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=激活