How can you add one CCC descriptor for every notification characteristic in a service?

Hello,

I am implementing the Physical Activity Monitor Service which looks like this:


/* Physical Activity Monitor Service Declaration */
BT_GATT_SERVICE_DEFINE(pam_svc,
 BT_GATT_PRIMARY_SERVICE(BT_UUID_PAMS),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_PHY_AMF, BT_GATT_CHRC_READ,
 BT_GATT_PERM_READ, read_amf, NULL, NULL),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_GEN_AID, BT_GATT_CHRC_NOTIFY,
 BT_GATT_PERM_NONE, NULL, NULL, NULL), 
 BT_GATT_CCC(pamc_ccc_cfg_changed, BT_GATT_PERM_WRITE | BT_GATT_PERM_READ),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_GEN_ASD, BT_GATT_CHRC_INDICATE,
 BT_GATT_PERM_NONE, NULL, NULL, NULL), //TODO: Indication callback
 BT_GATT_CCC(pamc_ccc_cfg_changed, BT_GATT_PERM_WRITE | BT_GATT_PERM_READ),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_CR_AID, BT_GATT_CHRC_NOTIFY,
 BT_GATT_PERM_NONE, NULL, NULL, NULL), 
 BT_GATT_CCC(pamc_ccc_cfg_changed, BT_GATT_PERM_WRITE | BT_GATT_PERM_READ),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_CR_ASD, BT_GATT_CHRC_INDICATE,
 BT_GATT_PERM_NONE, NULL, NULL, NULL), //TODO: Indication callback
 BT_GATT_CCC(pamc_ccc_cfg_changed, BT_GATT_PERM_WRITE | BT_GATT_PERM_READ),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SLP_AID, BT_GATT_CHRC_NOTIFY,
 BT_GATT_PERM_NONE, NULL, NULL, NULL), 
 BT_GATT_CCC(pamc_ccc_cfg_changed, BT_GATT_PERM_WRITE | BT_GATT_PERM_READ),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SC_ASD, BT_GATT_CHRC_INDICATE,
 BT_GATT_PERM_NONE, NULL, NULL, NULL), //TODO: Indication callback
 BT_GATT_CCC(pamc_ccc_cfg_changed, BT_GATT_PERM_WRITE | BT_GATT_PERM_READ),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SLP_ASD, BT_GATT_CHRC_INDICATE,
 BT_GATT_PERM_NONE, NULL, NULL, NULL), //TODO: Indication callback
 BT_GATT_CCC(pamc_ccc_cfg_changed, BT_GATT_PERM_WRITE | BT_GATT_PERM_READ),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_PHY_AMCP, BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE,
 PAMS_GATT_PERM_DEFAULT & GATT_PERM_WRITE_MASK,
 NULL, ctrl_point_write, NULL), //TODO: Test control point write callback
 BT_GATT_CCC(pamc_ccc_cfg_changed, BT_GATT_PERM_WRITE | BT_GATT_PERM_READ),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_ACS, BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_READ,
 BT_GATT_PERM_NONE, read_acs, NULL, NULL), //TODO: Indication callback
 BT_GATT_CCC(pamc_ccc_cfg_changed, BT_GATT_PERM_WRITE | BT_GATT_PERM_READ),
 BT_GATT_CHARACTERISTIC(BT_UUID_GATT_PHY_ASDESC, BT_GATT_CHRC_INDICATE,
 BT_GATT_PERM_NONE, NULL, NULL, NULL), //TODO: Indication callback
 BT_GATT_CCC(pamc_ccc_cfg_changed, BT_GATT_PERM_WRITE | BT_GATT_PERM_READ),
}

Every notify or indicate characteristic needs a CCC according to The PAMS spec, but when I try to do this, I need to match every characteristic to its associated callback. Right now, using the HRS example, it simply iterates over every registered callback with a the notify boolean:

static void pams_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
	ARG_UNUSED(attr);

	struct bt_pams_cb *listener;

	bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);

	LOG_INF("PAMS notifications %s", notif_enabled ? "enabled" : "disabled");

	SYS_SLIST_FOR_EACH_CONTAINER(&pams_cbs, listener, _node) {
		if (listener->ntf_changed) {
			listener->ntf_changed(notif_enabled); 
		}
	}
}

I am now trying to match the GATT characteristic with the correct `_node` in order to turn on notifications on a per-characteristic basis as the spec says. 

However, the attribute passed into the ccc function is just the UUID of the CCC function. How can I know which function (below) to pass it to?

