Bluetooth Speaker/Mic + RPi Wyoming Satellite working with RPi Bluetooth

I kept seeing people say that bluetooth speakers/mic wouldn’t work directly without gutting them and putting an ESP in them.

Thought it would be an interesting challenge to figure it out as it’s just a basic speak/mic attached to Linux. I didn’t see why it wouldn’t work with the wyoming-satellite etc.

I don’t own any bluetooth speaker/mic combo devices but I had this Bluetooth Transmitter/Receiver for my vehicle which has a mic built in. It also allows you to plug powered speakers into the side and get audio out. I also liked that it showed the name of the device connected on the LCD screen. The mic quality is pretty bad but it worked (I don’t recommend it). Maybe they have made better ones now since this one. They can be found on Aliexpress from any place around $10-$20 and there is quite a gambit of them.

image

You may have to play with some more Linux/Pipewire settings to get it to autoload reliably but it indeed does work ok.

You need to use Debian 12 “Bookworm” as Pipewire is now default in it. You can use backports with Debian 11 “Bullseye” but why bother.

The bluetooth mic works using Profile: Headset Head Unit (HSP/HFP, codec CVSD) ie you will have lower sound quality than A2DP. A2DP does not allow for microphone use currently.

In researching this topic though I saw where people were adding scripts to profile swap for other purposes but would switch to A2DP while listening to music.

Personally, I could care less about quality and just that it is working for this demo as it sounded ok to me. I did notice the difference though. I saw some interesting things about codecs you could use to make HSP sound better but you had to compile some stuff for flac usage. I didn’t look further into it or maybe I misunderstood what they were talking about.

Installation

Some of these things may or may not be required but they helped in troubleshooting things with some visual representation. They are not needed for a CLI based setup. On Rasbian/Debian 12 most of this stuff is already there so maybe just try it out and hope for the best with no need to install any of this. I’m just tacking it here from a bare-bones look…

sudo apt install pipewire \
  libspa-0.2-bluetooth \
  pipewire-audio-client-libraries \
  pipewire-media-session- \
  wireplumber \
  bluez \
  blueman \
  pavucontrol

Note: Most documentation states old locations /etc/pipewire or /etc/pulse for configurations. Currently, all the settings for pipewire are located here: /usr/share/pipewire/ unless you are playing with local user stuff.

Uncomment this extra command module-switch-on-connect should tell pipewire-pulse to autoswitch to the bluetooth audio device when connected. If the device is not an always-on thing not sure if that is 100% thought out. I don’t think there is a wake on bluetooth with most power saving devices :smiley: .

nano /usr/share/pipewire/pipewire-pulse.conf

# Extra commands can be executed here.
#   load-module : loads a module with args and flags
#      args = "<module-name> <module-args>"
#      flags = [ "no-fail" ]
pulse.cmd = [
    { cmd = "load-module" args = "module-always-sink" flags = [ ] }
    { cmd = "load-module" args = "module-switch-on-connect" }
    #{ cmd = "load-module" args = "module-gsettings" flags = [ "nofail" ] }
]

This is not required but to clear up some vcp, mcp, bap bluetooth service errors uncomment Experimental=true in:

sudo nano /etc/bluetooth/main.conf
# Enables D-Bus experimental interfaces
# Possible values: true or false
Experimental = true

Make sure this is uncommented as well:

# AutoEnable defines option to enable all controllers when they are found.
# This includes adapters present on start as well as adapters that are plugged
# in later on. Defaults to 'true'.
AutoEnable=true
sudo systemctl restart bluetooth
sudo usermod -G bluetooth -a pi
su - $USER
id
rfkill list
0: phy0: Wireless LAN
        Soft blocked: no
        Hard blocked: no
1: hci0: Bluetooth
        Soft blocked: yes
        Hard blocked: no
rfkill unblock all
rfkill list
0: phy0: Wireless LAN
        Soft blocked: no
        Hard blocked: no
1: hci0: Bluetooth
        Soft blocked: no
        Hard blocked: no
bluetoothctl
Agent registered

power on
Changing power on succeeded

agent on
Agent is already registered

