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

Parents
  • Hi,

    I am not sure I understand the issue her, so I have some questions.

    The problem is that the battery level is transmitted only when the connection is established.

    Notifications can only be send when a connection is active. Can you elaborate on what you mean here?

    Obviously Android phone which I use for tests does not subscribe for this notification.

    Why does it not? If the GATT client want notifications from a characteristic, it shoudl enable it in the CCCD. This is the normal behaviour (thoguh as you have seen, it is also legal to sent notifications even though not enabled, though that is not common).

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

    In what way is it invalid? Can you elaborate? Also, can you share the changes you did to the sample and instructions on how to reproduce so I can test this on my end?

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

    It is good that works, but as you say it is not ideal, and it really should not be needed, so we should get to the bottom of this.

Reply
  • Hi,

    I am not sure I understand the issue her, so I have some questions.

    The problem is that the battery level is transmitted only when the connection is established.

    Notifications can only be send when a connection is active. Can you elaborate on what you mean here?

    Obviously Android phone which I use for tests does not subscribe for this notification.

    Why does it not? If the GATT client want notifications from a characteristic, it shoudl enable it in the CCCD. This is the normal behaviour (thoguh as you have seen, it is also legal to sent notifications even though not enabled, though that is not common).

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

    In what way is it invalid? Can you elaborate? Also, can you share the changes you did to the sample and instructions on how to reproduce so I can test this on my end?

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

    It is good that works, but as you say it is not ideal, and it really should not be needed, so we should get to the bottom of this.

