Bluetooth adapter control in the odio API
The Bluetooth backend drives the node’s adapter over D-Bus (BlueZ), in both directions:
- Input (A2DP sink): the node acts as an audio receiver, so phones and computers can stream to it.
- Output: the node connects to nearby speakers or headphones and uses them as an audio output sink.
Enabled when Bluetooth is installed on the node.
Endpoints
Section titled “Endpoints”Status
Section titled “Status”GET /bluetoothReturns adapter state (powered, pairing and scan state) and the known_devices list. Each device carries paired, bonded, trusted, and connected flags, so a bonded speaker and a freshly scanned one share the same shape:
{ "address": "AA:BB:CC:DD:EE:FF", "name": "JBL Go 3", "paired": true, "bonded": true, "trusted": true, "connected": true}bonded (since odio-api v0.14.0) means the node still holds the device’s pairing key, so it can reconnect without re-pairing. connected is the live link state.
Input control
Section titled “Input control”POST /bluetooth/power_upPOST /bluetooth/power_downPOST /bluetooth/pairing_modePairing mode makes the node discoverable for a configurable duration (default 60s). Devices are automatically trusted on first pairing.
Output
Section titled “Output”GET /bluetooth/devicesPOST /bluetooth/scanPOST /bluetooth/scan/stopPOST /bluetooth/connectPOST /bluetooth/disconnectGET /bluetooth/devicesreturns the same device list as theknown_devicesfield ofGET /bluetooth.POST /bluetooth/scanstarts active discovery (powering the adapter up if needed) and filters for classic audio devices. Discovered devices stream live throughbluetooth.discoveredevents and are merged into the device list as they appear (since odio-api v0.14.0; also pushed viabluetooth.updated), then reconciled with BlueZ when the scan stops. The scan auto-stops afterscanTimeout(default 60s).POST /bluetooth/scan/stopstops the scan (idempotent).POST /bluetooth/connectwith{"address": "AA:BB:CC:DD:EE:FF"}. The address is validated first (malformed →400), then the call blocks until BlueZ answers (it can take a few seconds and may trigger pairing) and returns the real outcome. A not-yet-bonded device gets a brief pairable window for the connect; a bonded one reconnects without it. On success the device is trusted and any active scan is stopped.POST /bluetooth/disconnectwith the same{"address": ...}body.
Connected output devices route through PulseAudio/PipeWire as a regular output sink.
Events
Section titled “Events”| Event | Trigger |
|---|---|
bluetooth.updated | Adapter or device state change (power, pairing, scan, connection) |
bluetooth.discovered | A device appears during an active scan |
Configuration
Section titled “Configuration”bluetooth: enabled: true timeout: 5s pairingTimeout: 60s idleTimeout: 30m scanTimeout: 60sidleTimeout auto-powers down the adapter when no device is connected (0 to disable). scanTimeout auto-stops a scan after the given delay (0 to disable).
System setup
Section titled “System setup”The backend requires BlueZ. The odio installer handles this automatically. For standalone installations, see Configuration — Bluetooth.
How it works
Section titled “How it works”The backend communicates with BlueZ via D-Bus. On power-up, it auto-unblocks soft-blocked rfkill devices. Devices streaming to the node (A2DP sink) appear as MPRIS players and PulseAudio clients; devices the node connects to appear as PulseAudio output sinks.