Bluetooth Pairing And Connecting - arkq/bluez-alsa GitHub Wiki

Bluetooth Pairing and Connecting

Pairing

A Bluetooth device must be paired with the BlueALSA host to establish its identity and create encryption keys before any profile services can be connected. BlueALSA does not participate directly in the pairing process itself, but the bluealsa daemon must be running so that BlueZ advertises support for the audio profiles to the remote device.

So it is possible to use any Bluetooth pairing tool on our BlueALSA host, so long as that tool does not itself depend on PulseAudio or Pipewire (for example the Gnome desktop Bluetooth settings application works fine with BlueALSA, so long as the PulseAudio bluetooth modules are disabled). Given that the main focus of BlueALSA is on small, possibly embedded, devices, they often do not have a GUI and sometimes no user interface at all. In these cases we need a tool that can be run either from the command line in an SSH session, or within a script running as a background service.

In this article we look at 2 such tools: bluetoothctl from the Bluez project and bluez-tools by Alex Orlenko.

Bluetooth specifies a number of different pairing algorithms, and the devices choose which to use depending on their ability to interact with the user. For this to work each device must inform the other of its "capability". The Bluetooth specification defines a standard set of input and output capabilities which are discussed in Bluetooth Pairing Part 1.

When receiving pairing requests from a remote device, Bluez delegates pairing interaction to an "agent". bluetoothctl has its own built-in agent while bluez-tools uses a dedicated tool (bt-agent) for this purpose. So we need to tell the agent which "capability" we want our BlueALSA host to use. In this article we focus on having as little user interaction as possible on the BlueALSA host, so we tell our agent to use the "NoInputNoOutput" capability.

The majority of bluetooth headphones or speakers do not have any method to initiate the pairing process; they generally have a button or other control that puts them into a "pairable" mode, and then it is up to the other device (typically a mobile phone) to handle the user interaction. So when connecting the BlueALSA host to such devices we must use our chosen pairing tool to drive the pairing process. In this role we refer to the BlueALSA host as the "initiator" of the pairing request.

Conversely, when pairing with a mobile phone, we use the pairing tool to act in the passive "discoverable" mode and drive the pairing process from the phone. In this role we refer to the BlueALSA host as the "responder" of the pairing request.

Connecting

Each of the Bluetooth profiles consists of one or more services which must be "connected" before use. Most devices will connect all profiles as a single "connection" as far as the user is concerned, but "under the hood" each of them must be individually authorized. For the device from which the connection request originates each service is automatically authorized, but for the device receiving the connection request some form of action must be taken for authorization.

Bluez can be set either to accept all service connection requests (from a paired device) by setting the "Trusted" property for that device, or to delegate service connection authorization to the agent. However, the agent "capability" property applies only to pairing requests, not to connection requests, so if the authorization is delegated to the agent then the agent may still prompt the user even when using NoInputNoOutput capability. In this article we consider only the "Trusted" method.

Examples

Make sure that the bluealsa daemon is running, with the required command-line parameters, before starting any of these procedures. See the bluealsa(8) manual page for details. In these examples we use the following devices:

  • BlueALSA host
    address: 04:42:1A:55:01:01

  • Bluetooth Headphones
    name: Excellent Headset
    address: C4:67:B5:37:02:02

  • Mobile Phone
    name: Super Phone
    address: B8:27:EB:B5:03:03

bluetoothctl

The examples here were created using Bluez version 5.64. Later versions may produce more verbose output, but the user command sequence is the same. In the following example bluetoothctl sessions, lines beginning [bluetooth]# are prompts asking for the next user command, other lines are output by bluetoothctl.

BlueALSA host as initiator

An example bluetoothctl interactive session to pair with headphones (or speakers, or hands-free devices).

By default bluetoothctl will register the capability "KeyboardDisplay" when started. This may cause the remote device to require us to input a PIN, so we start bluetoothctl as:

user@host:~$ bluetoothctl --agent=NoInputNoOutput
Agent registered
[CHG] Controller 04:42:1A:55:01:01 Pairable: yes
[bluetooth]# 

Register this program as the default agent, to ensure that it is our chosen capability (NoInputNoOutput) that is reported to the remote device.

[bluetooth]# default-agent 
Default agent request successful
[bluetooth]#

On the headphones, enable pairing mode (consult the headphones documentation for instructions). Pairing mode is usually time-limited, typically 3 minutes or so. Now back in the bluetoothctl session start a scan to discover the headphones:

[bluetooth]# scan on
Discovery started
[CHG] Controller 04:42:1A:55:01:01 Discovering: yes

After a short while, anywhere from less than 1 second to about 2 minutes, the headphones will be discovered and we will see, amongst other discovery info:

[NEW] Device C4:67:B5:37:02:02 Excellent Headset

At this point we should stop the scan:

