FrameFilm

FrameFilm BLE 通信协议详细规范

目录

1. 概述

本文档定义 FrameFilm 设备与上位机之间的 BLE(蓝牙低功耗)通信协议。协议采用 GATT 方式进行数据传输,支持 FILM 文件传输、OTA 固件升级、设备控制、WiFi 网络配置等功能。

同源说明:两套冰箱贴固件 —— firmware/frame_film/(三机型)与 firmware/frame_film_ark/(通行证版,单机型)—— 的 BLE 协议完全同源:命令值、数据格式与回包结构一致。 app 框架与 UI 层(film_app + film_ui)只存在于 frame_film_ark;app 通道(0x45~0x4E,App 控制 / App 参数 / 时间同步 / 远程按键)及通行证资料读取 0x53 只有 frame_film_ark 实现,frame_film(三机型)是经典固件、会直接忽略这些命令。 底座固件 firmware/frame_film_dock/ 在其基础上另有专属通道(见 §3.8 键盘键值),差异以各节标注为准。

2. 协议框架

2.1 BLE GATT 通道

设备提供 3 个 GATT 数据通道 用于与上位机通信:

通道 用途 方向 说明
CH1 (CMD) 命令通道 双向 用于发送命令和接收响应
CH2 (DATA) 数据通道 1 上行 用于大数据量传输(预留)
CH3 (DATA) 数据通道 2 上行 用于大数据量传输(预留)

2.2 数据包结构

所有命令和数据均采用统一的数据包格式:

+--------+--------+--------+----------+--------+
|  HEAD  |  CH    |  LEN   |  DATA    |  SUM   |
+--------+--------+--------+----------+--------+
|  1 B   |  1 B   |  1 B   |  N B     |  1 B   |
+--------+--------+--------+----------+--------+
字段 长度 说明
HEAD 1 B 帧头,固定值 0x55
CH 1 B 通道号,标识命令类型
LEN 1 B DATA 字段的长度(字节数)
DATA N B 数据内容,长度由 LEN 指定
SUM 1 B 校验和,HEAD + CH + LEN + DATA 所有字节之和

3. 通道定义

3.1 FILM 文件传输通道 (0x00 - 0x08)

通道 名称 方向 数据格式 说明
0x00 FILE_NAME 下行 字符串 文件名或相对路径(含 / 时按显式路径写入),ASCII 编码
0x01 FILE_LEN 下行 uint32 (Big-Endian) 文件大小
0x02 FILE_DATA 下行 N bytes 文件数据内容
0x03 FILE_START 下行 无 开始传输
0x04 FILE_STOP 下行 无 结束传输
0x05 FILE_DELETE 下行 uint8 删除指定 ID 文件
0x06 FILE_LIST 上行 响应数据包 查询文件列表
0x07 FILE_DISPLAY 下行 uint8 显示指定 ID 文件
0x08 FILE_DISPLAY_GET 上行 uint8 获取当前显示文件 ID

3.2 OTA 升级通道 (0x10 - 0x13)

通道 名称 方向 数据格式 说明
0x10 OTA_LEN 下行 uint32 (Big-Endian) OTA 文件大小
0x11 OTA_DATA 下行 N bytes OTA 数据内容
0x12 OTA_START 下行 无 开始 OTA
0x13 OTA_STOP 下行 无 结束 OTA

3.3 设备控制通道 (0x20 - 0x2B)

通道 名称 方向 数据格式 说明
0x20 CTRL_MODE 下行 uint8 [已废弃] Film 模式切换,收到后忽略并打日志;播放模式改由图片 app 参数通道 (0x45) 设置
0x21 CTRL_MODE_GET 上行 uint8 [已废弃] 查询当前模式,固定回 0xFF;播放模式改由图片 app 参数通道 (0x46) 查询
0x22 CTRL_RESET 下行 无 重置设备到出厂设置
0x23 CTRL_PWRREAD 上行 uint8 获取电量 (0-100%)
0x24 CTRL_REBOOT 下行 无 重启设备
0x25 CTRL_SLEEPONOFF 下行 uint8 休眠模式开关 (0: 关闭, 1: 开启)
0x26 CTRL_SLEEPONOFF_GET 上行 uint8 查询休眠模式开关状态
0x27 CTRL_SLEEPMODE 下行 uint8 定时唤醒开关 (0: 关闭, 1: 开启)
0x28 CTRL_SLEEPMODE_GET 上行 uint8 查询定时唤醒开关状态
0x29 CTRL_SLEEPMODE_TIME 下行 uint32 (Big-Endian) 定时唤醒时间 (分钟)
0x2A CTRL_SLEEPMODE_TIME_GET 上行 uint32 (Big-Endian) 查询定时唤醒时间
0x2B CTRL_SDRESET 下行 无 SD卡格式化 (v0.2新增)

3.4 WiFi 网络配置通道 (0x30 - 0x3B) v0.2新增

通道 名称 方向 数据格式 说明
0x30 WIFI_ENABLE 下行 uint8 WiFi 开关设置 (0: 关闭, 1: 开启)
0x31 WIFI_ENABLE_GET 上行 uint8 查询 WiFi 开关状态
0x32 WIFI_SSID 下行 string 设置 WiFi SSID(最大63字符)
0x33 WIFI_SSID_GET 上行 string 查询 WiFi SSID
0x34 WIFI_PASSWORD 下行 string 设置 WiFi 密码(最大63字符)
0x35 WIFI_PASSWORD_GET 上行 string 查询 WiFi 密码
0x36 FILM_API_URL 下行 string 设置 film 下载 API 地址(最大127字符)
0x37 FILM_API_URL_GET 上行 string 查询 film API 地址
0x38 WIFI_CONNECT 下行 无 连接 WiFi
0x39 WIFI_DISCONNECT 下行 无 断开 WiFi 连接
0x3A WIFI_CONNECT_GET 上行 uint8 查询 WiFi 连接状态 (0: 未连接, 1: 已连接)
0x3B WIFI_CLEAR 下行 无 清除所有网络配置

3.5 屏幕信息通道 (0x42)

通道 名称 方向 数据格式 说明
0x42 SCREEN_RESOLUTION_GET 上行 ID(1B) + uint16 ×2 (Big-Endian) 查询屏幕面板 ID 与分辨率(宽、高)

3.6 App 控制通道 (0x4B ~ 0x4C)

适用固件:本通道仅 frame_film_ark(通行证版)实现;frame_film(三机型)是经典固件、会直接忽略 0x4B / 0x4C。

为何跳号:0x43 / 0x44 已由 Dock 底座固件占用(键盘键值设置/查询,见 §3.8), 故 App 控制通道排在 App 参数通道(0x45 ~ 0x4A)之后,使 0x45 ~ 0x4C 成为连续的 App 通道区间。 冰箱贴固件不使用 0x43 / 0x44。

本通道的远程切换(0x4B)在所有机型/屏幕上均有效,与面板无关。 它只受两点约束:

  1. app_id 必须已注册。注册集合由 sys_cfg.h 的 SYS_APP_SWITCH_MODE 决定: FULL 注册 7 个(4 个内容 app + 通行证 0x06 + 系统设置 0x04 + 主菜单 0x05);NONE/SIMPLE 仅注册 图片(0x00) 与 模板(0x01), 切到其余 id 会被忽略。
  2. 越界 app_id 被忽略(设备侧打日志 switch ignored: app id=... not registered)。

按键交互能力另由 SYS_APP_SWITCH_MODE 决定(与远程切换解耦): NONE 关闭按键切换 / SIMPLE 简易(图片 ↔ 最近推送的 app)/ FULL 全功能 (上电出开机画面 → 主菜单;菜单内 上/下 选择、确认键进入;其余 app 内长按确认键回主菜单)。 FULL 需要 UI 层可用(SYS_UI_ENABLE=1 + 面板具备 MonoFast 快刷 + 有上/下导航与确认键), 在其余硬件上会自动降级为 SIMPLE。

另注:0x04 设置 与 0x05 主菜单 都是 UI 层页面(LVGL),只在 FULL 模式下注册。

通道 名称 方向 数据格式 说明
0x4B APP_SWITCH 下行 uint8 切换 app(app_id 见下表),设备不回复
0x4C APP_CURRENT_GET 上行 uint8 查询当前 app(返回 app_id,0xFF 表示未知)
0x4F ANIM_PLAY 下行 + 应答 文件名 UTF-8 + NUL 按文件名播放动画并切到动图 app;回 1 字节 status

0x4F 不是 app_id 切换命令:它解析 /sdcard/animation 列表、选中目标文件后切到动图 app。 status:0 成功 / 1 文件不存在或不可播放 / 2 不支持 / 3 设备忙或休眠流程中 / 4 参数错误。

app_id 应用
0x00 图片 image
0x01 模板 template
0x02 时钟 clock
0x03 动图 animation
0x04 系统设置 settings(UI 层)
0x05 主菜单 menu(UI 层,调度器根状态)
0x06 通行证 pass(SD 个人档案,UI 层显示)

3.7 App 参数通道 (0x45 ~ 0x4A) v1.7新增

适用固件:本通道仅 frame_film_ark(通行证版)实现;frame_film(三机型)会直接忽略 0x45 ~ 0x4A。

app 各自的可配置参数(播放模式、定时间隔、网络拉取等)通过本通道读写,与屏幕面板无关。 通道号与 app 的绑定在 app 注册时建立:约定 param_ch 为设置通道,查询通道 = param_ch + 1。 时钟 app 无参数,不占通道。

路由原则:按 app 归属路由,不按当前 app。手机可以在显示图片时预设动图/模板参数,设备照样写入目标 app(目标 app 无需处于运行态)。

通道 名称 方向 数据格式 说明
0x45 APP_IMAGE_PARAM 下行 TLV 列表 图片 app 参数设置,设备不回包
0x46 APP_IMAGE_PARAM_GET 上行 TLV 列表 图片 app 参数查询
0x47 APP_TEMPLATE_PARAM 下行 TLV 列表 模板 app 参数设置,设备不回包
0x48 APP_TEMPLATE_PARAM_GET 上行 TLV 列表 模板 app 参数查询
0x49 APP_ANIM_PARAM 下行 TLV 列表 动图 app 参数设置,设备不回包
0x4A APP_ANIM_PARAM_GET 上行 TLV 列表 动图 app 参数查询

TLV 结构(DATA 部分):

[TAG(1B)][LEN(1B)][VALUE(LEN B)] [TAG(1B)][LEN(1B)][VALUE(LEN B)] ...

