防止树莓派 4 通过 a2dp 协议连接
Preventing RPi 4 from connecting through a2dp protocol
一些背景知识,我想用我的 Pi 为我的 phone 模拟蓝牙键盘。
我实际上已经设法让它与我的 phone 一起工作,但我在自动连接方面遇到了困难。
我希望 pi 扫描附近的设备并启动与已配对设备的连接,但由于某些原因它不起作用。
如果我的 pi 连接到屏幕(我的屏幕有一个内置扬声器)而不是出于某种原因自动连接只连接到音频配置文件,我必须手动连接到 HID 配置文件,如果我的 pi 没有连接到一个屏幕,它根本无法连接,我得到
org.bluez.Error.Failed: Protocol not available
当我尝试使用 sudo systemctl status bluetooth
查看蓝牙状态时,我得到了关于缺少什么协议的提示。这是输出:
● 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
我觉得我已经尝试了一切,没有在线documentation/information真是令人沮丧。
我什至从我的设备中完全清除了 pulseaudio,希望它会停止尝试连接到音频配置文件,但它没有用。
以下是我认为相关的一些代码:
设备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()
服务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
initiate_connection_with_devices_in_range get 方法每 10 秒左右被另一个与此问题无关的文件调用。
""" 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
如果我从 phone 手动连接,它工作正常,目前只有自动连接有问题。
如有任何帮助,我们将不胜感激,到目前为止,我所做的大部分工作都是反复试验的结果,因此我可能在某处犯了错误
我认为目前 pi“想要”作为音频设备进行连接,但缺乏这样做的能力,因为它没有连接到任何允许它的硬件。所以我需要以某种方式实现它“忘了”关于音频配置文件。
如果需要,我很乐意提供更多信息。
这似乎有点前后颠倒,因为您有外围设备试图连接到 phone。您是否考虑过使用 ConnectProfile 而不是 Connect
?
如果您想让 HID 设备启动与 HID 主机的连接(或重新连接)(RPi 重新连接到您的 phone),那么这就是 SDP 记录中的更改。更多信息请见下方gist
一些背景知识,我想用我的 Pi 为我的 phone 模拟蓝牙键盘。 我实际上已经设法让它与我的 phone 一起工作,但我在自动连接方面遇到了困难。
我希望 pi 扫描附近的设备并启动与已配对设备的连接,但由于某些原因它不起作用。
如果我的 pi 连接到屏幕(我的屏幕有一个内置扬声器)而不是出于某种原因自动连接只连接到音频配置文件,我必须手动连接到 HID 配置文件,如果我的 pi 没有连接到一个屏幕,它根本无法连接,我得到
org.bluez.Error.Failed: Protocol not available
当我尝试使用 sudo systemctl status bluetooth
查看蓝牙状态时,我得到了关于缺少什么协议的提示。这是输出:
● 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
我觉得我已经尝试了一切,没有在线documentation/information真是令人沮丧。
我什至从我的设备中完全清除了 pulseaudio,希望它会停止尝试连接到音频配置文件,但它没有用。
以下是我认为相关的一些代码:
设备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()
服务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 initiate_connection_with_devices_in_range get 方法每 10 秒左右被另一个与此问题无关的文件调用。
""" 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
如果我从 phone 手动连接,它工作正常,目前只有自动连接有问题。
如有任何帮助,我们将不胜感激,到目前为止,我所做的大部分工作都是反复试验的结果,因此我可能在某处犯了错误
我认为目前 pi“想要”作为音频设备进行连接,但缺乏这样做的能力,因为它没有连接到任何允许它的硬件。所以我需要以某种方式实现它“忘了”关于音频配置文件。
如果需要,我很乐意提供更多信息。
这似乎有点前后颠倒,因为您有外围设备试图连接到 phone。您是否考虑过使用 ConnectProfile 而不是 Connect
?
如果您想让 HID 设备启动与 HID 主机的连接(或重新连接)(RPi 重新连接到您的 phone),那么这就是 SDP 记录中的更改。更多信息请见下方gist