default-agent
Default agent request successful

scan on
Discovery started
[CHG] Controller DE:AD:BE:EF:CA:FE Discovering: yes
[NEW] Device DE:AD:BE:EF:FA:CE AudioHeadSet

pair de:ad:be:ef:fa:ce
Attempting to pair with DE:AD:BE:EF:FA:CE
[CHG] Device DE:AD:BE:EF:FA:CE Connected: yes
[CHG] Device DE:AD:BE:EF:FA:CE UUIDs: #########-####-####-####-###############
[CHG] Device DE:AD:BE:EF:FA:CE UUIDs: #########-####-####-####-###############
[CHG] Device DE:AD:BE:EF:FA:CE ServicesResolved: yes
[CHG] Device DE:AD:BE:EF:FA:CE Paired: yes
Pairing successful
[CHG] Device DE:AD:BE:EF:FA:CE ServicesResolved: no
[CHG] Device DE:AD:BE:EF:FA:CE Connected: no

trust de:ad:be:ef:fa:ce
[CHG] Device DE:AD:BE:EF:FA:CE Trusted: yes
Changing DE:AD:BE:EF:FA:CE trust succeeded

connect de:ad:be:ef:fa:ce
Attempting to connect to DE:AD:BE:EF:FA:CE
[CHG] Device DE:AD:BE:EF:FA:CE Connected: yes
Connection successful
[CHG] Device DE:AD:BE:EF:FA:CE ServicesResolved: yes

scan off
quit

Note: If HSP is not showing up do not do this for your first time setting up! Unless you think you broke something during setup. It will delete all bluetooth devices that were trusted:

