ukBaz / python-bluezero

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

how to return simple string from update value? #384

Closed David-Afik closed 1 year ago

David-Afik commented 1 year ago

if there is another place to ask - please tell me

I'm trying to use the example of "cpu_temperature.py " in order to publish my own data (not Temp, but a string) I can see the data is "changing" from the read_value function on every read but nothing in the update_value

def GetSampleFromDevice():
     data= str(random.randrange(0, ,5678) )
     return data 

def read_value():
    sample= GetSampleFromDevice()

    print('read value function  -> data is  :  ' , sample)
    return sample

def update_value(characteristic):
    print('before')
    new_value = read_value()
    print('after')
    characteristic.set_value(new_value)
    print ('new value is ' , new_value)
    return characteristic.is_notifying

def notify_callback(notifying, characteristic):
    if notifying:
        async_tools.add_timer_seconds(2, update_value, characteristic)

it doesn't even enter the update_value function

what am I missing ?

Thanks,

(if there is a forum I can ask and used please inform me)

ukBaz commented 1 year ago

Asking the question here is fine. There are not enough questions on the repo to justify setting anything else up.

There is nothing obviously wrong from just looking at the extract you have posted.

A couple of candidates for where things might be going wrong are the CPU_TMP_CHRC and CPU_FMT_DSCP are set to values from the Bluetooth Standard that relate to sending temperature values. I would remove the descriptor and set the characteristic to a randomly generated UUID. Look at https://github.com/ukBaz/python-bluezero/blob/main/examples/ble_uart.py for an example.

The other thing is that characteristics can only send bytes. Try generating some random bytes and sending them rather than a string.

When running your script have separate terminals open with the following running to get more debug information:

If that doesn't help I'll try and take a look the weekend.

ukBaz commented 1 year ago

The below test has worked successfully for me. I used nRF Connect app as the client on my phone.

The main thing to change was the value of the characteristic to be sent in bytes. Without that it was not sending data.

I have also removed the GATT descriptor and changed the GATT characteristic to a custom UUID just to follow best practice. This is also why I changed the name of your function to snake_case.

"""Example of how to create a Peripheral device/GATT Server"""
# Standard modules
import logging
import random

# Bluezero modules
from bluezero import async_tools
from bluezero import adapter
from bluezero import peripheral

# constants
# Custom service uuid
STRING_SRVC = '12341000-1234-1234-1234-123456789abc'
STRING_CHRC = '12341001-1234-1234-1234-123456789abc'

def get_sample_from_device():
    data = str(random.randrange(0, 5678))
    return data
    return data

def read_value():
    sample = get_sample_from_device()

    print('read value function  -> data is  :  ', sample)
    return sample

def update_value(characteristic):
    print('before')
    new_value = read_value()
    print('after')
    characteristic.set_value(new_value.encode())
    print('new value is ', new_value)
    return characteristic.is_notifying

def notify_callback(notifying, characteristic):
    if notifying:
        async_tools.add_timer_seconds(2, update_value, characteristic)

def main(adapter_address):
    """Creation of peripheral"""
    logger = logging.getLogger('localGATT')
    logger.setLevel(logging.DEBUG)
    # Example of the output from read_value
    print(f'String is {read_value()}')
    # Create peripheral
    str_monitor = peripheral.Peripheral(adapter_address,
                                        local_name='String Monitor',
                                        appearance=1344)
    # Add service
    str_monitor.add_service(srv_id=1, uuid=STRING_SRVC, primary=True)
    # Add characteristic
    str_monitor.add_characteristic(srv_id=1, chr_id=1, uuid=STRING_CHRC,
                                   value=[], notifying=False,
                                   flags=['read', 'notify'],
                                   read_callback=read_value,
                                   write_callback=None,
                                   notify_callback=notify_callback
                                   )
    # Publish peripheral and start event loop
    str_monitor.publish()

if __name__ == '__main__':
    # Get the default adapter address and pass it to main
    main(list(adapter.Adapter.available())[0].address)

The transcript from the session was:

$ python3 peripheral_str_characteristic.py 
read value function  -> data is  :   4547
String is 4547
Failed to register advertisement: org.bluez.Error.Failed: Failed to register advertisement
before
read value function  -> data is  :   2460
after
new value is  2460
before
read value function  -> data is  :   1286
after
new value is  1286
before
read value function  -> data is  :   516
after
new value is  516
before
read value function  -> data is  :   3665
after
new value is  3665
read value function  -> data is  :   3499

Please let me know if this works for you and I'll go ahead and close this issue.

ukBaz commented 1 year ago

I've just realised that it is an integer value are sending as a string. This is very inefficient of Bluetooth data rate over the air. Sending it as an integer value with to_bytes is far more efficient (although I accept you can't then use a general client).

David-Afik commented 1 year ago

great , thank you ! didn't understand that I need to send it as bytes and not as string

now it's working !

another question - it there a unique \custom UUID for sending GPS data ? and can I add my own data to the coordinates? or it's best to use my own UUID and send it as string ?

Thanks,

ukBaz commented 1 year ago

The Bluetooth SIG website is the place to look for this kind of information. https://www.bluetooth.com/specifications/assigned-numbers/

The "16-Bit UUIDs" and "GATT Specification Supplement" should have the majority of information you might need.

There is also the specifications at https://www.bluetooth.com/specifications/specs/

There is a GNSS (uses the older style "classic" Bluetooth Serial Port Profile (SPP)) and LNS.

If you want to structure your own data then you are probably best sticking with a custom UUID.

There is some more information on customer UUIDs at: https://novelbits.io/uuid-for-custom-services-and-characteristics/

David-Afik commented 1 year ago

OK I'm going to use my own data structure (adding more data to the GPS)

2 more questions that are not related for the same project:

  1. can I encrypt the string I'm sending ? so if someone will connect to me he will not see the raw data? or for this option I will have to use a password or MAC list? I mean right now my device is without a password - so if someone will use "nRF connect" , he will see I have a service with "read" option , and when he will enter it he will see the data
  2. can I publish the data without the need to be connected to the device ? so if devices will scan and see my device they will all see the same data (without the need to be connected)

Thanks ,

ukBaz commented 1 year ago
  1. Security

there is the ability to set more secure flags on characteristics as documented at https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt#n267

They use this in one of the BlueZ example GATT server characteristics: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/test/example-gatt-server#n606

What the difference is between "read", "encrypt-read", "encrypt-authenticated-read", and "secure-read" is not clear to me as I haven't found this documented anywhere. I think it matches up with the four security levels BLE has: Level 1: No Security (No authentication and no encryption) Level 2: Unauthenticated pairing with encryption Level 3: Authenticated pairing with encryption Level 4: Authenticated LE Secure Connections pairing with encryption

  1. Broadcast Data

This is like a BLE beacon does? They do it by putting data in the advertisement. This is either in the "service data" or "manufacturing data". You can do this with BlueZ by setting one of those values in your advert. Couple of things to watch out for. BlueZ stops advertising once a connection is made. If you want to change the value in the advert you have to stop the advertisement, change the value, and then start the advert again. By the very nature of this being broadcast data, it does not encrypt your data. You would have to encrypt the values yourself.

David-Afik commented 1 year ago

OK you gave me some things to think about , I will see what is better solution for me and what I need

Thanks!