Skip to content

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.

CharacteristicUUIDNotes
Device Name0x2A00 (on 0x1800)Triki <serial>
Manufacturer Name0x2A29string
Model Number0x2A24string
Firmware Revision0x2A26version string (e.g. X.Y.Z)
Hardware Revision0x2A27string
Software Revision0x2A28string
Serial Number0x2A25per-device — not recorded here
System ID0x2A238-byte, per-device — not recorded here
PnP ID0x2A50source + 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 back 0x00; any value >= 0x01 → reads back 0x01. 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

OpcodeReplyMeaning / notes
0x0585 + 12 bytesstatic config/status — does NOT change with motion
0x0787 + 8 bytesdevice unique ID — stable across reads; value redacted
0x0989 + ~54 bytes (multi-packet)status block — counter + state fields; NOT motion data
0x0c8c 01 00 00 00short status/counter
0x43c3 + ~54 byteschanges every read → live buffer/counter; value redacted
0x0a, 0x42, 0x44, 0x46reboot (soft reset)
all other 0x000x7fsilent (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 Hz is 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.