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,

    No callback.

    I would expect that this API would allow me to start/stop resource-consuming peripherals only when needed.


  • @semireg
    Thank a lot, looks great. Good call on catching the descriptor write in modbt.c

    We'll review it and it should make into the next release.

  • I've created a pull request at 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 and make sure my changes are sane.


  • Digging through led me to - 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 the bt_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,  
                                                     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,  
                                                     indicate_data, true);
                 else if (descr_value == 0x0000){
                     ESP_LOGI(GATTS_TAG, "notify/indicate disable ");
                     ESP_LOGE(GATTS_TAG, "unknown value");
        example_write_event_env(gatts_if, &a_prepare_write_env, param); 


    @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

  • I see that now in the source code at

    #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.


Pycom on Twitter