通用约定:

  1. 增量设置:一包可携带任意个 TLV,只发想改的字段即可,未出现的字段保持原值。
  2. 未知 TAG 跳过:设备不认识的 TAG 按 LEN 安全跳过,不报错。
  3. LEN 不符:某 TAG 的 LEN 与定义不符时,仅跳过该 TAG(打警告),不整包丢弃。
  4. SET 不回包:设置通道(0x45/0x47/0x49)静默生效,需要确认请用对应 GET 通道回读。
  5. GET 空包:目标 app 未注册或 LEN=0 时,返回 LEN=0 的空数据包。
  6. 立即生效:设置后参数立即写入并持久化(NVS);涉及画面刷新的字段(如 file_id)仅在目标 app 为当前 app 时刷新屏幕。
  7. 单包长度限制:下行 SET 经设备事件总线投递,单包 DATA 建议不超过 15 字节(超出会被截断)。字段较多时请拆分为多包下发,每包可只含一个 TLV。

3.8 Dock 键盘键值通道 (0x43 ~ 0x44,仅 Dock 底座)

这两个通道只在 Dock 底座固件(firmware/frame_film_dock/)中实现。Dock 的按键不操作本机, 而是作为 PC 键盘:单击/双击/长按按配置好的 HID 键值发送。冰箱贴固件不使用 0x43 / 0x44 (其 App 通道为 0x45 ~ 0x4C),对这两个命令号不做处理。

报文格式、校验和与其他通道完全一致;Dock 的 BLE 与 USB(CDC) 共用同一套命令解析, 两条链路均可下发,应答按来源链路原路返回。

通道 名称 方向 数据格式 说明
0x43 KEYBOARD_KEY_SET 下行 事件(1B) + 修饰键(1B) + 键数(1B) + 键码(nB) 设置某事件的 HID 键值,设备不回复
0x44 KEYBOARD_KEY_GET 上行 [修饰键(1B) + 键数(1B) + 键码(6B)] × N 查询键值,最多返回单击/双击/长按三组

事件取值:0 单击(SHORT)/ 1 双击(DOUBLE)/ 2 长按(LONG)。

修饰键:HID 修饰键位掩码 —— bit0 Ctrl / bit1 Shift / bit2 Alt / bit3 GUI,0x00 表示无修饰键。

键码:HID Usage ID 列表(0x04=A …… 0x29=Esc),组合键时按顺序填入, 单组最多 6 个(SERVICE_KEY_MAX_NUM);键数为 0 表示该事件不发送任何按键。

3.9 时间同步通道 (0x4D) v1.9新增

适用固件:本通道仅 frame_film_ark(通行证版)实现;frame_film(三机型)会直接忽略 0x4D。

连接端(Web / 小程序)把本机时间 + 本机时区一次性下发给设备,用于校正设备时钟。

通道 名称 方向 数据格式 说明
0x4D TIME_SYNC 下行 + 应答 Unix秒(uint32, 4B) + 时区(int16, 2B) 设置时间与时区,设备回显同样的 6 字节表示已应用

载荷(6 字节):

偏移 长度 含义
0 4 Unix 时间戳(UTC,秒),大端 uint32
4 2 时区:距 UTC 的分钟数,大端有符号 int16,东为正(香港/北京 = +480,UTC = 0,纽约 = -300)

取值校验(不通过则不回包,只写设备日志):

语义:

3.10 远程按键注入通道 (0x4E) v1.10新增

连接端(Web 遥控面板)模拟设备本机按键下发,用于在手机/电脑上远程操作菜单与页面。 该通道只在通行证版 frame_film_ark 实现(需 UI 层与上/下 + 确认键,SYS_APP_SWITCH_MODE=FULL);frame_film(三机型)会直接忽略。 Dock 底座的按键是 PC 键盘(USB HID,见 §3.8),本机不做导航,不支持本通道。

通道 名称 方向 数据格式 说明
0x4E KEY_INJECT 下行 + 应答 KEY(1B) 注入一次按键,设备回显该 1 字节表示已收到

键值定义(KEY 字节):

值 宏 语义(与设备本机按键完全一致)
0x00 BLE_KEY_SHORT 确认键单击(确认 / 进入)
0x01 BLE_KEY_LONG 确认键长按(= 手动休眠)
0x02 BLE_KEY_UP 上(编码器 +)
0x03 BLE_KEY_DOWN 下(编码器 −)
0x04 BLE_KEY_DOUBLE 确认键双击(= 从 app 退回主菜单)

语义与约束:

3.11 Ark 临时 WiFi 直传通道 (0x50 ~ 0x52)

仅 frame_film_ark 与 Android 直传客户端使用,经典固件和 Dock 不支持。

通道 命令 请求 DATA 同通道响应 DATA
0x50 DIRECT_START SSID\0PASSWORD\0URL\0 1B:0 接受 / 1 忙 / 2 参数错误 / 3 资源不足
0x51 DIRECT_STATUS 空 11B:state、progress、error、received(u32 BE)、total(u32 BE)
0x52 DIRECT_CANCEL 空 1B:0 已接受取消请求(幂等)

3.12 Ark 通行证资料读取 (0x53)

仅 frame_film_ark 实现,读取固定的 /sdcard/app/pass/profile.json;经典固件和 Dock 不支持。

通道 命令 请求 DATA 同通道响应 DATA
0x53 PROFILE_READ offset(u32 BE) + count(u8,1–128) status(1) + offset(u32 BE) + total(u32 BE) + bytes(0–128)

3.13 Ark 设备名称(0x54 / 0x55,固件 3.2.5 起)

通道 命令 请求 DATA 同通道响应 DATA
0x54 DEVICE_NAME_GET 空 status(1) + 已配置完整名称 UTF-8 + NUL
0x55 DEVICE_NAME_SET 名称后缀 UTF-8 + NUL status(1) + 已配置完整名称 UTF-8 + NUL

仅 Ark 实现;详情见 §4.13。

3.14 Ark WiFi OTA 通道(0x56~0x58)

仅 frame_film_ark 支持,完整状态和提交语义见 §4.14。

通道 名称 请求 响应
0x56 BUILD_INFO_GET 空 能力版本、OTA容量、实际ELF SHA256、version、project
0x57 DIRECT_OTA_START size(u32 BE) + 文件SHA256(32B) + SSID\0PASSWORD\0URL\0 0接受 / 1忙 / 2参数 / 3资源
0x58 DIRECT_OTA_APPLY 空 0已激活 / 1非READY / 2激活失败

3.15 Ark 能力查询通道(0x59)

仅 frame_film_ark 支持,文件格式与兼容规则见 §4.15。

通道 名称 请求 响应
0x59 CAPABILITIES_GET 空 版本 1(u8) + flags(u32 BE),bit0=彩色通行证、bit1=通用 WiFi 文件上传、bit2=只读 SD 目录与统计、bit3=按文件名打开图片、bit4=Ark SoftAP 直传、bit5=实验 ABC9 512 色预览播放、bit6=硬件身份查询、bit7=NFC 卡包(仅 N16R8)、bit8=按目录/文件名管理文件(0x60/0x61 + 0x5B 目录3)、bit9=返回键双击截图(/sdcard/screenshot,仅 CONFIG_ARK_SCREENSHOT=y 固件)

3.16 Ark 通用 WiFi 文件上传(0x5A)

仅 frame_film_ark 支持,完整会话与路径语义见 §4.16。

通道 名称 请求 DATA 同通道响应 DATA
0x5A WIFI_DIRECT_FILE_START SSID\0PASSWORD\0URL\0相对目标路径\0 1B:0 接受 / 1 忙 / 2 参数错误 / 3 资源不足

3.17 Ark SoftAP 准备(0x5C)

仅 frame_film_ark 支持,拓扑与兼容规则见 §4.18。

通道 名称 请求 DATA 同通道响应 DATA
0x5C DIRECT_AP_PREPARE 空 status(u8);status=0 时追加 SSID\0PASSWORD\0IP\0,失败时仅 status

3.18 Ark 硬件身份(0x5D)

通道 名称 请求 DATA 同通道响应 DATA
0x5D HARDWARE_INFO_GET 空 schema(u8=1)、chipId(u16 BE)、chipRevision(u16 BE)、physicalFlashBytes(u32 BE)、psramBytes(u32 BE)、psramBus(u8)、targetId(ASCII NUL)

3.19 NFC 卡包通道(0x63 ~ 0x69,仅 n16r8)

仅 frame_film_ark 的 n16r8 变体(CONFIG_ARK_HAS_NFC)实现,卡库保存在 SD 卡(wallet.bin,封面 cover_<id>.bin)。n4r2 不处理 NFC 操作,经典固件不实现该功能。字段与状态码见 §4.20。

通道 名称 请求 DATA 同通道响应 DATA
0x63 NFC_CARD_BEGIN kind(1) mode(1) uid_len(1) sak(1) atqa(2) uid(uid_len) name_len(1) name( name_len ) total(2) crc32(4) 成功 0, 0, 180(3B);失败 1B 状态
0x64 NFC_CARD_DATA offset(2, BE) + chunk(≤180) status, received(2, BE)(3B)
0x65 NFC_CARD_COMMIT 空 status, id, count, current, uid_len, uid...
0x66 NFC_WALLET_CTRL op(1) [+参数] 见 §4.20.4
0x67 NFC_COVER_BEGIN id(1) fmt(1) w(2) h(2) total(2) crc32(4) 成功 0, 0, 180;失败 1B
0x68 NFC_COVER_DATA offset(2, BE) + chunk(≤180) status, received(2, BE)(3B)
0x69 NFC_COVER_COMMIT 空 status, id, 1(3B)

状态码:0 OK / 1 BUSY / 2 PARAM / 3 NOSD / 4 NOTFOUND / 5 NOMEM / 6 SAVE / 7 EMU_FAIL。

3.20 Ark 实时日志通道(0x5E / 0x5F)

仅 frame_film_ark 实现。0x5E 为开关/查询,0x5F 为设备单向推送的日志流(沿用 CH1 notify,与命令回包同一个特征值,靠通道号区分)。详见 §4.21。

通道 名称 请求 DATA 同通道响应 DATA
0x5E LOG_STREAM_CTRL enable(1):0=关 1=开;空=查询 state(1):0=关 1=开
0x5F LOG_STREAM_DATA —(仅设备→客户端) 原始日志文本(UTF-8,≤192B/帧,一行可能跨帧)

默认关闭:设备不安装日志钩子、不建任务、不分配缓冲,行为与未实现该功能时完全一致。打开后设备按当前日志级别(不改变全局级别)采集日志;关闭或 BLE 断开时自动卸载钩子。

3.21 Ark 文件管理与截图(0x60 / 0x61 + 0x5B 目录 3)

仅 frame_film_ark 实现(经典固件忽略)。用于网页管理 /sdcard/animation 与 /sdcard/screenshot, 不依赖设备当前 app 与活跃目录。完整字段与状态码见 §4.22。

通道 名称 请求 DATA 同通道响应 DATA
0x5B STORAGE_CATALOG dir(1) + index(u32 BE),dir 1/2/3 status(1) + total(u32 BE) + name(UTF-8,NUL)
0x60 FILE_PATH_DELETE dir(1) + name(UTF-8,NUL) status(1)
0x61 FILE_PATH_READ dir(1) + offset(u32 BE) + count(1) + name(UTF-8,NUL) status(1) + offset(u32 BE) + total(u32 BE) + bytes(0..180)

