I needed a possibility to programmatically control the Bluetooth device on a raspberry pi.
(At least, list the discovered Bluetooth devices around.)
The only command line method/program I've found, that reliably works on Linux was "bluetoothctl".
But it is intended to be used interactively.
In order to overcome this challenge, I've used co-processes.
This script resets the Bluetooth local device and lists the discovered Bluetooth devices around.
(I've noticed that devices are easier discovered if one of the Bluetooth devices around is re-enabled.)
This script is intended to be only a starting point for the development of your own scripts.
#!/bin/bash
#===============================================================================
# Uses "bluetoothctl" in order to query bluetooth devices around.
# Starts "bluetoothctl" as coprocess, communicating with it simulating interative input.
# See https://www.gnu.org/software/bash/manual/html_node/Coprocesses.html#Coprocesses
# An alternative implementations would be:
# - the usage of named pipes:
# See https://en.wikipedia.org/wiki/Named_pipe
# - or perhaps using expect:
# See https://www.thegeekstuff.com/2010/10/expect-examples/
#===============================================================================
# ---- Constants ----
typeset -r C_MAX_TRIES=999
typeset -r C_WAIT_SECS=2
# ---- Global variables ----
typeset g_output=""
typeset g_result=""
typeset g_separator=""
typeset g_device=""
let g_count=0
#----------------------------------
# Functions
#----------------------------------
sendToCOPROC () {
local send_cmd="${1}"
echo -e "I: Sending to COPROC\n-----\n${send_cmd}\n-----\n"
# See https://www.gnu.org/software/bash/manual/html_node/Coprocesses.html#Coprocesses
echo -e "${send_cmd}\n" >&"${COPROC[1]}"
}
showHedxump () {
echo "D:Hexdump BEGIN ---------------------"
echo -e "${1}" | hd
echo "D:Hexdump END -----------------------"
}
#------------------------------------------------------------------------------
# The 1st parameter must be the name of a variable that will receive the output
# of this function.
# If 2nd parameter (regular expression pattern) is given, "receiveFromCOPROC()"
# will return only the lines that match the given pattern.
#
# ATTENTION
# This function must run in this same shell because of:
# read -t 2 output <&"${COPROC[0]}
# e.g. Do NOT call this function in this way:
# outputVar="$(receiveFromCOPROC)"
# This function must be called in this way:
# receiveFromCOPROC outputVar []
#-----------------------------------------------------------------------------------------------
receiveFromCOPROC () {
if [[ -z "$1" ]]; then
>&2 echo -e "\nE: receiveFromCOPROC() Missing first parameter"
>&2 echo -e " This parameter is a variable that will 'receive' the output of this function\n"
exit 1
fi
local regexpPattern=""
if [[ -n "$2" ]]; then
regexpPattern="$2"
fi
echo "D: receiveFromCOPROC() Receiving COPROC output into variable '$1'"
# "recFro_COPROC" is a reference to the variable given as 1st argument.
# I am using such a complicated variable name, in order to avoid naming conflicts.
# The output of "bluetoothctl" will be writen to "recFro_COPROC" and so also
# to the referenced variable given as 1st parameter to this function.
declare -n recFro_COPROC=$1
local output=""
local lineQty=0
while [[ 1 -eq 1 ]]; do
# See https://www.gnu.org/software/bash/manual/html_node/Coprocesses.html#Coprocesses
read -t 2 output <&"${COPROC[0]}"
# Remove color codes, carriage return characters and "[bluetooth]# " from bluetoothctl output
output="$(sed 's/\x1B\[[0-9;]*[JKmsu]//g; s/\r//g; s/\[bluetooth\]# *//g' <<< "${output}")"
if [[ -n "${regexpPattern}" ]]; then
output="$(egrep -e "${regexpPattern}" <<< "${output}")"
fi
#echo -e "receiveFromCOPROC() output: ${output}"
if [[ -z "${output}" ]]; then
break
fi
if [[ ${lineQty} -eq 0 ]]; then
recFro_COPROC="${output}"
else
recFro_COPROC="${recFro_COPROC}\n${output}"
fi
#echo "${output}"
((lineQty++))
done
#if [[ ${lineQty} -gt 0 ]]; then
# echo -e "D: receiveFromCOPROC() Received:\n####\n${recFro_COPROC}\n####\n"
#fi
}
doSleep () {
echo "I: Sleep ${1} secs ..."
sleep ${1}
}
#---------------------------------------
# Echoes an empty string if Ok
# Otherwise the error message
#---------------------------------------
isBluetoothServiceOk () {
systemctl status bluetooth | grep "Active: failed"
}
repairIfNeeded () {
typeset res="$(isBluetoothServiceOk)"
if [[ -n "${res}" ]]; then
echo "I: Bluetooth service has a problem. Restarting bluetooth service"
systemctl restart bluetooth
res="$(isBluetoothServiceOk)"
if [[ -n "${res}" ]]; then
echo "I: Bluetooth service still has a problem. Abort"
exit 1
else
echo "I: Bluetooth service is OK now"
fi
else
echo "I: Bluetooth service is OK"
fi
}
isBluetoothCtlRunning () {
pgrep -c "bluetoothctl"
}
killBluetooth () {
echo "I: Killing bluetoothctl"
pkill "bluetoothctl"
}
flushCOPROC () {
local fl_output
>&2 echo "D: flushCOPROC ()"
receiveFromCOPROC fl_output
echo -e "D: flushing\n++++"
while [[ -n "$(egrep -e " Controller |Agent |power on|scan on|Discovery" <<< ${fl_output})" ]]; do
receiveFromCOPROC fl_output
echo "${fl_output}"
done
echo "++++"
}
listDevices () {
local ld_output
echo "D: listDevices ()"
sendToCOPROC "devices"
doSleep 1
receiveFromCOPROC ld_output
echo -e "D: Listing devices\n*****"
while [[ -n "$(egrep -e " Device " <<< ${ld_output})" ]]; do
receiveFromCOPROC ld_output
echo "${ld_output}"
done
echo "*****"
}
sendCmdWaitAndShowOutput () {
local cmd="${1}"
local sleepTime="${2}"
local sCWASO_output
sendToCOPROC "${cmd}"
doSleep "${sleepTime}"
receiveFromCOPROC sCWASO_output
}
#============= Main ==============
if [[ "$(whoami)" != "root" ]]; then
echo "E: Wrong user. This script must be started as root"
exit 1
fi
if [[ $(isBluetoothCtlRunning) -gt 0 ]]; then
echo "I: bluetoothctl is already started"
killBluetooth
#echo "I: systemctl restart bluetooth"
#systemctl restart bluetooth
#else
# repairIfNeeded
fi
echo "I: Restarting bluetooth to be sure that is works correctly"
systemctl restart bluetooth
doSleep 2
# See https://www.gnu.org/software/bash/manual/html_node/Coprocesses.html#Coprocesses
echo "I: Starting 'bluetoothctl' as coprocess"
coproc COPROC { bluetoothctl; }
doSleep 1
# Cleanup on exit (whenever the exit occurs)
trap '
echo "I: Exit trap called"
sendToCOPROC "exit"
doSleep 3
if [[ $(isBluetoothCtlRunning) -gt 0 ]]; then
echo "E: NOK - bluetoothctl is still runnning"
killBluetooth
else
echo "I: OK - bluetoothctl is stopped"
fi
' EXIT
if [[ $(isBluetoothCtlRunning) -gt 0 ]]; then
echo "I: OK - bluetoothctl is started"
else
echo "E: NOK - bluetoothctl not started"
exit 2
fi
sendCmdWaitAndShowOutput "power on" 1
sendCmdWaitAndShowOutput "discoverable on" 1
sendCmdWaitAndShowOutput "scan on" 1
#sendCmdWaitAndShowOutput "show" 1
#doSleep 10
#flushCOPROC
while [[ ${g_count} -lt ${C_MAX_TRIES} ]]; do
((g_count++))
sendToCOPROC "devices"
doSleep 1
g_result=""
g_separator=""
while [[ 1 -eq 1 ]]; do
g_output=""
receiveFromCOPROC g_output "Device "
if [[ -n "${g_output}" ]]; then
g_result="${g_result}${g_separator}${g_output}"
g_separator="\n"
else
break
fi
done
if [[ -n "${g_result}" ]]; then
echo -e "I: Found bluetooth device(s)\n-------\n${g_result}\n-------\n"
g_result="$(echo -e "${g_result}" | sort -u)"
echo -e "I: Found bluetooth device(s) after sort removing duplicates\n-------\n${g_result}\n-------\n"
break
else
echo "E: No bluetooth devide found"
fi
done
#listDevices
sendToCOPROC 'scan off'
Output example:
# ./btscanWithCoproc.bash I: Restarting bluetooth to be sure that is works correctly I: Sleep 2 secs ... I: Starting 'bluetoothctl' as co-process I: Sleep 1 secs ... I: OK - bluetoothctl is started I: Sending to COPROC ----- power on ----- I: Sleep 1 secs ... D: receiveFromCOPROC() Receiving COPROC output into variable 'sCWASO_output' I: Sending to COPROC ----- discoverable on ----- I: Sleep 1 secs ... D: receiveFromCOPROC() Receiving COPROC output into variable 'sCWASO_output' I: Sending to COPROC ----- scan on ----- I: Sleep 1 secs ... D: receiveFromCOPROC() Receiving COPROC output into variable 'sCWASO_output' I: Sending to COPROC ----- devices ----- I: Sleep 1 secs ... D: receiveFromCOPROC() Receiving COPROC output into variable 'g_output' E: No bluetooth devide found I: Sending to COPROC ----- devices ----- I: Sleep 1 secs ... D: receiveFromCOPROC() Receiving COPROC output into variable 'g_output' D: receiveFromCOPROC() Receiving COPROC output into variable 'g_output' D: receiveFromCOPROC() Receiving COPROC output into variable 'g_output' I: Found bluetooth device(s) ------- Device 80:**:**:**:**:B9 Galaxy S9 Device 30:**:**:**:**:96 30-**-**-**-**-96 Device 80:**:**:**:**:B9 Galaxy S9 Device 30:**:**:**:**:96 30-**-**-**-**-96 ------- I: Found bluetooth device(s) after sort removing duplicates ------- Device 30:**:**:**:**:96 30-**-**-**-**-96 Device 80:**:**:**:**:B9 Galaxy S9 ------- I: Sending to COPROC ----- scan off ----- I: Exit trap called I: Sending to COPROC ----- exit ----- I: Sleep 3 secs ... I: OK - bluetoothctl is stoppedI hope this helps someone.
Andreas