Skip to content
Documentation version: v0.6.1

OpenZilo BLE protocol reference

Device applicability

This document applies to OpenZilo Q.

Connection requirements

Your computer needs Bluetooth Low Energy (BLE). The SDK communicates with the ring through NUS and supports protocol v4.

1. Scope

This reference describes protocol v4 framing, commands and fields used by the SDK. Firmware may implement only a subset; query features to check. Button and LED behavior is covered by the user guide.

See the API index for calls and architecture for transport and audio handling.

2. BLE services

The protocol uses Nordic UART Service (NUS):

Purpose UUID Direction
Service 6E400001-B5A3-F393-E0A9-E50E24DCCA9E Service identifier.
TX Characteristic 6E400003-B5A3-F393-E0A9-E50E24DCCA9E Ring notifies SDK.
RX Characteristic 6E400002-B5A3-F393-E0A9-E50E24DCCA9E SDK writes to ring.

Connect by the scanned address (UUID on macOS), without depending on advertising names.

SDK RX writes use chunks of up to 20 bytes; the last may be shorter. This is a BLE write policy, not a packet boundary or a replacement for system MTU negotiation. TX notifications can use other sizes; reconstruct them with OpenZiloClient or PacketStream.

3. Protocol v4 packets

The fixed header is 11 bytes:

Offset Field Type Bytes Description
0 magic_number u8 1 Always 0x3F.
1 version u16 2 Currently 4.
3 command u16 2 Command number.
5 body_length u32 4 Body size in bytes.
9 body_crc u16 2 Body CRC16; 0 for an empty body.
11 body bytes variable Command body.

Header and body integers are network byte order (big-endian). Strings and raw bytes are not reordered. CRC covers only the body, using SDK crc16_compute() with initial value 0xFFFF.

A packet can span notifications and a notification can contain multiple packets. Reconstruct using the fixed header and body_length before parsing. OpenZiloClient and PacketStream do this already.

4. Commands

Function Request/event Response SDK API
System information 0x0101 0x0102 get_system_info()
Feature discovery 0x0105 0x0106 get_device_capabilities()
Command error 0x01FF device response None DeviceError
Log storage information 0x0301 0x0302 get_log_storage()
Log data 0x0303 0x0304 read_log_chunk()
Time synchronization Device requests 0x0401 SDK replies 0x0402 enable_time_sync(), send_time_response()
Recording count 0x0501 0x0502 get_audio_file_count()
Normal recording extraction 0x0503 0x0504 get_audio_file_info()
Recording data 0x0506 0x0505 read_audio_frame()
End normal extraction 0x0507 0x0508 end_audio_extract()
Fast recording extraction 0x0509 0x0504, consecutive 0x0505 download_audio_file(..., quick=True)
V2.001.0002.0000 post-save push Consecutive device 0x0505 No initial 0x0504; metadata queried for recovery receive_auto_audio_file()
Recording saved Device 0x050A None wait_audio_record_result(), receive_auto_audio_file()
Live audio subscription 0x050D 0x050E set_live_audio_subscription()
Live audio data Device 0x050F, 0x0510, 0x0511 None receive_live_audio_file()
Delete all recordings 0x050B 0x050C clear_audio_files()
Start recording 0x0512 0x0513 start_audio_recording()
Stop recording 0x0514 0x0515 stop_audio_recording()
Recording status 0x0516 0x0517 get_audio_recording_status()
Read recording settings 0x0518 0x0519 get_audio_recording_config()
Write recording settings 0x051A 0x051B set_audio_recording_config()
Start IMU reports 0x0601 0x0602 start_sensor_report()
Stop IMU reports 0x0603 0x0604 stop_sensor_report()
IMU batches Device 0x0605 None wait_sensor_data()
Physical double tap Device 0x0701 None wait_sensor_double_tap_event()
Double button press Device 0x0703 None wait_sensor_key_double_press_event()
Single button press Device 0x0704 None wait_sensor_key_single_press_event()

5. Message bodies

All u16, i16 and u32 below are big-endian. A nonzero response error_code makes subsequent fields invalid; a response may contain only that error. Some recording failure paths return no response, so timeout alone does not identify the cause.

5.1 System information

0x0101 Get system information

Empty body.

0x0102 System information response