目录号:1=/sdcard/film、2=/sdcard/animation、3=/sdcard/screenshot。 0x5B 目录 3 与 0x60/0x61 需要能力位 bit8;截图功能(返回键双击)需要 bit9。 截图文件为单帧 MonoFast .film(720×480、1bpp、1=黑),命名 shot_*.film。

默认关闭:设备不安装日志钩子、不建任务、不分配缓冲,行为与未实现该功能时完全一致。打开后设备按当前日志级别(不改变全局级别)采集日志;关闭或 BLE 断开时自动卸载钩子。

4. 命令详解

4.1 通用规则

  1. 校验和计算:SUM = HEAD + CH + LEN + 所有 DATA 字节(只取低 8 位)
  2. 字符串格式:除命令另有规定外,文本使用 ASCII 编码,以 \0 结尾;设备名称 0x54/0x55 使用 UTF-8,通行证 JSON 的编码由文件内容定义。
  3. 整数格式:多字节整数采用 Big-Endian(大端序)编码
  4. 响应延迟:列表查询时每条响应间隔 10ms,避免阻塞

4.2 FILM 文件传输

4.2.1 传输流程

客户端                              设备
  |                                  |
  | -------- FILE_START ------------> | (1) 开始传输
  |                                  |
  | -------- FILE_NAME ------------> | (2) 发送文件名
  |                                  |
  | -------- FILE_LEN ---------------> | (3) 发送文件大小
  |                                  |    (4) 打开文件准备写入
  |                                  |
  | -------- FILE_DATA 1 -----------> | (5) 发送数据块 1
  | -------- FILE_DATA 2 -----------> | (6) 发送数据块 2
  | -------- FILE_DATA N -----------> | (7) 发送数据块 N
  |                                  |
  | -------- FILE_STOP -------------> | (8) 结束传输
  |                                  |    (9) 保存文件

4.2.2 状态机

          ┌────────────────────────────────────────────────────────┐
          │                                                         │
          ▼                                                         │
    ┌──────────┐                                                    │
    │   IDLE   │◄───────────────────────────────┐                   │
    └────┬─────┘                                │                   │
         │ FILE_START                           │ (完成)            │
         ▼                                       │                   │
    ┌──────────┐                                │                   │
    │ STARTED  │───────────────────────────────┘                   │
    └────┬─────┘                                │                   │
         │ FILE_NAME                            │ (错误)            │
         ▼                                       │                   │
    ┌──────────────┐                            │                   │
    │  RECV_NAME   │───────────────────────────┘                   │
    └──────┬───────┘                                                │
           │ FILE_LEN                                               │
           ▼                                                        │
    ┌──────────────┐                                                │
    │  RECV_LEN    │                                                │
    └──────┬───────┘                                                │
           │ FILE_DATA                                              │
           ▼                                                        │
    ┌──────────────┐                                                │
    │  RECV_DATA   │ ───────────────────────────────> FILE_STOP ────┘
    └──────────────┘

4.2.3 详细命令

FILE_START (0x03)

方向:下行(客户端 → 设备)

数据:无

说明:开始 FILM 文件传输,重置传输状态,清空缓冲区。


FILE_NAME (0x00)

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+ ... +--------+
|  NAME_LEN (1B) |  FILENAME (N B)     |
+--------+--------+--------+ ... +--------+

说明:

示例 1:传输文件 test.film(普通模式)

HEAD    = 0x55
CH      = 0x00
LEN     = 10
DATA    = [0x74, 0x65, 0x73, 0x74, 0x2E, 0x66, 0x69, 0x6C, 0x6D, 0x00]
         "test.film\0"
SUM     = 0x55 + 0x00 + 0x0A + 0x74 + ... + 0x00 = 0x1F9 (低8位 = 0xF9)

示例 2:写入图片 app 封面(显式路径模式,写入 /sdcard/app/image/cover.film)

HEAD    = 0x55
CH      = 0x00
LEN     = 21
DATA    = "app/image/cover.film\0"
SUM     = (0x55 + 0x00 + 0x15 + 所有字符字节) & 0xFF

后续 FILE_LEN / FILE_DATA / FILE_STOP 流程与普通模式完全一致。


FILE_LEN (0x01)

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+--------+
|  FILE_SIZE (4 B, Big-Endian)     |
+--------+--------+--------+--------+

说明:

示例:文件大小 = 10000

HEX: 00 00 27 10
DEC: 10000 bytes

FILE_DATA (0x02)

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+ ... +--------+
|  DATA (N B)                        |
+--------+--------+--------+ ... +--------+

说明:


FILE_STOP (0x04)

方向:下行(客户端 → 设备)

数据:可选(1 字节 flag)

LEN DATA 含义
0(默认,兼容旧版) 无 保存完成后自动加载显示新文件
1 0x00 显式指定自动加载显示
1 0x01 静默保存:保存并刷新文件列表,但不自动加载显示(批量上传用)

说明:结束 FILM 文件传输,关闭文件句柄,完成保存。v0.3 新增可选 flag 参数。 默认(LEN=0)保持旧行为。批量上传场景下客户端每张携带 0x01,避免设备反复刷新 EPD;全部传完后由客户端显式发送 FILE_DISPLAY (0x07) 上屏最后一张。 旧固件不识别该参数时会忽略多余字节,按自动加载处理,兼容安全。

Ark 文件保存与重试行为(仅 frame_film_ark):


FILE_DELETE (0x05)

方向:下行(客户端 → 设备)

数据格式:

+--------+
|  FILE_ID (1 B)  |
+--------+

说明:

响应:无


FILE_LIST (0x06)

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x06 的命令(数据为空)

响应数据格式(每条文件一条响应):

+--------+--------+--------+----------+--------+ ... +--------+
|  HEAD  |   CH   |  LEN   |  FILE_ID | NAME_LEN | FILENAME |
+--------+--------+--------+----------+--------+ ... +--------+
|  0x55  |  0x06  | 2+N+1  |   1 B    |   1 B    |  N B     |
+--------+--------+--------+----------+--------+ ... +--------+

说明:

多文件响应:设备会逐条发送所有文件信息,间隔 10ms。 文件名放不进单条通知时(帧长 6+N+1 超过 GATT 上限 200 字节,即名字超过 193 字节)该条会被跳过并打日志,不出现在列表里。

示例:

文件列表:
ID=0: "frame1.film"
ID=1: "frame2.film"

响应包 1:
HEAD = 0x55
CH   = 0x06
LEN  = 2 + 11 = 13 (0x0D)
DATA = [0x00, 0x0B, 0x66, 0x72, 0x61, 0x6D, 0x65, 0x31, 0x2E, 0x66, 0x69, 0x6C, 0x6D]
      = [ID(0), NAME_LEN(11), "frame1.film\0"]

响应包 2:
HEAD = 0x55
CH   = 0x06
LEN  = 2 + 11 = 13
DATA = [0x01, 0x0B, 0x66, 0x72, 0x61, 0x6D, 0x65, 0x32, 0x2E, 0x66, 0x69, 0x6C, 0x6D]
      = [ID(1), NAME_LEN(11), "frame2.film\0"]

FILE_DISPLAY (0x07)

方向:下行(客户端 → 设备)

数据格式:

+--------+
|  FILE_ID (1 B)  |
+--------+

说明:

响应:无


FILE_DISPLAY_GET (0x08)

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x08 的命令(数据为空)

响应数据格式:

+--------+--------+--------+----------+--------+
|  HEAD  |   CH   |  LEN   |  FILE_ID |  SUM   |
+--------+--------+--------+----------+--------+
|  0x55  |  0x08  |   1    |   1 B    |  1 B   |
+--------+--------+--------+----------+--------+

说明:


4.3 OTA 升级

4.3.1 传输流程

客户端                              设备
  |                                  |
  | -------- OTA_LEN ----------------> | (1) 发送固件大小
  |                                  |    (2) 初始化 OTA
  | -------- OTA_START -------------> | (3) 开始升级
  | -------- OTA_DATA 1 -----------> | (4) 发送数据块 1
  | -------- OTA_DATA 2 -----------> | (5) 发送数据块 2
  | -------- OTA_DATA N -----------> | (6) 发送数据块 N
  | -------- OTA_STOP --------------> | (7) 完成升级
  |                                  |

4.3.2 状态机

    ┌──────────────────────────────────────────────────────────────┐
    │                                                               │
    ▼                                                               │
┌────────┐                                                          │
│  IDLE  │◄─────────────────────────────────────────────────────┐   │
└────┬───┘                                                      │   │
     │ OTA_LEN                                                  │   │
     ▼                                                           │   │
┌────────────┐                                                   │   │
│ RECV_LEN   │◄──────────────────┐                              │   │
└──────┬─────┘                    │                              │   │
       │ OTA_DATA                 │ (OTA_START 直接开始)         │   │
       ▼                          │                              │   │
┌────────────┐                    │                              │   │
│ RECV_DATA  │ ──────────────────────────────────────────────────> OTA_STOP
└────────────┘                                                     │
                                                                 │

4.3.3 详细命令

OTA_LEN (0x10)

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+--------+
|  OTA_SIZE (4 B, Big-Endian)    |
+--------+--------+--------+--------+

说明:固件包总大小


OTA_DATA (0x11)

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+ ... +--------+
|  DATA (N B)                        |
+--------+--------+--------+ ... +--------+

说明:固件数据内容,可分多次发送


OTA_START (0x12)

方向:下行(客户端 → 设备)

数据:无

说明:开始 OTA 传输,进入升级模式


OTA_STOP (0x13)

方向:下行(客户端 → 设备)

数据:无

说明:完成 OTA 传输,设备会验证并安装固件


4.4 设备控制

4.4.1 CTRL_MODE (0x20) - Film 模式切换 [已废弃]

[已废弃] 播放模式语义已下移到图片 app 参数通道(0x45 的 play_mode / auto_interval,见 §4.8.1)。 本命令号按「命令值不再变更」原则保留,设备收到后忽略并打日志,不产生任何效果。

方向:下行(客户端 → 设备)

数据格式:

+--------+
|  MODE (1 B)  |
+--------+
MODE 值 含义
0 手动模式
1 本地轮播模式
2 WiFi 轮播模式 (v0.2新增)

响应:无(已废弃,设备不再处理)


4.4.2 CTRL_MODE_GET (0x21) - 查询当前模式 [已废弃]

[已废弃] 播放模式改由图片 app 参数通道(0x46,见 §4.8.1)查询。 本命令设备固定回 0xFF,表示已废弃。

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x21 的命令

响应数据格式:

+--------+--------+--------+----------+--------+
|  HEAD  |   CH   |  LEN   |  MODE    |  SUM   |
+--------+--------+--------+----------+--------+
|  0x55  |  0x21  |   1    |   0xFF   |  1 B   |
+--------+--------+--------+----------+--------+

4.4.3 CTRL_RESET (0x22) - 重置设备

方向:下行(客户端 → 设备)

