Ultimate Speakers
Jump to navigation
Jump to search
Item 1: The Million Meter Bluetooth Speaker
Item 2: A Bluetooth Speaker with WiFi connectivity, Wired Ethernet, TTL Serial Communications Port, Aux Audio Output (3.5 / 1/8"), with Battery BackUp, and the ability to 'broadcast' via Bluetooth to 4 other Bluetooth Speakers
How? See the 'ingredients' below;
Bluetooth Connection and Aggregation Script
#!/bin/sh
### REMEMBER: Speakers must be Paired and Trusted Manually with the bluetoothctl command first!
## echo -e "select $Controller\rconnect $Device\rexit" | bluetoothctl
## The above series of commands for bluetoothctl (all in a row), work just fine in reality, but cause the below errors to be logged
## daemon.err bluetoothd[1907]: src/service.c:btd_service_connect() a2dp-source profile connect failed for F4:4E:FC:67:DB:9D: Resource busy
## daemon.err bluetoothd[1907]: profiles/audio/avctp.c:avctp_control_confirm() Control: Refusing unexpected connect
## ...so just live with it (btmgmt doesn't have a connect command)
### BlueTooth #1: Zip Speaker(s) Primary & (Secondary) - USB Broadcom Corp BCM20702A0 BlueTooth 4.0
Name1="ZipSpeaker-s"
Device1="F4:4E:FC:67:DB:9D"
SinkName1="bluez_sink.$(echo "$Device1" | tr ':' '_').a2dp_sink"
Controller1="5C:F3:70:9C:2B:20"
### BlueTooth #2: Downstairs Speakers - USB BlueTooth 5.3
##Name2="DownStairsSpeakers"
##Device2=""
##SinkName2="bluez_sink.$(echo "$Device2" | tr ':' '_').a2dp_sink"
##Controller2="8A:88:0B:20:68:D5"
### BlueTooth #3: Upstairs Speakers - USB BlueTooth 5.3
##Name3="UpstairsSpeakers"
##Device3=""
##SinkName3="bluez_sink.$(echo "$Device3" | tr ':' '_').a2dp_sink"
##Controller3="8A:88:4B:80:CF:CB"
### BlueTooth #4: Zip Speaker (Secondary) - USB BlueTooth 5.0
Name4="ZipSpeaker_Secondary"
Device4="F4:4E:FC:32:05:C0"
SinkName4="bluez_sink.$(echo "$Device4" | tr ':' '_').a2dp_sink"
Controller4="8C:88:4B:67:30:18"
### TEST: BlueTooth #5: MusicBaby1 Speaker - USB BlueTooth 5.3
Name5="MusicBaby1"
Device5="B3:55:35:2F:CC:56"
SinkName5="bluez_sink.$(echo "$Device5" | tr ':' '_').a2dp_sink"
Controller5="8A:88:0B:20:68:D5"
### TEST: BlueTooth #6: MusicBaby2 Speaker - USB BlueTooth 5.3
Name6="MusicBaby2"
Device6="2F:C8:D0:DE:65:06"
SinkName6="bluez_sink.$(echo "$Device6" | tr ':' '_').a2dp_sink"
Controller6="8A:88:4B:80:CF:CB"
DeviceCounter=1
MaxCount=10
TotalCount=0
FoundDevicesCounter=1
DeviceConnected=""
DeviceConnecting=""
DevicesConnected=""
DevicesConnecting=""
DevicesFound=0
SinkName=""
CombinedSinks=""
logger "Starting Bluetooth Connect Script (/etc/SCRIPTS/BlueToothConnect.sh)"
### FUNCTION: TotalCount Check (Allows for X (MaxCount) number of non-defined speakers, then terminates the script)
Speaker_Scan_Check () {
if [ -z "$NameString" ]; then
TotalCount=$(expr $TotalCount + 1)
logger "Scanning for Speakers (found $(expr $DeviceCounter - $TotalCount) so far) on Scan Number: $TotalCount"
if [ "$TotalCount" -ge "$MaxCount" ]; then
logger "Iterated through $MaxCount additional String Variables after last Device, no more found..."
break
fi
DeviceCounter=$(expr $DeviceCounter + 1)
continue
fi
}
### FUNCTION: Check Bluetooth Device Connection Status
Is_Device_Connected() {
local Name=$1
local Device=$2
local Controller=$3
logger "Checking to see if $Name ($Device) is connected on: $Controller BT Controller"
echo -e "select $Controller\ninfo $Device\nexit" | bluetoothctl | grep -q "Connected: yes"
}
### FUNCTION: Connect Device to a Bluetooth Controller
Connect_Device() {
local Name=$1
local Device=$2
local Controller=$3
logger "Connecting to device $Device using controller $Controller"
echo -e "select $Controller\rconnect $Device\rexit" | bluetoothctl
if [ $? -eq 0 ]; then
logger "Successfully connected $Name ($Device) on Controller: ($Controller)"
else
logger "Failed to connect to $Device on $Name ($Controller)"
fi
}
### MAIN SCRIPT: Connect Bluetooth Device(s)
while true; do
Name="Name$DeviceCounter"
eval NameString=\$$Name
Device="Device$DeviceCounter"
eval DeviceString=\$$Device
SinkName="SinkName$DeviceCounter"
eval SinkNameString=\$$SinkName
Controller="Controller$DeviceCounter"
eval ControllerString=\$$Controller
if [ -n "$NameString" ]; then
eval FoundDeviceName$FoundDevicesCounter=\"$NameString\"
eval FoundDeviceMAC$FoundDevicesCounter=\"$DeviceString\"
eval FoundDeviceSink$FoundDevicesCounter=\"$SinkNameString\"
FoundDevicesCounter=$(expr $FoundDevicesCounter + 1)
fi
Speaker_Scan_Check "$TotalCount" "$DeviceCounter"
logger "Running Is_Device_Connected FUNCTION: Checks status for $NameString ($DeviceString) on $ControllerString BT Controller"
if Is_Device_Connected "$NameString" "$DeviceString" "$ControllerString"; then
logger "$NameString is Connected!"
eval "DevicesConnected$DeviceCounter=\"$DeviceString\""
eval "logger \"MAC of Connected Device: \$DevicesConnected$DeviceCounter\""
else
sleep 1
logger "$NameString is NOT Connected. Attempting to connect..."
Connect_Device "$NameString" "$DeviceString" "$ControllerString"
sleep 1
eval "DevicesConnecting$DeviceCounter=\"$DeviceString\""
eval "logger \"MAC of Connecting Device: \$DevicesConnecting$DeviceCounter\""
sleep 1
fi
DeviceCounter=$(expr $DeviceCounter + 1)
done
DevicesFound=$(expr $DeviceCounter - $TotalCount)
logger "Number of Devices found: $DevicesFound"
if pactl list sinks short | grep -q "BlueToothDevices"; then
logger "BlueToothDevices Combined Sink exists. No need to create it."
else
for i in $(seq 1 $((FoundDevicesCounter-1))); do
logger "Devices (Speakers) Found: $(eval echo \$FoundDeviceName$i) ($(eval echo \$FoundDeviceMAC$i) - $(eval echo \$FoundDeviceSink$i))"
CombinedSinks="$CombinedSinks$(eval echo \$FoundDeviceSink$i),"
done
CombinedSinks="${CombinedSinks%?}"
sleep 6
logger "PACTL Command: pactl load-module module-combine-sink sink_name=BlueToothDevices slaves=$CombinedSinks"
pactl load-module module-combine-sink sink_name=BlueToothDevices slaves=$CombinedSinks
fi
Init.d Script (AKA Service / Daemon) to Monitor and Make Sure Bluetooth Speakers Stay Connected
#!/bin/sh /etc/rc.common
### Install: opkg install coreutils-stat procps-ng-pgrep
### To Monitor "Live" Logging: logread -f
START=99
STOP=10
Script="/etc/SCRIPTS/BlueToothConnect.sh"
PIDFile="/var/run/bt-reconnect.pid"
CheckDuration="33"
start() {
(while true; do
if pgrep -x "pulseaudio" > /dev/null; then
logger "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
logger "Starting Bluetooth Connection Service (BT-Connect)"
logger "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
logger "PulseAudio pre-requisit is running, now running $Script"
$Script | logger
logger "Bluetooth Connection(s) to Speakers Checked (repeating every $CheckDuration seconds)"
else
logger "PulseAudio is NOT running"
fi
logger "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
sleep $CheckDuration
done) &
echo $! > $PIDFile
}
stop() {
logger "Stopping BT-Connect service"
if [ -f $PIDFile ]; then
kill "$(cat $PIDFile)"
rm -f $PIDFile
fi
}
Another Script (used when testing) to Disconnect Bluetooth Devices (Speakers)
#!/bin/bash
### ZipSpeaker - Primary
echo -e "select 5C:F3:70:9C:2B:20\ndisconnect F4:4E:FC:67:DB:9D\nexit" | bluetoothctl
### ZipSpeaker - Secondary
echo -e "select 8C:88:4B:67:30:18\ndisconnect F4:4E:FC:32:05:C0\nexit" | bluetoothctl
### MusicBaby 1
echo -e "select 8A:88:0B:20:68:D5\ndisconnect B3:55:35:2F:CC:56\nexit" | bluetoothctl
### MusicBaby 2
echo -e "select 8A:88:4B:80:CF:CB\ndisconnect 2F:C8:D0:DE:65:06\nexit" | bluetoothctl
output=$(pactl list modules short)
while read -r line; do
if [[ "$line" == *"module-bluez5-device"* ]]; then
module_number=$(echo "$line" | awk '{print $1}')
pactl unload-module "$module_number"
fi
done <<< "$output"
while read -r line; do
if [[ "$line" == *"module-combine-sink"* ]]; then
module_number=$(echo "$line" | awk '{print $1}')
pactl unload-module "$module_number"
fi
done <<< "$output"
echo
echo
echo
pactl list modules short
echo
echo
pactl list sinks short
#pactl unload-module 23
#pactl unload-module 24
#pactl unload-module 25
#pactl unload-module 26
## Argument: path=/org/bluez/hci1/dev_F4_4E_FC_32_05_C0 autodetect_mtu=0
# bluez_sink.F4_4E_FC_67_DB_9D.a2dp_sink
# bluez_sink.F4_4E_FC_32_05_C0.a2dp_sink