sudo cp -r /var/lib/bluetooth /var/lib/bluetooth_BACKUP
sudo systemctl stop bluetooth
sudo rm -rf /var/lib/bluetooth/*
sudo systemctl start bluetooth
sudo reboot

Add back the device with the directions above.

sudo apt install gstreamer1.0-pipewire
gst-launch-1.0 audiotestsrc ! audioresample ! autoaudiosink
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
Redistribute latency...
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
Redistribute latency...
New clock: GstPulseSinkClock
0:00:00.1 / 99:99:99.

You should hear a beep.

pw-cli list-objects | grep node.name
                node.name = "Dummy-Driver"
                node.name = "Freewheel-Driver"
                node.name = "Midi-Bridge"
                node.name = "v4l2_input.platform-bcm2835-isp.2"
                node.name = "v4l2_input.platform-bcm2835-isp.3"
                node.name = "v4l2_input.platform-bcm2835-isp.6"
                node.name = "v4l2_input.platform-bcm2835-isp.7"
                node.name = "alsa_output.platform-bcm2835_audio.stereo-fallback"
                node.name = "bluez_input.DE_AD_BE_EF_FA_CE.0"
                node.name = "bluez_output.DE_AD_BE_EF_FA_CE.1"

You can switch between the AD2P and HSP (Head-Set Profile) like this:

pactl list cards short
54      alsa_card.platform-bcm2835_audio        alsa
55      alsa_card.platform-3f902000.hdmi        alsa
73      bluez_card.DE_AD_BE_EF_FA_CE    module-bluez5-device.c

pactl set-card-profile bluez_card.DE_AD_BE_EF_FA_CE a2dp-sink-sbc
pactl set-card-profile bluez_card.DE_AD_BE_EF_FA_CE headset-head-unit-cvsd
pactl list | grep "Active Profile"
        Active Profile: output:stereo-fallback
        Active Profile: off
        Active Profile: headset-head-unit-cvsd <=== Yay!

General Satellite Setup

Wyoming OpenWakeWord Setup

git clone https://github.com/rhasspy/wyoming-openwakeword.git
cd wyoming-openwakeword
script/setup
script/run --uri 'tcp://0.0.0.0:10400' --debug --preload-model 'hey_jarvis'
ctrl + c to quit

Wyoming-OpenWakeWord Service

sudo nano /etc/systemd/user/wyoming-openwakeword.service
[Unit]
Description=Home Assistant Wyoming OpenWakeWord
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStart=%h/code/wyoming-openwakeword/script/run \
  --uri 'tcp://0.0.0.0:10400' \
  --debug \
  --preload-model 'hey_jarvis'
WorkingDirectory=%h/code/wyoming-openwakeword
Restart=always
RestartSec=1

[Install]
WantedBy=default.target
systemctl --user enable wyoming-openwakeword
systemctl --user start wyoming-openwakeword

Wyoming Satellite Setup

sudo apt-get install python3 python3-pip python3-venv alsa-utils git
sudo apt-get install --no-install-recommends ffmpeg

git clone https://github.com/rhasspy/wyoming-satellite.git
cd wyoming-satellite
scripts/setup
script/run --name 'bluetooth satellite' --uri 'tcp://0.0.0.0:10700' --mic-command 'parecord --device bluez_input.DE_AD_BE_EF_FA_CE.0 --rate=16000 --channels=1 --format=s16le --raw' --snd-command 'paplay --device bluez_output.DE_AD_BE_EF_FA_CE.1 --rate=22000 --channels=1 --format=s16le --raw' --debug --wake-uri 'tcp://127.0.0.1:10400' --wake-word-name 'hey_jarvis' --done-wav sounds/done.wav --awake-wav sounds/awake.wav
ctrl + c to quit

Wyoming-Satellite service

sudo nano /etc/systemd/user/wyoming-satellite.service
[Unit]
Description=Home Assistant Wyoming-Satellite
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
WorkingDirectory=%h/code/wyoming-satellite
ExecStartPre=/usr/bin/timeout 30 sh -c 'while ! /usr/bin/ss -H -t -l -n sport = :10400 | /usr/bin/grep -q "^LISTEN.*:10400"; do /usr/bin/sleep 1; done'
ExecStar=%h/code/wyoming-satellite/script/run \
  --name 'bluetooth satellite' \
  --uri 'tcp://0.0.0.0:10700' \
  --mic-command \
    'parecord --device bluez_input.DE_AD_BE_EF_FA_CE.0 --rate=16000 --channels=1 --format=s16le --raw' \
  --snd-command 'paplay --device bluez_output.DE_AD_BE_EF_FA_CE.1 --rate=22000 --channels=1 --format=s16le --raw' \
  --debug \
  --wake-uri 'tcp://127.0.0.1:10400' \
  --wake-word-name 'hey_jarvis' \
  --done-wav '/home/pi/code/wyoming-satellite/sounds/done.wav' \
  --awake-wav '/home/pi/code/wyoming-satellite/sounds/awake.wav'
Restart=always
RestartSec=1

[Install]
WantedBy=default.target
systemctl --user enable --now wyoming-satellite
INFO:root:Ready
DEBUG:root:Detected IP: 10.0.42.100
DEBUG:root:Zeroconf discovery enabled (name=############, host=None)
DEBUG:root:Connecting to mic service: ['parecord', '--device', 'bluez_input.DE_AD_BE_EF_FA_CE.0', '--rate=16000', '--channels=1', '--format=s16le', '--raw']
DEBUG:root:Connecting to snd service: ['paplay', '--device', 'bluez_output.DE_AD_BE_EF_FA_CE.1', '--rate=16000', '--channels=1', '--format=s16le', '--raw']
DEBUG:root:Connecting to wake service: tcp://127.0.0.1:10400
INFO:root:Connected to services
DEBUG:root:Connected to mic service
DEBUG:root:Connected to wake service
DEBUG:root:Server set: 3667213960213
INFO:root:Connected to server
INFO:root:Waiting for wake word
DEBUG:root:Ping enabled
DEBUG:root:Detection(name='hey_jarvis_v0.1', timestamp=3674454417437)
DEBUG:root:Streaming audio
DEBUG:root:Event(type='run-pipeline', data={'start_stage': 'asr', 'end_stage': 'tts', 'restart_on_end': False, 'snd_format': {'rate': 22050, 'width': 2, 'channels': 1}}, payload=None)
DEBUG:root:Muting microphone for 0.8995918367346939 second(s)
DEBUG:root:Connected to snd service
DEBUG:root:Unmuted microphone
DEBUG:root:Connected to snd service
DEBUG:root:Unmuted microphone
DEBUG:root:Event(type='transcript', data={'text': ' Turn off the family room light.'}, payload=None)
INFO:root:Waiting for wake word
DEBUG:root:Connected to snd service
DEBUG:root:Event(type='synthesize', data={'text': 'Turned off the lights', 'voice': {'name': 'en_US-lessac-medium'}}, payload=None)
DEBUG:root:Connected to snd service
DEBUG:root:Detection(name='hey_jarvis_v0.1', timestamp=3784489216895)
DEBUG:root:Streaming audio

Random Fun

ncmpcpp Install

sudo apt install ncmpcpp
sudo nano $HOME/.config/ncmpcpp/config
mpd_host = "127.0.0.1"
mpd_port = 6600
mpd_music_dir = ~/Music

Mopidy Install

Add the archive’s GPG key:

sudo mkdir -p /etc/apt/keyrings
sudo wget -q -O /etc/apt/keyrings/mopidy-archive-keyring.gpg \
  https://apt.mopidy.com/mopidy.gpg

Add the APT repo to your package sources:

sudo wget -q -O /etc/apt/sources.list.d/mopidy.list https://apt.mopidy.com/bookworm.list

Install Mopidy and all dependencies:

sudo apt update
sudo apt install mopidy

To list all the extensions available from apt.mopidy.com, you can run:

apt search mopidy
sudo apt install mopidy-mpd mopidy-somafm mopidy-local mopidy-spotify mopidy-tunein 
sudo nano /etc/mopidy/mopidy.conf
[audio]
output = pulsesink server=127.0.0.1:4713 stream-properties="props,media.role=music"
[http]
enabled = true
hostname = 0.0.0.0
port = 6680
zeroconf = Mopidy HTTP server on $hostname
allowed_origins =
csrf_protection = true
default_app = mopidy

HACS

  1. Install HACS

  2. Go to any of the sections (integrations, frontend, automation).

  3. Click on the 3 dots in the top right corner.

  4. Select “Custom repositories”

  5. Add the URL to the repository: hass-integrations/custom_components/mopidy at main · bushvin/hass-integrations · GitHub

  6. Select the correct category.

  7. Click the “ADD” button.

  8. Go to Home Assistant settings → Integrations and add Mopidy

  9. Restart HA

  10. Go to the Integrations page and click + ADD INTEGRATION

  11. Select Mopidy in the list of integrations

  12. Fill out the requested information. Make sure to enter your correct FQDN or IP address. Using localhost, 127.0.0.1, ::1 or any other loopback address will disable Mopidy-Local artwork.
    Name: Mopidy001
    IP: 10.0.42.1
    Port: 6680

  13. Click Submit.

Edit pipewire-pulse.conf

/usr/share/piperwire/pipewire-pulse.conf
# Extra commands can be executed here.
#   load-module : loads a module with args and flags
#      args = "<module-name> <module-args>"
#      flags = [ "no-fail" ]
pulse.cmd = [
    { cmd = "load-module" args = "module-always-sink" flags = [ ] }
    { cmd = "load-module" args = "module-switch-on-connect" }
    { cmd = "load-module" args = "module-native-protocol-tcp auth-ip-acl=127.0.0.1" }
    { cmd = "load-module" args = "module-role-ducking trigger_roles=annouce,phone ducking_roles=music volume=75%" }
    #{ cmd = "load-module" args = "module-gsettings" flags = [ "nofail" ] }
]

stream.properties = {
    #node.latency          = 1024/48000
    #node.autoconnect      = true
    #resample.quality      = 4
    #channelmix.normalize  = false
    #channelmix.mix-lfe    = true
    #channelmix.upmix      = true
    #channelmix.upmix-method = psd  # none, simple
    #channelmix.lfe-cutoff = 150
    #channelmix.fc-cutoff  = 12000
    #channelmix.rear-delay = 12.0
    #channelmix.stereo-widen = 0.0
    #channelmix.hilbert-taps = 0
    #dither.noise = 0
}

pulse.properties = {
    # the addresses this server listens on
    server.address = [
        "unix:native"
        #"unix:/tmp/something"              # absolute paths may be used
        "tcp:4713"                         # IPv4 and IPv6 on all addresses
        #"tcp:[::]:9999"                    # IPv6 on all addresses
        #"tcp:127.0.0.1:8888"               # IPv4 on a single address
        #
        #{ address = "tcp:4713"             # address
        #  max-clients = 64                 # maximum number of clients
        #  listen-backlog = 32              # backlog in the server listen queue
        #  client.access = "restricted"     # permissions for clients
        #}
    ]
    #pulse.min.req          = 256/48000     # 5ms
    #pulse.default.req      = 960/48000     # 20 milliseconds
    #pulse.min.frag         = 256/48000     # 5ms
    #pulse.default.frag     = 96000/48000   # 2 seconds
    #pulse.default.tlength  = 96000/48000   # 2 seconds
    #pulse.min.quantum      = 256/48000     # 5ms
    #pulse.idle.timeout     = 0             # don't pause after underruns
    #pulse.default.format   = F32
    #pulse.default.position = [ FL FR ]
    # These overrides are only applied when running in a vm.
    vm.overrides = {
        pulse.min.quantum = 1024/48000      # 22ms
    }
}
systemctl --user restart pipewire-pulse

Helvum Install

sudo apt install flatpak
sudo apt install apt install gnome-software-plugin-flatpak
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install flathub org.pipewire.Helvum

Run

flatpak run org.pipewire.Helvum

Run ncmpcpp player to test mopidy server

ncmpcpp

Press 2 to see the plugins like SomaFM etc press Q to quit.

If you can’t hear the bluetooth audio from mopidy play with the patch in Helvum, pavucontrol or use the cli:

Show Inputs

pw-link -iI
  39 Midi-Bridge:Midi Through:(playback_0) Midi Through Port-0
  33 alsa_output.platform-bcm2835_audio.stereo-fallback:playback_FL
  65 alsa_output.platform-bcm2835_audio.stereo-fallback:playback_FR
  87 PulseAudio Volume Control:input_FL
  89 PulseAudio Volume Control:input_FR
  93 bluez_output.DE_AD_BE_EF_FA_CE.1:playback_FL
  92 bluez_output.DE_AD_BE_EF_FA_CE.1:playback_FR
 100 PulseAudio Volume Control:input_FL
  98 PulseAudio Volume Control:input_FR
  81 PulseAudio Volume Control:input_FL
  85 PulseAudio Volume Control:input_FR

Show Outputs

pw-link -oI
  40 Midi-Bridge:Midi Through:(capture_0) Midi Through Port-0
  57 v4l2_input.platform-bcm2835-isp.2:out_0
  59 v4l2_input.platform-bcm2835-isp.3:out_0
  61 v4l2_input.platform-bcm2835-isp.6:out_0
  63 v4l2_input.platform-bcm2835-isp.7:out_0
  67 alsa_output.platform-bcm2835_audio.stereo-fallback:monitor_FL
  66 alsa_output.platform-bcm2835_audio.stereo-fallback:monitor_FR
  88 PulseAudio Volume Control:monitor_FL
  90 PulseAudio Volume Control:monitor_FR
  84 bluez_output.DE_AD_BE_EF_FA_CE.1:monitor_FL
  91 bluez_output.DE_AD_BE_EF_FA_CE.1:monitor_FR
  99 PulseAudio Volume Control:monitor_FL
  72 PulseAudio Volume Control:monitor_FR
  78 [email protected]:4713:output_FL
  86 [email protected]:4713:output_FR
  80 PulseAudio Volume Control:monitor_FL
  79 PulseAudio Volume Control:monitor_FR

Manually Link Outputs

pw-link bluez_output.DE_AD_BE_EF_FA_CE.1:playback_FL [email protected]:4713:output_FL
pw-link bluez_output.DE_AD_BE_EF_FA_CE.1:playback_FR [email protected]:4713:output_FR

Mopidy in HA

2 Likes