Cannot find the correct handle to subscribe to in a HOGP object

Hello!

This is the third time I'm asking something about the same project, but I'm running into problems one after the other.

The current problem is this - the hogp object I have saved of a BLE keyboard I'm trying to read the input of has 6 HID handles.
I'm not sure which handle to subscribe to or if even bt_hogp_rep_subscribe() is the correct funciton to call. 

I've included the whole program and my prj.conf befow, and also shared  the relevant functions as code snippets.

/* --- Header --- */

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

// The main lib itself includes
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>

#include <bluetooth/scan.h>
#include <bluetooth/gatt_dm.h>
#include <bluetooth/services/hogp.h>

#define TARGET_DEVICE_NAME "Flow84@Lofree"

struct bt_conn *target_conn;
struct bt_hogp target_hogp;
struct bt_hogp_rep_info *target_rep = NULL;

LOG_MODULE_REGISTER(ska_logging, LOG_LEVEL_INF);

/* --- End Header --- */

/* --- BLE Connection Handling --- */

static void scan_filter_match(struct bt_scan_device_info *device_info, struct bt_scan_filter_match *filter_match, bool connectable);
static void scan_filter_no_match(struct bt_scan_device_info *device_info, bool connectable);
static void connecting_error(struct bt_scan_device_info *device_info);
static void connecting(struct bt_scan_device_info *device_info, struct bt_conn *conn);

static void exch_mtu_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_exchange_params *params);
static void conn_connected(struct bt_conn *conn, uint8_t err);
static void disconnected_cb(struct bt_conn *conn, uint8_t reason)  { LOG_INF("Disconnected, reason: %d", reason); }

BT_SCAN_CB_INIT(scan_cb, scan_filter_match, scan_filter_no_match, connecting_error, connecting);

static struct bt_conn_cb conn_callbacks = 
{
    .connected    = conn_connected,
    .disconnected = disconnected_cb,
};

static const struct bt_le_scan_param scan_params =
{
    .type     = BT_LE_SCAN_TYPE_ACTIVE,
    .options  = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
    .interval = BT_GAP_SCAN_FAST_INTERVAL,
    .window   = BT_GAP_SCAN_FAST_WINDOW,
};

static const struct bt_scan_init_param init_params = 
{
    .scan_param      = &scan_params,
    .connect_if_match = true,
    .conn_param      = NULL,
};

/* --- End BLE Connection Handling --- */

/* --- GATT Discovery Functions --- */

static void dm_completed(struct bt_gatt_dm *dm, void *context);
static void dm_error(struct bt_conn *conn, int err, void *context);
static void dm_service_not_found(struct bt_conn *conn, void *context);

static struct bt_gatt_dm_cb dm_cbs =
{
    .completed = dm_completed,
    .error_found = dm_error,
    .service_not_found = dm_service_not_found,
};

/* --- End GATT Discovery Functions */

/* --- HOGP Handling --- */

static void hogp_ready(struct bt_hogp *hogp);
static void hogp_init_error(struct bt_hogp *hogp, int error);
static void hogp_pm_update(struct bt_hogp *hogp);

static uint8_t hogp_read_cb(struct bt_hogp *hogp, struct bt_hogp_rep_info *rep, uint8_t err, const uint8_t *data);

static struct bt_hogp_init_params hogp_params =
{
    .ready_cb = hogp_ready,
    .prep_error_cb = hogp_init_error,
    .pm_update_cb = hogp_pm_update,
};



/* --- End HOGP Handling --- */

int main(void)
{
    int err;

    err = bt_enable(NULL);
    if(err) { LOG_INF("Error initializing Bluetooth stack."); return 1; }

    bt_hogp_init(&target_hogp, &hogp_params);

    bt_conn_cb_register(&conn_callbacks);

    // Note: The filtering needs to be defined after the scan object has been initialized
    bt_scan_init(&init_params);
    bt_scan_cb_register(&scan_cb);
    bt_scan_filter_add(BT_SCAN_FILTER_TYPE_UUID, BT_UUID_HIDS);
    bt_scan_filter_enable(BT_SCAN_UUID_FILTER, true);

    bt_scan_start(BT_SCAN_TYPE_SCAN_ACTIVE);

    return 0;
}

