Closed timherr closed 4 years ago
Looks like another user reported a similar problem here, but closed the ticket because it wasn't their primary issue: https://github.com/MiSTer-devel/Main_MiSTer/issues/151#issuecomment-565283023
Looks like there are two issues in btpair:
Quick and dirty fixes for each:
Here's a naive update that will attempt to connect to every device. In my case, it works because the connection to the Roku fails (which I don't want to connect, anyway) and the connection to the controller succeeds.
#!/bin/sh
echo Switch input device
echo to pairing mode.
echo searching...
echo
MACS=$(hcitool scan --flush | grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}')
IFS='
'
if [ ! -z "${MACS}" ]; then
for MAC in $MACS
do
echo Got device: $MAC
echo Pairing...
echo
pair-agent hci0 $MAC
echo Add to trust list...
MAC=$(echo $MAC | sed -e 's/:/_/g')
BTADAPTER=`dbus-send --system --dest=org.bluez --print-reply / org.bluez.Manager.DefaultAdapter | tail -1 | sed 's/^.*"\(.*\)".*$/\1/'`
dbus-send --system --dest=org.bluez --print-reply $BTADAPTER/dev_$MAC org.bluez.Device.SetProperty string:Trusted variant:boolean:true >/dev/nul
echo Connecting...
dbus-send --system --dest=org.bluez --print-reply $BTADAPTER/dev_$MAC org.bluez.Input.Connect >/dev/nul
echo Done.
done
else
echo nothing found.
fi
I’ve noticed the same symptoms sometimes; no idea if it’s the same root cause, though. I’ll try out your script when I get a chance.
Note: better to do IFS=$’\n’
for readability.
Had the same symptoms, I think the macs of multiple devices were been merged into one long string. The console seemed to suggest as much. I managed to muddle through with the help of tim's script above, but it is so hit and miss. I 9 times out of 10 get this error
C:\Users\Chloe>ssh root@192.168.1.133 Password: /root# cd /media/fat/scripts /media/fat/scripts# bluetooth_pair.sh Switch input device to pairing mode. searching...
Got device: 77:01:09:02:25:EB Pairing...
Traceback (most recent call last):
File "/sbin/pair-agent", line 117, in
Connecting... Error org.freedesktop.DBus.Error.UnknownObject: Method "Connect" with signature "" on interface "org.bluez.Input" doesn't exist
Done. Got device: E4:17:D8:03:05:79 Pairing...
Traceback (most recent call last):
File "/sbin/pair-agent", line 117, in
Connecting... Error org.freedesktop.DBus.Error.UnknownObject: Method "Connect" with signature "" on interface "org.bluez.Input" doesn't exist
Done. /media/fat/scripts#
Using Tim's start as inspiration, I worked on this for a few hours this evening and got it all working well. I'm working on creating a proper pull request of the fix.
I'm unable to find any git repo containing the raw files present in the Linux filesystem, so I don't see a way to prepare a pull request myself.
So, here are my versions of the two files I modified to get all of this working nicely:
/usr/sbin/btpair:
#!/bin/bash
echo Switch input device
echo to pairing mode.
echo searching...
echo
IFS=$'\n'; MACS=( $(hcitool scan --flush | grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}[[:space:]]*.*') )
if [ ! -z "${MACS[*]}" ]; then
for MAC in "${MACS[@]}"
do
NAME="$(echo "${MAC##*$'\t'}" )"
MAC="$(echo "${MAC%$'\t'*}" )"
echo Found device: $MAC \($NAME\)
echo Pairing...
if pair-agent hci0 $MAC; then
echo Add to trust list...
MAC=$(echo $MAC | sed -e 's/:/_/g')
BTADAPTER=`dbus-send --system --dest=org.bluez --print-reply / org.bluez.Manager.DefaultAdapter | tail -1 | sed 's/^.*"\(.*\)".*$/\1/'`
dbus-send --system --dest=org.bluez --print-reply $BTADAPTER/dev_$MAC org.bluez.Device.SetProperty string:Trusted variant:boolean:true >/dev/nul
echo Connecting...
dbus-send --system --dest=org.bluez --print-reply $BTADAPTER/dev_$MAC org.bluez.Input.Connect >/dev/nul
echo Done.
else
echo Skipped.
fi
echo
done
else
echo nothing found.
fi
/usr/sbin/pair-agent:
#!/usr/bin/python
from __future__ import absolute_import, print_function, unicode_literals
import gobject as GObject
import sys
import dbus
import dbus.service
import dbus.mainloop.glib
from optparse import OptionParser
from dbus import DBusException
def ask(prompt):
try:
return raw_input(prompt)
except:
return input(prompt)
class Rejected(dbus.DBusException):
_dbus_error_name = "org.bluez.Error.Rejected"
class Agent(dbus.service.Object):
_singleton = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
assert Agent._singleton is None
Agent._singleton = self
self.exit_on_release = True
self._mainloop = GObject.MainLoop()
self._result = 0
def run(self):
self._mainloop.run()
return self._result
def quit(self, result=0):
self._mainloop.quit()
self._result = result
def set_exit_on_release(self, exit_on_release):
self.exit_on_release = exit_on_release
@dbus.service.method("org.bluez.Agent",
in_signature="", out_signature="")
def Release(self):
if self.exit_on_release:
self.quit()
@dbus.service.method("org.bluez.Agent",
in_signature="os", out_signature="")
def Authorize(self, device, uuid):
print("Authorize (%s, %s)" % (device, uuid))
authorize = ask("Authorize connection (yes/no): ")
if (authorize == "yes"):
return
raise Rejected("Connection rejected by user")
@dbus.service.method("org.bluez.Agent",
in_signature="o", out_signature="s")
def RequestPinCode(self, device):
print("Enter PIN 0000 on device\n")
sys.stdout.flush()
return "0000"
@dbus.service.method("org.bluez.Agent",
in_signature="o", out_signature="u")
def RequestPasskey(self, device):
print("Enter PIN 1234 on device\n")
sys.stdout.flush()
return dbus.UInt32(1234)
@dbus.service.method("org.bluez.Agent",
in_signature="ou", out_signature="")
def DisplayPasskey(self, device, passkey):
print("DisplayPasskey (%s, %06d)" % (device, passkey))
@dbus.service.method("org.bluez.Agent",
in_signature="os", out_signature="")
def DisplayPinCode(self, device, pincode):
print("DisplayPinCode (%s, %s)" % (device, pincode))
@dbus.service.method("org.bluez.Agent",
in_signature="ou", out_signature="")
def RequestConfirmation(self, device, passkey):
print("RequestConfirmation (%s, %06d)" % (device, passkey))
confirm = ask("Confirm passkey (yes/no): ")
if (confirm == "yes"):
return
raise Rejected("Passkey doesn't match")
@dbus.service.method("org.bluez.Agent",
in_signature="s", out_signature="")
def ConfirmModeChange(self, mode):
print("ConfirmModeChange (%s)" % (mode))
authorize = ask("Authorize mode change (yes/no): ")
if (authorize == "yes"):
return
raise Rejected("Mode change by user")
@dbus.service.method("org.bluez.Agent",
in_signature="", out_signature="")
def Cancel(self):
print("Cancel")
@classmethod
def create_device_reply(cls, device):
print("New device (%s)" % (device))
cls._singleton.quit()
@classmethod
def create_device_error(cls, error):
print("Creating device failed: %s" % (error))
cls._singleton.quit(1)
if __name__ == '__main__':
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object("org.bluez", "/"),
"org.bluez.Manager")
capability = "KeyboardDisplay"
parser = OptionParser()
parser.add_option("-c", "--capability", action="store",
type="string", dest="capability")
(options, args) = parser.parse_args()
if options.capability:
capability = options.capability
if len(args) > 0:
path = manager.FindAdapter(args[0])
else:
path = manager.DefaultAdapter()
adapter = dbus.Interface(bus.get_object("org.bluez", path),
"org.bluez.Adapter")
path = "/test/agent"
agent = Agent(bus, path)
if len(args) > 1:
try:
device = adapter.FindDevice(args[1])
adapter.RemoveDevice(device)
except DBusException:
pass
agent.set_exit_on_release(False)
adapter.CreatePairedDevice(args[1], path, capability,
timeout=60000,
reply_handler=agent.create_device_reply,
error_handler=agent.create_device_error)
else:
adapter.RegisterAgent(path, capability)
print("Agent registered")
result = agent.run()
#adapter.UnregisterAgent(path)
#print("Agent unregistered")
sys.exit(result)
Thanks! i will import your changes into Linux
One more small change (for better readability when running the script inside the narrow OSD):
Change this line:
echo Found device: $MAC \($NAME\)
to this:
echo Found device:
echo $NAME
echo $MAC
I’m still working on additional functional improvements to this... I should have another revision soon.
Yep, current revision I’m working on finally fixes the inconsistent device discovery behavior. Root cause was pair-agent
using the same path
for both the adapter and the agent.
Now I’m just tying to figure out how to get CreatePairedDevice()
to actually honor the capability
option. (Note: KeyboardDisplay
isn’t even a valid capability choice according to the bluez
API docs — presumably DisplayYesNo
was intended. But there’s also no stdin stream connected between the OSD and btpair
, so it ought to be passing DisplayOnly
instead.) I’m close to having this last piece solved.
for keyboard pairing there are usually 2 options: 1) some internals from BT stack offer a key, and it's displayed like "type 1234 on BT keyboard and press enter" 2) use hardcoded in script value (can be 0000) and then use the same as (1)
Right. Just trying to make sure that keyboard, gamepad, and other device types all pair in a reasonable way via the OSD before I call this ready to go.
The multi-gamepad case is interesting, because as-is, the script forcibly removes and repairs every already-paired device, which actually causes my already-paired gamepad to power itself off (in response to being forcibly removed), such that it then fails to re-pair. So I’m revising that logic to check if the device is already paired and if so, don’t do anything.
Looks like part of what I’ve been struggling with is maybe a buggy bluetooth dongle or driver... it sometimes gets stuck in a bad state where it thinks it’s scanning but hcitool scan --flush
doesn’t find anything. Looks like a call to hciconfig hci0 reset
resolves it, so I’m going to add a forcible call to it immediately before.
Alright, I've finally got this whipped into what I'd call "ship ready".
Summary:
bluetooth_pair.sh
menu script (such as the addition of a "Rescan" button).
pair-agent
to correct many defects. I had to dig through the D-Bus, dbus-python, and bluez documentation to figure out exactly how it's all supposed to work. Now the capabilities and different pairing methods all work correctly.bash
scripts and into the pair-agent
Python script alongside the rest of the bluez API usages. This makes error handling and logging easier since it's all in one language and in one file.bluetooth_pair.sh
and btpair
so they now share singular codepaths.bluetooth_pair.sh
and btpair
.SIGINT
(CTRL+C/CTRL+BREAK) during the scripts to prevent the user from interrupting the D-Bus/bluez APIs, because in my testing that has a high probably of putting the Bluetooth adapter into a broken state for which the only solution is to reset (power cycle or unplug/replug) the adapter.The final work consists of changes to one new file and three existing files:
/usr/sbin/btscan (new):
#!/bin/bash
IFS=$'\n'
MAC_NAMES_ERE='([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}.*'
MAC_NAMES=( $( hcitool scan --flush | grep -o -E "$MAC_NAMES_ERE" ) )
# TODO: Investigate more this issue of Bluetooth adapters getting stuck in
# a bad state. As far as I can tell, this may be a result of bugs in D-bus,
# bluez, dbus-python, hcitool, or maybe in more than one of them. It does
# not appear to be a hardware-specific bug because I was able to repro
# the same issue using three different Bluetooth dongles using different
# chipsets.
#
# Ensure already-paired devices can auto-reconnect when powered on.
#hciconfig hci0 pscan
#
# Some BT adapters get stuck in a bad state wherein they
# claim to be page scanning but cannot find (or be discovered by)
# any remote Bluetooth devices. Work around this case by resetting
# the dongle and trying again.
#if [[ -z "${MAC_NAMES[*]}" ]] && hciconfig hci0 | grep -qoF "UP RUNNING PSCAN"; then
# hciconfig hci0 reset
# hciconfig hci0 noscan
# MAC_NAMES=( $( hcitool scan --flush | grep -o -E "$MAC_NAMES_ERE" ) )
#fi
for MAC_NAME in "${MAC_NAMES[@]}"; do
echo $MAC_NAME
done
/usr/sbin/btpair (modified):
#!/bin/bash
trap -- '' SIGINT
echo Switch input devices
echo to pairing mode.
echo Searching...
echo
OLD_IFS="$IFS"
IFS=$'\n'
MAC_NAMES=( $( btscan ) )
IFS="$OLD_IFS"
if [ -z "${MAC_NAMES[*]}" ]; then
echo nothing found.
exit 0
fi
for MAC_NAME in "${MAC_NAMES[@]}"; do
MAC=$(echo "${MAC_NAME}" | grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}')
NAME=$(echo "${MAC_NAME}" | sed 's/[^ ]*[ ]*//')
echo Found: $NAME
echo MAC: $MAC
pair-agent --capability NoInputNoOutput hci0 $MAC
echo
done
/usr/sbin/pair-agent (modified):
#!/usr/bin/python
from __future__ import absolute_import, print_function, unicode_literals
import dbus
from dbus import DBusException
import dbus.service
import dbus.mainloop.glib
import gobject as GObject
from optparse import OptionParser
import signal
import sys
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
_SYSTEM_BUS = dbus.SystemBus()
def get_bluez_object(path_, type_):
assert path_
obj = _SYSTEM_BUS.get_object("org.bluez", path_)
return dbus.Interface(obj, "org.bluez.%s" % (type_))
class Agent(dbus.service.Object):
_singleton = None
class Rejected(dbus.DBusException):
def get_dbus_name():
return "org.bluez.Agent.Rejected"
def __init__(self, adapter, adapter_path, *args, **kwargs):
assert Agent._singleton is None
Agent._singleton = self
self._path = adapter_path + "/agent"
super().__init__(conn=_SYSTEM_BUS, object_path=self._path)
self._adapter = adapter
self._adapter_path = adapter_path
self._mainloop = GObject.MainLoop()
self._error = None
@staticmethod
def _ask(prompt):
try:
return raw_input(prompt)
except:
return input(prompt)
@dbus.service.method("org.bluez.Agent",
in_signature="os", out_signature="")
def Authorize(self, device, uuid):
authorize = self._ask("Authorize connection (yes/no): ")
if (authorize != "yes"):
raise Rejected("Connection rejected by user.")
@dbus.service.method("org.bluez.Agent",
in_signature="o", out_signature="s")
def RequestPinCode(self, device):
pin = self._ask("Enter device PIN\nor <ENTER> to skip: ")
return pin
@dbus.service.method("org.bluez.Agent",
in_signature="o", out_signature="u")
def RequestPasskey(self, device):
passkey = self._ask("Enter device passkey\nor <ENTER> to skip: ")
return dbus.UInt32(passkey)
@dbus.service.method("org.bluez.Agent",
in_signature="ou", out_signature="")
def DisplayPasskey(self, device, passkey):
print("Enter this passkey\non your device: %06d" % (passkey))
sys.stdout.flush()
@dbus.service.method("org.bluez.Agent",
in_signature="os", out_signature="")
def DisplayPinCode(self, device, pincode):
print("Enter this PIN\non your device: %s" % (pincode))
sys.stdout.flush()
@dbus.service.method("org.bluez.Agent",
in_signature="ou", out_signature="")
def RequestConfirmation(self, device, passkey):
confirm = self._ask("Is %06d the\ndevice passkey?\nType 'yes' to confirm\nor <ENTER> to skip: " % (passkey))
if (confirm != "yes"):
raise Rejected("Passkey rejected by user.")
@dbus.service.method("org.bluez.Agent",
in_signature="s", out_signature="")
def ConfirmModeChange(self, mode):
print("Device is requesting change\nto mode: %s" % (mode))
authorize = self._ask("Type 'yes' to confirm\nor <ENTER> to skip: ")
if (authorize != "yes"):
raise Rejected("Mode change rejected by user.")
@classmethod
def create_paired_device_reply(cls, device):
cls._singleton._mainloop.quit()
@classmethod
def create_paired_device_error(cls, error):
if cls.Rejected.get_dbus_name() == error.get_dbus_name():
print(error.get_dbus_message())
else:
print("ERROR: %s" % (error.get_dbus_message()))
cls._singleton._error = error
cls._singleton._mainloop.quit()
def create_paired_device(self, mac, capability):
self._adapter.CreatePairedDevice(
mac, self._path, capability, timeout=10000,
reply_handler=Agent.create_paired_device_reply,
error_handler=Agent.create_paired_device_error)
self._mainloop.run()
if self._error is not None:
sys.exit(1)
return self._adapter.FindDevice(mac)
def create_paired_device(adapter, adapter_path, mac, capability):
agent = Agent(adapter, adapter_path)
return agent.create_paired_device(mac, capability)
def interrupt_handler(signum, frame):
# Prevent user from interrupting this script using
# CTRL+C or CTRL+BREAK! Be like Weird Al Yankovich
# (i.e. just eat it).
pass
if __name__ == '__main__':
signal.signal(signal.SIGINT, interrupt_handler)
manager = get_bluez_object("/", "Manager")
parser = OptionParser()
parser.add_option("-c", "--capability", action="store",
type="string", dest="capability",
default="DisplayYesNo")
(options, args) = parser.parse_args()
assert len(args) == 2
adapter_name = args[0]
device_mac = args[1]
print("Pairing...")
sys.stdout.flush()
adapter_path = manager.FindAdapter(adapter_name)
adapter = get_bluez_object(adapter_path, "Adapter")
try:
device_path = adapter.FindDevice(device_mac)
adapter.RemoveDevice(device_path)
except DBusException:
pass
device_path = create_paired_device(
adapter, adapter_path,
device_mac, options.capability)
print("Trusting...")
sys.stdout.flush()
device = get_bluez_object(device_path, "Device")
device.SetProperty("Trusted", True)
try:
print("Connecting...")
sys.stdout.flush()
input_device = get_bluez_object(device_path, "Input")
input_device.Connect()
print("Done.")
sys.stdout.flush()
except DBusException as e:
if e.get_dbus_name() == "org.bluez.Error.ConnectionAttemptFailed":
print("failed.")
else:
print(str(e))
sys.exit(1)
/media/fat/Scripts/bluetooth_pairing.sh (modified):
#!/bin/bash
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Original script by Alexey "Sorgelig" Melnikov https://github.com/MiSTer-devel/Main_MiSTer/wiki
# Copyright 2019 Alessandro "Locutus73" Miele
# You can download the latest version of this script from:
# https://github.com/MiSTer-devel/Scripts_MiSTer
# Version 1.0 - 2019-10-26 - First commit.
# ========= OPTIONS ==================
# ========= ADVANCED OPTIONS =========
DIALOG_HEIGHT="31"
# ========= CODE STARTS HERE =========
function checkTERMINAL {
# if [ "$(uname -n)" != "MiSTer" ]
# then
# echo "This script must be run"
# echo "on a MiSTer system."
# exit 1
# fi
if [[ ! (-t 0 && -t 1 && -t 2) ]]
then
echo "This script must be run"
echo "from an interactive terminal."
echo "Please press F9 (F12 to exit)"
echo "or use SSH."
exit 2
fi
}
function setupDIALOG {
DIALOG="dialog"
export NCURSES_NO_UTF8_ACS=1
: ${DIALOG_OK=0}
: ${DIALOG_CANCEL=1}
: ${DIALOG_HELP=2}
: ${DIALOG_EXTRA=3}
: ${DIALOG_ITEM_HELP=4}
: ${DIALOG_ESC=255}
: ${SIG_NONE=0}
: ${SIG_HUP=1}
: ${SIG_INT=2}
: ${SIG_QUIT=3}
: ${SIG_KILL=9}
: ${SIG_TERM=15}
}
function setupDIALOGtempfile {
DIALOG_TEMPFILE=`(DIALOG_TEMPFILE) 2>/dev/null` || DIALOG_TEMPFILE=/tmp/dialog_tempfile$$
trap "rm -f $DIALOG_TEMPFILE" 0 $SIG_NONE $SIG_HUP $SIG_INT $SIG_QUIT $SIG_TERM
}
function readDIALOGtempfile {
DIALOG_RETVAL=$?
DIALOG_OUTPUT="$(cat ${DIALOG_TEMPFILE})"
#rm -f ${DIALOG_TEMPFILE}
#unset DIALOG_TEMPFILE
}
checkTERMINAL
RESCAN=true
while $RESCAN; do
RESCAN=false
clear
echo Switch input devices
echo to pairing mode.
echo Searching...
echo
OLD_IFS="$IFS"
IFS=$'\n'
MAC_NAMES=( $( btscan ) )
IFS="$OLD_IFS"
unset MENU_ITEMS
if [ ! -z "${MAC_NAMES[*]}" ]; then
for MAC_NAME in "${MAC_NAMES[@]}"; do
MAC=$(echo "${MAC_NAME}" | grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}')
NAME=$(echo "${MAC_NAME}" | sed 's/[^ ]*[ ]*//')
MENU_ITEMS="${MENU_ITEMS} \"${MAC}\" \"${NAME}\""
done
setupDIALOG
setupDIALOGtempfile
eval ${DIALOG} --clear --colors --ok-label \"Pair\" \
--title \"Bluetooth pair\" \
--extra-button --extra-label \"Rescan\"\
--menu \"Please select the Controller/Keyboard/Mouse you want to pair.\" ${DIALOG_HEIGHT} 0 999 \
${MENU_ITEMS} \
2> ${DIALOG_TEMPFILE}
readDIALOGtempfile
case ${DIALOG_RETVAL} in
${DIALOG_OK})
clear
MAC="${DIALOG_OUTPUT}"
;;
${DIALOG_EXTRA})
RESCAN=true
;;
${DIALOG_CANCEL})
clear
exit 0
;;
*)
exit 1
;;
esac
fi
done
if [ ! -z "${MAC}" ]; then
pair-agent hci0 $MAC
else
echo nothing found.
fi
Ideas for future improvement:
hcitool scan --flush
returns ALL found Bluetooth devices, so the main btpair
loop wastes a lot of time in crowded environments trying to pair devices like televisions, computers, and speakers. Instead, we should use hcitool scan --class --flush
to get the class information (along with the name and MAC) for each device, and then use a hard-coded whitelist of major device classes to filter the list down to only suitable input-type devices (keyboards, gamepads, mice... maybe others?) That way the overall pairing process will be easier and faster for users trying to pair multiple Bluetooth input devices.bluez
and hcitool
, and also look into adding the bluetoothctl
command from bluez
to make it easier to get finer-grained control over bluez APIs from bash
scripts.btscan
, and the main looping logic out of btpair
, and pull it all into Python (which has a much easier way of accessing the bluez
API surface, and which is much nicer for doing string manipulations and error handling anyway).Thanks for rework! Bluez 5x has very complicated pairing. It uses ugly console tool in interactive mode to pair. So that's definitely not a good for MiSTer. Bluez 4 (current) is more friendly for scripts. I will include your files into linux except bluetooth_pairing.sh which you need to PR to scripts repository.
Great, thanks. I’ll create the PR asap.
bluez 5 doesn’t support D-Bus anymore?
I don't remember details already, but d-bus commands are very different in Bluez 5
They can't be too different, because Bluetooth itself hasn't fundamentally changed. The API surface may be different, but it has to accomplish all the same goals and concepts, so I'd think it should be pretty trivial to port this code forward.
I do know that I had to manually update my RetroPie distro to a newer version of bluez (which I had to build from source, as no precompiled binaries were made available for it) in order to resolve some Bluetooth stability problems I'd been having with it. So I'm very suspicious of bluez being the cause of the adapter getting stuck in a bad state.
believe me, the difference between 4 and 5 are fundamental. That's why buildroot even has separate option to use either 4.x or 5.x. If they would be compatible, then they wouldn't have such option. When you update some distro it already has everything updated to accommodate the differences.
At the end, it's Linux which means anarchy of APIs. Touch some package and you will find yourself at the end of day updating the whole linux :)
Yep, agreed -- Linux dependency management has always made Windows' "DLL Hell" look like a walk in the park :-)
I finally figured out the cause of another related Bluetooth reliability problem! Sometimes the Bluetooth adapter would not be initialized on startup, and it appears to be caused by the RememberPowered = true
setting in /etc/bluetooth/main.conf
. That behavior appears to set up a race condition with the hciconfig hci0 up
code in /etc/init.d/S35bluetooth
.
I changed that line to RememberPowered = false
, and now the Bluetooth adapter always starts right up (way faster than before) on every boot.
This issue was exposed by the /media/fat/Scripts/shutdown.sh
script I added (after discovering that just killing the power at the OSD actually corrupts the exFAT) for clean software shutdown from the OSD. Apparently the shutdown sequence in /etc/inittab
results in powering off the Bluetooth adapter, such that on next boot, the bluetooth service tries to restore the remembered “off” state while the startup sequence is simultaneously trying to turn the adapter on.
So I’d recommend changing that line in /etc/bluetooth/main.conf
.
I’ll file a new issue on the exFAT corruption shutdown problem and create a PR for the little shutdown script I wrote.
Thanks for investigating. I will modify /etc/bluetooth/main.conf
May be InitiallyPowered should be also set to false, so in case if some dongles aren't powered on restart, they will be explicitly powered by system.
InitiallyPowered = false make BT dongle not powered at boot.
I think it’s bad to have S35bluetooth and main.conf both trying to control the adapter’s power state at the same time. Do main.conf and InitiallyPowered always get applied on OS startup, not just cold boot from power off? If so, maybe we should just remove the “hciconfig up” calls from S35bluetooth and rely on InitiallyPowered=true to do the work for each boot. I can try it and report back with my findings.
But that can set up another race condition between InitiallyPowered=true and the rest of the work in S35bluetooth, so I think the “hciconfig up” calls are necessary for the rest of S35bluetooth to work reliably. So probably best to leave InitiallyPowered=true, so at least it agrees with what S35bluetooth is trying to do anyway. Even though it’s not great to have them both trying to do the same thing at the same time, at least that way it doesn’t matter who wins the race.
Is there anything more you want me to try while all of this is still fresh in my brain, or do you consider my work here complete?
@c0d3h4x0r Thank you so much, I tried your most recent changes and it fixed the issues I was having. I have two tvs with bluetooth as well as two nvidia shield tvs. I only ever managed to get things working with sheer persistence of numerous attempts and turning off the two tvs and the shields.
I tried your scripts and it connected my keyboard, a m30 pad and a sn30 pad together with no issues, it even tried to connected to my tv but I selected deny on my tv screen and it continued with an unauthorised error warning but no ill effects.
I am using a Broadcom BCM20702 usb bluetooth adapter that I had to add firmware for to the lib/firmware folder.
Since i have no problem with pairing originally i can't say if anything else need to be done. :)
Happy to help!
I plan to release Linux update soon, so will include your changes too.
You can go ahead and close this one now. :)
I am having trouble connecting a bluetooth controller when there are multiple devices nearby:
Unplugging my Roku allows the controller to pair perfectly. I suspect the btpair script isn't handling the newline in the $MAC variable appropriately, but I'll leave that to the experts to confirm. :)