ble.gattc_read() can read out invalid values

All ESP32 boards running MicroPython.
Target audience: MicroPython users with an ESP32 board.
Post Reply
henry_jian
Posts: 6
Joined: Thu Feb 04, 2021 1:35 am

ble.gattc_read() can read out invalid values

Post by henry_jian » Fri Feb 19, 2021 8:03 am

Hi micropython experts:

I modified the micropython bluetooth Demo codes; try to connect ESP32 and Micro:bit by bluetooth; and read RX charateristic data which Micro:bit send to ESP32.
PS: ESP32 as centrol and Micro:bit as peripheral .

Now, ESP32 can connect Micro:bit successfully, but failed when I use ble.gattc_read() to get data from RX characteristic which micro:bit send to ESP32.

The IRQ about GATTC_READ as following:
elif event == _IRQ_GATTC_READ_RESULT:
# A gattc_read() has completed.
conn_handle, value_handle, char_data = data
if conn_handle == self._conn_handle and value_handle == self._rx_handle:
print("received data is : ", conn_handle,value_handle,bytes(char_data))

The log it print as: received data is : 0 39 b'\x00'.
And I checked the rx charaterisctic hanlder and conn_handle are all correct.

But when I use "nRF Connect" on cell phone to connect micro:bit, I can see the data been received through RX charateristic. As following:
Image

That mean the Micro:bit send data to RX successfully, but esp32 can't read it correctlly. I really don't know why ?

Please help me, Thank you.

I paste the whole ESP32 source codes as following:

Code: Select all

import bluetooth
import random
import struct
import time
import micropython

from ble_advertising import decode_services, decode_name

from micropython import const

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)
_IRQ_GATTS_READ_REQUEST = const(4)
_IRQ_SCAN_RESULT = const(5)
_IRQ_SCAN_DONE = const(6)
_IRQ_PERIPHERAL_CONNECT = const(7)
_IRQ_PERIPHERAL_DISCONNECT = const(8)
_IRQ_GATTC_SERVICE_RESULT = const(9)
_IRQ_GATTC_SERVICE_DONE = const(10)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11)
_IRQ_GATTC_CHARACTERISTIC_DONE = const(12)
_IRQ_GATTC_DESCRIPTOR_RESULT = const(13)
_IRQ_GATTC_DESCRIPTOR_DONE = const(14)
_IRQ_GATTC_READ_RESULT = const(15)
_IRQ_GATTC_READ_DONE = const(16)
_IRQ_GATTC_WRITE_DONE = const(17)
_IRQ_GATTC_NOTIFY = const(18)
_IRQ_GATTC_INDICATE = const(19)

_ADV_IND = const(0x00)
_ADV_DIRECT_IND = const(0x01)
_ADV_SCAN_IND = const(0x02)
_ADV_NONCONN_IND = const(0x03)

_UART_SERVICE_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_RX_CHAR_UUID = bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_TX_CHAR_UUID = bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")


