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

  • Sorry, can you clarify?

    You're telling me the subscribe callback will be called depending on the number of handles that are dedicated to report  descriptors and not when receiving data from an actual report handle?

  • hogp_ready will be called only once when connected happened.

    Then hogp_ready_cb will be called when central receive data from peripheal   cause it's the third param of bt_hogp_rep_subscribe.
    reference here

Reply Children
No Data
Related