Ultimate Speakers

Wiki.TerraBase.info
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