docs: protocol.md 协议规范(实现标准)
This commit is contained in:
1 parent
cb7eee312f
commit
190182fb2e
7 files changed
+35996
No files matched your search
@@ -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.
@@ -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()
|
||||||
@@ -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=激活
|
||||||
Reference in new issue
Block a user