18

I have followed the following tutorial (http://mygeeks014.blogspot.nl/2015/01/audio-streaming-to-bluetooth-speaker.html) to connect a Bluetooth speaker to my Raspberry Pi. Everything works as it's supposed to, but the speaker will not automatically reconnect when the Raspberry gets restarted or the speaker gets turned on/off. Right now I manually reconnect the speaker via the Raspbian GUI, but I wonder if there's a simple solution to reconnect the speaker via the CLI. I will then be able to write a simple CRON to reconnect the speaker if it's not connected yet.

Den3243
  • 183
  • 1
  • 1
  • 4

4 Answers4

23

Here is a very detail explanation:

Den3243

Here is a command line solution:

First, let's scan, pair, trust your device with "bluetoothctl". To do that, run this at the command line, your terminal:

bluetoothctl -a

You should get a different command prompt like:

[bluetooth]

With your BT speaker on, type this:

scan on

In a few moments, you should see the BT devices available. Next to the device will be it's MAC address, like: 00:AA:22:BB:33. Now type this:

info <your mac address>

Exclude the greater than and less than characters. What your looking for is some kind of previous association with your BT speaker. You will know that there was a previous association because bluetoothctl will show information about your BT device. Some of this information will be about the device being paired and trusted. This is good.

If bluetoothctl complains about there is no device, then we need to set that up at this moment. To do that, type this:

pair <your mac address>

You should see a success message about your device pairing successfully. Now let's trust our new BT device. Type this:

trust <your mac address>

Again, you should see a success message about trusting. Let me pre-warn you. Your BT device might connect then again it might not. Never fear, we don't want it to connect. Go ahead and let's exit "bluetoothctl". To do that, type:

quit

Now you will be brought back to the command line prompt. In a previous post I suggested for you to create a scripts directory in your home directory. If you haven't, go ahead and do that now. Type this at the command prompt:

mkdir -p ~/scripts

Press enter and now let's create our autopair bash script. Type this:

nano ~/scripts/autopair

Enter this code into the script:

#!/bin/bash
bluetoothctl << EOF
connect [enter your MAC add]
EOF

Exclude the brackets!

Now press CTRL + x at the same time, and now press enter to save the script. We need to make it executable. To do that, type this:

chmod +x ~/scripts/autopair

I'm assuming that you don't use external analog speakers plug into the 3.5 mm jack. If this is true, let's disable alsa. To do that, let's edit a file in the /boot directory called config.txt. To do that, type this in your terminal:

sudo nano /boot/config.txt

Page down to the bottom of the file and look for two lines that read:

# Enable audio (loads snd_bcm2835)
dtparam=audio=on

Place a (pound sign #) in front of the line that reads:

dtparam=audio=on

To look like:

#dtparam=audio=on

Press CTRL + x and then press Enter to save your file.

I'm assuming you have pulseaudio installed? If not, go ahead and run this command from the command line:

sudo apt-get update && sudo apt-get install pulseaudio -y

This will get you a very important component to making bluetooth work! Now let's edit our .bashrc file in our home directory. Type this:

nano ~/.bashrc

Page down to the bottom and add this line:

pulseaudio --start

Press CTRL + x and now press Enter to save your file.

OK! We need to enter over into the Python world. I have wrote a Python program that will watch for the bluetooth device. In short, it will activate the connection between RPi and your bluetooth speaker, once your bluetooth speaker is turned on. And vice versa. Let's create a directory called python in your home directory To do that, type this:

mkdir -p ~/python

Now let's create the python program file. To do that, type this:

nano ~/python/on.py

Inside that file, we need to copy and paste the following:

#!/usr/bin/python
#
# Monitor removal of bluetooth reciever
import os
import sys
import subprocess
import time

def blue_it():
    status = subprocess.call('ls /dev/input/event0 2>/dev/null', shell=True)
    while status == 0:
        print("Bluetooth UP")
        print(status)
        time.sleep(15)
        status = subprocess.call('ls /dev/input/event0 2>/dev/null', shell=True)
    else:
        waiting()

def waiting():
    subprocess.call('killall -9 pulseaudio', shell=True)
    time.sleep(3)
    subprocess.call('pulseaudio --start', shell=True)
    time.sleep(2)
    status = subprocess.call('ls /dev/input/event0 2>/dev/null', shell=True)  
    while status == 2:
        print("Bluetooth DOWN")
        print(status)
        subprocess.call('~/scripts/autopair', shell=True)
        time.sleep(15)
        status = subprocess.call('ls /dev/input/event0 2>/dev/null', shell=True)
    else:
        blue_it() 

blue_it()

Now press CTRL + x and then press Enter to save the Python program file. Now we need to make this file executable. To do that, type this:

chmod +x ~/python/on.py

Finally, let's add this to our .bashrc script in our home directory:

nano ~/.bashrc

Page down to the bottom of the file and add these two lines:

wait
~/python/on.py

Now press CTRL + x and then press Enter to save. Turn your bluetooth speaker on and reboot your Raspberry Pi.

Good Luck!

-nitrolinux

Jason Woodruff
  • 431
  • 4
  • 8
4

I have found that there are current issues with pulseaudio5 especially when it comes to audio playback over bluetooth. As such I propose that instead of having to debug those when they come along simply use PulseAudio6 for what you want.

I have created a repo that will automate everything below so you don't need to do all the leg work, but if you're still set on doing it yourself continue on below.

Repo: https://github.com/BaReinhard/a2dp_bluetooth

Install Process:

git clone https://github.com/bareinhard/a2dp_bluetooth
cd a2dp_bluetooth/a2dp_source
./configure

Wait until the install process is done and reboot. Upon finishing you will need to initally, pair, trust, and connect your device. After the initial time you will only need to turn the device on.

Pairing, Trusting, and Connecting:

sudo bluetoothctl
[bluetooth]# power on
[bluetooth]# agent on
[bluetooth]# default-agent
[bluetooth]# scan on
[bluetooth]# pair XX:XX:XX:XX:XX
[bluetooth]# trust XX:XX:XX:XX:XX
[bluetooth]# connect XX:XX:XX:XX:XX
[bluetooth]# exit

-------------------- Complete walkthrough: --------------------

Compiling PulseAudio 6

Add the following Files

/etc/init.d/pulseaudio

#!/bin/sh -e
### BEGIN INIT INFO
# Provides:          pulseaudio esound
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Should-Start:      udev network-manager
# Should-Stop:       udev network-manager
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Start the PulseAudio sound server
# Description:       System mode startup script for
#                    the PulseAudio sound server.
### END INIT INFO

DAEMON=/usr/local/bin/pulseaudio
PIDDIR=/var/run/pulse
PIDFILE=$PIDDIR/pid
DAEMONUSER=pulse
PATH=/sbin:/bin:/usr/sbin:/usr/bin

test -x $DAEMON || exit 0

. /lib/lsb/init-functions

pulseaudio_start () {
        log_daemon_msg "Starting system PulseAudio Daemon"
        if [ ! -d $PIDDIR ]; then
                mkdir -p $PIDDIR
                chown $DAEMONUSER:$DAEMONUSER $PIDDIR
        fi
        start-stop-daemon -x $DAEMON -p $PIDFILE --start -- --system --disallow-exit --disallow-module-loading=0 --daemonize --log-target=syslog --high-priority
        status=$?
        if [ -e /var/run/pulse/.esd_auth ]; then
                chown pulse:pulse-access /var/run/pulse/.esd_auth
                chmod 640 /var/run/pulse/.esd_auth
        fi
        if [ -e /var/run/pulse/.pulse-cookie ]; then
                chown pulse:pulse-access /var/run/pulse/.pulse-cookie
                chmod 640 /var/run/pulse/.pulse-cookie
        fi
        log_end_msg ${status}
}

pulseaudio_stop () {
        log_daemon_msg "Stopping system PulseAudio Daemon"
        start-stop-daemon -p $PIDFILE --stop --retry 5 || echo -n "...which is not running"
        log_end_msg $?
}

case "$1" in
        start|stop)
                pulseaudio_${1}
                ;;
        restart|reload|force-reload)
                if [ -s $PIDFILE ] && kill -0 $(cat $PIDFILE) >/dev/null 2>&1; then
                        pulseaudio_stop
                        pulseaudio_start
                fi
                ;;
        force-stop)
                pulseaudio_stop
                killall pulseaudio || true
                sleep 2
                killall -9 pulseaudio || true
                ;;
        status)
                status_of_proc -p $PIDFILE "$DAEMON" "system-wide PulseAudio" && exit 0 || exit $?
                ;;
        *)
                echo "Usage: /etc/init.d/pulseaudio {start|stop|force-stop|restart|reload|force-reload|status}"
                exit 1
                ;;
