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, 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 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.