数据:无

说明:重置所有参数到出厂默认设置,设备会自动重启

响应:无


4.4.4 CTRL_PWRREAD (0x23) - 获取电量

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x23 的命令

响应数据格式:

+--------+--------+--------+----------+--------+
|  HEAD  |   CH   |  LEN   | PERCENT  |  SUM   |
+--------+--------+--------+----------+--------+
|  0x55  |  0x23  |   1    |   1 B    |  1 B   |
+--------+--------+--------+----------+--------+

说明:PERCENT 为电量百分比 (0-100)


4.4.5 CTRL_REBOOT (0x24) - 重启设备

方向:下行(客户端 → 设备)

数据:无

说明:设备执行软件重启

响应:无


4.4.6 CTRL_SLEEPONOFF (0x25) - 休眠模式开关

方向:下行(客户端 → 设备)

数据格式:

+--------+
|  STATE (1 B)  |
+--------+
STATE 值 含义
0 关闭休眠模式
1 开启休眠模式

响应:无


4.4.7 CTRL_SLEEPONOFF_GET (0x26) - 查询休眠模式开关

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x26 的命令

响应数据格式:

+--------+--------+--------+----------+--------+
|  HEAD  |   CH   |  LEN   |  STATE   |  SUM   |
+--------+--------+--------+----------+--------+

4.4.8 CTRL_SLEEPMODE (0x27) - 定时唤醒开关

方向:下行(客户端 → 设备)

数据格式:

+--------+
|  STATE (1 B)  |
+--------+
STATE 值 含义
0 关闭定时唤醒
1 开启定时唤醒

响应:无


4.4.9 CTRL_SLEEPMODE_GET (0x28) - 查询定时唤醒开关

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x28 的命令

响应数据格式:

+--------+--------+--------+----------+--------+
|  HEAD  |   CH   |  LEN   |  STATE   |  SUM   |
+--------+--------+--------+----------+--------+

4.4.10 CTRL_SLEEPMODE_TIME (0x29) - 设置定时唤醒时间

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+--------+
|  TIME (4 B, Big-Endian)        |
+--------+--------+--------+--------+

说明:TIME 为定时唤醒时间,单位为分钟

响应:无


4.4.11 CTRL_SLEEPMODE_TIME_GET (0x2A) - 查询定时唤醒时间

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x2A 的命令

响应数据格式:

+--------+--------+--------+--------------------------+--------+
|  HEAD  |   CH   |  LEN   |  TIME (4 B, Big-Endian) |  SUM   |
+--------+--------+--------+--------------------------+--------+
|  0x55  |  0x2A  |   4    |        4 B              |  1 B   |
+--------+--------+--------+--------------------------+--------+

4.4.12 CTRL_SDRESET (0x2B) - SD卡格式化 v0.2新增

方向:下行(客户端 → 设备)

数据:无

说明:格式化 SD 卡(SDNAND),清除所有存储的数据文件。

响应:无

注意:此操作不可逆,所有 .film 文件将被永久删除。


4.5 WiFi 网络配置 v0.2新增

WiFi 功能仅在 FrameFilm Pro 版本上可用。通过 BLE 命令配置设备以 STA 模式连接路由器,支持 HTTP 下载 film 文件。

4.5.1 配网流程

客户端                              设备
  |                                  |
  | -- WIFI_ENABLE(1) ------------> | (1) 开启 WiFi
  | -- WIFI_SSID("MyWiFi") ------> | (2) 设置 SSID
  | -- WIFI_PASSWORD("pwd123") --> | (3) 设置密码
  | -- WIFI_CONNECT --------------> | (4) 连接 WiFi
  |                                  |     - 初始化 WiFi STA
  |                                  |     - 连接路由器
  |                                  |     - 获取 IP 地址
  | <-- WIFI_CONNECT_GET --------- | (5) 查询连接状态
  |        返回: 0x01 (已连接)       |

4.5.2 WIFI_ENABLE (0x30) - WiFi 开关

方向:下行(客户端 → 设备)

数据格式:

+--------+
|  STATE (1 B)  |
+--------+
STATE 值 含义
0 关闭 WiFi
1 开启 WiFi

响应:无

说明:设置为 1 后设备会初始化 WiFi 硬件。若 SSID 已配置,WiFi 初始化完成后自动连接。


4.5.3 WIFI_ENABLE_GET (0x31) - 查询 WiFi 开关

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x31 的命令

响应数据格式:

+--------+--------+--------+----------+--------+
|  HEAD  |   CH   |  LEN   |  STATE   |  SUM   |
+--------+--------+--------+----------+--------+
|  0x55  |  0x31  |   1    |   1 B    |  1 B   |
+--------+--------+--------+----------+--------+

4.5.4 WIFI_SSID (0x32) - 设置 WiFi SSID

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+ ... +--------+
|  SSID (N B, 最大63字符)            |
+--------+--------+--------+ ... +--------+

说明:SSID 为 ASCII 编码的字符串,最大 63 字符。


4.5.5 WIFI_SSID_GET (0x33) - 查询 WiFi SSID

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x33 的命令

响应数据格式:

+--------+--------+--------+----------+--------+
|  HEAD  |   CH   |  LEN   |  SSID    |  SUM   |
+--------+--------+--------+----------+--------+
|  0x55  |  0x33  |   N    |  N B     |  1 B   |
+--------+--------+--------+----------+--------+

4.5.6 WIFI_PASSWORD (0x34) - 设置 WiFi 密码

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+ ... +--------+
|  PASSWORD (N B, 最大63字符)        |
+--------+--------+--------+ ... +--------+

说明:密码为 ASCII 编码的字符串,最大 63 字符。


4.5.7 WIFI_PASSWORD_GET (0x35) - 查询 WiFi 密码

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x35 的命令

响应数据格式:

+--------+--------+--------+-------------+--------+
|  HEAD  |   CH   |  LEN   |  PASSWORD   |  SUM   |
+--------+--------+--------+-------------+--------+
|  0x55  |  0x35  |   N    |  N B        |  1 B   |
+--------+--------+--------+-------------+--------+

4.5.8 FILM_API_URL (0x36) - 设置 Film 下载 API 地址

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+ ... +--------+
|  URL (N B, 最大127字符)            |
+--------+--------+--------+ ... +--------+

说明:设备通过 HTTP GET 请求该 URL 下载 film 文件。最大 127 字符。


4.5.9 FILM_API_URL_GET (0x37) - 查询 API 地址

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x37 的命令

响应数据格式:

+--------+--------+--------+----------+--------+
|  HEAD  |   CH   |  LEN   |  URL     |  SUM   |
+--------+--------+--------+----------+--------+
|  0x55  |  0x37  |   N    |  N B     |  1 B   |
+--------+--------+--------+----------+--------+

4.5.10 WIFI_CONNECT (0x38) - 连接 WiFi

方向:下行(客户端 → 设备)

数据:无

说明:使用已配置的 SSID 和密码连接 WiFi。连接结果通过 WIFI_CONNECT_GET 查询。


4.5.11 WIFI_DISCONNECT (0x39) - 断开 WiFi

方向:下行(客户端 → 设备)

数据:无

说明:断开当前 WiFi 连接。


4.5.12 WIFI_CONNECT_GET (0x3A) - 查询 WiFi 连接状态

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x3A 的命令

响应数据格式:

+--------+--------+--------+----------+--------+
|  HEAD  |   CH   |  LEN   |  STATE   |  SUM   |
+--------+--------+--------+----------+--------+
|  0x55  |  0x3A  |   1    |   1 B    |  1 B   |
+--------+--------+--------+----------+--------+
STATE 值 含义
0 未连接
1 已连接

4.5.13 WIFI_CLEAR (0x3B) - 清除所有网络配置

方向:下行(客户端 → 设备)

数据:无

说明:断开 WiFi 连接,清除 SSID、密码、API URL 等所有网络配置参数,并保存到 NVS。


4.6 屏幕信息

4.6.1 SCREEN_RESOLUTION_GET (0x42) - 查询屏幕面板 ID 与分辨率

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x42 的命令(数据为空)

响应数据格式:

+--------+--------+--------+-----------------------------------+--------+
|  HEAD  |   CH   |  LEN   |  ID + WIDTH + HEIGHT (5 B)       |  SUM   |
+--------+--------+--------+-----------------------------------+--------+
|  0x55  |  0x42  |   5    |  ID(1B) + W(2B) + H(2B) 大端     |  1 B   |
+--------+--------+--------+-----------------------------------+--------+

说明:

ID 屏幕面板 分辨率
0x01 3.68” 792×528
0x02 3.70” 720×480
0x03 3.60” 600×400
0x04 1.54” 240×240
0x05 7.09” 双面板 1200×1600
0x06 3.64” 760×568
0x07 3.98”(SE0398,六色) 768×552
0x08 3.98”(JD79665AA,四色) 768×552

面板 ID 只描述屏幕,不携带机型。0x08 为四色屏(黑/白/黄/红),客户端收到后应按 4 色出图;仅 frame_film(基础版 / Pro 接线)提供该选项。


4.7 App 控制

适用固件:本节命令(0x4B / 0x4C)仅 frame_film_ark(通行证版)实现;frame_film(三机型)会直接忽略。

4.7.1 APP_SWITCH (0x4B) - 切换 app

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+-------------+--------+
|  HEAD  |   CH   |  LEN   |  APP_ID(1B) |  SUM   |
+--------+--------+--------+-------------+--------+
|  0x55  |  0x4B  |   1    |   见 3.6    |  1 B   |
+--------+--------+--------+-------------+--------+

说明:LEN=1 请求切换到指定 app,保持原有恢复上次内容的行为,不返回应答。

Ark 按文件名原子打开图片扩展(先查询 0x59 bit3):

生效条件(与面板无关,任何机型/屏幕都可远程切换):

注:注册集合按 sys_cfg.h 的 SYS_APP_SWITCH_MODE 配置值判定。FULL 在非 3.7” 屏上虽按键交互降级为简易模式,仍保留 4 个 app 的注册,故远程切换行为不变。

4.7.2 APP_CURRENT_GET (0x4C) - 查询当前 app

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x4C 的命令(数据为空)

响应数据格式:

+--------+--------+--------+-------------+--------+
|  HEAD  |   CH   |  LEN   |  APP_ID(1B) |  SUM   |
+--------+--------+--------+-------------+--------+
|  0x55  |  0x4C  |   1    |   见 3.6    |  1 B   |
+--------+--------+--------+-------------+--------+

说明:APP_ID 为当前运行的 app(0xFF 表示 app 层尚未注册)。


4.8 App 参数(0x45 ~ 0x4A)

适用固件:本节命令(0x45 ~ 0x4A)仅 frame_film_ark(通行证版)实现;frame_film(三机型)会直接忽略。

4.8.0 通用报文

TLV 元素格式(DATA 部分,可重复出现):

+---------+---------+---------------+
| TAG(1B) | LEN(1B) | VALUE(LEN B)  |
+---------+---------+---------------+

