基于BlueZ的BLE iBeacon广告数据包发送间隔过长问题求助
问题描述
基于BlueZ v5.49用Python开发iBeacon广告系统,接收端(Sena Parani-UD100模块)工作正常,能快速解析手机APP发送的广告包并保存UUID匹配的major、minor与RSSI。但发送端(同型号模块)存在异常:每1.3-1.4秒仅发送一个广告包,无法达到每秒多个包的预期。尝试优化代码、修改数据包、调整接收端过滤规则均无效,确认问题出在发送端,但代码与HCI命令函数未发现明显错误。
代码实现
import sys import argparse import os import signal import time import struct import bluetooth._bluetooth as bluez import blescan import random import re # Configurable parameters intervalm = 1000 intervalM = 1000 tx = -55 # HCI BLE CORE COMMANDS LE_META_EVENT = 0x3e LE_PUBLIC_ADDRESS=0x00 LE_RANDOM_ADDRESS=0x01 LE_SET_SCAN_PARAMETERS_CP_SIZE=7 OGF_LE_CTL=0x08 OCF_LE_SET_SCAN_PARAMETERS=0x000B OCF_LE_SET_SCAN_ENABLE=0x000C OCF_LE_CREATE_CONN=0x000D OCF_LE_SET_ADVERTISING_PARAMETERS=0x0006 OCF_LE_SET_ADVERTISING_DATA=0x0008 OCF_LE_SET_ADVERTISE_ENABLE=0x000A OCF_LE_SET_RANDOM_MAC=0x0005 LE_ROLE_MASTER = 0x00 LE_ROLE_SLAVE = 0x01 # these are actually subevents of LE_META_EVENT EVT_LE_CONN_COMPLETE=0x01 EVT_LE_ADVERTISING_REPORT=0x02 EVT_LE_CONN_UPDATE_COMPLETE=0x03 EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE=0x04 # Advertisment event types ADV_IND=0x00 ADV_DIRECT_IND=0x01 ADV_SCAN_IND=0x02 ADV_NONCONN_IND=0x03 ADV_SCAN_RSP=0x04 def randomMAC(): return [ 0x1A, 0x3A, 0x3e, random.randint(0x00, 0x7f), random.randint(0x00, 0xff), random.randint(0x00, 0xff)] def macCanal(): return [0x11,0x11,0x11,0x11,0x11,0x11] def printpacket(pkt): for c in pkt: sys.stdout.write("%02x " % struct.unpack("B",c)[0]) def hci_le_set_advertising_parameters(sock, interval_min, interval_max, canal): min1 = interval_min/0.625 max1 = interval_max/0.625 if canal == 37: advertising_channel_map = 0x01 # 0b00000001 elif canal == 38: advertising_channel_map = 0x02 # 0b00000010 elif canal == 39: advertising_channel_map = 0x04 # 0b00000100 else: advertising_channel_map = 0x07 # Other channels cmd_pkt = struct.pack("<HHBBBBBB", int(min1), int(max1), ADV_NONCONN_IND, 0x00, 0x00, 0x00, advertising_channel_map, 0x00) bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_ADVERTISING_PARAMETERS, cmd_pkt) def hci_le_set_advertising_data(sock, minor, major, txpower): min = minor print("Minor:", min) maj = major print("major: ", maj) tx = txpower print("tx power: ", tx) #uu = uuid.uuid4().hex uu="11111111111111111111111111111002" print ("UUID: ", uu) uui = re.findall('.{1,2}', uu) txp = struct.pack(">h", int(tx)) txup = struct.unpack("<h", txp) cmd_pkt = struct.pack(">BBBBBBBBBBBBBBBBBBBBBBBBBBHHh", 0x1E, 0x02, 0x01, 0x1A, 0x1A, 0xFF, 0x4C, 0x00, 0x02, 0x15, int(uui[0], 16), int(uui[1], 16), int(uui[2], 16), int(uui[3], 16), int(uui[4], 16), int(uui[5], 16), int(uui[6], 16), int(uui[7], 16), int(uui[8], 16), int(uui[9], 16), int(uui[10], 16), int(uui[11], 16), int(uui[12], 16), int(uui[13], 16), int(uui[14], 16), int(uui[15], 16), int(min), int(maj), int(txup[0])) bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_ADVERTISING_DATA, cmd_pkt) def hci_enable_le_advertise(sock): hci_toggle_le_advertise(sock, 0x01) def hci_disable_le_advertise(sock): hci_toggle_le_advertise(sock, 0x00) def hci_toggle_le_advertise(sock, enable): cmd_pkt = struct.pack("<B", enable) bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_ADVERTISE_ENABLE, cmd_pkt) def hci_set_random_mac(sock, canal): mac = macCanal() #BREAKPOINT################# if canal == 37: print(canal) cmd_pkt = struct.pack("<BBBBBB", 55, mac[4], mac[3], mac[2], mac[1], mac[0]) bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_RANDOM_MAC, cmd_pkt) elif canal == 38: print(canal) cmd_pkt = struct.pack("<BBBBBB", 56, mac[4], mac[3], mac[2], mac[1], mac[0]) bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_RANDOM_MAC, cmd_pkt) elif canal == 39: print(canal) cmd_pkt = struct.pack("<BBBBBB", 57, mac[4], mac[3], mac[2], mac[1], mac[0]) bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_RANDOM_MAC, cmd_pkt) else: print("Tres canales") cmd_pkt = struct.pack("<BBBBBB", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]) bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_RANDOM_MAC, cmd_pkt) ######################################################################################################################## def showAdapters(): os.system("sudo hcitool dev") def signal_handler(signal, frame): print("You pressed Ctrl+C") blescan.hci_disable_le_advertise(sock) sys.exit(0) signal.signal(signal.SIGINT, signal_handler) parser = argparse.ArgumentParser() parser.add_argument("-i","--id", type=int, help="device id") parser.add_argument("-d", "--devices", action="store_true", help="Show devices") parser.add_argument("-t","--tx", type=int, help="Tx power") parser.add_argument("-u","--intervalm", type=int, help="Interval min") parser.add_argument("-U","--intervalM", type=int, help="Interval max") parser.add_argument("-s","--sleep", type=int, help="Sleep time") args = parser.parse_args() if args.sleep == None: sleep = 1 else: sleep = args.sleep if args.intervalm == None: intervalm = intervalm else: intervalm = args.intervalm if args.intervalM == None: intervalM = intervalM else: intervalM = args.intervalM if intervalM < intervalm: print("Max interval is smaller than min interval.") sys.exit(2) if args.tx == None: tx = tx else: tx = args.tx if args.id == None: args.id = 1 if args.devices: showAdapters() sys.exit(2) try: sock = bluez.hci_open_dev(args.id) print("ble thread started") except: print("error accessing bluetooth device") sys.exit(1) channel = 37 # First advertisement channel while True: blescan.hci_set_random_mac(sock, channel) blescan.hci_le_set_advertising_parameters(sock, int(intervalm), int(intervalM), channel) blescan.hci_le_set_advertising_data(sock, channel, channel, int(tx)) blescan.hci_enable_le_advertise(sock) time.sleep(1) if channel == 39: channel = 37 blescan.hci_disable_le_advertise(sock) else: channel = channel + 1 blescan.hci_disable_le_advertise(sock)
关键异常点
启用广告后执行time.sleep(1)期间,理论上应按设置的间隔发送多个广告包,但实际仅发送1个。
问题根源
广告间隔参数设置过大
代码中默认intervalm和intervalM均为1000ms,转换为蓝牙LE的时间单位(0.625ms/单位)后是1600单位,对应实际发送间隔就是1000ms,这直接限制了每秒最多发送1个包。频繁启停广告与信道切换导致延迟
主循环中每秒切换一次信道,每次切换都要重新设置MAC、广告参数、广告数据,然后启用广告,1秒后立即禁用。蓝牙模块在处理这些HCI命令时存在初始化延迟,加上1秒的休眠时间,每个信道的广告仅能发送一次就被关闭。随机MAC地址设置错误
hci_set_random_mac函数中错误地将信道号作为MAC地址的第一个字节,不符合蓝牙随机地址的规范(第一个字节需标识地址类型),可能导致模块行为异常,影响广告发送频率。
修复方案
1. 降低广告间隔参数
将广告间隔调整为蓝牙LE允许的最小值(20ms),这样理论上每秒可发送约50个包:
# 修改初始参数 intervalm = 20 intervalM = 20
2. 优化主循环逻辑,避免频繁启停
蓝牙LE支持同时在3个广告信道(37、38、39)发送广告,无需手动切换。只需初始化一次参数,持续开启广告即可:
# 初始化广告配置,仅执行一次 blescan.hci_set_random_mac(sock, 0) # 0表示使用正确的随机MAC设置 blescan.hci_le_set_advertising_parameters(sock, int(intervalm), int(intervalM), 0) # 0表示启用所有3个信道 blescan.hci_le_set_advertising_data(sock, 37, 37, int(tx)) # 根据需求设置major/minor blescan.hci_enable_le_advertise(sock) # 保持进程运行,无需频繁操作 while True: time.sleep(10)
3. 修正随机MAC地址设置函数
调整macCanal生成符合规范的静态随机地址,并修正hci_set_random_mac的参数格式:
def macCanal(): # 静态随机地址:最高两位为11,符合蓝牙规范 return [0xC0, 0x11, 0x11, 0x11, 0x11, 0x11] def hci_set_random_mac(sock, canal): mac = macCanal() # 正确的随机MAC格式:6字节小端序 cmd_pkt = struct.pack("<BBBBBB", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]) bluez.hci_send_cmd(sock, OGF_LE_CTL, OCF_LE_SET_RANDOM_MAC, cmd_pkt)
4. 验证修复效果
修改完成后,使用hcitool lescan或Wireshark抓包,可观察到广告包发送频率显著提升,达到每秒多个的预期。
内容的提问来源于stack exchange,提问作者ZNano

