18 KiB
BLE Recorder File Protocol Draft
版本:V0.1
本文档定义 APP 通过 BLE 同步设备 TF 卡内容的私有协议草案,覆盖 TF 信息查询、文件列表、文件信息、文件传输、删除文件、格式化和录音状态上报。
BLE GATT
建议使用一个自定义 Service,两个 Characteristic。
| Characteristic | 方向 | 属性 | 用途 |
|---|---|---|---|
| CMD_RX | APP -> Device | Write 或 Write Without Response | APP 下发命令、传输配置、取消 |
| CMD_TX | Device -> APP | Notify | 设备返回响应、连续文件数据 |
当前工程实现使用 16-bit UUID:
| 项目 | UUID |
|---|---|
| Service | 0xFDA5 |
| CMD_TX | 0xFDA6 |
| CMD_RX | 0xFDA7 |
文件传输使用 Notify,不使用 Indicate。Indicate 每包都需要 ATT 确认,吞吐会明显下降。
基础约定
- 所有多字节整数均为 little-endian。
- 所有结构体均为 packed。
- 每个 BLE value 均以
ble_rec_pkt_hdr_t开头。 - 请求和响应不再用
type区分,由 BLE 方向和cmd决定。 - 文件查询、传输、删除固定在 TF 卡
\REC\目录下。 - APP 只传文件名,不传路径,例如
MIC0016.OGG。 - 第一版文件名固定使用 8.3 短文件名,建议大写 ASCII,例如
MIC0016.OGG。 - 所有变长
name[]均必须带字符串结束符\0。 name_len表示name[]总长度,包含结尾\0。例如MIC0016.OGG的name_len = 12。- 接收方应同时校验
name_len和结尾\0,两者任一异常都按参数错误处理。 name_len最小值为 2,最后一个字节必须是0x00,前面的文件名内容不能包含\或/。- 协议版本不在线协商,APP 和固件按项目版本配套发布。后续新增字段尽量只在结构体末尾扩展。
结构体说明:
- 文档中的 packed 结构体表示 BLE 线格式,不要求 APP 或固件直接对收包 buffer 做结构体指针强转。
- 解析
u16/u32时应按 little-endian 从字节流读取,避免非对齐访问问题。 hdr.len表示 payload 长度,即BLE value 总长度 - sizeof(ble_rec_pkt_hdr_t)。- 变长数组所在结构体的实际长度需要按字段长度计算,不能使用
sizeof()直接得到完整包长。
示例宏:
#ifndef PACKED
#define PACKED __attribute__((packed))
#endif
包头
包头保留 magic/cmd/seq/len,用于快速识别协议包、匹配请求响应、检查 payload 长度。
#define BLE_REC_PKT_MAGIC 0xA5
typedef struct PACKED {
u8 magic; // BLE_REC_PKT_MAGIC
u8 cmd; // BLE_REC_CMD_xxx
u8 seq; // 发送方独立递增序号
u16 len; // payload 长度,不包含本包头
} ble_rec_pkt_hdr_t;
约定:
- APP -> Device 和 Device -> APP 两个方向的
seq独立递增。 seq为 u8,递增到 0xFF 后回绕到 0x00。- 设备响应不回填 APP 请求的
seq,而是使用设备侧自己的响应seq。 - 第一版协议按单事务处理:APP 发出请求后,等待对应
cmd的设备响应,再发下一个普通请求。 - 录音状态变化时的
REC_INFO和录音停止后紧随的FILE_LIST是设备主动 Notify,不对应 APP 请求。 - 文件传输 DATA 使用设备侧连续递增
seq,数据顺序和缺口主要由file_ofs判断。 len必须等于 BLE value 长度减sizeof(ble_rec_pkt_hdr_t)。- 收到
magic不匹配或len不匹配的包,直接丢弃。 - 普通命令建议 APP 使用 3 秒超时;超时后可重新发送当前命令。
MTU 和数据长度
BLE ATT Notify/Write 的应用 value 最大长度通常为:
ble_value_max = att_mtu - 3
协议本身不再协商 max_payload。设备端从 BLE 栈获取当前 MTU 后自行计算单包最大 data 长度。
文件 DATA 包最大数据长度:
data_max = att_mtu - 3 - sizeof(ble_rec_pkt_hdr_t) - sizeof(action_cmd)
- sizeof(file_ofs) - sizeof(control) - sizeof(sum)
data_max = att_mtu - 16
例:att_mtu = 247 时,data_max = 231。
设备实现时可再取 min(data_max, 512),避免文件系统读缓存过大。
命令总览
| Cmd | 名称 | 说明 | 第一版建议 |
|---|---|---|---|
| 0x01 | REC_INFO | 查询 TF 信息和录音状态;录音状态变化时设备主动上报 | 必做 |
| 0x02 | FILE_LIST | 获取完整文件列表 | 必做 |
| 0x03 | FILE_INFO | 查询指定文件信息 | 可选 |
| 0x04 | FILE_TRANSFER | 文件传输,设备连续 Notify | 必做 |
| 0x05 | FILE_DELETE | 删除文件 | 第二步 |
| 0x06 | REC_FORMAT | 格式化 TF 卡 | 第二步 |
| 0x07 | RTC_SYNC | APP 同步设备 RTC 时间 | 必做 |
说明:
FILE_INFO不是首版必需,因为FILE_LIST已返回文件大小和时间;保留它是为了 APP 删除/下载前单独刷新文件状态。- 删除和格式化属于危险操作,建议等列表和传输稳定后再接。
状态码
响应结构体中的 status 使用以下取值。
enum {
BLE_REC_ST_OK = 0x00,
BLE_REC_ST_PARAM_ERR = 0x01,
BLE_REC_ST_BUSY = 0x02,
BLE_REC_ST_NO_CARD = 0x03,
BLE_REC_ST_FS_ERR = 0x04,
BLE_REC_ST_FILE_NOT_FOUND = 0x05,
BLE_REC_ST_ACCESS_DENIED = 0x06,
BLE_REC_ST_UNSUPPORTED = 0x07,
BLE_REC_ST_NO_SPACE = 0x08,
BLE_REC_ST_CANCELLED = 0x09,
};
0x01 REC_INFO
查询 TF 卡信息和录音状态。容量单位为 KB;录音开始、停止时,设备也使用此响应结构主动 Notify。
#define BLE_REC_CMD_INFO 0x01
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_INFO, len = 0
} ble_rec_info_req_t;
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_INFO
u8 status; // BLE_REC_ST_xxx
u8 fs_type; // 1:FAT12 / 2:FAT16 / 3:FAT32 / 4:exFAT
u32 total_kb; // TF 总容量,单位 KB
u32 used_kb; // 已使用容量,单位 KB
u8 rec_state; // 0:已停止 / 1:录音中
} ble_rec_info_rsp_t;
u32 KB 单位最大可表示约 4TB,足够覆盖当前 128MB TF 卡和后续常见容量。
used_kb 建议按 total_kb - free_kb 计算。如果当前文件系统接口暂时无法快速获取剩余空间,可以后续补实现,但协议字段保持不变。
rec_state 始终有效。如果 status != BLE_REC_ST_OK,APP 仍可更新录音状态,其余信息字段按 0 处理。
录音状态变化时,设备主动发送 REC_INFO,status = BLE_REC_ST_OK。其中容量信息取最近一次成功查询的结果,APP 应以 rec_state 为主要关注字段。录音停止状态发送成功后,设备主动发送完整 FILE_LIST,用于刷新新生成的录音文件。
0x02 FILE_LIST
获取 \REC\ 目录下的完整文件列表。APP 只发一次请求,设备固定返回所有文件条目;如果一包放不下,设备连续 Notify 多个 FILE_LIST 响应包,直到 eof = 1。
文件顺序由 FAT 目录项扫描顺序决定,不保证按文件名或时间排序。APP 如需排序,建议本地排序。
#define BLE_REC_CMD_FILE_LIST 0x02
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_FILE_LIST, len = 0
} ble_rec_file_list_req_t;
typedef struct PACKED {
u32 size; // 文件大小
u32 ftime; // FAT 时间,格式同 fs_get_ftime()
u8 name_len; // name[] 总长度,包含结尾 '\0'
u8 name[]; // 文件名,变长,以 '\0' 结尾
} ble_rec_file_item_t;
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_FILE_LIST
u8 status; // BLE_REC_ST_xxx
u8 item_count; // 本包 item 数量
u8 eof; // 1: 所有文件列表返回结束
u8 items[]; // ble_rec_file_item_t 数组,逐项变长解析
} ble_rec_file_list_rsp_t;
建议:
- 默认固定扫描
\REC\目录,过滤隐藏/系统文件。 - 如果
\REC\目录不存在,返回status = BLE_REC_ST_OK、item_count = 0、eof = 1。 - 如果 TF 卡不存在,返回
status = BLE_REC_ST_NO_CARD、item_count = 0、eof = 1。 - 设备每个响应包尽量填满当前 MTU 可容纳的 item。
- 单个
ble_rec_file_item_t不跨 BLE 包;如果当前剩余空间放不下完整 item,则放到下一个 FILE_LIST 响应包。 - 每个 item 的实际长度为
sizeof(size) + sizeof(ftime) + sizeof(name_len) + name_len,其中name_len包含结尾\0。 - APP 解析时应同时校验
hdr.len和item_count,避免异常包导致越界解析。 - 如果文件列表需要多包返回,除最后一包外
eof = 0,最后一包eof = 1。 - 删除或新增文件后,APP 重新发送一次 FILE_LIST 获取完整列表。
0x03 FILE_INFO
查询指定文件信息。该命令可选,首版可以先不实现。
#define BLE_REC_CMD_FILE_INFO 0x03
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_FILE_INFO
u8 name_len; // name[] 总长度,包含结尾 '\0'
u8 name[]; // \REC\ 目录下的文件名,以 '\0' 结尾
} ble_rec_file_info_req_t;
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_FILE_INFO
u8 status; // BLE_REC_ST_xxx
u32 size; // 文件大小
u32 ftime; // FAT 时间
} ble_rec_file_info_rsp_t;
如果 status != BLE_REC_ST_OK,APP 只处理 status 字段,其余字段按 0 处理。
0x04 FILE_TRANSFER
文件传输使用一个命令,通过 action_cmd 区分配置、数据、取消。
核心策略:
- APP 发一次 SETUP。
- 设备返回 SETUP_RSP 后连续 Notify DATA。
- APP 不逐包请求数据。
- 设备同一时间只允许一个文件传输,DATA/CANCEL 默认作用于当前传输。
- 如果 APP 发现 offset 缺口,发送 CANCEL,然后重新 SETUP,从期望 offset 继续。
结构体
#define BLE_REC_CMD_FILE_TRANSFER 0x04
enum {
BLE_REC_TRANS_SETUP = 0x01, // APP -> Device,设置传输配置
BLE_REC_TRANS_SETUP_RSP = 0x02, // Device -> APP,配置响应
BLE_REC_TRANS_DATA = 0x03, // Device -> APP,文件数据
BLE_REC_TRANS_CANCEL = 0x04, // 双向,取消/中断传输
};
enum {
BLE_REC_DATA_CONTINUE = 0x00, // 继续
BLE_REC_DATA_END = 0x01, // 本包后传输结束
};
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_FILE_TRANSFER
u8 action_cmd; // BLE_REC_TRANS_xxx
union {
/* BLE_REC_TRANS_SETUP: APP -> Device */
struct PACKED {
u32 start_ofs; // 起始偏移,断点续传时使用
u8 name_len; // name[] 总长度,包含结尾 '\0'
u8 name[]; // \REC\ 目录下的文件名,以 '\0' 结尾
} setup;
/* BLE_REC_TRANS_SETUP_RSP: Device -> APP */
struct PACKED {
u8 status; // BLE_REC_ST_xxx
u32 file_size; // 文件总大小
u32 start_ofs; // 实际起始偏移
u32 ftime; // FAT 时间
u16 data_len; // 设备实际采用的单包 data 长度
} setup_rsp;
/* BLE_REC_TRANS_DATA: Device -> APP */
struct PACKED {
u32 file_ofs; // 本包 data 在文件中的偏移
u8 control; // BLE_REC_DATA_CONTINUE / BLE_REC_DATA_END
u16 sum; // 本包校验和,计算时 sum 字段按 0 处理
u8 data[]; // 文件数据
} data;
/* BLE_REC_TRANS_CANCEL: 双向 */
struct PACKED {
u8 reason; // 0:用户取消 / 1:offset异常 / 2:超时 / 3:设备错误
} cancel;
};
} ble_rec_file_transfer_t;
SETUP_RSP 说明:
- 如果
status != BLE_REC_ST_OK,APP 只处理status字段,其余字段按 0 处理。 - 如果
status == BLE_REC_ST_OK,APP 保存file_size/start_ofs/data_len,后续 DATA/CANCEL 都默认属于当前传输。 - 当前传输结束、取消、超时或 BLE 断开后,设备清空当前传输状态;断点续传需要重新 SETUP。
DATA 实际数据长度:
data_actual_len = hdr.len - sizeof(action_cmd)
- sizeof(file_ofs)
- sizeof(control)
- sizeof(sum)
data_actual_len = hdr.len - 8
APP 必须使用 data_actual_len 解析 data[],不要使用 SETUP_RSP 里的 data_len 作为每包固定长度。最后一包通常会小于 data_len。
DATA 校验和
sum 是一个轻量 sanity check,不用于替代 BLE 链路层 CRC。
建议算法:
u16 sum = 0;
for (i = 0; i < packet_len; i++) {
sum += packet[i];
}
校验范围为整个 BLE value,即从 hdr.magic 到 data[] 最后一个字节。计算时 sum 字段两个字节按 0 处理。APP 收到 DATA 后按同样方式计算,结果一致才写入本地文件。
如果实测 CPU 开销影响吞吐,可以后续约定 sum = 0 表示不启用 DATA 校验和;第一版建议先保留,方便调试。
设备发送策略
- 如果
\REC\目录不存在或目标文件不存在,SETUP_RSP 返回BLE_REC_ST_FILE_NOT_FOUND。 - SETUP 成功后,设备尽快连续发送 DATA。
- 能发就发,直到 BLE TX 队列满、文件结束、收到 CANCEL 或文件系统错误。
- 如果传输过程中发生文件系统错误,设备发送
BLE_REC_TRANS_CANCEL,reason = 3。 - BLE TX 队列满时不要忙等,等 notify complete/tx credit 回调后继续发送。
- 每次从 TF 读取的长度不超过
data_len,复用一个静态缓冲。 - 长文件传输循环中定期喂狗。
- 最后一包 DATA 的
control = BLE_REC_DATA_END。如果文件大小刚好按 data_len 对齐,可以发送一个 0 字节 END 包。
APP 接收策略
- 按
file_ofs写入本地文件。 - 如果收到重复 DATA,可按 offset 去重。
- 如果
file_ofs不等于期望 offset,发送 CANCEL,然后重新 SETUP,从期望 offset 继续。 - 如果 APP 超过 5 秒没有收到新的 DATA 或 CANCEL,可认为传输超时,重新 SETUP 从已保存 offset 续传。
0x05 FILE_DELETE
删除指定文件。
#define BLE_REC_CMD_FILE_DELETE 0x05
#define BLE_REC_DELETE_CONFIRM 0x44454C21 // "DEL!"
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_FILE_DELETE
u32 confirm; // BLE_REC_DELETE_CONFIRM
u8 name_len; // name[] 总长度,包含结尾 '\0'
u8 name[]; // \REC\ 目录下的文件名,以 '\0' 结尾
} ble_rec_file_delete_req_t;
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_FILE_DELETE
u8 status; // BLE_REC_ST_xxx
} ble_rec_file_delete_rsp_t;
安全要求:
- 录音中返回
BLE_REC_ST_BUSY。 - 文件传输中返回
BLE_REC_ST_BUSY,或要求 APP 先 CANCEL。 - 如果
\REC\目录不存在或目标文件不存在,返回BLE_REC_ST_FILE_NOT_FOUND。 - APP UI 侧必须二次确认。
0x06 REC_FORMAT
格式化 TF 卡。
#define BLE_REC_CMD_FORMAT 0x06
#define BLE_REC_FORMAT_CONFIRM 0x464D5421 // "FMT!"
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_FORMAT
u32 confirm; // BLE_REC_FORMAT_CONFIRM
u8 fs_type; // 0: 自动
} ble_rec_format_req_t;
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_FORMAT
u8 status; // BLE_REC_ST_xxx
} ble_rec_format_rsp_t;
安全要求:
- 录音中返回
BLE_REC_ST_BUSY。 - 文件传输中返回
BLE_REC_ST_BUSY。 - 低电量返回
BLE_REC_ST_ACCESS_DENIED。 - TF 卡不存在返回
BLE_REC_ST_NO_CARD。 - APP UI 侧必须二次确认。
0x07 RTC_SYNC
APP 将本地时间同步到设备 RTC。设备后续创建录音文件时,使用 RTC 时间生成 FAT 创建时间。
#define BLE_REC_CMD_RTC_SYNC 0x07
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_RTC_SYNC
u16 year; // 1980~2106
u8 month; // 1~12
u8 day; // 1~31,设备按年月校验
u8 hour; // 0~23
u8 minute; // 0~59
u8 second; // 0~59
} ble_rec_rtc_sync_req_t;
typedef struct PACKED {
ble_rec_pkt_hdr_t hdr; // cmd = BLE_REC_CMD_RTC_SYNC
u8 status; // BLE_REC_ST_xxx
} ble_rec_rtc_sync_rsp_t;
说明:
- 时间按设备本地时间理解,不做时区字段协商。
- 建议 APP 在 BLE 连接成功后主动同步一次 RTC,再查询/下载文件。
- 如果设备 RTC 未同步或时间无效,录音文件创建时间会回退到旧的递增时间策略,避免写入非法 FAT 时间。
典型流程
枚举文件
- APP 使能 CMD_TX Notify。
- APP ->
RTC_SYNC。 - APP ->
REC_INFO。 - APP ->
FILE_LIST。 - 设备连续返回 FILE_LIST 响应包,APP 收集 item,直到
eof = 1。
下载文件,最高速度模式
- APP ->
FILE_TRANSFER / SETUP,start_ofs = 0。 - 设备 ->
FILE_TRANSFER / SETUP_RSP。 - 设备连续 Notify
FILE_TRANSFER / DATA。 - APP 按 offset 写文件。
- APP 收到
control = BLE_REC_DATA_END后,确认本地文件大小等于file_size。
断点续传
- APP 本地已有
saved_offset。 - APP ->
FILE_TRANSFER / SETUP,start_ofs = saved_offset。 - 设备从该 offset 开始连续发送 DATA。
删除文件
- APP UI 二次确认。
- APP ->
FILE_DELETE(confirm = "DEL!")。 - 设备返回 OK 后,APP 重新
FILE_LIST。
格式化
- APP UI 二次确认。
- APP ->
REC_FORMAT(confirm = "FMT!")。 - 成功后 APP 重新
REC_INFO和FILE_LIST。
第一版实现建议
第一版建议只实现:
REC_INFORTC_SYNCFILE_LISTFILE_TRANSFER
之后再补:
FILE_INFOFILE_DELETEREC_FORMAT
这样可以先把 APP 浏览和高速下载主链路跑通,再加危险操作。