Field Type Bytes Description
error_code u16 2 Error code.
firmware_version_len u16 2 Firmware version string length.
firmware_version UTF-8 variable Firmware version string.
system_time u32 4 Unix timestamp in seconds.
audio_storage_total u32 4 Total recording storage in bytes.
audio_storage_available u32 4 Available recording storage in bytes.
battery_percent u16 2 Battery percentage.
battery_charging u8 1 0 not charging, 1 charging.
sn_len u16 2 Serial number string length.
sn UTF-8 variable Device serial number.
cpuid_len u16 2 CPUID string length.
cpuid UTF-8 variable Device CPUID.
model_len u16 2 Model string length.
model UTF-8 variable Device model.

5.2 Logs

0x0301 Get log storage

Empty body.

0x0302 Log storage response

Field Type Bytes Description
error_code u16 2 Error code.
page_size u32 4 Log page size or the device's current paging unit.
total_len u32 4 Total log data length.

0x0303 Read log data

Field Type Bytes Description
index u32 4 Log index.
offset u32 4 Starting offset.
size u32 4 Requested byte count.

0x0304 Log data response

Field Type Bytes Description
error_code u16 2 Error code.
data_len u32 4 Actual data length.
data bytes data_len Log bytes.

5.3 Time synchronization

0x0401 Device time request

Field Type Bytes Description
request_time u32 4 Request construction timestamp T1.

0x0402 SDK time response

Field Type Bytes Description
request_time u32 4 Original request timestamp T1.
response_time u32 4 Request arrival timestamp T2.
send_time u32 4 Reply send timestamp T3.

5.4 Recordings

0x0501 Get recording count

Empty body.

0x0502 Recording count response

Field Type Bytes Description
error_code u16 2 Error code.
file_count u32 4 Number of stored recordings.

0x0503 Begin normal extraction

Field Type Bytes Description
error_code u16 2 Always 0.
file_index u32 4 Recording file index.

0x0509 Begin fast extraction

Same body as 0x0503. The device first returns 0x0504, then consecutive 0x0505 packets until is_end=1.

0x0504 Recording file information

Field Type Bytes Description
error_code u16 2 Error code.
file_index u32 4 Recording file index.
record_time u32 4 Recording timestamp in seconds.
data_size u32 4 Total encoded data bytes.

0x0505 Recording data chunk

Field Type Bytes Description
error_code u16 2 Error code.
file_index u32 4 Recording file index.
frame_offset u32 4 Byte offset within recording data.
frame_size u32 4 Chunk byte count, currently at most 4096.
is_end u8 1 0 not final, 1 final chunk.
data bytes frame_size Encoded recording bytes.

data is a raw file fragment: OP2C on Opus firmware, or consecutive little-endian two-byte lengths followed by Speex payloads on legacy firmware. See recording formats. File-internal little-endian lengths do not change big-endian protocol fields.

After saving, legacy Speex firmware pushes this same 0x0505 format without preceding 0x0504. receive_auto_audio_file() selects the first frame's index, assembles by offset, and finishes at is_end=1. It returns (file_index, raw_audio) without AudioFileInfo.

Normal continuous push needs no extraction commands. For a gap, timeout or damaged frame, the SDK queries and verifies index and data_size using 0x0503 -> 0x0504, then requests the expected offset with 0x0506, up to three attempts. Results are bounded by data_size. Keep BLE connected during saving and push. If connection was too late, query the recording count and download by index.

0x0506 Request recording offset

Field Type Bytes Description
error_code u16 2 Always 0.
file_index u32 4 Recording file index.
frame_offset u32 4 Byte offset to read or recover.
reserved u16 2 Reserved padding required by current firmware length checks; always 0.

The SDK uses frame_offset for the next normal-download chunk and to recover missing fast-download or push data.

0x0507 End normal extraction

Field Type Bytes Description
error_code u16 2 Always 0.
file_index u32 4 Recording file index.

0x0508 Extraction-end response

Field Type Bytes Description
error_code u16 2 Error code.
file_index u32 4 Recording file index.

0x050A Recording save result

Field Type Bytes Description
error_code u16 2 Error code.
file_index u32 4 Newly saved recording index.

Firmware V2.001.0002.0001 sends this after saving instead of pushing 0x0505. Retrieve using normal 0x0503 extraction and finish with 0x0507. Failed saves may return only error_code.