设置报文示例(图片 app 设置播放模式=自动、定时间隔=30 分钟):

DATA = 01 01 01  02 02 00 1E
       └─TAG=0x01 play_mode, LEN=1, 值=1(自动)
                └─TAG=0x02 auto_interval, LEN=2, 大端 0x001E=30

GET 响应:DATA 为全部 TAG 的 TLV 顺序回填(顺序与下方各表一致),未在当前设备支持的 TAG 不出现。

GET 与当前显示的 app 无关。目标 app 尚未载入时,固件先从其原有 NVS 槽载入参数;没有有效数据则使用该 app 默认值,再组装回包。重复 GET 复用已载入状态,不切换页面、不启动播放、不写回 NVS。

多字节整数一律 Big-Endian(沿用 §4.1)。


4.8.1 图片 app 参数(0x45 / 0x46)

TAG 名称 LEN 取值 说明
0x01 play_mode 1 0=手动 1=自动 自动形态由休眠开关决定(见下)
0x02 auto_interval 2 1 ~ 120(分钟,大端) 自动切换间隔,仅休眠关闭时生效
0x03 file_id 4 0 ~ N-1(大端) 设置后立即跳转显示(仅目标 app 为当前 app 时刷屏)

自动模式两种形态:

GET 响应长度:3 + 4 + 6 = 13 字节。

示例:查询 → 设备回 01 01 00 02 02 00 1E 03 04 00 00 00 03 (play_mode=0 手动,auto_interval=30 分钟,file_id=3)。


4.8.2 模板 app 参数(0x47 / 0x48)

TAG 名称 LEN 取值 说明
0x01 pull_enable 1 0=关闭 1=开启 网络拉取开关
0x02 pull_interval 2 1 ~ 120(分钟,大端) 定时拉取间隔,仅休眠关闭时生效

自动拉取两种形态(均需 pull_enable=1):

GET 响应长度:3 + 4 = 7 字节。


4.8.3 动图 app 参数(0x49 / 0x4A)

TAG 名称 LEN 取值 说明
0x01 play_mode 1 0=单 film 循环 1=列表循环  
0x02 frame_ms 2 100 ~ 2000(毫秒,大端) 每帧停留时间(播放速度)
0x03 loop_seconds 2 0 ~ 600(秒,大端) 循环间隔:一个 film 播完到播下一个的等待时间,0=不等待
0x04 file_id 4 0 ~ N-1(大端) 当前播放的文件下标

播放模式:

GET 响应长度:3 + 4 + 4 + 6 = 17 字节。

注意:frame_ms 下限为 100(与设备 100ms 心跳基准对齐,更小的值不会生效)。若一包同时下发 4 个 TAG(合计 17 字节),超出设备事件总线的投递上限,请分包下发(每包 1~2 个 TAG)。


4.9 Dock 键盘键值(0x43 / 0x44,仅 Dock 底座)

通道概览见 §3.8。以下命令仅 firmware/frame_film_dock/ 实现,冰箱贴固件不支持。

4.9.1 KEYBOARD_KEY_SET (0x43) - 设置按键键值

方向:下行(客户端 → 设备)

数据格式:

+--------+--------+--------+-----------+-----------+-----------+--------------+--------+
|  HEAD  |   CH   |  LEN   | EVENT(1B) | MODIFIER  |  KEY_NUM  | KEYCODE(nB)  |  SUM   |
+--------+--------+--------+-----------+-----------+-----------+--------------+--------+
|  0x55  |  0x43  | 3 + n  |   见 3.8   |   见 3.8   |    0 ~ 6  |    见 3.8     |  1 B   |
+--------+--------+--------+-----------+-----------+-----------+--------------+--------+

说明:设置「单击 / 双击 / 长按」中某一事件的 HID 键值,设备不返回应答,设置后立即持久化(NVS)。

示例:把「长按」设为 Ctrl + Shift + Esc

HEAD    = 0x55
CH      = 0x43
LEN     = 4
DATA    = [0x02, 0x03, 0x01, 0x29]   ; 事件=长按, 修饰键=Ctrl+Shift, 键数=1, 键码=Esc
SUM     = (0x55 + 0x43 + 0x04 + 0x02 + 0x03 + 0x01 + 0x29) & 0xFF = 0xCB

完整数据包: 55 43 04 02 03 01 29 CB

4.9.2 KEYBOARD_KEY_GET (0x44) - 查询按键键值

方向:上行(设备 → 客户端)

触发:客户端发送通道为 0x44 的命令。 LEN=0(无数据)→ 返回单击/双击/长按三组;LEN=1 且值为事件号 → 只返回该组。

响应数据格式(每组固定 8 字节,按 单击 → 双击 → 长按 顺序拼接):

+--------+--------+--------+---------------------+---------------------+--------+
|  HEAD  |   CH   |  LEN   |  组 1 (8 B)          |  组 2 / 组 3 ...     |  SUM   |
+--------+--------+--------+---------------------+---------------------+--------+
|  0x55  |  0x44  | 8×N    | MOD(1)+NUM(1)+KC(6) |  MOD(1)+NUM(1)+KC(6) |  1 B   |
+--------+--------+--------+---------------------+---------------------+--------+

说明:

示例:查询三组(单击 = Ctrl+C,双击 = 未配置,长按 = Ctrl+Shift+Esc)

DATA  = 01 01 06 00 00 00 00 00   ; 单击: mod=0x01(Ctrl), num=1, keycode=C
        00 00 00 00 00 00 00 00   ; 双击: 未配置
        03 01 29 00 00 00 00 00   ; 长按: mod=0x03(Ctrl+Shift), num=1, keycode=Esc
LEN   = 24

4.10 远程按键注入(0x4E,仅通行证版)

通道概览见 §3.10。仅通行证版 frame_film_ark 实现(frame_film 三机型会直接忽略),Dock 底座不支持。

4.10.1 KEY_INJECT (0x4E) - 注入一次按键

方向:下行 + 应答(客户端 → 设备 → 客户端)

数据格式:

+--------+--------+--------+---------+--------+
|  HEAD  |   CH   |  LEN   | KEY(1B) |  SUM   |
+--------+--------+--------+---------+--------+
|  0x55  |  0x4E  |   1    |  见 3.10 |  1 B   |
+--------+--------+--------+---------+--------+

说明:设备把 KEY 映射成与 HAL 上报同构的输入事件入队,随后回显同一字节(LEN=1)表示已收到。

示例:注入「上」

HEAD = 0x55
CH   = 0x4E
LEN  = 1
DATA = [0x02]                  ; BLE_KEY_UP
SUM  = (0x55 + 0x4E + 0x01 + 0x02) & 0xFF = 0xA6

完整数据包: 55 4E 01 02 A6
设备回显:   55 4E 01 02 A6

4.11 Ark 临时 WiFi 直传

手机创建 2.4GHz Wi-Fi Direct GO,并在其 IPv4 地址上提供临时 HTTP 文件服务。Ark 作为普通 WiFi STA 加入,BLE 负责会话控制与状态查询,文件内容通过 WiFi 下载;不要求路由器,不与小米互传私有协议互通。

DIRECT_START 的三个字段为 ASCII,各自以 NUL 结尾,不带版本字节或额外尾部数据。SSID 为 1–32 字节,密码最多 63 字节,URL 使用 http://,URL 的文件名应为有效且唯一的 .film 名称。DATA 总长不得超过 192 字节。当前固件的 GATT 特征最大长度为 200,客户端请求 ATT MTU 200,并检查整帧 DATA长度+4 <= MTU-3;不足时应报错,不能截断发送。

开始前应排除其他 BLE 文件、OTA、WiFi 下载会话。接受开始仅表示任务已建立。直传期间固件拒绝冲突的文件写入、删除、OTA、重置、重启、格式化、网络配置与下载命令,并返回同通道 1B 1(忙);通行证资料读取返回自己的状态码 3(忙),其他只读查询仍可用。自动休眠在会话中暂缓,手动休眠先请求取消,等待清理后再入睡。BLE 断开也会请求取消。

DIRECT_STATUS 固定返回 11 字节,多字节值为大端,不可直接复制 C 结构体:

偏移 长度 含义
0 1 state:0 IDLE / 1 CONNECTING / 2 DOWNLOADING / 3 RESTORING / 4 DONE / 5 ERROR / 6 CANCELLED
1 1 progress:0–100,下载百分比;100 不等于会话结束
2 1 error:0 无 / 1 连接 / 2 HTTP / 3 保存 / 4 取消 / 5 恢复 / 6 资源
3 4 received:已交给文件保存流程的字节数
7 4 total:HTTP Content-Length,尚未获得时为 0

直传任务(含 OTA)临时设置 WIFI_PS_NONE 降低 DTIM 接收等待,完成、取消或失败后,在恢复原网络或反初始化前恢复原节能模式;设置失败仅记录警告并继续,恢复失败按 error=5 报告。BLE/WiFi 共存仍受时间切片约束,不保证固定吞吐率。

临时网络凭据只存 RAM,不修改已保存的 WiFi 开关、SSID、密码和 API 地址。文件复用现有 SD 保存事务:中断撤销未提交文件,完成后提交并通知图片播放。成功提交之后仍有 RESTORING 阶段,用于恢复之前的 WiFi 配置和连接状态;恢复失败为 ERROR/error=5。只有 DONE 才表示本次保存及网络恢复成功。终态保留到下一次开始,客户端可约每 800 ms 查询一次。

DIRECT_CANCEL 的确认包仅表示已请求取消。客户端应继续查询至 DONE/ERROR/CANCELLED,之后关闭手机 HTTP 服务并释放 GO。已完成提交的文件不因迟到的取消而删除。连接与恢复各最多等待 25 s,HTTP 单次操作超时 5 s、film/通用文件下载循环连续 120 s 无保存进展才超时,每次成功保存数据块后重置计时;Android 客户端整次非 OTA 传输预算为 180000 + fileSize * 1000 / 32768 ms(文件大小按字节计,按 32 KiB/s 预留下载时间),OTA 保持原有 180 s 客户端总时限与 120 s 固件下载总时限;既有 SD 文件任务若卡死,同步保存接口仍可能等待,不能把这些网络超时当作整个会话的绝对上限。

4.12 Ark 通行证资料读取(0x53)

请求 DATA=[offset(4B,大端), count(1B)],count 必须为 1–128。设备每次只读取固定路径 /sdcard/app/pass/profile.json,请求结束即关闭文件;文件最大 350000 字节。资料可以通过现有 FILE_START/FILE_NAME/FILE_LEN/FILE_DATA/FILE_STOP 上传,FILE_NAME 固定用 app/pass/profile.json。显式路径上传不会自动刷新页面,回到通行证页或单击确认键刷新。

响应 DATA=[status(1B), offset(4B,大端), total(4B,大端), bytes(0–128B)],LEN=9+实际读取字节数。offset 回显请求位置,total 是已成功取得的文件长度;状态码:

