A little bit of background, I want to use my Pi to emulate a bluetooth keyboard for my phone. I have actually managed to get it to work with my phone but I'm having a hard time with automatic connection.
I want the pi to scan nearby devices and initiate connection with already paired devices but for some reason it doesn't work.
If my pi is connected to a screen (my screen has a built in speaker) than for some reason automatic connection only connects to audio profiles and I have to manually connect to the HID profile, if my pi is not connected to a screen it just can't connect at all and I'm getting
org.bluez.Error.Failed: Protocol not available
when I'm trying to view the bluetooth status using sudo systemctl status bluetooth
I get a hint about what protocol is missing.. this is the output:
● bluetooth.service - Bluetooth service
Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2021-08-11 11:43:46 IDT; 52min ago
Docs: man:bluetoothd(8)
Main PID: 627 (bluetoothd)
Status: "Running"
Tasks: 1 (limit: 4915)
CGroup: /system.slice/bluetooth.service
└─627 /usr/lib/bluetooth/bluetoothd -P input
Aug 11 11:57:37 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
Aug 11 12:00:53 raspberrypi bluetoothd[627]: Endpoint registered: sender=:1.96 path=/MediaEndpoint/A2DPSource
Aug 11 12:00:53 raspberrypi bluetoothd[627]: Endpoint registered: sender=:1.96 path=/MediaEndpoint/A2DPSink
Aug 11 12:01:10 raspberrypi bluetoothd[627]: Endpoint unregistered: sender=:1.96 path=/MediaEndpoint/A2DPSource
Aug 11 12:01:10 raspberrypi bluetoothd[627]: Endpoint unregistered: sender=:1.96 path=/MediaEndpoint/A2DPSink
Aug 11 12:04:43 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
Aug 11 12:05:02 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
Aug 11 12:28:12 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
Aug 11 12:28:27 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
Aug 11 12:28:40 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
I feel like I've tried everything at this point, the lack of online documentation/information is really frustrating.
I have even purged pulseaudio completely from my device in hopes that it would stop trying to connect to audio profiles but it didn't work.
Here is some of the code that I think is relevant:
The device class
class BTKbDevice:
"""This class is used to define the bluetooth controller properties and capabilities"""
def __init__(self):
# Set up device
system_helper.init_device()
# log periodical scan results
ScanLogHelper().run()
# Declare class fields
self.server_control_port = None
self.server_interrupt_port = None
self.client_control_port = None
self.client_interrupt_port = None
# define some constants
self.__CONTROL_PORT = 17 # Service port - must match port configured in SDP record
self.__INTERRUPTION_PORT = 19 # Interrupt port - must match port configured in SDP record
self.__CURRENTLY_CONNECTED_DEVICE = "" # Used for logging connection/disconnection events
print("device started")
# listen for incoming client connections
def listen(self):
# We are not using BluetoothSocket constructor to have access to setsockopt method later
# instead we use the native socket equivalent
self.server_control_port = socket.socket(
socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) # BluetoothSocket(L2CAP)
self.server_interrupt_port = socket.socket(
socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) # BluetoothSocket(L2CAP)
# This allows the system to reuse the same port for different connections
# this is useful for situations where for some reason the port wasn't closed properly
# i.e. crashes, keyboard interrupts etc.
self.server_control_port.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_interrupt_port.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# bind these sockets to a port
# use BDADDR_ANY because we are only really interested in defining a constant port
self.server_control_port.bind((socket.BDADDR_ANY, self.__CONTROL_PORT))
self.server_interrupt_port.bind((socket.BDADDR_ANY, self.__INTERRUPTION_PORT))
# Start listening on the server sockets
self.server_control_port.listen(1)
self.server_interrupt_port.listen(1)
# Wait for connections
# the accept() method will block code execution until a connection was established
self.client_control_port, client_information = self.server_control_port.accept()
self.client_interrupt_port, client_information = self.server_interrupt_port.accept()
# We need to remember the connected device for disconnection logging
# client_information[0] is device's mac address
self.__CURRENTLY_CONNECTED_DEVICE = client_information[0]
def device_disconnected(self):
self.__CURRENTLY_CONNECTED_DEVICE = ""
def is_currently_connected_exists(self):
return self.__CURRENTLY_CONNECTED_DEVICE != ""
def get_currently_connected_device(self):
return self.__CURRENTLY_CONNECTED_DEVICE
# Cleanup
def close_connections(self):
self.server_control_port.close()
self.server_interrupt_port.close()
self.client_control_port.close()
self.client_interrupt_port.close()
The service class
class BTKbService(dbus.service.Object):
def __init__(self):
# set up as a dbus service
bus_name = dbus.service.BusName(
"org.thanhle.btkbservice", bus=dbus.SystemBus())
dbus.service.Object.__init__(
self, bus_name, "/org/thanhle/btkbservice")
print("service started. starting device")
# create and setup our device
self.device = BTKbDevice()
# start listening for connections
self.device.listen()
system_helper.py
"""A utility for handling system related operations and events"""
UUID = "00001124-0000-1000-8000-00805f9b34fb"
# Get available bluetooth devices list from system
def get_controllers_info():
return subprocess.getoutput("hcitool dev")
# Check if our device is available
def is_controller_available():
device_data = get_controllers_info()
return const.MY_ADDRESS in device_data.split()
# Handle device initialization
def init_device():
__init_hardware()
__init_bluez_profile()
# Configure the bluetooth hardware device
def __init_hardware():
# Reset everything to make sure there are no problems
os.system("hciconfig hci0 down")
os.system("systemctl daemon-reload")
os.system("/etc/init.d/bluetooth start")
# Activate device and set device name
os.system("hciconfig hci0 up")
os.system("hciconfig hci0 name " + const.MY_DEV_NAME)
# make the device discoverable
os.system("hciconfig hci0 piscan")
# set up a bluez profile to advertise device capabilities from a loaded service record
def __init_bluez_profile():
# read and return an sdp record from a file
service_record = __read_sdp_service_record()
# setup profile options
opts = {
"AutoConnect": True,
"RequireAuthorization": False,
"ServiceRecord": service_record
}
# retrieve a proxy for the bluez profile interface
bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object(
"org.bluez", "/org/bluez"), "org.bluez.ProfileManager1")
manager.RegisterProfile("/org/bluez/hci0", UUID, opts)
# Set device class
os.system("hciconfig hci0 class 0x0025C0")
def __read_sdp_service_record():
try:
fh = open(const.SDP_RECORD_PATH, "r")
except OSError:
sys.exit("Could not open the sdp record. Exiting...")
return fh.read()
def get_connected_devices_data():
return subprocess.getoutput("hcitool con")
def get_paired_devices_data():
return subprocess.getoutput("bluetoothctl paired-devices")
connection_helper.py The method initiate_connection_with_devices_in_range get's called every 10 seconds or so by another file that isn't relevant to this problem.
""" Responsible for finding paired devices in range and attempting connection with them """
def initiate_connection_with_devices_in_range(nearby_devices):
print("init connection started")
# Get paired devices from system
paired_devices = system_helper.get_paired_devices_data()
print("Paired device:\n" + paired_devices)
# Check nearby devices for a match
# no need to request data from bus if no paired device is available
for device in nearby_devices:
mac, name = device
print("checking for device " + name + " " + mac)
if mac in paired_devices.split():
print(name + " is paired, let's attempt connection")
# Paired device found, try to connect
__attempt_connection()
def __attempt_connection():
print("attempting connection")
# Get reference for the bus object, and for the objects it manages
bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object("org.bluez", "/"),
"org.freedesktop.DBus.ObjectManager")
objects = manager.GetManagedObjects()
# Extract device objects from bus
all_devices = (str(path) for path, interfaces in objects.items() if
"org.bluez.Device1" in interfaces.keys())
# Extract only devices managed by our adapter
device_list = None
for path, interfaces in objects.items():
if "org.bluez.Adapter1" not in interfaces.keys():
continue
device_list = [d for d in all_devices if d.startswith(path + "/")]
if device_list is not None:
print(device_list)
# Devices found, attempt connection
for dev_path in device_list:
print("trying to connect keyboard profile with " + dev_path)
dev_obj = bus.get_object('org.bluez', dev_path)
methods = dbus.Interface(dev_obj, 'org.bluez.Device1')
props = dbus.Interface(dev_obj, dbus.PROPERTIES_IFACE)
try:
methods.Connect()
except Exception as e:
print("Exception caught in connect method! {}".format(e))
# this actually print Exception caught in connect method! org.bluez.Error.Failed: Protocol not available
If I manually connect from my phone it works just fine, only automatic connection is problematic at the moment.
ANY help would be appreciated, most of what I did so far is the result of trial and error so it's possible that I made a mistake somewhere
I think that at the moment the pi "wants" to connect as an audio device but lacks ability to do so as it is not connected to any hardware that will allow it.. So somehow I need to make it "forget" about the audio profiles.
I would gladly provide more information if needed.
This does seem a little back to front as you have the peripheral trying to connect to the phone. Have you looked at using ConnectProfile rather than Connect
?
If you want to have the HID device initiating connection (or reconnection) to HID host (RPi reconnects to your phone), then that is a change in the SDP record. More information at the following gist