Notify battery level without active subscription

Hello,

we are developing a device which implements BLE hid keyboard. The code is based on the hid keyboard example in the NRF Connect SDK.

The example uses battery service (bas.h and bas.c) to notify client about battery level percentage.

The problem is that the battery level is transmitted only when the connection is established. Further calls of bas_notify() from the example do not update the percentage. Obviously Android phone which I use for tests does not subscribe for this notification. The notifications are only received if I actively enable them in the NRF Connect App.

I added configuration parameter `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n` to the prj.conf. Unfortunately this did not solve the problem. After debugging a while I found that the notification process exits on these lines in gatt.c:

static uint8_t notify_cb(const struct bt_gatt_attr *attr, uint16_t handle,
 void *user_data)
{
  ...
  ...

 /* Notify all peers configured */
 for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
     struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
     struct bt_conn *conn;
     int err;
     /* Check if config value matches data type since consolidated
     * value may be for a different peer.
     */
     if (cfg->value != data->type) { // this condition is true in my case. cfg->value == 0, data->type == 1
         continue;
     }
 ...

So for some reason ccc.cfg array contains invalid data.

I have only one connection and the maximum number of connection is configured to be 1.

For now the workaround is to pass the connection to the bt_bas_set_battery_level() function and call bt_gatt_notify() with the known connection, so that gatt_notify is called directly:

int bt_bas_set_battery_level(uint8_t level, struct bt_conn *conn)
{
 int rc;

 if (level > 100U) {
 return -EINVAL;
 }

 battery_level = level;

 rc = bt_gatt_notify(conn, &bas.attrs[1], &level, sizeof(level));

 if (IS_ENABLED(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT)) {
 bt_bas_bls_set_battery_level(level);
 }

 return rc == -ENOTCONN ? 0 : rc;
}

This works but changing the SDK files is not the best idea.

The SDK version is 2.9.1. The question is what I'm doing wrong Slight smile

  • Thank you for the constructive response. Maybe it also makes sense to modify Zephyr API, because using only k_config is not that flexible because it changes behavior for all kinds of notifications. Imagine some sensor, for example temperature sensor. The client can decide whether it receives notifications periodically or it actively reads the temperature characteristic. But the low battery notification could be done without subscription because it triggers not that often and it is critical.

    So instead of configuration variable it can be a parameter in gatt notify* functions, whether to honor the subscription or not. Then the server can decide on each notification individually.

    Do you think this is worth bringing up for discussion in the Zephyr community or is this proposal not in line with how it is done in Bluetooth? Because I am quite new to Bluetooth development.

  • Hi,

    Honestly I do not see a significant need for this feature, I can only think of having seend discussions about this a handfull of times over qutie a few years. As mentionned, this is in no way a normal thing to do, and there is likely another issue in your use case that shoudl be adressed in a different way. So instead of focusing on this I would recomend backtracking a bit to understand how the devices end up in a state where notifications are no longer enabled in the CCCD for some reason. Is the database locally on the nRF somehow not persisstent, so tha teven if a bonded peer has enabled notifications, this is not remembered for the next connection, or is something else not going as planned? Or is the state lost somewhere else?

  • it's not lost. If I subscribe in the NRF Connect App, the notifications are coming through also after reconnect. So it looks like in the nRF stack everything is fine.

    The problem is that Android manages keyboard connection natively. You don't need an app for that. And it seems that it does not subscribe automatically, it only reads the value once upon connect. So if you stay connected for a long time, which is normal for a keyboard, you don't see the actual battery level.

  • Then that is an issue that needs to be fixed on the android side, everything else will be a hack.

  • disagree :), because the specification allows this scenario, but Zephyr API does not implement it. But I feel that this will be a tough discussion if I bring it up to Zephyr community. We will stay with the hack for now.

Related