python-3.xbluetooth-lowenergyibeaconbluez

Why my code about advertisement in BLE takes around 1 second between packages sent


I made a program using BlueZ (v5.49) in python. The goal is send advertising packages (iBeacon) and, with a sniffer, unpack it and save the major, minor and rssi if the UUID is the correct one. The major and minor values are the advertisement channel used to send the data.

First of all, I am using two modules Sena Parani-UD100. One transmit the information and the other recives it. The receiver works perfectly fine, I tested it with my mobile phone with an app that send advertisement packages and it takes them really quick and do what I expected in terms of receive the package.

So the problem comes when I send information with the other module, it only send the advertisement packages once a second (a bit more really, around 1.3-1.4sec) and I don't know where is the problem and why it takes that much.

Here is my code:


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)

Notice that when I put the program to sleep after enable the advertisement, in that time lot of packages must be send but it sends only one.

I tried to send multiple packages per second as an usual advertisemet process in BLE but it only sends one per second so I tried to optmize the code and tried to modify the package and also the filter in the receiver thinking that the problem was in the receiver but none of that worked. After many comprobations, I ended up concludingthat the problem is the transmitter and for me, the code is alright. And the functions with the HCI commands too so I don´t know where is the problem.


Solution

  • The problem is likely on the transmitter side. You can use BlueZ HCI commands to try to increase the advertising rate as shown in my answer here (shell script with the hcitool command, sorry, but you should be able to apply it to Python).

    sudo hcitool -i hci0 cmd 0x08 0x0006 A0 00 A0 00 03 00 00 00 00 00 00 00 00 07 00
    

    [the] hcitool command (0x08 0x0006) is "LE Set Advertising Parameters. The first two bytes A0 00 are the "min interval". The second two bytes A0 00 are the "max interval". In this example, it sets the time between advertisements to 100ms. The granularity of this setting is 0.625ms, so setting the interval to 01 00 sets the advertisement to go every 0.625ms. Setting it to A0 00 sets the advertisement to go every 0xA0*0.625ms = 100ms.

    Different BLE platforms support different advertising rates (1 Hz, 10 Hz or more), so it is possible you are limited on the hardware side. To test this while eliminating any issues with your receiver, the easiest way is to use a mobile app to measure the reception rate. I would suggest my BeaconScope app for Android, which provides these stats. (There is also an iOS version, but iOS cannot measure packet reception rates for iBeacon, as APIs limit detections to once per second.)