Inquiry on HID Mouse Functionality with iOS on Nordic nRF52840 Dongle

Hi DevZone Team,

I hope this message finds you well. I am reaching out to seek your assistance regarding the HID mouse implementation we are working on using the Nordic nRF52840 dongle.

Current Status

We have successfully installed the SDK toolchain and flashed the peripheral_hid_mouse zephyr sample main.c firmware onto the Nordic nRF52840 dongle. The device is currently functioning as expected with Android devices. We have tested basic movements, and the mouse is moving and appearing correctly on the screen.

Issue with iOS

However, we are encountering significant issues when attempting to connect the dongle to iOS devices. Here are the specific challenges we are facing:

  • Bluetooth Connection: The dongle successfully establishes a Bluetooth connection with the iOS device.
  • No Mouse Pointer: Despite the connection, the mouse pointer does not appear on the iOS screen.
  • No Movement Registration: We are sending movement commands through the mouse_movement_send function, but the iOS device does not register any mouse movements.
  • No Error Messages: There are no error messages or notifications that indicate what might be wrong during the connection or operation.

Request for Assistance

Given these circumstances, we would appreciate any insights or guidance you can provide regarding potential compatibility issues or specific requirements for HID mouse implementations on iOS devices.

Additionally, if there are any specific documentation references or troubleshooting steps you recommend, it would be incredibly helpful.


Parents
  • Hi

    Do you have any sort of pairing/bonding procedure to encrypt the connection here? If I recall correctly, iOS requires some security for HID devices and that's my guess as to why it's not working by default. Can you share some details as to what SDK version you're working on and what changes you've made to the sample project on your end (if any)?

    Best regards,

    Simon

Reply
  • Hi

    Do you have any sort of pairing/bonding procedure to encrypt the connection here? If I recall correctly, iOS requires some security for HID devices and that's my guess as to why it's not working by default. Can you share some details as to what SDK version you're working on and what changes you've made to the sample project on your end (if any)?

    Best regards,

    Simon