esac

exit 0

/etc/init.d/bluetooth

#!/bin/sh -e
### BEGIN INIT INFO
# Provides:            bluetooth
# Required-Start:      $local_fs $syslog dbus
# Required-Stop:       $local_fs $syslog
# Default-Start:       2 3 4 5
# Default-Stop:        0 1 6
# Short-Description:   Starts bluetooth daemons
### END INIT INFO

. /lib/lsb/init-functions

DESC=bluetoothd
DAEMON=/usr/libexec/bluetooth/bluetoothd
#SSD_OPTIONS="--oknodo --quiet --exec $DAEMON --plugin=a2dp"
SSD_OPTIONS="--oknodo --quiet --exec $DAEMON" #Change to this if you want media control using DBus at the expense of volume control 
HCI=hci0

case "${1}" in
    start)
       log_daemon_msg "Starting Bluetooth daemon bluetoothd..."
       start-stop-daemon --start --background $SSD_OPTIONS
       log_progress_msg "${DAEMON}"

       hciconfig $HCI up > /dev/null 2>&1
       log_end_msg 0
       ;;

    stop)
        log_daemon_msg "Stopping Bluetooth daemon bluetoothd..."
        start-stop-daemon --stop $SSD_OPTIONS
        log_progress_msg "${DAEMON}"
        log_end_msg 0
       ;;

    restart)
       ${0} stop
       sleep 1
       ${0} start
       ;;

    status)
        status_of_proc "$DAEMON" "$DESC" && exit 0 || exit $?
       ;;

    *)
         echo "Usage: ${0} {start|stop|restart|status}"
         exit 1
       ;;