static void scan_filter_match(struct bt_scan_device_info *device_info, struct bt_scan_filter_match *filter_match, bool connectable)
{
    char addr_str[BT_ADDR_LE_STR_LEN];
    bt_addr_le_to_str(device_info->recv_info->addr, addr_str, sizeof(addr_str));

    if(filter_match->uuid.match) { LOG_INF("HID Device found at addr %s with RSSI of %d db. The device is %s", addr_str, device_info->recv_info->rssi, connectable ? "connectable." : "not connectable"); }
}

static void scan_filter_no_match(struct bt_scan_device_info *device_info, bool connectable)
{
    // char addr_str[BT_ADDR_LE_STR_LEN];
    // bt_addr_le_to_str(device_info->recv_info->addr, addr_str, sizeof(addr_str));

    // LOG_INF("Non-HID device found at address %s with RSSI of %d db", addr_str, device_info->recv_info->rssi);
}

static void connecting_error(struct bt_scan_device_info *device_info) 
{ 
    char addr_str[BT_ADDR_LE_STR_LEN];
    bt_addr_le_to_str(device_info->recv_info->addr, addr_str, sizeof(addr_str));

    LOG_ERR("Error connecting to target device at address %s.", addr_str); 
    return;
}

// I'm doing quite a bit in the connecting function, but it's the only logical point to start most of the opetations
static void connecting(struct bt_scan_device_info *device_info, struct bt_conn *conn)
{
    int err;

    char addr_str[BT_ADDR_LE_STR_LEN];
    bt_addr_le_to_str(device_info->recv_info->addr, addr_str, sizeof(addr_str));

    LOG_INF("Target device found at address %s, connecting...", addr_str);

    if(!conn) { LOG_ERR("Received NULL pointer for connection object, aborting."); return; }

    target_conn = bt_conn_ref(conn);
    
    LOG_INF("Connected! Connection information saved in the global variable. Now starting GATT discovery");

    // err = bt_gatt_dm_start(conn, BT_UUID_HIDS, &dm_cbs, NULL);
}

static void exch_mtu_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_exchange_params *params)
{
    if (err) {
        LOG_ERR("MTU exchange failed (%u)", err);
        return;
    }
    LOG_INF("MTU exchanged, starting GATT discovery");
    bt_gatt_dm_start(conn, BT_UUID_HIDS, &dm_cbs, NULL);
}

static void conn_connected(struct bt_conn *conn, uint8_t err)
{
    if (err) {
        LOG_ERR("Connection failed (%u)", err);
        return;
    }
    LOG_INF("Connection ready, exchanging MTU");
    static struct bt_gatt_exchange_params exch = { .func = exch_mtu_cb };
    bt_gatt_exchange_mtu(conn, &exch);
}


static void dm_completed(struct bt_gatt_dm *dm, void *context) 
{ 
    LOG_INF("HID Service discovery complete!");

    int err = bt_hogp_handles_assign(dm, &target_hogp);
    if (err) {
        LOG_ERR("Failed to assign HOGP handles: %d", err);
        bt_gatt_dm_data_release(dm);
        return;
    }

    bt_gatt_dm_data_print(dm);
    // const struct bt_hogp_rep_info *rep = NULL;

    bt_gatt_dm_data_release(dm);
}

static void dm_error(struct bt_conn *conn, int err, void *context) { LOG_INF("Error when discovering HID service: %d", err); }

static void dm_service_not_found(struct bt_conn *conn, void *context) { LOG_INF("HID service not found."); }