class BLESimpleCentral:
    def __init__(self, ble):
        self._ble = ble
        self._ble.active(True)
        self._ble.irq(self._irq)

        self._reset()

    def _reset(self):
        # Cached name and address from a successful scan.
        self._name = None
        self._addr_type = None
        self._addr = None

        # Callbacks for completion of various operations.
        # These reset back to None after being invoked.
        self._scan_callback = None
        self._conn_callback = None
        self._read_callback = None

        # Persistent callback for when new data is notified from the device.
        self._notify_callback = None

        # Connected device.
        self._conn_handle = None
        self._start_handle = None
        self._end_handle = None
        self._tx_handle = None
        self._rx_handle = None

    def _irq(self, event, data):
        if event == _IRQ_SCAN_RESULT:
            addr_type, addr, adv_type, rssi, adv_data = data
            #if adv_type in (_ADV_IND, _ADV_DIRECT_IND) and _UART_SERVICE_UUID in decode_services(
            #    adv_data
            #):
            if adv_type in (_ADV_IND, _ADV_DIRECT_IND):
                # Found a potential device, remember it and stop scanning.
                self._addr_type = addr_type
                self._addr = bytes(
                    addr
                )  # Note: addr buffer is owned by caller so need to copy it.
                self._name = decode_name(adv_data) or "?"
                self._ble.gap_scan(None)

        elif event == _IRQ_SCAN_DONE:
            if self._scan_callback:
                if self._addr:
                    # Found a device during the scan (and the scan was explicitly stopped).
                    self._scan_callback(self._addr_type, self._addr, self._name)
                    self._scan_callback = None
                else:
                    # Scan timed out.
                    self._scan_callback(None, None, None)

        elif event == _IRQ_PERIPHERAL_CONNECT:
            # Connect successful.
            conn_handle, addr_type, addr = data
            if addr_type == self._addr_type and addr == self._addr:
                self._conn_handle = conn_handle
                self._ble.gattc_discover_services(self._conn_handle)

        elif event == _IRQ_PERIPHERAL_DISCONNECT:
            # Disconnect (either initiated by us or the remote end).
            conn_handle, _, _ = data
            if conn_handle == self._conn_handle:
                # If it was initiated by us, it'll already be reset.
                self._reset()

        elif event == _IRQ_GATTC_SERVICE_RESULT:
            # Connected device returned a service.
            conn_handle, start_handle, end_handle, uuid = data
            print("service", data)
            if conn_handle == self._conn_handle and uuid == _UART_SERVICE_UUID:
                self._start_handle, self._end_handle = start_handle, end_handle

        elif event == _IRQ_GATTC_SERVICE_DONE:
            # Service query complete.
            if self._start_handle and self._end_handle:
                self._ble.gattc_discover_characteristics(
                    self._conn_handle, self._start_handle, self._end_handle
                )
            else:
                print("Failed to find uart service.")

        elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT:
            # Connected device returned a characteristic.
            conn_handle, def_handle, value_handle, properties, uuid = data
            if conn_handle == self._conn_handle and uuid == _UART_RX_CHAR_UUID:
                self._rx_handle = value_handle
                print("Adam rx :",value_handle)
            if conn_handle == self._conn_handle and uuid == _UART_TX_CHAR_UUID:
                self._tx_handle = value_handle
                print("Adam tx :", value_handle)

        elif event == _IRQ_GATTC_CHARACTERISTIC_DONE:
            # Characteristic query complete.
            if self._tx_handle is not None and self._rx_handle is not None:
                # We've finished connecting and discovering device, fire the connect callback.
                if self._conn_callback:
                    self._conn_callback()
            else:
                print("Failed to find uart rx characteristic.")

        elif event == _IRQ_GATTC_READ_RESULT:
            # A gattc_read() has completed.
            conn_handle, value_handle, char_data = data
            if conn_handle == self._conn_handle and value_handle == self._rx_handle:
                print("received data is : ", conn_handle,value_handle,bytes(char_data))

        elif event == _IRQ_GATTC_READ_DONE:
            # A gattc_read() has completed.
            # Note: The value_handle will be zero on btstack (but present on NimBLE).
            # Note: Status will be zero on success, implementation-specific value otherwise.
            conn_handle, value_handle, status = data
            if conn_handle == self._conn_handle and value_handle == self._rx_handle:
                print("RX complete   status is ", status)

        elif event == _IRQ_GATTC_WRITE_DONE:
            conn_handle, value_handle, status = data
            print("TX complete")

        elif event == _IRQ_GATTC_NOTIFY:
            conn_handle, value_handle, notify_data = data
            if conn_handle == self._conn_handle and value_handle == self._tx_handle:
                if self._notify_callback:
                    self._notify_callback(notify_data)

    # Returns true if we've successfully connected and discovered characteristics.
    def is_connected(self):
        return (
            self._conn_handle is not None
            and self._tx_handle is not None
            and self._rx_handle is not None
        )

    # Find a device advertising the environmental sensor service.
    def scan(self, callback=None):
        self._addr_type = None
        self._addr = None
        self._scan_callback = callback
        self._ble.gap_scan(2000, 30000, 30000)

    # Connect to the specified device (otherwise use cached address from a scan).
    def connect(self, addr_type=None, addr=None, callback=None):
        self._addr_type = addr_type or self._addr_type
        self._addr = addr or self._addr
        self._conn_callback = callback
        if self._addr_type is None or self._addr is None:
            return False
        self._ble.gap_connect(self._addr_type, self._addr)
        return True

    # Disconnect from current device.
    def disconnect(self):
        if not self._conn_handle:
            return
        self._ble.gap_disconnect(self._conn_handle)
        self._reset()

    #Read data over the UART
    def read(self):
        if not self.is_connected():
            return
        print("start to read data from rx_handle")
        self._ble.gattc_read(self._conn_handle, self._rx_handle)

    # Send data over the UART
    def write(self, v, response=False):
        if not self.is_connected():
            return
        self._ble.gattc_write(self._conn_handle, self._rx_handle, v, 1 if response else 0)

    # Set handler for when data is received over the UART.
    def on_notify(self, callback):
        self._notify_callback = callback


