pycom / pycom-micropython-sigfox

A fork of MicroPython with the ESP32 port customized to run on Pycom's IoT multi-network modules.
MIT License
200 stars 166 forks source link

BLE CHAR_NOTIFY_EVENT callback with same value (but must be different) #449

Open herrfrei opened 4 years ago

herrfrei commented 4 years ago

Board: WiPy 2.0 Firmware: 1.20.2.rc7

I'm trying to read data from a battery management system. This has a TX characteristic where one can write commands to it and a RX where the client gets the answer. The protocol starts with 0xDD and ends a message with 0x77. The info message I want to read is sent in two sub-packages and I get two callback calls. But both contain the second part of the message (ending with 0x77). This occurs all the time when I call the info message command.

Callback is registered with:

char.callback(trigger=Bluetooth.CHAR_NOTIFY_EVENT, handler=self._rx_callback)

And the callback itself is:

def _rx_callback(self, char=None):
    data = char.value()
    print('_rx_callback data={}'.format(hex(data)))

I call a function every 10 seconds that outputs the name and writes the "get info command" to the device:

def print_data(self):
    if self.OD[0x1800][0x2A00]['value']:
        print('Name: %s' % self.OD[0x1800][0x2A00]['value'])
    self.bms_get_base_info()

And here is the output I get:

Name: DL1907xxxx<\r><\n>
_rx_callback data=000019130304010B68FCE277<\r><\n>
_rx_callback data=000019130304010B68FCE277<\r><\n>

Thank you!

herrfrei commented 4 years ago

Here are some further investigations:

The received data are copied to a char_obj's value field and the pointer to this object is queued in modbt.c:

case ESP_GATTC_NOTIFY_EVT: {
        bt_char_obj_t *char_obj;
        char_obj = find_gattc_char (p_data->notify.conn_id, p_data->notify.handle);
        if (char_obj != NULL) {
            // copy the new value into the characteristic
            memcpy(&char_obj->value, p_data->notify.value, p_data->notify.value_len);
            char_obj->value_len = p_data->notify.value_len;

            // register the event
            if (p_data->notify.is_notify) {
                char_obj->events |= MOD_BT_GATTC_NOTIFY_EVT;
            } else {
                char_obj->events |= MOD_BT_GATTC_INDICATE_EVT;
            }
            if ((char_obj->trigger & MOD_BT_GATTC_NOTIFY_EVT) || (char_obj->trigger & MOD_BT_GATTC_INDICATE_EVT)) {
                mp_irq_queue_interrupt_non_ISR(gattc_char_callback_handler, char_obj);
            }
        }
        break;

mp_irq_queue_interrupt_non_ISR() calls FreeRTOS' xQueueSend() that copies the pointer contents into the queue. But the the value inside the pointing object is not copied, it references the same memory. So multiple fast notifications lead to the problems I observed.

Right now I have no idea to solve this problem.

geza-pycom commented 4 years ago

I think the same method would solve this problem what is used in gatts_event_handler() under ESP_GATTS_WRITE_EVT event (Lines 903-911). There a copy of the incoming value is created and that is passed to the registered callback. However it may also happen that the value of the characteristic is overwritten by a next, fast, write request, but the callback for sure will get the value which has triggered it.

herrfrei commented 4 years ago

Many thanks for pointing in the right direction! It worked and I made a pull request: 453.