kevincar / bless

Cross-platform Bluetooth Low Energy Server Python Library
MIT License
110 stars 30 forks source link

Need Notify Example and documentation #102

Open tljstewart opened 1 year ago

tljstewart commented 1 year ago

Great library, was pointed here by @dlech on bleak repo looking for a BLE server. Successfully added read and write callback, but notify callback appears to be missing in the api, is it possible to notify once the characteristic.value is updated?

tljstewart commented 1 year ago

I think I found it, you need to call server.update_value(service_uuid, char_uuid) to notify, I would of suspected a callback similar to read_request_func , write_request_func... I'd recommend an api such as on_read, on_write, on_notify .

Currently, it seems a bit strange to set the characteristic.value then call server.update_value(service_uuid, char_uuid) to notify.

Perhaps when characteristic.value is set it should call server.update_value if notify property is set and has subscribers? Just some thoughts, as I use the library

If there is a readthedocs or a readme, I'd love to read over it. Thanks

Great library, and thank you for making it MIT licensed.

JHorwitz1011 commented 1 year ago

I second the need for some documentation and a notify example! Hesitant to use this without notify being easily usable

MarkusPiotrowski commented 1 year ago

This works for me, modified from the gattserver.py example. It's not very elegant, but may serve as an example. The asyncio usage in the examples is a little bit outdated, but I didn't change it here:

"""
Example for a BLE 4.0 Notify Server using a GATT dictionary of services and
characteristics
"""

import logging
import asyncio

from typing import Any, Dict

from bless import (  # type: ignore
        BlessServer,
        BlessGATTCharacteristic,
        GATTCharacteristicProperties,
        GATTAttributePermissions
)

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(name=__name__)

def write_request(
        characteristic: BlessGATTCharacteristic,
        value: Any,
        **kwargs
        ):
    characteristic.value = value
    logger.debug(f"Char value set to {characteristic.value}")
    # if characteristic.value == b'\x0f':
        # logger.debug("Nice")
        # trigger.set()

def notify(server, value):
    server.get_characteristic(
       YOUR_NOTIFY_UUID,
    ).value = value
    server.update_value(
        YOUR_SERVICE_UUID",
        YOUR_NOTIFY_UUID"
    )

async def run(loop):
    # Instantiate the server
    gatt: Dict = {
        YOUR_SERVICE_UUID: {
            YOUR_NOTIFY_UUID: {
                "Properties": GATTCharacteristicProperties.notify,
                "Permissions": GATTAttributePermissions.readable,
                "Value": None
            },
            "YOUR_WRITE_UUID: {  # This is not required, I need it for my case
                "Properties": (GATTCharacteristicProperties.write |
                               GATTCharacteristicProperties.write_without_response),
                "Permissions": GATTAttributePermissions.writeable,
                "Value": None
            },
        }
    }

    server_name = "MY_NOTIFY_SERVER"

    server = BlessServer(name=server_name, loop=loop, name_overwrite=True)
    server.write_request_func = write_request  # not required if we don't have a write characteristic

    await server.add_gatt(gatt)
    await server.start()

    logger.debug("Waiting for someone to subscribe")
    while not await server.is_connected():  # The name of this method is slightly misleading
        await asyncio.sleep(0.1)
    logger.debug("Someone has subscribed.")
    notify(server, b'Welcome') 

    # This is the actual notifying loop:
    while True:
        a = input("Value: ")
        if a == "quit":
            break
        else:
           notify(server, a.encode())
        await asyncio.sleep(0.5)

    logger.debug("Stopping")
    await server.stop()

loop = asyncio.get_event_loop()
loop.run_until_complete(run(loop))
pawel-movelab commented 2 weeks ago

@MarkusPiotrowski Thx for your example. I used it with added values for YOUR_SERVICE_UUID, YOUR_NOTIFY_UUID, YOUR_WRITE_UUID from https://bleid.netlify.app

For debugging I used NRF Connect App on iOS. However, Im getting spammed with Bluetooth Pairing requests by iOS. Does it have to do with wrong UUID for my service and iOS is getting confused what service is it connecting to? Or some pairing handling is needed on the script side?

In between pairing requests I tapped subscribe button and send 1,2 ,3 via terminal running script on my Raspberry Pi. These values were received, but popup still keep on showing up 🤔

https://imgur.com/a/gDPVyw4

Any help is appreciated :)


Edit:

Solution was found here in my case: https://github.com/ukBaz/python-bluezero/issues/335

After tapping pair once, pairing requests are not shown anymore on iOS

One cause of this is that Linux tries to read the battery state from the iPhone, which triggers the pairing request. You can configure bluez to prevent this. One of my colleagues at Ditto wrote some docs to fix it, which the forum won't let me link directly, but I will copy-paste the main instructions here.

To disable auto Battery reading

  1. Open the bluetooth service file /usr/lib/systemd/system/bluetooth.service, or /etc/systemd/system/bluetooth.target.wants/bluetooth.service in a text editor. You may need sudo permission to write to this file.

  2. Add -P battery to the end of the ExecStart line to disable the Battery feature in bluetoothd. Your ExecStart should look something like ExecStart=/usr/lib/bluetooth/bluetoothd -P battery now. Save the file.

3.Run systemctl daemon-reload and systemctl restart bluetooth to apply the changes to the Bluetooth service