0x050B Delete all recordings

Empty body. Irreversibly deletes all recordings on the device.

0x050C Delete response

Field Type Bytes Description
error_code u16 2 Error code.

0x050D Live audio subscription

Firmware version

This usage requires firmware V2.001.0002.0001 or later.

Field Type Bytes Description
enabled u8 1 1 enable, 0 disable.

Subscribing does not start recording; users still hold the button in recording mode. Live-only output uses BLE without flash saving; configurable firmware can also save a copy. Disconnect clears the subscription. Unsubscribe aborts live transmission.

0x050E Subscription response

Field Type Bytes Description
error_code u16 2 Error code; notifications disabled or ATT MTU below 128 causes an error.
enabled u8 1 Current device subscription state.

0x050F Live recording START

Field Type Bytes Description
session_id u32 4 Nonzero recording session ID.
reserved u32 4 Always 0.
record_time u32 4 Recording timestamp in seconds.
reserved u32 4 Always 0.
sample_rate u16 2 Always 16000.
channels u8 1 1 or 2, matching recording settings.
codec u8 1 Always 3 (Opus).

0x0510 Live recording DATA

Field Type Bytes Description
session_id u32 4 Same as 0x050F.
sequence u32 4 Packet sequence starting at 0.
byte_offset u32 4 Offset in transmitted Opus bytes, starting at 0.
length u16 2 Opus packet length, 1–100 bytes.
data bytes length One 20 ms Opus packet, with the START channel count.

byte_offset counts Opus data only, excluding OP2C length prefixes.

0x0511 Live recording END

Field Type Bytes Description
error_code u16 2 Error code.
session_id u32 4 Same as 0x050F.
reserved u32 4 Always 0.
produced_bytes u32 4 Opus bytes produced by the device encoder.
sent_bytes u32 4 Opus bytes sent by the device.
dropped_frames u32 4 Frames dropped by the device.

A recording is complete only when error_code=0, dropped_frames=0, and both byte counts equal the actual received data. Downloads during live recording may receive device-busy in 0x0504.

5.5 Live IMU

Firmware V2.001.0002.0000 requires gesture mode first. Firmware V2.001.0002.0001 removes mode switching and starts capture directly on 0x0601. Both retain the following command numbers.

0x0601 Start live reports

Empty body. Direct capture startup requires firmware V2.001.0002.0001 or later. Firmware V2.001.0002.0000 requires gesture mode first. Starting from stopped resets sequence_start; an active subscription does not reset it.

Use this sequence:

Firmware V2.001.0002.0001: no mode change required
-> Send 0x0601
-> Receive 0x0602 with error_code=0
-> Receive batch 0x0605

In recording mode on V2.001.0002.0000, 0x0601 returns device-busy. Firmware V2.001.0002.0001 has no such mode restriction. wait_sensor_data() only waits for data and does not send 0x0601.

0x0602 Start-report response

Field Type Bytes Description
error_code u16 2 Error code.
sample_rate_hz u16 2 Current sample rate; use this returned value.
accel_range_g u16 2 Accelerometer range.
gyro_range_dps u16 2 Gyroscope range.

0x0603 Stop live reports

Empty body. Firmware V2.001.0002.0001 stops both BLE reporting and IMU capture. Firmware V2.001.0002.0000 only stops BLE reports and can retain local capture in gesture mode.

0x0604 Stop-report response

Field Type Bytes Description
error_code u16 2 Error code.

0x0605 Single-sample and batch IMU data

A successful single-sample body is error_code:u16 + sequence:u32 + timestamp_ms:u32 + accel_x/y/z:i16 + gyro_x/y/z:i16, exactly 22 big-endian bytes. The SDK returns a one-sample SensorDataBatch.

Batch format follows; its successful length is 10 + 16*N, distinct from single-sample format:

Field Type Bytes Description
error_code u16 2 Error code.
sequence_start u32 4 First sample sequence in this batch.
frame_count u16 2 Samples in this batch.
sample_size u16 2 Bytes per sample, currently 16.
samples bytes frame_count * sample_size Consecutive samples.

Each 16-byte sample has this layout:

