BLE 扫描期间管道破裂 (RPi, Python 3.7)

Broken Pipe During BLE Scan (RPi, Python 3.7)

我有一些 Python 3.7 BLE 扫描代码,通常在生产中的 RPi3 设备上运行得非常好。然而,最近我看到设备*被引入到环境中,这些设备会以我不知道如何 prevent/detect.

的方式使 BLE 扫描器崩溃

*注意:带有蓝牙芯片Qualcomm Atheros QCA61x4 Bluetooth 4.1的Windows 10 Lenovo笔记本电脑可以让这段代码崩溃。我还听说 运行 BLE Beacon 站点旁边的人也经常崩溃。

崩溃发生在pkt = my_sock.recv(255)命令期间,异常是_bluetooth.error (32, 'Broken Pipe')

下面是演示问题的最小代码示例:

import logging
import select
import struct
import sys
import time
from subprocess import check_output, STDOUT, CalledProcessError

import bluetooth._bluetooth as bluez

ROOT_LOGGER = logging.getLogger()
ROOT_LOGGER.setLevel(logging.DEBUG)
ROOT_LOGGER.addHandler(logging.NullHandler())

SYS_CTL_LOG_FMT = '%(filename)-28s [%(lineno)-4d] %(levelname)-7s %(message)s'
SYSCTL_HANDLER = logging.StreamHandler(sys.stdout)
SYSCTL_HANDLER.setLevel(logging.DEBUG)
SYSCTL_HANDLER.setFormatter(logging.Formatter(SYS_CTL_LOG_FMT))
ROOT_LOGGER.addHandler(SYSCTL_HANDLER)
logger = logging.getLogger(__name__)

DEV_ID = 0
OGF_LE_CTL = 0x08
OCF_LE_SET_SCAN_PARAMETERS = 0x000B
OCF_LE_SET_SCAN_ENABLE = 0x000C
SCAN_RANDOM = 0x01
OWN_TYPE = SCAN_RANDOM
SCAN_TYPE = 0x01
INTERVAL = 0x10
WINDOW = 0x10
FILTER = 0x00  # all advertisements, not just whitelisted devices
ENABLE = 0x01


if __name__ == '__main__':
    while True:
        my_sock = bluez.hci_open_dev(DEV_ID)
        my_sock.settimeout(30.0)

        cmd_pkt = struct.pack("<BBBBBBB", SCAN_TYPE, 0x0, INTERVAL, 0x0, WINDOW, OWN_TYPE, FILTER)
        bluez.hci_send_cmd(my_sock, OGF_LE_CTL, OCF_LE_SET_SCAN_PARAMETERS, cmd_pkt)

        cmd_pkt = struct.pack("<BB", ENABLE, 0x00)
        bluez.hci_send_cmd(my_sock, OGF_LE_CTL, OCF_LE_SET_SCAN_ENABLE, cmd_pkt)

        flt = bluez.hci_filter_new()
        bluez.hci_filter_all_events(flt)
        bluez.hci_filter_set_ptype(flt, bluez.HCI_EVENT_PKT)
        my_sock.setsockopt(bluez.SOL_HCI, bluez.HCI_FILTER, flt)
        try:
            packets_received = 0
            while True:
                ready_to_read, ready_to_write, in_error = select.select([my_sock, ], [my_sock, ], [my_sock, ], 5)
                if len(ready_to_read) == 0:
                    time.sleep(0.001)
                    continue
                try:
                    pkt = my_sock.recv(255)
                except bluez.error as exc_data:
                    logger.error(f'Received a _bluetooth.error while trying to read. Aborting: {exc_data}')
                    raise
                packets_received += 1
                ptype, event, plen = struct.unpack("BBB", pkt[:3])
                logger.info(f'{packets_received} {ptype}, {event}, {plen}')

        except bluez.error:
            my_sock.close()
            while True:
                # this loops until hciconfig is able to successfully restart
                try:
                    check_output('sudo hciconfig hci0 up', shell=True, stderr=STDOUT)
                except CalledProcessError as exc_data:
                    logger.warning(f'{type(exc_data)}: {exc_data}')
                    continue
                time.sleep(1)
                break

