freedomdev
freedomdev

Reputation: 53

Ubuntu 20.04.2 Bluetoothctl SCAN from bash script

Ubuntu 20.04.2 LTS bluetoothctl Version 5.53

The problem I have is a bluetooth headset being unable to directly pair without errors when switching between Ubuntu/Windows on a dual boot machine (I believe due to the process which bluetooth issues/stores public keys)

I am trying to run a simple script to remove a specific bluetooth address (device), flush its information and re-pair.

I have tried various iterations of:

#!/bin/bash

#remove old connection 
echo -e 'power on\ndisconnect 88:88:E1:21:52:BE\nremove 88:88:E1:21:52:BE\nquit' | bluetoothctl

sudo systemctl restart bluetooth 

sleep 1
echo -e 'power on' | bluetoothctl
echo -e 'default-agent' | bluetoothctl
echo -e 'discoverable on\ndiscoverable-timeout 100\nscan on' | bluetoothctl
sleep 10
echo -e 'pairable on' | bluetoothctl

# Re-Add our device
echo -e 'trust 88:88:E1:21:52:BE\npair 88:88:E1:21:52:BE' | bluetoothctl
sleep 4
echo -e 'connect 88:88:E1:21:52:BE' | bluetoothctl
echo -e 'discoverable off' | bluetoothctl
echo -e 'quit' | bluetoothctl

Output as follows:

Agent registered
[bluetooth]# power on
[bluetooth]# disconnect 88:88:E1:21:52:BE
Attempting to disconnect from 88:88:E1:21:52:BE
[bluetooth]# remove 88:88:E1:21:52:BE
[bluetooth]# quit
Agent registered
[bluetooth]# power on
Agent registered
[bluetooth]# default-agent
Agent registered
[bluetooth]# discoverable on
[bluetooth]# discoverable-timeout 100
[bluetooth]# scan on
Agent registered
[bluetooth]# pairable on
Agent registered
[bluetooth]# trust 88:88:E1:21:52:BE
Device 88:88:E1:21:52:BE not available
[bluetooth]# pair 88:88:E1:21:52:BE
Device 88:88:E1:21:52:BE not available
Agent registered
[bluetooth]# connect 88:88:E1:21:52:BE
Device 88:88:E1:21:52:BE not available
Agent registered
[bluetooth]# discoverable off
Agent registered
[bluetooth]# quit

However while following on the bluetoothctl command output in a seperate window

[CHG] Controller C8:58:C0:C4:41:F8 Name: [ControllerID]
[CHG] Controller C8:58:C0:C4:41:F8 Alias: BlueZ 5.53
[CHG] Controller C8:58:C0:C4:41:F8 Alias: [ControllerID]
**[CHG] Controller C8:58:C0:C4:41:F8 Discovering: no**
[CHG] Controller C8:58:C0:C4:41:F8 Discoverable: yes

The Scan on command has not had the desired effect. The bluetooth controller is not discovering and discovers no devices ergo no connection to said devices can be made.

When moving directly into the bluetoothctl command line, the scan on command has the desired effect and the controller begins scanning. Why is the scan ON command from the ubuntu terminal/bash script not having the desired effect?

Upvotes: 3

Views: 2553

Answers (3)

Yanarof Foranay
Yanarof Foranay

Reputation: 126

When you run bluetoothctl scan on, it seems that it exits directly the scan and thus does not discover anything. One alternative to using python or expect would be to use coproc which is already included in ubuntu.

Using using coproc has the advantage of not having to install yet another package.

#!/bin/bash #you'll need to run it in bash script (does not work in zsh)
coproc mycoproc { bluetoothctl; }
sleep 2
echo "scan on" >&${mycoproc[1]}

while true; do
    available_devices=$(bluetoothctl devices | grep -E "YOUR DEVICE MAC OR NAME HERE")
    sleep 1
    if [[ ! -z "$available_devices" ]]; then #if found device
        break
    fi
done

DEVICE_MAC="$(echo $available_devices | awk '{print $2}')"
bluetoothctl pair $DEVICE_MAC
sleep 4 # Wait for the pairing to establish
bluetoothctl connect $DEVICE_MAC
sleep 3 # Wait for the connection to establish

echo "scan off" >&${mycoproc[1]}
echo "exit" >&${mycoproc[1]} #this exits the coproc session

Here, the only part, where I'm unsure is about how much needs to be waited between each bluetoothctl command

Then, specific to OP's problem, you'll be able to set the output of your computer to this device:

DEVICE_MAC_UNDERSCORE="${DEVICE_MAC//:/_}" #replace the : to _
CARD_INDEX=$(pactl list cards short | grep "$DEVICE_MAC_UNDERSCORE" | awk '{print $1}')
pactl set-card-profile $CARD_INDEX a2dp-sink #you'll find the profiles in "pactl list sinks"
SINK=$(pactl list sinks short | grep "$DEVICE_MAC_UNDERSCORE" | awk '{print $2}')
pactl set-default-sink "$SINK" #set the device as audio output

Upvotes: 0

gluk47
gluk47

Reputation: 1830

Even though there seems to be no clear CLI, tcl/expect can help

#!/usr/bin/expect -f

set MAC "XX:XX:XX:XX:XX:XX"
set timeout 30

spawn bluetoothctl

send "remove $MAC\n"

expect "Device $MAC " {
    send "scan on\n"
}

expect " Device $MAC " {
    send "pair $MAC\n"
}

expect "Paired: yes" {
    send "connect $MAC\n"
}

expect "Connection successful" {
    send "quit\n"
}

Save this as ./bt.sh and execute just as any script. You may need to apt install expect or whatever it is spelled in your distribution.

Upvotes: 0

ukBaz
ukBaz

Reputation: 7994

I don't think it was ever intended for bluetoothctl to ever be used in that way.

BlueZ provide a DBus APIs that are documented at: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc

As an example of what can be done with the API, here is an example in Python doing something similar to your script.

from gi.repository import GLib
from pydbus import SystemBus
from pprint import pprint

SCAN_TIME = 15
DEVICE_INTERFACE = 'org.bluez.Device1'
adapter_path = '/org/bluez/hci0'
device_address = '88:88:E1:21:52:BE'
dev_path = f'{adapter_path}/dev_{device_address.replace(":", "_").upper()}'


remove_list = set()

def stop_scan():
    adapter.StopDiscovery()
    mainloop.quit()


def clean_device(rm_dev):
    try:
        adapter.RemoveDevice(rm_dev)
    except GLib.Error as err:
        pass

def on_iface_added(path, interfaces):
    if DEVICE_INTERFACE in interfaces:
        on_device_found(path, interfaces[DEVICE_INTERFACE])

def on_device_found(device_path, device_props):
    address = device_props.get('Address')
    if address == device_address:
        stop_scan()

def pair_device():
    dev = bus.get('org.bluez', dev_path)
    dev.Trusted = True
    dev.Pair()


bus = SystemBus()
adapter = bus.get('org.bluez', adapter_path)
mngr = bus.get('org.bluez', '/')
mngr.onInterfacesAdded = on_iface_added

clean_device(dev_path)

mainloop = GLib.MainLoop()

GLib.timeout_add_seconds(SCAN_TIME, stop_scan)
adapter.SetDiscoveryFilter({'DuplicateData': GLib.Variant.new_boolean(True)})
adapter.StartDiscovery()

mainloop.run()

pair_device()

Upvotes: 1

Related Questions