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)坏了管道。
我有一些 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)坏了管道。