QS08 Recorder BLE Communication Protocol

Item Content
Version V1.0 (2026-07-10)
Project Scope `E:\codex\record`
Communication Roles Mini Program as Central / Host; Recorder as Peripheral / Slave
Basis Vendor document Recorder and App Communication Protocol (20260313) plus CB08 live sniffing and download validation
Validation Scope Connection, battery level, file list, WAV download, frame CRC, byte order, and output file structure
  • The frame header LEN and CRC fields are both little-endian.
  • In the file list, count / time / size are big-endian.
  • CRC uses CRC-16/XMODEM.
  • The full file download request frame for command 2-2 is 36 bytes.
  • CB08 requires the entire 36-byte frame in a single GATT write. Splitting it into `20 + 16` bytes reliably returns “file does not exist”.

This protocol is used by a WeChat Mini Program to control the CB08 recorder over BLE, receive real-time audio, read the device file list, and import recordings from the device as WAV or OPUS files.

If a multi-byte field is explicitly marked LE, parse it as little-endian. Integer fields in the file list structure use BE according to live packet captures. Unless otherwise stated, all command IDs are expressed in decimal.

Term Description
App / Mini Program BLE Central. Actively scans, connects, and sends commands.
Dev / Recorder BLE Peripheral. Provides the AE20 service and sends data back through Notify.
LE Little Endian. Low byte first.
BE Big Endian. High byte first.
Single GATT Write One complete characteristic value written in a single `writeBLECharacteristicValue` call.
Object UUID Property Direction / Usage
Service `0xAE20` Primary Service Recorder business service
Characteristic `0xAE21` `WRITE_WITHOUT_RESPONSE` App → Dev, sends protocol frames
Characteristic `0xAE22` `NOTIFY` Dev → App, control responses, audio, list, and file data
Characteristic `0xAE23` `NOTIFY` Dev → App, physical key events and recording status

After a successful connection, AE22 must be subscribed first. AE23 carries key and recording-state messages, and should also be subscribed. AE22 and AE23 should use independent frame buffers to avoid byte interleaving between notification channels.

Android may actively negotiate MTU with `setBLEMTU`. On iOS, MTU is negotiated by the system. Regular commands may use `MTU - 3` as payload size, but the file import request 2-2 must follow the live-device single-write constraint described in Section 7.

Offset Length Field Byte Order Description
0 1B MAGIC - Fixed `0x5A`
1 1B SEQ - Packet sequence number, loops from `0` to `255`
2 2B CRC LE CRC-16/XMODEM over `LEN(2B) + DATA`
4 2B LEN LE Actual byte count of `DATA`
6 LEN DATA Depends on command `[TYPE:1B][CMD:1B][PARAMS…]`; ACK may contain only `TYPE`
Parameter Value
Polynomial `0x1021`
Initial value `0x0000`
RefIn / RefOut `false / false`
XorOut `0x0000`
Standard test vector ASCII `123456789` → `0x31C3`

CRC input must contain the two raw LEN bytes from the frame header followed by DATA. It must not include `MAGIC`, `SEQ`, or the `CRC` field itself.

TYPE Name Content
0 Control Commands Time, battery, capacity, firmware, authorization code
1 Real-Time Audio / Transcription Start, audio data, stop, pause/resume, device state
2 File Operations List, import, data, finish, delete, terminate
3 Key / Recording Control Physical keys, app control, state, gain

In the vendor document, Section 4 once described `TYPE=3` as ACK, while Section 7 defined it as key commands. Implementation should follow the Section 7 command table. If DATA contains only a single TYPE byte, treat it as ACK.

CMD Direction Name Parameters / Response
0 App → Dev Sync Time `year:2B LE + month/day/hour/minute/second`, each of the latter fields is 1B, total 7B
1 App → Dev Get Capacity None
2 Dev → App Capacity Response `remain:4B LE + total:4B LE`; vendor text marks the unit as 8 KB, current implementation displays it as 1 KB
3 App → Dev Get Battery None
4 Dev → App Battery Response `1B`: `0-100`; `110` means charging
10 App → Dev Get Firmware Version None
11 Dev → App Firmware Version Response `6B` ASCII, for example `V1.0.0`
12 App → Dev Get Authorization Code None
13 Dev → App Authorization Code Response Authorization code byte string
CMD Direction Name Parameters / Response
0 App → Dev Start Real-Time Transcription None
0 Dev → App Current Recording Filename Sent before the device starts pushing audio
1 Dev → App Real-Time Audio Data Compressed audio byte stream; current model uses OPUS-family encoding
2 App → Dev Stop Real-Time Transcription None
3 App → Dev Pause / Resume `1B`: `0 = resume`, `1 = pause`
4 Dev → App Device State `1B`: `0 = resume`, `1 = pause`, `2 = stop`

