mjbrown / bgapi

A python package for communicating with BlueGiga modules through their proprietary API.
39 stars 25 forks source link

Using python bgapi library and reading client data #45

Closed mrzaheri closed 6 years ago

mrzaheri commented 6 years ago

Hello,

I am using the bgapi module in python 3.7 and trying to communicate with my sensor.

On my sensor I have defined proprietary service for receiving/sending data/commands.

If I send 0x2E to my sensor on handle 17, the sensor sends me the time on handle 14. So If do so every second, the received data should be updated. But I am getting same data every time on the call back function. (I have tried my code with LightBlue and it is working fine - time is getting updated on each response)

Also, I am using the BLED112 dongle (With original FW which was shipped on it)

Here is my code. Am I missing any thing?

`

from future import print_function import time import logging.handlers from bgapi.module import GATTCharacteristic, GATTService, BlueGigaClient

CLIENT_SERIAL = "COM10" WRITE_HANDLE = 17 READ_HANDLE = 14

def read_callback(value): print(value)

if name == "main": logging.basicConfig(filename='myapp.log', level=logging.INFO) logging.info('Started') ble_client = BlueGigaClient(port=CLIENT_SERIAL, baud=115200, timeout=0.1)

ble_client.reset_ble_state()
responses = ble_client.scan_general(timeout=2)
i = 0
for resp in responses:
    resp.get_services()
    for data in resp.adv_payload:
        if data.type_name == 'BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME':
            print("device number {} name: {}".format(i, data.data))
    i = i + 1

input_device = input("Enter the desired device to connect to from the list:")
target = responses[int(input_device)]

connection = ble_client.connect(target=target)

connection.read_by_group_type(group_type=GATTService.PRIMARY_SERVICE_UUID)
for service in connection.get_services():
    connection.find_information(service=service)
    connection.read_by_type(service=service, type=GATTCharacteristic.CHARACTERISTIC_UUID)

# assign the call back function:
connection.assign_attrclient_value_callback(handle=READ_HANDLE, callback=read_callback)

while 1:
    connection.write_by_handle(WRITE_HANDLE, b'\x2e')
    time.sleep(1)
    connection.read_long_by_handle(READ_HANDLE)

`

pckbls commented 6 years ago

Hi there,

could you set the debug level to logging.DEBUG and attach the produced log? That way we can see if the remote device actually sends new data on every read request.

Best regards, Patrick

mjbrown commented 6 years ago

Also, perhaps the data is supposed to come via indication or notification, and you don't have the code which writes the client characteristic configuration to enable them. If this is the problem, you won't see anything in the log either, because you haven't subscribed. I think Light blue does it automatically.

mrzaheri commented 6 years ago

Hi there,

could you set the debug level to logging.DEBUG and attach the produced log? That way we can see if the remote device actually sends new data on every read request.

Best regards, Patrick

Attached find the logged debug level data. myapp.log

Thanks for your help in advance.

mrzaheri commented 6 years ago

notification

Good point. On LightBlue I listen to notification and get the data. In My PC, I am getting the first data but not the rest.

I updated my code to subscribe for the notification (See the code below) but I am getting an error:

Traceback (most recent call last): File "C:/Users/projects/python-fitparse-master/ble_forum.py", line 39, in connection.read_by_handle(characteristic.value_handle) File "C:\Program Files\Python37\lib\site-packages\bgapi\module.py", line 286, in wrapper return fn(self, *args, **kwargs) File "C:\Program Files\Python37\lib\site-packages\bgapi\module.py", line 414, in read_by_handle self._api.ble_cmd_attclient_read_by_handle(self.handle, handle) File "C:\Program Files\Python37\lib\contextlib.py", line 119, in exit next(self.gen) File "C:\Program Files\Python37\lib\site-packages\bgapi\module.py", line 259, in procedure_call raise RemoteError(handle.result) bgapi.module.RemoteError: 0x0402: Read Not Permitted

Here is my new code:

