Open AntonKlimenkov opened 1 month ago
Do you have a minimal reproducible example piece of code I could use for testing?
Looking at this now, it doesn't seem to be well documented in the library, but shouldn't it be something like:
write_value(b'\x02\x00...', {'type': 'command'})
I don't have a minimal example yet. Will try to come up with it..
But I think the issue is that in your example {'type': 'command'}
will be converted to a dbus.Array before it's passed to WriteValue in localGatt.py.
` def write_value(self, value, flags=''):
"""
Write a new value to the characteristic.
:param value: A list of byte values
:param flags: Optional dictionary.
Typically empty. Values defined at:
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt
"""
try:
self.characteristic_methods.WriteValue(value, dbus.Array(flags))
except AttributeError:
logger.error('Service: %s with Characteristic: %s not defined '
'on device: %s. Cannot write_value', self.srv_uuid,
self.chrc_uuid, self.device_addr)`
And it seems that dbus.Array() loses the values of the dictionary.
In [14]: dbus.Array({'type': 'command'})
Out[14]: dbus.Array(['type'], signature=None)
I agree with you. It looks like it should be:
dbus.Dictionary({'type': 'command'}, signature=dbus.Signature('sv'))
I've looked through the test cases in Bluezero and there doesn't seem to be anything that tests this.
Are you able to take a look on your setup as to what works?
Minimal example to catch exception:
from bluezero import adapter, central, constants, peripheral
dongle = next(adapter.Adapter.available())
dongle.nearby_discovery()
device_addr = 'SOME_ADDR'
service_uuid = 'SOME_SERV_UUI'
write_uuid = 'SOME_WRITE_UUI'
monitor = central.Central(device_addr=device_addr)
monitor.connect()
write_char = monitor.add_characteristic(service_uuid, write_uuid)
monitor.load_gatt()
write_char.write_value(b'asdad', flags={'type': 'command'})
Changing dbus.Array(flags)
to dbus.Dictionary(flags)
or passing flags
directly works: as in data does get send in 'command' mode = not waiting for acks.
I've spent a little time on this issue and considering how this should look and fit with the API complexity goals of Bluezero
I'm not sure I'm happy for this to be a final solution, but to give visibility of what I've tried.
I created a little script to exercise some different values using a Micro:Bit I had laying around.
from random import randint
import dbus
from bluezero import central
ubit_addr = 'E1:4B:6C:22:56:F0'
temperature_srvc = 'e95d6100-251d-470a-a062-fa1922dfa9a8'
temperature_period = 'e95d1b25-251d-470a-a062-fa1922dfa9a8'
def get_rand_period():
rand_value = randint(1000, 1500)
return int(rand_value).to_bytes(2, byteorder='little', signed=False)
ubit = central.Central(device_addr=ubit_addr)
rw_char = ubit.add_characteristic(temperature_srvc, temperature_period)
ubit.connect()
test_write_options = [
None,
{},
{'type': 'request', 'offset': 0},
{'type': dbus.String('request'), 'offset': dbus.UInt16(0)},
]
print('Test write flags:')
for flags in test_write_options:
period_rand = get_rand_period()
print(f"\twriting {int.from_bytes(period_rand, byteorder='little', signed=False)} with {flags}")
rw_char.write_value(period_rand, flags=flags)
test_read_options = [
None,
{},
{'offset': 0},
{'offset': dbus.UInt16(0)}
]
print("Test read flags")
for flags in test_read_options:
result = rw_char.read_raw_value(flags=flags)
print(f"\tRead value: {int.from_bytes(result, byteorder='little', signed=False)} with {flags}")
ubit.disconnect()
And then changed the functions in GATT.py to be consistent with this:
def read_raw_value(self, flags=None):
"""
Return this characteristic's value (if allowed).
:param flags: "offset": Start offset
"device": Device path (Server only)
:return:
Possible Errors: org.bluez.Error.Failed
org.bluez.Error.InProgress
org.bluez.Error.NotPermitted
org.bluez.Error.InvalidValueLength
org.bluez.Error.NotAuthorized
org.bluez.Error.NotSupported
"""
_flags = {}
if flags is None:
flags = {}
else:
for k, v in flags.items():
if k in ['offset', 'mtu']:
_flags[k] = dbus.UInt16(v)
if k == 'device':
_flags[k] = dbus.ObjectPath(v)
try:
return self.characteristic_methods.ReadValue(dbus.Dictionary(_flags))
except AttributeError:
logger.error('Service: %s with Characteristic: %s not defined on '
'device: %s', self.srv_uuid, self.chrc_uuid,
self.device_addr)
return []
def write_value(self, value, flags=None):
"""
Write a new value to the characteristic.
:param value: A list of byte values
:param flags: Optional dictionary.
Typically empty. Values defined at:
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt
"""
_flags = {}
if flags is None:
_flags = {}
else:
for k, v in flags.items():
if k in ['offset', 'mtu']:
_flags[k] = dbus.UInt16(v)
if k == ['type', 'link']:
_flags[k] = dbus.String(v)
if k == 'device':
_flags[k] = dbus.ObjectPath(v)
try:
self.characteristic_methods.WriteValue(value, dbus.Dictionary(_flags))
except AttributeError:
logger.error('Service: %s with Characteristic: %s not defined '
'on device: %s. Cannot write_value', self.srv_uuid,
self.chrc_uuid, self.device_addr)
I have not looked at what would be required for localGATT.py
Example oputput from test run:
Test write flags:
writing 1444 with None
writing 1190 with {}
writing 1225 with {'type': 'request', 'offset': 0}
writing 1381 with {'type': dbus.String('request'), 'offset': dbus.UInt16(0)}
Test read flags
Read value: 1381 with None
Read value: 1381 with {}
Read value: 1381 with {'offset': 0}
Read value: 1381 with {'offset': dbus.UInt16(0)}
Thank you for the investigation! So you would feel reluctant introducing this change?
Another approach that would satisfy my needs would be to pass the same flags
as parameters to add_characteristic
of Central
class in the way it's passed to Peripheral
class.
So you would feel reluctant introducing this change?
I'm reluctant to make it a permanent change to the API at this time without getting more feedback on it.
Another approach that would satisfy my needs would be to pass the same flags as parameters to add_characteristic of Central class in the way it's passed to Peripheral class.
Values for mtu
and offset
could be set differently per read so I think moving to the add_characteristic
command doesn't feel right for the library generally. Although I'm open to the idea if you have convincing evidence.
I guess there are two different needs here.
The a fix for the library where this has never worked correctly. This is the first time it has been raised as an issue so I'm assuming this is an "advanced" usage corner case. While I am keen to fix it in the library, I would like to not break other things, and having some unit tests would be a good way of upping the confidence levels. The goal of this library is to help people get started with BLE and move away from the library when they are more knowledgable and want more control.
The other need is that you want a quick fix so you can get on with your project.
Could I possibly provide more feedback/testing?
And please disregard my suggestion about add_characteristic
. I've realised Central and Peripherla use different Characteristic
class underneath, so it'd not be just passing parameters to the next function.
Could I possibly provide more feedback/testing?
Absolutely!
I'm pushed the changes to GATT.py to the read_write_options branch.
The feedback I'm interested in:
I think the following
230 if k == ['type', 'link']:
should be
230 if k in ['type', 'link']:
Tried to create a PR into the branch, but understandably I don't have the rights.
Added fix with 86afd98
Thanks for the fixes! What I can say from trying them:
flags=None
to write_value
works as before for me. Slow.flags={'type': 'command'}
actually sends 'commands' and speeds things up considerably. Since in my experience passing anything to 'flags' on main branch leads to an exception, I'd assume every existing user of the library is not passing anything to 'flags'. So this change would not break anything for them.
https://github.com/ukBaz/python-bluezero/blob/e1202855bf25ee145d5bd6941afddcda88998808/bluezero/GATT.py#L204 Passing any flags specified in https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/org.bluez.GattCharacteristic.rst to 'write_value' in GATT.py as a python dictionary leads to an exception: