You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于BlueZ的BLE iBeacon广告数据包发送间隔过长问题求助

BlueZ v5.49发送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个。


问题根源

  1. 广告间隔参数设置过大
    代码中默认intervalm和intervalM均为1000ms,转换为蓝牙LE的时间单位(0.625ms/单位)后是1600单位,对应实际发送间隔就是1000ms,这直接限制了每秒最多发送1个包。

  2. 频繁启停广告与信道切换导致延迟
    主循环中每秒切换一次信道,每次切换都要重新设置MAC、广告参数、广告数据,然后启用广告,1秒后立即禁用。蓝牙模块在处理这些HCI命令时存在初始化延迟,加上1秒的休眠时间,每个信道的广告仅能发送一次就被关闭。

  3. 随机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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 12:38:10