status 含义
0 成功;offset==total 时为 EOF,bytes 为空
1 未配置(文件不存在)
2 读取失败(含文件超过 350000B)
3 忙(BLE 文件/OTA 上传或临时 WiFi 直传进行中)
4 参数错误(请求长度、count 或 offset 不合法)

offset 可等于 total;大于 total 返回状态 4。错误响应没有 bytes;文件尚未打开时 total=0。读取整份资料时按响应中的实际 bytes 长度推进 offset,直至 offset==total。单帧最大 137B DATA + 4B 帧头尾 = 141B,客户端需协商 ATT MTU 至少 144;推荐沿用现有 200 MTU。设备入睡门闸期间同其他普通命令一样不应答。

4.13 Ark 设备名称(0x54 / 0x55)

完整蓝牙名称为 FRAMEFILMARK-<后缀>,固定前缀用于机型识别。默认后缀是完整蓝牙 MAC 的 12 位大写十六进制字符(不含冒号),使不同设备具有不同默认名称。用户可以修改后缀,但自定义同名不会被全局查重;客户端仍按蓝牙设备地址/标识区分实体设备。

DEVICE_NAME_SET 只传后缀,长度为 1–16 个 UTF-8 字节,另加一个 NUL;不是 16 个汉字。固件拒绝非法 UTF-8、控制字符、嵌入 NUL、空后缀和超长输入。完整名称最长 29 字节;scan response 仅含完整名称 AD,连同 AD 头最多 31 字节。广播包保留服务 UUID 和设备 MAC,客户端使用 FRAMEFILMARK 名称前缀扫描,兼容旧的无后缀名称。

响应状态 0 表示成功,后跟以 NUL 结尾的完整名称;1 表示参数无效。错误响应仅含状态字节。GET 请求必须为空。最大响应 DATA 为 31 字节,完整帧 35 字节,ATT MTU 至少 38,建议保持 200。

名称后缀存于系统参数 blob 的 ServiceBle_Def_t.ble_name(无独立 NVS key),不修改 SD 通行证资料。SET 成功即表示已提交 NVS;当前连接不重启或断开。后缀写入异常由参数层的 ESP_ERROR_CHECK 终止,不再单独返回“保存失败”状态。新广播名与 GATT Device Name(0x2A00)在下次蓝牙初始化时生效,客户端提示重启设备即可。GET 返回已保存配置,可能与本次连接缓存的旧广播名不同。设备入睡门闸期间不应答;旧固件不支持命令时,客户端应超时提示升级,不能把 GATT 写成功当作保存成功。

4.14 Ark WiFi OTA(0x56~0x58)

仅 Ark 支持;保留 film 0x50/0x51/0x52 与旧 BLE OTA 0x10~0x13。BLE 控制、手机 WiFi Direct GO HTTP 提供原始 app .bin、设备流式拉取到 inactive OTA partition,不需要 SD。当前未启用 boot rollback,不承诺启动失败后自动回滚。

CH 请求 DATA 响应 DATA
0x56 BUILD_INFO_GET 空 capVersion=1(u8), otaMax(u32 BE), 当前 app ELF SHA256(32B), version(NUL ASCII), project(NUL ASCII)
0x57 DIRECT_OTA_START size(u32 BE), 文件SHA256(32B), SSID\0PASSWORD\0URL\0 0接受 / 1忙 / 2参数或容量无效 / 3资源不足
0x58 DIRECT_OTA_APPLY 空 0已激活待重启 / 1非READY或已取消 / 2激活失败

0x57 DATA 总长仍须 ≤192B,三字符串约束与0x50一致;OTA大小必须非零且不超过0x56的otaMax。只接受项目名 frame_film_ark、ESP32-S3的app镜像,不支持merged flash镜像。HTTP必须200且Content-Length等于请求size;逐块写入并验证完整文件SHA256,ESP-IDF镜像检查通过后才能准备提交。version(含3.2.6彩蛋)仅供展示,不作为升级限制或成功标识;同项目、同S3且合法的镜像可升级。

0x51仍返回11B(state,progress,error,received:u32 BE,total:u32 BE)。原0~6不变,新增7=READY、8=APPLYING。READY表示完整镜像已校验、原WiFi已恢复、启动分区尚未改变;该状态仍独占会话,120秒未apply/cancel自动清理。APPLYING表示正在激活/待重启。百分比100仅表示数据接收完成。新增error:7=OTA写入/镜像/目标检查失败,8=HTTP声明长度或SHA256不符,9=READY等待超时;0~6沿用原定义。

0x52共用取消;下载或READY取消均abort暂存并保持旧boot partition,清理后可以从头重试,无断点续传。apply提交点之后取消不能撤销启动分区。0x58返回0后设备延迟约1.5秒重启,为BLE应答留出发送时间;App应释放HTTP服务/GO,重连同一设备后读0x56并核对导入镜像的实际ELF SHA256,匹配才表示升级成功,不能只比较version或把断连/100%当成功。超时应显示“结果待确认”,允许重连确认。

4.15 Ark 能力查询(0x59)与彩色通行证

仅 frame_film_ark 实现 CAPABILITIES_GET:请求 LEN=0,回包 LEN=5,capVersion=1(u8) 后接 flags(u32 BE)。bit0 (0x00000001) 表示支持通行证彩色 profile.bin;bit1 (0x00000002) 表示支持 0x5A 通用 WiFi 文件上传,bit2 (0x00000004) 表示支持只读 SD 目录与统计 0x5B,bit3 (0x00000008) 表示支持0x4B按文件名原子打开图片,bit4 (0x00000010) 表示支持 Ark SoftAP 直传准备命令 0x5C,bit5 (0x00000020) 表示支持实验 ABC9 512 色预览播放,bit6 (0x00000040) 表示支持硬件身份查询0x5D,bit7(0x00000080)表示支持 NFC 卡包,仅 N16R8 固件设置;其余未定义位保留。capVersion 保持 1;旧固件不响应此命令,客户端按能力位选择功能,不能凭用户可见版本号判断能力。

bit5 仅在 CONFIG_ARK_EPD_ABC_PREVIEW=y(默认)或 CONFIG_ARK_EPD_ABC_DIAG=y、实际 ABC9 播放路径已编译时宣告;bit0–bit4 的既有含义不变,包含硬件查询能力的默认 flags 为 0x0000007F,两个 ABC 开关均关闭时为 0x0000005F。N16R8 在此基础上额外设置 bit7。此位只表示能播放 实验 ABC9 容器:Format 0x7F、720×480 单帧、ABC9 标记、691232 字节,LE16 低9位 A/B/C 码;不声明已完成512实色校准,也不启用诊断图生成或其他 lab 模式。客户端只有收到该位才开放实验预览上传与播放;旧固件或无该位时保留既有出图选项。

当前 Ark 固件额外设置 bit8(0x00000100,按目录/文件名列表、删除、分块读取:0x5B 目录 3 + 0x60 + 0x61)与 bit9(0x00000200,返回键双击截图到 /sdcard/screenshot)。bit9 只在 CONFIG_ARK_SCREENSHOT=y 时设置:n4r2 默认关闭截图(同时关闭 0x61 读取),flags 为 0x0000015F;n16r8 保持开启,含 NFC bit7 时 flags 为 0x000003DF。

通行证卡片文件仍放在 /sdcard/app/pass/profile.bin,文件头 16B:FFUI(4B)、version(1B)、format(1B)、width=440(u16 LE)、height=608(u16 LE)、保留零(6B)。原 v1/format1 的正文为 33440B、每字节 MSB 起 8 个黑白像素(1=黑);新 v2/format2 的正文为 133760B、每字节高 nibble 为左像素,色码为黑0、白1、黄2、红3、蓝5、绿6。整文件分别为 33456B 和 133776B。彩色卡仅在 Ark UI 通行证页合成到黑白外壳,整屏走 EPD 4bpp 彩色刷新;退出页面回到原黑白 UI 路径。

4.16 Ark 通用 WiFi 文件上传(0x5A)

WIFI_DIRECT_FILE_START 使用 0x50 相同的临时 WiFi 网络、HTTP 下载与状态机,请求为四个 NUL 结尾的 ASCII 字段 SSID\0PASSWORD\0URL\0相对目标路径\0,不含额外字节。SSID、密码、HTTP URL 和 192B DATA 上限沿用 0x50;目标路径相对 /sdcard,不得以 / 开头、包含反斜杠或 ..,且不能超过文件保存接口的路径长度。示例:app/pass/profile.bin、app/pass/profile.json。响应 0/1/2/3 与 0x50 一致。客户端应先查 0x59 的 bit1,再发送 0x5A。

HTTP 响应需为 200 且有非零 Content-Length;文件可为任意非空二进制或文本,不要求 32B film 头或 .film 后缀。显式目标以暂存文件写入,只有完整下载、长度检查和提交都成功后,0x51 才能最终报 DONE;保存失败报 ERROR/error=3,取消时撤销未提交暂存。目标若为 .film,仍按现有显式路径规则验证 film 内容。上传不自动切换或刷新 app 页面,也不触发图片列表事件。已提交文件不会因网络恢复失败或迟到取消而删除;0x51 的 DONE 还要求恢复原 WiFi 状态。0x51 状态查询和 0x52 取消的格式、终态与 0x50/0x57 共用。直传忙时另一个 0x5A 返回 1。

4.17 Ark 只读 SD 目录与统计(0x5B)

仅 Ark;客户端先查询 0x59 bit2。旧固件无此能力时提示升级,不得使用 0x4B 切 app 模拟目录浏览。

0x5B 请求 op=0(1B)查询统计;响应 26B:status(u8)、valid(u8)、totalBytes(u64 BE)、freeBytes(u64 BE)、filmCount(u32 BE)、animationCount(u32 BE)。status=0 表示已挂载,1=未挂载;valid bit0=容量有效、bit1=图片数有效、bit2=动画数有效。无效字段必须显示未读取,不可显示为0。容量为 FAT 文件系统总/剩余容量,不包含文件系统以外的保留空间。

请求 op=1(图片)、2(动画)或 3(截图)+index(u32 BE),响应 status(u8)+total(u32 BE)+name(UTF-8,NUL,仅成功时存在)。status=0=单条成功,1=SD未挂载,2=读取失败,3=参数无效,4=目录结束(index=total,包括空目录),5=文件名超过186 UTF-8字节(不截断)。每页一条,按目录扫描顺序;total变化则客户端丢弃未完成列表并提示刷新。读取到结束包后列表才完整。目录在读取间可以变化,因此结果是浏览快照,索引不是可直接用于0x05/0x07的播放文件ID。

两个只读操作均独立扫描固定 /sdcard/film、/sdcard/animation 或 /sdcard/screenshot;只统计常规 .film 文件且大小至少32字节,与播放列表规则一致。不存在的目录视为空且不创建目录,不清理临时文件,不修改活跃目录、播放缓冲或文件列表,不发布文件事件,也不切 app 或刷新画面。读取失败可得到部分有效统计。