Real-time audio data may be saved directly as backup data. Actual speech-to-text requires backend OPUS decoding/transcoding plus streaming ASR. Permanent cloud service keys must not be written into the Mini Program.

CMD Direction Name Parameters / Response
0 App → Dev Get File List None
1 Dev → App File List Data `count:4B BE + N x 28B entries`
2 App → Dev Request File Import `offset:4B LE + filename:24B`
3 Dev → App Import Started Actual imported filename
4 Dev → App File Data Audio file byte chunks
5 Dev → App Import Finished `1B` status code: `0 = done`, `1 = not found`, `2 = offset too large`, `3 = other stop`
7 App → Dev Terminate Import None
8 App → Dev Delete Single File Same 28B entry format as file list
9 App → Dev Delete All Files None
10 Dev → App Delete All Response `1B`: `0 = success`, `1 = fail`; some old firmware may not send this
11 Dev → App Terminate Import Response None
12 App → Dev Segment Import `start:4B LE + end:4B LE + filename`
13 Dev → App Delete Single Response `1B`: `0 = success`, `1 = fail`; some old firmware may not send this
18 Dev → App File List Complete `1B`: `0` means complete
Field Length Byte Order Description
count 4B BE Number of file entries in this frame, not the total number of all device files
time 4B BE Recording duration in seconds; some firmware may use an absolute timestamp
size 4B BE Compressed file size stored on the device, in bytes
name 20B UTF-8 / ASCII Fixed-length field padded with NUL; long filenames truncate the extension

The device may send the file list over multiple `CMD=1` frames. The app should accumulate all `N` records from each frame and return the full list after receiving `CMD=18`. To remain compatible with older firmware that does not send `CMD=18`, use an idle timeout of about 1.2 seconds after the final list frame.

A typical filename such as `note20260710-162938.opus` is 24 bytes long, while the file list field is only 20 bytes. The list therefore actually returns `note20260710-162938.` and the full extension must be reconstructed during download.

Parameter Length Byte Order Example
offset 4B LE First download request uses `00 00 00 00`
filename 24B ASCII / UTF-8 `note20260710-162938.wav + 00`

Mandatory Constraint: CMD 2-2 Must Be Written as One Complete Frame

The complete protocol frame is 36 bytes. Live testing on CB08 devices A and B confirmed the following:

  • A single 36-byte GATT write succeeds.
  • If the application layer splits the frame into `20B + 16B`, the device parses the filename incorrectly and returns `CMD=5 / code=1` (file does not exist).

Do not let a generic packet splitter fragment the `2-2` frame.

Download Procedure

  1. Step 1: Read the actual MTU

After connection, `getBLEMTU` may be used to obtain `ATT_MTU`. Regular commands may use `MTU-3`, but `2-2` still requires the forced single-write rule above.

  1. Step 2: Rebuild the target filename

Prefer `base.wav` so the recorder outputs a standard WAV file. If that fails, try `base.opus` and finally the raw truncated name from the file list. The `filename` field is fixed at 24 bytes and must be NUL-padded.

  1. Step 3: Send the complete request

Build `TYPE=2`, `CMD=2`, `offset=0`, and the 24-byte filename, then send the full frame including the 6-byte generic header in a single write to `AE21`.

  1. Step 4: Receive file data

After receiving `CMD=3`, create the transfer session and keep appending the body bytes from `CMD=4`. Each valid data chunk may refresh the idle timeout.

  1. Step 5: Handle the finish code

When `CMD=5` is received:

  • `code=0`: write the file to disk.
  • `code=1`: try the next candidate filename.
  • `code=2`: reset offset.
  • `code=3`: stop and report the transfer as interrupted or stopped.

Real transmitted request frame for a successful download of `note20260710-162938.wav` captured on 2026-07-10:

5a 03 9e 20 1e 00 02 02 00 00 00 00 6e 6f 74 65 32 30 32 36 30 37 31 30 2d 31 36 32 39 33 38 2e 77 61 76 00
Segment Parsed Meaning
`5a` MAGIC
`03` SEQ = 3
`9e 20` CRC = `0x209E` stored in LE
`1e 00` LEN = 30
`02 02` TYPE = 2, CMD = 2
`00 00 00 00` offset = 0
Remaining 24B filename = `note20260710-162938.wav` with trailing NUL
Check Item Live Result
Device / Address `CB08 / D1:A1:C7:00:02:F2`
File List 24 items
Requested File `note20260710-162938.wav`
IMPORT_END `code=0`
Received Bytes `38,444B`
RIFF Declared Length `38,444B`, matches actual length
Audio Parameters PCM, 16 kHz, 16-bit, mono, 1.2 seconds