`

import time import logging.handlers from bgapi.module import GATTCharacteristic, GATTService, BlueGigaClient

CLIENT_SERIAL = "COM10" WRITE_HANDLE = 17 READ_HANDLE = 14

def read_callback(value): print(value)

if name == "main": logging.basicConfig(filename='myapp.log', level=logging.DEBUG) logging.info('Started') ble_client = BlueGigaClient(port=CLIENT_SERIAL, baud=115200, timeout=0.1)

ble_client.reset_ble_state()
responses = ble_client.scan_general(timeout=2)
i = 0
for resp in responses:
    resp.get_services()
    for data in resp.adv_payload:
        if data.type_name == 'BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME':
            print("device number {} name: {}".format(i, data.data))
    i = i + 1

input_device = input("Enter the desired device to connect to from the list:")
target = responses[int(input_device)]

connection = ble_client.connect(target=target)

connection.read_by_group_type(group_type=GATTService.PRIMARY_SERVICE_UUID)
for service in connection.get_services():
    connection.find_information(service=service)
    connection.read_by_type(service=service, type=GATTCharacteristic.CHARACTERISTIC_UUID)

for characteristic in connection.get_characteristics():
    connection.read_by_handle(characteristic.value_handle)
    if characteristic.has_notify() or characteristic.has_indicate():
        connection.characteristic_subscription(characteristic,
                                               characteristic.has_indicate(),
                                               characteristic.has_notify() and not characteristic.has_indicate())

# assign the call back function:
connection.assign_attrclient_value_callback(handle=READ_HANDLE, callback=read_callback)

while 1:
    connection.write_by_handle(WRITE_HANDLE, b'\x2e')
    time.sleep(1)
    connection.read_long_by_handle(READ_HANDLE)

`

mrzaheri commented 6 years ago

automatically

Any comment on my code? I am stock and I really appreciate your help.

In a nut shell, I am trying to subscribe for the notification using the following piece of code:

` for characteristic in connection.get_characteristics(): connection.read_by_handle(characteristic.value_handle) if characteristic.has_notify() or characteristic.has_indicate(): connection.characteristic_subscription(characteristic, characteristic.has_indicate(), characteristic.has_notify() and not characteristic.has_indicate())

`

But I am getting an error: Traceback (most recent call last): File "C:/Users/projects/python-fitparse-master/ble_forum.py", line 39, in connection.read_by_handle(characteristic.value_handle) File "C:\Program Files\Python37\lib\site-packages\bgapi\module.py", line 286, in wrapper return fn(self, *args, **kwargs) File "C:\Program Files\Python37\lib\site-packages\bgapi\module.py", line 414, in read_by_handle self._api.ble_cmd_attclient_read_by_handle(self.handle, handle) File "C:\Program Files\Python37\lib\contextlib.py", line 119, in exit next(self.gen) File "C:\Program Files\Python37\lib\site-packages\bgapi\module.py", line 259, in procedure_call raise RemoteError(handle.result) bgapi.module.RemoteError: 0x0402: Read Not Permitted

Am I missing any thing?

mjbrown commented 6 years ago

You likely don't have "read" enabled in your firmware for every handle. Each attribute handle has many flags: read/write/indicate/notify/write_wo_response/authenticated_write/authenticated_read and more. Just as you must check for "has_notify" and "has_indicate" you also need to check for read enabled. Just because you can receive notifications and indications, doesn't mean you can read the value.

There isn't an api function for this, because usually you aren't doing a blanket read of every characteristic on the device. Usually you would check the characteristic UUID to target the specific characteristic you want to read, with a priori knowledge that the characteristic is readable.

More succinctly, you can't "read_by_handle" every characteristic as you have done in your code. You must selectively read_by_handle, or just skip that line altogether.

mrzaheri commented 6 years ago

You likely don't have "read" enabled in your firmware for every handle. Each attribute handle has many flags: read/write/indicate/notify/write_wo_response/authenticated_write/authenticated_read and more. Just as you must check for "has_notify" and "has_indicate" you also need to check for read enabled. Just because you can receive notifications and indications, doesn't mean you can read the value.

There isn't an api function for this, because usually you aren't doing a blanket read of every characteristic on the device. Usually you would check the characteristic UUID to target the specific characteristic you want to read, with a priori knowledge that the characteristic is readable.

More succinctly, you can't "read_by_handle" every characteristic as you have done in your code. You must selectively read_by_handle, or just skip that line altogether.

Thanks, That was a good hint.

I changed the code to: ` for characteristic in connection.get_characteristics(): if characteristic.is_readable(): connection.read_by_handle(characteristic.value_handle)

    if characteristic.has_notify() or characteristic.has_indicate():
        characteristic_subscription(connection, characteristic.has_indicate(),
                                               characteristic.has_notify() and not characteristic.has_indicate())

`

Now it is working. As you can see, I had to send the command to the device to subscribe for notification.

' def characteristic_subscription(connection, indicate=True, notify=True, timeout=1): config = struct.pack('BB', (2 if indicate else 0) + (1 if notify else 0), 0) with connection.procedure_call(PROCEDURE, timeout): connection.write_by_handle(NOTIFICATION_HANDLE, config, timeout=timeout) '

Now it is working.