mjbrown/bgapi

Using python bgapi library and reading client data

Closed this issue · 7 comments

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)

`

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

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.

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.

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)

`

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?

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.

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.