Callback when BLE client subscribes to notify characteristic?
-
Is it possible to get a callback when a BLE client subscribes to a notify characteristic?
I tried this:
self.my_char = self.service.characteristic(uuid=Ble.MY_CHAR_UUID, properties=Bluetooth.PROP_NOTIFY, value=0x1234) self.my_char.callback(trigger=Bluetooth.CHAR_NOTIFY_EVENT, handler=self.my_char_handler)
No callback.
I would expect that this API would allow me to start/stop resource-consuming peripherals only when needed.
Thoughts?
-
@semireg
Thank a lot, looks great. Good call on catching the descriptor write in modbt.cWe'll review it and it should make into the next release.
-
I've created a pull request at https://github.com/pycom/pycom-micropython-sigfox/pull/121. Let me know if a github issue should be created for this, too.
-
I've got this working to my satisfaction. I'll clean up the code and submit a pull request. I don't fully understand how MP_DEFINE_CONST_FUN_OBJ_X works... I'll have to brush up on http://micropython-dev-docs.readthedocs.io/en/latest/adding-module.html and make sure my changes are sane.
-
Digging through https://github.com/espressif/esp-idf/blob/master/components/bt/bluedroid/api/include/esp_gatts_api.h led me to https://github.com/pycom/pycom-micropython-sigfox/blob/master/esp32/mods/modbt.c - from here, lines 551-580 are of interest.
if (attr_obj->is_char) { // characteristic bt_gatts_char_obj_t *char_obj = (bt_gatts_char_obj_t *)attr_obj; char_obj->events |= MOD_BT_GATTS_WRITE_EVT; if (char_obj->trigger & MOD_BT_GATTS_WRITE_EVT) { mp_irq_queue_interrupt(gatts_char_callback_handler, char_obj); } } else { // descriptor if (attr_obj->uuid.len == ESP_UUID_LEN_16 && attr_obj->uuid.uuid.uuid16 == GATT_UUID_CHAR_CLIENT_CONFIG) { uint16_t value = param->write.value[1] << 8 | param->write.value[0]; if (value == 0x0001) { // notifications enabled bt_gatts_char_obj_t *char_obj = (bt_gatts_char_obj_t *)attr_obj->parent; // the size of value[] needs to be less than MTU size esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, char_obj->attr_obj.handle, char_obj->attr_obj.value_len, char_obj->attr_obj.value, false); } } }
There are a few ways to implement this feature. Enabling notifications requires the client to write to the server's characteristic descriptor. This comes in as a ESP_GATTS_WRITE_EVT and gets translated to an MP_QSTR_CHAR_WRITE_EVENT (presumably, Bluetooth.CHAR_WRITE_EVENT). This then triggers a callback via
mp_irq_queue_interrupt(gatts_char_callback_handler, char_obj);
Descriptors are not exposed in the python interface so passing along the
attr_obj
that represents the descriptor isn't a good option.One solution would be to expose the descriptor notification value on the characteristic. That way,
Bluetooth.CHAR_NOTIFY_EVENT
could be reused for the server. If that's not possible because of a 1:1 relationship in thebt_locals_dict_table[]
, then a new constant could be defined such as CHAR_SUBSCRIBE_EVENT. Again, the actual values of the characteristic descriptor should be accessible on the characteristic itself.Lastly, the esp-idf GATT Server Example Walkthrough features a code snippet that seems more complete, in that it handles notifications and indications (write with and without response). I'm pasting that here for easy reference.
case ESP_GATTS_WRITE_EVT: { ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); if (!param->write.is_prep){ ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len); if (gl_profile_tab[PROFILE_B_APP_ID].descr_handle == param->write.handle && param->write.len == 2){ uint16_t descr_value= param->write.value[1]<<8 | param->write.value[0]; if (descr_value == 0x0001){ if (b_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){ ESP_LOGI(GATTS_TAG, "notify enable"); uint8_t notify_data[15]; for (int i = 0; i < sizeof(notify_data); ++i) { notify_data[i] = i%0xff; } //the size of notify_data[] need less than MTU size esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_B_APP_ID].char_handle, sizeof(notify_data), notify_data, false); } }else if (descr_value == 0x0002){ if (b_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){ ESP_LOGI(GATTS_TAG, "indicate enable"); uint8_t indicate_data[15]; for (int i = 0; i < sizeof(indicate_data); ++i) { indicate_data[i] = i % 0xff; } //the size of indicate_data[] need less than MTU size esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_B_APP_ID].char_handle, sizeof(indicate_data), indicate_data, true); } } else if (descr_value == 0x0000){ ESP_LOGI(GATTS_TAG, "notify/indicate disable "); }else{ ESP_LOGE(GATTS_TAG, "unknown value"); } } } example_write_event_env(gatts_if, &a_prepare_write_env, param); break; }
@jmarcelino, thanks for the replies. Where is a good place to learn about how /esp32/build/WIPY/release/genhdr/qstr/mods__modbt.c.qstr is used? Is this the resource files to be edited or are these files populated automatically from scanning the source? Thank you!
-
You would start with modbt.c as you found, that's the MicroPython interface to the ESP-IDF SDK.
The actual Bluetooth C interface would be written against the ESP32 ESP-IDF Bluetooth API and specifically the GATTS part but I'm not sure it gives you that level of access - it's a high level GATT server view you don't see the CCCD etc - so you might have to add it to ESP-IDF using the lower APIs.
That source is also open but a more appropriate forum for questions around that would be esp32.com
-
I see that now in the source code at https://github.com/pycom/pycom-micropython-sigfox/blob/0411a97a97b1061c7931ab4a4de981af3c004c7e/esp32/mods/modbt.c
#define MOD_BT_GATTC_ADV_EVT (0x0001) #define MOD_BT_GATTS_CONN_EVT (0x0002) #define MOD_BT_GATTS_DISCONN_EVT (0x0004) #define MOD_BT_GATTS_READ_EVT (0x0008) #define MOD_BT_GATTS_WRITE_EVT (0x0010) #define MOD_BT_GATTC_NOTIFY_EVT (0x0020) #define MOD_BT_GATTC_INDICATE_EVT (0x0040)
I've worked quite a bit with BLE but I'm new to the pycom-micropython-sigfox codebase. Does anyone have any pointers on where I could start to begin adding this capability?
-
@semireg
That's not available at the moment. What you get with CHAR_NOTIFY_EVENT is a notification for the client that the value has been updated.We are planning work on BLE interface later this quarter, I'll add that as a feature request.