def demo():
    ble = bluetooth.BLE()
    central = BLESimpleCentral(ble)

    not_found = False

    def on_scan(addr_type, addr, name):
        if addr_type is not None:
            print("Found peripheral:", addr_type, addr, name)
            central.connect()
        else:
            nonlocal not_found
            not_found = True
            print("No peripheral found.")

    central.scan(callback=on_scan)

    # Wait for connection...
    while not central.is_connected():
        time.sleep_ms(100)
        if not_found:
            return

    print("Connected")

    def on_rx(v):
        print("RX", v)

    central.on_notify(on_rx)

    with_response = False

    i = 0
    #central.read()
    while central.is_connected():
        try:
            #v = str(i) + "_"
            #print("TX", v)
            #central.write(v, with_response)
            central.read()
            time.sleep_ms(1000)
        except:
            print("TX failed")
        i += 1
        time.sleep_ms(400 if with_response else 30)

    print("Disconnected")


if __name__ == "__main__":
    demo()

henry_jian
Posts: 6
Joined: Thu Feb 04, 2021 1:35 am

Re: ble.gattc_read() can read out invalid values

Post by henry_jian » Sat Feb 20, 2021 3:06 am

Now, I found the UART service Micro:bit provided enalbed the "Indicate" property. I think that mean the client must send back response to Micro:bit when receive data from RX characteristic; else the Micro:bit will always send same data in RX characteristic.

My question is that:
1. Should I use TX characteristic send back response?
2. What value should be send back to MICRO:BIT?

User avatar
jimmo
Posts: 2244
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: ble.gattc_read() can read out invalid values

Post by jimmo » Mon Feb 22, 2021 3:48 am

henry_jian wrote:
Sat Feb 20, 2021 3:06 am
Now, I found the UART service Micro:bit provided enalbed the "Indicate" property. I think that mean the client must send back response to Micro:bit when receive data from RX characteristic; else the Micro:bit will always send same data in RX characteristic.

My question is that:
1. Should I use TX characteristic send back response?
2. What value should be send back to MICRO:BIT?
The way the Nordic UART Service works is that you have a TX characteristic that transmits data from server to client using either gatt notify or gatt indicate, and an RX characteristic that transmits data from client to server using gatt write.

The example in https://github.com/micropython/micropyt ... ipheral.py uses notify for the TX characteristic, but some implementations do use indicate.

The reason for this is the way that GATT works. Server characterstics just have local values that can be read from or written to by a client. When a server writes to a characteristic, it just changes the value locally, a client has to read it before it sees it. That's why notify (and indicate) exist -- they tell a connected client to get a new value.

Indicate and Notify are almost the same, except indicate has an acknowledgement.

henry_jian
Posts: 6
Joined: Thu Feb 04, 2021 1:35 am

Re: ble.gattc_read() can read out invalid values

Post by henry_jian » Wed Feb 24, 2021 6:53 am

Hi jimmo,

Thank you for your reply.

1.Do you mean I should send back response to micro:bit via RX charateristic(_UART_RX_CHAR_UUID = bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")) and read data via TX charateristic(_UART_TX_CHAR_UUID = bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")) on client side (ESP32 side) ?

2.Do you mean I should read data IRQ :

Code: Select all

elif event == _IRQ_GATTC_INDICATE:
        # A server has sent an indicate request.
        conn_handle, value_handle, notify_data = data
         if conn_handle == self._conn_handle and value_handle == self._tx_handle:
         	self._ble.gattc_read(self._conn_handle, self._rx_handle)
3. Is there a example for GATT indicate?

Thank you

User avatar
jimmo
Posts: 2244
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: ble.gattc_read() can read out invalid values

Post by jimmo » Thu Feb 25, 2021 12:19 am