Children
  • Hi Simon,
    Thank you for your response. Please check the version of both toolchain (v2.6.1) and SDK(v2.6.1) below-



    and also disabled BLE security for hid device by adding default value n here in -

    config BT_HIDS_SECURITY_ENABLED
    bool "Enable security"
    default n
    select BT_SMP
    select BT_SETTINGS
    select FLASH
    select FLASH_PAGE_LAYOUT
    select FLASH_MAP
    select NVS
    select SETTINGS
    select CONFIG_BT_HIDS_DEFAULT_PERM_RW_ENCRYPT
    help
    "Enable BLE security for the HIDS service"

    i did this because when working on android it used to ask to enter passkey and not connect. Along with this I had to also comment out the following lines in -
    1) in pairing_process()

    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");
    }

    2) bt_conn_auth_cb conn_auth_callbacks

    static struct bt_conn_auth_cb conn_auth_callbacks = {
    // .passkey_display = auth_passkey_display,
    // .passkey_confirm = auth_passkey_confirm,
    // .cancel = auth_cancel,
    };



    3) 
    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);
    // }
    }

    lastly I added my own while loop in main to test some send movements -

    while (1) {
    mouse_movement_send((int16_t)10, (int16_t)0);
    printk("Sent Mouse Movement Right");
    k_sleep(K_SECONDS(5));
    mouse_movement_send((int16_t)0, (int16_t)10);
    printk("Sent Mouse Movement Down");
    k_sleep(K_SECONDS(5));
    mouse_movement_send((int16_t)-10, (int16_t)0);
    printk("Sent Mouse Movement Left");
    k_sleep(K_SECONDS(5));
    mouse_movement_send((int16_t)0, (int16_t)-10);
    printk("Sent Mouse Movement Up");
    k_sleep(K_SECONDS(5));

    // k_sleep(K_SECONDS(1));
    /* Battery level simulation */
    // bas_notify();
    }

    For your reference I will add both main.c and prj.conf here 
    Also please let me know if hid report also is expected to be different for iOS??

    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
    
    /* 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,
    	    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;
    
    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)
    {
    	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 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)
    {
    	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();
    }
    
    
    #ifdef CONFIG_BT_HIDS_SECURITY_ENABLED
    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);
    	}
    }
    #endif
    
    
    BT_CONN_CB_DEFINE(conn_callbacks) = {
    	.connected = connected,
    	.disconnected = disconnected,
    #ifdef CONFIG_BT_HIDS_SECURITY_ENABLED
    	.security_changed = security_changed,
    #endif
    };
    
    
    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[DIV_ROUND_UP(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);
    	}
    }
    
    #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)
    {
    	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];
    	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\n", addr, reason);
    }
    
    
    static struct bt_conn_auth_cb conn_auth_callbacks = {
    	// .passkey_display = auth_passkey_display,
    	// .passkey_confirm = auth_passkey_confirm,
    	// .cancel = auth_cancel,
    };
    
    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)
    {
    	bool data_to_send = false;
    	struct mouse_pos pos;
    	uint32_t buttons = button_state & has_changed;
    
    	memset(&pos, 0, sizeof(struct mouse_pos));
    
    	if (IS_ENABLED(CONFIG_BT_HIDS_SECURITY_ENABLED)) {
    		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__);
    		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);
    }
    
    
    int main(void)
    {
    	int err;
    
    	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 0;
    		}
    
    		err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
    		if (err) {
    			printk("Failed to register authorization info callbacks.\n");
    			return 0;
    		}
    	}
    
    	/* DIS initialized at system boot with SYS_INIT macro. */
    	hid_init();
    
    	err = bt_enable(NULL);
    	if (err) {
    		printk("Bluetooth init failed (err %d)\n", err);
    		return 0;
    	}
    
    	printk("Bluetooth initialized\n");
    
    	k_work_init(&hids_work, mouse_handler);
    	k_work_init(&adv_work, advertising_process);
    	if (IS_ENABLED(CONFIG_BT_HIDS_SECURITY_ENABLED)) {
    		k_work_init(&pairing_work, pairing_process);
    	}
    
    	if (IS_ENABLED(CONFIG_SETTINGS)) {
    		settings_load();
    	}
    
    	advertising_start();
    
    	configure_buttons();
    
    	while (1) {
    		mouse_movement_send((int16_t)10, (int16_t)0);
    		printk("Sent Mouse Movement Right");
    		k_sleep(K_SECONDS(5));
    		mouse_movement_send((int16_t)0, (int16_t)10);
    		printk("Sent Mouse Movement Down");
    		k_sleep(K_SECONDS(5));
    		mouse_movement_send((int16_t)-10, (int16_t)0);
    		printk("Sent Mouse Movement Left");
    		k_sleep(K_SECONDS(5));
    		mouse_movement_send((int16_t)0, (int16_t)-10); 
    		printk("Sent Mouse Movement Up");
    		k_sleep(K_SECONDS(5));
    
    		// k_sleep(K_SECONDS(1));
    		/* Battery level simulation */
    		// bas_notify();
    	}
    }


    prj.conf
    #
    # Copyright (c) 2019 Nordic Semiconductor ASA
    #
    # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
    #
    CONFIG_NCS_SAMPLES_DEFAULTS=y
    
    CONFIG_BT=y
    CONFIG_BT_SMP=y
    CONFIG_BT_DEBUG_LOG=y
    CONFIG_BT_MAX_CONN=2
    CONFIG_BT_MAX_PAIRED=2
    CONFIG_BT_L2CAP_TX_BUF_COUNT=5
    CONFIG_BT_PERIPHERAL=y
    CONFIG_BT_DEVICE_NAME="Nordic_HIDS_mouse_test8"
    CONFIG_BT_DEVICE_APPEARANCE=962
    
    CONFIG_BT_BAS=y
    CONFIG_BT_HIDS=y
    CONFIG_BT_HIDS_MAX_CLIENT_COUNT=2
    CONFIG_BT_GATT_UUID16_POOL_SIZE=40
    CONFIG_BT_GATT_CHRC_POOL_SIZE=20
    
    CONFIG_BT_CONN_CTX=y
    
    
    CONFIG_BT_DIS=y
    CONFIG_BT_DIS_PNP=y
    CONFIG_BT_DIS_MANUF="NordicSemiconductor"
    CONFIG_BT_DIS_PNP_VID_SRC=2
    CONFIG_BT_DIS_PNP_VID=0x1915
    CONFIG_BT_DIS_PNP_PID=0xEEEE
    CONFIG_BT_DIS_PNP_VER=0x0100
    
    CONFIG_MAIN_STACK_SIZE=1536
    CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
    
    CONFIG_DK_LIBRARY=y
    

Related