Field Type Bytes Description
timestamp_ms u32 4 Device capture timestamp in milliseconds.
accel_x i16 2 Raw X-axis acceleration.
accel_y i16 2 Raw Y-axis acceleration.
accel_z i16 2 Raw Z-axis acceleration.
gyro_x i16 2 Raw X-axis gyroscope value.
gyro_y i16 2 Raw Y-axis gyroscope value.
gyro_z i16 2 Raw Z-axis gyroscope value.

Successful body length must equal:

2 + 4 + 2 + 2 + frame_count * sample_size

Batches contain at most 32 samples; use frame_count for the actual size. The next sequence is sequence_start + frame_count. Firmware V2.001.0002.0001 stops capture on disconnect and requires another 0x0601 after reconnecting. Firmware V2.001.0002.0000 can resume local gesture-mode detection after reconnecting, but reporting still needs another subscription.

5.6 Motion and button events

Both V2.001.0002.0000 and V2.001.0002.0001 retain these event layouts. Changes to capture conditions and single-press behavior are described below.

0x0701 Physical double tap

Field Type Bytes Description
timestamp_ms u32 4 Event timestamp in milliseconds.

Firmware V2.001.0002.0001 needs 0x0601 to start IMU capture before detecting taps. Firmware V2.001.0002.0000 detects taps in gesture mode without BLE sample reporting. Timestamps are milliseconds since boot, unlike the device-second timestamps of button events.

0x0703 Double button press

Field Type Bytes Description
timestamp_ms u32 4 Device timestamp in seconds; SDK retains the field name timestamp_ms.

A recognized double press emits only 0x0703, without the corresponding single press or mode change.

0x0704 Single button press

Field Type Bytes Description
timestamp_ms u32 4 Device timestamp in seconds; SDK retains the field name timestamp_ms.

Firmware V2.001.0002.0001 only emits 0x0704, without switching modes. Firmware V2.001.0002.0000 also attempts to switch recording/gesture modes. Confirmation waits for the double-press window; the body has no mode field.

After synchronization, button seconds represent device Unix time. Before synchronization, do not interpret them as calendar time. Do not compare directly with IMU/tap milliseconds since boot.

5.7 Remote recording and settings

Firmware version

This usage requires firmware V2.001.0002.0001 or later.

These commands implement recording-control interface v1. Start, status and configuration-read requests have empty bodies. Integers are big-endian. See recording control for application examples.

0x0512 / 0x0513 Start recording

Empty request. A successful response is error_code:u16, session_id:u32, six bytes. Nonzero session ID confirms acceptance, not completion; query 0x0516 for final status. After START timeout, query status instead of automatically retrying.

0x0514 / 0x0515 Stop recording

Request: session_id:u32, cancel:u8. cancel=0 stops according to output settings; cancel=1 discards uncommitted audio. Success: error_code:u16, session_id:u32. The ID must match; final status must still be awaited.

0x0516 / 0x0517 Query recording status

Empty request. Successful response is 14 bytes:

Field Type Description
error_code u16 Error code for the status query.
session_id u32 Session ID; 0 when idle.
state u8 0 idle, 1 queued, 2 capturing, 3 finalizing, 4 complete, 5 failed, 6 cancelled.
saved u8 1 if a device file was saved, otherwise 0.
recording_error u16 Recording error code, separate from the query error.
file_index u32 Saved file index; 0 if not saved.

The SDK uses saved to determine whether the index is valid. Without a saved file, returned file_index is None.

0x0518 / 0x0519 Read recording configuration

Empty request. Response begins with error_code:u16; on success the 14-byte settings below are followed by sample rate, frame duration and fixed codec parameters.

Field Type Description
version u8 Configuration structure version, currently 1.
output u8 0 automatic, 1 flash, 2 live, 3 live plus flash.
disconnect_policy u8 0 stop, 1 continue saving on device.
channels u8 1 or 2.
bitrate u32 Bitrate in bit/s.
gain_left_q8, gain_right_q8 u16 each Left/right digital gain; 256 means 1x.
max_duration_ms u16 Maximum duration in milliseconds.

Successful responses are 34 bytes. Settings are followed by sample_rate:u16, frame_duration_ms:u16, then codec:u8, complexity:u8, vbr:u8, constrained_vbr:u8, dtx:u8, fec:u8, packet_loss_percent:u8, signal:u16, bit_depth:u8, max_packet_size:u16, application:u16. The device returns these fixed parameters; configuration writes cannot change them.

