I played with Bluetooth last week. I decided to write this article to organize my learnings.
First of all
These are my development environments
Hardware
- Raspberry Pi CM4 Model B
- Raspberry Pi CM4 IO board
- Beats Flex Wireless Bluetooth In-Ear Headphones with Mic
Software
- Raspberry Pi OS Bookworm
- BlueZ v5.66
Background
I don’t use Bluetooth for my job now. However, I could need the skill shortly and I had a week which I took for fun. So I decided to take the last week to get familiar with Bluetooth.
I usually use Python for Raspberry Pi and did a quick research for it. Then Bleak came to me first.
When I saw the number of stars in GitHub, I was like “It should be easy.” But it was not so straightforward. I got some errors when I tried to connect with my headset and I couldn’t understand what was going on from the error logs even after I googled the error message. Bleak is a tool to control bluetoothctl
with Python. So I started to figure out whether I could do without Python using bluetoothctl
directly. What I wanted to do was to connect with my headset, play sound, and record my voice notes.
What I did
This is the docs about bluetoothctl
I took a look at the docs but it’s not so useful. Basically, I repeated to try something and google the error.
Connect
I was following the article.
However, when I ran the pair
command, I had trouble, which was that I needed to confirm the pairing with 6 digits. Obviously, my headset doesn’t have any keyboards or displays. So I needed to turn off the confirmation. I googled it and found the following forum.
So bluetoothctl
has a default agent to confirm pairing, which is DisplayYesNo
. So I need to turn off the agent or set NoInputNoOutput
as agent.
The following commands are what I did in bluetoothctl
or bluetoothctl --agent NoInputNoOutput
. One note. agent no
works the same as agent off
but I didn’t find any resources for agent no
[bluetooth]# agent off
Agent unregistered
[bluetooth]# scan on
Discovery started
[CHG] Controller DC:A6:32:FF:B1:CE Discovering: yes
[NEW] Device 1D:53:12:55:EB:E6 1D-53-12-55-EB-E6
...
[bluetooth]# devices
Device A8:91:3D:DB:EE:A0 Beats Flex
Device 74:45:CE:75:6C:32 ATH-SQ1TW
Device 1D:53:12:55:EB:E6 1D-53-12-55-EB-E6
...
[bluetooth]# pair A8:91:3D:DB:EE:A0
Attempting to pair with A8:91:3D:DB:EE:A0
[CHG] Device A8:91:3D:DB:EE:A0 Connected: yes
[CHG] Device A8:91:3D:DB:EE:A0 Bonded: yes
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 00001000-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 00001101-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 0000110b-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 0000110c-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 0000110e-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 0000111e-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 00001200-0000-1000-8000-00805f9b34fb
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 02030302-1d19-415f-86f2-22a2106a0a78
[CHG] Device A8:91:3D:DB:EE:A0 UUIDs: 74ec2172-0bad-4d01-8f77-997b2be0722a
[CHG] Device A8:91:3D:DB:EE:A0 ServicesResolved: yes
[CHG] Device A8:91:3D:DB:EE:A0 Paired: yes
Pairing successful
[bluetooth]# connect A8:91:3D:DB:EE:A0
Attempting to connect to A8:91:3D:DB:EE:A0
...
[Beats Flex]# info
Device A8:91:3D:DB:EE:A0 (public)
Name: Beats Flex
Alias: Beats Flex
Class: 0x00240418
Icon: audio-headphones
Paired: yes
Bonded: yes
Trusted: no
Blocked: no
Connected: yes
LegacyPairing: no
UUID: Service Discovery Serve.. (00001000-0000-1000-8000-00805f9b34fb)
UUID: Serial Port (00001101-0000-1000-8000-00805f9b34fb)
UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
UUID: Advanced Audio Distribu.. (0000110d-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
UUID: Handsfree (0000111e-0000-1000-8000-00805f9b34fb)
UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb)
UUID: Vendor specific (02030302-1d19-415f-86f2-22a2106a0a78)
UUID: Vendor specific (74ec2172-0bad-4d01-8f77-997b2be0722a)
Modalias: bluetooth:v004Cp2010d0372
Play sound
Still following the article. I would say nothing difficult here.
The following commands are what I did in the terminal.
paplay your_sound_file.wav
Record voice notes
I followed the article to record. Also, I’ve attached the docs of options for arecord
One thing I want to mention here is Device Profiles, which are the settings for the input and output of the Bluetooth device. You can find more details in the following article.
What happened to me was that I could hear sounds from my headset but I couldn’t record my voice with it. After reading the article, I checked my device profile following the web page.
$ pactl list cards
Card #70
Name: bluez_card.A8_91_3D_DB_EE_A0
Driver: module-bluez5-device.c
Owner Module: n/a
Properties:
api.bluez5.address = "A8:91:3D:DB:EE:A0"
api.bluez5.class = "0x240418"
api.bluez5.connection = "disconnected"
api.bluez5.device = ""
api.bluez5.icon = "audio-headphones"
api.bluez5.path = "/org/bluez/hci0/dev_A8_91_3D_DB_EE_A0"
bluez5.auto-connect = "[ hfp_hf hsp_hs a2dp_sink ]"
bluez5.profile = "off"
device.alias = "Beats Flex"
device.api = "bluez5"
device.bus = "bluetooth"
device.description = "Beats Flex"
device.form_factor = "headphone"
device.icon_name = "audio-headphones-bluetooth"
device.name = "bluez_card.A8_91_3D_DB_EE_A0"
device.product.id = "0x2010"
device.string = "A8:91:3D:DB:EE:A0"
device.vendor.id = "bluetooth:004c"
media.class = "Audio/Device"
factory.id = "14"
client.id = "34"
object.id = "70"
object.serial = "70"
Profiles:
off: Off (sinks: 0, sources: 0, priority: 0, available: yes)
a2dp-sink: High Fidelity Playback (A2DP Sink) (sinks: 1, sources: 0, priority: 16, available: yes)
headset-head-unit: Headset Head Unit (HSP/HFP) (sinks: 1, sources: 1, priority: 1, available: yes)
a2dp-sink-sbc: High Fidelity Playback (A2DP Sink, codec SBC) (sinks: 1, sources: 0, priority: 18, available: yes)
a2dp-sink-sbc_xq: High Fidelity Playback (A2DP Sink, codec SBC-XQ) (sinks: 1, sources: 0, priority: 17, available: yes)
headset-head-unit-cvsd: Headset Head Unit (HSP/HFP, codec CVSD) (sinks: 1, sources: 1, priority: 2, available: yes)
headset-head-unit-msbc: Headset Head Unit (HSP/HFP, codec mSBC) (sinks: 1, sources: 1, priority: 3, available: yes)
Active Profile: a2dp-sink-sbc
Ports:
headphone-output: Headphone (type: Headphones, priority: 0, latency offset: 0 usec, available)
Properties:
port.type = "headphones"
Part of profile(s): a2dp-sink, a2dp-sink-sbc, a2dp-sink-sbc_xq
headphone-hf-input: Handsfree (type: Headphones, priority: 0, latency offset: 0 usec, available)
Properties:
port.type = "headphones"
Part of profile(s): headset-head-unit, headset-head-unit-cvsd, headset-head-unit-msbc
headphone-hf-output: Handsfree (type: Headphones, priority: 0, latency offset: 0 usec, available)
Properties:
port.type = "headphones"
Part of profile(s): headset-head-unit, headset-head-unit-cvsd, headset-head-unit-msbc
Then I realized the device profile is a2dp-sink-sbc
. So I needed to change from A2DP to HSP. So I changed it like below.
$ pactl set-card-profile [Your Card ID] headset-head-unit-msbc
$ pactl set-card-profile 70 headset-head-unit-msbc # in my case
Finally, I recorded my voice notes following arecord
command.
arecord -d 10 sample.wav
I got along with bluetoothctl
so I’ll return back to Bleak and play with it again.
That’s it!
Appendix
bluetoothctl
command lists
bluetoothctl
connect/pair errors
org.bluez.Error.NotReady
Failed to pair: org.bluez.Error.AlreadyExists
- Check Bluetooth status
$ sudo service bluetooth status
● bluetooth.service - Bluetooth service
Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; preset: enabled)
Active: active (running) since Sat 2024-02-03 19:10:47 GMT; 5min ago
Docs: man:bluetoothd(8)
Main PID: 769 (bluetoothd)
Status: "Running"
Tasks: 1 (limit: 3912)
CPU: 216ms
CGroup: /system.slice/bluetooth.service
└─769 /usr/libexec/bluetooth/bluetoothd
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/aptx_ll_1
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/aptx_ll_0
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/aptx_ll_duplex_1
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/aptx_ll_duplex_0
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/faststream
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/faststream_duplex
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSink/opus_05
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/opus_05
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSink/opus_05_duplex
Feb 03 19:10:57 blue bluetoothd[769]: Endpoint registered: sender=:1.40 path=/MediaEndpoint/A2DPSource/opus_05_duplex
If there is an error in the log, restart Bluetooth with sudo service bluetooth restart