static void hogp_ready(struct bt_hogp *hogp) 
{ 
    LOG_INF("The HOG profile has been created successfully. Now attempting to subscribe...");
    
    struct bt_hogp_rep_info *rep = bt_hogp_rep_find(hogp, BT_HIDS_REPORT_TYPE_INPUT, 1);

    size_t hid = bt_hogp_rep_count(hogp);

    LOG_INF("There are %zu HID reports in the HOGP object.", hid);
    
    int err = bt_hogp_rep_subscribe(hogp, rep, hogp_read_cb);
    if (err) { LOG_INF("There was an error subscribing to the hogp read report: %d", err); }

    
}

static void hogp_init_error(struct bt_hogp *hogp, int error) { LOG_INF("Error initializing the HOGP struct: %d", error); } 

static void hogp_pm_update(struct bt_hogp *hogp) {  }

static uint8_t hogp_read_cb(struct bt_hogp *hogp, struct bt_hogp_rep_info *rep, uint8_t err, const uint8_t *data)
{
    if (err) {
        LOG_ERR("Error in report notification: %d", err);
        return 0;
    }

    size_t size = bt_hogp_rep_size(rep);
    LOG_HEXDUMP_INF(data, size, "Received input report (keycodes):");
    return 0;
}
3513.prj.conf

The hogp ready callback that is subscribing to the handle and invoking the read callback:
static void hogp_ready(struct bt_hogp *hogp) 
{ 
    LOG_INF("The HOG profile has been created successfully. Now attempting to subscribe...");
    
    struct bt_hogp_rep_info *rep = bt_hogp_rep_find(hogp, BT_HIDS_REPORT_TYPE_INPUT, 1);

    size_t hid = bt_hogp_rep_count(hogp);

    LOG_INF("There are %zu HID reports in the HOGP object.", hid);
    
    int err = bt_hogp_rep_subscribe(hogp, rep, hogp_read_cb);
    if (err) { LOG_INF("There was an error subscribing to the hogp read report: %d", err); }

    
}


the read callback itself:
static uint8_t hogp_read_cb(struct bt_hogp *hogp, struct bt_hogp_rep_info *rep, uint8_t err, const uint8_t *data)
{
    if (err) {
        LOG_ERR("Error in report notification: %d", err);
        return 0;
    }

    size_t size = bt_hogp_rep_size(rep);
    LOG_HEXDUMP_INF(data, size, "Received input report (keycodes):");
    return 0;
}


Currently, the callback does get called, meaning the subscribe function does succeed, but it gets called once and that's it.
As far as I understand it, this callback should be called every time the keyboard sends out a report on that handle.

Any help with identifying the correct handle to subscribe to and what the correct flow for subscribing is would be greatly appreciated!