输出如下所示:

pi@raspberrypi:~/my_test $ sudo python3 distilled_test.py
distilled_test.py            [63  ] INFO    1 4, 14, 4
distilled_test.py            [63  ] INFO    2 4, 14, 4
distilled_test.py            [63  ] INFO    3 4, 62, 27
distilled_test.py            [63  ] INFO    4 4, 62, 26
distilled_test.py            [63  ] INFO    5 4, 62, 12
distilled_test.py            [63  ] INFO    6 4, 62, 31
distilled_test.py            [63  ] INFO    7 4, 62, 31
distilled_test.py            [63  ] INFO    8 4, 62, 31
distilled_test.py            [63  ] INFO    9 4, 62, 31
distilled_test.py            [63  ] INFO    10 4, 62, 31
distilled_test.py            [63  ] INFO    11 4, 62, 31
distilled_test.py            [63  ] INFO    12 4, 62, 31
distilled_test.py            [63  ] INFO    13 4, 62, 31
distilled_test.py            [63  ] INFO    14 4, 62, 31
distilled_test.py            [63  ] INFO    15 4, 62, 31
distilled_test.py            [63  ] INFO    16 4, 62, 31
distilled_test.py            [63  ] INFO    17 4, 62, 31
distilled_test.py            [63  ] INFO    18 4, 62, 31
distilled_test.py            [63  ] INFO    19 4, 62, 31
distilled_test.py            [63  ] INFO    20 4, 62, 31
distilled_test.py            [63  ] INFO    21 4, 62, 31
distilled_test.py            [63  ] INFO    22 4, 62, 31
distilled_test.py            [59  ] ERROR   Received a _bluetooth.error while trying to read. Aborting: (32, 'Broken pipe')
distilled_test.py            [72  ] WARNING <class 'subprocess.CalledProcessError'>: Command 'sudo hciconfig hci0 up' returned non-zero exit status 1.
distilled_test.py            [72  ] WARNING <class 'subprocess.CalledProcessError'>: Command 'sudo hciconfig hci0 up' returned non-zero exit status 1.
distilled_test.py            [72  ] WARNING <class 'subprocess.CalledProcessError'>: Command 'sudo hciconfig hci0 up' returned non-zero exit status 1.
distilled_test.py            [72  ] WARNING <class 'subprocess.CalledProcessError'>: Command 'sudo hciconfig hci0 up' returned non-zero exit status 1.
distilled_test.py            [72  ] WARNING <class 'subprocess.CalledProcessError'>: Command 'sudo hciconfig hci0 up' returned non-zero exit status 1.
distilled_test.py            [63  ] INFO    1 4, 14, 4
distilled_test.py            [63  ] INFO    2 4, 14, 4
distilled_test.py            [63  ] INFO    3 4, 62, 27
distilled_test.py            [63  ] INFO    4 4, 62, 26
distilled_test.py            [63  ] INFO    5 4, 62, 40
distilled_test.py            [63  ] INFO    6 4, 62, 39

我的理论是,新的 BLE 广播设备正在用蓝牙流量淹没 RPi,以至于我无法足够快地接收它,并且蓝牙服务关闭了套接字。有什么建议?

Raspbian 巴斯特精简版 bluez-5.52.tar.xz gattlib-0.20150805 pybluez-0.23 Python3.7.3

我还应该注意,这个 Lenovo/Qualcomm 笔记本电脑蓝牙广告足以让我的首选 Android 应用程序 nRF Connect 反复循环蓝牙。虽然我意识到我无法阻止 Lenovo/Qualcomm 调皮,但我仍然觉得我需要保护我的应用程序不因蓝牙噪音而崩溃。

所以,事实证明,broken pipe 实际上确实意味着 broken pipe...想象一下。

我将项目连接到 RPi4,并且能够看到代码处理蓝牙消息的速度足以跟上。正如我在最初的问题中所假设的那样,RPi3 代码跟不上蓝牙芯片接收消息的速度,在某些时候,某种 buffer/pipe/queue 填满了,蓝牙(可能是 Bluez)坏了管道。