hids_client: merge states for boot and report mode

This commit is contained in:
Milanka Ringwald 2021-03-22 12:12:43 +01:00
parent 84b19b6775
commit 021192e149
6 changed files with 512 additions and 309 deletions

View File

@ -328,7 +328,7 @@ hog_mouse_demo: hog_mouse_demo.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SER
hog_boot_host_demo: ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_CLIENT_OBJ} hog_boot_host_demo.c hog_boot_host_demo: ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_CLIENT_OBJ} hog_boot_host_demo.c
${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@
hog_host_demo: ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_CLIENT_OBJ} hog_host_demo.c hog_host_demo: ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_CLIENT_OBJ} btstack_hid_parser.o hid.o hog_host_demo.c
${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@
sm_pairing_peripheral: sm_pairing_peripheral.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SERVER_OBJ} ${GATT_CLIENT_OBJ} sm_pairing_peripheral.c sm_pairing_peripheral: sm_pairing_peripheral.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SERVER_OBJ} ${GATT_CLIENT_OBJ} sm_pairing_peripheral.c

View File

@ -35,15 +35,15 @@
* *
*/ */
#define BTSTACK_FILE__ "hog_boot_host_demo.c" #define BTSTACK_FILE__ "hog_host_demo.c"
/* /*
* hog_boot_host_demo.c * hog_host_demo.c
*/ */
/* EXAMPLE_START(hog_boot_host_demo): HID Boot Host LE /* EXAMPLE_START(hog_host_demo): HID Host LE
* *
* @text This example implements a minimal HID-over-GATT Boot Host. It scans for LE HID devices, connects to it, * @text This example implements a minimal HID-over-GATT Host. It scans for LE HID devices, connects to it,
* discovers the Characteristics relevant for the HID Service and enables Notifications on them. * discovers the Characteristics relevant for the HID Service and enables Notifications on them.
* It then dumps all Boot Keyboard and Mouse Input Reports * It then dumps all Boot Keyboard and Mouse Input Reports
*/ */
@ -77,6 +77,10 @@ static enum {
static le_device_addr_t remote_device; static le_device_addr_t remote_device;
static hci_con_handle_t connection_handle; static hci_con_handle_t connection_handle;
static uint16_t hids_cid; static uint16_t hids_cid;
static hid_protocol_mode_t protocol_mode = HID_PROTOCOL_MODE_REPORT;
// SDP
static uint8_t hid_descriptor_storage[500];
// used to implement connection timeout and reconnect timer // used to implement connection timeout and reconnect timer
static btstack_timer_source_t connection_timer; static btstack_timer_source_t connection_timer;
@ -138,31 +142,56 @@ static const uint8_t keytable_us_shift[] = {
'6', '7', '8', '9', '0', '.', 0xb1, /* 97-100 */ '6', '7', '8', '9', '0', '.', 0xb1, /* 97-100 */
}; };
/**
* @section HOG Boot Keyboard Handler
* @text Boot Keyboard Input Report contains a report of format
* [ modifier, reserved, 6 x usage for key 1..6 from keyboard usage]
* Track new usages, map key usage to actual character and simulate terminal
*/
/* last_keys stores keyboard report to detect new key down events */
#define NUM_KEYS 6 #define NUM_KEYS 6
static uint8_t last_keys[NUM_KEYS]; static uint8_t last_keys[NUM_KEYS];
static void hid_handle_input_report(uint8_t service_index, const uint8_t * report, uint16_t report_len){
// check if HID Input Report
if (report_len < 1) return;
btstack_hid_parser_t parser;
switch (protocol_mode){
case HID_PROTOCOL_MODE_BOOT:
btstack_hid_parser_init(&parser,
hid_get_boot_descriptor_data(),
hid_get_boot_descriptor_len(),
HID_REPORT_TYPE_INPUT, report, report_len);
break;
static void handle_boot_keyboard_event(const uint8_t * report, uint16_t report_len){ default:
UNUSED(report_len); btstack_hid_parser_init(&parser,
hids_client_descriptor_storage_get_descriptor_data(hids_cid, service_index),
hids_client_descriptor_storage_get_descriptor_len(hids_cid, service_index),
HID_REPORT_TYPE_INPUT, report, report_len);
break;
}
int shift = 0;
uint8_t new_keys[NUM_KEYS]; uint8_t new_keys[NUM_KEYS];
memset(new_keys, 0, sizeof(new_keys)); memset(new_keys, 0, sizeof(new_keys));
int new_keys_count = 0; int new_keys_count = 0;
while (btstack_hid_parser_has_more(&parser)){
bool shift = (report[0] & 0x22) != 0; uint16_t usage_page;
uint16_t usage;
uint8_t key_index; int32_t value;
for (key_index = 0; key_index < NUM_KEYS; key_index++){ btstack_hid_parser_get_field(&parser, &usage_page, &usage, &value);
if (usage_page != 0x07) continue;
uint16_t usage = report[2 + key_index]; switch (usage){
if (usage == 0) continue; case 0xe1:
case 0xe6:
if (value){
shift = 1;
}
continue;
case 0x00:
continue;
default:
break;
}
if (usage >= sizeof(keytable_us_none)) continue; if (usage >= sizeof(keytable_us_none)) continue;
// store new keys // store new keys
@ -177,7 +206,6 @@ static void handle_boot_keyboard_event(const uint8_t * report, uint16_t report_l
} }
if (usage == 0) continue; if (usage == 0) continue;
// lookup character based on usage + shift modifier
uint8_t key; uint8_t key;
if (shift){ if (shift){
key = keytable_us_shift[usage]; key = keytable_us_shift[usage];
@ -185,36 +213,15 @@ static void handle_boot_keyboard_event(const uint8_t * report, uint16_t report_l
key = keytable_us_none[usage]; key = keytable_us_none[usage];
} }
if (key == CHAR_ILLEGAL) continue; if (key == CHAR_ILLEGAL) continue;
if (key == CHAR_BACKSPACE){ if (key == CHAR_BACKSPACE){
printf("\b \b"); // go back one char, print space, go back one char again printf("\b \b"); // go back one char, print space, go back one char again
continue; continue;
} }
printf("%c", key); printf("%c", key);
} }
// store current as last report
memcpy(last_keys, new_keys, NUM_KEYS); memcpy(last_keys, new_keys, NUM_KEYS);
} }
/**
* @section HOG Boot Mouse Handler
* @text Boot Mouse Input Report contains a report of format
* [ buttons, dx, dy, dz = scroll wheel]
* Decode packet and print on stdout
*
* @param report
* @param report_len
*/
static void handle_boot_mouse_event(const uint8_t * report, uint16_t report_len){
UNUSED(report_len);
uint8_t buttons = report[0];
int8_t dx = (int8_t) report[1];
int8_t dy = (int8_t) report[2];
int8_t dwheel = (int8_t) report[3];
printf("Mouse: %i, %i - wheel %i - buttons 0x%02x\n", dx, dy, dwheel, buttons);
}
/** /**
* @section Test if advertisement contains HID UUID * @section Test if advertisement contains HID UUID
* @param packet * @param packet
@ -321,7 +328,6 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
UNUSED(size); UNUSED(size);
uint8_t status; uint8_t status;
uint8_t report_id;
if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META){ if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META){
return; return;
@ -349,22 +355,12 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
break; break;
} }
break; break;
case GATTSERVICE_SUBEVENT_HID_REPORT: case GATTSERVICE_SUBEVENT_HID_REPORT:
report_id = gattservice_subevent_hid_report_get_report_id(packet); hid_handle_input_report(
switch (report_id){ gattservice_subevent_hid_report_get_service_index(packet),
case HID_BOOT_MODE_MOUSE_ID: gattservice_subevent_hid_report_get_report(packet),
handle_boot_mouse_event( gattservice_subevent_hid_report_get_report_len(packet));
gattservice_subevent_hid_report_get_report(packet),
gattservice_subevent_hid_report_get_report_len(packet));
break;
case HID_BOOT_MODE_KEYBOARD_ID:
handle_boot_keyboard_event(
gattservice_subevent_hid_report_get_report(packet),
gattservice_subevent_hid_report_get_report_len(packet));
break;
default:
break;
}
break; break;
default: default:
@ -442,7 +438,7 @@ static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *pack
printf("Search for HID service.\n"); printf("Search for HID service.\n");
app_state = W4_HID_CLIENT_CONNECTED; app_state = W4_HID_CLIENT_CONNECTED;
status = hids_client_connect(connection_handle, handle_gatt_client_event, HID_PROTOCOL_MODE_REPORT, &hids_cid); status = hids_client_connect(connection_handle, handle_gatt_client_event, protocol_mode, &hids_cid);
if (status != ERROR_CODE_SUCCESS){ if (status != ERROR_CODE_SUCCESS){
printf("HID client connection failed, status 0x%02x\n", status); printf("HID client connection failed, status 0x%02x\n", status);
} }
@ -514,7 +510,7 @@ int btstack_main(int argc, const char * argv[]){
(void)argc; (void)argc;
(void)argv; (void)argv;
/* LISTING_START(HogBootHostSetup): HID-over-GATT Boot Host Setup */ /* LISTING_START(HogBootHostSetup): HID-over-GATT Host Setup */
// register for events from HCI // register for events from HCI
hci_event_callback_registration.callback = &packet_handler; hci_event_callback_registration.callback = &packet_handler;
@ -531,7 +527,8 @@ int btstack_main(int argc, const char * argv[]){
l2cap_init(); l2cap_init();
sm_init(); sm_init();
gatt_client_init(); gatt_client_init();
hids_client_init();
hids_client_init(hid_descriptor_storage, sizeof(hid_descriptor_storage));
/* LISTING_END */ /* LISTING_END */

View File

@ -153,7 +153,10 @@ static void hids_client_descriptor_storage_delete(hids_client_t * client){
const uint8_t * hids_client_descriptor_storage_get_descriptor_data(uint16_t hids_cid, uint8_t service_index){ const uint8_t * hids_client_descriptor_storage_get_descriptor_data(uint16_t hids_cid, uint8_t service_index){
hids_client_t * client = hids_get_client_for_cid(hids_cid); hids_client_t * client = hids_get_client_for_cid(hids_cid);
if (!client){ if (client == NULL){
return NULL;
}
if (service_index >= client->num_instances){
return NULL; return NULL;
} }
return &hids_client_descriptor_storage[client->services[service_index].hid_descriptor_offset]; return &hids_client_descriptor_storage[client->services[service_index].hid_descriptor_offset];
@ -161,7 +164,10 @@ const uint8_t * hids_client_descriptor_storage_get_descriptor_data(uint16_t hids
uint16_t hids_client_descriptor_storage_get_descriptor_len(uint16_t hids_cid, uint8_t service_index){ uint16_t hids_client_descriptor_storage_get_descriptor_len(uint16_t hids_cid, uint8_t service_index){
hids_client_t * client = hids_get_client_for_cid(hids_cid); hids_client_t * client = hids_get_client_for_cid(hids_cid);
if (!client){ if (client == NULL){
return 0;
}
if (service_index >= client->num_instances){
return 0; return 0;
} }
return client->services[service_index].hid_descriptor_len; return client->services[service_index].hid_descriptor_len;
@ -198,24 +204,8 @@ static uint8_t find_report_index_for_report_id_and_type(hids_client_t * client,
return HIDS_CLIENT_INVALID_REPORT_INDEX; return HIDS_CLIENT_INVALID_REPORT_INDEX;
} }
static hids_client_report_t * find_report_for_report_id_and_type(hids_client_t * client, uint8_t report_id, hid_report_type_t report_type){ static uint8_t hids_client_add_characteristic(hids_client_t * client, gatt_client_characteristic_t * characteristic, uint8_t report_id, hid_report_type_t report_type, bool boot_report){
uint8_t i;
for (i = 0; i < client->num_reports; i++){
if ( (client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){
return &client->reports[i];
}
}
return NULL;
}
static hids_client_report_t * get_boot_mouse_input_report(hids_client_t * client){
return find_report_for_report_id_and_type(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT);
}
static hids_client_report_t * get_boot_keyboard_input_report(hids_client_t * client){
return find_report_for_report_id_and_type(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT);
}
static uint8_t hids_client_add_characteristic(hids_client_t * client, gatt_client_characteristic_t * characteristic, uint8_t report_id, hid_report_type_t report_type){
uint8_t report_index = find_report_index_for_value_handle(client, characteristic->value_handle); uint8_t report_index = find_report_index_for_value_handle(client, characteristic->value_handle);
if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){ if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
return report_index; return report_index;
@ -230,9 +220,10 @@ static uint8_t hids_client_add_characteristic(hids_client_t * client, gatt_clien
client->reports[report_index].service_index = client->service_index; client->reports[report_index].service_index = client->service_index;
client->reports[report_index].report_id = report_id; client->reports[report_index].report_id = report_id;
client->reports[report_index].report_type = report_type; client->reports[report_index].report_type = report_type;
client->reports[report_index].boot_report = boot_report;
// log_info("add index %d, id %d, type %d, value handle 0x%02x", report_index, report_id, report_type, characteristic->value_handle); // log_info("add index %d, id %d, type %d, value handle 0x%02x", report_index, report_id, report_type, characteristic->value_handle);
log_info("add index %d, id %d, type %d, value handle 0x%02x", report_index, report_id, report_type, characteristic->value_handle); log_info("add index %d, id %d, type %d, value handle 0x%02x, properties 0x%02x", report_index, report_id, report_type, characteristic->value_handle, characteristic->properties);
client->num_reports++; client->num_reports++;
return report_index; return report_index;
} else { } else {
@ -247,13 +238,24 @@ static void hids_client_get_characteristic_for_report_index(hids_client_t * clie
characteristic->properties = client->reports[report_index].properties; characteristic->properties = client->reports[report_index].properties;
} }
static uint8_t hids_client_get_characteristic(hids_client_t * client, uint8_t report_id, hid_report_type_t report_type, gatt_client_characteristic_t * characteristic){ static bool hid_clients_has_reports_in_report_mode(hids_client_t * client){
uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, report_type); uint8_t i;
if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){ for (i = 0; i < client->num_reports; i++){
return report_index; if (!client->reports[i].boot_report){
return true;
}
} }
hids_client_get_characteristic_for_report_index(client, report_index, characteristic); return false;
return report_index; }
static bool hid_clients_has_reports_in_boot_mode(hids_client_t * client){
uint8_t i;
for (i = 0; i < client->num_reports; i++){
if (client->reports[i].boot_report){
return true;
}
}
return false;
} }
static uint8_t hids_client_get_next_active_report_map_index(hids_client_t * client){ static uint8_t hids_client_get_next_active_report_map_index(hids_client_t * client){
@ -294,19 +296,19 @@ static uint8_t hids_client_get_next_active_report_map_uuid_index(hids_client_t *
uint8_t i; uint8_t i;
uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX; uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX;
for (i = client->active_index; i < client->num_reports; i++){ for (i = client->report_index; i < client->num_reports; i++){
hids_client_report_t report = client->reports[i]; hids_client_report_t report = client->reports[i];
if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_MAP_ID){ if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_MAP_ID){
index = i; index = i;
break; break;
} }
} }
client->active_index = index; client->report_index = index;
return index; return index;
} }
static bool hids_client_report_query_next_report_map_uuid(hids_client_t * client){ static bool hids_client_report_query_next_report_map_uuid(hids_client_t * client){
client->active_index++; client->report_index++;
if (hids_client_get_next_active_report_map_uuid_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){ if (hids_client_get_next_active_report_map_uuid_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID; client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID;
return true; return true;
@ -317,7 +319,7 @@ static bool hids_client_report_query_next_report_map_uuid(hids_client_t * client
static bool hids_client_report_map_uuid_query_init(hids_client_t * client){ static bool hids_client_report_map_uuid_query_init(hids_client_t * client){
client->active_index = 0; client->report_index = 0;
if (hids_client_get_next_active_report_map_uuid_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){ if (hids_client_get_next_active_report_map_uuid_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID; client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID;
@ -329,19 +331,35 @@ static bool hids_client_report_map_uuid_query_init(hids_client_t * client){
static uint8_t hids_client_get_next_report_index(hids_client_t * client){ static uint8_t hids_client_get_next_report_index(hids_client_t * client){
uint8_t i; uint8_t i;
uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX; uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX;
for (i = client->active_index; i < client->num_reports; i++){ switch (client->protocol_mode){
hids_client_report_t report = client->reports[i]; case HID_PROTOCOL_MODE_REPORT:
if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_ID){ for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){
index = i; hids_client_report_t report = client->reports[i];
if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_ID){
index = i;
client->service_index = report.service_index;
}
}
break;
case HID_PROTOCOL_MODE_BOOT:
for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){
hids_client_report_t report = client->reports[i];
if (report.boot_report){
index = i;
client->service_index = report.service_index;
}
}
break;
default:
break; break;
}
} }
client->active_index = index;
client->report_index = index;
return index; return index;
} }
static bool hids_client_report_query_next_report(hids_client_t * client){ static bool hids_client_report_query_next_report(hids_client_t * client){
client->active_index++; client->report_index++;
if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){ if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
client->state = HIDS_CLIENT_STATE_W2_FIND_REPORT; client->state = HIDS_CLIENT_STATE_W2_FIND_REPORT;
return true; return true;
@ -350,8 +368,7 @@ static bool hids_client_report_query_next_report(hids_client_t * client){
} }
static bool hids_client_report_query_init(hids_client_t * client){ static bool hids_client_report_query_init(hids_client_t * client){
client->service_index = 0; client->report_index = 0;
client->active_index = 0;
if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){ if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
client->state = HIDS_CLIENT_STATE_W2_FIND_REPORT; client->state = HIDS_CLIENT_STATE_W2_FIND_REPORT;
@ -360,6 +377,62 @@ static bool hids_client_report_query_init(hids_client_t * client){
return false; return false;
} }
static uint8_t hids_client_get_next_notification_report_index(hids_client_t * client){
uint8_t i;
uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX;
switch (client->protocol_mode){
case HID_PROTOCOL_MODE_REPORT:
for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){
hids_client_report_t report = client->reports[i];
if (report.report_type != HID_REPORT_TYPE_INPUT){
continue;
}
if (!report.boot_report){
index = i;
}
}
break;
case HID_PROTOCOL_MODE_BOOT:
for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){
hids_client_report_t report = client->reports[i];
if (report.report_type != HID_REPORT_TYPE_INPUT){
continue;
}
if (report.boot_report){
index = i;
}
}
break;
default:
break;
}
client->report_index = index;
return index;
}
static bool hids_client_report_next_notification_report_index(hids_client_t * client){
client->report_index++;
if (hids_client_get_next_notification_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
client->state = HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS;
return true;
}
return false;
}
static bool hids_client_report_notifications_init(hids_client_t * client){
client->report_index = 0;
if (hids_client_get_next_notification_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){
client->state = HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS;
return true;
}
return false;
}
static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){ static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){
hids_client_t * client = btstack_memory_hids_client_get(); hids_client_t * client = btstack_memory_hids_client_get();
if (!client){ if (!client){
@ -375,6 +448,12 @@ static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t
} }
static void hids_finalize_client(hids_client_t * client){ static void hids_finalize_client(hids_client_t * client){
// stop listening
uint8_t i;
for (i = 0; i < client->num_reports; i++){
gatt_client_stop_listening_for_characteristic_value_updates(&client->reports[i].notification_listener);
}
hids_client_descriptor_storage_delete(client); hids_client_descriptor_storage_delete(client);
btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client); btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client);
btstack_memory_hids_client_free(client); btstack_memory_hids_client_free(client);
@ -395,12 +474,46 @@ static void hids_emit_connection_established(hids_client_t * client, uint8_t sta
(*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event)); (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
} }
static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_index, uint8_t *buffer, uint16_t report_len){
uint16_t pos = 0;
buffer[pos++] = HCI_EVENT_GATTSERVICE_META;
pos++; // skip len
buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT;
little_endian_store_16(buffer, pos, client->cid);
pos += 2;
buffer[pos++] = client->reports[report_index].service_index;
buffer[pos++] = client->reports[report_index].report_id;
little_endian_store_16(buffer, pos, report_len + 1);
pos += 2;
buffer[pos++] = client->reports[report_index].report_id;
buffer[1] = pos + report_len + 1 - 2;
}
static void handle_report_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
btstack_assert(client != NULL);
uint8_t report_index = find_report_index_for_value_handle(client, gatt_event_notification_get_value_handle(packet));
if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){
return;
}
uint8_t * in_place_event = &packet[-1];
hids_client_setup_report_event(client, report_index, in_place_event, gatt_event_notification_get_value_length(packet));
(*client->client_handler)(HCI_EVENT_GATTSERVICE_META, client->cid, in_place_event, size);
}
static void hids_run_for_client(hids_client_t * client){ static void hids_run_for_client(hids_client_t * client){
uint8_t att_status; uint8_t att_status;
gatt_client_service_t service; gatt_client_service_t service;
gatt_client_characteristic_t characteristic; gatt_client_characteristic_t characteristic;
uint8_t report_index;
switch (client->state){ switch (client->state){
case HIDS_CLIENT_STATE_W2_QUERY_SERVICE: case HIDS_CLIENT_STATE_W2_QUERY_SERVICE:
@ -420,51 +533,24 @@ static void hids_run_for_client(hids_client_t * client){
UNUSED(att_status); UNUSED(att_status);
break; break;
case HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD: case HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE:
client->state = HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED;
report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT, &characteristic);
if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
}
if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
client->reports[report_index].value_handle = 0;
client->state = HIDS_CLIENT_STATE_CONNECTED;
}
break;
case HIDS_CLIENT_STATE_W2_ENABLE_MOUSE:
client->state = HIDS_CLIENT_STATE_W4_MOUSE_ENABLED;
report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT, &characteristic);
if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
}
if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){
client->reports[report_index].value_handle = 0;
client->state = HIDS_CLIENT_STATE_CONNECTED;
}
break;
case HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE:
client->state = HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE;
att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle, client->protocol_mode_value_handle, 1, (uint8_t *)&client->required_protocol_mode); att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle, client->protocol_mode_value_handle, 1, (uint8_t *)&client->required_protocol_mode);
UNUSED(att_status); UNUSED(att_status);
client->protocol_mode = client->required_protocol_mode; client->protocol_mode = client->required_protocol_mode;
client->state = HIDS_CLIENT_STATE_CONNECTED; if (hids_client_report_query_init(client)){
hids_emit_connection_established(client, ERROR_CODE_SUCCESS); break;
}
hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
hids_finalize_client(client);
break; break;
case HIDS_CLIENT_W2_SEND_REPORT:{ case HIDS_CLIENT_W2_SEND_REPORT:{
client->state = HIDS_CLIENT_STATE_CONNECTED; client->state = HIDS_CLIENT_STATE_CONNECTED;
att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle, att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle,
client->reports[client->active_index].value_handle, client->reports[client->report_index].value_handle,
client->report_len, (uint8_t *)client->report); client->report_len, (uint8_t *)client->report);
UNUSED(att_status); UNUSED(att_status);
break; break;
@ -472,7 +558,7 @@ static void hids_run_for_client(hids_client_t * client){
case HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR: case HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR:
client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR; client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR;
att_status = gatt_client_read_long_value_of_characteristic_using_value_handle(&handle_gatt_client_event, client->con_handle, client->services[client->service_index].report_map_value_handle); att_status = gatt_client_read_value_of_characteristic_using_value_handle(&handle_gatt_client_event, client->con_handle, client->services[client->service_index].report_map_value_handle);
UNUSED(att_status); UNUSED(att_status);
break; break;
@ -489,7 +575,7 @@ static void hids_run_for_client(hids_client_t * client){
case HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID: case HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID:
client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_REFERENCE_UUID; client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_REFERENCE_UUID;
att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->reports[client->active_index].value_handle); att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->reports[client->report_index].value_handle);
UNUSED(att_status); UNUSED(att_status);
break; break;
@ -505,7 +591,7 @@ static void hids_run_for_client(hids_client_t * client){
case HIDS_CLIENT_STATE_W2_FIND_REPORT: case HIDS_CLIENT_STATE_W2_FIND_REPORT:
client->state = HIDS_CLIENT_STATE_W4_REPORT_FOUND; client->state = HIDS_CLIENT_STATE_W4_REPORT_FOUND;
client->descriptor_handle = 0; client->descriptor_handle = 0;
hids_client_get_characteristic_for_report_index(client, client->active_index, &characteristic); hids_client_get_characteristic_for_report_index(client, client->report_index, &characteristic);
att_status = gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic); att_status = gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic);
UNUSED(att_status); UNUSED(att_status);
break; break;
@ -517,55 +603,39 @@ static void hids_run_for_client(hids_client_t * client){
client->descriptor_handle = 0; client->descriptor_handle = 0;
UNUSED(att_status); UNUSED(att_status);
break; break;
case HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS:
client->state = HIDS_CLIENT_STATE_W4_INPUT_REPORTS_ENABLED;
characteristic.value_handle = client->reports[client->report_index].value_handle;
characteristic.end_handle = client->reports[client->report_index].end_handle;
characteristic.properties = client->reports[client->report_index].properties;
att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
if (att_status != ERROR_CODE_SUCCESS){
if (hids_client_report_next_notification_report_index(client)){
hids_run_for_client(client);
break;
}
client->state = HIDS_CLIENT_STATE_CONNECTED;
hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
} else {
gatt_client_listen_for_characteristic_value_updates(
&client->reports[client->report_index].notification_listener,
&handle_report_hid_event, client->con_handle, &characteristic);
client->state = HIDS_CLIENT_STATE_CONNECTED;
hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
}
UNUSED(att_status);
break;
default: default:
break; break;
} }
} }
static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_id, uint8_t *buffer, uint16_t report_len){
uint16_t pos = 0;
buffer[pos++] = HCI_EVENT_GATTSERVICE_META;
pos++; // skip len
buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT;
little_endian_store_16(buffer, pos, client->cid);
pos += 2;
buffer[pos++] = report_id;
little_endian_store_16(buffer, pos, report_len);
pos += 2;
buffer[1] = pos + report_len - 2;
}
static void handle_boot_keyboard_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
btstack_assert(client != NULL);
uint8_t * in_place_event = packet;
hids_client_setup_report_event(client, HID_BOOT_MODE_KEYBOARD_ID, in_place_event, gatt_event_notification_get_value_length(packet));
(*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
}
static void handle_boot_mouse_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return;
hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
btstack_assert(client != NULL);
uint8_t * in_place_event = packet;
hids_client_setup_report_event(client, HID_BOOT_MODE_MOUSE_ID, in_place_event, gatt_event_notification_get_value_length(packet));
(*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size);
}
static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
UNUSED(packet_type); UNUSED(packet_type);
@ -578,12 +648,11 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
gatt_client_characteristic_t characteristic; gatt_client_characteristic_t characteristic;
gatt_client_characteristic_descriptor_t characteristic_descriptor; gatt_client_characteristic_descriptor_t characteristic_descriptor;
hids_client_report_t * boot_keyboard_report; // hids_client_report_t * boot_keyboard_report;
hids_client_report_t * boot_mouse_report; // hids_client_report_t * boot_mouse_report;
const uint8_t * characteristic_descriptor_value; const uint8_t * characteristic_descriptor_value;
uint8_t i; uint8_t i;
uint8_t report_index; uint8_t report_index;
uint8_t next_service_index;
const uint8_t * descriptor; const uint8_t * descriptor;
uint16_t descriptor_len; uint16_t descriptor_len;
@ -599,15 +668,17 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
break; break;
} }
next_service_index = client->num_instances; if (client->num_instances < MAX_NUM_HID_SERVICES){
if (next_service_index < MAX_NUM_HID_SERVICES){ uint8_t index = client->num_instances;
gatt_event_service_query_result_get_service(packet, &service); gatt_event_service_query_result_get_service(packet, &service);
client->services[next_service_index].start_handle = service.start_group_handle; client->services[index].start_handle = service.start_group_handle;
client->services[next_service_index].end_handle = service.end_group_handle; client->services[index].end_handle = service.end_group_handle;
hids_client_descriptor_storage_init(client, next_service_index);
client->num_instances++; client->num_instances++;
}
hids_client_descriptor_storage_init(client, index);
} else {
log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances + 1, MAX_NUM_HID_SERVICES);
}
break; break;
case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
@ -636,19 +707,19 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
break; break;
case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT: case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT:
hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT); hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT, true);
break; break;
case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT: case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT:
hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT); hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT, true);
break; break;
case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT: case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT:
hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_OUTPUT); hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_OUTPUT, false);
break; break;
case ORG_BLUETOOTH_CHARACTERISTIC_REPORT: case ORG_BLUETOOTH_CHARACTERISTIC_REPORT:
hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_ID, HID_REPORT_TYPE_RESERVED); hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_ID, HID_REPORT_TYPE_RESERVED, false);
break; break;
case ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP: case ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP:
@ -658,12 +729,10 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
case ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION: case ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION:
// printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION\n"); // printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION\n");
hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_HID_INFORMATION_ID, HID_REPORT_TYPE_INPUT);
break; break;
case ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT: case ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT:
// printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT\n"); // printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT\n");
hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_HID_CONTROL_POINT_ID, HID_REPORT_TYPE_INPUT);
break; break;
default: default:
break; break;
@ -677,10 +746,9 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
client = hids_get_client_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet)); client = hids_get_client_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet));
btstack_assert(client != NULL); btstack_assert(client != NULL);
// printf("HID descriptor found\n");
descriptor_len = gatt_event_characteristic_value_query_result_get_value_length(packet); descriptor_len = gatt_event_characteristic_value_query_result_get_value_length(packet);
descriptor = gatt_event_characteristic_value_query_result_get_value(packet); descriptor = gatt_event_characteristic_value_query_result_get_value(packet);
for (i = 0; i < descriptor_len; i++){ for (i = 0; i < descriptor_len; i++){
bool stored = hids_client_descriptor_storage_store(client, client->service_index, descriptor[i]); bool stored = hids_client_descriptor_storage_store(client, client->service_index, descriptor[i]);
if (!stored){ if (!stored){
@ -699,7 +767,7 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT: case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT:
// setup for descriptor value query // setup for descriptor value query
if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_EXTERNAL_REPORT_REFERENCE){ if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_EXTERNAL_REPORT_REFERENCE){
report_index = hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_MAP_ID, HID_REPORT_TYPE_RESERVED); report_index = hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_MAP_ID, HID_REPORT_TYPE_RESERVED, false);
if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){ if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){
client->reports[report_index].value_handle = characteristic_descriptor.handle; client->reports[report_index].value_handle = characteristic_descriptor.handle;
@ -738,13 +806,13 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
break; break;
case HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE: case HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE:
client->reports[client->active_index].report_id = characteristic_descriptor_value[0]; client->reports[client->report_index].report_id = characteristic_descriptor_value[0];
client->reports[client->active_index].report_type = (hid_report_type_t)characteristic_descriptor_value[1]; client->reports[client->report_index].report_type = (hid_report_type_t)characteristic_descriptor_value[1];
log_info("update index %d, id %d, type %d, value handle 0x%02x", log_info("update index %d, id %d, type %d, value handle 0x%02x",
client->active_index, client->report_index,
client->reports[client->active_index].report_id, client->reports[client->report_index].report_id,
client->reports[client->active_index].report_type, client->reports[client->report_index].report_type,
client->reports[client->active_index].value_handle); client->reports[client->report_index].value_handle);
break; break;
@ -773,11 +841,6 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
break; break;
} }
if (client->num_instances > MAX_NUM_HID_SERVICES) {
log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances, MAX_NUM_HID_SERVICES);
client->num_instances = MAX_NUM_HID_SERVICES;
}
client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC; client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
client->service_index = 0; client->service_index = 0;
break; break;
@ -788,43 +851,67 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
hids_finalize_client(client); hids_finalize_client(client);
break; break;
} }
if ((client->service_index + 1) < client->num_instances){
// discover characteristics of next service
client->service_index++;
client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
break;
}
switch (client->required_protocol_mode){ switch (client->required_protocol_mode){
case HID_PROTOCOL_MODE_BOOT: case HID_PROTOCOL_MODE_REPORT:
if (get_boot_keyboard_input_report(client) != NULL){ client->protocol_mode = HID_PROTOCOL_MODE_REPORT;
client->state = HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD; if (hid_clients_has_reports_in_report_mode(client)){
break; client->protocol_mode = HID_PROTOCOL_MODE_REPORT;
}
if (get_boot_mouse_input_report(client) != NULL){
client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
break; break;
} }
hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); hids_emit_connection_established(client, att_status);
hids_finalize_client(client); hids_finalize_client(client);
break; return;
case HID_PROTOCOL_MODE_REPORT:
if ((client->service_index + 1) < client->num_instances){
// discover characteristics of next service
client->service_index++;
client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC;
} else {
// 1. we need to get HID Descriptor and case HID_PROTOCOL_MODE_REPORT_WITH_FALLBACK_TO_BOOT:
// 2. get external Report characteristics if referenced from Report Map if (hid_clients_has_reports_in_report_mode(client)){
if (hids_client_report_map_query_init(client)){ client->protocol_mode = HID_PROTOCOL_MODE_REPORT;
break;
}
if (hid_clients_has_reports_in_boot_mode(client)){
if (client->protocol_mode_value_handle != 0){
client->state = HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE;
break; break;
} }
hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); hids_emit_connection_established(client, att_status);
hids_finalize_client(client); hids_finalize_client(client);
return;
} }
break; break;
default: default:
if (hid_clients_has_reports_in_boot_mode(client)){
if (client->protocol_mode_value_handle != 0){
client->state = HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE;
break;
}
hids_emit_connection_established(client, att_status);
hids_finalize_client(client);
return;
}
break; break;
} }
if (client->state == HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE){
break;
}
// 1. we need to get HID Descriptor and
// 2. get external Report characteristics if referenced from Report Map
if (hids_client_report_map_query_init(client)){
break;
}
hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
hids_finalize_client(client);
break; break;
// HID descriptor found // HID descriptor found
case HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR: case HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR:
if (att_status != ATT_ERROR_SUCCESS){ if (att_status != ATT_ERROR_SUCCESS){
@ -868,7 +955,6 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
break; break;
case HIDS_CLIENT_STATE_W4_EXTERNAL_REPORT_CHARACTERISTIC_RESULT: case HIDS_CLIENT_STATE_W4_EXTERNAL_REPORT_CHARACTERISTIC_RESULT:
// discover characteristic descriptor for all Report characteristics, // discover characteristic descriptor for all Report characteristics,
// then read value of characteristic descriptor to get Report ID // then read value of characteristic descriptor to get Report ID
if (hids_client_report_query_init(client)){ if (hids_client_report_query_init(client)){
@ -901,66 +987,19 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint
break; break;
} }
// TODO continue with report mode if (hids_client_report_notifications_init(client)){
break;
}
hids_emit_connection_established(client, ERROR_CODE_SUCCESS); hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
break; break;
case HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED: case HIDS_CLIENT_STATE_W4_INPUT_REPORTS_ENABLED:
boot_keyboard_report = get_boot_keyboard_input_report(client); if (hids_client_report_next_notification_report_index(client)){
if (boot_keyboard_report != NULL){
// setup listener
characteristic.value_handle = boot_keyboard_report->value_handle;
gatt_client_listen_for_characteristic_value_updates(&boot_keyboard_report->notifications, &handle_boot_keyboard_hid_event, client->con_handle, &characteristic);
}
// check if there is mouse input report
boot_mouse_report = get_boot_mouse_input_report(client);
if (boot_mouse_report != NULL){
client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE;
break;
}
// if none of the reports is found bail out
if (!boot_mouse_report && !boot_keyboard_report){
hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
hids_finalize_client(client);
break; break;
} }
hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
// set protocol
if (client->protocol_mode_value_handle != 0){
client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
} else {
client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
client->state = HIDS_CLIENT_STATE_CONNECTED;
hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
}
hids_run_for_client(client);
break; break;
case HIDS_CLIENT_STATE_W4_MOUSE_ENABLED:
boot_mouse_report = get_boot_mouse_input_report(client);
if (boot_mouse_report != NULL){
// setup listener
characteristic.value_handle = boot_mouse_report->value_handle;
gatt_client_listen_for_characteristic_value_updates(&boot_mouse_report->notifications, &handle_boot_mouse_hid_event, client->con_handle, &characteristic);
}
boot_keyboard_report = get_boot_keyboard_input_report(client);
if (!boot_mouse_report && !boot_keyboard_report){
hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
hids_finalize_client(client);
break;
}
if (client->protocol_mode_value_handle != 0){
client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE;
} else {
client->protocol_mode = HID_PROTOCOL_MODE_BOOT;
client->state = HIDS_CLIENT_STATE_CONNECTED;
hids_emit_connection_established(client, ERROR_CODE_SUCCESS);
}
break;
default: default:
break; break;
} }
@ -1038,7 +1077,7 @@ uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint
} }
client->state = HIDS_CLIENT_W2_SEND_REPORT; client->state = HIDS_CLIENT_W2_SEND_REPORT;
client->active_index = report_index; client->report_index = report_index;
client->report = report; client->report = report;
client->report_len = report_len; client->report_len = report_len;
@ -1046,6 +1085,9 @@ uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint
return ERROR_CODE_SUCCESS; return ERROR_CODE_SUCCESS;
} }
void hids_client_init(void){} void hids_client_init(uint8_t * hid_descriptor_storage, uint16_t hid_descriptor_storage_len){
hids_client_descriptor_storage = hid_descriptor_storage;
hids_client_descriptor_storage_len = hid_descriptor_storage_len;
}
void hids_client_deinit(void){} void hids_client_deinit(void){}