[bluetooth]# scan off
Discovery stopped

There may be some more output, which can be ignored, before we get the prompt back. For example:

[CHG] Device C4:67:B5:37:02:02 TxPower is nil
[CHG] Device C4:67:B5:37:02:02 RSSI is nil
[CHG] Controller 04:42:1A:55:01:01 Discovering: no
[bluetooth]# 

The collected information from the headphones will only be stored temporarily (by default for 30 seconds), so when the prompt appears we must immediately initiate pairing:

[bluetooth]# pair C4:67:B5:37:02:02 
Attempting to pair with C4:67:B5:37:02:02
[CHG] Device C4:67:B5:37:02:02 Connected: yes
[CHG] Device C4:67:B5:37:02:02 UUIDs: 00001107-d102-11e1-9b23-00025b00a5a5
[CHG] Device C4:67:B5:37:02:02 UUIDs: 00001108-0000-1000-8000-00805f9b34fb
[CHG] Device C4:67:B5:37:02:02 UUIDs: 0000110b-0000-1000-8000-00805f9b34fb
[CHG] Device C4:67:B5:37:02:02 UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
[CHG] Device C4:67:B5:37:02:02 UUIDs: 0000110e-0000-1000-8000-00805f9b34fb
[CHG] Device C4:67:B5:37:02:02 UUIDs: 0000111e-0000-1000-8000-00805f9b34fb
[CHG] Device C4:67:B5:37:02:02 UUIDs: 00001200-0000-1000-8000-00805f9b34fb
[CHG] Device C4:67:B5:37:02:02 ServicesResolved: yes
[CHG] Device C4:67:B5:37:02:02 Paired: yes
Pairing successful
[CHG] Device C4:67:B5:37:02:02 ServicesResolved: no
[CHG] Device C4:67:B5:37:02:02 Connected: no
[bluetooth]# 

Now test that we can connect to the headphones:

[bluetooth]# connect C4:67:B5:37:02:02 
Attempting to connect to C4:67:B5:37:02:02
[CHG] Device C4:67:B5:37:02:02 Connected: yes
Connection successful
[CHG] Device C4:67:B5:37:02:02 ServicesResolved: yes
[Excellent Headset]# 

If we wish the headphones to be able to connect automatically when they are switched on, then we tell Bluez to "trust" them. This step is optional:

[Excellent Headset]# trust C4:67:B5:37:02:02 
[CHG] Device C4:67:B5:37:02:02 Trusted: yes
Changing C4:67:B5:37:02:02 trust succeeded
[Excellent Headset]#

The pairing and trust information is now permanently stored, so we can quit from bluetoothctl:

[Excellent Headset]# quit
user@host:~$ 

If we chose not to "trust" the headphones then we must manually connect from the BlueALSA host when we want to use the headphones:

user@host:~$ bluetoothctl connect C4:67:B5:37:02:02
Attempting to connect to C4:67:B5:37:02:02
[CHG] Device C4:67:B5:37:02:02 Connected: yes
Connection successful
user@host:~$

BlueALSA host as responder

When receiving the pairing request from the remote device, we need an agent, so we tell bluetoothctl to use the NoInputNoOutput agent capability. This method is normally used when building a bluetooth speaker with BlueALSA, and pairing it with a mobile phone (or computer).

user@host:~$ bluetoothctl --agent=NoInputNoOutput
Agent registered
[CHG] Controller 04:42:1A:55:01:01 Pairable: yes
[bluetooth]# 

When the [bluetooth]# prompt appears, register this session as the "default agent"; this ensures that all incoming requests from remote devices will be handled by this program.

[bluetooth]# default-agent 
Default agent request successful
[bluetooth]#

Next turn on discoverable mode, which will cause Bluez to start advertising its supported profiles and services so that remote devices can discover it:

[bluetooth]# discoverable on
Changing discoverable on succeeded
[CHG] Controller 04:42:1A:55:01:01 Discoverable: yes
[bluetooth]# 

The BlueALSA host will remain in discoverable mode for a limited time, by default 3 minutes, so now we must immediately begin the pairing process on the mobile phone. Tell the phone to search for devices. When it discovers the BlueALSA host, tell the phone to pair with the BlueALSA host. At this stage we should see pairing process messages in the bluetoothcl session, for example:

[NEW] Device B8:27:EB:B5:03:03 Super Phone
[CHG] Device B8:27:EB:B5:03:03 Modalias: usb:v1D6Bp0246d0537
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 0000110a-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 0000110e-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 00001112-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 0000111f-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 00001200-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 ServicesResolved: yes
[CHG] Device B8:27:EB:B5:03:03 Paired: yes
[CHG] Device B8:27:EB:B5:03:03 ServicesResolved: no
[CHG] Device B8:27:EB:B5:03:03 Connected: no
[bluetooth]#

