nrf sample peripheral_hids_mouse does not connect to desktop with BT_HIDS_SECURITY_ENABLED=n, disconnected reason 19

Hi there,

I am trying to achieve "just works" pairing between the peripheral_hids_mouse example and my desktop computer, without the need for a passkey. The peripheral_hids_mouse example does work with a passkey in it's default configuration with BT_HIDS_SECURITY_ENABLED=y however when this is set to BT_HIDS_SECURITY_ENABLED=n is does not connect, with the example returning "Disconnected (reason 19)".

This is how I have configured BT_HIDS_SECURITY_ENABLED=n

And have commented out the references to authentication callbacks in main()

No other changes to the sample have been made.

This is the failure message output when disconnecting with reason 19:

I am running this sample on the NRF52 DK, and my desktop is running Windows 10.

Any help trying to achieve "just works" connection without a passkey for a hids peripheral is appreciated.

Regards,

Bryn

Parents
  • Hi Bryn,

    With CONFIG_BT_HIDS_SECURITY_ENABLED=n just works pairing is still enabled, but not numeric comparison. That is a bug though (and listed under known issues), CONFIG_BT_SMP=y is set in prj.conf so it is included even when CONFIG_BT_HIDS_SECURITY_ENABLED=n. I do not have a Windows 10 computer to test this on at hand, so I only tested and saw that this works with just works pairing on Apple devices with the only change to the example being adding CONFIG_BT_HIDS_SECURITY_ENABLED=n.

    To properly get the functionality you describe (just works pairing), I suggest you instead do these changes to main.c (for nRF Connect SDK 2.3.0):

    full main.c:

    /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #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/settings/settings.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 <dk_buttons_and_leds.h>
    
    #define DEVICE_NAME     CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
    
    #define BASE_USB_HID_SPEC_VERSION   0x0101
    
    /* Number of pixels by which the cursor is moved when a button is pushed. */
    #define MOVEMENT_SPEED              5
    /* Number of input reports in this application. */
    #define INPUT_REPORT_COUNT          3
    /* Length of Mouse Input Report containing button data. */
    #define INPUT_REP_BUTTONS_LEN       3
    /* Length of Mouse Input Report containing movement data. */
    #define INPUT_REP_MOVEMENT_LEN      3
    /* Length of Mouse Input Report containing media player data. */
    #define INPUT_REP_MEDIA_PLAYER_LEN  1
    /* Index of Mouse Input Report containing button data. */
    #define INPUT_REP_BUTTONS_INDEX     0
    /* Index of Mouse Input Report containing movement data. */
    #define INPUT_REP_MOVEMENT_INDEX    1
    /* Index of Mouse Input Report containing media player data. */
    #define INPUT_REP_MPLAYER_INDEX     2
    /* Id of reference to Mouse Input Report containing button data. */
    #define INPUT_REP_REF_BUTTONS_ID    1
    /* Id of reference to Mouse Input Report containing movement data. */
    #define INPUT_REP_REF_MOVEMENT_ID   2
    /* Id of reference to Mouse Input Report containing media player data. */
    #define INPUT_REP_REF_MPLAYER_ID    3
    
    /* HIDs queue size. */
    #define HIDS_QUEUE_SIZE 10
    
    /* Key used to move cursor left */
    #define KEY_LEFT_MASK   DK_BTN1_MSK
    /* Key used to move cursor up */
    #define KEY_UP_MASK     DK_BTN2_MSK
    /* Key used to move cursor right */
    #define KEY_RIGHT_MASK  DK_BTN3_MSK
    /* Key used to move cursor down */
    #define KEY_DOWN_MASK   DK_BTN4_MSK
    
    /* HIDS instance. */
    BT_HIDS_DEF(hids_obj,
    	    INPUT_REP_BUTTONS_LEN,
    	    INPUT_REP_MOVEMENT_LEN,
    	    INPUT_REP_MEDIA_PLAYER_LEN);
    
    static struct k_work hids_work;
    struct mouse_pos {
    	int16_t x_val;
    	int16_t y_val;
    };
    
    /* Mouse movement queue. */
    K_MSGQ_DEFINE(hids_queue,
    	      sizeof(struct mouse_pos),
    	      HIDS_QUEUE_SIZE,
    	      4);
    
    #if CONFIG_BT_DIRECTED_ADVERTISING
    /* Bonded address queue. */
    K_MSGQ_DEFINE(bonds_queue,
    	      sizeof(bt_addr_le_t),
    	      CONFIG_BT_MAX_PAIRED,
    	      4);
    #endif
    
    static 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)),
    };
    
    static const struct bt_data sd[] = {
    	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    };
    
    static struct conn_mode {
    	struct bt_conn *conn;
    	bool in_boot_mode;
    } conn_mode[CONFIG_BT_HIDS_MAX_CLIENT_COUNT];
    
    static struct k_work adv_work;
    
    
    #if CONFIG_BT_DIRECTED_ADVERTISING
    static void bond_find(const struct bt_bond_info *info, void *user_data)
    {
    	int err;
    
    	/* Filter already connected peers. */
    	for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    		if (conn_mode[i].conn) {
    			const bt_addr_le_t *dst =
    				bt_conn_get_dst(conn_mode[i].conn);
    
    			if (!bt_addr_le_cmp(&info->addr, dst)) {
    				return;
    			}
    		}
    	}
    
    	err = k_msgq_put(&bonds_queue, (void *) &info->addr, K_NO_WAIT);
    	if (err) {
    		printk("No space in the queue for the bond.\n");
    	}
    }
    #endif
    
    static void advertising_continue(void)
    {
    	struct bt_le_adv_param adv_param;
    
    #if CONFIG_BT_DIRECTED_ADVERTISING
    	bt_addr_le_t addr;
    
    	if (!k_msgq_get(&bonds_queue, &addr, K_NO_WAIT)) {
    		char addr_buf[BT_ADDR_LE_STR_LEN];
    
    		adv_param = *BT_LE_ADV_CONN_DIR(&addr);
    		adv_param.options |= BT_LE_ADV_OPT_DIR_ADDR_RPA;
    
    		int err = bt_le_adv_start(&adv_param, NULL, 0, NULL, 0);
    
    		if (err) {
    			printk("Directed advertising failed to start\n");
    			return;
    		}
    
    		bt_addr_le_to_str(&addr, addr_buf, BT_ADDR_LE_STR_LEN);
    		printk("Direct advertising to %s started\n", addr_buf);
    	} else
    #endif
    	{
    		int err;
    
    		adv_param = *BT_LE_ADV_CONN;
    		adv_param.options |= BT_LE_ADV_OPT_ONE_TIME;
    		err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad),
    				  sd, ARRAY_SIZE(sd));
    		if (err) {
    			printk("Advertising failed to start (err %d)\n", err);
    			return;
    		}
    
    		printk("Regular advertising started\n");
    	}
    }
    
    static void advertising_start(void)
    {
    #if CONFIG_BT_DIRECTED_ADVERTISING
    	k_msgq_purge(&bonds_queue);
    	bt_foreach_bond(BT_ID_DEFAULT, bond_find, NULL);
    #endif
    
    	k_work_submit(&adv_work);
    }
    
    static void advertising_process(struct k_work *work)
    {
    	advertising_continue();
    }
    
    
    static void insert_conn_object(struct bt_conn *conn)
    {
    	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;
    
    			return;
    		}
    	}
    
    	printk("Connection object could not be inserted %p\n", conn);
    }
    
    
    static bool is_conn_slot_free(void)
    {
    	for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    		if (!conn_mode[i].conn) {
    			return true;
    		}
    	}
    
    	return false;
    }
    
    
    static 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) {
    		if (err == BT_HCI_ERR_ADV_TIMEOUT) {
    			printk("Direct advertising to %s timed out\n", addr);
    			k_work_submit(&adv_work);
    		} else {
    			printk("Failed to connect to %s (%u)\n", addr, err);
    		}
    		return;
    	}
    
    	printk("Connected %s\n", addr);
    
    	err = bt_hids_connected(&hids_obj, conn);
    
    	if (err) {
    		printk("Failed to notify HID service about connection\n");
    		return;
    	}
    
    	insert_conn_object(conn);
    
    	if (is_conn_slot_free()) {
    		advertising_start();
    	}
    }
    
    
    static void disconnected(struct bt_conn *conn, uint8_t reason)
    {
    	int err;
    	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 %u)\n", addr, reason);
    
    	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;
    			break;
    		}
    	}
    
    	advertising_start();
    }
    
    
    static 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 changed: %s level %u\n", addr, level);
    	} else {
    		printk("Security failed: %s level %u err %d\n", addr, level,
    			err);
    	}
    }
    
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
    	.connected = connected,
    	.disconnected = disconnected,
    	.security_changed = security_changed,
    };
    
    
    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) {
    		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_param = { 0 };
    	struct bt_hids_inp_rep *hids_inp_rep;
    	static const uint8_t mouse_movement_mask[ceiling_fraction(INPUT_REP_MOVEMENT_LEN, 8)] = {0};
    
    	static const uint8_t report_map[] = {
    		0x05, 0x01,     /* Usage Page (Generic Desktop) */
    		0x09, 0x02,     /* Usage (Mouse) */
    
    		0xA1, 0x01,     /* Collection (Application) */
    
    		/* Report ID 1: Mouse buttons + scroll/pan */
    		0x85, 0x01,       /* Report Id 1 */
    		0x09, 0x01,       /* Usage (Pointer) */
    		0xA1, 0x00,       /* Collection (Physical) */
    		0x95, 0x05,       /* Report Count (3) */
    		0x75, 0x01,       /* Report Size (1) */
    		0x05, 0x09,       /* Usage Page (Buttons) */
    		0x19, 0x01,       /* Usage Minimum (01) */
    		0x29, 0x05,       /* Usage Maximum (05) */
    		0x15, 0x00,       /* Logical Minimum (0) */
    		0x25, 0x01,       /* Logical Maximum (1) */
    		0x81, 0x02,       /* Input (Data, Variable, Absolute) */
    		0x95, 0x01,       /* Report Count (1) */
    		0x75, 0x03,       /* Report Size (3) */
    		0x81, 0x01,       /* Input (Constant) for padding */
    		0x75, 0x08,       /* Report Size (8) */
    		0x95, 0x01,       /* Report Count (1) */
    		0x05, 0x01,       /* Usage Page (Generic Desktop) */
    		0x09, 0x38,       /* Usage (Wheel) */
    		0x15, 0x81,       /* Logical Minimum (-127) */
    		0x25, 0x7F,       /* Logical Maximum (127) */
    		0x81, 0x06,       /* Input (Data, Variable, Relative) */
    		0x05, 0x0C,       /* Usage Page (Consumer) */
    		0x0A, 0x38, 0x02, /* Usage (AC Pan) */
    		0x95, 0x01,       /* Report Count (1) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0xC0,             /* End Collection (Physical) */
    
    		/* Report ID 2: Mouse motion */
    		0x85, 0x02,       /* Report Id 2 */
    		0x09, 0x01,       /* Usage (Pointer) */
    		0xA1, 0x00,       /* Collection (Physical) */
    		0x75, 0x0C,       /* Report Size (12) */
    		0x95, 0x02,       /* Report Count (2) */
    		0x05, 0x01,       /* Usage Page (Generic Desktop) */
    		0x09, 0x30,       /* Usage (X) */
    		0x09, 0x31,       /* Usage (Y) */
    		0x16, 0x01, 0xF8, /* Logical maximum (2047) */
    		0x26, 0xFF, 0x07, /* Logical minimum (-2047) */
    		0x81, 0x06,       /* Input (Data, Variable, Relative) */
    		0xC0,             /* End Collection (Physical) */
    		0xC0,             /* End Collection (Application) */
    
    		/* Report ID 3: Advanced buttons */
    		0x05, 0x0C,       /* Usage Page (Consumer) */
    		0x09, 0x01,       /* Usage (Consumer Control) */
    		0xA1, 0x01,       /* Collection (Application) */
    		0x85, 0x03,       /* Report Id (3) */
    		0x15, 0x00,       /* Logical minimum (0) */
    		0x25, 0x01,       /* Logical maximum (1) */
    		0x75, 0x01,       /* Report Size (1) */
    		0x95, 0x01,       /* Report Count (1) */
    
    		0x09, 0xCD,       /* Usage (Play/Pause) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x0A, 0x83, 0x01, /* Usage (Consumer Control Configuration) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x09, 0xB5,       /* Usage (Scan Next Track) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x09, 0xB6,       /* Usage (Scan Previous Track) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    
    		0x09, 0xEA,       /* Usage (Volume Down) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x09, 0xE9,       /* Usage (Volume Up) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x0A, 0x25, 0x02, /* Usage (AC Forward) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x0A, 0x24, 0x02, /* Usage (AC Back) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0xC0              /* End Collection */
    	};
    
    	hids_init_param.rep_map.data = report_map;
    	hids_init_param.rep_map.size = sizeof(report_map);
    
    	hids_init_param.info.bcd_hid = BASE_USB_HID_SPEC_VERSION;
    	hids_init_param.info.b_country_code = 0x00;
    	hids_init_param.info.flags = (BT_HIDS_REMOTE_WAKE |
    				      BT_HIDS_NORMALLY_CONNECTABLE);
    
    	hids_inp_rep = &hids_init_param.inp_rep_group_init.reports[0];
    	hids_inp_rep->size = INPUT_REP_BUTTONS_LEN;
    	hids_inp_rep->id = INPUT_REP_REF_BUTTONS_ID;
    	hids_init_param.inp_rep_group_init.cnt++;
    
    	hids_inp_rep++;
    	hids_inp_rep->size = INPUT_REP_MOVEMENT_LEN;
    	hids_inp_rep->id = INPUT_REP_REF_MOVEMENT_ID;
    	hids_inp_rep->rep_mask = mouse_movement_mask;
    	hids_init_param.inp_rep_group_init.cnt++;
    
    	hids_inp_rep++;
    	hids_inp_rep->size = INPUT_REP_MEDIA_PLAYER_LEN;
    	hids_inp_rep->id = INPUT_REP_REF_MPLAYER_ID;
    	hids_init_param.inp_rep_group_init.cnt++;
    
    	hids_init_param.is_mouse = true;
    	hids_init_param.pm_evt_handler = hids_pm_evt_handler;
    
    	err = bt_hids_init(&hids_obj, &hids_init_param);
    	__ASSERT(err == 0, "HIDS initialization failed\n");
    }
    
    
    static void mouse_movement_send(int16_t x_delta, int16_t y_delta)
    {
    	for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    
    		if (!conn_mode[i].conn) {
    			continue;
    		}
    
    		if (conn_mode[i].in_boot_mode) {
    			x_delta = MAX(MIN(x_delta, SCHAR_MAX), SCHAR_MIN);
    			y_delta = MAX(MIN(y_delta, SCHAR_MAX), SCHAR_MIN);
    
    			bt_hids_boot_mouse_inp_rep_send(&hids_obj,
    							     conn_mode[i].conn,
    							     NULL,
    							     (int8_t) x_delta,
    							     (int8_t) y_delta,
    							     NULL);
    		} else {
    			uint8_t x_buff[2];
    			uint8_t y_buff[2];
    			uint8_t buffer[INPUT_REP_MOVEMENT_LEN];
    
    			int16_t x = MAX(MIN(x_delta, 0x07ff), -0x07ff);
    			int16_t y = MAX(MIN(y_delta, 0x07ff), -0x07ff);
    
    			/* Convert to little-endian. */
    			sys_put_le16(x, x_buff);
    			sys_put_le16(y, y_buff);
    
    			/* Encode report. */
    			BUILD_ASSERT(sizeof(buffer) == 3,
    					 "Only 2 axis, 12-bit each, are supported");
    
    			buffer[0] = x_buff[0];
    			buffer[1] = (y_buff[0] << 4) | (x_buff[1] & 0x0f);
    			buffer[2] = (y_buff[1] << 4) | (y_buff[0] >> 4);
    
    
    			bt_hids_inp_rep_send(&hids_obj, conn_mode[i].conn,
    						  INPUT_REP_MOVEMENT_INDEX,
    						  buffer, sizeof(buffer), NULL);
    		}
    	}
    }
    
    
    static void mouse_handler(struct k_work *work)
    {
    	struct mouse_pos pos;
    
    	while (!k_msgq_get(&hids_queue, &pos, K_NO_WAIT)) {
    		mouse_movement_send(pos.x_val, pos.y_val);
    	}
    }
    
    
    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);
    }
    
    
    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);
    }
    
    
    static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing failed conn: %s, reason %d\n", addr, reason);
    }
    
    
    static struct bt_conn_auth_cb conn_auth_callbacks = {
    	.cancel = auth_cancel,
    };
    
    static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
    	.pairing_complete = pairing_complete,
    	.pairing_failed = pairing_failed
    };
    
    
    void button_changed(uint32_t button_state, uint32_t has_changed)
    {
    	bool data_to_send = false;
    	struct mouse_pos pos;
    	uint32_t buttons = button_state & has_changed;
    
    	memset(&pos, 0, sizeof(struct mouse_pos));
    
    	if (buttons & KEY_LEFT_MASK) {
    		pos.x_val -= MOVEMENT_SPEED;
    		printk("%s(): left\n", __func__);
    		data_to_send = true;
    	}
    	if (buttons & KEY_UP_MASK) {
    		pos.y_val -= MOVEMENT_SPEED;
    		printk("%s(): up\n", __func__);
    		data_to_send = true;
    	}
    	if (buttons & KEY_RIGHT_MASK) {
    		pos.x_val += MOVEMENT_SPEED;
    		printk("%s(): right\n", __func__);
    		data_to_send = true;
    	}
    	if (buttons & KEY_DOWN_MASK) {
    		pos.y_val += MOVEMENT_SPEED;
    		printk("%s(): down\n", __func__);
    		data_to_send = true;
    	}
    
    	if (data_to_send) {
    		int err;
    
    		err = k_msgq_put(&hids_queue, &pos, K_NO_WAIT);
    		if (err) {
    			printk("No space in the queue for button pressed\n");
    			return;
    		}
    		if (k_msgq_num_used_get(&hids_queue) == 1) {
    			k_work_submit(&hids_work);
    		}
    	}
    }
    
    
    void configure_buttons(void)
    {
    	int err;
    
    	err = dk_buttons_init(button_changed);
    	if (err) {
    		printk("Cannot init buttons (err: %d)\n", err);
    	}
    }
    
    
    static void bas_notify(void)
    {
    	uint8_t battery_level = bt_bas_get_battery_level();
    
    	battery_level--;
    
    	if (!battery_level) {
    		battery_level = 100U;
    	}
    
    	bt_bas_set_battery_level(battery_level);
    }
    
    
    void main(void)
    {
    	int err;
    
    	printk("Starting Bluetooth Peripheral HIDS mouse example\n");
    
    	err = bt_conn_auth_cb_register(&conn_auth_callbacks);
    	if (err) {
    		printk("Failed to register authorization callbacks.\n");
    		return;
    	}
    
    	err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
    	if (err) {
    		printk("Failed to register authorization info callbacks.\n");
    		return;
    	}
    
    	err = bt_enable(NULL);
    	if (err) {
    		printk("Bluetooth init failed (err %d)\n", err);
    		return;
    	}
    
    	printk("Bluetooth initialized\n");
    
    	/* DIS initialized at system boot with SYS_INIT macro. */
    	hid_init();
    
    	k_work_init(&hids_work, mouse_handler);
    	k_work_init(&adv_work, advertising_process);
    
    	if (IS_ENABLED(CONFIG_SETTINGS)) {
    		settings_load();
    	}
    
    	advertising_start();
    
    	configure_buttons();
    
    	while (1) {
    		k_sleep(K_SECONDS(1));
    		/* Battery level simulation */
    		bas_notify();
    	}
    }
    

    diff from unmodified example to easily see the changes:

    diff --git a/samples/bluetooth/peripheral_hids_mouse/src/main.c b/samples/bluetooth/peripheral_hids_mouse/src/main.c
    index edc81bef8..ab63df8fc 100644
    --- a/samples/bluetooth/peripheral_hids_mouse/src/main.c
    +++ b/samples/bluetooth/peripheral_hids_mouse/src/main.c
    @@ -68,10 +68,6 @@
     /* Key used to move cursor down */
     #define KEY_DOWN_MASK   DK_BTN4_MSK
     
    -/* Key used to accept or reject passkey value */
    -#define KEY_PAIRING_ACCEPT DK_BTN1_MSK
    -#define KEY_PAIRING_REJECT DK_BTN2_MSK
    -
     /* HIDS instance. */
     BT_HIDS_DEF(hids_obj,
     	    INPUT_REP_BUTTONS_LEN,
    @@ -118,16 +114,6 @@ static struct conn_mode {
     
     static struct k_work adv_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);
     
     #if CONFIG_BT_DIRECTED_ADVERTISING
     static void bond_find(const struct bt_bond_info *info, void *user_data)
    @@ -208,25 +194,6 @@ static void advertising_process(struct k_work *work)
     	advertising_continue();
     }
     
    -static 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);
    -	printk("Press Button 1 to confirm, Button 2 to reject.\n");
    -}
    -
     
     static void insert_conn_object(struct bt_conn *conn)
     {
    @@ -314,7 +281,6 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
     }
     
     
    -#ifdef CONFIG_BT_HIDS_SECURITY_ENABLED
     static void security_changed(struct bt_conn *conn, bt_security_t level,
     			     enum bt_security_err err)
     {
    @@ -329,15 +295,12 @@ static void security_changed(struct bt_conn *conn, bt_security_t level,
     			err);
     	}
     }
    -#endif
     
     
     BT_CONN_CB_DEFINE(conn_callbacks) = {
     	.connected = connected,
     	.disconnected = disconnected,
    -#ifdef CONFIG_BT_HIDS_SECURITY_ENABLED
     	.security_changed = security_changed,
    -#endif
     };
     
     
    @@ -550,42 +513,6 @@ static void mouse_handler(struct k_work *work)
     	}
     }
     
    -#if defined(CONFIG_BT_HIDS_SECURITY_ENABLED)
    -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)
     {
    @@ -610,16 +537,6 @@ static void pairing_complete(struct bt_conn *conn, bool bonded)
     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));
     
    @@ -628,8 +545,6 @@ static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
     
     
     static struct bt_conn_auth_cb conn_auth_callbacks = {
    -	.passkey_display = auth_passkey_display,
    -	.passkey_confirm = auth_passkey_confirm,
     	.cancel = auth_cancel,
     };
     
    @@ -637,37 +552,6 @@ static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
     	.pairing_complete = pairing_complete,
     	.pairing_failed = pairing_failed
     };
    -#else
    -static struct bt_conn_auth_cb conn_auth_callbacks;
    -static struct bt_conn_auth_info_cb conn_auth_info_callbacks;
    -#endif /* defined(CONFIG_BT_HIDS_SECURITY_ENABLED) */
    -
    -
    -static void num_comp_reply(bool accept)
    -{
    -	struct pairing_data_mitm pairing_data;
    -	struct bt_conn *conn;
    -
    -	if (k_msgq_get(&mitm_queue, &pairing_data, K_NO_WAIT) != 0) {
    -		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 button_changed(uint32_t button_state, uint32_t has_changed)
    @@ -678,20 +562,6 @@ void button_changed(uint32_t button_state, uint32_t has_changed)
     
     	memset(&pos, 0, sizeof(struct mouse_pos));
     
    -	if (k_msgq_num_used_get(&mitm_queue)) {
    -		if (buttons & KEY_PAIRING_ACCEPT) {
    -			num_comp_reply(true);
    -
    -			return;
    -		}
    -
    -		if (buttons & KEY_PAIRING_REJECT) {
    -			num_comp_reply(false);
    -
    -			return;
    -		}
    -	}
    -
     	if (buttons & KEY_LEFT_MASK) {
     		pos.x_val -= MOVEMENT_SPEED;
     		printk("%s(): left\n", __func__);
    @@ -759,18 +629,16 @@ void main(void)
     
     	printk("Starting Bluetooth Peripheral HIDS mouse example\n");
     
    -	if (IS_ENABLED(CONFIG_BT_HIDS_SECURITY_ENABLED)) {
    -		err = bt_conn_auth_cb_register(&conn_auth_callbacks);
    -		if (err) {
    -			printk("Failed to register authorization callbacks.\n");
    -			return;
    -		}
    +	err = bt_conn_auth_cb_register(&conn_auth_callbacks);
    +	if (err) {
    +		printk("Failed to register authorization callbacks.\n");
    +		return;
    +	}
     
    -		err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
    -		if (err) {
    -			printk("Failed to register authorization info callbacks.\n");
    -			return;
    -		}
    +	err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
    +	if (err) {
    +		printk("Failed to register authorization info callbacks.\n");
    +		return;
     	}
     
     	err = bt_enable(NULL);
    @@ -785,7 +653,6 @@ void main(void)
     	hid_init();
     
     	k_work_init(&hids_work, mouse_handler);
    -	k_work_init(&pairing_work, pairing_process);
     	k_work_init(&adv_work, advertising_process);
     
     	if (IS_ENABLED(CONFIG_SETTINGS)) {
    

Reply
  • Hi Bryn,

    With CONFIG_BT_HIDS_SECURITY_ENABLED=n just works pairing is still enabled, but not numeric comparison. That is a bug though (and listed under known issues), CONFIG_BT_SMP=y is set in prj.conf so it is included even when CONFIG_BT_HIDS_SECURITY_ENABLED=n. I do not have a Windows 10 computer to test this on at hand, so I only tested and saw that this works with just works pairing on Apple devices with the only change to the example being adding CONFIG_BT_HIDS_SECURITY_ENABLED=n.

    To properly get the functionality you describe (just works pairing), I suggest you instead do these changes to main.c (for nRF Connect SDK 2.3.0):

    full main.c:

    /*
     * Copyright (c) 2018 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     */
    
    #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/settings/settings.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 <dk_buttons_and_leds.h>
    
    #define DEVICE_NAME     CONFIG_BT_DEVICE_NAME
    #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)
    
    #define BASE_USB_HID_SPEC_VERSION   0x0101
    
    /* Number of pixels by which the cursor is moved when a button is pushed. */
    #define MOVEMENT_SPEED              5
    /* Number of input reports in this application. */
    #define INPUT_REPORT_COUNT          3
    /* Length of Mouse Input Report containing button data. */
    #define INPUT_REP_BUTTONS_LEN       3
    /* Length of Mouse Input Report containing movement data. */
    #define INPUT_REP_MOVEMENT_LEN      3
    /* Length of Mouse Input Report containing media player data. */
    #define INPUT_REP_MEDIA_PLAYER_LEN  1
    /* Index of Mouse Input Report containing button data. */
    #define INPUT_REP_BUTTONS_INDEX     0
    /* Index of Mouse Input Report containing movement data. */
    #define INPUT_REP_MOVEMENT_INDEX    1
    /* Index of Mouse Input Report containing media player data. */
    #define INPUT_REP_MPLAYER_INDEX     2
    /* Id of reference to Mouse Input Report containing button data. */
    #define INPUT_REP_REF_BUTTONS_ID    1
    /* Id of reference to Mouse Input Report containing movement data. */
    #define INPUT_REP_REF_MOVEMENT_ID   2
    /* Id of reference to Mouse Input Report containing media player data. */
    #define INPUT_REP_REF_MPLAYER_ID    3
    
    /* HIDs queue size. */
    #define HIDS_QUEUE_SIZE 10
    
    /* Key used to move cursor left */
    #define KEY_LEFT_MASK   DK_BTN1_MSK
    /* Key used to move cursor up */
    #define KEY_UP_MASK     DK_BTN2_MSK
    /* Key used to move cursor right */
    #define KEY_RIGHT_MASK  DK_BTN3_MSK
    /* Key used to move cursor down */
    #define KEY_DOWN_MASK   DK_BTN4_MSK
    
    /* HIDS instance. */
    BT_HIDS_DEF(hids_obj,
    	    INPUT_REP_BUTTONS_LEN,
    	    INPUT_REP_MOVEMENT_LEN,
    	    INPUT_REP_MEDIA_PLAYER_LEN);
    
    static struct k_work hids_work;
    struct mouse_pos {
    	int16_t x_val;
    	int16_t y_val;
    };
    
    /* Mouse movement queue. */
    K_MSGQ_DEFINE(hids_queue,
    	      sizeof(struct mouse_pos),
    	      HIDS_QUEUE_SIZE,
    	      4);
    
    #if CONFIG_BT_DIRECTED_ADVERTISING
    /* Bonded address queue. */
    K_MSGQ_DEFINE(bonds_queue,
    	      sizeof(bt_addr_le_t),
    	      CONFIG_BT_MAX_PAIRED,
    	      4);
    #endif
    
    static 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)),
    };
    
    static const struct bt_data sd[] = {
    	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
    };
    
    static struct conn_mode {
    	struct bt_conn *conn;
    	bool in_boot_mode;
    } conn_mode[CONFIG_BT_HIDS_MAX_CLIENT_COUNT];
    
    static struct k_work adv_work;
    
    
    #if CONFIG_BT_DIRECTED_ADVERTISING
    static void bond_find(const struct bt_bond_info *info, void *user_data)
    {
    	int err;
    
    	/* Filter already connected peers. */
    	for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    		if (conn_mode[i].conn) {
    			const bt_addr_le_t *dst =
    				bt_conn_get_dst(conn_mode[i].conn);
    
    			if (!bt_addr_le_cmp(&info->addr, dst)) {
    				return;
    			}
    		}
    	}
    
    	err = k_msgq_put(&bonds_queue, (void *) &info->addr, K_NO_WAIT);
    	if (err) {
    		printk("No space in the queue for the bond.\n");
    	}
    }
    #endif
    
    static void advertising_continue(void)
    {
    	struct bt_le_adv_param adv_param;
    
    #if CONFIG_BT_DIRECTED_ADVERTISING
    	bt_addr_le_t addr;
    
    	if (!k_msgq_get(&bonds_queue, &addr, K_NO_WAIT)) {
    		char addr_buf[BT_ADDR_LE_STR_LEN];
    
    		adv_param = *BT_LE_ADV_CONN_DIR(&addr);
    		adv_param.options |= BT_LE_ADV_OPT_DIR_ADDR_RPA;
    
    		int err = bt_le_adv_start(&adv_param, NULL, 0, NULL, 0);
    
    		if (err) {
    			printk("Directed advertising failed to start\n");
    			return;
    		}
    
    		bt_addr_le_to_str(&addr, addr_buf, BT_ADDR_LE_STR_LEN);
    		printk("Direct advertising to %s started\n", addr_buf);
    	} else
    #endif
    	{
    		int err;
    
    		adv_param = *BT_LE_ADV_CONN;
    		adv_param.options |= BT_LE_ADV_OPT_ONE_TIME;
    		err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad),
    				  sd, ARRAY_SIZE(sd));
    		if (err) {
    			printk("Advertising failed to start (err %d)\n", err);
    			return;
    		}
    
    		printk("Regular advertising started\n");
    	}
    }
    
    static void advertising_start(void)
    {
    #if CONFIG_BT_DIRECTED_ADVERTISING
    	k_msgq_purge(&bonds_queue);
    	bt_foreach_bond(BT_ID_DEFAULT, bond_find, NULL);
    #endif
    
    	k_work_submit(&adv_work);
    }
    
    static void advertising_process(struct k_work *work)
    {
    	advertising_continue();
    }
    
    
    static void insert_conn_object(struct bt_conn *conn)
    {
    	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;
    
    			return;
    		}
    	}
    
    	printk("Connection object could not be inserted %p\n", conn);
    }
    
    
    static bool is_conn_slot_free(void)
    {
    	for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    		if (!conn_mode[i].conn) {
    			return true;
    		}
    	}
    
    	return false;
    }
    
    
    static 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) {
    		if (err == BT_HCI_ERR_ADV_TIMEOUT) {
    			printk("Direct advertising to %s timed out\n", addr);
    			k_work_submit(&adv_work);
    		} else {
    			printk("Failed to connect to %s (%u)\n", addr, err);
    		}
    		return;
    	}
    
    	printk("Connected %s\n", addr);
    
    	err = bt_hids_connected(&hids_obj, conn);
    
    	if (err) {
    		printk("Failed to notify HID service about connection\n");
    		return;
    	}
    
    	insert_conn_object(conn);
    
    	if (is_conn_slot_free()) {
    		advertising_start();
    	}
    }
    
    
    static void disconnected(struct bt_conn *conn, uint8_t reason)
    {
    	int err;
    	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 %u)\n", addr, reason);
    
    	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;
    			break;
    		}
    	}
    
    	advertising_start();
    }
    
    
    static 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 changed: %s level %u\n", addr, level);
    	} else {
    		printk("Security failed: %s level %u err %d\n", addr, level,
    			err);
    	}
    }
    
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
    	.connected = connected,
    	.disconnected = disconnected,
    	.security_changed = security_changed,
    };
    
    
    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) {
    		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_param = { 0 };
    	struct bt_hids_inp_rep *hids_inp_rep;
    	static const uint8_t mouse_movement_mask[ceiling_fraction(INPUT_REP_MOVEMENT_LEN, 8)] = {0};
    
    	static const uint8_t report_map[] = {
    		0x05, 0x01,     /* Usage Page (Generic Desktop) */
    		0x09, 0x02,     /* Usage (Mouse) */
    
    		0xA1, 0x01,     /* Collection (Application) */
    
    		/* Report ID 1: Mouse buttons + scroll/pan */
    		0x85, 0x01,       /* Report Id 1 */
    		0x09, 0x01,       /* Usage (Pointer) */
    		0xA1, 0x00,       /* Collection (Physical) */
    		0x95, 0x05,       /* Report Count (3) */
    		0x75, 0x01,       /* Report Size (1) */
    		0x05, 0x09,       /* Usage Page (Buttons) */
    		0x19, 0x01,       /* Usage Minimum (01) */
    		0x29, 0x05,       /* Usage Maximum (05) */
    		0x15, 0x00,       /* Logical Minimum (0) */
    		0x25, 0x01,       /* Logical Maximum (1) */
    		0x81, 0x02,       /* Input (Data, Variable, Absolute) */
    		0x95, 0x01,       /* Report Count (1) */
    		0x75, 0x03,       /* Report Size (3) */
    		0x81, 0x01,       /* Input (Constant) for padding */
    		0x75, 0x08,       /* Report Size (8) */
    		0x95, 0x01,       /* Report Count (1) */
    		0x05, 0x01,       /* Usage Page (Generic Desktop) */
    		0x09, 0x38,       /* Usage (Wheel) */
    		0x15, 0x81,       /* Logical Minimum (-127) */
    		0x25, 0x7F,       /* Logical Maximum (127) */
    		0x81, 0x06,       /* Input (Data, Variable, Relative) */
    		0x05, 0x0C,       /* Usage Page (Consumer) */
    		0x0A, 0x38, 0x02, /* Usage (AC Pan) */
    		0x95, 0x01,       /* Report Count (1) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0xC0,             /* End Collection (Physical) */
    
    		/* Report ID 2: Mouse motion */
    		0x85, 0x02,       /* Report Id 2 */
    		0x09, 0x01,       /* Usage (Pointer) */
    		0xA1, 0x00,       /* Collection (Physical) */
    		0x75, 0x0C,       /* Report Size (12) */
    		0x95, 0x02,       /* Report Count (2) */
    		0x05, 0x01,       /* Usage Page (Generic Desktop) */
    		0x09, 0x30,       /* Usage (X) */
    		0x09, 0x31,       /* Usage (Y) */
    		0x16, 0x01, 0xF8, /* Logical maximum (2047) */
    		0x26, 0xFF, 0x07, /* Logical minimum (-2047) */
    		0x81, 0x06,       /* Input (Data, Variable, Relative) */
    		0xC0,             /* End Collection (Physical) */
    		0xC0,             /* End Collection (Application) */
    
    		/* Report ID 3: Advanced buttons */
    		0x05, 0x0C,       /* Usage Page (Consumer) */
    		0x09, 0x01,       /* Usage (Consumer Control) */
    		0xA1, 0x01,       /* Collection (Application) */
    		0x85, 0x03,       /* Report Id (3) */
    		0x15, 0x00,       /* Logical minimum (0) */
    		0x25, 0x01,       /* Logical maximum (1) */
    		0x75, 0x01,       /* Report Size (1) */
    		0x95, 0x01,       /* Report Count (1) */
    
    		0x09, 0xCD,       /* Usage (Play/Pause) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x0A, 0x83, 0x01, /* Usage (Consumer Control Configuration) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x09, 0xB5,       /* Usage (Scan Next Track) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x09, 0xB6,       /* Usage (Scan Previous Track) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    
    		0x09, 0xEA,       /* Usage (Volume Down) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x09, 0xE9,       /* Usage (Volume Up) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x0A, 0x25, 0x02, /* Usage (AC Forward) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0x0A, 0x24, 0x02, /* Usage (AC Back) */
    		0x81, 0x06,       /* Input (Data,Value,Relative,Bit Field) */
    		0xC0              /* End Collection */
    	};
    
    	hids_init_param.rep_map.data = report_map;
    	hids_init_param.rep_map.size = sizeof(report_map);
    
    	hids_init_param.info.bcd_hid = BASE_USB_HID_SPEC_VERSION;
    	hids_init_param.info.b_country_code = 0x00;
    	hids_init_param.info.flags = (BT_HIDS_REMOTE_WAKE |
    				      BT_HIDS_NORMALLY_CONNECTABLE);
    
    	hids_inp_rep = &hids_init_param.inp_rep_group_init.reports[0];
    	hids_inp_rep->size = INPUT_REP_BUTTONS_LEN;
    	hids_inp_rep->id = INPUT_REP_REF_BUTTONS_ID;
    	hids_init_param.inp_rep_group_init.cnt++;
    
    	hids_inp_rep++;
    	hids_inp_rep->size = INPUT_REP_MOVEMENT_LEN;
    	hids_inp_rep->id = INPUT_REP_REF_MOVEMENT_ID;
    	hids_inp_rep->rep_mask = mouse_movement_mask;
    	hids_init_param.inp_rep_group_init.cnt++;
    
    	hids_inp_rep++;
    	hids_inp_rep->size = INPUT_REP_MEDIA_PLAYER_LEN;
    	hids_inp_rep->id = INPUT_REP_REF_MPLAYER_ID;
    	hids_init_param.inp_rep_group_init.cnt++;
    
    	hids_init_param.is_mouse = true;
    	hids_init_param.pm_evt_handler = hids_pm_evt_handler;
    
    	err = bt_hids_init(&hids_obj, &hids_init_param);
    	__ASSERT(err == 0, "HIDS initialization failed\n");
    }
    
    
    static void mouse_movement_send(int16_t x_delta, int16_t y_delta)
    {
    	for (size_t i = 0; i < CONFIG_BT_HIDS_MAX_CLIENT_COUNT; i++) {
    
    		if (!conn_mode[i].conn) {
    			continue;
    		}
    
    		if (conn_mode[i].in_boot_mode) {
    			x_delta = MAX(MIN(x_delta, SCHAR_MAX), SCHAR_MIN);
    			y_delta = MAX(MIN(y_delta, SCHAR_MAX), SCHAR_MIN);
    
    			bt_hids_boot_mouse_inp_rep_send(&hids_obj,
    							     conn_mode[i].conn,
    							     NULL,
    							     (int8_t) x_delta,
    							     (int8_t) y_delta,
    							     NULL);
    		} else {
    			uint8_t x_buff[2];
    			uint8_t y_buff[2];
    			uint8_t buffer[INPUT_REP_MOVEMENT_LEN];
    
    			int16_t x = MAX(MIN(x_delta, 0x07ff), -0x07ff);
    			int16_t y = MAX(MIN(y_delta, 0x07ff), -0x07ff);
    
    			/* Convert to little-endian. */
    			sys_put_le16(x, x_buff);
    			sys_put_le16(y, y_buff);
    
    			/* Encode report. */
    			BUILD_ASSERT(sizeof(buffer) == 3,
    					 "Only 2 axis, 12-bit each, are supported");
    
    			buffer[0] = x_buff[0];
    			buffer[1] = (y_buff[0] << 4) | (x_buff[1] & 0x0f);
    			buffer[2] = (y_buff[1] << 4) | (y_buff[0] >> 4);
    
    
    			bt_hids_inp_rep_send(&hids_obj, conn_mode[i].conn,
    						  INPUT_REP_MOVEMENT_INDEX,
    						  buffer, sizeof(buffer), NULL);
    		}
    	}
    }
    
    
    static void mouse_handler(struct k_work *work)
    {
    	struct mouse_pos pos;
    
    	while (!k_msgq_get(&hids_queue, &pos, K_NO_WAIT)) {
    		mouse_movement_send(pos.x_val, pos.y_val);
    	}
    }
    
    
    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);
    }
    
    
    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);
    }
    
    
    static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
    {
    	char addr[BT_ADDR_LE_STR_LEN];
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	printk("Pairing failed conn: %s, reason %d\n", addr, reason);
    }
    
    
    static struct bt_conn_auth_cb conn_auth_callbacks = {
    	.cancel = auth_cancel,
    };
    
    static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
    	.pairing_complete = pairing_complete,
    	.pairing_failed = pairing_failed
    };
    
    
    void button_changed(uint32_t button_state, uint32_t has_changed)
    {
    	bool data_to_send = false;
    	struct mouse_pos pos;
    	uint32_t buttons = button_state & has_changed;
    
    	memset(&pos, 0, sizeof(struct mouse_pos));
    
    	if (buttons & KEY_LEFT_MASK) {
    		pos.x_val -= MOVEMENT_SPEED;
    		printk("%s(): left\n", __func__);
    		data_to_send = true;
    	}
    	if (buttons & KEY_UP_MASK) {
    		pos.y_val -= MOVEMENT_SPEED;
    		printk("%s(): up\n", __func__);
    		data_to_send = true;
    	}
    	if (buttons & KEY_RIGHT_MASK) {
    		pos.x_val += MOVEMENT_SPEED;
    		printk("%s(): right\n", __func__);
    		data_to_send = true;
    	}
    	if (buttons & KEY_DOWN_MASK) {
    		pos.y_val += MOVEMENT_SPEED;
    		printk("%s(): down\n", __func__);
    		data_to_send = true;
    	}
    
    	if (data_to_send) {
    		int err;
    
    		err = k_msgq_put(&hids_queue, &pos, K_NO_WAIT);
    		if (err) {
    			printk("No space in the queue for button pressed\n");
    			return;
    		}
    		if (k_msgq_num_used_get(&hids_queue) == 1) {
    			k_work_submit(&hids_work);
    		}
    	}
    }
    
    
    void configure_buttons(void)
    {
    	int err;
    
    	err = dk_buttons_init(button_changed);
    	if (err) {
    		printk("Cannot init buttons (err: %d)\n", err);
    	}
    }
    
    
    static void bas_notify(void)
    {
    	uint8_t battery_level = bt_bas_get_battery_level();
    
    	battery_level--;
    
    	if (!battery_level) {
    		battery_level = 100U;
    	}
    
    	bt_bas_set_battery_level(battery_level);
    }
    
    
    void main(void)
    {
    	int err;
    
    	printk("Starting Bluetooth Peripheral HIDS mouse example\n");
    
    	err = bt_conn_auth_cb_register(&conn_auth_callbacks);
    	if (err) {
    		printk("Failed to register authorization callbacks.\n");
    		return;
    	}
    
    	err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
    	if (err) {
    		printk("Failed to register authorization info callbacks.\n");
    		return;
    	}
    
    	err = bt_enable(NULL);
    	if (err) {
    		printk("Bluetooth init failed (err %d)\n", err);
    		return;
    	}
    
    	printk("Bluetooth initialized\n");
    
    	/* DIS initialized at system boot with SYS_INIT macro. */
    	hid_init();
    
    	k_work_init(&hids_work, mouse_handler);
    	k_work_init(&adv_work, advertising_process);
    
    	if (IS_ENABLED(CONFIG_SETTINGS)) {
    		settings_load();
    	}
    
    	advertising_start();
    
    	configure_buttons();
    
    	while (1) {
    		k_sleep(K_SECONDS(1));
    		/* Battery level simulation */
    		bas_notify();
    	}
    }
    

    diff from unmodified example to easily see the changes:

    diff --git a/samples/bluetooth/peripheral_hids_mouse/src/main.c b/samples/bluetooth/peripheral_hids_mouse/src/main.c
    index edc81bef8..ab63df8fc 100644
    --- a/samples/bluetooth/peripheral_hids_mouse/src/main.c
    +++ b/samples/bluetooth/peripheral_hids_mouse/src/main.c
    @@ -68,10 +68,6 @@
     /* Key used to move cursor down */
     #define KEY_DOWN_MASK   DK_BTN4_MSK
     
    -/* Key used to accept or reject passkey value */
    -#define KEY_PAIRING_ACCEPT DK_BTN1_MSK
    -#define KEY_PAIRING_REJECT DK_BTN2_MSK
    -
     /* HIDS instance. */
     BT_HIDS_DEF(hids_obj,
     	    INPUT_REP_BUTTONS_LEN,
    @@ -118,16 +114,6 @@ static struct conn_mode {
     
     static struct k_work adv_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);
     
     #if CONFIG_BT_DIRECTED_ADVERTISING
     static void bond_find(const struct bt_bond_info *info, void *user_data)
    @@ -208,25 +194,6 @@ static void advertising_process(struct k_work *work)
     	advertising_continue();
     }
     
    -static 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);
    -	printk("Press Button 1 to confirm, Button 2 to reject.\n");
    -}
    -
     
     static void insert_conn_object(struct bt_conn *conn)
     {
    @@ -314,7 +281,6 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
     }
     
     
    -#ifdef CONFIG_BT_HIDS_SECURITY_ENABLED
     static void security_changed(struct bt_conn *conn, bt_security_t level,
     			     enum bt_security_err err)
     {
    @@ -329,15 +295,12 @@ static void security_changed(struct bt_conn *conn, bt_security_t level,
     			err);
     	}
     }
    -#endif
     
     
     BT_CONN_CB_DEFINE(conn_callbacks) = {
     	.connected = connected,
     	.disconnected = disconnected,
    -#ifdef CONFIG_BT_HIDS_SECURITY_ENABLED
     	.security_changed = security_changed,
    -#endif
     };
     
     
    @@ -550,42 +513,6 @@ static void mouse_handler(struct k_work *work)
     	}
     }
     
    -#if defined(CONFIG_BT_HIDS_SECURITY_ENABLED)
    -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)
     {
    @@ -610,16 +537,6 @@ static void pairing_complete(struct bt_conn *conn, bool bonded)
     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));
     
    @@ -628,8 +545,6 @@ static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
     
     
     static struct bt_conn_auth_cb conn_auth_callbacks = {
    -	.passkey_display = auth_passkey_display,
    -	.passkey_confirm = auth_passkey_confirm,
     	.cancel = auth_cancel,
     };
     
    @@ -637,37 +552,6 @@ static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
     	.pairing_complete = pairing_complete,
     	.pairing_failed = pairing_failed
     };
    -#else
    -static struct bt_conn_auth_cb conn_auth_callbacks;
    -static struct bt_conn_auth_info_cb conn_auth_info_callbacks;
    -#endif /* defined(CONFIG_BT_HIDS_SECURITY_ENABLED) */
    -
    -
    -static void num_comp_reply(bool accept)
    -{
    -	struct pairing_data_mitm pairing_data;
    -	struct bt_conn *conn;
    -
    -	if (k_msgq_get(&mitm_queue, &pairing_data, K_NO_WAIT) != 0) {
    -		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 button_changed(uint32_t button_state, uint32_t has_changed)
    @@ -678,20 +562,6 @@ void button_changed(uint32_t button_state, uint32_t has_changed)
     
     	memset(&pos, 0, sizeof(struct mouse_pos));
     
    -	if (k_msgq_num_used_get(&mitm_queue)) {
    -		if (buttons & KEY_PAIRING_ACCEPT) {
    -			num_comp_reply(true);
    -
    -			return;
    -		}
    -
    -		if (buttons & KEY_PAIRING_REJECT) {
    -			num_comp_reply(false);
    -
    -			return;
    -		}
    -	}
    -
     	if (buttons & KEY_LEFT_MASK) {
     		pos.x_val -= MOVEMENT_SPEED;
     		printk("%s(): left\n", __func__);
    @@ -759,18 +629,16 @@ void main(void)
     
     	printk("Starting Bluetooth Peripheral HIDS mouse example\n");
     
    -	if (IS_ENABLED(CONFIG_BT_HIDS_SECURITY_ENABLED)) {
    -		err = bt_conn_auth_cb_register(&conn_auth_callbacks);
    -		if (err) {
    -			printk("Failed to register authorization callbacks.\n");
    -			return;
    -		}
    +	err = bt_conn_auth_cb_register(&conn_auth_callbacks);
    +	if (err) {
    +		printk("Failed to register authorization callbacks.\n");
    +		return;
    +	}
     
    -		err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
    -		if (err) {
    -			printk("Failed to register authorization info callbacks.\n");
    -			return;
    -		}
    +	err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
    +	if (err) {
    +		printk("Failed to register authorization info callbacks.\n");
    +		return;
     	}
     
     	err = bt_enable(NULL);
    @@ -785,7 +653,6 @@ void main(void)
     	hid_init();
     
     	k_work_init(&hids_work, mouse_handler);
    -	k_work_init(&pairing_work, pairing_process);
     	k_work_init(&adv_work, advertising_process);
     
     	if (IS_ENABLED(CONFIG_SETTINGS)) {
    

Children
  • Hi Einar,

    Thank you very much for looking into this. I used the main.c code exactly as you provided (with nRF Connect SDK 2.3.0) but I got the same result (using Windows 10):

    I also get a similar message output on disconnection from the peripheral sample, however this time it specifies security failed level 1 err 4, and pairing failed reason 4.

    Regards,

    Bryn

  • Hi Bryn,

    I tested on a Windows 10 Enterprise now (version 22H2), and the mouse works as expected on my end. Can you double-check that the issue is not related to bonding information existing on one of the ends? Specifically, make sure to remove the nRF from Bluetooth settings on the PC if you have any there form before, and similarly, on the nRF make sure there are no existing bonding information (the simplest way to do it is perhaps to do a full erase with "nrfjprog --recover" before you program the mouse example.

    If that does not help, perhaps you can make a sniffer trace so that we can see what actually happens on air?

    Einar

  • Hi Einar,

    Your suggestion worked and the peripheral sample now connects with Just Works pairing, after a full erase with "nrfjprog --recover".

    However another problem has arisen from here - when I power cycle or reset the NRF52 DK it does not reconnect automatically to the PC (direct advertising timed out). And if I remove the nRF from Bluetooth settings on the PC and try to reconnect, it returns the same error as before:

    So it connects fine the first time after a full erase, however does not subsequently re-connect after this. It's almost as though it's storing incorrect bonding information?

    Regards,

    Bryn

  • Hi Bryn,

    BrynMorgan said:
    However another problem has arisen from here - when I power cycle or reset the NRF52 DK it does not reconnect automatically to the PC (direct advertising timed out)

    That is odd. I tested this on my end, and the reconnect happens automatically as expected. Here is a log form my computer where that is the case (starting with the nRF booting up after the reset):

    *** Booting Zephyr OS build v3.2.99-ncs2 ***
    Starting Bluetooth Peripheral HIDS mouse example
    I: 6 Sectors of 4096 bytes
    I: alloc wra: 0, f70
    I: data wra: 0, dc
    I: SoftDevice Controller build revision: 
    I: d8 0c 2d 2f 36 ae e2 5c |..-/6..\
    I: 80 26 80 4c 3f 4d 16 53 |.&.L?M.S
    I: 50 96 c7 73             |P..s    
    I: HW Platform: Nordic Semiconductor (0x0002)
    I: HW Variant: nRF52x (0x0002)
    I: Firmware: Standard Bluetooth controller (0x00) Version 216.11532 Build 3803067951
    I: No ID address. App must call settings_load()
    Bluetooth initialized
    I: Identity: F7:4C:A0:04:45:5C (random)
    I: HCI: version 5.3 (0x0c) revision 0x124b, manufacturer 0x0059
    I: LMP: version 5.3 (0x0c) subver 0x124b
    Direct advertising to A0:59:50:DB:B0:13 (public) started
    Direct advertising to A0:59:50:DB:B0:13 (public) timed out
    Regular advertising started
    I: BAS Notifications enabled
    Connected A0:59:50:DB:B0:13 (public)
    Regular advertising started
    Security changed: A0:59:50:DB:B0:13 (public) level 2
    button_changed(): left
    button_changed(): left
    button_changed(): up
    button_changed(): up
    button_changed(): up
    button_changed(): down
    button_changed(): down
    button_changed(): down

    BrynMorgan said:
    And if I remove the nRF from Bluetooth settings on the PC and try to reconnect, it returns the same error as before

    In this case that is expected. Security failed, because the PC has deleted the bond but the nRF has not, and the default configuration is to not allow re-pairing. If you want the nRF to accept bonding again with an already bonded device you need to add CONFIG_BT_SMP_ALLOW_UNAUTH_OVERWRITE=y to prj.conf.

  • Hi Einar,

    It looks like the nRF power cycling connection problem was on my end. Restarting my PC and allowing more time for the nRF to connect automatically after a power cycle has it working properly as you describe.

    And that is good to know regarding the default configuration to not allow re-pairing for unauthenticated bonding. Is this documented anywhere? I cannot seem to find much documentation on Zephyr's BLE connection nuances like this one.

    Overall I consider my issue solved. Many thanks for your help.

    Kind regards,

    Bryn

Related