ukBaz / python-bluezero

A simple Python interface to Bluez
MIT License
387 stars 112 forks source link

`Peripheral.add_characteristic(...)` doesn't return the created `Characteristic` object #318

Closed MaikuMori closed 3 years ago

MaikuMori commented 3 years ago

Peripheral.add_characteristic(...) doesn't return the created characteristic which makes it hard to later set the value. In the examples the characteristic is obtained in notify_callback, but if notify is not used there is no easy way to obtain it.

Meanwhile, Central.add_characteristic(...) does return the remote Characteristic object.

ukBaz commented 3 years ago

Apologies for the slow response on this; it appears the email of this issue being opened got caught by the spam filter.

Anyway, the reason that the object is not returned is because it shouldn't be needed for the peripheral. The event-loop is running for the peripheral and so there is the read, write, notify callbacks.

The example I assume you are referencing is: https://github.com/ukBaz/python-bluezero/blob/master/examples/ble_uart.py This is a little unusual example because when a value is written on one characteristic it is forcing a notify on a different one.

Maybe if you stepped backed and explained what you are trying to do I could better understand what is missing from Bluezero.

Is your use case when the Central device writes to the peripheral device? What is not happening that should? That write will update the characteristic automatically. The write_callback is to allow some other action to happen based on the written value. For example, turn on an LED for example.

If you can give me some context I can help and maybe get a better example for the library.

MaikuMori commented 3 years ago

I have a peripheral which doesn't use notify on any of the characteristics. I have several read and write characteristics.

I need to write to a characteristic as a response to data received from another characteristic.

So far I'm doing something like this:

def __init__(self, adapter_address):
    # ...
    self.p = peripheral.Peripheral(adapter_address, local_name="...")
    # ...
    self.p.add_characteristic(
        srv_id=1,
        chr_id=2,
        uuid=COMMAND_CHARACTERISTIC,
        value=[],
        notifying=False,
        flags=["write"],
        write_callback=self.on_write,
    )

    self.p.add_characteristic(
        srv_id=1,
        chr_id=3,
        uuid=GET_CHARACTERISTIC,
        value=[],
        notifying=False,
        flags=["read"],
    )
    # Workaround because it's not returned.
    self.get_ch = self.p.characteristics[-1]

    # ...

# ...

def on_write(self, value, options) -> None:
    # ...
    self.get_ch.set_value(value)
ukBaz commented 3 years ago

OK, I think I understand why you are having difficulty. Maybe my example was not helpful because the reads and writes should be accessing another object.

Is the following a more helpful example:

from gi.repository import GLib

# Bluezero modules
from bluezero import adapter
from bluezero import peripheral

# constants
THING_SRV = 'BBBB0001-B5A3-F393-E0A9-E50E24DCCA9E'
THING_W = 'BBBB0002-B5A3-F393-E0A9-E50E24DCCA9E'
THING_R = 'BBBB0003-B5A3-F393-E0A9-E50E24DCCA9E'

class MyThing:
    value = [0]

    @classmethod
    def peri_read(cls):
        print('Read:', cls.value)
        return cls.value

    @classmethod
    def peri_write(cls, value, options):
        print('Write:', value)
        cls.value = value
        print(cls.value)

def main(adapter_address):
    ble_thing = peripheral.Peripheral(adapter_address, local_name='BLE Thing')
    ble_thing.add_service(srv_id=1, uuid=THING_SRV, primary=True)
    ble_thing.add_characteristic(srv_id=1, chr_id=1, uuid=THING_W,
                                value=[], notifying=False,
                                flags=['write', 'write-without-response'],
                                write_callback=MyThing.peri_write,
                                read_callback=None,
                                notify_callback=None)
    ble_thing.add_characteristic(srv_id=1, chr_id=2, uuid=THING_R,
                                value=[], notifying=False,
                                flags=['read'],
                                notify_callback=None,
                                read_callback=MyThing.peri_read,
                                write_callback=None)
    ble_thing.publish()

if __name__ == '__main__':
    main(list(adapter.Adapter.available())[0].address)
MaikuMori commented 3 years ago

I see, so that's the intended usage.

I can re-write to something similar. I'd probably still embed the Peripheral in my class and then not use static methods. The key takeaway is to use the read_callback.