Notice that pairing has succeeded, but connection has failed. We need to "trust" the phone so that it can connect. This step is essential. For example:

[bluetooth]# trust B8:27:EB:B5:03:03 
[CHG] Device B8:27:EB:B5:03:03 Trusted: yes
Changing B8:27:EB:B5:03:03 trust succeeded
[bluetooth]#

We no longer need to be discoverable, so to save bandwidth we disable that mode:

[bluetooth]# discoverable off
Changing discoverable off succeeded
[CHG] Controller 04:42:1A:55:01:01 Discoverable: no
[bluetooth]#

Now, back on the phone, we can try to connect, and this time we should see the connection process messages in the bluetoothctl session, for example:

[CHG] Device B8:27:EB:B5:03:03 Connected: yes
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 0000110a-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 0000110d-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 0000110e-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 00001112-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 0000111f-0000-1000-8000-00805f9b34fb
[CHG] Device B8:27:EB:B5:03:03 UUIDs: 00001200-0000-1000-8000-00805f9b34fb
[Super Phone]#

We can now quit the bluetoothctl session. The pairing and trust information is stored so that future connection requests from the phone will be accepted.

[Super Phone]# quit
user@host:~$ 

bluez-tools

For a simpler user interface, that is easier to use in scripts for example, there is a project called bluez-tools (https://github.com/khvzak/bluez-tools) which provides a set of non-interactive command-line tools for managing bluetooth devices. It appears that the project is no longer actively maintained, but the tools are still compatible with current Bluez releases and many Linux distributions include a bluez-tools package in their repositories. If the remote device bluetooth address is known then these tools can be used to complete the pairing process without any user interaction.

BlueALSA host as initiator

We begin by putting the headphones into pairing mode (consult the headphones documentation for instructions). Pairing mode is usually time-limited, typically 3 minutes or so.

If we do not already know the bluetooth address of the headphones we can run a discovery process to find it. If the bluetooth address of the headphones is already known, skip this step.

user@host:~$ bt-adapter --discover
Searching...
[C4:67:B5:37:02:02]
  Name: Excellent Headset
  Alias: Excellent Headset
  Address: C4:67:B5:37:02:02
  Icon: audio-headphones
  Class: 0x240418
  LegacyPairing: 0
  Paired: 0
  RSSI: -68

When the discovery process has found the correct device, stop bt-adapter by pressing Ctrl-C.

bluez-tools has a single command to perform the entire pairing process. This is done using the bt-device application, and confusingly the command is called connect, even though it only pairs with the device, it does not connect the services.

user@host:~$ bt-device --connect C4:67:B5:37:02:02
Connecting to: C4:67:B5:37:02:02
Done

If we wish the headphones to be able to connect automatically when they are switched on, then we set the "Trusted" attribute. This step is optional:

user@host:~$ bt-device --set  C4:67:B5:37:02:02 Trusted on

If we chose not to trust the headphones, then we will need to connect manually to use them. The bluez-tools suite does not include a tool for this, but we can use bluetoothctl in non-interactive mode:

user@host:~$ bluetoothctl connect C4:67:B5:37:02:02

BlueALSA host as responder

Begin by starting a bluetooth agent in the background. Unfortunately bt-agent still asks for approval of service connections even in "NoInputNoOutput" mode. So it correctly handles the pairing process without user interaction, but if the phone then tries to connect it will wait for confirmation from the user. If running in the background this will cause it to be stopped as background processes are not permitted to read from the terminal. We can work around this by using the yes command which will automatically reply "y" whenever bt-agent asks for user input.

user@host:~$ /usr/bin/yes | bt-agent -c NoInputNoOutput &
[1] 86245
Agent registered
Default agent requested

Ensure that the local adapter is pairable and discoverable. Discoverable mode is by default time-limited to 3 minutes

user@host:~$ bt-adapter --set Pairable on
Pairable: 0 -> 1
user@host:~$ bt-adapter --set Discoverable on
Discoverable: 0 -> 1

Now on the phone search for bluetooth devices, and when it finds the BlueALSA host tell it to pair with the BlueALSA host. When the pairing is complete the phone will show the BlueALSA host in its list of paired devices, and may try to connect. This initial connection request, if attempted, will be accepted by bt-agent.

We can now stop the agent. bt-agent does not stop when sent a SIGTERM signal, so we use SIGHUP:

user@host:~$ kill -HUP %1

Also disable pairable mode and discoverable mode

user@host:~$ bt-adapter --set Pairable off
Pairable: 1 -> 0
user@host:~$ bt-adapter --set Discoverable off
Discoverable: 1 -> 0

Since we do not wish to have an agent running permanently to accept service connection requests, we set the "Trusted" property for the phone:

user@host:~$ bt-device --set B8:27:EB:B5:03:03 Trusted on

If the phone is not already connected, it should now be possible to connect it.