文档类型:核心机制 适用读者:Hardware SDK、App Hardware、Firmware 与 QA 工程师 内容状态:当前实现 + 分日期历史基线 代码范围:
hd-transport、hd-transport-*、Core Protocol V2 文件写入与 Pro2 host-asset package 最后代码核验:2026-08-26 前置阅读:Protocol V1/V2 传输协议、Pro2 设备管理
- 给出当前 SDK 的 BLE packet、file chunk、write mode 和无损压缩结论。
- 区分已完成的代码/单元测试与真正跑过的物理平台,禁止跨平台外推。
- 用分段耗时判断瓶颈位于 Host 写入、BLE 链路还是 firmware 串行 ACK。
Protocol V2 BLE 的生产默认值如下:
| 范围 | 当前行为 | 边界 |
|---|---|---|
| Protocol 选择 | method 明确只支持 V2 时直接按 expected V2 探测 |
双协议与 V1-only method 不改变原顺序;不根据名称/PID 推导 BLE 协议 |
普通 FilesystemFileWrite |
1800B |
保留最长 127-byte filesystem path 的 frame 空间 |
| firmware 固定 staging path | 1960B |
最长当前路径的完整 frame 低于 2048B |
firmware 1.0.1+ 固定 wallpaper.okpkg path |
1960B |
仅通过内部 BLE-only override 放宽;WebUSB 和 legacy wallpaper path 不受影响 |
| Protocol V2 BLE packet | 协商值,生产上限 244B |
LowLevel 未报告能力时回退 192B;React Native 缺失 MTU 时不猜测高容量 |
| Write mode | 支持时默认 withoutResponse |
characteristic 仅支持 acknowledged write 或调用方显式设置 writeWithResponse: true 时例外 |
| Host-asset compression | dependency-free raw LZ4,优先 16KiB block、超限回退 8KiB |
解压后的 RGB565 bytes 不变,保留现有 dithering;兼容 firmware compressed-buffer 上限 |
| File response | 每个 chunk 等待 FilesystemFile 与 processed_byte |
Protocol V2 响应仍按串行 session 管理,不并发发送有副作用的 file-write |
2048B 限制作用于完整 Protocol V2 frame,而不是只作用于 file data。完整 frame 还包含 protobuf
中的 path、offset、total size、flags,以及 Protocol V2 header/CRC。使用生产 schema、最大 uint32
offset/total size 和完整 flags 编码后的边界如下:
| 场景 | UTF-8 path | 1960B data 对应完整 frame |
结论 |
|---|---|---|---|
vol1:/wallpapers/wallpaper.okpkg |
32B |
2028B |
安全,余量 20B |
| 最长 firmware staging path | 24B |
2020B |
安全,余量 28B |
| SDK 允许的最长 filesystem path | 127B |
2123B |
超限 75B |
最长合法 path 下,1885B data 编码后已经恰好是 2048B。物理测试中,完整 frame 恰好占满
firmware UART FIFO 的配置会卡住,因此不能使用该理论极值作为生产默认。通用值保留 1800B;
只有 path 由 SDK 固定、且完整 frame 已通过生产 schema 边界测试的调用方才放宽到 1960B。
| SDK 场景 | Path 来源 | 1960B 最坏 frame |
当前 BLE chunk | 判断 |
|---|---|---|---|---|
| Firmware 主组件 | SDK 固定 staging path,最长 24B |
2020B |
1960B |
已启用并有 frame test |
| Wallpaper host-asset package | SDK 固定 32B path |
2028B |
1960B |
已启用、frame test 与 macOS CLI 实测通过 |
| Firmware resource archive | 签名 package header,最多 64B;boot resource staging 为 52B |
boot staging 为 2048B |
1800B |
不能提升;会命中已知卡死边界 |
| NFT image/thumbnail/metadata/package | SDK 根据 hash 与 safe-integer timestamp 生成,最长 45B |
2041B |
1800B |
协议边界可容纳,但仅余 7B,尚无真机结果 |
| Portfolio pending package | SDK 固定 39B path |
2035B |
1800B |
协议边界可容纳,但尚无真机结果 |
公共 fileWrite |
调用方提供,最长 127B |
2123B |
1800B |
必须保持通用安全值 |
NFT 与 Portfolio 的数值是生产 schema 静态编码结果,不是物理平台验证结果。若后续放宽,应使用
caller-specific BLE limit、增加固定 path frame test,并分别做真机上传与应用验证;不能修改公共
fileWrite 默认值,也不能把结果外推到 firmware resource archive。
不同原生库对 mtu 字段的语义并不一致,SDK 先归一化再取 244B 上限:
| 平台链路 | 原生上报 | SDK 计算 |
|---|---|---|
| React Native iOS | maximumWriteValueLength(.withoutResponse) + 3 |
减 3 后取不超过 244B |
| React Native Android | ATT MTU | 减 3 后取不超过 244B |
| Electron macOS | Noble 上报 maximumWriteValueLength(.withoutResponse) payload |
Electron main process 先补 3 为 ATT MTU,renderer 再减 3 |
| Electron Windows | Noble 上报 MaxPduSize payload |
Electron main process 先补 3 为 ATT MTU,renderer 再减 3 |
| Electron Linux | Noble HCI 上报 ATT MTU | 保持原值,再减 3 |
| CLI LowLevel | 插件直接报告单次 characteristic write payload | 直接使用并限制到 244B,缺失时回退 192B |
Electron 的归一化发生在 main process,getDevice() 与 MTU change event 对 renderer 始终暴露
ATT MTU;main process 自己分包时也使用同一归一化值。此设计不依赖 Noble 升级。
- 设备:OneKey Pro 2 4B8F,firmware
1.0.1。 - Host:macOS,BLE,SDK worktree
perf/pro2-ble-transfer。 - 测试入口:本地构建 CLI
upload-wallpaper;正式 CLI1.2.1仅用于版本 preflight。 - 输入:
604x1024JPEG,文件大小106869B,SHA-256d34dcf3ad944e4c415bc81e9b4f3380561c3ef3fd34add7b7a56f8d9caa5a182。 - 设备条件:屏幕保持点亮、设备已解锁、流程不需要 PIN/确认。
- 指标口径:Core
[FileWrite]日志为传输分段事实;CLI metrics 用于核对最终总量与进度。
前四行使用相同的 882528B host-asset package,仅改变 transport 参数;后两行依次记录
4KiB block 的上一版 LZ4 encoder,以及当前自适应 16KiB/8KiB block encoder。
| Packet capacity | File chunk | Package bytes | Core transfer | Throughput | 结果状态 |
|---|---|---|---|---|---|
64B |
1800B |
882528 |
97.83s |
8.81 KiB/s |
baseline 成功 |
192B |
1800B |
882528 |
69.81s |
12.35 KiB/s |
成功 |
192B |
1960B |
882528 |
64.05s |
13.46 KiB/s |
成功 |
244B |
1960B |
882528 |
50.64s / 52.53s |
平均 16.72 KiB/s |
两次成功 |
244B |
1960B |
832131 |
49.20s |
16.34 KiB/s |
上一版成功应用 |
244B |
1960B |
750295 |
51.30s / 47.40s / 52.13s |
中位数 15.12 KiB/s |
三次成功应用 |
当前自适应版本的 CLI 独立口径为 51.12s / 47.28s / 51.96s,中位数 51.12s;它与
Core 的计时起止点略有差异,不应混在同一列做 A/B。相对最初 64B/1800B baseline,当前
实现的 Core 中位传输时间缩短约 47.6%,中位吞吐提升约 71.6%。
16KiB 版本相对 4KiB 版本把 package 减少 81836B(9.8%),file-write ACK 从 425
次降为 383 次(9.9%)。但 4KiB 只记录过一次 49.20s,16KiB 三次结果受 BLE
response latency 波动影响,其中两次比该单次结果慢,不能据此宣称端到端稳定提升 9.8%。
可确认的收益是相同链路条件下减少发送 bytes 和串行 ACK 数;耗时收益需要更多交错 A/B 才能
从无线链路噪声中分离。
同一台设备的 get-state 冷连接对比结果:
| Probe 顺序 | 总耗时 |
|---|---|
| V1-first,失败后重连并探测 V2 | 13.913s |
| method contract 明确 V2-only,直接 expected V2 | 10.572s |
V2-first 节省 3.341s。该优化来源是 BaseMethod.getSupportedProtocols() 的明确契约,不使用
BLE name、设备型号或 PID 推导协议。
当前 750295B package 三次上传的 Core 日志为:
| 指标 | 结果 |
|---|---|
| File transfer | 51.30s / 47.40s / 52.13s |
| Host complete-frame write total | 0.90s / 0.90s / 1.02s |
| Firmware response wait total | 50.24s / 46.31s / 50.92s |
| Measured attempts | 每次均为 383 |
| Timeout retry | 三次均为 0 |
| 最终状态 | 三次均完成接收、校验、解包并应用为 wallpaper.bin |
response wait 占传输时间约 98%。每次 ACK 的平均 response wait 在三轮之间约为
121-133ms,足以覆盖减少 42 次 ACK 所带来的理论收益。因此在当前 firmware 的串行
FilesystemFileWrite -> FilesystemFile(processed_byte) 模型下,继续减少 JS write delay 的收益很小;
显著提升需要改变 ACK 粒度、允许安全的多请求关联,或扩大 firmware frame/FIFO 边界。
This experiment kept the SDK packet capacity (244B), file chunk (1960B), package
(750295B), PHY, DLE, and serial request/response model unchanged. The only production candidate
in the BLE firmware was changing the preferred maximum connection interval from 30ms to 15ms.
The firmware was built with the CI-matching Arm GNU Toolchain 15.2.1, loaded through J-Link at
4000kHz, and started through the existing MBR/SoftDevice vector. The tested image was an unsigned
debug image; it is test evidence, not a signed release artifact.
RTT established the link-level facts:
| Link event | 30ms baseline |
15ms candidate |
|---|---|---|
| Initial central-selected interval | 24 units (30ms) |
24 units (30ms) |
| Peripheral parameter update | Not required because 30ms was inside the old range |
After about 5.28s: min/max 12 units (15ms) |
| Slave latency | 0 |
0 |
| PHY | 2M TX / 2M RX |
2M TX / 2M RX |
| Effective data length | 251B TX / 251B RX, 2120us |
251B TX / 251B RX, 2120us |
The same CLI command and wallpaper package produced:
| Firmware setting | CLI transfer time | CLI throughput | Core response wait | Attempts | Result |
|---|---|---|---|---|---|
max interval 30ms |
51.51s |
14.22 KiB/s |
51.52s |
383 |
Success |
max interval 15ms, run 1 |
35.96s |
20.38 KiB/s |
35.98s |
383 |
Success |
max interval 15ms, run 2 |
36.23s |
20.22 KiB/s |
36.28s |
383 |
Success |
The two 15ms runs had a median transfer time of about 36.10s and median throughput of about
20.30 KiB/s. Against the single 30ms control run, this reduced transfer time by about 29.9%
and increased throughput by about 42.8%. Mean response wait per request fell from about 134.5ms
to 94.0-94.7ms; host writes remained below 0.3s, so the improvement came from link scheduling,
not JavaScript write throughput.
Reducing FIRST_CONN_PARAMS_UPDATE_DELAY from 5s to 1s was also tested. RTT confirmed that the
link reached 15ms about 1.27s after connection, but completed runs ranged from 35.82s to
39.71s, with no stable improvement over the two 5s runs. The 1s change was therefore reverted
to avoid overlapping the connection-parameter procedure with security, DLE, and PHY negotiation.
This is a macOS CLI result only. The 15ms GAP preference is firmware-wide, but each iOS, Android,
Windows, or macOS central may select or reject connection parameters differently; those platforms
still require physical regression testing. A 7.5ms fixed interval was not selected because it
would materially increase cross-platform compatibility and power-risk without evidence from those
centrals. With 15ms, 2M PHY, 251B DLE, zero slave latency, and a 15ms BLE event-length budget,
the next major limit remains the one-response-per-FilesystemFileWrite firmware/main-MCU path.
The same Pro 2 4B8F was placed in bootloader mode and updated over BLE with locally built and
signed P1/P2 application artifacts. The device started and ended the run on firmware 1.0.1,
bootloader 1.0.0, and BLE firmware 1.0.20.
| Metric | Result |
|---|---|
| Total staged bytes | 2,440,562B |
| Transfer phase | 145.62s |
| Average transfer throughput | 16.37 KiB/s |
| Device install phase | 13.65s |
| End-to-end CLI duration | 211.96s |
| SDK transfer retries | 0 |
| Final state | Normal mode; versions verified |
The first progress sample was about 9.16 KiB/s; later samples stabilized around
15.7-17.0 KiB/s. Consumers should therefore wait for at least 2s and 64KiB before showing an
ETA. This avoids presenting the connection and first-write warm-up as a stable estimate.
FIRMWARE_PROGRESS now treats transferredBytes, totalBytes, rateBytesPerSecond, and
elapsedMs as one batch-level metric stream. The clock and byte numerator do not reset when the
update moves from P1 to P2, another component, or a resource package. Recovery and retry time stays
inside the elapsed value because it is time the user actually waits. The terminal 100% event also
carries the final metric snapshot, so App consumers do not lose the completed transfer when the
install phase begins.
The SDK reports measured transfer facts only. ETA formatting, warm-up policy, and the distinction between active duration and wall-clock workflow duration belong to the App layer. This keeps the same metric contract available to iOS, Android, Windows, and macOS without adding platform-specific transport behavior.
第一阶段的 LZ4 best-match encoder 把同一 RGB565 wallpaper package 从 882528B 降到
832131B,减少 50397B(约 5.7%)。在此基础上,扩大 independently compressed block
可以利用跨 4KiB 边界的重复像素,并减少 block index 开销:
| LZ4 block | Package bytes | Block count | Package build median | 相对 4KiB |
|---|---|---|---|---|
4KiB |
832131 |
303 |
53.60ms |
baseline |
8KiB |
787184 |
152 |
54.83ms |
-5.4% |
16KiB |
750295 |
76 |
51.55ms |
-9.8% |
16KiB 离线 package 的独立 raw-LZ4 decoder 输出与原始 1237004B RGB565 data 逐字节一致,
二者 SHA-256 均为 f315fef8198114ef1d037fc8a2fa3f03d940150b96f6d2b26402bc3787fe57c4。
基准图的最大 compressed block 为 13401B,低于 firmware 的 16384B buffer 上限。
不可压缩的 16KiB raw block 可能编码成大于 16384B 的 LZ4 block。当前 SDK 因此先尝试
16KiB,只要该 entry 的任一 compressed block 超限,就把该 entry 整体重新编码为 8KiB。
Firmware 1.0.1 接受 block_size_log2=9..14,且 work buffer 已按 16KiB 分配;8KiB
fallback 的 LZ4 worst-case 大小仍低于该 buffer。自动化测试覆盖 preferred、fallback 和两条路径的
byte-for-byte round trip。
以下是 search depth 的补充对比;它不如 block size 有效:
| Search depth | Package bytes | Package build |
|---|---|---|
64 |
832131 |
约 61ms |
256 |
831944 |
约 66-69ms |
4KiB 下 256 只再减少 187B;16KiB 下把 depth 从 64 提升到 1024 也只再减少
945B,没有足够收益,当前保留 64。这项优化只改变 raw LZ4 token 和 block 边界选择,
firmware 解压后的 RGB565 bytes 与压缩前逐字节一致。
断开 USB 后,Pro2 4B8F firmware 1.0.1 对 16KiB package 连续三次完成 BLE 上传、设备端
校验、解包和应用;没有 timeout/retry。该结果验证了 firmware compatibility 和数据完整流程,
不代表 iOS、Android 或 Windows 的无线性能结果。
当前实现没有关闭 dithering。Dithering 是 RGB888 转 RGB565 前的误差扩散,用细小的像素变化 减少渐变色带;关闭后虽然更容易压缩,但可能出现天空、阴影等区域的 banding,不能称为同画质方案。
| 平台 | 当前代码覆盖 | 自动化验证 | 物理验证 |
|---|---|---|---|
| macOS CLI LowLevel | 244B、1960B 固定 wallpaper path、V2-first、无损 LZ4 |
已覆盖 packet/fallback 与 Core 文件写入 | 已完成,本节结果 |
| React Native iOS | MTU refresh、244B ceiling、默认 withoutResponse、共享 Core 优化 |
RN strategy/link tests 已通过 | 本轮未执行 |
| React Native Android | MTU 517 request、244B ceiling、High connection priority、默认 withoutResponse |
RN strategy/link tests 已通过 | 本轮未执行 |
| Electron macOS | Noble payload 到 ATT MTU 归一化、244B、零 high-throughput pacing |
platform normalization test 已通过 | 本轮未执行 |
| Electron Windows | MaxPduSize 到 ATT MTU 归一化、已配对后 withoutResponse |
platform normalization test 已通过 | 本轮未执行 |
| Electron Linux | ATT MTU 保持原值 | platform normalization test 已通过 | 本轮未执行 |
当前自动化记录:传输/RN/CLI 聚焦集合 6 suites / 158 tests 全通过;Core host package、Protocol V2
frame/file-write 与 Electron MTU 集合 6 suites / 384 passed / 4 skipped。hd-transport、LowLevel、Core、
React Native transport、Electron transport、Web Device transport 与 CLI 均已完成 build。构建只出现
仓库既有的 external dependency、source map、mixed exports、circular dependency,以及 Electron
未使用参数 warning,没有 build error。App package 发布、安装和 App 真机验证不属于本页已完成结论。
以下结果只代表当日 OneKey Pro2、iOS 真机、React Native Demo 与 react-native-ble-plx 组合。
当时 SDK 普通 BLE file chunk 为 1800B,测试目标是区分 GATT raw write、RN 写队列与
FilesystemFileWrite 串行 ACK。
这一轮通过 RN Demo 的 Pro2 BLE 固件升级入口测试不同 transport pacing 参数。测试结果如下:
| Profile | 结果 |
|---|---|
withResponse baseline |
2.00 KB/s |
default |
8.0 KB/s |
faster pacing |
9.0 KB/s |
aggressive |
9.64 KB/s |
writeWithoutResponse 相比 writeWithResponse 有明显收益,2.00 KB/s -> 8.0 KB/s 说明写入方式是有效优化点。
但 default -> aggressive 只从 8.0 KB/s 提升到 9.64 KB/s,收益约 20%。这说明 packetLength、burstSize、pauseMs、flushDelayMs 这类 pacing 参数不是主要瓶颈,只能做边缘优化。
按 chunk=1800B 粗略换算:
8 KB/s ~= 1800B / 220ms
9 KB/s ~= 1800B / 195ms
10 KB/s ~= 1800B / 176ms
也就是说,当前速率更像是每次 FilesystemFileWrite 都要等待设备完成处理并返回 processed_byte,下一块才能继续发送。这个串行 ACK 模型会把吞吐限制在“单块大小 / 单轮回包耗时”。
这一轮绕过 SDK,仅使用 react-native-ble-plx 直接向 OneKey BLE write characteristic 写入数据,用于测 BLE GATT 上行写入天花板。
测试参数和结果:
| requestMTU | Size | chunk | Raw write 结果 | Ping RTT 结果 |
|---|---|---|---|---|
256 |
64 KB |
20B |
6.0 KB/s |
iters=40 时 no ack within 3s |
256 |
64 KB |
128B |
21 KB/s |
未记录 |
256 |
64 KB |
182B |
25 KB/s |
未记录 |
256 |
64 KB |
253B |
无回复 | 未记录 |
256 |
64 KB |
244B |
34.4 KB/s |
未记录 |
512 |
64 KB |
509B |
无回复,卡在 writing | 未记录 |
在 iOS 真机 + react-native-ble-plx + Pro2 的组合下,稳定可用的 writeWithoutResponse payload 上限更接近 244B,不是 requestMTU - 3 推导出的 253B,更不是 512 - 3 = 509B。
chunk=244B 的 raw write 约 34.4 KB/s,这是不经过 SDK、也不等待协议层 ACK 的上行天花板。真实 firmwareUpdateV4 只有 8-10 KB/s,属于 raw ceiling 上叠加设备端文件写入、FilesystemFileWrite 回包、JS/native 调度后的结果。
chunk=253B 和 chunk=509B 无回复,说明不能只信 requestMTU,需要以真机实际稳定结果为准。requestMTU=512 在当前组合下不适合作为 Pro2 BLE 固件升级参数。
raw write 会直接写非协议数据,只适合测试 GATT 写入队列,可能污染设备协议解析状态。做完 raw write 后,应断开并重新连接,再跑 Ping RTT 或 SDK 固件升级流程。
用于底层 BLE 上行测速:
requestMTU: 256
raw size: 64KB 或 256KB
raw chunk: 244B
Ping RTT iters: 40 或 100
用于 SDK 层 firmwareUpdateV4 调试:
普通 FilesystemFileWrite chunkSize: 1800B
FirmwareUpdateV4 固定 staging 路径 chunkSize: 1960B
iOS packetLength: min(协商 MTU - 3, 244B)
Android packetLength: min(协商 MTU - 3, 244B)
write mode: writeWithoutResponse
固定 burst pause: 0ms
固定 flush pause: 0ms
React Native 只在取得有效 MTU 后计算 packet capacity;缺失或无效 MTU 不猜测 244B。
iOS 连接后通过 requestMTU(247) 刷新 react-native-ble-plx 的设备快照;该调用不会要求 iOS
重新协商,但会返回由 CoreBluetooth 最大无响应写长度换算出的 MTU。CLI LowLevel 不具备平台
MTU 快照时使用经过当前设备验证的 192B fallback,而不是旧的 64B fallback。
Android 默认同样使用经过验证的 244B 上限。更大的包长只能通过显式 BLE tuning 配置用于
特定手机、固件和设备组合的真机实验,不能作为生产默认值。
不建议继续使用:
iOS raw requestMTU: 512
raw chunk: 509B
raw chunk: 253B
writeWithResponse 作为提速方案
目前更值得关注的是协议层 ACK 粒度,而不是继续微调 pacing。
FirmwareUpdateV4 的固定 staging 路径已按生产 Protocol V2 schema 测量 protobuf 和帧头开销。
最长路径 vol0:/application_p1.bin 在 uint32 最大 offset/total size 下最多容纳 1988B data,
且完整 frame 正好为 2048B;SDK 使用 1960B,保留 28B 余量。固定 wallpaper package
path 使用 1960B 时完整 frame 为 2028B,保留 20B。普通 FileWrite 和未知资源路径仍保持
1800B,因为允许的 path 最长可达 127B,此时 1960B data 会生成 2123B frame。
不能直接测试 2400B、3072B 或 4096B:这些数据块加上 protobuf 和 V2 帧开销后必然超过当前 Transport 的 2048B 限制。
如果未来要显著增大单次文件块,必须由固件接收缓冲、Protocol V2 BLE 最大帧和各 BLE Transport 的限制共同升级;只修改 chunkSize 不会绕过帧上限。
如果 raw write 维持在 34 KB/s 左右,即使放大 FilesystemFileWrite chunk,RN BLE 链路上限也会限制最终速度,很难接近 WebUSB 或 desktop BLE 的量级。
FirmwareUpdateV4 在连接恢复后不再无条件从文件偏移 0 重传。SDK 只记录设备通过
processed_byte 明确确认的文件偏移;重连、物理设备身份校验和初始化完成后,再通过
FilesystemPathInfoQuery 检查 staging 文件。只有远端文件存在、不是目录,且文件大小位于
“本地确认偏移到目标文件总大小”之间时才续传。
远端大小大于本地确认偏移时,SDK 仍从本地确认偏移重写相同数据,不会跳过响应丢失后未确认的
字节。远端文件缺失、大小落后、超过目标大小或状态查询失败时,安全回退到偏移 0 并重新覆盖。
这项优化减少了 BLE 断连后的重复传输,同时不把副作用命令的自动重放下沉到 Transport。
文件写入的内部确认回调仍在每个 ACK 后触发,用于保存续传水位和记录真实吞吐;App 可见的
FIRMWARE_PROGRESS 则只在整数百分比变化、超过 1s 心跳间隔或单文件完成时发送。这样可减少
RN bridge 和 Desktop renderer 的高频事件开销,不影响确认偏移的精度。
- 如果 raw write 明显高于 SDK 固件升级速率,瓶颈在
FilesystemFileWrite的设备处理和协议 ACK。 - 如果 raw write 自身很低,瓶颈在 RN BLE / iOS BLE /
react-native-ble-plx写入队列。 - 如果 Ping RTT 很低但固件升级慢,瓶颈更偏设备端文件系统或 flash 写入。
- 如果 Ping RTT 接近
180-220ms,则1800B下8-10 KB/s属于当前串行模型的自然结果。