esac

exit 0

Enable new init.d services and make executable

sudo chmod +x /etc/init.d/bluetooth
sudo chmod +x /etc/init.d/pulseaudio
sudo update-rc.d bluetooth defaults
sudo update-rc.d pulseaudio defaults

Ensure we have all the necessary modules

sudo apt-get install bluez pulseaudio-module-bluetooth python-dbus libtool intltool libsndfile-dev libcap-dev libjson0-dev libasound2-dev libavahi-client-dev libbluetooth-dev libglib2.0-dev libsamplerate0-dev libsbc-dev libspeexdsp-dev libssl-dev libtdb-dev libbluetooth-dev intltool autoconf autogen automake build-essential libasound2-dev libflac-dev libogg-dev libtool libvorbis-dev pkg-config python -y

Change to Home Directory and Install json-c from git source (required for PA6)

cd ~
git clone https://github.com/json-c/json-c.git
cd json-c
./configure 
make
sudo make install

Change to Home Directory and Install libsndfile from git source

git clone git://github.com/erikd/libsndfile.git
cd libsndfile
./autogen.sh
./configure --enable-werror
make
sudo make install

Make sure that Bluetooth is searching (sudo hciconfig hci0 piscan is deprecated)

cat << EOT | sudo tee -a /etc/bluetooth/main.conf
[Policy]
AutoEnable=true
EOT

Navigate to Home Directory and Install PulseAudio 6 from git source

git clone --branch v6.0 https://github.com/pulseaudio/pulseaudio
cd pulseaudio
sudo ./bootstrap.sh
sudo make
sudo make install
sudo ldconfig

Ensure pulse is in all the necessary groups

sudo addgroup --system pulse
sudo adduser --system --ingroup pulse --home /var/run/pulse pulse
sudo addgroup --system pulse-access
sudo adduser pulse audio
sudo adduser root pulse-access
sudo adduser pulse lp

Update /etc/pulse/system.pa and /etc/pulse/daemon.conf to look as the following :

/etc/pulse/system.pa

#!/usr/bin/pulseaudio -nF
#
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

# This startup script is used only if PulseAudio is started in system
# mode.

### Automatically load driver modules depending on the hardware available
.ifexists module-udev-detect.so
 #load-module module-udev-detect
 load-module module-udev-detect tsched=0
.else
### Use the static hardware detection module (for systems that lack udev/hal support)
load-module module-detect
.endif

