docs/doc/developer/Protocol.mdx
This document describes the Bluetooth Low Energy (BLE) protocol used to communicate between the Omi app and device. Use this reference when building custom apps or SDKs.
The official app discovers the device by scanning for BLE devices with the name Omi.
The Omi wearable implements three BLE services:
<CardGroup cols={3}> <Card title="Battery Service" icon="battery-full"> Standard BLE battery level monitoring </Card> <Card title="Device Info Service" icon="circle-info"> Device metadata and firmware version </Card> <Card title="Audio Service" icon="microphone"> Audio streaming and codec configuration </Card> </CardGroup>| Property | Value |
|---|---|
| Service UUID | 0x180F (standard) |
| Characteristic UUID | 0x2A19 (Battery Level) |
| Supports Notifications | Yes (firmware v1.5+) |
This is the standard BLE Battery Service.
| Property | Value |
|---|---|
| Service UUID | 0x180A (standard) |
This is the standard BLE Device Information Service.
| Characteristic | UUID | Value |
|---|---|---|
| Manufacturer Name | 0x2A29 | "Based Hardware" |
| Model Number | 0x2A24 | "Omi" |
| Hardware Revision | 0x2A27 | "Seeed Xiao BLE Sense" |
| Firmware Revision | 0x2A26 | e.g., "1.0.3" |
This is the main service for streaming audio from the device to the app.
| Property | Value |
|---|---|
| Service UUID | 19B10000-E8F2-537E-4F6C-D104768A1214 |
| Characteristic | UUID | Purpose |
|---|---|---|
| Audio Data | 19B10001-E8F2-537E-4F6C-D104768A1214 | Audio stream from device |
| Codec Type | 19B10002-E8F2-537E-4F6C-D104768A1214 | Audio codec identifier |
The codec type characteristic determines how to decode the audio data:
| Value | Codec | Sample Rate | Bit Depth |
|---|---|---|---|
0 | PCM | 16 kHz | 16-bit mono |
1 | PCM | 8 kHz | 16-bit mono |
10 | Mu-law | 16 kHz | 8-bit mono |
11 | Mu-law | 8 kHz | 8-bit mono |
20 | Opus | 16 kHz | 16-bit mono |
Audio data is sent as BLE notifications on the Audio Data characteristic.
flowchart LR
subgraph Header["Header (3 bytes)"]
PN[Packet Number
2 bytes]
IDX[Index
1 byte]
end
subgraph Payload["Audio Payload"]
DATA[Audio Samples
160 samples/packet]
end
Header --> Payload
| Bytes | Field | Description |
|-------|-------|-------------|
| 0-1 | Packet Number | Overall packet counter (0-65535, wraps around) |
| 2 | Index | Position of this value within the current packet |
**Example:** PCM 16-bit at 16 kHz
- 160 samples × 2 bytes = 320 bytes per packet
- On iOS devices, this typically results in:
- Notification 1: packet n, index 0, 251 bytes
- Notification 2: packet n+1, index 1, 75 bytes
# Example: Parsing audio packet header
def parse_audio_header(data: bytes):
packet_number = int.from_bytes(data[0:2], 'little')
index = data[2]
audio_data = data[3:]
return packet_number, index, audio_data