Bluetooth Classic contains a handful of profiles which use AT commands to work with telephony devices over an RFComm connection. We discussed these briefly in #3 of this series and today will look in more detail and the most commonly implemented one – Hands Free Profile.
Look No Hands!
Two similar profiles exist in Headset Profile and Handsfree Profile. Headset was designed for devices with a single action button and mono audio.
Hands-free Profile is more advanced and is what you’ll find in most car kits and headphone devices. It allows for more complex actions such as dialling numbers and additional audio codecs.
What all the “AT-command” profile share is a set way of communicating over the serial connection using text commands which have a long history in the world of modems and telephony devices. We will look at some of these commands used to establish a connection later on.
There are two separate (yet equally important) entities in the hands-free profile, the Audio Gateway (AG) which is the source of the audio (generally a Mobile Phone) and the Hands Free device which has speakers, microphone and controls which we want to operate the AG from.
You Probably Don’t Want To Do This
Whenever I’m asked about hands-free service I usually suggest steering clear of implementing this. In most cases your host device already has support for this profile and by establishing a connection you are potentially fighting with the OS itself. The other issue is that the audio part of the equation is handled via a separate Synchronous Connection Oriented (SCO) channel and this is handled lower in the stack than application developers normally have access to. Even in the sample program I created for this article you have to fight with Windows to ensure it is not already connected – the hands-free device won’t respond to multiple connections at the same time.
In recent versions of Windows this built-in support for Handsfree Profile has had a public WinRT API which we can use. This means you can connect to a mobile phone and make your PC into a hands free device or speaker phone. This is how Thy Phone works with any mobile device whereas Microsoft chose to only allow Android devices with their Phone Link app. There is also support for the Windows PC to be the AG and connect to a Hands Free device such as a headset or one of these cool Fisher Price phones if you are lucky enough to have one!
If the hands free device is connected in Windows settings, you’ll need to disconnect it so that it just shows “Paired” then it is ready to accept a connection.
The sample app performs a couple of basic operations and will not establish an audio link.
Into The Code
The sample app is a simple .NET MAUI app created from the new project template. The full code is available here on GitHub. I’ve changed the default button to initiate a connection following these steps:-
- Use the BluetoothDevicePicker to present the available devices to the user to pick one.
- If selected use BluetoothClient to connect to the Hands-free service.
- If successful get a Stream to read from the device and respond.
The rest of the functionality then uses a listening loop which will read blocks of data from the device as ASCII text, and process each line at a time. All messages from the Hands-free device are a single line terminated by a return character. For each command we may need to return a specific response but all have to be followed with an “
OK” response (or “
ERROR” but we are not doing any error checking here). For responses from the AG to the HF each message is preceded and followed by a pair of carriage return and newline characters e.g. “
\r\nOK\r\n” in C#.
To establish a connection with the HF there are a number of standard and optional queries and responses to deal with. Firstly the HF device will tell the AG device which optional features it supports from a bitmask using the
+BRSF code and you must reply similarly, though you can use “
BRSF:0” to support none of them.
The listening loop checks for these core messages and responds with the default responses. To any other command it will return OK. I won’t go into every command but you can look at the source code for more detail. I will highlight a couple of interesting ones:-
From Core to Apple
Apple published their own extension to the hands-free profile which is now quite widely supported and it allows the hands free device to report its battery level back to the iPhone (or any device that supports the command). The HF will first send “
AT+XAPL” to the AG and if you respond with “
+XAPL=iPhone,2” you indicate you support this iPhone specific battery notification. There are a couple of other flags other than 2 you can specify but this is the interesting one.
After this the HF will send you “
AT+IPHONEACCEV” containing key/values depending on which notifications you showed support for. You’ll need to parse this to find the value corresponding to the key 1 and this is a digit between 0 and 9 which give you the battery level of the hands free device to the nearest 10%. There is a helper method in the sample code which shows how to get this. This isn’t as accurate as the Bluetooth LE Battery Level service we looked at before but most handsfree devices use this method. Annoyingly, although Windows has an API for working with hands-free it doesn’t expose the battery level of devices even though you can see through the Windows Settings app that it knows. You can see from the screenshot above that my Chatter Telephone is at 70% battery.
The last interesting command we listen for is the one to dial a number – “
ATD“. The Fisher Price phone is not the most practical in this regard because you have the inherent slowness of dialling each digit and waiting for the dial to return to start with but you have to wait a few seconds following the last digit before it sends the ATD command to the AG. In the sample app we simply parse the number and display an alert dialog to the user. You could technically use this to kick off other actions, but I can’t see it catching on as an alternative to the Stream Deck or similar!
If you’ve found this interesting, please look at the full sample project on GitHub. In order to start better housekeeping of the 32feet.NET repository I’ll be updating and moving sample apps into their own repository. If you have ideas for new samples or improvements to the existing ones please open an issue so that they can be prioritised, and of course pull-requests are welcomed too.