A valid WAV file should satisfy `bytes[0:4] = RIFF` and `bytes[8:12] = WAVE`. The `size` field in the file list is the compressed size on the device, not the transcoded WAV size. WAV progress may be estimated by `duration x 32000B/s + 44`.

CMD Name Parameters / Response
1 / 2 Start Recording / Start Result Result `1B`: `1 = success`, `2 = fail`
3 / 4 Save Recording / Save Result Result `1B`: `1 = success`, `2 = fail`
5 / 6 Pause Recording / Pause Result Result `1B`: `1 = success`, `2 = fail`
7 / 8 Resume Recording / Resume Result Result `1B`: `1 = success`, `2 = fail`
19 / 20 Get / Response Recording Status `1 = recording`, `2 = idle`, `3 = paused`
21 / 22 Get / Response Recording Duration `duration:2B LE + currentSize:4B LE`
23 / 24 Get / Response Current Filename Filename byte string
25 / 26 Get / Response Gain `1 = low`, `2 = medium`, `3 = high`
27 / 28 Set Gain / Set Result Set value `1-3`; result `0 = success`, `1 = fail`

`CMD 1 / 3 / 5 / 7` may also be triggered by physical buttons and reported through `AE23`. Implementations should distinguish active command responses from device-generated events based on the characteristic source and the current session state.

Scenario Recommended Handling
BLE Notify Fragmentation Reassemble across notifications according to LEN. One notification may contain half a frame or multiple frames.
Concurrent AE22 / AE23 Use independent `FrameParser` buffers and dispatch parsed frames through a unified layer.
CRC Error After byte order is locked, discard corrupted frames and log the raw hex.
Unknown Byte Order Probe LEN/CRC by sending a battery query after connection; once CRC matches, lock the byte order.
File List Without CMD=18 After receiving list data, use an idle timeout of about 1.2 seconds and return the accumulated list.
File Download Without Data After about 12 seconds of idle time, treat it as timeout. If `received = 0`, try another candidate filename.
Transfer Interrupted After Partial Data Do not automatically switch filename. Report failure or support resume via offset.
User-Initiated Cancel Send `CMD=7`, clear timers and promises, and ignore cancel-style popups.
Same-Name Local Files Add `time / size / sequence` to the local cache path to avoid overwriting.
File Responsibility
`utils/crc16.js` CRC-16/XMODEM implementation
`utils/protocol.js` Command constants, frame builder, stream parser, and field decoding
`utils/ble.js` Scan, connect, MTU, notify subscription, AE21 write, and forced single-write for `2-2`
`utils/recorder.js` High-level request/response, file list assembly, download session, and event handling
`pages/scan` Discover and connect to recorder devices
`pages/files` List, download, playback, export, and delete
`pages/transcribe` Real-time audio, waveform, timer, pause/resume, and ASR text
`test/integration/ble_e2e.py` End-to-end validation script over local Bluetooth
Check Item Pass Criteria
Scan CB08 is found and the advertising payload includes service `AE20`
Connect `AE21 / AE22` are discovered and AE22 Notify is enabled successfully
Battery After request `0-3`, response `0-4` is received and the value is reasonable
File List After `2-0`, `2-1` frames accumulate and the count matches the original app
Download Request `2-2` full 36-byte frame is written once and the device returns `2-3` instead of directly `2-5/code1`
File Data `2-4` frames continue arriving and end with `2-5/code0`
WAV `RIFF/WAVE` header is correct, declared length equals actual length, and the file is playable
Disconnect Download promise ends, timers are released, and the UI returns to disconnected state
Location Value Meaning
Battery `0-4` `110` Charging; UI may show `100%` or `Charging`
Real-Time State `1-4` `0 / 1 / 2` Resume / Pause / Stop
Import Finish `2-5` `0 / 1 / 2 / 3` Done / File not found / Offset too large / Other stop
Recording Status `3-20` `1 / 2 / 3` Recording / Not recording / Paused
Gain `3-26` `1 / 2 / 3` Low / Medium / High

File deletion commands are destructive. End-to-end validation should perform read and download only by default, and should not execute `CMD=8 / CMD=9` unless validated on dedicated test recordings first. Before release, confirm that the big-endian 28-byte file entry matches the target firmware exactly.

请输入您的评论. 可以使用维基语法:
 
  • wiki/recorder-ble-protocol-en.txt
  • 最后更改: 2026/08/16 02:14
  • admin