0x051A / 0x051B Write recording configuration

Request contains only the 14-byte settings above. Response matches configuration readback, showing accepted settings. Write only while idle. Continue-saving on disconnect requires flash or live-plus-flash output.

6. Error codes

Value Name Description
0 SUCCESS Success.
1 UNKNOWN Unknown error.
2 DEVICE_BUSY Device busy.
3 FILE_NOT_EXIST File not found.
4 CMD_GROUP_NOT_EXIST Command group not found.
5 CMD_NOT_EXIST Command not found.
6 TIMEOUT Operation timed out.
7 PARAM_INVALID Invalid argument.
8 COMMUNICATION Communication error.
9 UNSUPPORTED_SCHEMA Unsupported message structure version.

See the firmware update guide. The SDK provides update_firmware() and ota-update; calls are documented in the SDK firmware updates.

Capability discovery messages

The release tags for V2.001.0002.0000 and V2.001.0002.0001 do not implement the discovery commands below. This section documents the SDK parser for the discovery extension; it does not attribute that extension to the V2.001.0002.0001 release.

Protocol v4 and existing GET_INFO and recording-v1 layouts remain unchanged. All integers are big-endian.

Command Direction Body
0x0105 Host → device Empty
0x0106 Device → host error:u16; success adds schema:u8=1 followed by TLVs
0x01FF Device → host original_command:u16, error:u16, exactly four bytes

Errors: 2 busy, 5 unknown command, 7 invalid argument, 9 unsupported structure version. An unknown command returns 5, nonempty discovery request returns 7, and unknown version in the 14-byte recording settings returns 9. Existing command-specific errors remain valid; not all legacy failures use generic errors. Never reply to an error with another error.

TLVs contain tag:u16, length:u16, value:length bytes, within v4's 5120-byte limit; current firmware constructs responses in a buffer of at most 512 bytes. Duplicate tags, truncation, invalid UTF-8 and missing required device fields fail parsing. Unknown optional tags are skipped; an unknown tag with its top bit set requires an SDK upgrade.

Tag Content
1 Full firmware version, UTF-8.
2 Product model, UTF-8.
3 Firmware compilation target, UTF-8.
4 Eight-byte random boot_id for this boot.
5 Firmware build ID, UTF-8.
6 Supported protocol versions as consecutive u16 values; currently 4.
0x0100 + feature_id Feature interface version major:u8, minor:u8.
0x0204 Recording limits, defined below.

Feature IDs: 1 system/1, 2 time_sync/1, 3 audio_files/1, 4 recording_control/1, 5 live_audio/1, 6 imu/2, 7 key_events/1, 8 double_tap/1, 9 ota/1, 10 gesture/1. The suffix is the supported major version. Current firmware does not advertise gesture. IMU/2 starts capture on subscription; an implementation requiring separate gesture mode must not advertise it.

Firmware advertises features only for fully registered command groups. Button/tap events require connected producers in the product build. Advertisement identifies implementation support, not successful hardware initialization, current connection readiness or an idle device.

Recording limits occupy exactly 26 bytes:

Position Type Meaning
1–2 u16, u16 Sample rate and frame duration in milliseconds.
3–6 u8 × 4 Channel mask, output mask, continue-saving output mask, reserved=0.
7–8 u32 × 2 Minimum and maximum bitrate, inclusive.
9–10 u16 × 2 Minimum and maximum Q8 digital gain.
11–13 u16 × 3 Minimum and maximum duration, and duration step in milliseconds.

Masks use enum values as bit positions: channels 1/2 give 0x06; CONTINUE_SAVING with FLASH=1/BOTH=3 gives 0x0A. Recording interface v1 requires these limits. Read fixed Opus parameters through the existing configuration-read command.

Interface evolution

Use optional TLVs for additive fields so older clients can skip them. Incompatible fields or behavior need a higher major version and new command or structure. Mark client-required fields as required. Reject unsupported fields in writes. Detect recording formats from content, independently of current firmware.

Requests have no transaction IDs. Query state before retrying START. Release notes specify OTA compatibility; the SDK does not infer every starting-version combination or provide automatic rollback. Application and bootloader security versions are independent.