static void pams_gen_aid_ntf_changed(bool enabled)
{
    pams_gen_aid_ntf_en = enabled;
    
    LOG_INF("PAMS GEN AID notification status changed: %s\n",
    enabled ? "enabled" : "disabled");
}

static struct bt_pams_cb pams_gen_aid_cb = {
    .ntf_changed = pams_gen_aid_ntf_changed,
};

static void pams_cr_aid_ntf_changed(bool enabled)
{
    pams_cr_aid_ntf_en = enabled;

    LOG_INF("PAMS CR AID notification status changed: %s\n",
    enabled ? "enabled" : "disabled");
}

static struct bt_pams_cb pams_cf_aid_cb = {
    .ntf_changed = pams_cr_aid_ntf_changed,
};

static void pams_slp_aid_ntf_changed(bool enabled)
{
    pams_slp_aid_ntf_en = enabled;

    LOG_INF("PAMS SLEEP AID notification status changed: %s\n",
    enabled ? "enabled" : "disabled");
}

static struct bt_pams_cb pams_slp_aid_cb = {
    .ntf_changed = pams_slp_aid_ntf_changed,
};

static void pams_ctrl_point_ntf_changed(bool enabled)
{
    pams_ctrl_point_ntf_en = enabled;

    LOG_INF("PAMS CTRL Point notification status changed: %s\n",
    enabled ? "enabled" : "disabled");
}

Do I have to make a separate CCC_changed function for every single characteristic?

Is there a way I can return a node of the desired callback and the attribute (uuid for example) of the clicked "notify" in the nrfConnect app? I am a bit stuck as a lot of the macros have less than ideal documentation. 

Thank you.

Parents
  • Hi,

    Right, so your callback function pamc_ccc_cfg_changed takes as the first argument a pointer to a bt_gatt_attr structure on which you can use bt_gatt_attr_get_handle() to get the handle. But that is perhaps not what you are looking for?

    I must admit I get a bit confused when you refer to ctrl_point_write() from HRS, as from what I can tell that one is used for writing to the characteristic, and so it uses the write callback, while what you are looking to do is implement support for indications, which uses the cfg_changed callback.

    Maybe a look at Bluetooth Low Energy Fundamentals, Lesson 4, Exercise 2, which gives a walk-through of adding notification and indication support, provides enough pointers to callbacks and structures that it will clear things up.

    Regards,
    Terje

  • You are absolutely right, I copied the wrong function (this heat wave here was getting to me apparently). I have edited the post as it was indeed not really easy to know what I mean with that mistake. (I have already done the fundamentals, and it doesn't deal with multiple characteristics with different notification callbacks in the same service)

    So now as it is:

    - Every time I click to enable a notification in the nrfConnect app, every characteristic that has the callback registered (the ntf_changed callback member of the struct), in this case, pams_slp_aid_cb, pams_gen_aid_cb, and pams_cr_aid_cb get looped by SYS_SLIST_FOR_EACH_CONTAINER so every time the notification status is changed on one, it propagates through them all. In short, enabling notifications for one characteristic enables notifications for 3 characteristics at the same time.

    - Right now, the function pams_ccc_cfg_changed only knows the CCC attribute that it was called from, and the value saying "change notification". It doesn't know which characteristic it originated from to know which callback to call.

    - I would like to make it so that 1 characteristic configuration change (in this case, enabling notification) propagates ONLY to the characteristic on which it was enabled (maybe, I could use the handle for this?)

    So my question is more: How can the pointer *listener know which characteristic (SLP_AID, GEN_AID, CR_AID) the notification was enabled on? 

  • Hi,

    Right, thanks! Yes, I heard about the heat wave. I guess our 12°C and light rain are good in the grand scheme of things.

    So, from what I understand (although I am unable to completely wrap my head around it), the implementation for HRS (and other similar services) is just a convoluted way to allow for multiple service instances on the same device.

    I.e., if there had been different types of characteristics, they would have had one set of callbacks each. I.e. separate callback functions for different characteristics belonging to the same service. Does that make sense?

    Regards,
    Terje

Reply
  • Hi,

    Right, thanks! Yes, I heard about the heat wave. I guess our 12°C and light rain are good in the grand scheme of things.

    So, from what I understand (although I am unable to completely wrap my head around it), the implementation for HRS (and other similar services) is just a convoluted way to allow for multiple service instances on the same device.

    I.e., if there had been different types of characteristics, they would have had one set of callbacks each. I.e. separate callback functions for different characteristics belonging to the same service. Does that make sense?

    Regards,
    Terje

Children
No Data
Related