BLE protocol
Notes on the Triki BLE interface, captured with bleak on macOS.
OPSEC: device-identifying values (chip ID, any per-device value) are NOT recorded here — only structure is kept.
Advertising
Name TRIKI <serial> / Triki <serial>, address XX:XX:XX:XX:XX:XX (random static), service 0x0001.
GATT
Nordic UART Service 6e400001-b5a3-f393-e0a9-e50e24dcca9e
RX …0002 [write, write-no-response] commands host -> token
TX …0003 [notify] responses token -> host
ctrl …0004 [read, write] control register
Battery 0x180F → 0x2A19 (Battery Level)The token speaks a request/response protocol over NUS: TX stays silent until a command arrives on RX.
Device Information Service (0x180A)
Besides the NUS, the token also exposes the standard Generic Access (0x1800) and Device Information (0x180A) services. An independent reader (TrikiReader) reads the full set below; confirm per-device with tools/ble_dump.py before relying on any one field — not every unit necessarily populates all of them.
| Characteristic | UUID | Notes |
|---|---|---|
| Device Name | 0x2A00 (on 0x1800) | Triki <serial> |
| Manufacturer Name | 0x2A29 | string |
| Model Number | 0x2A24 | string |
| Firmware Revision | 0x2A26 | version string (e.g. X.Y.Z) |
| Hardware Revision | 0x2A27 | string |
| Software Revision | 0x2A28 | string |
| Serial Number | 0x2A25 | per-device — not recorded here |
| System ID | 0x2A23 | 8-byte, per-device — not recorded here |
| PnP ID | 0x2A50 | source + vendor / product / version IDs |
PnP ID (0x2A50) is the standard 7-byte record [source][vendorId LE][productId LE][versionId LE], where source 0x01 = Bluetooth SIG, 0x02 = USB — a potential lead on the vendor/product identity.
OPSEC: Serial Number, System ID and the advertised
<serial>are per-device identifiers — keep them out of tracked files (use placeholders<serial>/XX:XX:XX:XX:XX:XX).
Control register 0x0004 (green LED)
- 1-bit flag. Writes saturate:
0x00→ reads back0x00; any value>= 0x01→ reads back0x01. Effective search space = 2 states. - Bit0 directly drives the green LED:
0x01= green on,0x00= off — immediate, no timeout (confirmed visually).
RX 0x0002 (command interface)
- Commands are byte sequences (byte0 = opcode). Some are single-byte, some are multi-byte (e.g. the 8-byte IMU start command — see IMU streaming).
- Response opcode = request opcode | 0x80 (bit7 set = "this is a reply"), delivered on TX as one or more 20-byte notification packets.
- A few opcodes reboot the device (short LED blink, link drops, comes back as the same device — a soft reset).
Opcode map (RX → TX), so far
| Opcode | Reply | Meaning / notes |
|---|---|---|
0x05 | 85 + 12 bytes | static config/status — does NOT change with motion |
0x07 | 87 + 8 bytes | device unique ID — stable across reads; value redacted |
0x09 | 89 + ~54 bytes (multi-packet) | status block — counter + state fields; NOT motion data |
0x0c | 8c 01 00 00 00 | short status/counter |
0x43 | c3 + ~54 bytes | changes every read → live buffer/counter; value redacted |
0x0a, 0x42, 0x44, 0x46 | — | reboot (soft reset) |
all other 0x00–0x7f | — | silent (no single-byte reply; likely need parameters) |
The raw IMU is not on these single-byte opcodes — it comes from the dedicated start-stream command on the IMU streaming page.
Sample rate (ODR)
The 8-byte IMU start command (20 10 00 D0 07 68 00 03, see IMU streaming) carries the sample rate in bytes 5–6 (little-endian uint16, in Hz):
68 00=0x0068= 104 Hz — the default.- The firmware accepts the LSM6DSL ODR steps 26 / 52 / 104 / 208 / 416 Hz (exposed as 0.25× … 4× in the web controller).
12.5 Hzis rejected. - Above ~208 Hz BLE often can't keep up — watch the actual streaming N Hz reported by the controller rather than the requested rate.
- Bytes 3–4 (
D0 07= 2000) and byte 7 (03) are a fixed, not-yet-decoded part of the command — they are not the rate; leave them as-is.