Children
  • > The problem is that the battery level is transmitted only when the connection is established. 

    sorry, bad wording. I mean that it is sent once at the moment when the devices get connected after the advertising. If I call bas_notify() later, while the connection is already on for a while, the notification is not sent.

    > Obviously Android phone which I use for tests does not subscribe for this notification.

    I mean it does not do this automatically. But as I mentioned, it is possible to do in the NRF Connect App by clicking on the "three arrows icon". But this is not the use case we are planning.

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

    > In what way is it invalid? Can you elaborate? Also, can you share the changes you did to the sample and instructions on > how to reproduce so I can test this on my end?

    I checked the source code again, this was premature to say that it's invalid :) I guess these lines are exactly the condition that checks whether the client is subscribed or not. I was confused by the field names.

    So the problem is actually that the function notify_cb() in gatt.c does not take into account the value of CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION. Is it possible that you add this in the next version of SDK?

    If you confirm that this is a problem, I think there is not need to prepare an example to reproduce the issue.

    I'm attaching the modified hid.c. There are many other changes (it uses message queue to communicate with the main thread), so it will be difficult to use it directly in the example.

    The relevant change is in the function bas_notify(), specifically I pass the active connection to bt_bas_set_battery_level(), which was modified in tthe SDK folder (the code is in the previous message).

    #include <zephyr/types.h>
    #include <stddef.h>
    #include <string.h>
    #include <errno.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/sys/byteorder.h>
    #include <zephyr/kernel.h>
    #include <zephyr/drivers/gpio.h>
    #include <soc.h>
    #include <assert.h>
    #include <zephyr/spinlock.h>
    
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/bluetooth/hci.h>
    #include <zephyr/bluetooth/conn.h>
    #include <zephyr/bluetooth/uuid.h>
    #include <zephyr/bluetooth/gatt.h>
    
    #include <zephyr/bluetooth/services/bas.h>
    #include <bluetooth/services/hids.h>
    #include <zephyr/bluetooth/services/dis.h>
    
    #include <zephyr/logging/log.h>
    LOG_MODULE_REGISTER(HID_AMPEL, LOG_LEVEL_INF);
    
    
    #include "definitions.h"
    #include "ascii2keyboard.h"
    
    #define DEVICE_NAME     CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
    
    #define BASE_USB_HID_SPEC_VERSION   0x0101
    
    #define OUTPUT_REPORT_MAX_LEN            1
    #define OUTPUT_REPORT_BIT_MASK_CAPS_LOCK 0x02
    #define INPUT_REP_KEYS_REF_ID            0
    #define OUTPUT_REP_KEYS_REF_ID           0
    #define MODIFIER_KEY_POS                 0
    #define SHIFT_KEY_CODE                   0x02
    #define SCAN_CODE_POS                    2
    #define KEYS_MAX_LEN                    (INPUT_REPORT_KEYS_MAX_LEN - \
    					SCAN_CODE_POS)
    
    /* HIDs queue elements. */
    #define HIDS_QUEUE_SIZE 10
    
    /* Note: The configuration below is the same as BOOT mode configuration
     * This simplifies the code as the BOOT mode is the same as REPORT mode.
     * Changing this configuration would require separate implementation of
     * BOOT mode report generation.
     */
    #define KEY_CTRL_CODE_MIN 224 /* Control key codes - required 8 of them */
    #define KEY_CTRL_CODE_MAX 231 /* Control key codes - required 8 of them */
    #define KEY_CODE_MIN      0   /* Normal key codes */
    #define KEY_CODE_MAX      101 /* Normal key codes */
    #define KEY_PRESS_MAX     6   /* Maximum number of non-control keys
    			                   * pressed simultaneously */
    
    /* Number of bytes in key report
     *
     * 1B - control keys
     * 1B - reserved
     * rest - non-control keys
     */
    #define INPUT_REPORT_KEYS_MAX_LEN (1 + 1 + KEY_PRESS_MAX)
    
    const uint8_t shift_key[] = { 225 };
    
    /* Current report map construction requires exactly 8 buttons */
    BUILD_ASSERT((KEY_CTRL_CODE_MAX - KEY_CTRL_CODE_MIN) + 1 == 8);
    
    /* OUT report internal indexes.
     *
     * This is a position in internal report table and is not related to
     * report ID.
     */
    enum {
    	OUTPUT_REP_KEYS_IDX = 0
    };
    
    /* INPUT report internal indexes.
     *
     * This is a position in internal report table and is not related to
     * report ID.
     */
    enum {
    	INPUT_REP_KEYS_IDX = 0
    };
    
    /* Current report status
     */
    static struct ampel_state {
    	uint8_t ctrl_keys_state; /* Current keys state */
    	uint8_t keys_state[KEY_PRESS_MAX];
    } hid_ampel_state;
    
    #if CONFIG_NFC_OOB_PAIRING
    static struct k_work adv_work;
    #endif
    
    static struct k_work advertising_work;
    static struct k_work pairing_work;
    
    struct pairing_data_mitm {
    	struct bt_conn *conn;
    	unsigned int passkey;
    };
    
    K_MSGQ_DEFINE(mitm_queue,
        sizeof(struct pairing_data_mitm),
        CONFIG_BT_HIDS_MAX_CLIENT_COUNT,
        4);
    
    /* HIDS instance. */
    BT_HIDS_DEF(hids_obj,
        OUTPUT_REPORT_MAX_LEN,
        INPUT_REPORT_KEYS_MAX_LEN);
    
    const struct bt_data ad[] = {
        BT_DATA_BYTES(BT_DATA_GAP_APPEARANCE,
                    (CONFIG_BT_DEVICE_APPEARANCE >> 0) & 0xff,
                    (CONFIG_BT_DEVICE_APPEARANCE >> 8) & 0xff),
        BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
        BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_HIDS_VAL),
                            BT_UUID_16_ENCODE(BT_UUID_BAS_VAL)),
    };
    
    const struct bt_data sd[] = {
    	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    };
    
    struct conn_mode {
    	struct bt_conn *conn;
    	bool in_boot_mode;
    } conn_mode[CONFIG_BT_HIDS_MAX_CLIENT_COUNT];
    
    
    static int bond_count;
    static volatile bool is_adv;
    static volatile bool is_connected = false;
    
    // If device is not connected in a reasonable time, it should go to sleep
    // User must press a button to start advertising again
    static void advertising_timeout() {
    	printk("advertising timeout\n");
    	uint16_t action = MSG_ADVERTISING_TIMEOUT;
    	k_msgq_put(&main_msgq, &action, K_NO_WAIT);
    }
    K_TIMER_DEFINE(advertising_timeout_timer, advertising_timeout, NULL);
    
    void pairing_process(struct k_work *work);
    void connected(struct bt_conn *conn, uint8_t err);
    void disconnected(struct bt_conn *conn, uint8_t reason);
    void advertising_start();
    
    static void hid_init();
    static void pairing_complete(struct bt_conn *conn, bool bonded);
    static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason);
    void security_changed(struct bt_conn *conn, bt_security_t level,
            enum bt_security_err err);
    
    
    static struct bt_conn_auth_cb conn_auth_callbacks = {
    	// .passkey_display = auth_passkey_display,
    	// .passkey_confirm = auth_passkey_confirm,
    	// .cancel = auth_cancel,
    	.passkey_display = NULL,
    	.passkey_confirm = NULL,
    	.cancel = NULL,
    
    	.passkey_entry = NULL,
    
    #if CONFIG_NFC_OOB_PAIRING
    	.oob_data_request = auth_oob_data_request,
    #endif
    };
    
    static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
    	.pairing_complete = pairing_complete,
    	.pairing_failed = pairing_failed
    };
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
    	.connected = connected,
    	.disconnected = disconnected,
    	.security_changed = security_changed,
    };
    
    static void advertising_start_worker(struct k_work *item)
    {
    	advertising_start();
    }
    
    static void add_bonded_addr_to_filter_list(const struct bt_bond_info *info, void *data)
    {
    	char addr_str[BT_ADDR_LE_STR_LEN];
    
    	bt_le_filter_accept_list_add(&info->addr);
    	bt_addr_le_to_str(&info->addr, addr_str, sizeof(addr_str));
    	printk("Added %s to advertising accept filter list\n", addr_str);
    	bond_count++;
    }
    
    bool hid_ampel_init()
    {
        int err;
    
    	err = bt_conn_auth_cb_register(&conn_auth_callbacks);
    	if (err) {
    		LOG_ERR("Failed to register authorization callbacks. %d\n", err);
    		return 0;
    	}
    
    	err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
    	if (err) {
    		LOG_ERR("Failed to register authorization info callbacks.\n");
    		return 0;
    	}
    
        hid_init();
        k_work_init(&advertising_work, advertising_start_worker);
    
        return 1;
    }
    
    void refresh_bonds_filter(void)
    {
    	bond_count = 0;
    	bt_foreach_bond(BT_ID_DEFAULT, add_bonded_addr_to_filter_list, NULL);
    	printk("Bond count=%d\n", bond_count);
    }
    
    void advertising_start()
    {
        int err;
    
    	k_timer_start(&advertising_timeout_timer, K_MSEC(ADVERTISING_TIMEOUT_MS), K_NO_WAIT);
    
        struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM(
                            BT_LE_ADV_OPT_CONNECTABLE |
                            BT_LE_ADV_OPT_ONE_TIME,
                            BT_GAP_ADV_FAST_INT_MIN_2 * 2,
                            BT_GAP_ADV_FAST_INT_MAX_2 * 2,
                            NULL);
    
    	if (bond_count > 0) {
    		adv_param->options |= BT_LE_ADV_OPT_FILTER_CONN | BT_LE_ADV_OPT_FILTER_SCAN_REQ;
    	}
    
        err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd,
                        ARRAY_SIZE(sd));
        if (err) {
            if (err == -EALREADY) {
                printk("Advertising continued\n");
            } else {
                printk("Advertising failed to start (err %d)\n", err);
    			if (err == -ENOMEM) {
    				printk("ENOMEM\n");
    			}
            }
    
            return;
        }
    
        is_adv = true;
        uint16_t msg = MSG_BLE_ADVERTISING_STARTED;
        k_msgq_put(&main_msgq, &msg, K_NO_WAIT);
        LOG_INF("Advertising successfully started\n");
    }
    
    void advertising_stop()
    {
        int err = bt_le_adv_stop();
        if (err) {
            printk("failed to disable advertising, err %d\n", err);
        }
    
        is_adv = false;
    }
    
    void unBond()
    {
    	bt_unpair(BT_ID_DEFAULT, BT_ADDR_LE_ANY);
    	bt_le_filter_accept_list_clear();
    	refresh_bonds_filter();
    }
    
    void pairing_process(struct k_work *work)
    {
    	int err;
    	struct pairing_data_mitm pairing_data;
    
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	err = k_msgq_peek(&mitm_queue, &pairing_data);
    	if (err) {
    		return;
    	}
    
    	bt_addr_le_to_str(bt_conn_get_dst(pairing_data.conn),
    			  addr, sizeof(addr));
    
    	printk("Passkey for %s: %06u\n", addr, pairing_data.passkey);
    
    	if (IS_ENABLED(CONFIG_SOC_SERIES_NRF54HX) || IS_ENABLED(CONFIG_SOC_SERIES_NRF54LX)) {
    		printk("Press Button 0 to confirm, Button 1 to reject.\n");
    	} else {
    		printk("Press Button 1 to confirm, Button 2 to reject.\n");
    	}
    }
    
    bool hid_ampel_is_advertising(void) {
        return is_adv;
    }
    
    bool hid_ampel_is_connected(void) {
        return is_connected;
    }
    
    void connected(struct bt_conn *conn, uint8_t err)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	if (err) {
    		printk("Failed to connect to %s 0x%02x %s\n", addr, err, bt_hci_err_to_str(err));
    		return;
    	}
    
    	printk("Connectedd %s\n", addr);
    	struct bt_conn_info info;
    	err = bt_conn_get_info(conn, &info);
    	if (err) {
    		printk("dd Failed to get conn info, err=%d\n", err);
    	} else {
    		printk("id=%d, role=%d, sec flags=%d, sec level=%d, type=%d\n", info.id, info.role, info.security.flags, info.security.level, info.type);
    	}
    
        // TODO green led on (stanalone)
    	// dk_set_led_on(CON_STATUS_LED);
    
    	// err = bt_hids_connected(&hids_obj, conn);
    	// if (err) {
    	// 	printk("Failed to notify HID service about connection (err %d)\n", err);
    	// 	return;
    	// }
    
    	for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    		if (!conn_mode[i].conn) {
    			conn_mode[i].conn = conn;
    			conn_mode[i].in_boot_mode = false;
    			break;
    		}
    	}
    
    #if CONFIG_NFC_OOB_PAIRING
    	for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    		if (!conn_mode[i].conn) {
    			advertising_start();
    			return;
    		}
    	}
    #endif
    	is_adv = false;
    }
    
    void disconnected(struct bt_conn *conn, uint8_t reason)
    {
    	int err;
    	bool is_any_dev_connected = false;
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Disconnected from %s, reason 0x%02x %s\n", addr, reason, bt_hci_err_to_str(reason));
    
    	if (reason == BT_HCI_ERR_AUTH_FAIL) {
    		struct bt_conn_info info;
    		err = bt_conn_get_info(conn, &info);
    		if (err) {
    			printk("Failed to get conn info, err=%d. Unpair all\n", err);
    			bt_unpair(BT_ID_DEFAULT, BT_ADDR_LE_ANY);
    		} else {
    			err = bt_unpair(info.id, bt_conn_get_dst(conn));
    			if (err) {
    				printk("Failed to unpair, err=%d\n", err);
    			} else {
    				printk("unpired\n");
    			}
    		}
    
    		bt_le_filter_accept_list_clear();
    		refresh_bonds_filter();
    	}
    
    	err = bt_hids_disconnected(&hids_obj, conn);
    
    	if (err) {
    		printk("Failed to notify HID service about disconnection\n");
    	}
    
    	for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    		if (conn_mode[i].conn == conn) {
    			conn_mode[i].conn = NULL;
    		} else {
    			if (conn_mode[i].conn) {
    				is_any_dev_connected = true;
    			}
    		}
    	}
    
    #if CONFIG_NFC_OOB_PAIRING
    	if (is_adv) {
    		printk("Advertising stopped after disconnect\n");
    		bt_le_adv_stop();
    		is_adv = false;
    	}
    #else
    	is_connected = false;
        uint16_t msg = MSG_BLE_HID_DISCONNECTED;
        k_msgq_put(&main_msgq, &msg, K_NO_WAIT);
        if (reason != BT_HCI_ERR_LOCALHOST_TERM_CONN) {
    	    k_work_submit(&advertising_work);
        }
    #endif
    }
    
    static struct bt_conn *active_conn;
    void security_changed(struct bt_conn *conn, bt_security_t level,
        enum bt_security_err err)
    {
        char addr[BT_ADDR_LE_STR_LEN];
    
        bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    	if (err) {
    		printk("Security failed: %s level %u err %d %s\n", addr, level, err, bt_security_err_to_str(err));
            if (err == BT_SECURITY_ERR_UNSPECIFIED) {
                // ignore BT_SECURITY_ERR_UNSPECIFIED, this can be just poor radio connection
                return;
            }
    		printk("unpair device\n");
    
    		struct bt_conn_info info;
    		err = bt_conn_get_info(conn, &info);
    		if (err) {
    			printk("Failed to get conn info, err=%d. Unpair all\n", err);
    			bt_unpair(BT_ID_DEFAULT, BT_ADDR_LE_ANY);
    		} else {
    			err = bt_unpair(info.id, bt_conn_get_dst(conn));
    			if (err) {
    				printk("Failed to unpair, err=%d\n", err);
    			} else {
    				printk("unpaired\n");
    			}
    		}
    
    		bt_le_filter_accept_list_clear();
    		refresh_bonds_filter();
    		return;
    	}
    
    	LOG_INF("Security changed: %s level %u\n", addr, level);
    	
    	if (level == 2) {
    		err = bt_hids_connected(&hids_obj, conn);
    		if (err) {
    			printk("Failed to notify HID service about connection\n");
    			return;
    		}
    
    		k_timer_stop(&advertising_timeout_timer);
    		LOG_INF("HID service notified\n");
    
    		is_adv = false;
    		is_connected = true;
            active_conn = conn;
    
            uint16_t msg = MSG_BLE_HID_CONNECTED;
            k_msgq_put(&main_msgq, &msg, K_NO_WAIT);
    	}
    }
    
    static void caps_lock_handler(const struct bt_hids_rep *rep)
    {
    	// uint8_t report_val = ((*rep->data) & OUTPUT_REPORT_BIT_MASK_CAPS_LOCK) ?
    	// 		  1 : 0;
        
        // we don't need this
        // dk_set_led(LED_CAPS_LOCK, report_val);
    }
    
    static void hids_outp_rep_handler(struct bt_hids_rep *rep,
    	struct bt_conn *conn,
    	bool write)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	if (!write) {
    		printk("Output report read\n");
    		return;
    	};
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    	printk("Output report has been received %s\n", addr);
    	caps_lock_handler(rep);
    }
    
    static void hids_boot_kb_outp_rep_handler(struct bt_hids_rep *rep,
    					  struct bt_conn *conn,
    					  bool write)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	if (!write) {
    		printk("Output report read\n");
    		return;
    	};
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    	printk("Boot Keyboard Output report has been received %s\n", addr);
    	caps_lock_handler(rep);
    }
    
    static void hids_pm_evt_handler(enum bt_hids_pm_evt evt,
    				struct bt_conn *conn)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    	size_t i;
    
    	for (i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    		if (conn_mode[i].conn == conn) {
    			break;
    		}
    	}
    
    	if (i >= CONFIG_BT_HIDS_MAX_CLIENT_COUNT) {
    		printk("Cannot find connection handle when processing PM");
    		return;
    	}
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	switch (evt) {
    	case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED:
    		printk("Boot mode entered %s\n", addr);
    		conn_mode[i].in_boot_mode = true;
    		break;
    
    	case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED:
    		printk("Report mode entered %s\n", addr);
    		conn_mode[i].in_boot_mode = false;
    		break;
    
    	default:
    		break;
    	}
    }
    
    static void hid_init(void)
    {
    	int err;
    	struct bt_hids_init_param    hids_init_obj = { 0 };
    	struct bt_hids_inp_rep       *hids_inp_rep;
    	struct bt_hids_outp_feat_rep *hids_outp_rep;
    
    	static const uint8_t report_map[] = {
    		0x05, 0x01,       /* Usage Page (Generic Desktop) */
    		0x09, 0x06,       /* Usage (Keyboard) */
    		0xA1, 0x01,       /* Collection (Application) */
    
    		/* Keys */
    #if INPUT_REP_KEYS_REF_ID
    		0x85, INPUT_REP_KEYS_REF_ID,
    #endif
    		0x05, 0x07,       /* Usage Page (Key Codes) */
    		0x19, 0xe0,       /* Usage Minimum (224) */
    		0x29, 0xe7,       /* Usage Maximum (231) */
    		0x15, 0x00,       /* Logical Minimum (0) */
    		0x25, 0x01,       /* Logical Maximum (1) */
    		0x75, 0x01,       /* Report Size (1) */
    		0x95, 0x08,       /* Report Count (8) */
    		0x81, 0x02,       /* Input (Data, Variable, Absolute) */
    
    		0x95, 0x01,       /* Report Count (1) */
    		0x75, 0x08,       /* Report Size (8) */
    		0x81, 0x01,       /* Input (Constant) reserved byte(1) */
    
    		0x95, 0x06,       /* Report Count (6) */
    		0x75, 0x08,       /* Report Size (8) */
    		0x15, 0x00,       /* Logical Minimum (0) */
    		0x25, 0x65,       /* Logical Maximum (101) */
    		0x05, 0x07,       /* Usage Page (Key codes) */
    		0x19, 0x00,       /* Usage Minimum (0) */
    		0x29, 0x65,       /* Usage Maximum (101) */
    		0x81, 0x00,       /* Input (Data, Array) Key array(6 bytes) */
    
    		/* LED */
    #if OUTPUT_REP_KEYS_REF_ID
    		0x85, OUTPUT_REP_KEYS_REF_ID,
    #endif
    		0x95, 0x05,       /* Report Count (5) */
    		0x75, 0x01,       /* Report Size (1) */
    		0x05, 0x08,       /* Usage Page (Page# for LEDs) */
    		0x19, 0x01,       /* Usage Minimum (1) */
    		0x29, 0x05,       /* Usage Maximum (5) */
    		0x91, 0x02,       /* Output (Data, Variable, Absolute), */
    				  /* Led report */
    		0x95, 0x01,       /* Report Count (1) */
    		0x75, 0x03,       /* Report Size (3) */
    		0x91, 0x01,       /* Output (Data, Variable, Absolute), */
    				  /* Led report padding */
    
    		0xC0              /* End Collection (Application) */
    	};
    
    	hids_init_obj.rep_map.data = report_map;
    	hids_init_obj.rep_map.size = sizeof(report_map);
    
    	hids_init_obj.info.bcd_hid = BASE_USB_HID_SPEC_VERSION;
    	hids_init_obj.info.b_country_code = 0x00;
    	hids_init_obj.info.flags = (BT_HIDS_REMOTE_WAKE |
    				    BT_HIDS_NORMALLY_CONNECTABLE);
    
    	hids_inp_rep =
    		&hids_init_obj.inp_rep_group_init.reports[INPUT_REP_KEYS_IDX];
    	hids_inp_rep->size = INPUT_REPORT_KEYS_MAX_LEN;
    	hids_inp_rep->id = INPUT_REP_KEYS_REF_ID;
    	hids_init_obj.inp_rep_group_init.cnt++;
    
    	hids_outp_rep =
    		&hids_init_obj.outp_rep_group_init.reports[OUTPUT_REP_KEYS_IDX];
    	hids_outp_rep->size = OUTPUT_REPORT_MAX_LEN;
    	hids_outp_rep->id = OUTPUT_REP_KEYS_REF_ID;
    	hids_outp_rep->handler = hids_outp_rep_handler;
    	hids_init_obj.outp_rep_group_init.cnt++;
    
    	hids_init_obj.is_kb = true;
    	hids_init_obj.boot_kb_outp_rep_handler = hids_boot_kb_outp_rep_handler;
    	hids_init_obj.pm_evt_handler = hids_pm_evt_handler;
    
    	err = bt_hids_init(&hids_obj, &hids_init_obj);
    	if (err != 0) {
    		printk("bt_hids_init error %d\n", err);
    	}
    	__ASSERT(err == 0, "HIDS initialization failed\n");
    }
    
    static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Passkey for %s: %06u\n", addr, passkey);
    }
    
    static void auth_passkey_confirm(struct bt_conn *conn, unsigned int passkey)
    {
    	int err;
    
    	struct pairing_data_mitm pairing_data;
    
    	pairing_data.conn    = bt_conn_ref(conn);
    	pairing_data.passkey = passkey;
    
    	err = k_msgq_put(&mitm_queue, &pairing_data, K_NO_WAIT);
    	if (err) {
    		printk("Pairing queue is full. Purge previous data.\n");
    	}
    
    	/* In the case of multiple pairing requests, trigger
    	 * pairing confirmation which needed user interaction only
    	 * once to avoid display information about all devices at
    	 * the same time. Passkey confirmation for next devices will
    	 * be proccess from queue after handling the earlier ones.
    	 */
    	if (k_msgq_num_used_get(&mitm_queue) == 1) {
    		k_work_submit(&pairing_work);
    	}
    }
    
    static void auth_cancel(struct bt_conn *conn)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing cancelled: %s\n", addr);
    }
    
    #if CONFIG_NFC_OOB_PAIRING
    static void auth_oob_data_request(struct bt_conn *conn,
    				  struct bt_conn_oob_info *info)
    {
    	int err;
    	struct bt_le_oob *oob_local = app_nfc_oob_data_get();
    
    	printk("LESC OOB data requested\n");
    
    	if (info->type != BT_CONN_OOB_LE_SC) {
    		printk("Only LESC pairing supported\n");
    		return;
    	}
    
    	if (info->lesc.oob_config != BT_CONN_OOB_LOCAL_ONLY) {
    		printk("LESC OOB config not supported\n");
    		return;
    	}
    
    	/* Pass only local OOB data. */
    	err = bt_le_oob_set_sc_data(conn, &oob_local->le_sc_data, NULL);
    	if (err) {
    		printk("Error while setting OOB data: %d\n", err);
    	} else {
    		printk("Successfully provided LESC OOB data\n");
    	}
    }
    #endif
    
    static void pairing_complete(struct bt_conn *conn, bool bonded)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing completed: %s, bonded: %d\n", addr, bonded);
    	if (bonded) {
    		refresh_bonds_filter();
    	}
    
    	// int err = bt_hids_connected(&hids_obj, conn);
    	// if (err) {
    	// 	printk("Failed to notify HID service about connection\n");
    	// 	return;
    	// }
    	// printk("HID service notified\n");
    
    	// bt_conn_ctx_get(hids_obj.conn_ctx, conn);
    }
    
    // static void pairing_confirm(struct bt_conn *conn)
    // {
    // 	printk("pairing confirm\n");
    // }
    
    static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    	struct pairing_data_mitm pairing_data;
    
    	if (k_msgq_peek(&mitm_queue, &pairing_data) != 0) {
    		return;
    	}
    
    	if (pairing_data.conn == conn) {
    		bt_conn_unref(pairing_data.conn);
    		k_msgq_get(&mitm_queue, &pairing_data, K_NO_WAIT);
    	}
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing failed conn: %s, reason %d %s\n", addr, reason,
    	       bt_security_err_to_str(reason));
    }
    
    /** @brief Function process keyboard state and sends it
     *
     *  @param pstate     The state to be sent
     *  @param boot_mode  Information if boot mode protocol is selected.
     *  @param conn       Connection handler
     *
     *  @return 0 on success or negative error code.
     */
    static int key_report_con_send(const struct ampel_state *state,
    			bool boot_mode,
    			struct bt_conn *conn)
    {
    	int err = 0;
    	uint8_t  data[INPUT_REPORT_KEYS_MAX_LEN];
    	uint8_t *key_data;
    	const uint8_t *key_state;
    	size_t n;
    
    	data[0] = state->ctrl_keys_state;
    	data[1] = 0;
    	key_data = &data[2];
    	key_state = state->keys_state;
    
    	for (n = 0; n < KEY_PRESS_MAX; ++n) {
    		*key_data++ = *key_state++;
    	}
    	if (boot_mode) {
    		err = bt_hids_boot_kb_inp_rep_send(&hids_obj, conn, data,
    							sizeof(data), NULL);
    	} else {
    		err = bt_hids_inp_rep_send(&hids_obj, conn,
    						INPUT_REP_KEYS_IDX, data,
    						sizeof(data), NULL);
    	}
    	return err;
    }
    
    /** @brief Function process and send keyboard state to all active connections
     *
     * Function process global keyboard state and send it to all connected
     * clients.
     *
     * @return 0 on success or negative error code.
     */
    static int key_report_send(void)
    {
    	for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    		if (conn_mode[i].conn) {
    			int err;
    
    			err = key_report_con_send(&hid_ampel_state,
    						  conn_mode[i].in_boot_mode,
    						  conn_mode[i].conn);
    			if (err) {
    				printk("Key report send error: %d\n", err);
    				return err;
    			}
    		}
    	}
    	return 0;
    }
    
    /** @brief Change key code to ctrl code mask
     *
     *  Function changes the key code to the mask in the control code
     *  field inside the raport.
     *  Returns 0 if key code is not a control key.
     *
     *  @param key Key code
     *
     *  @return Mask of the control key or 0.
     */
    static uint8_t button_ctrl_code(uint8_t key)
    {
    	if (KEY_CTRL_CODE_MIN <= key && key <= KEY_CTRL_CODE_MAX) {
    		return (uint8_t)(1U << (key - KEY_CTRL_CODE_MIN));
    	}
    	return 0;
    }
    
    static int hid_kbd_state_key_set(uint8_t key)
    {
    	uint8_t ctrl_mask = button_ctrl_code(key);
    
    	if (ctrl_mask) {
    		hid_ampel_state.ctrl_keys_state |= ctrl_mask;
    		return 0;
    	}
    	for (size_t i = 0; i < KEY_PRESS_MAX; ++i) {
    		if (hid_ampel_state.keys_state[i] == 0) {
    			hid_ampel_state.keys_state[i] = key;
    			return 0;
    		}
    	}
    	/* All slots busy */
    	return -EBUSY;
    }
    
    static int hid_kbd_state_key_clear(uint8_t key)
    {
    	uint8_t ctrl_mask = button_ctrl_code(key);
    
    	if (ctrl_mask) {
    		hid_ampel_state.ctrl_keys_state &= ~ctrl_mask;
    		return 0;
    	}
    	for (size_t i = 0; i < KEY_PRESS_MAX; ++i) {
    		if (hid_ampel_state.keys_state[i] == key) {
    			hid_ampel_state.keys_state[i] = 0;
    			return 0;
    		}
    	}
    	/* Key not found */
    	return -EINVAL;
    }
    
    /** @brief Press a button and send report
     *
     *  @note Functions to manipulate hid state are not reentrant
     *  @param keys
     *  @param cnt
     *
     *  @return 0 on success or negative error code.
     */
    int hid_buttons_press(const uint8_t *keys, size_t cnt)
    {
    	while (cnt--) {
    		int err;
    
    		err = hid_kbd_state_key_set(*keys++);
    		if (err) {
    			LOG_ERR("Cannot set selected key. err=%d", err);
    			return err;
    		}
    	}
    
    	// printk("hid_keyboard_state.ctrl_keys_state=%x\n.keys_state:\n",hid_keyboard_state.ctrl_keys_state);
    	// dump_array(hid_keyboard_state.keys_state, sizeof(hid_keyboard_state.keys_state));
    
    	return key_report_send();
    }
    
    /** @brief Release the button and send report
     *
     *  @note Functions to manipulate hid state are not reentrant
     *  @param keys
     *  @param cnt
     *
     *  @return 0 on success or negative error code.
     */
    int hid_buttons_release(const uint8_t *keys, size_t cnt)
    {
    	while (cnt--) {
    		int err;
    
    		err = hid_kbd_state_key_clear(*keys++);
    		if (err) {
    			LOG_ERR("Cannot clear selected key. err=%d", err);
    			return err;
    		}
    	}
    
    	return key_report_send();
    }
    
    void bas_notify(uint8_t battery_level)
    {
        if (battery_level > 100U) {
            battery_level = 100U;
        }
    	bt_bas_set_battery_level(battery_level, active_conn);
    }
    
    static void num_comp_reply(bool accept)
    {
    	struct pairing_data_mitm pairing_data;
    	struct bt_conn *conn;
    
    	int err = k_msgq_get(&mitm_queue, &pairing_data, K_NO_WAIT);
    	if (err != 0) {
    		printk("k_msgq_get err %d", err);
    		return;
    	}
    
    	conn = pairing_data.conn;
    
    	if (accept) {
    		bt_conn_auth_passkey_confirm(conn);
    		printk("Numeric Match, conn %p\n", conn);
    	} else {
    		bt_conn_auth_cancel(conn);
    		printk("Numeric Reject, conn %p\n", conn);
    	}
    	bt_conn_unref(pairing_data.conn);
    
    	if (k_msgq_num_used_get(&mitm_queue)) {
    		k_work_submit(&pairing_work);
    	}
    }
    
    void hid_ampel_disconnect()
    {
        if (is_connected) {
            bt_conn_disconnect(active_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
        } else if (is_adv) {
            advertising_stop();
        }
    }
    
    void ampel_sendAscii(const uint8_t *buffer, uint32_t size) {
        key_info_t keyInfo;
        bool shiftKeyPressed = false;
        for (uint32_t i = 0; i < size; ++i) {
            ascii2keyCode(buffer[i], &keyInfo);
            if (keyInfo.shift) {
                if (!shiftKeyPressed) {
                    shiftKeyPressed = true;
                    hid_buttons_press(shift_key, 1);
                }
            } else {
                if (shiftKeyPressed) {
                    shiftKeyPressed = false;
                    hid_buttons_release(shift_key, 1);
                }
            }
    
            // LOG_INF("keyInfo: 0x%04x, key: %d, shift %d, keyInfo & 0xFF = %d, keyInfo & 0xFF00 = 0x%02x", keyInfo, *((uint8_t *)(&keyInfo)), *((uint8_t *)(&keyInfo)+1), keyInfo & 0xFF, keyInfo & 0xFF00);
    
            hid_buttons_press(&keyInfo.keyCode, 1);
            hid_buttons_release(&keyInfo.keyCode, 1);
        }
    
        if (shiftKeyPressed) {
            hid_buttons_release(shift_key, 1);
        }
    }
    

Related