Bluetooth Interface Documentation¶
Service Overview¶
The scooter exposes several Bluetooth LE services for control and monitoring. All services use the base UUID: xxxx-6e67-5d0d-aab9-ad9126b66f91
Device Information¶
- Device Name: "unu Scooter"
- Company ID: 0xE50A
- Advertising: 1 second interval, 3 minute duration
- Connection Interval: 20-75ms
- Security: MITM enabled, LESC enabled, display-only pairing (6-digit PIN), bonding supported
What a client can do without bonding¶
Reads and writes require an encrypted, MITM-protected link on every characteristic
except two: 9a59a021 (Reset Reason) and 9a59a022 (Reset Count), which are open.
Everything else, including both control characteristics, is protected. There is no
unbonded path to scooter:state unlock or to any power command.
An unbonded client can still:
- see the advertisement: device name and manufacturer data (company
0xE50A, payload = the first two bytes of the BLE MAC, so an app can tell scooters apart before connecting) - connect and discover the full GATT structure: every service, characteristic and its properties
- read the two reset counters above
Advertising modes¶
Advertising follows the vehicle state, and it filters connections rather than changing the advertisement. The payload is byte-identical in both modes.
| Vehicle state | Mode |
|---|---|
parked |
open, accepts new centrals |
off, stand-by, updating, shutting-down, hop-on, ready-to-drive |
whitelist-only (bonded peers) |
ready-to-drive is whitelist-only rather than off, so a bonded phone can reconnect
mid-ride while unknown phones are ignored. Consequence when testing: a locked scooter
advertises and is visible to a scanner, but will not accept a connection from an
unbonded client. Unlock it (parked) to pair a new one.
The mode can be overridden at runtime:
redis-cli lpush scooter:bluetooth advertising-restart-no-whitelisting
redis-cli lpush scooter:bluetooth advertising-start-with-whitelisting
redis-cli lpush scooter:bluetooth advertising-stop
The override does not survive the next change of advertising-mode class (parked and anything else), a disconnect, or a bond change, each of which recomputes the mode from the vehicle state. A transition between two whitelist-only states leaves it in place.
Internal UUID ranges (not GATT)¶
Some UUIDs in this range address messages on the internal MDB to nRF52 link rather than characteristics. They are not part of the GATT table: no client can discover, read or subscribe to them, and they are listed here only so they are not mistaken for a service that failed to appear.
| Range | Carries |
|---|---|
| 9a590120-9a590123 | Aux charger (LTC4020) control |
| 9a590200-9a590202 | Accelerometer wake-up from suspend / hibernation |
| 9a590800+ | Power management handshake (the client-visible state is 9a5900a1) |
Control Service (9a590000)¶
Primary control interface for scooter functions
| Characteristic | Description | Values |
|---|---|---|
| 9a590001 | Control Input | - "scooter:state lock" - "scooter:state unlock" - "scooter:seatbox open" - "scooter:blinker right" - "scooter:blinker left" - "scooter:blinker both" - "scooter:blinker off" |
| 9a590002 | Power Control | - "hibernate" - "wakeup" - "reboot" (soft reboot via pm-service) - "hard-reboot" (power-cycle all rails via nRF52) |
hard-reboot is only accepted during normal operation (stand-by, suspend, parked, ready-to-drive). It's rejected during active hibernation entry or if a hard reboot is already in progress.
Status Service (9a590020)¶
Provides scooter state information
| Characteristic | Description | Values |
|---|---|---|
| 9a590021 | Operating State | - "stand-by" - "off" - "parked" - "shutting-down" - "ready-to-drive" - "updating" |
The set above is the full set BLE clients ever observe. When the scooter is in the internal hop-on state, the firmware cloaks the characteristic as "stand-by" so mobile apps with no concept of hop-on still see a locked scooter and offer their unlock affordance. hop-on-learning is collapsed to "parked" upstream (in bluetooth-service) and never reaches the firmware as a distinct value. See Vehicle States.
| 9a590022 | Seatbox State | - "open"
- "closed"
- "unknown" |
| 9a590023 | Handlebar Lock | - "locked"
- "unlocked" |
Battery Service (9a590040)¶
Auxiliary battery monitoring
| Characteristic | Description | Values |
|---|---|---|
| 9a590041 | AUX Voltage | 0-15000mV |
| 9a590043 | Charge Status | - "absorption-charge" - "not-charging" - "float-charge" - "bulk-charge" |
| 9a590044 | Charge Level | 0-100% in 25% steps |
Connectivity Battery Service (9a590060)¶
CBB status monitoring
| Characteristic | Description | Values |
|---|---|---|
| 9a590061 | Charge Level | 0-100% |
| 9a590063 | Remaining Capacity | Integer |
| 9a590064 | Full Capacity | Integer |
| 9a590065 | Cell Voltage | mV |
| 9a590072 | Charge Status | - "not-charging" - "charging" - "unknown" |
Power State Service (9a5900a0)¶
System power state monitoring
| Characteristic | Description | Values |
|---|---|---|
| 9a5900a1 | Power State | - "booting" - "running" - "suspending" - "suspending-imminent" - "hibernating-imminent" - "hibernating" |
(cf. State Diagram)
Main Battery Service (9a5900e0)¶
Primary battery monitoring
| Characteristic | Description | Values |
|---|---|---|
| 9a5900e2 | Primary State | - "unknown" - "asleep" - "active" - "idle" |
| 9a5900e3 | Primary Present | 0/1 |
| 9a5900e6 | Primary Cycles | Integer |
| 9a5900e9 | Primary SoC | 0-100% |
| 9a5900ee | Secondary State | - "unknown" - "asleep" - "active" - "idle" |
| 9a5900ef | Secondary Present | 0/1 |
| 9a5900f2 | Secondary Cycles | Integer |
| 9a5900f5 | Secondary SoC | 0-100% |
Power Mux Service (9a590100)¶
| Characteristic | Description | Values |
|---|---|---|
| 9a590101 | Selected Input (read) | Active battery source |
Extended Commands Service (9a590400)¶
Unified extensible command/response channel for phone app interaction
| Characteristic | Description | Values |
|---|---|---|
| 9a590401 | Command | Write-only, up to 128 bytes. Null-terminated string command (MITM-protected) |
| 9a590402 | Response | Read+notify, up to 512 bytes. Response string from iMX (MITM-protected) |
Supported commands:
| Command | Description | Response |
|---|---|---|
nav:dest lat,lon[,name] |
Set navigation destination | nav:ok |
nav:clear |
Clear navigation destination | nav:ok |
nav:fav:add lat,lon,name |
Save a location | nav:fav:added:<id> |
nav:fav:delete <id> |
Delete saved location | nav:ok |
nav:fav:navigate <id> |
Navigate to saved location | nav:ok |
nav:fav:list |
List saved locations | nav:fav:count:<n> then nav:fav:<id>:lat,lon,name per entry |
usb:ums |
Enter USB Mass Storage mode | usb:ok |
usb:normal |
Exit USB Mass Storage mode | usb:ok |
keycard:list |
List keycards | Response via keycard-service |
keycard:count |
Count keycards | Response via keycard-service |
keycard:add:<uid> |
Add keycard | Response via keycard-service |
keycard:remove:<uid> |
Remove keycard | Response via keycard-service |
time:set <unix_timestamp> |
Set system clock | time:ok |
config:apn <value> |
Set cellular APN | config:ok |
config:hibernate-timer <seconds> |
Set hibernation timeout | config:ok |
config:update-channel <channel> |
Set OTA update channel (stable/testing/nightly) | config:ok |
config:auto-standby-seconds <seconds> |
Set auto-lock idle timeout when parked (0=disabled, 0-3600) | config:ok |
alarm:enable |
Enable alarm system | alarm:ok |
alarm:disable |
Disable alarm system | alarm:ok |
alarm:arm |
Arm alarm (runtime, bypasses enabled setting) | alarm:ok |
alarm:disarm |
Disarm alarm | alarm:ok |
alarm:start / alarm:start:<N> |
Trigger alarm; duration in seconds, default from settings | alarm:ok |
alarm:stop |
Stop active alarm | alarm:ok |
ltc:enable |
Safe-enable the LTC4020 auxiliary charger | ltc:ok |
ltc:disable |
Disable the LTC4020 auxiliary charger | ltc:ok |
ltc:force-enable |
Force-enable the auxiliary charger, bypassing the safety check | ltc:ok |
ltc:force-disable |
Force-disable the auxiliary charger | ltc:ok |
ltc:status |
Query the LTC4020 charger state | ltc:status:on or ltc:status:off |
pm:hibernate-for <duration> |
Hibernate for the given duration (Go syntax: 30s, 10m, 8h); nRF52 wakes the iMX6 after the duration |
pm:ok |
pm:hibernate-cancel |
Cancel a pending hibernate-for and disarm the wake timer | pm:ok |
status:maps-available |
Query if offline maps are installed | status:maps-available:true or false |
status:navigation-available |
Query if routing engine is available | status:navigation-available:true or false |
status:version:mdb |
Query the installed MDB OS image version | status:version:mdb:<version> |
status:version:dbc |
Query the installed DBC OS image version | status:version:dbc:<version> |
ble:forget |
Ask the scooter to forget the phone sending the command, so an app's "forget this scooter" clears both halves of the bond. Ends the connection it arrives on. Needs nRF v2.8.0-ls or later, so probe cap:ble first |
ble:forget:ok, or ble:error:unsupported |
cap:list |
Enumerate supported capability categories | cap:count:<n> then cap:<name> per category |
cap:<category> |
List commands for a category | cap:<category>:count:<n> then cap:<category>:<command> per command |
get:<key> |
Read a schema-declared setting | get:<key>:<value> |
set:<key>:<value> |
Set a writable schema-declared setting | set:ok:<key> or set:error:<reason> |
Error responses follow the pattern <prefix>:error:<details>. See
Generic settings for the complete contract.
Generic settings¶
get:<key> and set:<key>:<value> use the schema published by settings-service
in settings:schema. Reads and writes reject unknown keys; writes also reject
read-only keys and schema-invalid values. A value begins after the first colon,
so a value may itself contain colons. get:list and get:list:<prefix> return
get:count:<n> followed by exactly n get:<key> notifications.
All extended commands, including generic settings, require the encrypted MITM-protected bonded connection described above. The protocol has no per-phone authorization: any bonded phone with extended-command access can read schema-declared setting values and change any writable schema setting. Treat bonding as the authorization boundary.
OTA Transfer Service (9a590500)¶
Firmware bundle transfer from the phone to the scooter (MDB and DBC updates over BLE). Windowed, resumable, integrity-checked; the nRF forwards these characteristics verbatim to bluetooth-service. See BLE OTA Firmware Transfer for the full protocol.
| Characteristic | Description | Values |
|---|---|---|
| 9a590501 | OTA Data (write w/o response, max 244 B) | [offset:u32 LE][chunk ≤ 240 B] |
| 9a590502 | OTA Control (write, max 128 B) | START / COMPLETE / ABORT / STATUS_REQ messages |
| 9a590503 | OTA Status (read+notify, max 64 B) | START_ACK / ACK / COMPLETE_ACK / INSTALL_PROGRESS / ABORT_ACK / ERROR messages |
Scooter Info Service (9a59a040)¶
System-level telemetry and configuration
| Characteristic | Description | Values |
|---|---|---|
| 9a59a041 | iMX Software Version | Version string (read-only) |
| 9a59a042 | Mileage/Odometer | Signed 32-bit little-endian integer, meters (read-only) |
| 9a59a043 | System Time | Unix timestamp string (write-only) |
| 9a59a044 | Navigation Active | 0 = no destination, 1 = destination set (read+notify) |
| 9a59a045 | UMS Status | 0 = normal, 1 = USB Mass Storage mode (read+notify) |
System Info Service (9a59a000)¶
| Characteristic | Description | Values |
|---|---|---|
| 9a59a001 | nRF Version | Version string (e.g. "v2.2.1-ls") |
| 9a59a021 | Reset Reason | Nordic RESETREAS register value (see below). Readable without bonding |
| 9a59a022 | Reset Count | Integer. Readable without bonding |
RESETREAS (Reset Reason Register)¶
Reset reason register. Cumulative unless cleared. Fields are cleared by writing '1'. If no reset sources are flagged, indicates reset from on-chip reset generator (power-on-reset or brownout reset).
| Field | Bit(s) | Access | Description | Value | Meaning |
|---|---|---|---|---|---|
| RESETPIN | 0 | RW | Reset from pin-reset detected | 0 1 |
Not detected Detected |
| DOG | 1 | RW | Reset from watchdog detected | 0 1 |
Not detected Detected |
| SREQ | 2 | RW | Reset from soft reset detected | 0 1 |
Not detected Detected |
| LOCKUP | 3 | RW | Reset from CPU lock-up detected | 0 1 |
Not detected Detected |
| OFF | 16 | RW | Reset due to wake up from System OFF mode (DETECT signal from GPIO) | 0 1 |
Not detected Detected |
| LPCOMP | 17 | RW | Reset due to wake up from System OFF mode (ANADETECT signal from LPCOMP) | 0 1 |
Not detected Detected |
| DIF | 18 | RW | Reset due to wake up from System OFF mode (debug interface mode entry) | 0 1 |
Not detected Detected |
| NFC | 19 | RW | Reset due to wake up from System OFF mode (NFC field detect) | 0 1 |
Not detected Detected |
| VBUS | 20 | RW | Reset due to wake up from System OFF mode (VBUS rising into valid range) | 0 1 |
Not detected Detected |
Register layout has:
- Low bits (0-3): Basic reset sources
- High bits (16-20): System OFF wake-up sources