henry_jian wrote:
Wed Feb 24, 2021 6:53 am
1.Do you mean I should send back response to micro:bit via RX charateristic(_UART_RX_CHAR_UUID = bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")) and read data via TX charateristic(_UART_TX_CHAR_UUID = bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")) on client side (ESP32 side) ?
The TX and RX characteristics are named from the perspective of the server. (Typically the server is the peripheral, i.e. the advertiser).

The client:
- Sends data by writing to the RX characteristic. The server will therefore receive a _IRQ_GATTS_WRITE event.
- Receives data by listening for notifications (or indications) on the TX characteristic. It will receive an _IRQ_GATTC_NOTIFY (or _IRQ_GATTC_INDICATE) event.

The server:
- Sends data by notifiying (or indicating) the TX characteristic. The client will then receive _IRQ_GATTC_NOTIFY (or _IRQ_GATTC_INDICATE). In the case of indication, the server will then receive _IRQ_GATTS_INDICATE_DONE.
- Receives data by waiting for writes to the RX characteristic. When the client writes, it will get an _IRQ_GATTS_WRITE event.
henry_jian wrote:
Wed Feb 24, 2021 6:53 am
3. Is there a example for GATT indicate?
I haven't written an example using indicate, but in

https://github.com/micropython/micropyt ... /bluetooth

the ble_simple_central.py and ble_simple_peripheral.py show both sides of the TX/RX for the Nordic Uart Service.

The change would be in the peripheral (server) would be:
- add a _FLAG_INDICATE constant (whcih has the value 0x0020)
- change _FLAG_NOTIFY in the definition to _FLAG_INDICATE
- call ble.gatts_indicate instead of ble.gatts_notify (and write the value beforehand):

Code: Select all

    def send(self, data):
        self._ble.gatts_write(self._handle_tx, data)
        for conn_handle in self._connections:
            self._ble.gatts_indicate(conn_handle, self._handle_tx)
The change on the central would be:

- Handle the _IRQ_GATTC_INDICATE event instead of _IRQ_GATTC_NOTIFY
- (I think that's all)

henry_jian
Posts: 6
Joined: Thu Feb 04, 2021 1:35 am

Re: ble.gattc_read() can read out invalid values

Post by henry_jian » Thu Feb 25, 2021 3:49 am

Hi jimmo,

Firstly, thank you for your replay.

In my experiment, Micro:bit as peripheral (server side), and I'm coding for it with Makecode. I want to let Micro:bit to send sensor data to ESP32 via BLE.
Now, I can use "nRF Connect" APP on my smartphone to connect micro:bit, and read sensor data via Nordic UART Service; just like the snapshot I sent before;and find the property of RX charateristic is indicate. So I think the server side worked well.
Image


And Now, I can use ESP32 as client role to connect MIcro:bit. but can't read any data.

I add a new irq as:

Code: Select all

        elif event == _IRQ_GATTC_INDICATE:
            # A server has sent an indicate request.
            conn_handle, value_handle, notify_data = data
            #if conn_handle == self._conn_handle and value_handle == self._tx_handle:
            if conn_handle == self._conn_handle:
                print("Adam in Indicate, data is: ", notify_data)
But there isn't any data. I really don't know what I need to do more ?

Thank you for your help.

User avatar
jimmo
Posts: 2244
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: ble.gattc_read() can read out invalid values

Post by jimmo » Thu Feb 25, 2021 6:22 am

henry_jian wrote:
Thu Feb 25, 2021 3:49 am
But there isn't any data. I really don't know what I need to do more ?
You probably need to subscribe to indications by writing to the CCCD descriptor for the RX characteristic.

Here's an example from a higher level library I'm working on that makes it easier to use ubluetooth.
https://github.com/jimmo/micropython-li ... nt.py#L381

Tinus
Posts: 6
Joined: Sun Feb 14, 2021 4:53 pm

Re: ble.gattc_read() can read out invalid values

Post by Tinus » Sun Feb 28, 2021 7:20 pm

jimmo wrote:
Thu Feb 25, 2021 6:22 am
Here's an example from a higher level library I'm working on that makes it easier to use ubluetooth.
https://github.com/jimmo/micropython-li ... nt.py#L381
I have been bashing my head on the desk for a few days now trying to get notifications from a ble heart rate sensor.
This will help so much, thank you for your work.

Post Reply