### Load several protocols
.ifexists module-esound-protocol-unix.so
load-module module-esound-protocol-unix
.endif
load-module module-native-protocol-unix

### Automatically restore the volume of streams and devices
load-module module-stream-restore
load-module module-device-restore

### Automatically restore the default sink/source when changed by the user
### during runtime
### NOTE: This should be loaded as early as possible so that subsequent modules
### that look up the default sink/source get the right value
load-module module-default-device-restore

### Automatically move streams to the default sink if the sink they are
### connected to dies, similar for sources
load-module module-rescue-streams

### Make sure we always have a sink around, even if it is a null sink.
load-module module-always-sink

### Automatically suspend sinks/sources that become idle for too long
load-module module-suspend-on-idle

### Enable positioned event sounds
load-module module-position-event-sounds

### Automatically load driver modules for Bluetooth hardware
.ifexists module-bluetooth-discover.so
    load-module module-bluetooth-discover
.endif
load-module module-bluetooth-policy
load-module module-switch-on-connect

/etc/pulse/daemon.conf

# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA.

## Configuration file for the PulseAudio daemon. See pulse-daemon.conf(5) for
## more information. Default values are commented out.  Use either ; or # for
## commenting.

; daemonize = no
; fail = yes
; allow-module-loading = yes
; allow-exit = yes
; use-pid-file = yes
; system-instance = no
; local-server-type = user
; enable-shm = yes
; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB
; lock-memory = no
; cpu-limit = no

; high-priority = yes
; nice-level = -15

; realtime-scheduling = yes
; realtime-priority = 5

exit-idle-time = -1
; scache-idle-time = 20

; dl-search-path = (depends on architecture)

; load-default-script-file = yes
; default-script-file = /etc/pulse/default.pa

; log-target = auto
; log-level = notice
; log-meta = no
; log-time = no
; log-backtrace = 0

# resample-method defaults to  speex-float-1 on most architectures,
# speex-fixed-1 on ARM
; resample-method = speex-float-1
resample-method = ffmpeg
enable-remixing = no
enable-lfe-remixing = no

; flat-volumes = yes

; rlimit-fsize = -1
; rlimit-data = -1
; rlimit-stack = -1
; rlimit-core = -1
; rlimit-as = -1
; rlimit-rss = -1
; rlimit-nproc = -1
; rlimit-nofile = 256
; rlimit-memlock = -1
; rlimit-locks = -1
; rlimit-sigpending = -1
; rlimit-msgqueue = -1
; rlimit-nice = 31
; rlimit-rtprio = 9
; rlimit-rttime = 1000000

default-sample-format = s16le
default-sample-rate = 44100
;alternate-sample-rate = 48000
default-sample-channels = 2
; default-channel-map = front-left,front-right

default-fragments = 10
default-fragment-size-msec = 10

; enable-deferred-volume = yes
; deferred-volume-safety-margin-usec = 8000
; deferred-volume-extra-delay-usec = 0

Setup udev Rule

Edit /etc/udev/rules.d/99-com.rules and add the following two lines:

SUBSYSTEM=="input", GROUP="input", MODE="0660"
KERNEL=="input[0-9]*", RUN+="/usr/local/bin/bluez-udev"

Create /usr/local/bin/bluez-udev

/usr/local/bin/bluez-udev

#!/bin/bash
name=$(sed 's/\"//g' <<< $NAME)
#exit if not a BT address
if [[ ! $name =~ ^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$ ]]; then exit 0;  fi

bt_name=`grep Name /var/lib/bluetooth/*/$name/info | awk -F'=' '{print $2}'`

audio_sink=bluez_source.$(sed 's/:/_/g' <<< $name)

action=$(expr "$ACTION" : "\([a-zA-Z]\+\).*")
logger "Action: $action"
if [ "$action" = "add" ]; then
    logger "[$(basename $0)] Bluetooth device is being added [$name] - $bt_name"
    logger "[$(basename $0)] Patching $audio_source into ALSA sink #$audio_sink"
    #hciconfig hci0 noscan
    bluetoothctl << EOT