Parents
  • Hi Orlin,

    I wonder  when connected to peripheal,did this "There are %zu HID reports in the HOGP object" printout?

    And how your peripheal side send data?

  • Hello!

    Yes, it printed out, saying there are 6 HID reports, however when tested (by changing the id in the bt_hogp_rep_find() function, it only successfully connected from 1 to 5, with 6 giving the same error code -22 as it did in one of my previous tickets - error -22 when subscribing to HOGP reports 

    none of the handles (I'm honestly not sure if calling them handles is correct as they're ids in a struct) work as intended (calling the read_cb() when I press a key) which leads me to think I'm not using the correct function. 

    I'll test out with a few more keyboards today to see if something changes, and last chance I'll flash another dk I have (good thing I ordered 2) with a peripheral sample and test against that to see if the number of reports/behaviour changes. I also plan to write some more info functions to see what the different handles are since I"m more or less shooting blind right now XD

  • Hi,

    About your question why it's just subscribe just once.

    As I know, how many times it's subscribed is based on peripheral side's report_map id.

    If peripheral has two or more report id ,it will subscribe same times.

    Like this is only one id 1 .

    	static const uint8_t report_map[] = {
        0x05, 0x01, // Usage Page (Generic Desktop)
        0x09, 0x06, // Usage (Keyboard)
        0xA1, 0x01, // Collection (Application)
        0x85, INPUT_REP_KEYS_REF_ID, // Report Id (1)
        0x05, 0x07, // Usage Page (Keyboard)
        0x19, 0xE0, // Usage Minimum (Keyboard Left Control)
        0x29, 0xE7, // Usage Maximum (Keyboard Right GUI)
        0x15, 0x00, // Logical minimum (0)
        0x25, 0x01, // Logical maximum (1)
        0x75, 0x01, // Report Size (1)
        0x95, 0x08,  // Report Count (8)
        0x81, 0x02, // Input (Data,Value,Absolute,Bit Field)
        0x95, 0x01, // Report Count (1)
        0x75, 0x08, // Report Size (8)
        0x81, 0x01, // Input (Constant,Array,Absolute,Bit Field)
        0x95, 0x05, // Report Count (5)
        0x75, 0x01, // Report Size (1)
    
        0x05, 0x08, // Usage Page (LEDs)
        0x19, 0x01, // Usage Minimum
        0x29, 0x05, // Usage Maximum
        0x91, 0x02, // Output (Data,Value,Absolute,Non-volatile,Bit Field)
        0x95, 0x01, // Report Count (1)
        0x75, 0x03, // Report Size (3)
        
    	0x91, 0x01, // Output (Constant,Array,Absolute,Non-volatile,Bit Field)
        0x95, 0x06, // Report Count (6)
        0x75, 0x08, // Report Size (8)
        0x15, 0x00, // Logical minimum (0)
        0x25, 0x65, // Logical maximum (101)
    
        0x05, 0x07, // Usage Page (Keyboard)
        0x19, 0x00, // Usage Minimum (No event indicated)
        0x29, 0x65, // Usage Maximum (Keyboard Application)
        0x81, 0x00, // Input (Data,Array,Absolute,Bit Field)
        0xC0,       // End Collection (Application)
    };

Reply
  • Hi,

    About your question why it's just subscribe just once.

    As I know, how many times it's subscribed is based on peripheral side's report_map id.

    If peripheral has two or more report id ,it will subscribe same times.

    Like this is only one id 1 .

    	static const uint8_t report_map[] = {
        0x05, 0x01, // Usage Page (Generic Desktop)
        0x09, 0x06, // Usage (Keyboard)
        0xA1, 0x01, // Collection (Application)
        0x85, INPUT_REP_KEYS_REF_ID, // Report Id (1)
        0x05, 0x07, // Usage Page (Keyboard)
        0x19, 0xE0, // Usage Minimum (Keyboard Left Control)
        0x29, 0xE7, // Usage Maximum (Keyboard Right GUI)
        0x15, 0x00, // Logical minimum (0)
        0x25, 0x01, // Logical maximum (1)
        0x75, 0x01, // Report Size (1)
        0x95, 0x08,  // Report Count (8)
        0x81, 0x02, // Input (Data,Value,Absolute,Bit Field)
        0x95, 0x01, // Report Count (1)
        0x75, 0x08, // Report Size (8)
        0x81, 0x01, // Input (Constant,Array,Absolute,Bit Field)
        0x95, 0x05, // Report Count (5)
        0x75, 0x01, // Report Size (1)
    
        0x05, 0x08, // Usage Page (LEDs)
        0x19, 0x01, // Usage Minimum
        0x29, 0x05, // Usage Maximum
        0x91, 0x02, // Output (Data,Value,Absolute,Non-volatile,Bit Field)
        0x95, 0x01, // Report Count (1)
        0x75, 0x03, // Report Size (3)
        
    	0x91, 0x01, // Output (Constant,Array,Absolute,Non-volatile,Bit Field)
        0x95, 0x06, // Report Count (6)
        0x75, 0x08, // Report Size (8)
        0x15, 0x00, // Logical minimum (0)
        0x25, 0x65, // Logical maximum (101)
    
        0x05, 0x07, // Usage Page (Keyboard)
        0x19, 0x00, // Usage Minimum (No event indicated)
        0x29, 0x65, // Usage Maximum (Keyboard Application)
        0x81, 0x00, // Input (Data,Array,Absolute,Bit Field)
        0xC0,       // End Collection (Application)
    };

Children
Related