How to clean registered advertisements from a BLE server?

3.6k Views Asked by At

I am trying to run the following python script: https://github.com/Jumperr-labs/python-gatt-server (gatt-server-example.py) which is a Bluetooth Low Energy server.

It works perfectly fine on my computer, but when I try to run it on an intel-edison I get the following error:

Failed to register advertisement: org.bluez.Error.NotPermitted: Maximum        
advertisements reached

Python-dbus and Bluez are installed on this device (Bluez v5.50). I am looking for a way to clean registered advertisements if there are some and get to launch my server on this intel-edison.

2

There are 2 best solutions below

0
Ferry Toth On BEST ANSWER

If you run latest Yocto image from github.com/edison-fw and switch to the latest python3 example gatt server the server runs without error. Bluetoothctl show shows that the Heart Rate service is automatically unregistered after killing the gatt server.

0
Gino Mempin On

The BlueZ API docs provides a bit of an explanation for the error:

Methods     RegisterAdvertisement(object advertisement, dict options)

            Registers an advertisement object to be sent over the LE
            Advertising channel.  The service must be exported
            under interface LEAdvertisement1.

            ...

            If the maximum number of advertisement instances is
            reached it will result in NotPermitted error.

So, the error is coming from the RegisterAdvertisement call:

ad_manager.RegisterAdvertisement(test_advertisement.get_path(), {},
                                 reply_handler=register_ad_cb, 
                                 error_handler=functools.partial(register_ad_error_cb, mainloop))

A "brute-force" way to cleanup previously registered advertisements is to always restart the bluetooth service before running your BLE server.

# Restart bluetooth
$ systemctl restart bluetooth
$ systemctl status bluetooth
● bluetooth.service - Bluetooth service
   Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; vendor preset
   Active: active (running) since Thu 2020-08-20 18:07:55 JST; 4s ago
...

# Start BLE server
$ python3 gatt-server.py

A more "elegant" way is to try making sure that your BLE server implementation has handling for proper shutdown or cleanup. Try calling the BlueZ advertisement manager and GATT manager APIs Unregister* methods when the server process ends/quits:

You might also need to quit the created GLib.MainLoop.

The example-gatt-server mentioned in the accepted answer does not do this, but you can also check example-advertisement code (which is also from BlueZ) that does do this:

def shutdown(timeout):
    print('Advertising for {} seconds...'.format(timeout))
    time.sleep(timeout)
    mainloop.quit()


def main(timeout=0):
    
    ...

    mainloop = GObject.MainLoop()

    ad_manager.RegisterAdvertisement(test_advertisement.get_path(), {},
                                     reply_handler=register_ad_cb,
                                     error_handler=register_ad_error_cb)

    if timeout > 0:
        threading.Thread(target=shutdown, args=(timeout,)).start()
    else:
        print('Advertising forever...')

    mainloop.run()  # blocks until mainloop.quit() is called

    ad_manager.UnregisterAdvertisement(test_advertisement)
    print('Advertisement unregistered')
    dbus.service.Object.remove_from_connection(test_advertisement)

Notice that when the mainloop is quit, the main function ends with cleanup calls.

The codes in https://github.com/Jumperr-labs/python-gatt-server are just ported/re-organized versions of the BlueZ codes. An improvement would be modifying the advertising and gatt-server modules to add proper cleanup.

One way to do this is by gracefully killing the BLE server process with a SIGTERM signal, then catching that SIGTERM within the app.

# ----------------------------------------------------------------------
def terminate(signum, frame):
    adv_manager.UnregisterAdvertisement(...)
    gatt_manager.UnregisterApplication(...)
    main_loop.quit()
    return True
# ----------------------------------------------------------------------
signal.signal(signal.SIGTERM, terminate)