Skip to content

Bluetooth output

odio can act as a Bluetooth source, routing whatever it’s playing (Spotify Connect, MPD, AirPlay, CDs, …) to a Bluetooth speaker or headphones. Once paired, the remote device shows up as a PulseAudio sink in the embedded UI’s Default Sink dropdown, selectable like any other output.

  1. Put your Bluetooth speaker in pairing mode (usually a long-press on the Bluetooth button).

  2. SSH into the odio node and open an interactive bluetoothctl session:

    Terminal window
    bluetoothctl
  3. Power the adapter, scan for the speaker, connect, and trust it:

    power on
    scan on

    Wait for your speaker to appear in the scan output, for example:

    [NEW] Device 40:C1:F6:D4:67:88 JBL Go 3

    Then stop the scan, connect, and trust the device for future reconnections:

    scan off
    connect 40:C1:F6:D4:67:88
    trust 40:C1:F6:D4:67:88
    exit

    Replace the MAC with yours. Because the speaker is in pairing mode, connect auto-pairs and bonds, no PIN needed. trust lets the node reconnect the speaker without manual confirmation after a reboot.

If you reflash the node, BlueZ loses its pairing state, but the speaker still has the previous odio in its trusted list. Put the speaker back in pairing mode and run the same bluetoothctl flow (power on, scan on, wait for the speaker to show up, scan off), with one difference: trust comes before connect.

[bluetoothctl]> trust 40:C1:F6:D4:67:88
[CHG] Device 40:C1:F6:D4:67:88 Trusted: yes
Changing 40:C1:F6:D4:67:88 trust succeeded
[bluetoothctl]> connect 40:C1:F6:D4:67:88
Attempting to connect to 40:C1:F6:D4:67:88
[CHG] Device 40:C1:F6:D4:67:88 Connected: yes

The speaker side still has odio trusted from the first pairing, so trusting on the node side first puts both ends back in sync before the connect attempt.

Open the embedded UI. The paired speaker appears in Audio Server → Default Sink. Pick it, and every odio source now plays through it.

odio embedded UI with a JBL Go 3 selected as the Default Sink, Spotifyd streaming through it

Volume sync works the same way as for a built-in DAC: moving the UI slider changes the speaker’s volume over AVRCP, and physical buttons on the speaker update the UI.

Once trusted, BlueZ reconnects the speaker automatically when both sides are powered and in range, including after a reboot of the node. This usually just works, but auto-reconnect isn’t 100% reliable across every speaker and firmware. If the sink doesn’t show up, reconnect manually:

Terminal window
bluetoothctl connect 40:C1:F6:D4:67:88

You can wrap this in a systemd user unit and add it to the systemd.user whitelist in go-odio-api’s config, which makes it manageable from the Services panel in the embedded UI.

  • Bluetooth for the opposite direction, using the odio node as an A2DP speaker that your phone streams to.