防止树莓派 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