You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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 时间。

典型流程

枚举文件

  1. APP 使能 CMD_TX Notify。
  2. APP -> RTC_SYNC。
  3. APP -> REC_INFO。
  4. APP -> FILE_LIST。
  5. 设备连续返回 FILE_LIST 响应包,APP 收集 item,直到 eof = 1。

下载文件,最高速度模式

  1. APP -> FILE_TRANSFER / SETUP,start_ofs = 0。
  2. 设备 -> FILE_TRANSFER / SETUP_RSP。
  3. 设备连续 Notify FILE_TRANSFER / DATA。
  4. APP 按 offset 写文件。
  5. APP 收到 control = BLE_REC_DATA_END 后,确认本地文件大小等于 file_size。

断点续传

  1. APP 本地已有 saved_offset。
  2. APP -> FILE_TRANSFER / SETUP,start_ofs = saved_offset。
  3. 设备从该 offset 开始连续发送 DATA。

删除文件

  1. APP UI 二次确认。
  2. APP -> FILE_DELETE(confirm = "DEL!")。
  3. 设备返回 OK 后,APP 重新 FILE_LIST。

格式化

  1. APP UI 二次确认。
  2. APP -> REC_FORMAT(confirm = "FMT!")。
  3. 成功后 APP 重新 REC_INFO 和 FILE_LIST。

第一版实现建议

第一版建议只实现:

  • REC_INFO
  • RTC_SYNC
  • FILE_LIST
  • FILE_TRANSFER

之后再补:

  • FILE_INFO
  • FILE_DELETE
  • REC_FORMAT

这样可以先把 APP 浏览和高速下载主链路跑通,再加危险操作。