客户端显式“显示图片”时,有0x59 bit3的固件使用0x4B按文件名原子打开,不把只读目录下标当播放ID,也不先切页恢复旧图。动画目录优先使用 0x4F 按文件名播放(见 §4.22),不必先切 app;旧固件没有 bit8 时才回落到“切到目标 app、读取活跃列表、按名称映射 ID”的流程。删除动图/截图时,有 bit8 的固件直接用 0x60 按目录+文件名删除(见 §4.22);旧固件仅在对应 app 已活跃时可用:先回读 app、重读活跃列表、按文件名映射 ID,再发 0x05。禁止把浏览索引直接当作 ID,也禁止为了删除隐式切 app。删除确认后沿用既有删除、列表刷新与播放缓冲失效语义。

4.18 Ark SoftAP 文件直传(0x5C)

本拓扑仅用于支持能力的 Ark 固件。Android 10 及以上手机作为 WiFi STA 提供 HTTP 文件服务;设备通过 0x5C 准备自己的 SoftAP,手机连接设备 AP 后,设备访问手机 STA 地址上的 HTTP URL。Android 9 与不支持该能力的旧固件继续使用既有 WiFi Direct P2P GO 流程。固件显示版本号可能为 3.2.5 / 3.2.6,能力应通过 0x59 查询,不以版本号推断。

DIRECT_AP_PREPARE 请求 DATA 必须为空。设备预留直传会话并异步启动 SoftAP,响应 DATA 为 status(u8);status=0 表示已接受准备,响应 DATA 随后包含 SSID\0PASSWORD\0IP\0,字符串为 ASCII NUL 结尾,IP 为设备 AP 地址。status=1 忙、2 参数错误、3 资源不足时响应只有 status 字节。接受不代表 AP 已就绪;客户端应使用返回的凭据连接,按需重试连接,随后以既有 0x50 或 0x5A(文件)或 0x57(OTA)启动下载。启动 payload 保持原格式,SSID 和密码必须与 0x5C 返回值一致,URL 指向手机 STA 地址上的现有 HTTP 文件服务。0x50/0x5A/0x57 的原始请求结构与内容校验语义不变。

0x59 的 capVersion 保持 1,flags 继续使用 u32 大端;新增 bit4(0x00000010)表示 Ark SoftAP 直传能力,bit0–bit3 含义不变。客户端在该位存在时可发送空请求 0x5C。早期上游 NFC 固件也使用 bit4,但会对空 0x5C 返回参数错误 2;遇到该响应时可回退到旧 P2P 流程。

会话准备阶段 BLE 保持可用,0x52 可取消已接受的准备;0x51 仍查询同一会话状态。实际进入数据期时,固件按计划暂停 BLE 以完成 WiFi 传输。取消路径先关闭手机 HTTP socket;设备恢复 BLE 后,客户端重新连接并用 0x51 确认终态与字节数。HTTP 服务端发送进度仅表示手机已发送数据,不能作为 SD 保存成功证据;只有设备恢复 BLE 后报告的 0x51 DONE 才表示保存完成。既有 0x51 状态字段、0x52 幂等取消语义和 0x58 OTA apply 语义均保留。普通文件传输正常结束无需重启;OTA 仍须 READY 后调用 0x58 apply,再重连并以构建 fingerprint 确认升级结果。

已完成同一份 47,347,232 字节 film 文件的两轮 App 实机接收保存与 BLE 恢复验证(约 1.142 / 1.134 MB/s)。真正中途取消后重试、新拓扑 WiFi OTA 和通行证实体流程仍待验收;这些单设备结果不构成所有手机的最低速度保证。

4.19 Ark 硬件身份与 OTA 目标匹配(0x5D)

0x53 仍为通行证资料读取,0x56 的 v1 BUILD_INFO 格式保持不变。硬件身份使用空请求 0x5D;客户端可先查询 0x59 bit6。历史上游 NFC 固件会对空请求返回参数错误 2。在 bit4 已设置且 bit6 未设置时,此响应按旧固件无硬件身份处理,升级仍须手动选型确认。硬件身份响应字段如下:

响应偏移 长度 字段
0 1 schema=1
1 2 chipId,大端;ESP32-S3=9
3 2 chipRevision,大端,SDK的major×100+minor
5 4 physicalFlashBytes,大端,来自实际Flash芯片容量
9 4 psramBytes,大端,已成功初始化的PSRAM字节数,失败为0
13 1 psramBus:0未知/未初始化、1 Quad、2 Octal
14 可变 targetId,ASCII NUL结尾;未知组合为空字符串

仅精确匹配以下检测容量与已采用总线的组合才提供targetId:ark-esp32s3-n4r2 为ESP32-S3、4194304B Flash、2097152B PSRAM、Quad;ark-esp32s3-n16r8 为ESP32-S3、16777216B Flash、8388608B PSRAM、Octal。异常或无法读取的组合不回退到n4r2。Flash使用物理容量查询,不能使用当前镜像头配置容量或空闲heap代替硬件容量。

原始app.bin在固定偏移288包含64B硬件descriptor,由ESP-IDF的.rodata_custom_desc布局提供;该结构自身的多字节整数为little-endian,与BLE大端序不同:

descriptor偏移 长度 字段
0 8 ASCII FFARKHW1,不含NUL
8 1 schema=1
9 1 size=64
10 2 chipId,小端
12 4 flashBytes,小端
16 4 psramBytes,小端
20 1 psramBus,取值同上
21 3 保留,全0
24 32 targetId,ASCII NUL结尾,余字节为0
56 8 保留,全0

App应按设备targetId选择发布产物,并校验下载/导入镜像内的descriptor。固件BLE OTA与HTTP OTA共用目标收尾校验:ESP-IDF镜像完整性、芯片/项目、descriptor及实际硬件目标均匹配,且大小不超过现有OTA槽,才允许选择新boot partition。错误目标、缺descriptor或未知硬件均不能激活,当前镜像保留。HTTP拒绝表现为原有OTA错误7,BLE沿用失败后重启当前镜像的行为。该校验不修改原电纸屏驱动或显示版本3.2.6。

旧设备首次获得0x5D需要一次明确型号的升级;新客户端不得根据版本3.2.5 / 3.2.6、广播名、同款屏幕或0x56的ELF指纹猜硬件型号。缺少可信硬件信息时保留其他功能,禁止自动推荐/推送未知目标。OTA app.bin不会迁移Flash分区表,n16r8镜像不自动将1900K OTA槽变为4096K;容量仍以0x56返回的实际OTA槽上限为准。

4.20 NFC 卡包(0x63 ~ 0x69,仅 n16r8)

通道概览见 §3.19。仅 frame_film_ark 的 n16r8 变体实现;需要 SD 卡,卡库为 wallet.bin,封面为 cover_<id>.bin。设备端通过 ST25R3916 复制/模拟卡片,手机端只负责建卡、管理、下发封面。

NFC 使用独立通道段 0x63 ~ 0x69,与 SoftAP 准备 0x5C、硬件身份查询 0x5D 完全分离,不再按 payload 长度判别语义。0x59 bit7 宣告 NFC 卡包;旧固件(0x5C/0x5D 复用式通道)不宣告 bit7。

4.20.1 卡片类型与内容长度

kind 含义 内容长度 total(mode=0 时固定)
0 M1 / UID(只模拟卡号) 0
1 NTAG213 180
2 NTAG215 540
3 NTAG216 924
4 M1 1K(块镜像) 1024
5 M1 2K(块镜像) 2048
6 M1 4K(块镜像) 4096

mode:0 完整内容、1 仅 UID(只对 NTAG 有效)、2 M1 块镜像(只对 kind≥4 有效)。 NTAG 的 uid_len 必须为 7、sak=0x00、ATQA=0x0044;M1 的 uid_len 必须为 4,kind=0 时 sak 取 0x08/0x18/0x38。

4.20.2 NFC_CARD_BEGIN (0x63) - 开始建卡

+------+------+------+-----+------+-----+----------+---------+-------+-------+--------+-----+
| HEAD |  CH  | LEN  |kind |mode  |uidlen|  sak    | atqa(2) | uid   | name_len| name  |total|
+------+------+------+-----+------+-----+----------+---------+-------+-------+--------+-----+
                                                                        crc32(4) 在 total 之后

成功回 status=0, 0, 180(3B,180 为单包建议分片上限);失败只回 1B 状态码。设备为该会话分配缓冲区,随后用 0x64 按 offset 递增上传内容,最后 0x65 提交。

4.20.3 NFC_CARD_DATA / NFC_CARD_COMMIT(0x64 / 0x65)

4.20.4 NFC_WALLET_CTRL (0x66) - 卡库管理

op 名称 请求 响应
0x00 LIST start(1) status, count, current, start, cnt, 记录...
0x01 SELECT id status, current
0x02 DELETE id status, count, current
0x03 GET id status, id, kind, uid_len, sak, atqa(2), uid(7), flags, name_len, name, mem_len(2), cover_len(2)
0x04 STATS 无 status, count, current, max
0x05 RENAME id, len(1), name status, id, len, name
0x06 CLEAR_COVER id status, id

LIST 单条记录:id, kind, uid_len, sak, atqa(2), uid(7), flags, name_len, name。

4.20.5 封面(0x67 ~ 0x69)

封面为 440×200 单色位图,每行 55 字节、MSB 在左(1=黑)。设备端不显示卡号,封面区只显示图片或默认卡面。

4.21 Ark 实时日志(0x5E / 0x5F)

4.21.1 LOG_STREAM_CTRL (0x5E) - 日志终端开关/查询

请求 DATA:

同通道响应 DATA(1 字节):state,0=关 1=开。

4.21.2 LOG_STREAM_DATA (0x5F) - 日志流

设备 → 客户端单向推送,帧格式与普通命令一致:

55 5F LEN TEXT... SUM

4.22 Ark 文件管理与截图(0x4F / 0x60 / 0x61)

仅 frame_film_ark 实现;客户端先查询 0x59 bit8(文件管理)或 bit9(截图)。经典固件与 Dock 忽略这些通道。

4.22.1 ANIM_PLAY (0x4F)

请求 DATA 为 文件名 UTF-8 + NUL(文件名须在 /sdcard/animation 中存在且可播放)。 设备在内部解析动画目录,选中目标文件并切到动图 app;不需要客户端先切 app,也不会临时改写当前 app 的文件目录。

响应 DATA(1B):0 成功 / 1 文件不存在或不可播放 / 2 当前固件不支持 / 3 设备忙或休眠流程中 / 4 参数错误(空名、缺少 NUL 等)。

播放参数仍走 0x49(TLV),客户端可先发 0x49 再发 0x4F,使新选的动图按当前速度/模式播放。

4.22.2 FILE_PATH_DELETE (0x60)

请求 DATA:dir(1) + 文件名 UTF-8 + NUL。目录号 1=film / 2=animation / 3=screenshot。 设备按 /<目录>/<文件名> 删除,不依赖当前 app 的活跃列表;若删除的正是当前工作目录,会同时作废播放缓冲并刷新列表(动图 app 收到列表事件后重启到有效文件)。