View File

@ -67,6 +67,8 @@ typedef enum {
HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC, HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC,
HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT, HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT,
HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE,
// for each REPORT_MAP characteristic, read HID Descriptor (Report Map Characteristic Value) // for each REPORT_MAP characteristic, read HID Descriptor (Report Map Characteristic Value)
HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR, HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR,
HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR, HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR,
@ -91,16 +93,9 @@ typedef enum {
HIDS_CLIENT_STATE_W2_READ_REPORT_ID_AND_TYPE, HIDS_CLIENT_STATE_W2_READ_REPORT_ID_AND_TYPE,
HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE, HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE,
// Boot Mode HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS,
HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD, HIDS_CLIENT_STATE_W4_INPUT_REPORTS_ENABLED,
HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED,
HIDS_CLIENT_STATE_W2_ENABLE_MOUSE,
HIDS_CLIENT_STATE_W4_MOUSE_ENABLED,
HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE,
HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE,
HIDS_CLIENT_STATE_CONNECTED, HIDS_CLIENT_STATE_CONNECTED,
HIDS_CLIENT_W2_SEND_REPORT HIDS_CLIENT_W2_SEND_REPORT
} hid_service_client_state_t; } hid_service_client_state_t;
@ -121,7 +116,8 @@ typedef struct {
uint8_t service_index; uint8_t service_index;
uint8_t report_id; uint8_t report_id;
hid_report_type_t report_type; hid_report_type_t report_type;
gatt_client_notification_t notifications; uint8_t boot_report;
gatt_client_notification_t notification_listener;
} hids_client_report_t; } hids_client_report_t;
typedef struct { typedef struct {
@ -163,7 +159,7 @@ typedef struct {
uint8_t num_reports; uint8_t num_reports;
// index used for report and report map search // index used for report and report map search
uint8_t active_index; uint8_t report_index;
uint16_t descriptor_handle; uint16_t descriptor_handle;
uint16_t report_len; uint16_t report_len;
const uint8_t * report; const uint8_t * report;
@ -174,8 +170,10 @@ typedef struct {
/** /**
* @brief Initialize Battery Service. * @brief Initialize Battery Service.
* @param hid_descriptor_storage
* @param hid_descriptor_storage_len
*/ */
void hids_client_init(void); void hids_client_init(uint8_t * hid_descriptor_storage, uint16_t hid_descriptor_storage_len);
/* @brief Connect to HID Services of remote device. /* @brief Connect to HID Services of remote device.
* *
@ -204,8 +202,18 @@ uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint
*/ */
uint8_t hids_client_disconnect(uint16_t hids_cid); uint8_t hids_client_disconnect(uint16_t hids_cid);
/*
* @brief Get descriptor data
* @param hid_cid
* @result data
*/
const uint8_t * hids_client_descriptor_storage_get_descriptor_data(uint16_t hids_cid, uint8_t service_index); const uint8_t * hids_client_descriptor_storage_get_descriptor_data(uint16_t hids_cid, uint8_t service_index);
/*
* @brief Get descriptor length
* @param hid_cid
* @result length
*/
uint16_t hids_client_descriptor_storage_get_descriptor_len(uint16_t hids_cid, uint8_t service_index); uint16_t hids_client_descriptor_storage_get_descriptor_len(uint16_t hids_cid, uint8_t service_index);
/** /**

141
src/hid.c Normal file
View File

@ -0,0 +1,141 @@
/*
* Copyright (C) 2021 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
* RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/
#include "hid.h"
// from USB HID Specification 1.1, Appendix B.1
const uint8_t hid_descriptor_boot_mode[] = {
// Keyboard
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xa1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID 1
// Modifier byte
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x05, 0x07, // Usage Page (Key codes)
0x19, 0xe0, // Usage Minimum (Keyboard LeftControl)
0x29, 0xe7, // Usage Maxium (Keyboard Right GUI)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x81, 0x02, // Input (Data, Variable, Absolute)
// Reserved byte
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x03, // Input (Constant, Variable, Absolute)
// LED report + padding
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (Num Lock)
0x29, 0x05, // Usage Maxium (Kana)
0x91, 0x02, // Output (Data, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x03, // Output (Constant, Variable, Absolute)
// Keycodes
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0xff, // Logical Maximum (1)
0x05, 0x07, // Usage Page (Key codes)
0x19, 0x00, // Usage Minimum (Reserved (no event indicated))
0x29, 0xff, // Usage Maxium (Reserved)
0x81, 0x00, // Input (Data, Array)
0xc0, // End collection
// Mouse
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x02, // USAGE (Mouse)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x01, // Report ID 1
0x09, 0x01, // USAGE (Pointer)
0xa1, 0x00, // COLLECTION (Physical)
#if 1
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x03, // REPORT_COUNT (3)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x05, // REPORT_SIZE (5)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
#endif
#if 1
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x06, // INPUT (Data,Var,Rel)
#endif
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};
const uint8_t * hid_get_boot_descriptor_data(void){
return &hid_descriptor_boot_mode[0];
}
uint16_t hid_get_boot_descriptor_len(void){
return sizeof(hid_descriptor_boot_mode);
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020 BlueKitchen GmbH * Copyright (C) 2021 BlueKitchen GmbH
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
@ -42,7 +42,8 @@
extern "C" { extern "C" {
#endif #endif
/* API_START */
#include <stdint.h>
#define HID_BOOT_MODE_KEYBOARD_ID 1 #define HID_BOOT_MODE_KEYBOARD_ID 1
#define HID_BOOT_MODE_MOUSE_ID 2 #define HID_BOOT_MODE_MOUSE_ID 2
@ -105,6 +106,20 @@ typedef enum {
HID_REPORT_ID_INVALID HID_REPORT_ID_INVALID
} hid_report_id_status_t; } hid_report_id_status_t;
/* API_START */
/*
* @brief Get boot descriptor data
* @result data
*/
const uint8_t * hid_get_boot_descriptor_data(void);
/*
* @brief Get boot descriptor length
* @result length
*/
uint16_t hid_get_boot_descriptor_len(void);
/* API_END */ /* API_END */
#if defined __cplusplus #if defined __cplusplus