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
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.
-
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?
-
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
-
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_objthat 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_EVENTcould 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!
-
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.

-
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.
-
@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.
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login