响应 DATA(1B):0 成功 / 1 文件不存在 / 2 读写失败 / 4 参数错误。

4.22.3 FILE_PATH_READ (0x61)

请求 DATA:dir(1) + offset(u32 BE) + count(1) + 文件名 UTF-8 + NUL,count 取值 1–180。 响应 DATA:status(1) + offset(u32 BE) + total(u32 BE) + bytes(0..180)。 status=0 时 total 为文件总字节数,bytes 为本次读到的数据;客户端按 offset 递增直到 offset + bytes == total。 status:0 成功 / 1 文件不存在或 SD 未挂载 / 2 读写失败 / 4 参数错误。

4.22.4 截图

设备端 双击返回键 截取当前画面,保存为单帧 MonoFast .film 到 /sdcard/screenshot/shot_*.film。 该功能由 CONFIG_ARK_SCREENSHOT 控制,且需要 n16r8 的返回键(GPIO18):n4r2 默认关闭,n16r8 默认开启;关闭时设备不宣告 0x59 bit9,且不响应 0x61 读取。 即使固件已编入截图(如 n16r8),设备系统设置里的「截图」开关也默认关闭;关闭时 HAL 不再上报返回键双击(返回键保持 50ms 单击结算),开启后才会恢复双击识别并生成截图。

截图直接复制 LVGL UI 层的 mono 帧缓冲,因此只支持 UI 层页面(主菜单 / 系统设置 / 时钟 / 通行证 / 卡包 / 开机页)。 图片、模板、动图等直绘页面没有 UI 帧缓冲,不会生成截图。

客户端读取截图列表用 0x5B 的 dir=3,下载用 0x61,删除用 0x60。

5. 校验和计算

校验和采用 简单求和法(Sum Check),计算公式:

SUM = (HEAD + CH + LEN + DATA[0] + DATA[1] + ... + DATA[N-1]) & 0xFF

示例 1:发送 FILE_DISPLAY 命令 (显示文件 ID=3)

HEAD  = 0x55
CH    = 0x07
LEN   = 0x01
DATA  = [0x03]

SUM = (0x55 + 0x07 + 0x01 + 0x03) & 0xFF
    = 0x60 & 0xFF
    = 0x60

完整数据包: 55 07 01 03 60

示例 2:发送 FILE_LEN 命令 (文件大小=10000)

HEAD  = 0x55
CH    = 0x01
LEN   = 0x04
DATA  = [0x00, 0x00, 0x27, 0x10]

SUM = (0x55 + 0x01 + 0x04 + 0x00 + 0x00 + 0x27 + 0x10) & 0xFF
    = 0x91 & 0xFF
    = 0x91

完整数据包: 55 01 04 00 00 27 10 91

6. C 语言示例

6.1 构造命令包

#include <stdint.h>
#include <string.h>

#define BLE_CMD_HEAD  0x55

typedef struct {
    uint8_t head;
    uint8_t ch;
    uint8_t len;
    uint8_t data[256];
} ble_cmd_packet_t;

static uint8_t ble_checksum(uint8_t* data, int len) {
    uint8_t sum = 0;
    for (int i = 0; i < len; i++) {
        sum += data[i];
    }
    return sum;
}

int build_film_display_cmd(uint8_t file_id, uint8_t* out_buf, int buf_size) {
    if (buf_size < 5) return -1;

    out_buf[0] = BLE_CMD_HEAD;
    out_buf[1] = 0x07;  // FILE_DISPLAY
    out_buf[2] = 1;    // LEN
    out_buf[3] = file_id;

    uint8_t sum = ble_checksum(out_buf, 4);
    out_buf[4] = sum;

    return 5;
}

int build_file_list_cmd(uint8_t* out_buf, int buf_size) {
    if (buf_size < 4) return -1;

    out_buf[0] = BLE_CMD_HEAD;
    out_buf[1] = 0x06;  // FILE_LIST
    out_buf[2] = 0;    // LEN
    out_buf[3] = ble_checksum(out_buf, 3);

    return 4;
}

6.2 解析响应包

typedef enum {
    PARSE_STATE_HEAD,
    PARSE_STATE_CH,
    PARSE_STATE_LEN,
    PARSE_STATE_DATA,
    PARSE_STATE_SUM
} parse_state_t;

int parse_response(uint8_t* in_buf, int in_len, uint8_t* out_ch, uint8_t* out_data, int* out_data_len) {
    static parse_state_t state = PARSE_STATE_HEAD;
    static uint8_t expected_len = 0;
    static int data_idx = 0;
    static uint8_t cmd_buf[260];

    for (int i = 0; i < in_len; i++) {
        uint8_t byte = in_buf[i];

        switch (state) {
            case PARSE_STATE_HEAD:
                if (byte == BLE_CMD_HEAD) {
                    cmd_buf[0] = byte;
                    state = PARSE_STATE_CH;
                }
                break;

            case PARSE_STATE_CH:
                cmd_buf[1] = byte;
                state = PARSE_STATE_LEN;
                break;

            case PARSE_STATE_LEN:
                cmd_buf[2] = byte;
                expected_len = byte;
                data_idx = 0;
                state = (expected_len > 0) ? PARSE_STATE_DATA : PARSE_STATE_SUM;
                break;

            case PARSE_STATE_DATA:
                cmd_buf[3 + data_idx] = byte;
                data_idx++;
                if (data_idx >= expected_len) {
                    state = PARSE_STATE_SUM;
                }
                break;

            case PARSE_STATE_SUM:
                cmd_buf[3 + expected_len] = byte;
                uint8_t calculated_sum = ble_checksum(cmd_buf, 3 + expected_len);
                if (calculated_sum == byte) {
                    *out_ch = cmd_buf[1];
                    *out_data_len = expected_len;
                    memcpy(out_data, &cmd_buf[3], expected_len);
                    state = PARSE_STATE_HEAD;
                    return 1;
                }
                state = PARSE_STATE_HEAD;
                break;
        }
    }

    return 0;
}

7. 版本历史

版本 日期 描述
1.22 2026-11-12 Ark 截图手势由双击上键改为双击返回键(仅 n16r8),上键恢复普通单击;关闭截图开关时返回键不认双击
1.21 2026-11-12 Ark 截图开关关闭时同时关闭上键双击识别并恢复 50ms 单击结算窗口,上键不再为双击延迟
1.20 2026-11-12 Ark 新增 0x4F 动图按名播放、0x60 按目录/文件名删除、0x61 分块读取;0x5B 目录扩展 3(截图);0x59 新增 bit8(文件管理)/ bit9(上键双击截图);截图保存到 /sdcard/screenshot
1.19 2026-11-07 NFC 卡包通道由 0x5C~0x62 迁移到 0x63~0x69;0x5C/0x5D 恢复为 SoftAP 准备/硬件身份单一语义,不再按 payload 长度判别;Web 客户端同步
1.18 2026-10-29 Ark 新增 SoftAP 直传准备命令 0x5C 与 0x59 bit4;记录 Android 10+ 新拓扑、BLE 暂停/恢复确认及旧 P2P 兼容边界;两轮传图与恢复已验收,中途取消重试、OTA 和通行证仍待实机验收
1.17 2026-10-16 Ark 新增 0x5A 通用 WiFi 文件上传;0x59 bit1 宣告能力,复用 0x51/0x52 状态与取消
1.16 2026-10-16 合并上游参数布局 v6 后保留 Ark 0x59 能力查询及彩色 FFUI v2 通行证;升级迁移 v5 系统参数与原独立蓝牙名
1.15 2026-10-12 Ark 设备名称后缀改随系统参数 blob 持久化(ServiceBle_Def_t.ble_name,取消独立 NVS key ble_name;SERVICE_PARAM_VER 升至 6);0x54/0x55 报文与生效时机不变
1.14 2026-10-08 Ark WiFi OTA 0x56~0x58:实际ELF SHA构建身份、inactive Flash流式校验、READY/apply、取消与超时;未启用boot rollback
1.13 2026-10-07 Ark 3.2.5 设备名称读取/保存 0x54/0x55:UTF-8 后缀、独立 NVS、重启后广播生效
1.12 2026-10-07 Ark 通行证固定资料读取 0x53:分块读取 SD 上的 profile.json,不改变既有上传或其他固件命令
1.11 2026-10-07 新增 Ark 临时 WiFi 直传 0x50–0x52:RAM 网络会话、下载进度、取消与恢复;Android GO 提供临时 HTTP 文件服务
1.10 2026-09-22 新增 §3.10 / §4.10 远程按键注入通道 (0x4E,仅通行证版 frame_film_ark):1B 键值(确认/长按/上/下/双击),设备映射为 HAL 同构输入事件并回显该字节;app_id 表补 0x06 通行证 pass(FULL 模式注册项 6 → 7)
1.9 2026-09-20 新增 §3.9 时间同步通道 (0x4D):4B 大端 Unix 秒(UTC)+ 2B 大端时区(距 UTC 分钟数,东为正),设备回显同样 6 字节;配套固件新增 service_time 模块,时区随 tz_min 落盘(SERVICE_PARAM_VER 升至 3),时间不落盘
1.8 2026-09-17 App 控制通道 APP_SWITCH / APP_CURRENT_GET 调整为 0x4B / 0x4C(0x43 / 0x44 归 Dock 底座键盘键值通道,避免撞号);新增 §3.8 / §4.9 记录 Dock 键盘键值通道 (0x43 / 0x44,仅 Dock)
1.7 2026-09-10 新增 App 参数通道 (0x45~0x4A):TLV 格式增量读写图片/模板/动图 app 参数;CTRL_MODE (0x20)/CTRL_MODE_GET (0x21) 标废弃(0x20 忽略并打日志、0x21 固定回 0xFF),播放模式语义下移到图片 app 参数通道;WiFi 心跳上报报文移除 play_mode 字段(后续统一设计)
1.6 2026-09-10 FILE_NAME (0x00) 支持相对路径:含 / 时按 /sdcard/<相对路径> 显式写入(自动建父目录,不参与列表/事件),用于推送 app 封面等资源
1.5 2026-09-09 新增 App 控制通道:APP_SWITCH、APP_CURRENT_GET(初版编号 0x43 / 0x44,自 1.8 起调整为 0x4B / 0x4C)
1.4 2026-08-27 新增屏幕面板 ID 与分辨率查询 (0x42)
1.3 2026-08-01 FILE_STOP (0x04) 新增可选静默 flag(LEN=1 DATA=0x01 不自动加载),兼容旧固件
1.2 2026-07-22 新增 WiFi 网络配置通道 (0x30-0x3B),新增 SD 卡格式化 (0x2B),更新 MODE 支持 WiFi 轮播
1.1 2026-07-21 新增 SD 卡格式化、WiFi 网络配置命令
1.0 2026-05-25 初始版本,基于 service_ble.c v0.1