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