discoverable off
EOT
    # Grab Card Number
    PACARD=`pactl list cards | grep "Card #" | sed "s/Card #//"`

    # Grab Sink Input if it exists
    audio_source=`pactl pactl list sink-inputs | grep "Sink Input" | sed "s/Sink Input #//"`
    if [ $audio_source = "" ];then
        sleep 5
        audio_source=`pactl pactl list sink-inputs | grep "Sink Input" | sed "s/Sink Input #//"`

    fi
    pactl set-sink-volume $audio_sink 65537
    if [ $audio_source != "" ]; then
        pactl set-source-volume $audio_source 90%
    fi
    pactl set-card-profile $PACARD a2dp_sink


    pactl set-default-sink $audio_sink





    # loop back this source to the default sink
    handle=$(pactl load-module module-loopback source=$audio_source sink=$audio_sink)
    logger "[$(basename $0)] PulseAudio module-loopback returned handle [$handle]"
    logger "$bt_name"


fi

if [ "$action" = "remove" ]; then
    # Grab Sink Input if it exists
    audio_source=`pactl pactl list sink-inputs | grep "Sink Input" | sed "s/Sink Input #//"`
    if [ $audio_source = "" ];then
        sleep 5
        audio_source=`pactl pactl list sink-inputs | grep "Sink Input" | sed "s/Sink Input #//"`

    fi
    pactl set-sink-volume 0 65537
    if [ $audio_source = "" ]; then
#        pactl set-default-sink 0
        pactl set-source-volume $audio_source 90%
    else
        pactl move-sink-input $audio_source 0 
    fi

    logger "[$(basename $0)] Bluetooth device is being removed [$name] - $bt_name"
    #hciconfig hci0 pscan

    bluetoothctl << EOT
discoverable on
EOT

    # remove any loopback modules assigned to this source
    # only required for USB sound cards, which PulseAudio will not automatically remove
    for handle in $(pactl list short modules | grep module-loopback | grep source=$audio_source | cut -f 1); do
        logger "[$(basename $0)] Unloading module-loopback with handle [$handle]"
        pactl unload-module $handle
    done

    sleep 5
    amixer cset numid=3 80%
    amixer cset numid=3 80%
fi

Ensure that bluez-udev is executable

sudo chmod +x /usr/local/bin/bluez-udev

Summary

What's being done here?

  • Creating init.d services for bluetooth and pulseaudio and enabling them
  • Installing Dependencies for PulseAudio6
  • Compiling PulseAudio6 and adding the pulse user to necessary groups (most will have already been done)
  • Setup daemon.conf and system.pa to load proper modules
  • Create udev rule, to run bluez-udev each time a device is connected. bluez-udev checks to see if the device is a bluetooth device, if it is it will try to connect current playing audio to the bluetooth device sink created by pulseaudio. Upon bluetooth disconnect it will move the stream back to the default sink, or sink 0. There you have it, after all that you should now have a automatically connected bluetooth device, the bluez-udev rule will automatically connect the playing music to the newly connected bluetooth device. Of course, if this seems Daunting
Brett Reinhard
  • 454
  • 1
  • 4
  • 14
  • Did you verify this answer on a Raspbian Desktop system, or a Raspbian Lite system? – Seamus Sep 12 '20 at 01:15
  • 1
    @Seamus this was only verified on lite, and it was quite a while ago raspbian Jessie iirc, so this may no longer work due to changes in the BT stack. – Brett Reinhard Sep 12 '20 at 01:18
  • Yes - that's what I've heard. I'm struggling with this, and trying to find a way ahead. I feel there ought to be a simpler option than installing the 650+MB pulseaudio-module-bluetooth package. The issues are unclear to me now, but thought I'd reach out for some feedback. Thnx. – Seamus Sep 12 '20 at 01:27
  • @Seamus I had done some work to solve this but unfortunately it’s been at least a year or more since I did so, unfortunately I don’t remember the details. I wish I had better news, maybe there is a discord to converse about this with others. – Brett Reinhard Sep 13 '20 at 03:41
1

Have you tried making a Bash script that uses hcitool to connect?

#!/bin/bash
sudo hcitool cc [speaker Bluetooth address]


Add executable rights to that file then add it to cron (you can choose any time).

This worked for me when I tried to connect to a Bluetooth Keyboard. I'm not sure if it will work for a speaker (not sure if it's a different protocol). Hope this helps!

ALinuxLover
  • 133
  • 4
0

found this even better

sudo bluetoothctl <<EOF
power on
discoverable on
pairable on
agent NoInputNoOutput
default-agent 
EOF