Przeglądaj źródła

Merge branch 'feature/esp-hid-component' into 'master'

ESP-HID Component

See merge request espressif/esp-idf!6908
Jiang Jiang Jian 5 lat temu
rodzic
commit
922a0fa485
57 zmienionych plików z 9529 dodań i 13 usunięć
  1. 3 0
      components/bt/CMakeLists.txt
  2. 2 0
      components/bt/component.mk
  3. 7 0
      components/bt/host/bluedroid/Kconfig.in
  4. 3 2
      components/bt/host/bluedroid/bta/hh/bta_hh_act.c
  5. 1 1
      components/bt/host/bluedroid/bta/hh/bta_hh_api.c
  6. 1 0
      components/bt/host/bluedroid/bta/hh/bta_hh_main.c
  7. 1 1
      components/bt/host/bluedroid/bta/hh/bta_hh_utils.c
  8. 583 0
      components/bt/host/bluedroid/btc/profile/std/hid/hidh_api.c
  9. 1036 0
      components/bt/host/bluedroid/btc/profile/std/hid/hidh_conn.c
  10. 2 1
      components/bt/host/bluedroid/btc/profile/std/hid/include/hid_conn.h
  11. 10 3
      components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h
  12. 5 0
      components/bt/host/bluedroid/common/include/common/bt_target.h
  13. 1 1
      components/bt/host/bluedroid/common/include/common/bt_trace.h
  14. 20 0
      components/esp_hid/CMakeLists.txt
  15. 3 0
      components/esp_hid/component.mk
  16. 257 0
      components/esp_hid/include/esp_hid_common.h
  17. 206 0
      components/esp_hid/include/esp_hidd.h
  18. 40 0
      components/esp_hid/include/esp_hidd_gatts.h
  19. 31 0
      components/esp_hid/include/esp_hidd_transport.h
  20. 294 0
      components/esp_hid/include/esp_hidh.h
  21. 41 0
      components/esp_hid/include/esp_hidh_bluedroid.h
  22. 40 0
      components/esp_hid/include/esp_hidh_gattc.h
  23. 33 0
      components/esp_hid/include/esp_hidh_transport.h
  24. 33 0
      components/esp_hid/private/ble_hidd.h
  25. 34 0
      components/esp_hid/private/ble_hidh.h
  26. 34 0
      components/esp_hid/private/bt_hidh.h
  27. 367 0
      components/esp_hid/private/esp_bt_hh_api.h
  28. 42 0
      components/esp_hid/private/esp_hidd_private.h
  29. 121 0
      components/esp_hid/private/esp_hidh_private.h
  30. 1005 0
      components/esp_hid/src/ble_hidd.c
  31. 708 0
      components/esp_hid/src/ble_hidh.c
  32. 445 0
      components/esp_hid/src/bt_hidh.c
  33. 519 0
      components/esp_hid/src/esp_hid_common.c
  34. 121 0
      components/esp_hid/src/esp_hidd.c
  35. 488 0
      components/esp_hid/src/esp_hidh.c
  36. 3 0
      components/esp_hid/test/CMakeLists.txt
  37. 40 0
      components/esp_hid/test/README.md
  38. 1 0
      components/esp_hid/test/component.mk
  39. 403 0
      components/esp_hid/test/hid_descriptor.h
  40. 214 0
      components/esp_hid/test/test_esp_hid.c
  41. 7 0
      examples/bluetooth/esp_hid_device/CMakeLists.txt
  42. 8 0
      examples/bluetooth/esp_hid_device/Makefile
  43. 5 0
      examples/bluetooth/esp_hid_device/main/CMakeLists.txt
  44. 3 0
      examples/bluetooth/esp_hid_device/main/component.mk
  45. 385 0
      examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c
  46. 807 0
      examples/bluetooth/esp_hid_device/main/esp_hid_gap.c
  47. 68 0
      examples/bluetooth/esp_hid_device/main/esp_hid_gap.h
  48. 6 0
      examples/bluetooth/esp_hid_device/sdkconfig.defaults
  49. 7 0
      examples/bluetooth/esp_hid_host/CMakeLists.txt
  50. 8 0
      examples/bluetooth/esp_hid_host/Makefile
  51. 5 0
      examples/bluetooth/esp_hid_host/main/CMakeLists.txt
  52. 3 0
      examples/bluetooth/esp_hid_host/main/component.mk
  53. 807 0
      examples/bluetooth/esp_hid_host/main/esp_hid_gap.c
  54. 68 0
      examples/bluetooth/esp_hid_host/main/esp_hid_gap.h
  55. 134 0
      examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c
  56. 6 0
      examples/bluetooth/esp_hid_host/sdkconfig.defaults
  57. 4 4
      tools/ci/config/target-test.yml

+ 3 - 0
components/bt/CMakeLists.txt

@@ -47,6 +47,7 @@ if(CONFIG_BT_ENABLED)
             host/bluedroid/btc/profile/esp/blufi/include
             host/bluedroid/btc/profile/esp/include
             host/bluedroid/btc/profile/std/a2dp/include
+            host/bluedroid/btc/profile/std/hid/include
             host/bluedroid/btc/profile/std/include
             host/bluedroid/btc/include
             host/bluedroid/stack/btm/include
@@ -166,6 +167,8 @@ if(CONFIG_BT_ENABLED)
                    "host/bluedroid/btc/profile/std/hf_ag/btc_hf_ag.c"
                    "host/bluedroid/btc/profile/std/hf_client/btc_hf_client.c"
                    "host/bluedroid/btc/profile/std/hf_client/bta_hf_client_co.c"
+                   "host/bluedroid/btc/profile/std/hid/hidh_api.c"
+                   "host/bluedroid/btc/profile/std/hid/hidh_conn.c"
                    "host/bluedroid/btc/profile/std/gap/btc_gap_ble.c"
                    "host/bluedroid/btc/profile/std/gap/btc_gap_bt.c"
                    "host/bluedroid/btc/profile/std/gap/bta_gap_bt_co.c"

+ 2 - 0
components/bt/component.mk

@@ -45,6 +45,7 @@ COMPONENT_PRIV_INCLUDEDIRS +=   host/bluedroid/bta/include                   \
                                 host/bluedroid/btc/profile/std/gatt/include  \
                                 host/bluedroid/btc/profile/std/gap/include   \
                                 host/bluedroid/btc/profile/std/a2dp/include  \
+                                host/bluedroid/btc/profile/std/hid/include   \
                                 host/bluedroid/btc/profile/std/include       \
                                 host/bluedroid/btc/include                   \
                                 host/bluedroid/btif/include                  \
@@ -96,6 +97,7 @@ COMPONENT_SRCDIRS +=    host/bluedroid/bta/dm                      \
                         host/bluedroid/btc/profile/std/spp         \
                         host/bluedroid/btc/profile/std/hf_ag       \
                         host/bluedroid/btc/profile/std/hf_client   \
+                        host/bluedroid/btc/profile/std/hid         \
                         host/bluedroid/btc/profile                 \
                         host/bluedroid/stack/btm                   \
                         host/bluedroid/stack/btu                   \

+ 7 - 0
components/bt/host/bluedroid/Kconfig.in

@@ -99,6 +99,13 @@ config BT_HFP_WBS_ENABLE
         This enables Wide Band Speech. Should disable it when SCO data path is PCM.
         Otherwise there will be no data transmited via GPIOs.
 
+config BT_HID_HOST_ENABLED
+    bool "Classic BT HID Host"
+    depends on BT_CLASSIC_ENABLED
+    default n
+    help
+        This enables the BT HID Host
+
 config BT_SSP_ENABLED
     bool "Secure Simple Pairing"
     depends on BT_CLASSIC_ENABLED

+ 3 - 2
components/bt/host/bluedroid/bta/hh/bta_hh_act.c

@@ -34,6 +34,7 @@
 #include "bta_hh_int.h"
 #include "bta/bta_hh_co.h"
 #include "bta/utl.h"
+#include "osi/allocator.h"
 
 /*****************************************************************************
 **  Constants
@@ -196,7 +197,7 @@ static void bta_hh_sdp_cback(UINT16 result, UINT16 attr_mask,
         }
 
 #if BTA_HH_DEBUG
-        APPL_TRACE_EVENT("bta_hh_sdp_cback: p_cb: %d result 0x%02x, \
+        APPL_TRACE_EVENT("bta_hh_sdp_cback: p_cb: %p result 0x%02x, \
                             attr_mask 0x%02x, handle %x", \
                          p_cb, result, attr_mask, p_cb->hid_handle);
 #endif
@@ -261,7 +262,7 @@ static void bta_hh_di_sdp_cback(UINT16 result)
     tSDP_DI_GET_RECORD  di_rec;
     tHID_STATUS ret;
 #if BTA_HH_DEBUG
-    APPL_TRACE_EVENT("bta_hh_di_sdp_cback: p_cb: %d result 0x%02x", p_cb, result);
+    APPL_TRACE_EVENT("bta_hh_di_sdp_cback: p_cb: %p result 0x%02x", p_cb, result);
 #endif
 
     /* if DI record does not exist on remote device, vendor_id in tBTA_HH_DEV_DSCP_INFO will be

+ 1 - 1
components/bt/host/bluedroid/bta/hh/bta_hh_api.c

@@ -35,7 +35,7 @@
 #include "stack/l2c_api.h"
 #include "bta/utl.h"
 
-#include "osi/include/log.h"
+#include "osi/allocator.h"
 
 /*****************************************************************************
 **  Constants

+ 1 - 0
components/bt/host/bluedroid/bta/hh/bta_hh_main.c

@@ -30,6 +30,7 @@
 
 #include "bta/bta_hh_api.h"
 #include "bta_hh_int.h"
+#include "osi/allocator.h"
 
 /*****************************************************************************
 ** Constants and types

+ 1 - 1
components/bt/host/bluedroid/bta/hh/bta_hh_utils.c

@@ -20,7 +20,7 @@
 #include "common/bt_target.h"
 #if defined(BTA_HH_INCLUDED) && (BTA_HH_INCLUDED == TRUE)
 
-
+#include "osi/allocator.h"
 #include "bta_hh_int.h"
 
 /* if SSR max latency is not defined by remote device, set the default value

+ 583 - 0
components/bt/host/bluedroid/btc/profile/std/hid/hidh_api.c

@@ -0,0 +1,583 @@
+/******************************************************************************
+ *
+ *  Copyright (C) 2002-2012 Broadcom Corporation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at:
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ ******************************************************************************/
+
+/******************************************************************************
+ *
+ *  This file contains the HID HOST API entry points
+ *
+ ******************************************************************************/
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "common/bt_target.h"
+#include "osi/allocator.h"
+#include "stack/bt_types.h"
+#include "stack/hiddefs.h"
+#include "stack/hidh_api.h"
+#include "hidh_int.h"
+#include "stack/btm_api.h"
+#include "stack/btu.h"
+#include "btm_int.h"
+
+#if (HID_HOST_INCLUDED == TRUE)
+
+#if HID_DYNAMIC_MEMORY == FALSE
+tHID_HOST_CTB   hh_cb;
+#endif
+
+static void hidh_search_callback (UINT16 sdp_result);
+
+/*******************************************************************************
+**
+** Function         HID_HostGetSDPRecord
+**
+** Description      This function reads the device SDP record
+**
+** Returns          tHID_STATUS
+**
+*******************************************************************************/
+tHID_STATUS HID_HostGetSDPRecord ( BD_ADDR addr, tSDP_DISCOVERY_DB *p_db, UINT32 db_len,
+                                   tHID_HOST_SDP_CALLBACK *sdp_cback )
+{
+    tSDP_UUID   uuid_list;
+
+    if ( hh_cb.sdp_busy ) {
+        return HID_ERR_SDP_BUSY;
+    }
+
+    uuid_list.len = 2;
+    uuid_list.uu.uuid16 = UUID_SERVCLASS_HUMAN_INTERFACE;
+
+    hh_cb.p_sdp_db = p_db;
+    SDP_InitDiscoveryDb (p_db, db_len, 1, &uuid_list, 0, NULL);
+
+    if (SDP_ServiceSearchRequest (addr, p_db, hidh_search_callback)) {
+        hh_cb.sdp_cback = sdp_cback ;
+        hh_cb.sdp_busy = TRUE;
+        return HID_SUCCESS;
+    } else {
+        return HID_ERR_NO_RESOURCES;
+    }
+}
+
+void hidh_get_str_attr( tSDP_DISC_REC *p_rec, UINT16 attr_id, UINT16 max_len, char *str )
+{
+    tSDP_DISC_ATTR          *p_attr;
+    UINT16                  name_len;
+
+    if ((p_attr = SDP_FindAttributeInRec(p_rec, attr_id)) != NULL) {
+        if ((name_len = SDP_DISC_ATTR_LEN(p_attr->attr_len_type)) < max_len ) {
+            memcpy( str, (char *) p_attr->attr_value.v.array, name_len );
+            str[name_len] = '\0';
+        } else {
+            memcpy( str, (char *) p_attr->attr_value.v.array, max_len - 1 );
+            str[max_len - 1] = '\0';
+        }
+    } else {
+        str[0] = '\0';
+    }
+}
+
+
+static void hidh_search_callback (UINT16 sdp_result)
+{
+    tSDP_DISCOVERY_DB       *p_db = hh_cb.p_sdp_db;
+    tSDP_DISC_REC           *p_rec;
+    tSDP_DISC_ATTR          *p_attr, *p_subattr1, *p_subattr2, *p_repdesc;
+    tBT_UUID                hid_uuid;
+    tHID_DEV_SDP_INFO       *p_nvi = &hh_cb.sdp_rec;
+    UINT16                  attr_mask = 0;
+
+    hid_uuid.len       = LEN_UUID_16;
+    hid_uuid.uu.uuid16 = UUID_SERVCLASS_HUMAN_INTERFACE;
+
+    hh_cb.sdp_busy = FALSE;
+
+    if (sdp_result != SDP_SUCCESS) {
+        hh_cb.sdp_cback(sdp_result, 0, NULL);
+        return;
+    }
+
+    if ((p_rec = SDP_FindServiceUUIDInDb (p_db, &hid_uuid, NULL)) == NULL) {
+        hh_cb.sdp_cback(HID_SDP_NO_SERV_UUID, 0, NULL);
+        return;
+    }
+
+    memset (&hh_cb.sdp_rec, 0, sizeof( tHID_DEV_SDP_INFO ));
+
+    /* First, verify the mandatory fields we care about */
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_DESCRIPTOR_LIST)) == NULL)
+            || (SDP_DISC_ATTR_TYPE(p_attr->attr_len_type) != DATA_ELE_SEQ_DESC_TYPE)
+            || ((p_subattr1 = p_attr->attr_value.v.p_sub_attr) == NULL)
+            || (SDP_DISC_ATTR_TYPE(p_subattr1->attr_len_type) != DATA_ELE_SEQ_DESC_TYPE)
+            || ((p_subattr2 = p_subattr1->attr_value.v.p_sub_attr) == NULL)
+            || ((p_repdesc = p_subattr2->p_next_attr) == NULL)
+            || (SDP_DISC_ATTR_TYPE(p_repdesc->attr_len_type) != TEXT_STR_DESC_TYPE)) {
+        hh_cb.sdp_cback(HID_SDP_MANDATORY_MISSING, 0, NULL);
+        return;
+    }
+
+    if ((p_nvi->dscp_info.dl_len = SDP_DISC_ATTR_LEN(p_repdesc->attr_len_type)) != 0) {
+        p_nvi->dscp_info.dsc_list = (UINT8 *) &p_repdesc->attr_value;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_VIRTUAL_CABLE)) != NULL) &&
+            (p_attr->attr_value.v.u8) ) {
+        attr_mask |= HID_VIRTUAL_CABLE;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_RECONNECT_INITIATE)) != NULL) &&
+            (p_attr->attr_value.v.u8) ) {
+        attr_mask |= HID_RECONN_INIT;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_NORMALLY_CONNECTABLE)) != NULL) &&
+            (p_attr->attr_value.v.u8) ) {
+        attr_mask |= HID_NORMALLY_CONNECTABLE;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_SDP_DISABLE)) != NULL) &&
+            (p_attr->attr_value.v.u8) ) {
+        attr_mask |= HID_SDP_DISABLE;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_BATTERY_POWER)) != NULL) &&
+            (p_attr->attr_value.v.u8) ) {
+        attr_mask |= HID_BATTERY_POWER;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_REMOTE_WAKE)) != NULL) &&
+            (p_attr->attr_value.v.u8) ) {
+        attr_mask |= HID_REMOTE_WAKE;
+    }
+
+    hidh_get_str_attr( p_rec, ATTR_ID_SERVICE_NAME, HID_MAX_SVC_NAME_LEN, p_nvi->svc_name );
+    hidh_get_str_attr( p_rec, ATTR_ID_SERVICE_DESCRIPTION, HID_MAX_SVC_DESCR_LEN, p_nvi->svc_descr );
+    hidh_get_str_attr( p_rec, ATTR_ID_PROVIDER_NAME, HID_MAX_PROV_NAME_LEN, p_nvi->prov_name );
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_DEVICE_RELNUM)) != NULL)) {
+        p_nvi->rel_num = p_attr->attr_value.v.u16;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_COUNTRY_CODE)) != NULL)) {
+        p_nvi->ctry_code = p_attr->attr_value.v.u8;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_DEVICE_SUBCLASS)) != NULL)) {
+        p_nvi->sub_class = p_attr->attr_value.v.u8;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_PARSER_VERSION)) != NULL)) {
+        p_nvi->hpars_ver = p_attr->attr_value.v.u16;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_LINK_SUPERVISION_TO)) != NULL)) {
+        attr_mask |= HID_SUP_TOUT_AVLBL;
+        p_nvi->sup_timeout = p_attr->attr_value.v.u16;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_SSR_HOST_MAX_LAT)) != NULL)) {
+        attr_mask |= HID_SSR_MAX_LATENCY;
+        p_nvi->ssr_max_latency = p_attr->attr_value.v.u16;
+    } else {
+        p_nvi->ssr_max_latency = HID_SSR_PARAM_INVALID;
+    }
+
+    if (((p_attr = SDP_FindAttributeInRec (p_rec, ATTR_ID_HID_SSR_HOST_MIN_TOUT)) != NULL)) {
+        attr_mask |= HID_SSR_MIN_TOUT;
+        p_nvi->ssr_min_tout = p_attr->attr_value.v.u16;
+    } else {
+        p_nvi->ssr_min_tout = HID_SSR_PARAM_INVALID;
+    }
+
+    hh_cb.sdp_rec.p_sdp_layer_rec = p_rec;
+    hh_cb.sdp_cback(SDP_SUCCESS, attr_mask, &hh_cb.sdp_rec);
+}
+
+
+/*******************************************************************************
+**
+** Function         HID_HostInit
+**
+** Description      This function initializes the control block and trace variable
+**
+** Returns          void
+**
+*******************************************************************************/
+void HID_HostInit (void)
+{
+    memset(&hh_cb, 0, sizeof(tHID_HOST_CTB));
+
+#if defined(HID_INITIAL_TRACE_LEVEL)
+    hh_cb.trace_level = HID_INITIAL_TRACE_LEVEL;
+#else
+    hh_cb.trace_level = BT_TRACE_LEVEL_NONE;
+#endif
+}
+
+/*******************************************************************************
+**
+** Function         HID_HostSetTraceLevel
+**
+** Description      This function sets the trace level for HID Host. If called with
+**                  a value of 0xFF, it simply reads the current trace level.
+**
+** Returns          the new (current) trace level
+**
+*******************************************************************************/
+UINT8 HID_HostSetTraceLevel (UINT8 new_level)
+{
+    if (new_level != 0xFF) {
+        hh_cb.trace_level = new_level;
+    }
+
+    return (hh_cb.trace_level);
+}
+
+/*******************************************************************************
+**
+** Function         HID_HostRegister
+**
+** Description      This function registers HID-Host with lower layers
+**
+** Returns          tHID_STATUS
+**
+*******************************************************************************/
+tHID_STATUS HID_HostRegister (tHID_HOST_DEV_CALLBACK *dev_cback)
+{
+    tHID_STATUS st;
+
+    if ( hh_cb.reg_flag ) {
+        return HID_ERR_ALREADY_REGISTERED;
+    }
+
+    if ( dev_cback == NULL ) {
+        return HID_ERR_INVALID_PARAM;
+    }
+
+    /* Register with L2CAP */
+    if ( (st = hidh_conn_reg()) != HID_SUCCESS ) {
+        return st;
+    }
+
+    hh_cb.callback = dev_cback ;
+    hh_cb.reg_flag = TRUE;
+
+    return (HID_SUCCESS);
+}
+
+/*******************************************************************************
+**
+** Function         HID_HostDeregister
+**
+** Description      This function is called when the host is about power down.
+**
+** Returns          tHID_STATUS
+**
+*******************************************************************************/
+tHID_STATUS HID_HostDeregister(void)
+{
+    UINT8 i;
+
+    if ( !hh_cb.reg_flag ) {
+        return (HID_ERR_NOT_REGISTERED);
+    }
+
+    for ( i = 0; i < HID_HOST_MAX_DEVICES; i++ ) {
+        HID_HostRemoveDev( i ) ;
+    }
+
+    hidh_conn_dereg();
+    hh_cb.reg_flag = FALSE;
+
+    return (HID_SUCCESS) ;
+}
+
+/*******************************************************************************
+**
+** Function         HID_HostAddDev
+**
+** Description      This is called so HID-host may manage this device.
+**
+** Returns          tHID_STATUS
+**
+*******************************************************************************/
+tHID_STATUS HID_HostAddDev ( BD_ADDR addr, UINT16 attr_mask, UINT8 *handle )
+{
+    int i;
+    /* Find an entry for this device in hh_cb.devices array */
+    if ( !hh_cb.reg_flag ) {
+        return (HID_ERR_NOT_REGISTERED);
+    }
+
+    for ( i = 0; i < HID_HOST_MAX_DEVICES; i++) {
+        if ((hh_cb.devices[i].in_use) &&
+                (!memcmp(addr, hh_cb.devices[i].addr, BD_ADDR_LEN))) {
+            break;
+        }
+    }
+
+    if (i == HID_HOST_MAX_DEVICES ) {
+        for ( i = 0; i < HID_HOST_MAX_DEVICES; i++) {
+            if ( !hh_cb.devices[i].in_use) {
+                break;
+            }
+        }
+    }
+
+    if ( i == HID_HOST_MAX_DEVICES ) {
+        return HID_ERR_NO_RESOURCES;
+    }
+
+    if (!hh_cb.devices[i].in_use) {
+        hh_cb.devices[i].in_use = TRUE;
+        memcpy( hh_cb.devices[i].addr, addr, sizeof( BD_ADDR ) ) ;
+        hh_cb.devices[i].state = HID_DEV_NO_CONN;
+        hh_cb.devices[i].conn_tries = 0 ;
+    }
+
+    if (attr_mask != HID_ATTR_MASK_IGNORE) {
+        hh_cb.devices[i].attr_mask = attr_mask;
+    }
+
+    *handle = i;
+
+    return (HID_SUCCESS);
+}
+
+
+/*******************************************************************************
+**
+** Function         HID_HostRemoveDev
+**
+** Description      This removes the device from list devices that host has to manage.
+**
+** Returns          tHID_STATUS
+**
+*******************************************************************************/
+tHID_STATUS HID_HostRemoveDev ( UINT8 dev_handle )
+{
+    if ( !hh_cb.reg_flag ) {
+        return (HID_ERR_NOT_REGISTERED);
+    }
+
+    if ( (dev_handle >= HID_HOST_MAX_DEVICES) || (!hh_cb.devices[dev_handle].in_use) ) {
+        return HID_ERR_INVALID_PARAM;
+    }
+
+    HID_HostCloseDev( dev_handle ) ;
+    hh_cb.devices[dev_handle].in_use = FALSE;
+    hh_cb.devices[dev_handle].conn.conn_state = HID_CONN_STATE_UNUSED;
+    hh_cb.devices[dev_handle].conn.ctrl_cid = hh_cb.devices[dev_handle].conn.intr_cid = 0;
+    hh_cb.devices[dev_handle].attr_mask = 0;
+    return HID_SUCCESS;
+}
+
+/*******************************************************************************
+**
+** Function         HID_HostOpenDev
+**
+** Description      This function is called when the user wants to initiate a
+**                  connection attempt to a device.
+**
+** Returns          void
+**
+*******************************************************************************/
+tHID_STATUS HID_HostOpenDev ( UINT8 dev_handle )
+{
+    if ( !hh_cb.reg_flag ) {
+        return (HID_ERR_NOT_REGISTERED);
+    }
+
+    if ( (dev_handle >= HID_HOST_MAX_DEVICES) || (!hh_cb.devices[dev_handle].in_use) ) {
+        return HID_ERR_INVALID_PARAM;
+    }
+
+    if ( hh_cb.devices[dev_handle].state != HID_DEV_NO_CONN ) {
+        return HID_ERR_ALREADY_CONN;
+    }
+
+    hh_cb.devices[dev_handle].conn_tries = 1;
+    return hidh_conn_initiate( dev_handle );
+}
+
+/*******************************************************************************
+**
+** Function         HID_HostWriteDev
+**
+** Description      This function is called when the host has a report to send.
+**
+**                  report_id: is only used on GET_REPORT transaction if is specified.
+**                              only valid when it's a non-zero value.
+**
+** Returns          void
+**
+*******************************************************************************/
+tHID_STATUS HID_HostWriteDev( UINT8 dev_handle, UINT8 t_type,
+                              UINT8 param, UINT16 data, UINT8 report_id, BT_HDR *pbuf  )
+{
+    tHID_STATUS status = HID_SUCCESS;
+
+    if ( !hh_cb.reg_flag ) {
+        HIDH_TRACE_ERROR("HID_ERR_NOT_REGISTERED");
+        status = HID_ERR_NOT_REGISTERED;
+    }
+
+    if ( (dev_handle >= HID_HOST_MAX_DEVICES) || (!hh_cb.devices[dev_handle].in_use) ) {
+        HIDH_TRACE_ERROR("HID_ERR_INVALID_PARAM");
+        status = HID_ERR_INVALID_PARAM;
+    }
+
+    else if ( hh_cb.devices[dev_handle].state != HID_DEV_CONNECTED ) {
+        HIDH_TRACE_ERROR("HID_ERR_NO_CONNECTION dev_handle %d", dev_handle);
+        status = HID_ERR_NO_CONNECTION;
+    }
+
+    if (status != HID_SUCCESS) {
+        if (pbuf) {
+            osi_free ((void *)pbuf);
+        }
+    } else {
+        status = hidh_conn_snd_data( dev_handle, t_type, param, data, report_id, pbuf ) ;
+    }
+
+    return status;
+}
+
+/*******************************************************************************
+**
+** Function         HID_HostCloseDev
+**
+** Description      This function disconnects the device.
+**
+** Returns          void
+**
+*******************************************************************************/
+tHID_STATUS HID_HostCloseDev( UINT8 dev_handle )
+{
+    if ( !hh_cb.reg_flag ) {
+        return (HID_ERR_NOT_REGISTERED);
+    }
+
+    if ( (dev_handle >= HID_HOST_MAX_DEVICES) || (!hh_cb.devices[dev_handle].in_use) ) {
+        return HID_ERR_INVALID_PARAM;
+    }
+
+    hh_cb.devices[dev_handle].conn_tries = HID_HOST_MAX_CONN_RETRY + 1;
+    btu_stop_timer( &(hh_cb.devices[dev_handle].conn.timer_entry) ) ;
+
+    if ( hh_cb.devices[dev_handle].state != HID_DEV_CONNECTED ) {
+        return HID_ERR_NO_CONNECTION;
+    }
+
+    hh_cb.devices[dev_handle].conn_tries = HID_HOST_MAX_CONN_RETRY + 1;
+    return hidh_conn_disconnect( dev_handle );
+}
+
+tHID_STATUS HID_HostSetSecurityLevel( char serv_name[], UINT8 sec_lvl )
+{
+    if (!BTM_SetSecurityLevel (FALSE, serv_name, BTM_SEC_SERVICE_HIDH_SEC_CTRL,
+                               sec_lvl, HID_PSM_CONTROL, BTM_SEC_PROTO_HID, HID_SEC_CHN)) {
+        HIDH_TRACE_ERROR ("Security Registration 1 failed");
+        return (HID_ERR_NO_RESOURCES);
+    }
+
+    if (!BTM_SetSecurityLevel (TRUE, serv_name, BTM_SEC_SERVICE_HIDH_SEC_CTRL,
+                               sec_lvl, HID_PSM_CONTROL, BTM_SEC_PROTO_HID, HID_SEC_CHN)) {
+        HIDH_TRACE_ERROR ("Security Registration 2 failed");
+        return (HID_ERR_NO_RESOURCES);
+    }
+
+    if (!BTM_SetSecurityLevel (FALSE, serv_name, BTM_SEC_SERVICE_HIDH_NOSEC_CTRL,
+                               BTM_SEC_NONE, HID_PSM_CONTROL, BTM_SEC_PROTO_HID, HID_NOSEC_CHN)) {
+        HIDH_TRACE_ERROR ("Security Registration 3 failed");
+        return (HID_ERR_NO_RESOURCES);
+    }
+
+    if (!BTM_SetSecurityLevel (TRUE, serv_name, BTM_SEC_SERVICE_HIDH_NOSEC_CTRL,
+                               BTM_SEC_NONE, HID_PSM_CONTROL, BTM_SEC_PROTO_HID, HID_NOSEC_CHN)) {
+        HIDH_TRACE_ERROR ("Security Registration 4 failed");
+        return (HID_ERR_NO_RESOURCES);
+    }
+
+    if (!BTM_SetSecurityLevel (TRUE, serv_name, BTM_SEC_SERVICE_HIDH_INTR,
+                               BTM_SEC_NONE, HID_PSM_INTERRUPT, BTM_SEC_PROTO_HID, 0)) {
+        HIDH_TRACE_ERROR ("Security Registration 5 failed");
+        return (HID_ERR_NO_RESOURCES);
+    }
+
+    if (!BTM_SetSecurityLevel (FALSE, serv_name, BTM_SEC_SERVICE_HIDH_INTR,
+                               BTM_SEC_NONE, HID_PSM_INTERRUPT, BTM_SEC_PROTO_HID, 0)) {
+        HIDH_TRACE_ERROR ("Security Registration 6 failed");
+        return (HID_ERR_NO_RESOURCES);
+    }
+
+    return ( HID_SUCCESS );
+}
+
+/******************************************************************************
+**
+** Function         hid_known_hid_device
+**
+** Description      check if this device is  of type HID Device
+**
+** Returns          TRUE if device is HID Device else FALSE
+**
+*******************************************************************************/
+BOOLEAN hid_known_hid_device (BD_ADDR bd_addr)
+{
+    UINT8 i;
+    tBTM_INQ_INFO *p_inq_info = BTM_InqDbRead(bd_addr);
+
+    if ( !hh_cb.reg_flag ) {
+        return FALSE;
+    }
+
+    /* First  check for class of device , if Inq DB has information about this device*/
+    if (p_inq_info != NULL) {
+        /* Check if remote major device class is of type BTM_COD_MAJOR_PERIPHERAL */
+        if ((p_inq_info->results.dev_class[1] & BTM_COD_MAJOR_CLASS_MASK)
+                == BTM_COD_MAJOR_PERIPHERAL ) {
+            HIDH_TRACE_DEBUG("hid_known_hid_device:dev found in InqDB & COD matches HID dev");
+            return TRUE;
+        }
+    } else {
+        /* Look for this device in security device DB */
+        tBTM_SEC_DEV_REC  *p_dev_rec = btm_find_dev (bd_addr);
+        if ((p_dev_rec != NULL) &&
+                ((p_dev_rec->dev_class[1] & BTM_COD_MAJOR_CLASS_MASK) == BTM_COD_MAJOR_PERIPHERAL )) {
+            HIDH_TRACE_DEBUG("hid_known_hid_device:dev found in SecDevDB & COD matches HID dev");
+            return TRUE;
+        }
+    }
+
+    /* Find an entry for this device in hh_cb.devices array */
+    for ( i = 0; i < HID_HOST_MAX_DEVICES; i++) {
+        if ((hh_cb.devices[i].in_use) &&
+                (memcmp(bd_addr, hh_cb.devices[i].addr, BD_ADDR_LEN) == 0)) {
+            return TRUE;
+        }
+    }
+    /* Check if this device is marked as HID Device in IOP Dev */
+    HIDH_TRACE_DEBUG("hid_known_hid_device:remote is not HID device");
+    return FALSE;
+}
+
+#endif //HID_HOST_INCLUDED

+ 1036 - 0
components/bt/host/bluedroid/btc/profile/std/hid/hidh_conn.c

@@ -0,0 +1,1036 @@
+/******************************************************************************
+ *
+ *  Copyright (C) 2002-2012 Broadcom Corporation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at:
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ ******************************************************************************/
+
+/******************************************************************************
+ *
+ *  This file contains HID HOST internal definitions
+ *
+ ******************************************************************************/
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+
+#include "common/bt_target.h"
+#include "osi/allocator.h"
+#include "stack/bt_types.h"
+
+#include "stack/l2cdefs.h"
+#include "stack/l2c_api.h"
+
+#include "stack/btu.h"
+#include "stack/btm_api.h"
+#include "btm_int.h"
+
+#include "stack/hiddefs.h"
+
+#include "stack/hidh_api.h"
+#include "hidh_int.h"
+#include "osi/osi.h"
+
+#if (HID_HOST_INCLUDED == TRUE)
+
+static UINT8 find_conn_by_cid (UINT16 cid);
+static void hidh_conn_retry (UINT8 dhandle);
+
+/********************************************************************************/
+/*              L O C A L    F U N C T I O N     P R O T O T Y P E S            */
+/********************************************************************************/
+static void hidh_l2cif_connect_ind (BD_ADDR  bd_addr, UINT16 l2cap_cid,
+                                    UINT16 psm, UINT8 l2cap_id);
+static void hidh_l2cif_connect_cfm (UINT16 l2cap_cid, UINT16 result);
+static void hidh_l2cif_config_ind (UINT16 l2cap_cid, tL2CAP_CFG_INFO *p_cfg);
+static void hidh_l2cif_config_cfm (UINT16 l2cap_cid, tL2CAP_CFG_INFO *p_cfg);
+static void hidh_l2cif_disconnect_ind (UINT16 l2cap_cid, BOOLEAN ack_needed);
+static void hidh_l2cif_data_ind (UINT16 l2cap_cid, BT_HDR *p_msg);
+static void hidh_l2cif_disconnect_cfm (UINT16 l2cap_cid, UINT16 result);
+static void hidh_l2cif_cong_ind (UINT16 l2cap_cid, BOOLEAN congested);
+
+static const tL2CAP_APPL_INFO hst_reg_info = {
+    hidh_l2cif_connect_ind,
+    hidh_l2cif_connect_cfm,
+    NULL,
+    hidh_l2cif_config_ind,
+    hidh_l2cif_config_cfm,
+    hidh_l2cif_disconnect_ind,
+    hidh_l2cif_disconnect_cfm,
+    NULL,
+    hidh_l2cif_data_ind,
+    hidh_l2cif_cong_ind,
+    NULL                        /* tL2CA_TX_COMPLETE_CB */
+};
+
+/*******************************************************************************
+**
+** Function         hidh_l2cif_reg
+**
+** Description      This function initializes the SDP unit.
+**
+** Returns          void
+**
+*******************************************************************************/
+tHID_STATUS hidh_conn_reg (void)
+{
+    int xx;
+
+    /* Initialize the L2CAP configuration. We only care about MTU and flush */
+    memset(&hh_cb.l2cap_cfg, 0, sizeof(tL2CAP_CFG_INFO));
+
+    hh_cb.l2cap_cfg.mtu_present          = TRUE;
+    hh_cb.l2cap_cfg.mtu                  = HID_HOST_MTU;
+    hh_cb.l2cap_cfg.flush_to_present     = TRUE;
+    hh_cb.l2cap_cfg.flush_to             = HID_HOST_FLUSH_TO;
+
+    /* Now, register with L2CAP */
+    if (!L2CA_Register (HID_PSM_CONTROL, (tL2CAP_APPL_INFO *) &hst_reg_info)) {
+        HIDH_TRACE_ERROR ("HID-Host Control Registration failed");
+        return (HID_ERR_L2CAP_FAILED) ;
+    }
+    if (!L2CA_Register (HID_PSM_INTERRUPT, (tL2CAP_APPL_INFO *) &hst_reg_info)) {
+        L2CA_Deregister( HID_PSM_CONTROL ) ;
+        HIDH_TRACE_ERROR ("HID-Host Interrupt Registration failed");
+        return (HID_ERR_L2CAP_FAILED) ;
+    }
+
+    for (xx = 0; xx < HID_HOST_MAX_DEVICES; xx++) {
+        hh_cb.devices[xx].in_use = FALSE ;
+        hh_cb.devices[xx].conn.conn_state = HID_CONN_STATE_UNUSED;
+    }
+
+    return (HID_SUCCESS);
+}
+
+/*******************************************************************************
+**
+** Function         hidh_conn_disconnect
+**
+** Description      This function disconnects a connection.
+**
+** Returns          TRUE if disconnect started, FALSE if already disconnected
+**
+*******************************************************************************/
+tHID_STATUS hidh_conn_disconnect (UINT8 dhandle)
+{
+    tHID_CONN *p_hcon = &hh_cb.devices[dhandle].conn;
+
+    HIDH_TRACE_EVENT ("HID-Host disconnect");
+
+    if ((p_hcon->ctrl_cid != 0) || (p_hcon->intr_cid != 0)) {
+        p_hcon->conn_state = HID_CONN_STATE_DISCONNECTING;
+
+        /* Set l2cap idle timeout to 0 (so ACL link is disconnected
+         * immediately after last channel is closed) */
+        L2CA_SetIdleTimeoutByBdAddr(hh_cb.devices[dhandle].addr, 0, BT_TRANSPORT_BR_EDR);
+        /* Disconnect both interrupt and control channels */
+        if (p_hcon->intr_cid) {
+            L2CA_DisconnectReq (p_hcon->intr_cid);
+        } else if (p_hcon->ctrl_cid) {
+            L2CA_DisconnectReq (p_hcon->ctrl_cid);
+        }
+    } else {
+        p_hcon->conn_state = HID_CONN_STATE_UNUSED;
+    }
+
+    return (HID_SUCCESS);
+}
+
+/*******************************************************************************
+**
+** Function         hidh_sec_check_complete_term
+**
+** Description      HID security check complete callback function.
+**
+** Returns          Send L2CA_ConnectRsp OK if secutiry check succeed; otherwise
+**                  send security block L2C connection response.
+**
+*******************************************************************************/
+void hidh_sec_check_complete_term (BD_ADDR bd_addr, tBT_TRANSPORT transport, void *p_ref_data, UINT8 res)
+{
+    tHID_HOST_DEV_CTB *p_dev = (tHID_HOST_DEV_CTB *) p_ref_data;
+    UNUSED(bd_addr);
+    UNUSED (transport);
+
+    if ( res == BTM_SUCCESS && p_dev->conn.conn_state == HID_CONN_STATE_SECURITY ) {
+        p_dev->conn.disc_reason = HID_SUCCESS;  /* Authentication passed. Reset disc_reason (from HID_ERR_AUTH_FAILED) */
+
+        p_dev->conn.conn_state = HID_CONN_STATE_CONNECTING_INTR;
+
+        /* Send response to the L2CAP layer. */
+        L2CA_ConnectRsp (p_dev->addr, p_dev->conn.ctrl_id, p_dev->conn.ctrl_cid, L2CAP_CONN_OK, L2CAP_CONN_OK);
+
+        /* Send a Configuration Request. */
+        L2CA_ConfigReq (p_dev->conn.ctrl_cid, &hh_cb.l2cap_cfg);
+
+    }
+    /* security check fail */
+    else if (res != BTM_SUCCESS) {
+        p_dev->conn.disc_reason = HID_ERR_AUTH_FAILED;      /* Save reason for disconnecting */
+        p_dev->conn.conn_state = HID_CONN_STATE_UNUSED;
+        L2CA_ConnectRsp (p_dev->addr, p_dev->conn.ctrl_id, p_dev->conn.ctrl_cid, L2CAP_CONN_SECURITY_BLOCK, L2CAP_CONN_OK);
+    }
+}
+
+/*******************************************************************************
+**
+** Function         hidh_l2cif_connect_ind
+**
+** Description      This function handles an inbound connection indication
+**                  from L2CAP. This is the case where we are acting as a
+**                  server.
+**
+** Returns          void
+**
+*******************************************************************************/
+static void hidh_l2cif_connect_ind (BD_ADDR  bd_addr, UINT16 l2cap_cid, UINT16 psm, UINT8 l2cap_id)
+{
+    tHID_CONN    *p_hcon;
+    BOOLEAN      bAccept = TRUE;
+    UINT8        i = HID_HOST_MAX_DEVICES;
+    tHID_HOST_DEV_CTB *p_dev;
+
+    HIDH_TRACE_EVENT ("HID-Host Rcvd L2CAP conn ind, PSM: 0x%04x  CID 0x%x", psm, l2cap_cid);
+
+    /* always add incoming connection device into HID database by default */
+    if (HID_HostAddDev(bd_addr, HID_SEC_REQUIRED, &i) != HID_SUCCESS) {
+        L2CA_ConnectRsp (bd_addr, l2cap_id, l2cap_cid, L2CAP_CONN_SECURITY_BLOCK, 0);
+        return;
+    }
+
+    p_hcon = &hh_cb.devices[i].conn;
+    p_dev  = &hh_cb.devices[i];
+
+    /* Check we are in the correct state for this */
+    if (psm == HID_PSM_INTERRUPT) {
+        if (p_hcon->ctrl_cid == 0) {
+            HIDH_TRACE_WARNING ("HID-Host Rcvd INTR L2CAP conn ind, but no CTL channel");
+            bAccept = FALSE;
+        }
+        if (p_hcon->conn_state != HID_CONN_STATE_CONNECTING_INTR) {
+            HIDH_TRACE_WARNING ("HID-Host Rcvd INTR L2CAP conn ind, wrong state: %d",
+                                p_hcon->conn_state);
+            bAccept = FALSE;
+        }
+    } else { /* CTRL channel */
+#if defined(HID_HOST_ACPT_NEW_CONN) && (HID_HOST_ACPT_NEW_CONN == TRUE)
+        p_hcon->ctrl_cid = p_hcon->intr_cid = 0;
+        p_hcon->conn_state = HID_CONN_STATE_UNUSED;
+#else
+        if (p_hcon->conn_state != HID_CONN_STATE_UNUSED) {
+            HIDH_TRACE_WARNING ("HID-Host - Rcvd CTL L2CAP conn ind, wrong state: %d",
+                                p_hcon->conn_state);
+            bAccept = FALSE;
+        }
+#endif
+    }
+
+    if (!bAccept) {
+        L2CA_ConnectRsp (bd_addr, l2cap_id, l2cap_cid, L2CAP_CONN_NO_RESOURCES, 0);
+        return;
+    }
+
+    if (psm == HID_PSM_CONTROL) {
+        p_hcon->conn_flags = 0;
+        p_hcon->ctrl_cid   = l2cap_cid;
+        p_hcon->ctrl_id    = l2cap_id;
+        p_hcon->disc_reason = HID_L2CAP_CONN_FAIL;  /* In case disconnection occurs before security is completed, then set CLOSE_EVT reason code to 'connection failure' */
+
+        p_hcon->conn_state = HID_CONN_STATE_SECURITY;
+        if (btm_sec_mx_access_request (p_dev->addr, HID_PSM_CONTROL,
+                                       FALSE, BTM_SEC_PROTO_HID,
+                                       (p_dev->attr_mask & HID_SEC_REQUIRED) ? HID_SEC_CHN : HID_NOSEC_CHN,
+                                       &hidh_sec_check_complete_term, p_dev) == BTM_CMD_STARTED) {
+            L2CA_ConnectRsp (bd_addr, l2cap_id, l2cap_cid, L2CAP_CONN_PENDING, L2CAP_CONN_OK);
+        }
+
+        return;
+    }
+
+    /* Transition to the next appropriate state, configuration */
+    p_hcon->conn_state = HID_CONN_STATE_CONFIG;
+    p_hcon->intr_cid   = l2cap_cid;
+
+    /* Send response to the L2CAP layer. */
+    L2CA_ConnectRsp (bd_addr, l2cap_id, l2cap_cid, L2CAP_CONN_OK, L2CAP_CONN_OK);
+
+    /* Send a Configuration Request. */
+    L2CA_ConfigReq (l2cap_cid, &hh_cb.l2cap_cfg);
+
+    HIDH_TRACE_EVENT ("HID-Host Rcvd L2CAP conn ind, sent config req, PSM: 0x%04x  CID 0x%x",
+                      psm, l2cap_cid);
+}
+
+/*******************************************************************************
+**
+** Function         hidh_proc_repage_timeout
+**
+** Description      This function handles timeout (to page device).
+**
+** Returns          void
+**
+*******************************************************************************/
+void hidh_proc_repage_timeout (TIMER_LIST_ENT *p_tle)
+{
+    hidh_conn_initiate( (UINT8) p_tle->param ) ;
+    hh_cb.devices[p_tle->param].conn_tries++;
+    hh_cb.callback( (UINT8) p_tle->param, hh_cb.devices[p_tle->param].addr,
+                    HID_HDEV_EVT_RETRYING, hh_cb.devices[p_tle->param].conn_tries, NULL ) ;
+}
+
+/*******************************************************************************
+**
+** Function         hidh_sec_check_complete_orig
+**
+** Description      This function checks to see if security procedures are being
+**                  carried out or not..
+**
+** Returns          void
+**
+*******************************************************************************/
+void hidh_sec_check_complete_orig (BD_ADDR bd_addr, tBT_TRANSPORT transport, void *p_ref_data, UINT8 res)
+{
+    tHID_HOST_DEV_CTB *p_dev = (tHID_HOST_DEV_CTB *) p_ref_data;
+    UINT8 dhandle;
+    UNUSED(bd_addr);
+    UNUSED (transport);
+
+    dhandle = ((UINT32)p_dev - (UINT32) & (hh_cb.devices[0])) / sizeof(tHID_HOST_DEV_CTB);
+    if ( res == BTM_SUCCESS && p_dev->conn.conn_state == HID_CONN_STATE_SECURITY ) {
+        HIDH_TRACE_EVENT ("HID-Host Originator security pass.");
+        p_dev->conn.disc_reason = HID_SUCCESS;  /* Authentication passed. Reset disc_reason (from HID_ERR_AUTH_FAILED) */
+
+        /* Transition to the next appropriate state, configuration */
+        p_dev->conn.conn_state = HID_CONN_STATE_CONFIG;
+        L2CA_ConfigReq (p_dev->conn.ctrl_cid, &hh_cb.l2cap_cfg);
+        HIDH_TRACE_EVENT ("HID-Host Got Control conn cnf, sent cfg req, CID: 0x%x", p_dev->conn.ctrl_cid);
+
+    }
+
+    if ( res != BTM_SUCCESS && p_dev->conn.conn_state == HID_CONN_STATE_SECURITY ) {
+#if (HID_HOST_MAX_CONN_RETRY > 0)
+        if ( res == BTM_DEVICE_TIMEOUT ) {
+            if ( p_dev->conn_tries <= HID_HOST_MAX_CONN_RETRY ) {
+                hidh_conn_retry (dhandle);
+                return;
+            }
+        }
+#endif
+        p_dev->conn.disc_reason = HID_ERR_AUTH_FAILED;      /* Save reason for disconnecting */
+        hidh_conn_disconnect(dhandle);
+    }
+
+}
+
+/*******************************************************************************
+**
+** Function         hidh_l2cif_connect_cfm
+**
+** Description      This function handles the connect confirm events
+**                  from L2CAP. This is the case when we are acting as a
+**                  client and have sent a connect request.
+**
+** Returns          void
+**
+*******************************************************************************/
+static void hidh_l2cif_connect_cfm (UINT16 l2cap_cid, UINT16 result)
+{
+    UINT8 dhandle;
+    tHID_CONN    *p_hcon = NULL;
+    UINT32  reason;
+    tHID_HOST_DEV_CTB *p_dev = NULL;
+
+    /* Find CCB based on CID, and verify we are in a state to accept this message */
+    if ( (dhandle = find_conn_by_cid(l2cap_cid)) < HID_HOST_MAX_DEVICES ) {
+        p_dev = &hh_cb.devices[dhandle];
+        p_hcon = &hh_cb.devices[dhandle].conn;
+    }
+
+    if ((p_hcon == NULL)
+            || (!(p_hcon->conn_flags & HID_CONN_FLAGS_IS_ORIG))
+            || ((l2cap_cid == p_hcon->ctrl_cid) && (p_hcon->conn_state != HID_CONN_STATE_CONNECTING_CTRL))
+            || ((l2cap_cid == p_hcon->intr_cid) && (p_hcon->conn_state != HID_CONN_STATE_CONNECTING_INTR)
+                && (p_hcon->conn_state != HID_CONN_STATE_DISCONNECTING))) {
+        HIDH_TRACE_WARNING ("HID-Host Rcvd unexpected conn cnf, CID 0x%x ", l2cap_cid);
+        return;
+    }
+
+    if (result != L2CAP_CONN_OK) {
+        if (l2cap_cid == p_hcon->ctrl_cid) {
+            p_hcon->ctrl_cid = 0;
+        } else {
+            p_hcon->intr_cid = 0;
+        }
+
+        hidh_conn_disconnect(dhandle);
+
+#if (HID_HOST_MAX_CONN_RETRY > 0)
+        if ( (hh_cb.devices[dhandle].conn_tries <= HID_HOST_MAX_CONN_RETRY) &&
+                (result == HCI_ERR_CONNECTION_TOUT || result == HCI_ERR_UNSPECIFIED ||
+                 result == HCI_ERR_PAGE_TIMEOUT) ) {
+            hidh_conn_retry(dhandle);
+        } else
+#endif
+        {
+            reason = HID_L2CAP_CONN_FAIL | (UINT32) result ;
+            hh_cb.callback( dhandle, hh_cb.devices[dhandle].addr, HID_HDEV_EVT_CLOSE, reason, NULL ) ;
+        }
+        return;
+    }
+    /* receive Control Channel connect confirmation */
+    if (l2cap_cid == p_hcon->ctrl_cid) {
+        /* check security requirement */
+        p_hcon->conn_state = HID_CONN_STATE_SECURITY;
+        p_hcon->disc_reason = HID_L2CAP_CONN_FAIL;  /* In case disconnection occurs before security is completed, then set CLOSE_EVT reason code to "connection failure" */
+
+        btm_sec_mx_access_request (p_dev->addr, HID_PSM_CONTROL,
+                                   TRUE, BTM_SEC_PROTO_HID,
+                                   (p_dev->attr_mask & HID_SEC_REQUIRED) ? HID_SEC_CHN : HID_NOSEC_CHN,
+                                   &hidh_sec_check_complete_orig, p_dev);
+    } else {
+        p_hcon->conn_state = HID_CONN_STATE_CONFIG;
+        /* Send a Configuration Request. */
+        L2CA_ConfigReq (l2cap_cid, &hh_cb.l2cap_cfg);
+        HIDH_TRACE_EVENT ("HID-Host got Interrupt conn cnf, sent cfg req, CID: 0x%x", l2cap_cid);
+    }
+
+    return;
+}
+
+/*******************************************************************************
+**
+** Function         hidh_l2cif_config_ind
+**
+** Description      This function processes the L2CAP configuration indication
+**                  event.
+**
+** Returns          void
+**
+*******************************************************************************/
+static void hidh_l2cif_config_ind (UINT16 l2cap_cid, tL2CAP_CFG_INFO *p_cfg)
+{
+    UINT8 dhandle;
+    tHID_CONN    *p_hcon = NULL;
+    UINT32  reason;
+
+    /* Find CCB based on CID */
+    if ( (dhandle = find_conn_by_cid(l2cap_cid)) < HID_HOST_MAX_DEVICES ) {
+        p_hcon = &hh_cb.devices[dhandle].conn;
+    }
+
+    if (p_hcon == NULL) {
+        HIDH_TRACE_WARNING ("HID-Host Rcvd L2CAP cfg ind, unknown CID: 0x%x", l2cap_cid);
+        return;
+    }
+
+    HIDH_TRACE_EVENT ("HID-Host Rcvd cfg ind, sent cfg cfm, CID: 0x%x", l2cap_cid);
+
+    /* Remember the remote MTU size */
+    if ((!p_cfg->mtu_present) || (p_cfg->mtu > HID_HOST_MTU)) {
+        p_hcon->rem_mtu_size = HID_HOST_MTU;
+    } else {
+        p_hcon->rem_mtu_size = p_cfg->mtu;
+    }
+
+    /* For now, always accept configuration from the other side */
+    p_cfg->flush_to_present = FALSE;
+    p_cfg->mtu_present      = FALSE;
+    p_cfg->result           = L2CAP_CFG_OK;
+
+    L2CA_ConfigRsp (l2cap_cid, p_cfg);
+
+    if (l2cap_cid == p_hcon->ctrl_cid) {
+        p_hcon->conn_flags |= HID_CONN_FLAGS_HIS_CTRL_CFG_DONE;
+        if ((p_hcon->conn_flags & HID_CONN_FLAGS_IS_ORIG) &&
+                (p_hcon->conn_flags & HID_CONN_FLAGS_MY_CTRL_CFG_DONE)) {
+            /* Connect interrupt channel */
+            p_hcon->disc_reason = HID_L2CAP_CONN_FAIL;  /* Reset initial reason for CLOSE_EVT: Connection Attempt was made but failed */
+            if ((p_hcon->intr_cid = L2CA_ConnectReq (HID_PSM_INTERRUPT, hh_cb.devices[dhandle].addr)) == 0) {
+                HIDH_TRACE_WARNING ("HID-Host INTR Originate failed");
+                reason = HID_L2CAP_REQ_FAIL ;
+                p_hcon->conn_state = HID_CONN_STATE_UNUSED;
+                hidh_conn_disconnect (dhandle);
+                hh_cb.callback( dhandle, hh_cb.devices[dhandle].addr, HID_HDEV_EVT_CLOSE, reason, NULL ) ;
+                return;
+            } else {
+                /* Transition to the next appropriate state, waiting for connection confirm on interrupt channel. */
+                p_hcon->conn_state = HID_CONN_STATE_CONNECTING_INTR;
+            }
+        }
+    } else {
+        p_hcon->conn_flags |= HID_CONN_FLAGS_HIS_INTR_CFG_DONE;
+    }
+
+    /* If all configuration is complete, change state and tell management we are up */
+    if (((p_hcon->conn_flags & HID_CONN_FLAGS_ALL_CONFIGURED) == HID_CONN_FLAGS_ALL_CONFIGURED)
+            && (p_hcon->conn_state == HID_CONN_STATE_CONFIG)) {
+        p_hcon->conn_state = HID_CONN_STATE_CONNECTED;
+        /* Reset disconnect reason to success, as connection successful */
+        p_hcon->disc_reason = HID_SUCCESS;
+
+        hh_cb.devices[dhandle].state = HID_DEV_CONNECTED;
+        hh_cb.callback( dhandle,  hh_cb.devices[dhandle].addr, HID_HDEV_EVT_OPEN, 0, NULL ) ;
+    }
+}
+
+
+/*******************************************************************************
+**
+** Function         hidh_l2cif_config_cfm
+**
+** Description      This function processes the L2CAP configuration confirmation
+**                  event.
+**
+** Returns          void
+**
+*******************************************************************************/
+static void hidh_l2cif_config_cfm (UINT16 l2cap_cid, tL2CAP_CFG_INFO *p_cfg)
+{
+    UINT8 dhandle;
+    tHID_CONN    *p_hcon = NULL;
+    UINT32  reason;
+
+    HIDH_TRACE_EVENT ("HID-Host Rcvd cfg cfm, CID: 0x%x  Result: %d", l2cap_cid, p_cfg->result);
+
+    /* Find CCB based on CID */
+    if ( (dhandle = find_conn_by_cid(l2cap_cid)) < HID_HOST_MAX_DEVICES ) {
+        p_hcon = &hh_cb.devices[dhandle].conn;
+    }
+
+    if (p_hcon == NULL) {
+        HIDH_TRACE_WARNING ("HID-Host Rcvd L2CAP cfg ind, unknown CID: 0x%x", l2cap_cid);
+        return;
+    }
+
+    /* If configuration failed, disconnect the channel(s) */
+    if (p_cfg->result != L2CAP_CFG_OK) {
+        hidh_conn_disconnect (dhandle);
+        reason = HID_L2CAP_CFG_FAIL | (UINT32) p_cfg->result ;
+        hh_cb.callback( dhandle, hh_cb.devices[dhandle].addr, HID_HDEV_EVT_CLOSE, reason, NULL ) ;
+        return;
+    }
+
+    if (l2cap_cid == p_hcon->ctrl_cid) {
+        p_hcon->conn_flags |= HID_CONN_FLAGS_MY_CTRL_CFG_DONE;
+        if ((p_hcon->conn_flags & HID_CONN_FLAGS_IS_ORIG) &&
+                (p_hcon->conn_flags & HID_CONN_FLAGS_HIS_CTRL_CFG_DONE)) {
+            /* Connect interrupt channel */
+            p_hcon->disc_reason = HID_L2CAP_CONN_FAIL;  /* Reset initial reason for CLOSE_EVT: Connection Attempt was made but failed */
+            if ((p_hcon->intr_cid = L2CA_ConnectReq (HID_PSM_INTERRUPT, hh_cb.devices[dhandle].addr)) == 0) {
+                HIDH_TRACE_WARNING ("HID-Host INTR Originate failed");
+                reason = HID_L2CAP_REQ_FAIL ;
+                p_hcon->conn_state = HID_CONN_STATE_UNUSED;
+                hidh_conn_disconnect (dhandle);
+                hh_cb.callback( dhandle, hh_cb.devices[dhandle].addr, HID_HDEV_EVT_CLOSE, reason, NULL ) ;
+                return;
+            } else {
+                /* Transition to the next appropriate state, waiting for connection confirm on interrupt channel. */
+                p_hcon->conn_state = HID_CONN_STATE_CONNECTING_INTR;
+            }
+        }
+    } else {
+        p_hcon->conn_flags |= HID_CONN_FLAGS_MY_INTR_CFG_DONE;
+    }
+
+    /* If all configuration is complete, change state and tell management we are up */
+    if (((p_hcon->conn_flags & HID_CONN_FLAGS_ALL_CONFIGURED) == HID_CONN_FLAGS_ALL_CONFIGURED)
+            && (p_hcon->conn_state == HID_CONN_STATE_CONFIG)) {
+        p_hcon->conn_state = HID_CONN_STATE_CONNECTED;
+        /* Reset disconnect reason to success, as connection successful */
+        p_hcon->disc_reason = HID_SUCCESS;
+
+        hh_cb.devices[dhandle].state = HID_DEV_CONNECTED;
+        hh_cb.callback( dhandle, hh_cb.devices[dhandle].addr, HID_HDEV_EVT_OPEN, 0, NULL ) ;
+    }
+}
+
+
+/*******************************************************************************
+**
+** Function         hidh_l2cif_disconnect_ind
+**
+** Description      This function handles a disconnect event from L2CAP. If
+**                  requested to, we ack the disconnect before dropping the CCB
+**
+** Returns          void
+**
+*******************************************************************************/
+static void hidh_l2cif_disconnect_ind (UINT16 l2cap_cid, BOOLEAN ack_needed)
+{
+    UINT8 dhandle;
+    tHID_CONN    *p_hcon = NULL;
+    UINT16 disc_res = HCI_SUCCESS;
+    UINT16 hid_close_evt_reason;
+
+    /* Find CCB based on CID */
+    if ( (dhandle = find_conn_by_cid(l2cap_cid)) < HID_HOST_MAX_DEVICES ) {
+        p_hcon = &hh_cb.devices[dhandle].conn;
+    }
+
+    if (p_hcon == NULL) {
+        HIDH_TRACE_WARNING ("HID-Host Rcvd L2CAP disc, unknown CID: 0x%x", l2cap_cid);
+        return;
+    }
+
+    if (ack_needed) {
+        L2CA_DisconnectRsp (l2cap_cid);
+    }
+
+    HIDH_TRACE_EVENT ("HID-Host Rcvd L2CAP disc, CID: 0x%x", l2cap_cid);
+
+    p_hcon->conn_state = HID_CONN_STATE_DISCONNECTING;
+
+    if (l2cap_cid == p_hcon->ctrl_cid) {
+        p_hcon->ctrl_cid = 0;
+    } else {
+        p_hcon->intr_cid = 0;
+    }
+
+    if ((p_hcon->ctrl_cid == 0) && (p_hcon->intr_cid == 0)) {
+        hh_cb.devices[dhandle].state = HID_DEV_NO_CONN;
+        p_hcon->conn_state = HID_CONN_STATE_UNUSED;
+
+        if ( !ack_needed ) {
+            disc_res = btm_get_acl_disc_reason_code();
+        }
+
+#if (HID_HOST_MAX_CONN_RETRY > 0)
+        if ( (disc_res == HCI_ERR_CONNECTION_TOUT || disc_res == HCI_ERR_UNSPECIFIED) &&
+                (!(hh_cb.devices[dhandle].attr_mask & HID_RECONN_INIT)) &&
+                (hh_cb.devices[dhandle].attr_mask & HID_NORMALLY_CONNECTABLE)) {
+            hh_cb.devices[dhandle].conn_tries = 0;
+            hh_cb.devices[dhandle].conn.timer_entry.param = (UINT32) dhandle;
+            btu_start_timer (&(hh_cb.devices[dhandle].conn.timer_entry), BTU_TTYPE_HID_HOST_REPAGE_TO, HID_HOST_REPAGE_WIN);
+            hh_cb.callback( dhandle,  hh_cb.devices[dhandle].addr, HID_HDEV_EVT_CLOSE, disc_res, NULL);
+        } else
+#endif
+        {
+            /* Set reason code for HID_HDEV_EVT_CLOSE */
+            hid_close_evt_reason = p_hcon->disc_reason;
+
+            /* If we got baseband sent HCI_DISCONNECT_COMPLETE_EVT due to security failure, then set reason to HID_ERR_AUTH_FAILED */
+            if ((disc_res == HCI_ERR_AUTH_FAILURE)                        ||
+                    (disc_res == HCI_ERR_KEY_MISSING)                         ||
+                    (disc_res == HCI_ERR_HOST_REJECT_SECURITY)                ||
+                    (disc_res == HCI_ERR_PAIRING_NOT_ALLOWED)                 ||
+                    (disc_res == HCI_ERR_UNIT_KEY_USED)                       ||
+                    (disc_res == HCI_ERR_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED) ||
+                    (disc_res == HCI_ERR_ENCRY_MODE_NOT_ACCEPTABLE)           ||
+                    (disc_res == HCI_ERR_REPEATED_ATTEMPTS)) {
+                hid_close_evt_reason = HID_ERR_AUTH_FAILED;
+            }
+
+            hh_cb.callback( dhandle,  hh_cb.devices[dhandle].addr, HID_HDEV_EVT_CLOSE, hid_close_evt_reason, NULL ) ;
+        }
+    }
+}
+
+
+/*******************************************************************************
+**
+** Function         hidh_l2cif_disconnect_cfm
+**
+** Description      This function handles a disconnect confirm event from L2CAP.
+**
+** Returns          void
+**
+*******************************************************************************/
+static void hidh_l2cif_disconnect_cfm (UINT16 l2cap_cid, UINT16 result)
+{
+    UINT8 dhandle;
+    tHID_CONN    *p_hcon = NULL;
+    UNUSED(result);
+
+    /* Find CCB based on CID */
+    if ( (dhandle = find_conn_by_cid(l2cap_cid)) < HID_HOST_MAX_DEVICES ) {
+        p_hcon = &hh_cb.devices[dhandle].conn;
+    }
+
+    if (p_hcon == NULL) {
+        HIDH_TRACE_WARNING ("HID-Host Rcvd L2CAP disc cfm, unknown CID: 0x%x", l2cap_cid);
+        return;
+    }
+
+    HIDH_TRACE_EVENT ("HID-Host Rcvd L2CAP disc cfm, CID: 0x%x", l2cap_cid);
+
+    if (l2cap_cid == p_hcon->ctrl_cid) {
+        p_hcon->ctrl_cid = 0;
+    } else {
+        p_hcon->intr_cid = 0;
+        if (p_hcon->ctrl_cid) {
+            HIDH_TRACE_EVENT ("HID-Host Initiating L2CAP Ctrl disconnection");
+            L2CA_DisconnectReq (p_hcon->ctrl_cid);
+        }
+    }
+
+    if ((p_hcon->ctrl_cid == 0) && (p_hcon->intr_cid == 0)) {
+        hh_cb.devices[dhandle].state = HID_DEV_NO_CONN;
+        p_hcon->conn_state = HID_CONN_STATE_UNUSED;
+        hh_cb.callback( dhandle, hh_cb.devices[dhandle].addr, HID_HDEV_EVT_CLOSE, p_hcon->disc_reason, NULL ) ;
+    }
+}
+
+
+/*******************************************************************************
+**
+** Function         hidh_l2cif_cong_ind
+**
+** Description      This function handles a congestion status event from L2CAP.
+**
+** Returns          void
+**
+*******************************************************************************/
+static void hidh_l2cif_cong_ind (UINT16 l2cap_cid, BOOLEAN congested)
+{
+    UINT8 dhandle;
+    tHID_CONN    *p_hcon = NULL;
+
+    /* Find CCB based on CID */
+    if ( (dhandle = find_conn_by_cid(l2cap_cid)) < HID_HOST_MAX_DEVICES ) {
+        p_hcon = &hh_cb.devices[dhandle].conn;
+    }
+
+    if (p_hcon == NULL) {
+        HIDH_TRACE_WARNING ("HID-Host Rcvd L2CAP congestion status, unknown CID: 0x%x", l2cap_cid);
+        return;
+    }
+
+    HIDH_TRACE_EVENT ("HID-Host Rcvd L2CAP congestion status, CID: 0x%x  Cong: %d", l2cap_cid, congested);
+
+    if (congested) {
+        p_hcon->conn_flags |= HID_CONN_FLAGS_CONGESTED;
+    } else {
+        p_hcon->conn_flags &= ~HID_CONN_FLAGS_CONGESTED;
+
+    }
+}
+
+
+/*******************************************************************************
+**
+** Function         hidh_l2cif_data_ind
+**
+** Description      This function is called when data is received from L2CAP.
+**                  if we are the originator of the connection, we are the SDP
+**                  client, and the received message is queued up for the client.
+**
+**                  If we are the destination of the connection, we are the SDP
+**                  server, so the message is passed to the server processing
+**                  function.
+**
+** Returns          void
+**
+*******************************************************************************/
+static void hidh_l2cif_data_ind (UINT16 l2cap_cid, BT_HDR *p_msg)
+{
+    UINT8           *p_data = (UINT8 *)(p_msg + 1) + p_msg->offset;
+    UINT8           ttype, param, rep_type, evt;
+    UINT8 dhandle;
+    tHID_CONN    *p_hcon = NULL;
+
+    HIDH_TRACE_DEBUG ("HID-Host hidh_l2cif_data_ind [l2cap_cid=0x%04x]", l2cap_cid);
+
+    /* Find CCB based on CID */
+    if ((dhandle = find_conn_by_cid(l2cap_cid)) < HID_HOST_MAX_DEVICES) {
+        p_hcon = &hh_cb.devices[dhandle].conn;
+    }
+
+    if (p_hcon == NULL) {
+        HIDH_TRACE_WARNING ("HID-Host Rcvd L2CAP data, unknown CID: 0x%x", l2cap_cid);
+        osi_free (p_msg);
+        return;
+    }
+
+
+    ttype    = HID_GET_TRANS_FROM_HDR(*p_data);
+    param    = HID_GET_PARAM_FROM_HDR(*p_data);
+    rep_type = param & HID_PAR_REP_TYPE_MASK;
+    p_data++;
+
+    /* Get rid of the data type */
+    p_msg->len--;
+    p_msg->offset++;
+
+    switch (ttype) {
+    case HID_TRANS_HANDSHAKE:
+        hh_cb.callback(dhandle,  hh_cb.devices[dhandle].addr, HID_HDEV_EVT_HANDSHAKE, param, NULL);
+        osi_free (p_msg);
+        break;
+
+    case HID_TRANS_CONTROL:
+        switch (param) {
+        case HID_PAR_CONTROL_VIRTUAL_CABLE_UNPLUG:
+            hidh_conn_disconnect( dhandle ) ;
+            /* Device is unplugging from us. Tell USB */
+            hh_cb.callback(dhandle,  hh_cb.devices[dhandle].addr, HID_HDEV_EVT_VC_UNPLUG, 0, NULL);
+            break;
+
+        default:
+            break;
+        }
+        osi_free (p_msg);
+        break;
+
+
+    case HID_TRANS_DATA:
+        evt = (hh_cb.devices[dhandle].conn.intr_cid == l2cap_cid) ?
+              HID_HDEV_EVT_INTR_DATA : HID_HDEV_EVT_CTRL_DATA;
+        hh_cb.callback(dhandle, hh_cb.devices[dhandle].addr, evt, rep_type, p_msg);
+        break;
+
+    case HID_TRANS_DATAC:
+        evt = (hh_cb.devices[dhandle].conn.intr_cid == l2cap_cid) ?
+              HID_HDEV_EVT_INTR_DATC : HID_HDEV_EVT_CTRL_DATC;
+        hh_cb.callback(dhandle,  hh_cb.devices[dhandle].addr, evt, rep_type, p_msg);
+        break;
+
+    default:
+        osi_free (p_msg);
+        break;
+    }
+
+}
+
+/*******************************************************************************
+**
+** Function         hidh_conn_snd_data
+**
+** Description      This function is sends out data.
+**
+** Returns          tHID_STATUS
+**
+*******************************************************************************/
+tHID_STATUS hidh_conn_snd_data (UINT8 dhandle, UINT8 trans_type, UINT8 param,
+                                UINT16 data, UINT8 report_id, BT_HDR *buf)
+{
+    tHID_CONN   *p_hcon = &hh_cb.devices[dhandle].conn;
+    BT_HDR      *p_buf;
+    UINT8       *p_out;
+    UINT16      bytes_copied;
+    BOOLEAN     seg_req = FALSE;
+    UINT16      data_size;
+    UINT16      cid;
+    UINT16      buf_size;
+    UINT8       use_data = 0 ;
+    BOOLEAN     blank_datc = FALSE;
+
+    if (!BTM_IsAclConnectionUp(hh_cb.devices[dhandle].addr, BT_TRANSPORT_BR_EDR)) {
+        if (buf) {
+            osi_free ((void *)buf);
+        }
+        return ( HID_ERR_NO_CONNECTION );
+    }
+
+    if (p_hcon->conn_flags & HID_CONN_FLAGS_CONGESTED) {
+        if (buf) {
+            osi_free ((void *)buf);
+        }
+        return ( HID_ERR_CONGESTED );
+    }
+
+    switch ( trans_type ) {
+    case HID_TRANS_CONTROL:
+    case HID_TRANS_GET_REPORT:
+    case HID_TRANS_SET_REPORT:
+    case HID_TRANS_GET_PROTOCOL:
+    case HID_TRANS_SET_PROTOCOL:
+    case HID_TRANS_GET_IDLE:
+    case HID_TRANS_SET_IDLE:
+        cid = p_hcon->ctrl_cid;
+        buf_size = HID_CONTROL_BUF_SIZE;
+        break;
+    case HID_TRANS_DATA:
+        cid = p_hcon->intr_cid;
+        buf_size = HID_INTERRUPT_BUF_SIZE;
+        break;
+    default:
+        return (HID_ERR_INVALID_PARAM) ;
+    }
+
+    if ( trans_type == HID_TRANS_SET_IDLE ) {
+        use_data = 1;
+    } else if ( (trans_type == HID_TRANS_GET_REPORT) && (param & 0x08) ) {
+        use_data = 2;
+    }
+
+    do {
+        if ( buf == NULL || blank_datc ) {
+            if ((p_buf = (BT_HDR *)osi_malloc(buf_size)) == NULL) {
+                return (HID_ERR_NO_RESOURCES);
+            }
+
+            p_buf->offset = L2CAP_MIN_OFFSET;
+            seg_req = FALSE;
+            data_size = 0;
+            bytes_copied = 0;
+            blank_datc = FALSE;
+        } else if ( (buf->len > (p_hcon->rem_mtu_size - 1))) {
+            if ((p_buf = (BT_HDR *)osi_malloc(buf_size)) == NULL) {
+                return (HID_ERR_NO_RESOURCES);
+            }
+
+            p_buf->offset = L2CAP_MIN_OFFSET;
+            seg_req = TRUE;
+            data_size = buf->len;
+            bytes_copied = p_hcon->rem_mtu_size - 1;
+        } else {
+            p_buf = buf ;
+            p_buf->offset -= 1;
+            seg_req = FALSE;
+            data_size = buf->len;
+            bytes_copied = buf->len;
+        }
+
+        p_out         = (UINT8 *)(p_buf + 1) + p_buf->offset;
+        *p_out++      = HID_BUILD_HDR(trans_type, param);
+
+        /* If report ID required for this device */
+        if ( (trans_type == HID_TRANS_GET_REPORT) && (report_id != 0) ) {
+            *p_out = report_id;
+            data_size = bytes_copied = 1;
+        }
+
+
+        if (seg_req) {
+            memcpy (p_out, (((UINT8 *)(buf + 1)) + buf->offset), bytes_copied);
+            buf->offset += bytes_copied;
+            buf->len -= bytes_copied;
+        } else if ( use_data == 1) {
+            *(p_out + bytes_copied) = data & 0xff;
+        } else if ( use_data == 2 ) {
+            *(p_out + bytes_copied) = data & 0xff;
+            *(p_out + bytes_copied + 1) = (data >> 8) & 0xff ;
+        }
+
+        p_buf->len   = bytes_copied + 1 + use_data;
+        data_size    -= bytes_copied;
+
+        /* Send the buffer through L2CAP */
+        if ((p_hcon->conn_flags & HID_CONN_FLAGS_CONGESTED) || (!L2CA_DataWrite (cid, p_buf))) {
+            return (HID_ERR_CONGESTED);
+        }
+
+        if (data_size) {
+            trans_type = HID_TRANS_DATAC;
+        } else if ( bytes_copied == (p_hcon->rem_mtu_size - 1) ) {
+            trans_type = HID_TRANS_DATAC;
+            blank_datc = TRUE;
+        }
+
+    } while ((data_size != 0) || blank_datc ) ;
+
+    return (HID_SUCCESS);
+}
+/*******************************************************************************
+**
+** Function         hidh_conn_initiate
+**
+** Description      This function is called by the management to create a connection.
+**
+** Returns          void
+**
+*******************************************************************************/
+tHID_STATUS hidh_conn_initiate (UINT8 dhandle)
+{
+    UINT8   service_id = BTM_SEC_SERVICE_HIDH_NOSEC_CTRL;
+    UINT32  mx_chan_id = HID_NOSEC_CHN;
+
+    tHID_HOST_DEV_CTB *p_dev = &hh_cb.devices[dhandle];
+
+    if ( p_dev->conn.conn_state != HID_CONN_STATE_UNUSED ) {
+        return ( HID_ERR_CONN_IN_PROCESS );
+    }
+
+    p_dev->conn.ctrl_cid = 0;
+    p_dev->conn.intr_cid = 0;
+    p_dev->conn.disc_reason = HID_L2CAP_CONN_FAIL;  /* Reset initial reason for CLOSE_EVT: Connection Attempt was made but failed */
+
+    /* We are the originator of this connection */
+    p_dev->conn.conn_flags = HID_CONN_FLAGS_IS_ORIG;
+
+    if (p_dev->attr_mask & HID_SEC_REQUIRED) {
+        service_id = BTM_SEC_SERVICE_HIDH_SEC_CTRL;
+        mx_chan_id = HID_SEC_CHN;
+    }
+    BTM_SetOutService (p_dev->addr, service_id, mx_chan_id);
+
+    /* Check if L2CAP started the connection process */
+    if ((p_dev->conn.ctrl_cid = L2CA_ConnectReq (HID_PSM_CONTROL, p_dev->addr)) == 0) {
+        HIDH_TRACE_WARNING ("HID-Host Originate failed");
+        hh_cb.callback( dhandle,  hh_cb.devices[dhandle].addr, HID_HDEV_EVT_CLOSE,
+                        HID_ERR_L2CAP_FAILED, NULL ) ;
+    } else {
+        /* Transition to the next appropriate state, waiting for connection confirm on control channel. */
+        p_dev->conn.conn_state = HID_CONN_STATE_CONNECTING_CTRL;
+    }
+
+    return ( HID_SUCCESS );
+}
+
+
+/*******************************************************************************
+**
+** Function         find_conn_by_cid
+**
+** Description      This function finds a connection control block based on CID
+**
+** Returns          address of control block, or NULL if not found
+**
+*******************************************************************************/
+static UINT8 find_conn_by_cid (UINT16 cid)
+{
+    UINT8      xx;
+
+    for (xx = 0; xx < HID_HOST_MAX_DEVICES; xx++) {
+        if ((hh_cb.devices[xx].in_use) && (hh_cb.devices[xx].conn.conn_state != HID_CONN_STATE_UNUSED)
+                && ((hh_cb.devices[xx].conn.ctrl_cid == cid) || (hh_cb.devices[xx].conn.intr_cid == cid))) {
+            break;
+        }
+    }
+
+    return (xx);
+}
+
+void hidh_conn_dereg( void )
+{
+    L2CA_Deregister (HID_PSM_CONTROL);
+    L2CA_Deregister (HID_PSM_INTERRUPT);
+}
+
+/*******************************************************************************
+**
+** Function         hidh_conn_retry
+**
+** Description      This function is called to retry a failed connection.
+**
+** Returns          void
+**
+*******************************************************************************/
+static void hidh_conn_retry(  UINT8 dhandle )
+{
+    tHID_HOST_DEV_CTB *p_dev = &hh_cb.devices[dhandle];
+
+    p_dev->conn.conn_state = HID_CONN_STATE_UNUSED;
+    p_dev->conn.timer_entry.param = (UINT32) dhandle;
+#if (HID_HOST_REPAGE_WIN > 0)
+    btu_start_timer (&(p_dev->conn.timer_entry), BTU_TTYPE_HID_HOST_REPAGE_TO, HID_HOST_REPAGE_WIN);
+#else
+    hidh_proc_repage_timeout( &(p_dev->conn.timer_entry) );
+#endif
+}
+
+#endif // HID_HOST_INCLUDED

+ 2 - 1
components/bt/host/bluedroid/btc/profile/std/hid/include/hid_conn.h

@@ -25,7 +25,9 @@
 #ifndef HID_CONN_H
 #define HID_CONN_H
 
+#include "common/bt_defs.h"
 #if (HID_HOST_INCLUDED == TRUE)
+
 /* Define the HID Connection Block
 */
 typedef struct hid_conn {
@@ -56,7 +58,6 @@ typedef struct hid_conn {
     UINT16            rem_mtu_size;
     UINT16            disc_reason;                       /* Reason for disconnecting (for HID_HDEV_EVT_CLOSE) */
     TIMER_LIST_ENT    timer_entry;
-
 } tHID_CONN;
 
 #define HID_SEC_CHN   1

+ 10 - 3
components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h

@@ -67,6 +67,13 @@
 #define UC_BT_HFP_CLIENT_ENABLED            FALSE
 #endif
 
+//HID HOST(BT)
+#ifdef CONFIG_BT_HID_HOST_ENABLED
+#define UC_BT_HID_HOST_ENABLED           	CONFIG_BT_HID_HOST_ENABLED
+#else
+#define UC_BT_HID_HOST_ENABLED           	FALSE
+#endif
+
 //SSP
 #ifdef CONFIG_BT_SSP_ENABLED
 #define UC_BT_SSP_ENABLED                   CONFIG_BT_SSP_ENABLED
@@ -292,10 +299,10 @@
 #define UC_BT_LOG_MCA_TRACE_LEVEL           UC_TRACE_LEVEL_WARNING
 #endif
 
-#ifdef CONFIG_BT_LOG_HID_TRACE_LEVEL
-#define UC_BT_LOG_HID_TRACE_LEVEL           CONFIG_BT_LOG_HID_TRACE_LEVEL
+#ifdef CONFIG_BT_LOG_HIDH_TRACE_LEVEL
+#define UC_BT_LOG_HIDH_TRACE_LEVEL           CONFIG_BT_LOG_HIDH_TRACE_LEVEL
 #else
-#define UC_BT_LOG_HID_TRACE_LEVEL           UC_TRACE_LEVEL_WARNING
+#define UC_BT_LOG_HIDH_TRACE_LEVEL           UC_TRACE_LEVEL_WARNING
 #endif
 
 #ifdef CONFIG_BT_LOG_APPL_TRACE_LEVEL

+ 5 - 0
components/bt/host/bluedroid/common/include/common/bt_target.h

@@ -127,6 +127,11 @@
 #define BT_SSP_INCLUDED             TRUE
 #endif /* UC_BT_SSP_ENABLED */
 
+#if UC_BT_HID_HOST_ENABLED
+#define HID_HOST_INCLUDED           TRUE
+#define BTA_HH_INCLUDED             TRUE
+#endif /* UC_BT_HID_HOST_ENABLED */
+
 #endif /* UC_BT_CLASSIC_ENABLED */
 
 #ifndef CLASSIC_BT_INCLUDED

+ 1 - 1
components/bt/host/bluedroid/common/include/common/bt_trace.h

@@ -198,7 +198,7 @@ static inline void trc_dump_buffer(const char *prefix, uint8_t *data, uint16_t l
 #define AVCT_INITIAL_TRACE_LEVEL            UC_BT_LOG_AVCT_TRACE_LEVEL
 #define AVRC_INITIAL_TRACE_LEVEL            UC_BT_LOG_AVRC_TRACE_LEVEL
 #define MCA_INITIAL_TRACE_LEVEL             UC_BT_LOG_MCA_TRACE_LEVEL
-#define HID_INITIAL_TRACE_LEVEL             UC_BT_LOG_HID_TRACE_LEVEL
+#define HIDH_INITIAL_TRACE_LEVEL            UC_BT_LOG_HIDH_TRACE_LEVEL
 #define APPL_INITIAL_TRACE_LEVEL            UC_BT_LOG_APPL_TRACE_LEVEL
 #define GATT_INITIAL_TRACE_LEVEL            UC_BT_LOG_GATT_TRACE_LEVEL
 #define SMP_INITIAL_TRACE_LEVEL             UC_BT_LOG_SMP_TRACE_LEVEL

+ 20 - 0
components/esp_hid/CMakeLists.txt

@@ -0,0 +1,20 @@
+set(srcs "src/esp_hidd.c"
+         "src/esp_hidh.c"
+         "src/esp_hid_common.c")
+         
+set(include_dirs "include")
+set(priv_include_dirs "private")
+
+if(CONFIG_BT_ENABLED)
+    if(CONFIG_BT_BLUEDROID_ENABLED)
+        list(APPEND srcs
+             "src/ble_hidd.c"
+             "src/ble_hidh.c"
+             "src/bt_hidh.c")
+    endif()
+endif()
+
+idf_component_register(SRCS "${srcs}"
+                       INCLUDE_DIRS "${include_dirs}"
+                       PRIV_INCLUDE_DIRS "${priv_include_dirs}"
+                       REQUIRES esp_event bt)

+ 3 - 0
components/esp_hid/component.mk

@@ -0,0 +1,3 @@
+COMPONENT_ADD_INCLUDEDIRS := include
+COMPONENT_PRIV_INCLUDEDIRS := private
+COMPONENT_SRCDIRS := src

+ 257 - 0
components/esp_hid/include/esp_hid_common.h

@@ -0,0 +1,257 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+/* HID Report Map Values */
+#define HID_RM_INPUT                        0x80
+#define HID_RM_OUTPUT                       0x90
+#define HID_RM_FEATURE                      0xb0
+#define HID_RM_COLLECTION                   0xa0
+#define HID_RM_END_COLLECTION               0xc0
+#define HID_RM_USAGE_PAGE                   0x04
+#define HID_RM_LOGICAL_MINIMUM              0x14
+#define HID_RM_LOGICAL_MAXIMUM              0x24
+#define HID_RM_PHYSICAL_MINIMUM             0x34
+#define HID_RM_PHYSICAL_MAXIMUM             0x44
+#define HID_RM_UNIT_EXPONENT                0x54
+#define HID_RM_UNIT                         0x64
+#define HID_RM_REPORT_SIZE                  0x74
+#define HID_RM_REPORT_ID                    0x84
+#define HID_RM_REPORT_COUNT                 0x94
+#define HID_RM_PUSH                         0xa4
+#define HID_RM_POP                          0xb4
+#define HID_RM_USAGE                        0x08
+#define HID_RM_USAGE_MINIMUM                0x18
+#define HID_RM_USAGE_MAXIMUM                0x28
+#define HID_RM_DESIGNATOR_INDEX             0x38
+#define HID_RM_DESIGNATOR_MINIMUM           0x48
+#define HID_RM_DESIGNATOR_MAXIMUM           0x58
+#define HID_RM_STRING_INDEX                 0x78
+#define HID_RM_STRING_MINIMUM               0x88
+#define HID_RM_STRING_MAXIMUM               0x98
+#define HID_RM_DELIMITER                    0xa8
+
+/* HID Usage Pages and Usages */
+#define HID_USAGE_PAGE_GENERIC_DESKTOP      0x01
+#define HID_USAGE_KEYBOARD                  0x06
+#define HID_USAGE_MOUSE                     0x02
+#define HID_USAGE_JOYSTICK                  0x04
+#define HID_USAGE_GAMEPAD                   0x05
+
+#define HID_USAGE_PAGE_CONSUMER_DEVICE      0x0C
+#define HID_USAGE_CONSUMER_CONTROL          0x01
+
+/* HID BT COD Peripheral Min Values Main Role */
+#define ESP_HID_COD_MIN_KEYBOARD            0x10
+#define ESP_HID_COD_MIN_MOUSE               0x20
+
+/* HID BLE Appearances */
+#define ESP_HID_APPEARANCE_GENERIC          0x03C0
+#define ESP_HID_APPEARANCE_KEYBOARD         0x03C1
+#define ESP_HID_APPEARANCE_MOUSE            0x03C2
+#define ESP_HID_APPEARANCE_JOYSTICK         0x03C3
+#define ESP_HID_APPEARANCE_GAMEPAD          0x03C4
+
+/* HID Report Types */
+#define ESP_HID_REPORT_TYPE_INPUT           1
+#define ESP_HID_REPORT_TYPE_OUTPUT          2
+#define ESP_HID_REPORT_TYPE_FEATURE         3
+
+/* HID Protocol Modes */
+#define ESP_HID_PROTOCOL_MODE_BOOT          0x00      // Boot Protocol Mode
+#define ESP_HID_PROTOCOL_MODE_REPORT        0x01      // Report Protocol Mode
+
+/* HID information flags */
+#define ESP_HID_FLAGS_REMOTE_WAKE           0x01      // RemoteWake
+#define ESP_HID_FLAGS_NORMALLY_CONNECTABLE  0x02      // NormallyConnectable
+
+/* Control point commands */
+#define ESP_HID_CONTROL_SUSPEND             0x00      // Suspend
+#define ESP_HID_CONTROL_EXIT_SUSPEND        0x01      // Exit Suspend
+
+/* Client Characteristic Configuration values */
+#define ESP_HID_CCC_NOTIFICATIONS_ENABLED   0x01      // Notifications enabled
+#define ESP_HID_CCC_INDICATIONS_ENABLED     0x02      // Indications enabled
+
+/* HID Transports */
+typedef enum {
+    ESP_HID_TRANSPORT_BT,
+    ESP_HID_TRANSPORT_BLE,
+    ESP_HID_TRANSPORT_USB,
+    ESP_HID_TRANSPORT_MAX
+} esp_hid_transport_t;
+
+/* HID Usage Types */
+typedef enum {
+    ESP_HID_USAGE_GENERIC  = 0,
+    ESP_HID_USAGE_KEYBOARD = 1,
+    ESP_HID_USAGE_MOUSE    = 2,
+    ESP_HID_USAGE_JOYSTICK = 4,
+    ESP_HID_USAGE_GAMEPAD  = 8,
+    ESP_HID_USAGE_TABLET   = 16,
+    ESP_HID_USAGE_CCONTROL = 32,
+    ESP_HID_USAGE_VENDOR   = 64
+} esp_hid_usage_t;
+
+/* HID BT COD Peripheral Min Values. Mask of (keyboard|mouse|ESP_HIDH_COD_*) */
+typedef enum {
+    ESP_HID_COD_MIN_GENERIC,
+    ESP_HID_COD_MIN_JOYSTICK,
+    ESP_HID_COD_MIN_GAMEPAD,
+    ESP_HID_COD_MIN_REMOTE,
+    ESP_HID_COD_MIN_SENSOR,
+    ESP_HID_COD_MIN_TABLET,
+    ESP_HID_COD_MIN_CARD_READER,
+    ESP_HID_COD_MIN_MAX
+} esp_hid_cod_min_t;
+
+/**
+ * @brief HID report item structure
+ */
+typedef struct {
+    uint8_t map_index;              /*!< HID report map index */
+    uint8_t report_id;              /*!< HID report id */
+    uint8_t report_type;            /*!< HID report type */
+    uint8_t protocol_mode;          /*!< HID protocol mode */
+    esp_hid_usage_t usage;          /*!< HID usage type */
+    uint16_t value_len;             /*!< HID report length in bytes */
+} esp_hid_report_item_t;
+
+/**
+ * @brief HID parsed report map structure
+ */
+typedef struct {
+    esp_hid_usage_t usage;              /*!< Dominant HID usage. (keyboard > mouse > joystick > gamepad > generic) */
+    uint16_t appearance;                /*!< Calculated HID Appearance based on the dominant usage */
+    uint8_t reports_len;                /*!< Number of reports discovered in the report map */
+    esp_hid_report_item_t *reports;     /*!< Reports discovered in the report map */
+} esp_hid_report_map_t;
+
+/**
+ * @brief HID raw report map structure
+ */
+typedef struct {
+    const uint8_t *data;                /*!< Pointer to the HID report map data */
+    uint16_t len;                       /*!< HID report map data length */
+} esp_hid_raw_report_map_t;
+
+/**
+ * @brief HID device config structure
+ */
+typedef struct {
+    uint16_t vendor_id;                     /*!< HID Vendor ID */
+    uint16_t product_id;                    /*!< HID Product ID */
+    uint16_t version;                       /*!< HID Product Version */
+    const char *device_name;                /*!< HID Device Name */
+    const char *manufacturer_name;          /*!< HID Manufacturer */
+    const char *serial_number;              /*!< HID Serial Number */
+    esp_hid_raw_report_map_t *report_maps;  /*!< Array of the raw HID report maps */
+    uint8_t report_maps_len;                /*!< number of raw report maps in the array */
+} esp_hid_device_config_t;
+
+/*
+ * @brief Parse RAW HID report map
+ *        It is a responsibility of the user to free the parsed report map,
+ *        when it's no longer needed. Use esp_hid_free_report_map
+ * @param hid_rm      : pointer to the hid report map data
+ * @param hid_rm_len  : length to the hid report map data
+ *
+ * @return: pointer to the parsed report map
+ */
+esp_hid_report_map_t *esp_hid_parse_report_map(const uint8_t *hid_rm, size_t hid_rm_len);
+
+/*
+ * @brief Free parsed HID report map
+ * @param map      : pointer to the parsed hid report map
+ */
+void esp_hid_free_report_map(esp_hid_report_map_t *map);
+
+/**
+ * @brief Calculate the HID Device usage type from the BLE Apperance
+ * @param appearance : BLE Apperance value
+ *
+ * @return: the hid usage type
+ */
+esp_hid_usage_t esp_hid_usage_from_appearance(uint16_t appearance);
+
+/**
+ * @brief Calculate the HID Device usage type from the BT CoD
+ * @param cod : BT CoD value
+ *
+ * @return: the hid usage type
+ */
+esp_hid_usage_t esp_hid_usage_from_cod(uint32_t cod);
+
+/**
+ * @brief Convert device usage type to string
+ * @param usage : The HID usage type to convert
+ *
+ * @return: a pointer to the string or NULL
+ */
+const char *esp_hid_usage_str(esp_hid_usage_t usage);
+
+/**
+ * @brief Convert HID protocol mode to string
+ * @param protocol_mode : The HID protocol mode to convert
+ *                        BOOT/REPORT
+ *
+ * @return: a pointer to the string or NULL
+ */
+const char *esp_hid_protocol_mode_str(uint8_t protocol_mode);
+
+/**
+ * @brief Convert HID report type to string
+ * @param report_type : The HID report type to convert
+ *                      INPUT/OUTPUT/FEATURE
+ *
+ * @return: a pointer to the string or NULL
+ */
+const char *esp_hid_report_type_str(uint8_t report_type);
+
+/**
+ * @brief Convert BT CoD major to string
+ * @param cod_major : The CoD major value to convert
+ *
+ * @return: a pointer to the string or NULL
+ */
+const char *esp_hid_cod_major_str(uint8_t cod_major);
+
+/**
+ * @brief Print BT CoD minor value
+ * @param cod_min : The CoD minor value to print
+ * @param fp      : pointer to the output file
+ */
+void esp_hid_cod_minor_print(uint8_t cod_min, FILE *fp);
+
+/**
+ * @brief Convert BLE disconnect reason to string
+ * @param reason : The value of the reason
+ *
+ * @return: a pointer to the string or NULL
+ */
+const char *esp_hid_disconnect_reason_str(esp_hid_transport_t transport, int reason);
+
+#ifdef __cplusplus
+}
+#endif

+ 206 - 0
components/esp_hid/include/esp_hidd.h

@@ -0,0 +1,206 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "sdkconfig.h"
+#include "esp_err.h"
+#include "esp_event.h"
+#include "esp_hid_common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "esp_hidd_transport.h"
+
+ESP_EVENT_DECLARE_BASE(ESP_HIDD_EVENTS);        // Declare the event base for HID device
+
+/**
+ * @brief HIDD callback events enum
+ */
+typedef enum {
+    ESP_HIDD_ANY_EVENT = ESP_EVENT_ANY_ID,      /*!< HID device any event */
+    ESP_HIDD_START_EVENT = 0,                   /*!< HID device stack started */
+    ESP_HIDD_CONNECT_EVENT,                     /*!< HID device connected */
+    ESP_HIDD_PROTOCOL_MODE_EVENT,               /*!< HID device protocol mode change */
+    ESP_HIDD_CONTROL_EVENT,                     /*!< HID device control request */
+    ESP_HIDD_OUTPUT_EVENT,                      /*!< HID device output report event */
+    ESP_HIDD_FEATURE_EVENT,                     /*!< HID device feature report event */
+    ESP_HIDD_DISCONNECT_EVENT,                  /*!< HID device disconnected */
+    ESP_HIDD_STOP_EVENT,                        /*!< HID device stack stopped */
+    ESP_HIDD_MAX_EVENT,                         /*!< HID events end marker */
+} esp_hidd_event_t;
+
+/**
+ * @brief HIDD structure forward declaration
+ */
+struct esp_hidd_dev_s;
+typedef struct esp_hidd_dev_s esp_hidd_dev_t;
+
+/**
+ * @brief HIDD callback parameters union
+ */
+typedef union {
+    /**
+     * @brief ESP_HIDD_CONNECT_EVENT
+     */
+    struct {
+        esp_hidd_dev_t *dev;                    /*!< HID device structure */
+    } connect;                                  /*!< HID callback param of ESP_HIDD_CONNECT_EVENT */
+
+    /**
+     * @brief ESP_HIDD_DISCONNECT_EVENT
+     */
+    struct {
+        esp_hidd_dev_t *dev;                    /*!< HID device structure */
+        int reason;                             /*!< Indicate the reason of disconnection */
+    } disconnect;                               /*!< HID callback param of ESP_HIDD_DISCONNECT_EVENT */
+
+    /**
+     * @brief ESP_HIDD_OUTPUT_EVENT
+     */
+    struct {
+        esp_hidd_dev_t *dev;                    /*!< HID device structure */
+        esp_hid_usage_t usage;                  /*!< HID report usage */
+        uint16_t report_id;                     /*!< HID report index */
+        uint16_t length;                        /*!< data length */
+        uint8_t  *data;                         /*!< The pointer to the data */
+        uint8_t map_index;                      /*!< HID config report map index */
+    } output;                                   /*!< HID callback param of ESP_HIDD_OUTPUT_EVENT */
+
+    /**
+     * @brief ESP_HIDD_FEATURE_EVENT
+     */
+    struct {
+        esp_hidd_dev_t *dev;                    /*!< HID device structure */
+        esp_hid_usage_t usage;                  /*!< HID report usage */
+        uint16_t report_id;                     /*!< HID report index */
+        uint16_t length;                        /*!< data length */
+        uint8_t  *data;                         /*!< The pointer to the data */
+        uint8_t map_index;                      /*!< HID config report map index */
+    } feature;                                  /*!< HID callback param of ESP_HIDD_FEATURE_EVENT */
+
+    /**
+     * @brief ESP_HIDD_PROTOCOL_MODE_EVENT
+     */
+    struct {
+        esp_hidd_dev_t *dev;                    /*!< HID device structure */
+        uint8_t protocol_mode;                  /*!< HID Protocol Mode */
+        uint8_t map_index;                      /*!< HID config report map index */
+    } protocol_mode;                            /*!< HID callback param of ESP_HIDD_PROTOCOL_MODE_EVENT */
+
+    /**
+     * @brief ESP_HIDD_CONTROL_EVENT
+     */
+    struct {
+        esp_hidd_dev_t *dev;                    /*!< HID device structure */
+        uint8_t control;                        /*!< HID Control Point */
+        uint8_t map_index;                      /*!< HID config report map index */
+    } control;                                  /*!< HID callback param of ESP_HIDD_CONTROL_EVENT */
+
+} esp_hidd_event_data_t;
+
+/**
+ * @brief Init HID Device
+ * @param       config   : configuration for the device
+ * @param       transport: protocol that the device will use (ESP_HID_TRANSPORT_BLE/BT/USB)
+ * @param       callback : function to call when events for this device are generated.
+ *                         Can be NULL, but will miss the START event.
+ * @param[out]  dev      : location to return the pointer to the device structure
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidd_dev_init(const esp_hid_device_config_t *config, esp_hid_transport_t transport, esp_event_handler_t callback, esp_hidd_dev_t **dev);
+
+/**
+ * @brief Deinit HID Device
+ * @param dev : pointer to the device to deinit
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidd_dev_deinit(esp_hidd_dev_t *dev);
+
+/**
+ * @brief Get the HID Device Transport
+ * @param dev : pointer to the HID Device
+ *
+ * @return: the transport of the connected device or ESP_HID_TRANSPORT_MAX
+ */
+esp_hid_transport_t esp_hidd_dev_transport_get(esp_hidd_dev_t *dev);
+
+/**
+ * @brief Check if HID Device is connected
+ * @param dev : pointer to the device
+ *
+ * @return: true if the device is connected
+ */
+bool esp_hidd_dev_connected(esp_hidd_dev_t *dev);
+
+/**
+ * @brief Set the battery level reported by the HID Device
+ * @param dev   : pointer to the device
+ * @param level : battery level (0-100)
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidd_dev_battery_set(esp_hidd_dev_t *dev, uint8_t level);
+
+/**
+ * @brief Send an INPUT report to the host
+ * @param dev       : pointer to the device
+ * @param map_index : index of the device report map in the init config
+ * @param report_id : id of the HID INPUT report
+ * @param data      : pointer to the data to send
+ * @param length    : length of the data to send
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidd_dev_input_set(esp_hidd_dev_t *dev, size_t map_index, size_t report_id, uint8_t *data, size_t length);
+
+/**
+ * @brief Send a FEATURE report to the host
+ * @param dev       : pointer to the device
+ * @param map_index : index of the device report map in the init config
+ * @param report_id : id of the HID FEATURE report
+ * @param data      : pointer to the data to send
+ * @param length    : length of the data to send
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidd_dev_feature_set(esp_hidd_dev_t *dev, size_t map_index, size_t report_id, uint8_t *data, size_t length);
+
+/**
+ * @brief Register function to listen for device events
+ * @param dev       : pointer to the device
+ * @param callback  : event handler function
+ * @param event     : event to listen for (ESP_HIDD_ANY_EVENT for all)
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidd_dev_event_handler_register(esp_hidd_dev_t *dev, esp_event_handler_t callback, esp_hidd_event_t event);
+
+/**
+ * @brief Unregister function that is listening for device events
+ * @param dev       : pointer to the device
+ * @param callback  : event handler function
+ * @param event     : event that is listening for (ESP_HIDD_ANY_EVENT for all)
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidd_dev_event_handler_unregister(esp_hidd_dev_t *dev, esp_event_handler_t callback, esp_hidd_event_t event);
+
+#ifdef __cplusplus
+}
+#endif

+ 40 - 0
components/esp_hid/include/esp_hidd_gatts.h

@@ -0,0 +1,40 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "sdkconfig.h"
+
+#if CONFIG_GATTS_ENABLE
+
+#include "esp_gatts_api.h" //for the callback
+
+/**
+ * @brief HID BLE GATTS System Callback. Attach it in your code
+ *        or call it from your gatts event handler to allow the HID stack to function
+ * @param event     : Event type
+ * @param gatts_if  : GATTS Interface ID
+ * @param param     : Point to callback parameter, currently is union type
+ */
+void esp_hidd_gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
+
+#endif /* CONFIG_GATTS_ENABLE */
+
+#ifdef __cplusplus
+}
+#endif

+ 31 - 0
components/esp_hid/include/esp_hidd_transport.h

@@ -0,0 +1,31 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "sdkconfig.h"
+
+#if CONFIG_GATTS_ENABLE
+#include "esp_hidd_gatts.h"
+#else
+typedef int esp_gatt_conn_reason_t;
+#endif /* CONFIG_GATTS_ENABLE */
+
+#ifdef __cplusplus
+}
+#endif

+ 294 - 0
components/esp_hid/include/esp_hidh.h

@@ -0,0 +1,294 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "sdkconfig.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "esp_hid_common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief HIDH structure forward declaration
+ */
+struct esp_hidh_dev_s;
+typedef struct esp_hidh_dev_s esp_hidh_dev_t;
+
+ESP_EVENT_DECLARE_BASE(ESP_HIDH_EVENTS);
+
+/**
+ * @brief HIDH callback events enum
+ */
+typedef enum {
+    ESP_HIDH_ANY_EVENT = ESP_EVENT_ANY_ID,          /*!< HID device any event */
+    ESP_HIDH_OPEN_EVENT = 0,                        /*!< HID device opened */
+    ESP_HIDH_BATTERY_EVENT,                         /*!< HID device battery level changed */
+    ESP_HIDH_INPUT_EVENT,                           /*!< Received HID device INPUT report */
+    ESP_HIDH_FEATURE_EVENT,                         /*!< Received HID device FEATURE report */
+    ESP_HIDH_CLOSE_EVENT,                           /*!< HID device closed */
+    ESP_HIDH_MAX_EVENT,                             /*!< HID events end marker */
+} esp_hidh_event_t;
+
+/**
+ * @brief HIDH callback parameters union
+ */
+typedef union {
+    /**
+     * @brief ESP_HIDH_OPEN_EVENT
+     */
+    struct {
+        esp_hidh_dev_t *dev;                        /*!< HID Remote bluetooth device */
+    } open;                                         /*!< HID callback param of ESP_HIDH_OPEN_EVENT */
+
+    /**
+     * @brief ESP_HIDH_CLOSE_EVENT
+     */
+    struct {
+        esp_hidh_dev_t *dev;                        /*!< HID Remote bluetooth device. */
+        int reason;                                 /*!< Reason why the connection was closed. BLE Only */
+    } close;                                        /*!< HID callback param of ESP_HIDH_CLOSE_EVENT */
+
+    /**
+     * @brief ESP_HIDH_BATTERY_EVENT
+     */
+    struct {
+        esp_hidh_dev_t *dev;                        /*!< HID Remote bluetooth device */
+        uint8_t level;                              /*!< Battery Level (0-100%) */
+    } battery;                                      /*!< HID callback param of ESP_HIDH_BATTERY_EVENT */
+
+    /**
+     * @brief ESP_HIDH_INPUT_EVENT
+     */
+    struct {
+        esp_hidh_dev_t *dev;                        /*!< HID Remote bluetooth device */
+        esp_hid_usage_t usage;                      /*!< HID report usage */
+        uint16_t report_id;                         /*!< HID report index */
+        uint16_t length;                            /*!< HID data length */
+        uint8_t  *data;                             /*!< The pointer to the HID data */
+        uint8_t map_index;                          /*!< HID report map index */
+    } input;                                        /*!< HID callback param of ESP_HIDH_INPUT_EVENT */
+
+    /**
+     * @brief ESP_HIDH_FEATURE_EVENT
+     */
+    struct {
+        esp_hidh_dev_t *dev;                        /*!< HID Remote bluetooth device */
+        esp_hid_usage_t usage;                      /*!< HID report usage */
+        uint16_t report_id;                         /*!< HID report index */
+        uint16_t length;                            /*!< HID data length */
+        uint8_t  *data;                             /*!< The pointer to the HID data */
+        uint8_t map_index;                          /*!< HID report map index */
+    } feature;                                      /*!< HID callback param of ESP_HIDH_FEATURE_EVENT */
+
+} esp_hidh_event_data_t;
+
+typedef struct {
+    esp_event_handler_t callback;
+} esp_hidh_config_t;
+
+/**
+ * @brief Initialize the HID Host component
+ * @param config           : pointer to esp_hidh_config_t structure
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidh_init(const esp_hidh_config_t *config);
+
+/**
+ * @brief De-initialize the HID Host component
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidh_deinit(void);
+
+/**
+ * @brief Close HID Device
+ * @param dev : pointer to the device
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidh_dev_close(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Free HID Device Memory
+ *        This function MUST be called when handling ESP_HIDH_CLOSE_EVENT
+ *        Only then all memory used for the device will be freed.
+ * @param dev : pointer to the device
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidh_dev_free(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Send an OUTPUT report to the device
+ * @param dev       : pointer to the device
+ * @param map_index : index of the device report map
+ * @param report_id : id of the HID OUTPUT report
+ * @param data      : pointer to the data to send
+ * @param length    : length of the data to send
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidh_dev_output_set(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, uint8_t *data, size_t length);
+
+/**
+ * @brief Send a FEATURE report to the device
+ * @param dev       : pointer to the device
+ * @param map_index : index of the device report map
+ * @param report_id : id of the HID FEATURE report
+ * @param data      : pointer to the data to send
+ * @param length    : length of the data to send
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidh_dev_feature_set(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, uint8_t *data, size_t length);
+
+/**
+ * @brief Get the value a FEATURE report from the device
+ * @param dev       : pointer to the device
+ * @param map_index : index of the device report map
+ * @param report_id : id of the HID FEATURE report
+ * @param max_len   : size of the buffer that will hold the data
+ * @param data      : pointer to the data buffer
+ * @param length    : pointer to the value that will be set to the number of bytes received
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidh_dev_feature_get(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, size_t max_len, uint8_t *data, size_t *length);
+
+/**
+ * @brief Dump the properties of HID Device to UART
+ * @param dev : pointer to the HID Device
+ * @param fp  : pointer to the output file
+ */
+void esp_hidh_dev_dump(esp_hidh_dev_t *dev, FILE *fp);
+
+/**
+ * @brief Get the BT Device Address of a HID Device
+ * @param dev : pointer to the HID Device
+ *
+ * @return: pointer to the BDA byte array or NULL
+ */
+const uint8_t *esp_hidh_dev_bda_get(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Get the HID Device Transport
+ * @param dev : pointer to the HID Device
+ *
+ * @return: the transport of the connected device or ESP_HID_TRANSPORT_MAX
+ */
+esp_hid_transport_t esp_hidh_dev_transport_get(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Get the HID Device Cofiguration
+ * @param dev : pointer to the HID Device
+ *
+ * @return: pointer to the config structure or NULL
+ */
+const esp_hid_device_config_t *esp_hidh_dev_config_get(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Get the name of a HID Device
+ * @param dev : pointer to the HID Device
+ *
+ * @return: pointer to the character array or NULL
+ */
+const char *esp_hidh_dev_name_get(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Get the manufacturer of a HID Device
+ * @param dev : pointer to the HID Device
+ *
+ * @return: pointer to the character array
+ */
+const char *esp_hidh_dev_manufacturer_get(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Get the serial number of a HID Device
+ * @param dev : pointer to the HID Device
+ *
+ * @return: pointer to the character array or NULL
+ */
+const char *esp_hidh_dev_serial_get(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Get the VID of a HID Device
+ * @param dev : pointer to the HID Device
+ *
+ * @return: the VID value
+ */
+uint16_t esp_hidh_dev_vendor_id_get(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Get the PID of a HID Device
+ * @param dev : pointer to the HID Device
+ *
+ * @return: the PID value
+ */
+uint16_t esp_hidh_dev_product_id_get(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Get the version HID Device
+ * @param dev : pointer to the HID Device
+ *
+ * @return: the version value
+ */
+uint16_t esp_hidh_dev_version_get(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Get the appearance of BLE HID Device
+ * @param dev : pointer to the BLE HID Device
+ *
+ * @return: the appearance value
+ */
+uint16_t esp_hidh_dev_appearance_get(esp_hidh_dev_t *dev); //BLE Only
+
+/**
+ * @brief Get the calculated HID Device usage type
+ * @param dev : pointer to the HID Device
+ *
+ * @return: the hid usage type
+ */
+esp_hid_usage_t esp_hidh_dev_usage_get(esp_hidh_dev_t *dev);
+
+/**
+ * @brief Get an array of all reports found on a device
+ * @param dev           : pointer to the device
+ * @param num_reports   : pointer to the value that will be set to the number of reports
+ * @param reports       : location to set to the pointer of the reports array
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidh_dev_reports_get(esp_hidh_dev_t *dev, size_t *num_reports, esp_hid_report_item_t **reports);
+
+/**
+ * @brief Get an array of the report maps found on a device
+ * @param dev        : pointer to the device
+ * @param num_maps   : pointer to the value that will be set to the number of report maps found
+ * @param maps       : location to set to the pointer of the report maps array
+ *
+ * @return: ESP_OK on success
+ */
+esp_err_t esp_hidh_dev_report_maps_get(esp_hidh_dev_t *dev, size_t *num_maps, esp_hid_raw_report_map_t **maps);
+
+#include "esp_hidh_transport.h"
+
+#ifdef __cplusplus
+}
+#endif

+ 41 - 0
components/esp_hid/include/esp_hidh_bluedroid.h

@@ -0,0 +1,41 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "sdkconfig.h"
+
+#if CONFIG_BLUEDROID_ENABLED
+
+#include "esp_bt_defs.h"
+
+/**
+ * @brief Open BlueTooth HID Device using BlueDroid
+ * @param bda               : BT Device Address
+ * @param transport         : BT Device Protocol (Classic/HID)
+ * @param remote_addr_type  : BLE Remote address type
+ *
+ * @return: ESP_OK on success
+ */
+esp_hidh_dev_t *esp_hidh_dev_open(esp_bd_addr_t bda, esp_hid_transport_t transport, uint8_t remote_addr_type);
+
+#endif /* CONFIG_BLUEDROID_ENABLED */
+
+#ifdef __cplusplus
+}
+#endif

+ 40 - 0
components/esp_hid/include/esp_hidh_gattc.h

@@ -0,0 +1,40 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "sdkconfig.h"
+
+#if CONFIG_GATTC_ENABLE
+
+#include "esp_gattc_api.h" //for the callback
+
+/**
+ * @brief HID BLE GATTC System Callback. Attach it in your code
+ *        or call it from your gattc event handler to allow the HID stack to function
+ * @param event     : Event type
+ * @param gattc_if  : GATTC Interface ID
+ * @param param     : Point to callback parameter, currently is union type
+ */
+void esp_hidh_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
+
+#endif /* CONFIG_GATTC_ENABLE */
+
+#ifdef __cplusplus
+}
+#endif

+ 33 - 0
components/esp_hid/include/esp_hidh_transport.h

@@ -0,0 +1,33 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "sdkconfig.h"
+
+#if CONFIG_GATTC_ENABLE
+#include "esp_hidh_gattc.h"
+#endif
+
+#if CONFIG_BLUEDROID_ENABLED
+#include "esp_hidh_bluedroid.h"
+#endif
+
+#ifdef __cplusplus
+}
+#endif

+ 33 - 0
components/esp_hid/private/ble_hidd.h

@@ -0,0 +1,33 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "esp_hidd.h"
+#include "esp_err.h"
+#include "esp_hid_common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if CONFIG_GATTS_ENABLE
+
+esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev, const esp_hid_device_config_t *config, esp_event_handler_t callback);
+
+#endif /* CONFIG_GATTS_ENABLE */
+
+#ifdef __cplusplus
+}
+#endif

+ 34 - 0
components/esp_hid/private/ble_hidh.h

@@ -0,0 +1,34 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "esp_hidh.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if CONFIG_GATTC_ENABLE
+
+esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config);
+esp_err_t esp_ble_hidh_deinit(void);
+
+esp_hidh_dev_t *esp_ble_hidh_dev_open(esp_bd_addr_t bda, esp_ble_addr_type_t address_type);
+
+#endif /* CONFIG_GATTC_ENABLE */
+
+#ifdef __cplusplus
+}
+#endif

+ 34 - 0
components/esp_hid/private/bt_hidh.h

@@ -0,0 +1,34 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "esp_hidh.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if CONFIG_BT_HID_HOST_ENABLED
+
+esp_err_t esp_bt_hidh_init(const esp_hidh_config_t *config);
+esp_err_t esp_bt_hidh_deinit(void);
+
+esp_hidh_dev_t *esp_bt_hidh_dev_open(esp_bd_addr_t bda);
+
+#endif /* CONFIG_BT_HID_HOST_ENABLED */
+
+#ifdef __cplusplus
+}
+#endif

+ 367 - 0
components/esp_hid/private/esp_bt_hh_api.h

@@ -0,0 +1,367 @@
+/******************************************************************************
+ *
+ *  Copyright (C) 2005-2012 Broadcom Corporation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at:
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ ******************************************************************************/
+
+#ifndef _ESP_BT_HH_API_H_
+#define _ESP_BT_HH_API_H_
+
+#include "esp_err.h"
+#include "esp_bt_defs.h"
+
+/*****************************************************************************
+**  Constants and Type Definitions
+*****************************************************************************/
+#ifndef BTA_HH_DEBUG
+#define BTA_HH_DEBUG    TRUE
+#endif
+
+#ifndef BTA_HH_SSR_MAX_LATENCY_DEF
+#define BTA_HH_SSR_MAX_LATENCY_DEF  800 /* 500 ms*/
+#endif
+
+#ifndef BTA_HH_SSR_MIN_TOUT_DEF
+#define BTA_HH_SSR_MIN_TOUT_DEF     2
+#endif
+
+/* defined the minimum offset */
+#define BTA_HH_MIN_OFFSET       L2CAP_MIN_OFFSET+1
+
+/* HID_HOST_MAX_DEVICES can not exceed 15 for th design of BTA HH */
+#define BTA_HH_IDX_INVALID      0xff
+#define BTA_HH_MAX_KNOWN        HID_HOST_MAX_DEVICES
+
+/* Security Service Levels [bit mask] (BTM_SetSecurityLevel)
+ ** Encryption should not be used without authentication
+ */
+#define BTM_SEC_NONE               0x0000 /* Nothing required */
+#define BTM_SEC_IN_AUTHORIZE       0x0001 /* Inbound call requires authorization */
+#define BTM_SEC_IN_AUTHENTICATE    0x0002 /* Inbound call requires authentication */
+#define BTM_SEC_IN_ENCRYPT         0x0004 /* Inbound call requires encryption */
+#define BTM_SEC_OUT_AUTHORIZE      0x0008 /* Outbound call requires authorization */
+#define BTM_SEC_OUT_AUTHENTICATE   0x0010 /* Outbound call requires authentication */
+#define BTM_SEC_OUT_ENCRYPT        0x0020 /* Outbound call requires encryption */
+#define BTM_SEC_MODE4_LEVEL4       0x0040 /* Secure Connections Only Mode */
+#define BTM_SEC_FORCE_MASTER       0x0100 /* Need to switch connection to be master */
+#define BTM_SEC_ATTEMPT_MASTER     0x0200 /* Try to switch connection to be master */
+#define BTM_SEC_FORCE_SLAVE        0x0400 /* Need to switch connection to be master */
+#define BTM_SEC_ATTEMPT_SLAVE      0x0800 /* Try to switch connection to be slave */
+#define BTM_SEC_IN_MITM            0x1000 /* inbound Do man in the middle protection */
+#define BTM_SEC_OUT_MITM           0x2000 /* outbound Do man in the middle protection */
+#define BTM_SEC_IN_MIN_16_DIGIT_PIN 0x4000 /* enforce a minimum of 16 digit for sec mode 2 */
+
+/* Security Setting Mask */
+#define BTA_SEC_NONE            BTM_SEC_NONE                                         /* No security. */
+#define BTA_SEC_AUTHORIZE       (BTM_SEC_IN_AUTHORIZE )                              /* Authorization required (only needed for out going connection )*/
+#define BTA_SEC_AUTHENTICATE    (BTM_SEC_IN_AUTHENTICATE | BTM_SEC_OUT_AUTHENTICATE) /* Authentication required. */
+#define BTA_SEC_ENCRYPT         (BTM_SEC_IN_ENCRYPT | BTM_SEC_OUT_ENCRYPT)           /* Encryption required. */
+#define BTA_SEC_MODE4_LEVEL4    (BTM_SEC_MODE4_LEVEL4)                               /* Mode 4 level 4 service, i.e. incoming/outgoing MITM and P-256 encryption */
+#define BTA_SEC_MITM            (BTM_SEC_IN_MITM | BTM_SEC_OUT_MITM)                 /* Man-In-The_Middle protection */
+#define BTA_SEC_IN_16_DIGITS    (BTM_SEC_IN_MIN_16_DIGIT_PIN)                        /* Min 16 digit for pin code */
+
+#if (defined BTA_HH_LE_INCLUDED && BTA_HH_LE_INCLUDED == TRUE)
+/* GATT_MAX_PHY_CHANNEL can not exceed 14 for the design of BTA HH */
+#define BTA_HH_LE_MAX_KNOWN     GATT_MAX_PHY_CHANNEL
+#define BTA_HH_MAX_DEVICE        (HID_HOST_MAX_DEVICES + GATT_MAX_PHY_CHANNEL)
+#else
+#define BTA_HH_MAX_DEVICE       HID_HOST_MAX_DEVICES
+#endif
+/* invalid device handle */
+#define BTA_HH_INVALID_HANDLE   0xff
+
+#define BTA_HH_SSR_PARAM_INVALID       HID_SSR_PARAM_INVALID
+
+/* id DI is not existing in remote device, vendor_id in tBTA_HH_DEV_DSCP_INFO will be set to 0xffff */
+#define BTA_HH_VENDOR_ID_INVALID       0xffff
+
+/* application ID(none-zero) for each type of device */
+#define BTA_HH_APP_ID_MI            1
+#define BTA_HH_APP_ID_KB            2
+#define BTA_HH_APP_ID_RMC           3
+#define BTA_HH_APP_ID_3DSG          4
+#define BTA_HH_APP_ID_JOY           5
+#define BTA_HH_APP_ID_GPAD          6
+#define BTA_HH_APP_ID_LE            0xff
+
+/* type of devices, bit mask */
+#define BTA_HH_DEVT_UNKNOWN      0x00
+#define BTA_HH_DEVT_JOS          0x01           /* joy stick */
+#define BTA_HH_DEVT_GPD          0x02           /* game pad */
+#define BTA_HH_DEVT_RMC          0x03           /* remote control */
+#define BTA_HH_DEVT_SED          0x04           /* sensing device */
+#define BTA_HH_DEVT_DGT          0x05           /* Digitizer tablet */
+#define BTA_HH_DEVT_CDR          0x06           /* card reader */
+#define BTA_HH_DEVT_KBD          0x10           /* keyboard */
+#define BTA_HH_DEVT_MIC          0x20           /* pointing device */
+#define BTA_HH_DEVT_COM          0x30           /* Combo keyboard/pointing */
+#define BTA_HH_DEVT_OTHER        0x80
+typedef uint8_t  tBTA_HH_DEVT;
+
+
+/* BTA HID Host callback events */
+#define BTA_HH_ENABLE_EVT       0       /* HH enabled */
+#define BTA_HH_DISABLE_EVT      1       /* HH disabled */
+#define BTA_HH_OPEN_EVT         2       /* connection opened */
+#define BTA_HH_CLOSE_EVT        3       /* connection closed */
+#define BTA_HH_GET_RPT_EVT      4       /* BTA_HhGetReport callback */
+#define BTA_HH_SET_RPT_EVT      5       /* BTA_HhSetReport callback */
+#define BTA_HH_GET_PROTO_EVT    6       /* BTA_GetProtoMode callback */
+#define BTA_HH_SET_PROTO_EVT    7       /* BTA_HhSetProtoMode callback */
+#define BTA_HH_GET_IDLE_EVT     8       /* BTA_HhGetIdle comes callback */
+#define BTA_HH_SET_IDLE_EVT     9       /* BTA_HhSetIdle finish callback */
+#define BTA_HH_GET_DSCP_EVT     10      /* Get report descriptor */
+#define BTA_HH_ADD_DEV_EVT      11      /* Add Device callback */
+#define BTA_HH_RMV_DEV_EVT      12      /* remove device finished */
+#define BTA_HH_VC_UNPLUG_EVT    13      /* virtually unplugged */
+#define BTA_HH_DATA_EVT         15
+#define BTA_HH_API_ERR_EVT      16      /* API error is caught */
+#define BTA_HH_UPDATE_SCPP_EVT  17      /* update scan paramter complete */
+typedef uint16_t tBTA_HH_EVT;
+
+/* type of protocol mode */
+#define BTA_HH_PROTO_RPT_MODE                   (0x00)
+#define BTA_HH_PROTO_BOOT_MODE                  (0x01)
+#define BTA_HH_PROTO_UNKNOWN                    (0xff)
+typedef uint8_t   tBTA_HH_PROTO_MODE;
+
+#define BTA_HH_VIRTUAL_CABLE           HID_VIRTUAL_CABLE
+#define BTA_HH_NORMALLY_CONNECTABLE    HID_NORMALLY_CONNECTABLE
+#define BTA_HH_RECONN_INIT             HID_RECONN_INIT
+#define BTA_HH_SDP_DISABLE             HID_SDP_DISABLE
+#define BTA_HH_BATTERY_POWER           HID_BATTERY_POWER
+#define BTA_HH_REMOTE_WAKE             HID_REMOTE_WAKE
+#define BTA_HH_SUP_TOUT_AVLBL          HID_SUP_TOUT_AVLBL
+#define BTA_HH_SEC_REQUIRED            HID_SEC_REQUIRED
+typedef uint16_t tBTA_HH_ATTR_MASK;
+
+enum {
+    BTA_HH_OK,
+    BTA_HH_HS_HID_NOT_READY,    /* handshake error : device not ready */
+    BTA_HH_HS_INVALID_RPT_ID,   /* handshake error : invalid report ID */
+    BTA_HH_HS_TRANS_NOT_SPT,    /* handshake error : transaction not spt */
+    BTA_HH_HS_INVALID_PARAM,    /* handshake error : invalid paremter */
+    BTA_HH_HS_ERROR,            /* handshake error : unspecified HS error */
+    BTA_HH_ERR,                 /* general BTA HH error */
+    BTA_HH_ERR_SDP,             /* SDP error */
+    BTA_HH_ERR_PROTO,           /* SET_Protocol error, only used in BTA_HH_OPEN_EVT callback */
+    BTA_HH_ERR_DB_FULL,         /* device database full error, used in BTA_HH_OPEN_EVT/BTA_HH_ADD_DEV_EVT */
+    BTA_HH_ERR_TOD_UNSPT,       /* type of device not supported */
+    BTA_HH_ERR_NO_RES,          /* out of system resources */
+    BTA_HH_ERR_AUTH_FAILED,     /* authentication fail */
+    BTA_HH_ERR_HDL,
+    BTA_HH_ERR_SEC
+};
+typedef uint8_t tBTA_HH_STATUS;
+
+enum {
+    BTA_HH_RPTT_RESRV,      /* reserved         */
+    BTA_HH_RPTT_INPUT,      /* input report     */
+    BTA_HH_RPTT_OUTPUT,     /* output report    */
+    BTA_HH_RPTT_FEATURE     /* feature report   */
+};
+typedef uint8_t tBTA_HH_RPT_TYPE;
+
+/* HID_CONTROL operation code used in BTA_HhSendCtrl()
+*/
+enum {
+    BTA_HH_CTRL_NOP         = 0,                       /* mapping from BTE */
+    BTA_HH_CTRL_HARD_RESET,                            /* hard reset       */
+    BTA_HH_CTRL_SOFT_RESET,                            /* soft reset       */
+    BTA_HH_CTRL_SUSPEND,                               /* enter suspend    */
+    BTA_HH_CTRL_EXIT_SUSPEND,                          /* exit suspend     */
+    BTA_HH_CTRL_VIRTUAL_CABLE_UNPLUG                   /* virtual unplug   */
+};
+typedef uint8_t tBTA_HH_TRANS_CTRL_TYPE;
+
+
+typedef struct desc_info {
+    uint16_t dl_len;
+    uint8_t *dsc_list;
+} tBTA_HH_DEV_DESCR;
+
+/* Define the header of each buffer used in the Bluetooth stack. */
+typedef struct {
+    uint16_t          event;
+    uint16_t          len;
+    uint16_t          offset;
+    uint16_t          layer_specific;
+    uint8_t           data[];
+} BT_HDR;
+
+#define BT_HDR_SIZE (sizeof (BT_HDR))
+
+/* callback event data for BTA_HH_OPEN_EVT */
+typedef struct {
+    esp_bd_addr_t       bda;            /* HID device bd address    */
+    tBTA_HH_STATUS      status;         /* operation status         */
+    uint8_t             handle;         /* device handle            */
+
+} tBTA_HH_CONN;
+
+typedef tBTA_HH_CONN tBTA_HH_DEV_INFO;
+
+/* callback event data */
+typedef struct {
+    tBTA_HH_STATUS      status;         /* operation status         */
+    uint8_t             handle;         /* device handle            */
+} tBTA_HH_CBDATA;
+
+/* report descriptor information */
+typedef struct {
+    uint16_t            vendor_id;      /* vendor ID */
+    uint16_t            product_id;     /* product ID */
+    uint16_t            version;        /* version */
+    uint16_t            ssr_max_latency;/* SSR max latency, BTA_HH_SSR_PARAM_INVALID if unknown */
+    uint16_t            ssr_min_tout;   /* SSR min timeout, BTA_HH_SSR_PARAM_INVALID if unknown */
+    uint8_t             ctry_code;      /*Country Code.*/
+    tBTA_HH_DEV_DESCR   descriptor;
+} tBTA_HH_DEV_DSCP_INFO;
+
+/* handshake data */
+typedef struct {
+    tBTA_HH_STATUS      status;         /* handshake status */
+    uint8_t             handle;         /* device handle    */
+    union {
+        tBTA_HH_PROTO_MODE  proto_mode; /* GET_PROTO_EVT :protocol mode */
+        BT_HDR              *p_rpt_data;/* GET_RPT_EVT   : report data  */
+        uint8_t             idle_rate;  /* GET_IDLE_EVT  : idle rate    */
+    } rsp_data;
+
+} tBTA_HH_HSDATA;
+
+/* union of data associated with HD callback */
+typedef union {
+    tBTA_HH_DEV_INFO        dev_info;           /* BTA_HH_ADD_DEV_EVT, BTA_HH_RMV_DEV_EVT   */
+    tBTA_HH_CONN            conn;               /* BTA_HH_OPEN_EVT      */
+    tBTA_HH_CBDATA          dev_status;         /* BTA_HH_CLOSE_EVT,
+                                                   BTA_HH_SET_PROTO_EVT
+                                                   BTA_HH_SET_RPT_EVT
+                                                   BTA_HH_SET_IDLE_EVT
+                                                   BTA_HH_UPDATE_SCPP_EVT */
+
+    tBTA_HH_STATUS          status;             /* BTA_HH_ENABLE_EVT */
+    tBTA_HH_DEV_DSCP_INFO   dscp_info;          /* BTA_HH_GET_DSCP_EVT */
+    tBTA_HH_HSDATA          hs_data;            /* GET_ transaction callback
+                                                   BTA_HH_GET_RPT_EVT
+                                                   BTA_HH_GET_PROTO_EVT
+                                                   BTA_HH_GET_IDLE_EVT */
+} tBTA_HH;
+
+/* BTA HH callback function */
+typedef void (tBTA_HH_CBACK) (tBTA_HH_EVT event, tBTA_HH *p_data);
+
+/*****************************************************************************
+**  External Function Declarations
+*****************************************************************************/
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* This function enable HID host and registers HID-Host with lower layers.*/
+extern void BTA_HhEnable(uint16_t sec_mask, tBTA_HH_CBACK *p_cback);
+
+/* This function is called when the host is about power down. */
+extern void BTA_HhDisable(void);
+
+/* This function is called to start an inquiry and read SDP record of responding devices; connect to a device if only one active HID device is found. */
+extern void BTA_HhOpen (esp_bd_addr_t dev_bda, uint8_t bd_type, tBTA_HH_PROTO_MODE mode, uint16_t sec_mask);
+
+/* This function disconnects the device. */
+extern void BTA_HhClose(uint8_t dev_handle);
+
+/* This function set the protocol mode at specified HID handle */
+extern void BTA_HhSetProtoMode(uint8_t handle, tBTA_HH_PROTO_MODE t_type);
+
+/* This function get the protocol mode of a specified HID device. */
+extern void BTA_HhGetProtoMode(uint8_t dev_handle);
+
+/* send SET_REPORT to device. */
+extern void BTA_HhSetReport(uint8_t dev_handle, tBTA_HH_RPT_TYPE r_type, BT_HDR *p_data);
+
+/* Send a GET_REPORT to HID device. */
+extern void BTA_HhGetReport(uint8_t dev_handle, tBTA_HH_RPT_TYPE r_type, uint8_t rpt_id, uint16_t buf_size);
+
+/* send SET_IDLE to device. */
+extern void BTA_HhSetIdle(uint8_t dev_handle, uint16_t idle_rate);
+
+/* Send a GET_IDLE to HID device. */
+extern void BTA_HhGetIdle(uint8_t dev_handle);
+
+/* Send HID_CONTROL request to a HID device. */
+extern void BTA_HhSendCtrl(uint8_t dev_handle, tBTA_HH_TRANS_CTRL_TYPE c_type);
+
+/* Send DATA transaction to a HID device. */
+extern void BTA_HhSendData(uint8_t dev_handle, esp_bd_addr_t dev_bda, BT_HDR  *p_buf);
+
+/* Get report descriptor of the device */
+extern void BTA_HhGetDscpInfo(uint8_t dev_handle);
+
+/* Add a virtually cabled device into HID-Host device list to manage and assign a device handle for future API call, host applciation call this API at start-up to initialize its virtually cabled devices. */
+extern void BTA_HhAddDev(esp_bd_addr_t bda, tBTA_HH_ATTR_MASK attr_mask, uint8_t sub_class, uint8_t app_id, tBTA_HH_DEV_DSCP_INFO dscp_info);
+
+/* Remove a device from the HID host devices list. */
+extern void BTA_HhRemoveDev(uint8_t dev_handle );
+
+enum {
+    BTA_HH_MOD_CTRL_KEY,
+    BTA_HH_MOD_SHFT_KEY,
+    BTA_HH_MOD_ALT_KEY,
+    BTA_HH_MOD_GUI_KEY,
+    BTA_HH_MOD_MAX_KEY
+};
+
+/* parsed boot mode keyboard report */
+typedef struct {
+    uint8_t          this_char[6];       /* virtual key code     */
+    bool             mod_key[BTA_HH_MOD_MAX_KEY];/* ctrl, shift, Alt, GUI */
+    bool             caps_lock;          /* is caps locked       */
+    bool             num_lock;           /* is Num key pressed   */
+} tBTA_HH_KEYBD_RPT;
+
+/* parsed boot mode mouse report */
+typedef struct {
+    uint8_t               mouse_button;       /* mouse button is clicked   */
+    int8_t                delta_x;            /* displacement x            */
+    int8_t                delta_y;            /* displacement y            */
+} tBTA_HH_MICE_RPT;
+
+enum {
+    BTA_HH_KEYBD_RPT_ID  =               1,
+    BTA_HH_MOUSE_RPT_ID
+};
+typedef uint8_t tBTA_HH_BOOT_RPT_ID;
+
+/* parsed Boot report */
+typedef struct {
+    tBTA_HH_BOOT_RPT_ID dev_type;           /* type of device report */
+    union {
+        tBTA_HH_KEYBD_RPT   keybd_rpt;      /* keyboard report      */
+        tBTA_HH_MICE_RPT    mice_rpt;       /* mouse report         */
+    }                   data_rpt;
+} tBTA_HH_BOOT_RPT;
+
+/* This utility function parse a boot mode report. */
+extern void BTA_HhParseBootRpt(tBTA_HH_BOOT_RPT *p_data, uint8_t *p_report, uint16_t report_len);
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ESP_BT_HH_API_H_ */

+ 42 - 0
components/esp_hid/private/esp_hidd_private.h

@@ -0,0 +1,42 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include "esp_err.h"
+#include "esp_hidd.h"
+#include "esp_hid_common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct esp_hidd_dev_s {
+    void *dev;
+    esp_hid_transport_t transport;
+
+    bool        (*connected)                (void *dev);
+    esp_err_t   (*deinit)                   (void *dev);
+    esp_err_t   (*battery_set)              (void *dev, uint8_t level);
+    esp_err_t   (*input_set)                (void *dev, size_t map_index, size_t report_id, uint8_t *data, size_t length);
+    esp_err_t   (*feature_set)              (void *dev, size_t map_index, size_t report_id, uint8_t *data, size_t length);
+    esp_err_t   (*event_handler_register)   (void *dev, esp_event_handler_t callback, esp_hidd_event_t event);
+    esp_err_t   (*event_handler_unregister) (void *dev, esp_event_handler_t callback, esp_hidd_event_t event);
+};
+
+typedef struct esp_hidd_dev_s esp_hidd_dev_t;
+
+#ifdef __cplusplus
+}
+#endif

+ 121 - 0
components/esp_hid/private/esp_hidh_private.h

@@ -0,0 +1,121 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _ESP_HIDH_PRIVATE_H_
+#define _ESP_HIDH_PRIVATE_H_
+
+#include "esp_hidh.h"
+#if CONFIG_BLUEDROID_ENABLED
+#include "esp_gap_bt_api.h"
+#endif /* CONFIG_BLUEDROID_ENABLED */
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+#include "esp_event.h"
+#include "sys/queue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief HIDH device report data
+ */
+typedef struct esp_hidh_dev_report_s {
+    struct esp_hidh_dev_report_s *next;
+    uint8_t map_index;      //the index of the report map
+    uint8_t report_id;      //the id of the report
+    uint8_t report_type;    //input, output or feature
+    uint8_t protocol_mode;  //boot or report
+    size_t value_len;       //maximum len of value by report map
+    esp_hid_usage_t usage;  //generic, keyboard or mouse
+    //BLE properties
+    uint16_t handle;        //handle to the value
+    uint16_t ccc_handle;    //handle to client config
+    uint8_t permissions;    //report permissions
+} esp_hidh_dev_report_t;
+
+/**
+ * @brief HIDH device data
+ */
+struct esp_hidh_dev_s {
+    struct esp_hidh_dev_s   *next;
+
+    esp_hid_device_config_t config;
+    esp_hid_usage_t         usage;
+    esp_hid_transport_t     transport; //BT, BLE or USB
+    bool                    connected; //we have all required data to communicate
+    bool                    opened;    //we opened the device manually, else the device connected to us
+    int                     status;    //status of the last command
+
+    size_t                  reports_len;
+    esp_hidh_dev_report_t   *reports;
+
+    void                    *tmp;
+    size_t                  tmp_len;
+
+    xSemaphoreHandle        semaphore;
+
+    esp_err_t               (*close)        (esp_hidh_dev_t *dev);
+    esp_err_t               (*report_write) (esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, uint8_t *data, size_t len);
+    esp_err_t               (*report_read)  (esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, size_t max_length, uint8_t *value, size_t *value_len);
+    void                    (*dump)         (esp_hidh_dev_t *dev, FILE *fp);
+
+#if CONFIG_BLUEDROID_ENABLED
+    esp_bd_addr_t bda;
+#endif /* CONFIG_BLUEDROID_ENABLED */
+
+    union {
+#if CONFIG_BT_HID_HOST_ENABLED
+        struct {
+            esp_bt_cod_t cod;
+            int handle;
+            uint8_t sub_class;
+            uint8_t app_id;
+            uint16_t attr_mask;
+        } bt;
+#endif /* CONFIG_BT_HID_HOST_ENABLED */
+#if CONFIG_GATTC_ENABLE
+        struct {
+            esp_ble_addr_type_t address_type;
+            int conn_id;
+            uint16_t appearance;
+            uint16_t battery_handle;
+            uint16_t battery_ccc_handle;
+        } ble;
+#endif /* CONFIG_GATTC_ENABLE */
+    };
+    TAILQ_ENTRY(esp_hidh_dev_s) devices;
+};
+
+typedef TAILQ_HEAD(esp_hidh_dev_head_s, esp_hidh_dev_s) esp_hidh_dev_head_t;
+
+esp_hidh_dev_t *esp_hidh_dev_malloc(void);
+
+#if CONFIG_BLUEDROID_ENABLED
+esp_hidh_dev_t *esp_hidh_dev_get_by_bda(esp_bd_addr_t bda); //BT/BLE
+esp_hidh_dev_t *esp_hidh_dev_get_by_handle(int handle); //BT Only
+esp_hidh_dev_t *esp_hidh_dev_get_by_conn_id(uint16_t conn_id); //BLE Only
+#endif /* CONFIG_BLUEDROID_ENABLED */
+
+esp_hidh_dev_report_t *esp_hidh_dev_get_report_by_id_and_type(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type);
+esp_hidh_dev_report_t *esp_hidh_dev_get_input_report_by_id_and_proto(esp_hidh_dev_t *dev, size_t report_id, int protocol_mode);
+esp_hidh_dev_report_t *esp_hidh_dev_get_report_by_handle(esp_hidh_dev_t *dev, uint16_t handle);  //BLE Only
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ESP_HIDH_PRIVATE_H_ */

+ 1005 - 0
components/esp_hid/src/ble_hidd.c

@@ -0,0 +1,1005 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include <stdbool.h>
+
+#include "ble_hidd.h"
+#if CONFIG_GATTS_ENABLE
+
+#include "esp_hidd_private.h"
+#include "esp_log.h"
+#include "esp_bt.h"
+#include "esp_bt_main.h"
+#include "esp_bt_defs.h"
+#include "esp_gatts_api.h"
+#include "esp_gatt_defs.h"
+#include "esp_gap_ble_api.h"
+
+static const char *TAG = "BLE_HIDD";
+
+/// Maximal length of Report Char. Value
+#define HIDD_LE_REPORT_MAX_LEN                (255)
+
+/// Maximal length of Report Map Char. Value
+#define HIDD_LE_REPORT_MAP_MAX_LEN            (512)
+
+/// Length of Boot Report Char. Value Maximal Length
+#define HIDD_LE_BOOT_REPORT_MAX_LEN           (8)
+
+/*
+ * UUIDs
+ * */
+
+//the uuid definition
+static const uint16_t s_primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
+static const uint16_t s_include_service_uuid = ESP_GATT_UUID_INCLUDE_SERVICE;
+static const uint16_t s_character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
+static const uint16_t s_character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
+
+//the property definition
+static const uint8_t s_char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ;
+//static const uint8_t s_char_prop_write = ESP_GATT_CHAR_PROP_BIT_WRITE;
+static const uint8_t s_char_prop_write_nr = ESP_GATT_CHAR_PROP_BIT_WRITE_NR;
+static const uint8_t s_char_prop_read_notify = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
+static const uint8_t s_char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ;
+static const uint8_t s_char_prop_read_write_nr = ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_READ;
+//static const uint8_t s_char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_NOTIFY;
+
+// Service UUIDs
+static const uint16_t s_bat_svc = ESP_GATT_UUID_BATTERY_SERVICE_SVC;
+static const uint16_t s_device_info_svc = ESP_GATT_UUID_DEVICE_INFO_SVC;
+static const uint16_t s_hid_le_svc = ESP_GATT_UUID_HID_SVC;
+
+// Battery UUIDs
+static const uint16_t s_bat_level_uuid = ESP_GATT_UUID_BATTERY_LEVEL;
+static const uint16_t s_bat_char_pres_format_uuid = ESP_GATT_UUID_CHAR_PRESENT_FORMAT;
+
+// Device Info UUIDs
+static const uint16_t s_device_info_pnp_id_uuid = ESP_GATT_UUID_PNP_ID;
+static const uint16_t s_device_info_manufacturer_uuid = ESP_GATT_UUID_MANU_NAME;
+static const uint16_t s_device_info_serial_num_uuid = ESP_GATT_UUID_SERIAL_NUMBER_STR;
+
+// HID General UUIDs
+static const uint16_t s_hid_report_map_uuid    = ESP_GATT_UUID_HID_REPORT_MAP;
+static const uint16_t s_hid_report_map_ext_desc_uuid = ESP_GATT_UUID_EXT_RPT_REF_DESCR;
+static const uint16_t s_hid_info_char_uuid = ESP_GATT_UUID_HID_INFORMATION;
+static const uint16_t s_hid_control_point_uuid = ESP_GATT_UUID_HID_CONTROL_POINT;
+static const uint16_t s_hid_proto_mode_uuid = ESP_GATT_UUID_HID_PROTO_MODE;
+
+// HID Report UUIDs
+static const uint16_t s_hid_report_ref_descr_uuid = ESP_GATT_UUID_RPT_REF_DESCR;
+static const uint16_t s_hid_report_uuid = ESP_GATT_UUID_HID_REPORT;
+
+// HID BOOT UUIDs
+static const uint16_t s_hid_boot_kb_input_uuid = ESP_GATT_UUID_HID_BT_KB_INPUT;
+static const uint16_t s_hid_boot_kb_output_uuid = ESP_GATT_UUID_HID_BT_KB_OUTPUT;
+static const uint16_t s_hid_boot_mouse_input_uuid = ESP_GATT_UUID_HID_BT_MOUSE_INPUT;
+
+// Battery Service Attributes Indexes
+enum {
+    BAS_IDX_SVC,
+
+    BAS_IDX_BATT_LVL_CHAR,
+    BAS_IDX_BATT_LVL_VAL,
+    BAS_IDX_BATT_LVL_CCC,
+    BAS_IDX_BATT_LVL_PRES_FMT,
+
+    BAS_IDX_NB,
+};
+
+// HID Service Attributes Indexes
+enum {
+    HIDD_LE_IDX_SVC,
+
+    // Included Service
+    HIDD_LE_IDX_INCL_SVC,
+
+    // HID Information
+    HIDD_LE_IDX_HID_INFO_CHAR,
+    HIDD_LE_IDX_HID_INFO_VAL,
+
+    // HID Control Point
+    HIDD_LE_IDX_HID_CTNL_PT_CHAR,
+    HIDD_LE_IDX_HID_CTNL_PT_VAL,
+
+    // Protocol Mode
+    HIDD_LE_IDX_PROTO_MODE_CHAR,
+    HIDD_LE_IDX_PROTO_MODE_VAL,
+
+    // Report Map
+    HIDD_LE_IDX_REPORT_MAP_CHAR,
+    HIDD_LE_IDX_REPORT_MAP_VAL,
+    HIDD_LE_IDX_REPORT_MAP_EXT_REP_REF,
+
+    HIDD_LE_IDX_NB,
+};
+
+/* Client Characteristic Configuration value structure */
+typedef union {
+    struct {
+        uint16_t notify_enable: 1;
+        uint16_t indicate_enable: 1;
+        uint16_t reserved: 14;
+    };
+    uint16_t value;
+} hidd_le_ccc_value_t;
+
+typedef struct {
+    uint8_t map_index;      //the index of the report map
+    uint8_t report_id;      //the id of the report
+    uint8_t report_type;    //input, output or feature
+    uint8_t protocol_mode;  //boot or report
+    esp_hid_usage_t usage; //generic, keyboard, mouse, joystick or gamepad
+    uint16_t value_len;     //maximum len of value by report map
+    //used by gatts
+    uint8_t index;          //index of the value in the gatts attr db
+    uint16_t handle;        //obtained once all attributes are registered
+    uint16_t ccc_handle;    //obtained once all attributes are registered
+    hidd_le_ccc_value_t ccc;    //notifications and/or indications enabled
+} hidd_le_report_item_t;
+
+typedef struct {
+    esp_gatt_if_t               gatt_if;
+    uint16_t                    handle;
+} hidd_le_service_t;
+
+typedef struct {
+    esp_hid_raw_report_map_t    reports_map;
+    uint8_t                     reports_len;
+    hidd_le_report_item_t      *reports;
+    hidd_le_service_t           hid_svc;
+    uint16_t                    hid_control_handle;
+    uint16_t                    hid_protocol_handle;
+} hidd_dev_map_t;
+
+struct esp_ble_hidd_dev_s {
+    esp_hidd_dev_t             *dev;
+    xSemaphoreHandle            sem;
+    esp_event_loop_handle_t     event_loop_handle;
+    esp_hid_device_config_t     config;
+    uint16_t                    appearance;
+
+
+    bool                        connected;
+    uint16_t                    conn_id;
+    esp_bd_addr_t               remote_bda;
+
+    hidd_le_ccc_value_t         bat_ccc;
+    uint8_t                     bat_level;  // 0 - 100 - battery percentage
+    uint8_t                     control;    // 0x00 suspend, 0x01 suspend off
+    uint8_t                     protocol;   // 0x00 boot, 0x01 report
+
+    hidd_le_service_t           bat_svc;
+    hidd_le_service_t           info_svc;
+    esp_gatts_incl_svc_desc_t   hid_incl_svc;
+
+    uint16_t                    bat_level_handle;
+    uint16_t                    bat_ccc_handle;
+
+
+    uint8_t                     pnp[7];
+
+    hidd_dev_map_t             *devices;
+    uint8_t                     devices_len;
+};
+typedef struct esp_ble_hidd_dev_s esp_ble_hidd_dev_t;
+
+// HID Information characteristic value
+static const uint8_t hidInfo[4] = {
+    0x11, 0x01,     // bcdHID (USB HID version)
+    0x00,           // bCountryCode
+    ESP_HID_FLAGS_REMOTE_WAKE | ESP_HID_FLAGS_NORMALLY_CONNECTABLE   // Flags
+};
+
+
+#define WAIT_CB(d) xSemaphoreTake(d->sem, portMAX_DELAY)
+#define SEND_CB(d) xSemaphoreGive(d->sem)
+
+static const char *gatts_evt_names[25] = { "REG", "READ", "WRITE", "EXEC_WRITE", "MTU", "CONF", "UNREG", "CREATE", "ADD_INCL_SRVC", "ADD_CHAR", "ADD_CHAR_DESCR", "DELETE", "START", "STOP", "CONNECT", "DISCONNECT", "OPEN", "CANCEL_OPEN", "CLOSE", "LISTEN", "CONGEST", "RESPONSE", "CREAT_ATTR_TAB", "SET_ATTR_VAL", "SEND_SERVICE_CHANGE"};
+
+static const char *gatts_evt_str(uint8_t event)
+{
+    if (event >= (sizeof(gatts_evt_names)/sizeof(*gatts_evt_names))) {
+        return "UNKNOWN";
+    }
+    return gatts_evt_names[event];
+}
+
+static void add_db_record(esp_gatts_attr_db_t *db, size_t index, uint8_t *uuid, uint8_t perm, uint16_t max_length, uint16_t length, uint8_t *value)
+{
+    db[index].attr_control.auto_rsp = ESP_GATT_AUTO_RSP;
+    db[index].att_desc.uuid_length = ESP_UUID_LEN_16;
+    db[index].att_desc.uuid_p = uuid;
+    db[index].att_desc.perm = perm;
+    db[index].att_desc.max_length = max_length;
+    db[index].att_desc.length = length;
+    db[index].att_desc.value = value;
+}
+
+static esp_gatts_attr_db_t *_last_db = NULL;
+
+static esp_err_t create_bat_db(esp_ble_hidd_dev_t *dev)
+{
+    _last_db = (esp_gatts_attr_db_t *)malloc(sizeof(esp_gatts_attr_db_t) * BAS_IDX_NB);
+    if (!_last_db) {
+        ESP_LOGE(TAG, "Malloc bat_db failed");
+        return ESP_FAIL;
+    }
+    add_db_record(_last_db, BAS_IDX_SVC, (uint8_t *)&s_primary_service_uuid, ESP_GATT_PERM_READ, 2, 2, (uint8_t *)&s_bat_svc);
+    add_db_record(_last_db, BAS_IDX_BATT_LVL_CHAR, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read_notify);
+    add_db_record(_last_db, BAS_IDX_BATT_LVL_VAL, (uint8_t *)&s_bat_level_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&dev->bat_level);
+    add_db_record(_last_db, BAS_IDX_BATT_LVL_CCC, (uint8_t *)&s_character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, 2, 0,  NULL);
+    add_db_record(_last_db, BAS_IDX_BATT_LVL_PRES_FMT, (uint8_t *)&s_bat_char_pres_format_uuid, ESP_GATT_PERM_READ, 7, 0,  NULL);
+    esp_err_t err = esp_ble_gatts_create_attr_tab(_last_db, dev->bat_svc.gatt_if, BAS_IDX_NB, 0);
+    return err;
+}
+
+static esp_err_t create_info_db(esp_ble_hidd_dev_t *dev)
+{
+    _last_db = (esp_gatts_attr_db_t *)malloc(sizeof(esp_gatts_attr_db_t) * 7);
+    if (!_last_db) {
+        ESP_LOGE(TAG, "Malloc info_db failed");
+        return ESP_FAIL;
+    }
+    size_t index = 0;
+    add_db_record(_last_db, index++, (uint8_t *)&s_primary_service_uuid, ESP_GATT_PERM_READ, 2, 2, (uint8_t *)&s_device_info_svc);
+
+    if (dev->config.product_id || dev->config.vendor_id || dev->config.version) {
+        uint8_t pnp_val[7] = {
+            0x02, //0x1=BT, 0x2=USB
+            dev->config.vendor_id & 0xFF, (dev->config.vendor_id >> 8) & 0xFF, //VID
+            dev->config.product_id & 0xFF, (dev->config.product_id >> 8) & 0xFF, //PID
+            dev->config.version & 0xFF, (dev->config.version >> 8) & 0xFF  //VERSION
+        };
+        memcpy(dev->pnp, pnp_val, 7);
+        add_db_record(_last_db, index++, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read);
+        add_db_record(_last_db, index++, (uint8_t *)&s_device_info_pnp_id_uuid, ESP_GATT_PERM_READ, 7, 7, (uint8_t *)dev->pnp);
+    }
+
+    if (dev->config.manufacturer_name && dev->config.manufacturer_name[0]) {
+        size_t name_len = strlen(dev->config.manufacturer_name);
+        add_db_record(_last_db, index++, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read);
+        add_db_record(_last_db, index++, (uint8_t *)&s_device_info_manufacturer_uuid, ESP_GATT_PERM_READ, name_len, name_len, (uint8_t *)dev->config.manufacturer_name);
+    }
+
+    if (dev->config.serial_number && dev->config.serial_number[0]) {
+        size_t name_len = strlen(dev->config.serial_number);
+        add_db_record(_last_db, index++, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read);
+        add_db_record(_last_db, index++, (uint8_t *)&s_device_info_serial_num_uuid, ESP_GATT_PERM_READ, name_len, name_len, (uint8_t *)dev->config.serial_number);
+    }
+
+    esp_err_t err = esp_ble_gatts_create_attr_tab(_last_db, dev->info_svc.gatt_if, index, 0);
+    return err;
+}
+
+static esp_err_t create_hid_db(esp_ble_hidd_dev_t *dev, int device_index)
+{
+    size_t report_attr_len = 0;
+    for (uint8_t i = 0; i < dev->devices[device_index].reports_len; i++) {
+        if (dev->devices[device_index].reports[i].report_type == ESP_HID_REPORT_TYPE_INPUT) {
+            report_attr_len += 3;
+        } else {
+            report_attr_len += 2;
+        }
+        if (dev->devices[device_index].reports[i].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) {
+            report_attr_len += 1;
+        }
+    }
+
+    _last_db = (esp_gatts_attr_db_t *)malloc(sizeof(esp_gatts_attr_db_t) * (HIDD_LE_IDX_NB + report_attr_len + (dev->devices_len * 3)));
+    if (!_last_db) {
+        ESP_LOGE(TAG, "Malloc hid_db failed");
+        return ESP_FAIL;
+    }
+
+    add_db_record(_last_db, HIDD_LE_IDX_SVC, (uint8_t *)&s_primary_service_uuid, ESP_GATT_PERM_READ_ENCRYPTED, 2, 2, (uint8_t *)&s_hid_le_svc);
+    add_db_record(_last_db, HIDD_LE_IDX_INCL_SVC, (uint8_t *)&s_include_service_uuid, ESP_GATT_PERM_READ, sizeof(esp_gatts_incl_svc_desc_t), sizeof(esp_gatts_incl_svc_desc_t), (uint8_t *)&dev->hid_incl_svc);
+
+    add_db_record(_last_db, HIDD_LE_IDX_HID_INFO_CHAR, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read);
+    add_db_record(_last_db, HIDD_LE_IDX_HID_INFO_VAL, (uint8_t *)&s_hid_info_char_uuid, ESP_GATT_PERM_READ, 4, 4, (uint8_t *)hidInfo);
+
+    add_db_record(_last_db, HIDD_LE_IDX_HID_CTNL_PT_CHAR, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_write_nr);
+    add_db_record(_last_db, HIDD_LE_IDX_HID_CTNL_PT_VAL, (uint8_t *)&s_hid_control_point_uuid, ESP_GATT_PERM_READ, 1, 0, NULL);
+
+    add_db_record(_last_db, HIDD_LE_IDX_PROTO_MODE_CHAR, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read_write_nr);
+    add_db_record(_last_db, HIDD_LE_IDX_PROTO_MODE_VAL, (uint8_t *)&s_hid_proto_mode_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, 1, 1, (uint8_t *)&dev->protocol);
+
+    add_db_record(_last_db, HIDD_LE_IDX_REPORT_MAP_CHAR, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read);
+    add_db_record(_last_db, HIDD_LE_IDX_REPORT_MAP_VAL, (uint8_t *)&s_hid_report_map_uuid, ESP_GATT_PERM_READ, HIDD_LE_REPORT_MAP_MAX_LEN, dev->devices[device_index].reports_map.len, (uint8_t *)dev->devices[device_index].reports_map.data);
+    add_db_record(_last_db, HIDD_LE_IDX_REPORT_MAP_EXT_REP_REF, (uint8_t *)&s_hid_report_map_ext_desc_uuid, ESP_GATT_PERM_READ, 2, 2, (uint8_t *)&s_bat_level_uuid);
+
+    size_t index = HIDD_LE_IDX_NB;
+
+    for (uint8_t i = 0; i < dev->devices[device_index].reports_len; i++) {
+        hidd_le_report_item_t *report = &dev->devices[device_index].reports[i];
+        if (report->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) {
+            if (report->report_type == ESP_HID_REPORT_TYPE_INPUT) {
+                //Input Report
+                add_db_record(_last_db, index++, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read_notify);
+                report->index = index;
+                add_db_record(_last_db, index++, (uint8_t *)&s_hid_report_uuid, ESP_GATT_PERM_READ, report->value_len, 0, NULL);
+                add_db_record(_last_db, index++, (uint8_t *)&s_character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, 2, 0, NULL);
+            } else {
+                //Output or Feature Report
+                add_db_record(_last_db, index++, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read_write);
+                report->index = index;
+                add_db_record(_last_db, index++, (uint8_t *)&s_hid_report_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, report->value_len, 0, NULL);
+            }
+            add_db_record(_last_db, index++, (uint8_t *)&s_hid_report_ref_descr_uuid, ESP_GATT_PERM_READ, 2, 2, (uint8_t *)&report->report_id);
+        } else {
+            if (report->report_type == ESP_HID_REPORT_TYPE_INPUT) {
+                add_db_record(_last_db, index++, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read_notify);
+                report->index = index;
+                if (report->usage == ESP_HID_USAGE_KEYBOARD) { //Boot Keyboard Input
+                    add_db_record(_last_db, index++, (uint8_t *)&s_hid_boot_kb_input_uuid, ESP_GATT_PERM_READ, HIDD_LE_BOOT_REPORT_MAX_LEN, 0, NULL);
+                } else { //Boot Mouse Input
+                    add_db_record(_last_db, index++, (uint8_t *)&s_hid_boot_mouse_input_uuid, ESP_GATT_PERM_READ, HIDD_LE_BOOT_REPORT_MAX_LEN, 0, NULL);
+                }
+                add_db_record(_last_db, index++, (uint8_t *)&s_character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, 2, 0, NULL);
+            } else { //Boot Keyboard Output
+                add_db_record(_last_db, index++, (uint8_t *)&s_character_declaration_uuid, ESP_GATT_PERM_READ, 1, 1, (uint8_t *)&s_char_prop_read_write);
+                report->index = index;
+                add_db_record(_last_db, index++, (uint8_t *)&s_hid_boot_kb_output_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, HIDD_LE_BOOT_REPORT_MAX_LEN, 0, NULL);
+            }
+        }
+
+    }
+    esp_err_t err = esp_ble_gatts_create_attr_tab(_last_db, dev->devices[device_index].hid_svc.gatt_if, index, device_index);
+    return err;
+}
+
+
+static void link_report_handles(hidd_dev_map_t *dev, uint16_t *handles)
+{
+
+    hidd_le_report_item_t *rpt = NULL;
+    for (uint8_t i = 0; i < dev->reports_len; i++) {
+        rpt = &dev->reports[i];
+        rpt->handle = handles[rpt->index];
+        if (rpt->report_type == ESP_HID_REPORT_TYPE_INPUT) {
+            rpt->ccc_handle = handles[rpt->index + 1];
+        }
+    }
+}
+
+static hidd_le_report_item_t *get_report_by_handle(esp_ble_hidd_dev_t *dev, uint16_t handle)
+{
+    hidd_le_report_item_t *rpt = NULL;
+    for (uint8_t d = 0; d < dev->devices_len; d++) {
+        for (uint8_t i = 0; i < dev->devices[d].reports_len; i++) {
+            rpt = &dev->devices[d].reports[i];
+            if (rpt->handle == handle || rpt->ccc_handle == handle) {
+                return rpt;
+            }
+        }
+    }
+    return NULL;
+}
+
+static hidd_le_report_item_t *get_report_by_id_and_type(esp_ble_hidd_dev_t *dev, uint8_t id, uint8_t type)
+{
+    hidd_le_report_item_t *rpt = NULL;
+    for (uint8_t d = 0; d < dev->devices_len; d++) {
+        for (uint8_t i = 0; i < dev->devices[d].reports_len; i++) {
+            rpt = &dev->devices[d].reports[i];
+            if (rpt->report_id == id && rpt->report_type == type && rpt->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) {
+                return rpt;
+            }
+        }
+    }
+    return NULL;
+}
+
+static void bat_event_handler(esp_ble_hidd_dev_t *dev, esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
+{
+    switch (event) {
+    case ESP_GATTS_REG_EVT:
+        ESP_LOGV(TAG, "Battery REG App ID: 0x%x", param->reg.app_id);
+        break;
+    case ESP_GATTS_CREAT_ATTR_TAB_EVT:
+        dev->bat_svc.handle = param->add_attr_tab.handles[BAS_IDX_SVC];
+        dev->bat_level_handle = param->add_attr_tab.handles[BAS_IDX_BATT_LVL_VAL];//so we notify of the change
+        dev->bat_ccc_handle = param->add_attr_tab.handles[BAS_IDX_BATT_LVL_CCC];//so we know if we can send notify
+        ESP_LOGV(TAG, "Battery CREAT_ATTR_TAB service handle = %d", dev->bat_svc.handle);
+
+        dev->hid_incl_svc.start_hdl = dev->bat_svc.handle;
+        dev->hid_incl_svc.end_hdl = dev->bat_svc.handle + BAS_IDX_NB - 1;
+
+        esp_ble_gatts_start_service(dev->bat_svc.handle);
+
+        // Add the info service next, because it's shared between all device maps
+        create_info_db(dev);
+        break;
+
+    case ESP_GATTS_READ_EVT:
+        if (param->read.handle == dev->bat_level_handle) {
+            ESP_LOGD(TAG, "Battery READ %d", dev->bat_level);
+        }
+        break;
+    case ESP_GATTS_WRITE_EVT: {
+        if (param->write.handle == dev->bat_ccc_handle) {
+            dev->bat_ccc.value = param->write.value[0];
+            ESP_LOGV(TAG, "Battery CCC: Notify: %s, Indicate: %s", dev->bat_ccc.notify_enable ? "On" : "Off", dev->bat_ccc.indicate_enable ? "On" : "Off");
+        }
+        break;
+    }
+    case ESP_GATTS_SET_ATTR_VAL_EVT: {
+        if (param->set_attr_val.attr_handle == dev->bat_level_handle) {
+            ESP_LOGD(TAG, "Battery SET %d, status: 0x%02x", dev->bat_level, param->set_attr_val.status);
+        }
+        break;
+    }
+    default:
+        ESP_LOGV(TAG, "Battery %s", gatts_evt_str(event));
+        break;
+    }
+}
+
+static void info_event_handler(esp_ble_hidd_dev_t *dev, esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
+{
+    switch (event) {
+    case ESP_GATTS_REG_EVT:
+        ESP_LOGV(TAG, "Dev Info REG App ID: 0x%x", param->reg.app_id);
+        break;
+    case ESP_GATTS_CREAT_ATTR_TAB_EVT:
+        dev->info_svc.handle = param->add_attr_tab.handles[0];
+        ESP_LOGV(TAG, "Dev Info service handle = %d", dev->info_svc.handle);
+        esp_ble_gatts_start_service(dev->info_svc.handle);
+        create_hid_db(dev, 0);
+        break;
+
+    default:
+        ESP_LOGV(TAG, "Dev Info %s", gatts_evt_str(event));
+        break;
+    }
+}
+
+static void hid_event_handler(esp_ble_hidd_dev_t *dev, int device_index, esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
+{
+    switch (event) {
+    case ESP_GATTS_REG_EVT: {
+        ESP_LOGV(TAG, "HID REG[%d] App ID: 0x%x", device_index, param->reg.app_id);
+        // is this the last report map app?
+        if (device_index == (dev->devices_len - 1)) {
+            // we should add the battery service first, because the hid service should include the battery service.
+            create_bat_db(dev);
+        }
+        break;
+    }
+    case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
+        dev->devices[device_index].hid_svc.handle = param->add_attr_tab.handles[HIDD_LE_IDX_SVC];
+        dev->devices[device_index].hid_control_handle = param->add_attr_tab.handles[HIDD_LE_IDX_HID_CTNL_PT_VAL];
+        dev->devices[device_index].hid_protocol_handle = param->add_attr_tab.handles[HIDD_LE_IDX_PROTO_MODE_VAL];
+        ESP_LOGV(TAG, "HID CREAT_ATTR_TAB[%u] service handle = %d", device_index, dev->devices[device_index].hid_svc.handle);
+
+        link_report_handles(&dev->devices[device_index], param->add_attr_tab.handles);
+        esp_ble_gatts_start_service(dev->devices[device_index].hid_svc.handle);
+        if ((device_index + 1) < dev->devices_len) {
+            create_hid_db(dev, device_index + 1);//add next device
+        }
+        break;
+    }
+    case ESP_GATTS_START_EVT: {
+        ESP_LOGD(TAG, "HID START[%d] status: 0x%02x", device_index, param->start.status);
+        if (device_index == (dev->devices_len - 1)) {
+            esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_START_EVENT, NULL, 0, portMAX_DELAY);
+        }
+        break;
+    }
+    case ESP_GATTS_CONNECT_EVT: {
+        ESP_LOGD(TAG, "HID CONNECT[%d] conn_id = %x", device_index, param->connect.conn_id);
+        if (!dev->connected && device_index == (dev->devices_len - 1)) {
+            dev->connected = true;
+            dev->conn_id   = param->connect.conn_id;
+            memcpy(dev->remote_bda, param->connect.remote_bda, ESP_BD_ADDR_LEN);
+
+            esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_NO_MITM);
+
+            esp_hidd_event_data_t cb_param = {
+                .connect.dev = dev->dev,
+            };
+            esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_CONNECT_EVENT, &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
+        }
+        break;
+    }
+    case ESP_GATTS_DISCONNECT_EVT: {
+        ESP_LOGD(TAG, "HID DISCONNECT[%d] 0x%x", device_index, param->disconnect.reason);
+        if (dev->connected) {
+            dev->connected = false;
+            esp_hidd_event_data_t cb_param = {0};
+            cb_param.disconnect.dev = dev->dev;
+            cb_param.disconnect.reason = param->disconnect.reason;
+            esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_DISCONNECT_EVENT, &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
+        }
+        break;
+    }
+    case ESP_GATTS_READ_EVT: {
+        hidd_le_report_item_t *map = get_report_by_handle(dev, param->read.handle);
+        if (map && map->handle == param->read.handle) {
+            ESP_LOGV(TAG, "HID READ[%d] %8s %7s %6s id: %d, need_resp: %d", device_index, esp_hid_usage_str(map->usage), esp_hid_report_type_str(map->report_type), esp_hid_protocol_mode_str(map->protocol_mode), map->report_id, param->read.need_rsp);
+        }
+        break;
+    }
+    case ESP_GATTS_WRITE_EVT: {
+
+        if (param->write.handle == dev->devices[device_index].hid_control_handle) {
+            dev->control = param->write.value[0];
+
+            esp_hidd_event_data_t cb_param = {0};
+            cb_param.control.dev = dev->dev;
+            cb_param.control.control = dev->control;
+            cb_param.control.map_index = device_index;
+            esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_CONTROL_EVENT, &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
+        } else if (param->write.handle == dev->devices[device_index].hid_protocol_handle) {
+            dev->protocol = param->write.value[0];
+
+            esp_hidd_event_data_t cb_param = {};
+            cb_param.protocol_mode.dev = dev->dev;
+            cb_param.protocol_mode.protocol_mode = dev->protocol;
+            cb_param.protocol_mode.map_index = device_index;
+            esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_PROTOCOL_MODE_EVENT, &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
+        } else {
+            hidd_le_report_item_t *map = get_report_by_handle(dev, param->write.handle);
+            if (map) {
+                if (param->write.handle == map->ccc_handle) {
+                    map->ccc.value = param->write.value[0];
+                    ESP_LOGV(TAG, "HID CCC[%d] %8s %7s %6s id: %d, Notify: %s, Indicate: %s", device_index, esp_hid_usage_str(map->usage), esp_hid_report_type_str(map->report_type), esp_hid_protocol_mode_str(map->protocol_mode), map->report_id, map->ccc.notify_enable ? "On" : "Off", map->ccc.indicate_enable ? "On" : "Off");
+                } else {
+                    ESP_LOGV(TAG, "HID WRITE %8s %7s %6s id: %d, len: %d", esp_hid_usage_str(map->usage), esp_hid_report_type_str(map->report_type), esp_hid_protocol_mode_str(map->protocol_mode), map->report_id, param->write.len);
+
+                    esp_hidd_event_data_t cb_param = {0};
+                    if (map->report_type == ESP_HID_REPORT_TYPE_OUTPUT) {
+                        cb_param.output.dev = dev->dev;
+                        cb_param.output.report_id = map->report_id;
+                        cb_param.output.usage = map->usage;
+                        cb_param.output.length = param->write.len;
+                        cb_param.output.data = param->write.value;
+                        cb_param.output.map_index = device_index;
+                        esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_OUTPUT_EVENT, &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
+                    } else {
+                        cb_param.feature.dev = dev->dev;
+                        cb_param.feature.report_id = map->report_id;
+                        cb_param.feature.usage = map->usage;
+                        cb_param.feature.length = param->write.len;
+                        cb_param.feature.data = param->write.value;
+                        cb_param.feature.map_index = device_index;
+                        esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_FEATURE_EVENT, &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
+                    }
+                }
+            }
+
+        }
+        break;
+    }
+    case ESP_GATTS_SET_ATTR_VAL_EVT: {
+        hidd_le_report_item_t *map = get_report_by_handle(dev, param->set_attr_val.attr_handle);
+        if (map && map->handle == param->set_attr_val.attr_handle) {
+            ESP_LOGV(TAG, "HID SET[%d] %8s %7s %6s id: %d, status: 0x%02x", device_index, esp_hid_usage_str(map->usage), esp_hid_report_type_str(map->report_type), esp_hid_protocol_mode_str(map->protocol_mode), map->report_id, param->set_attr_val.status);
+        }
+        SEND_CB(dev);
+        break;
+    }
+    case ESP_GATTS_CONF_EVT: {
+        ESP_LOGV(TAG, "HID CONF[%d] status: 0x%02x, len: %d", device_index, param->conf.status, param->conf.len);
+        SEND_CB(dev);
+        break;
+    }
+    case ESP_GATTS_MTU_EVT:
+        ESP_LOGV(TAG, "HID[%d] MTU = %d", device_index, param->mtu.mtu);
+        break;
+
+    default:
+        ESP_LOGV(TAG, "HID[%d] %s", device_index, gatts_evt_str(event));
+        break;
+    }
+}
+
+static int get_device_map_index_by_gatts_if (esp_ble_hidd_dev_t *dev, esp_gatt_if_t gatts_if)
+{
+    for (uint8_t d = 0; d < dev->devices_len; d++) {
+        if (dev->devices[d].hid_svc.gatt_if && gatts_if == dev->devices[d].hid_svc.gatt_if) {
+            return d;
+        }
+    }
+    return -1;
+}
+
+static esp_err_t ble_hid_start_gatts(esp_ble_hidd_dev_t *dev)
+{
+    esp_err_t ret;
+
+    if ((ret = esp_ble_gatts_app_register(ESP_GATT_UUID_BATTERY_SERVICE_SVC)) != ESP_OK) {
+        ESP_LOGE(TAG, "GATTS register battery service failed: %d", ret);
+        return ret;
+    }
+    if ((ret = esp_ble_gatts_app_register(ESP_GATT_UUID_DEVICE_INFO_SVC)) != ESP_OK) {
+        ESP_LOGE(TAG, "GATTS register device info service failed: %d", ret);
+        return ret;
+    }
+    for (uint8_t i = 0; i < dev->devices_len; i++) {
+        if ((ret = esp_ble_gatts_app_register(ESP_GATT_UUID_HID_SVC + i)) != ESP_OK) {
+            ESP_LOGE(TAG, "GATTS register HID[%u] service failed: %d", i, ret);
+            return ret;
+        }
+    }
+    return ret;
+}
+
+static esp_err_t ble_hid_stop_gatts(esp_ble_hidd_dev_t *dev)
+{
+    esp_err_t ret = ESP_OK;
+
+    for (uint8_t d = 0; d < dev->devices_len; d++) {
+        esp_ble_gatts_stop_service(dev->devices[d].hid_svc.handle);
+        esp_ble_gatts_delete_service(dev->devices[d].hid_svc.handle);
+        esp_ble_gatts_app_unregister(dev->devices[d].hid_svc.gatt_if);
+    }
+
+    esp_ble_gatts_stop_service(dev->info_svc.handle);
+    esp_ble_gatts_delete_service(dev->info_svc.handle);
+    esp_ble_gatts_app_unregister(dev->info_svc.gatt_if);
+
+    esp_ble_gatts_stop_service(dev->bat_svc.handle);
+    esp_ble_gatts_delete_service(dev->bat_svc.handle);
+    esp_ble_gatts_app_unregister(dev->bat_svc.gatt_if);
+
+    return ret;
+}
+
+static esp_err_t ble_hid_init_config(esp_ble_hidd_dev_t *dev, const esp_hid_device_config_t *config)
+{
+    memset((uint8_t *)(&dev->config), 0, sizeof(esp_hid_device_config_t));
+    dev->config.vendor_id = config->vendor_id;
+    dev->config.product_id = config->product_id;
+    dev->config.version = config->version;
+    if (config->device_name != NULL) {
+        dev->config.device_name = strdup(config->device_name);
+    }
+    if (config->manufacturer_name != NULL) {
+        dev->config.manufacturer_name = strdup(config->manufacturer_name);
+    }
+    if (config->serial_number != NULL) {
+        dev->config.serial_number = strdup(config->serial_number);
+    }
+    dev->appearance = ESP_HID_APPEARANCE_GENERIC;
+
+    if (config->report_maps_len) {
+        dev->devices = (hidd_dev_map_t *)malloc(config->report_maps_len * sizeof(hidd_dev_map_t));
+        if (dev->devices == NULL) {
+            ESP_LOGE(TAG, "devices malloc(%d) failed", config->report_maps_len);
+            return ESP_FAIL;
+        }
+        memset(dev->devices, 0, config->report_maps_len * sizeof(hidd_dev_map_t));
+        dev->devices_len = config->report_maps_len;
+        for (uint8_t d = 0; d < dev->devices_len; d++) {
+
+            //raw report map
+            uint8_t *map = (uint8_t *)malloc(config->report_maps[d].len);
+            if (map == NULL) {
+                ESP_LOGE(TAG, "report map malloc(%d) failed", config->report_maps[d].len);
+                return ESP_FAIL;
+            }
+            memcpy(map, config->report_maps[d].data, config->report_maps[d].len);
+
+            dev->devices[d].reports_map.data = (const uint8_t *)map;
+            dev->devices[d].reports_map.len = config->report_maps[d].len;
+
+            esp_hid_report_map_t *rmap = esp_hid_parse_report_map(config->report_maps[d].data, config->report_maps[d].len);
+            if (rmap == NULL) {
+                ESP_LOGE(TAG, "hid_parse_report_map[%d](%d) failed", d, config->report_maps[d].len);
+                return ESP_FAIL;
+            }
+            dev->appearance = rmap->appearance;
+            dev->devices[d].reports_len = rmap->reports_len;
+            dev->devices[d].reports = (hidd_le_report_item_t *)malloc(rmap->reports_len * sizeof(hidd_le_report_item_t));
+            if (dev->devices[d].reports == NULL) {
+                ESP_LOGE(TAG, "reports malloc(%d) failed", rmap->reports_len * sizeof(hidd_le_report_item_t));
+                free(rmap);
+                return ESP_FAIL;
+            }
+            for (uint8_t r = 0; r < rmap->reports_len; r++) {
+                dev->devices[d].reports[r].map_index = d;
+                dev->devices[d].reports[r].report_id = rmap->reports[r].report_id;
+                dev->devices[d].reports[r].protocol_mode = rmap->reports[r].protocol_mode;
+                dev->devices[d].reports[r].report_type = rmap->reports[r].report_type;
+                dev->devices[d].reports[r].usage = rmap->reports[r].usage;
+                dev->devices[d].reports[r].value_len = rmap->reports[r].value_len;
+            }
+            free(rmap->reports);
+            free(rmap);
+        }
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t ble_hid_free_config(esp_ble_hidd_dev_t *dev)
+{
+    for (uint8_t d = 0; d < dev->devices_len; d++) {
+        free((void *)dev->devices[d].reports);
+        free((void *)dev->devices[d].reports_map.data);
+    }
+
+    free((void *)dev->devices);
+    free((void *)dev->config.device_name);
+    free((void *)dev->config.manufacturer_name);
+    free((void *)dev->config.serial_number);
+    if (dev->sem != NULL) {
+        vSemaphoreDelete(dev->sem);
+    }
+    if (dev->event_loop_handle != NULL) {
+        esp_event_loop_delete(dev->event_loop_handle);
+    }
+    return ESP_OK;
+}
+
+/*
+ * PUBLIC FUNCTIONS
+ * */
+
+// there can be only one BLE HID device
+static esp_ble_hidd_dev_t *s_dev = NULL;
+
+void esp_hidd_gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
+{
+    if (!s_dev) {
+        return;
+    }
+
+    if (event == ESP_GATTS_REG_EVT) {
+        if (param->reg.status != ESP_GATT_OK) {
+            ESP_LOGE(TAG, "Reg app failed, app_id %04x, status %d", param->reg.app_id, param->reg.status);
+            return;
+        } else {
+            if (param->reg.app_id == ESP_GATT_UUID_DEVICE_INFO_SVC) {
+                s_dev->info_svc.gatt_if = gatts_if;
+            } else if (param->reg.app_id == ESP_GATT_UUID_BATTERY_SERVICE_SVC) {
+                s_dev->bat_svc.gatt_if = gatts_if;
+            } else if (param->reg.app_id >= ESP_GATT_UUID_HID_SVC && param->reg.app_id < (ESP_GATT_UUID_HID_SVC + s_dev->devices_len)) {
+                ESP_LOGV(TAG, "HID SVC[%u] IF: %d", param->reg.app_id - ESP_GATT_UUID_HID_SVC, gatts_if);
+                s_dev->devices[param->reg.app_id - ESP_GATT_UUID_HID_SVC].hid_svc.gatt_if = gatts_if;
+            } else {
+                ESP_LOGE(TAG, "Unknown Application, app_id %04x", param->reg.app_id);
+                return;
+            }
+        }
+    } else if (event == ESP_GATTS_CREAT_ATTR_TAB_EVT) {
+        free(_last_db);
+        _last_db = NULL;
+    }
+
+    if (s_dev->bat_svc.gatt_if && gatts_if == s_dev->bat_svc.gatt_if) {
+        bat_event_handler(s_dev, event, gatts_if, param);
+    } else if (s_dev->info_svc.gatt_if && gatts_if == s_dev->info_svc.gatt_if) {
+        info_event_handler(s_dev, event, gatts_if, param);
+    } else {
+        int devi = get_device_map_index_by_gatts_if (s_dev, gatts_if);
+        if (devi >= 0) {
+            hid_event_handler(s_dev, devi, event, gatts_if, param);
+        } else {
+            ESP_LOGE(TAG, "Unknown gatts_if %u", gatts_if);
+        }
+        return;
+    }
+}
+
+static esp_err_t esp_ble_hidd_dev_deinit(void *devp)
+{
+    esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
+    if (!s_dev) {
+        ESP_LOGE(TAG, "HID device profile already uninitialized");
+        return ESP_OK;
+    }
+
+    if (s_dev != dev) {
+        ESP_LOGE(TAG, "Wrong HID device provided");
+        return ESP_FAIL;
+    }
+    s_dev = NULL;
+
+    ble_hid_stop_gatts(dev);
+    esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_STOP_EVENT, NULL, 0, portMAX_DELAY);
+    ble_hid_free_config(dev);
+    free(dev);
+    return ESP_OK;
+}
+
+static bool esp_ble_hidd_dev_connected(void *devp)
+{
+    esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
+    return (dev != NULL && s_dev == dev && dev->connected);
+}
+
+static esp_err_t esp_ble_hidd_dev_battery_set(void *devp, uint8_t level)
+{
+    esp_err_t ret;
+    esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
+    if (!dev || s_dev != dev) {
+        return ESP_FAIL;
+    }
+    dev->bat_level = level;
+
+    if (!dev->connected || dev->bat_ccc.value == 0) {
+        //if we are not yet connected, that is not an error
+        return ESP_OK;
+    }
+
+    ret = esp_ble_gatts_send_indicate(dev->bat_svc.gatt_if, dev->conn_id, dev->bat_level_handle, 1, &dev->bat_level, dev->bat_ccc.indicate_enable);
+    if (ret) {
+        ESP_LOGE(TAG, "esp_ble_gatts_send_indicate failed: %d", ret);
+        return ESP_FAIL;
+    }
+    WAIT_CB(dev);
+    return ESP_OK;
+}
+
+static esp_err_t esp_ble_hidd_dev_input_set(void *devp, size_t index, size_t id, uint8_t *data, size_t length)
+{
+    hidd_le_report_item_t *p_rpt;
+    esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
+    if (!dev || s_dev != dev) {
+        return ESP_FAIL;
+    }
+    if (!dev->connected) {
+        ESP_LOGE(TAG, "Device Not Connected: %d", index);
+        return ESP_FAIL;
+    }
+
+    if ((p_rpt = get_report_by_id_and_type(dev, id, ESP_HID_REPORT_TYPE_INPUT)) != NULL && p_rpt->ccc.value) {
+        esp_err_t err = esp_ble_gatts_send_indicate(dev->devices[index].hid_svc.gatt_if, dev->conn_id, p_rpt->handle, length, data, p_rpt->ccc.indicate_enable);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "Send Input Indicate Failed: %d", err);
+            return ESP_FAIL;
+        }
+        WAIT_CB(dev);
+    } else {
+        ESP_LOGE(TAG, "Indicate Not Enabled: %d", 0);
+        return ESP_FAIL;
+    }
+    return ESP_OK;
+}
+
+static esp_err_t esp_ble_hidd_dev_feature_set(void *devp, size_t index, size_t id, uint8_t *data, size_t length)
+{
+    esp_err_t ret;
+    esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
+    if (!dev || s_dev != dev) {
+        return ESP_FAIL;
+    }
+    hidd_le_report_item_t *p_rpt;
+    if ((p_rpt = get_report_by_id_and_type(dev, id, ESP_HID_REPORT_TYPE_FEATURE)) != NULL) {
+        ret = esp_ble_gatts_set_attr_value(p_rpt->handle, length, data);
+        if (ret) {
+            ESP_LOGE(TAG, "esp_ble_gatts_set_attr_value failed: %d", ret);
+            return ESP_FAIL;
+        }
+        WAIT_CB(dev);
+        if (dev->connected && p_rpt->ccc.value) {
+            ret = esp_ble_gatts_send_indicate(dev->devices[index].hid_svc.gatt_if, dev->conn_id, p_rpt->handle, length, data, p_rpt->ccc.indicate_enable);
+            if (ret != ESP_OK) {
+                ESP_LOGE(TAG, "Send Feature Indicate Failed: %d", ret);
+                return ESP_FAIL;
+            }
+            WAIT_CB(dev);
+        }
+    } else {
+        ESP_LOGE(TAG, "FEATURE %d not found", id);
+        return ESP_FAIL;
+    }
+    return ESP_OK;
+}
+
+static esp_err_t esp_ble_hidd_dev_event_handler_register(void *devp, esp_event_handler_t callback, esp_hidd_event_t event)
+{
+    esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
+    if (!dev || s_dev != dev) {
+        return ESP_FAIL;
+    }
+    return esp_event_handler_register_with(dev->event_loop_handle, ESP_HIDD_EVENTS, event, callback, dev->dev);
+}
+
+static esp_err_t esp_ble_hidd_dev_event_handler_unregister(void *devp, esp_event_handler_t callback, esp_hidd_event_t event)
+{
+    esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
+    if (!dev || s_dev != dev) {
+        return ESP_FAIL;
+    }
+    return esp_event_handler_unregister_with(dev->event_loop_handle, ESP_HIDD_EVENTS, event, callback);
+}
+
+static void ble_hidd_dev_free(void)
+{
+    ble_hid_free_config(s_dev);
+    free(s_dev);
+    s_dev = NULL;
+}
+
+esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_config_t *config, esp_event_handler_t callback)
+{
+    esp_err_t ret;
+
+    if (s_dev) {
+        ESP_LOGE(TAG, "HID device profile already initialized");
+        return ESP_FAIL;
+    }
+
+    s_dev = (esp_ble_hidd_dev_t *)calloc(1, sizeof(esp_ble_hidd_dev_t));
+    if (s_dev == NULL) {
+        ESP_LOGE(TAG, "HID device could not be allocated");
+        return ESP_FAIL;
+    }
+
+    // Reset the hid device target environment
+    s_dev->bat_level = 100;
+    s_dev->bat_ccc.value = 0;
+    s_dev->control = ESP_HID_CONTROL_EXIT_SUSPEND;
+    s_dev->protocol = ESP_HID_PROTOCOL_MODE_REPORT;
+    s_dev->event_loop_handle = NULL;
+    s_dev->dev = dev_p;
+    s_dev->sem = xSemaphoreCreateBinary();
+    if (s_dev->sem == NULL) {
+        ESP_LOGE(TAG, "HID device semaphore could not be allocated");
+        ble_hidd_dev_free();
+        return ESP_FAIL;
+    }
+
+
+    esp_event_loop_args_t event_task_args = {
+        .queue_size = 5,
+        .task_name = "ble_hidd_events",
+        .task_priority = uxTaskPriorityGet(NULL),
+        .task_stack_size = 2048,
+        .task_core_id = tskNO_AFFINITY
+    };
+    ret = esp_event_loop_create(&event_task_args, &s_dev->event_loop_handle);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "HID device event loop could not be created");
+        ble_hidd_dev_free();
+        return ret;
+    }
+
+    ret = ble_hid_init_config(s_dev, config);
+    if (ret != ESP_OK) {
+        ble_hidd_dev_free();
+        return ret;
+    }
+
+    dev_p->dev = s_dev;
+    dev_p->connected = esp_ble_hidd_dev_connected;
+    dev_p->deinit = esp_ble_hidd_dev_deinit;
+    dev_p->battery_set = esp_ble_hidd_dev_battery_set;
+    dev_p->input_set = esp_ble_hidd_dev_input_set;
+    dev_p->feature_set = esp_ble_hidd_dev_feature_set;
+    dev_p->event_handler_register = esp_ble_hidd_dev_event_handler_register;
+    dev_p->event_handler_unregister = esp_ble_hidd_dev_event_handler_unregister;
+
+    if (callback != NULL) {
+        ret = esp_ble_hidd_dev_event_handler_register(s_dev, callback, ESP_EVENT_ANY_ID);
+        if (ret != ESP_OK) {
+            ble_hidd_dev_free();
+            return ret;
+        }
+    }
+
+    ret = ble_hid_start_gatts(s_dev);
+    if (ret != ESP_OK) {
+        ble_hidd_dev_free();
+        return ret;
+    }
+
+    return ret;
+}
+
+#endif /* CONFIG_GATTS_ENABLE */

+ 708 - 0
components/esp_hid/src/ble_hidh.c

@@ -0,0 +1,708 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include "ble_hidh.h"
+#if CONFIG_GATTC_ENABLE
+#include "esp_hidh_private.h"
+#include "esp_err.h"
+#include "esp_log.h"
+
+#include "esp_bt.h"
+#include "esp_bt_defs.h"
+#include "esp_bt_main.h"
+#include "esp_gattc_api.h"
+#include "esp_gatt_defs.h"
+#include "esp_gap_ble_api.h"
+#include "esp_hid_common.h"
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+
+static const char *TAG = "BLE_HIDH";
+
+static const char *s_gattc_evt_names[] = {"REG", "UNREG", "OPEN", "READ_CHAR", "WRITE_CHAR", "CLOSE", "SEARCH_CMPL", "SEARCH_RES", "READ_DESCR", "WRITE_DESCR", "NOTIFY", "PREP_WRITE", "EXEC", "ACL", "CANCEL_OPEN", "SRVC_CHG", "", "ENC_CMPL_CB", "CFG_MTU", "ADV_DATA", "MULT_ADV_ENB", "MULT_ADV_UPD", "MULT_ADV_DATA", "MULT_ADV_DIS", "CONGEST", "BTH_SCAN_ENB", "BTH_SCAN_CFG", "BTH_SCAN_RD", "BTH_SCAN_THR", "BTH_SCAN_PARAM", "BTH_SCAN_DIS", "SCAN_FLT_CFG", "SCAN_FLT_PARAM", "SCAN_FLT_STATUS", "ADV_VSC", "", "", "", "REG_FOR_NOTIFY", "UNREG_FOR_NOTIFY", "CONNECT", "DISCONNECT", "READ_MULTIPLE", "QUEUE_FULL", "SET_ASSOC", "GET_ADDR_LIST", "DIS_SRVC_CMPL"};
+
+const char *gattc_evt_str(uint8_t event)
+{
+    if (event >= (sizeof(s_gattc_evt_names)/sizeof(*s_gattc_evt_names))) {
+        return "UNKNOWN";
+    }
+    return s_gattc_evt_names[event];
+}
+
+static xSemaphoreHandle s_ble_hidh_cb_semaphore = NULL;
+
+static inline void WAIT_CB(void)
+{
+    xSemaphoreTake(s_ble_hidh_cb_semaphore, portMAX_DELAY);
+}
+
+static inline void SEND_CB(void)
+{
+    xSemaphoreGive(s_ble_hidh_cb_semaphore);
+}
+
+static esp_event_loop_handle_t event_loop_handle;
+static uint8_t *s_read_data_val = NULL;
+static uint16_t s_read_data_len = 0;
+static esp_gatt_status_t s_read_status = ESP_GATT_OK;
+
+static esp_gatt_status_t read_char(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t handle, esp_gatt_auth_req_t auth_req, uint8_t **out, uint16_t *out_len)
+{
+    s_read_data_val = NULL;
+    s_read_data_len = 0;
+    if (esp_ble_gattc_read_char(gattc_if, conn_id, handle, auth_req) != ESP_OK) {
+        ESP_LOGE(TAG, "read_char failed");
+        return ESP_GATT_ERROR;
+    }
+    WAIT_CB();
+    if (s_read_status == ESP_GATT_OK) {
+        *out = s_read_data_val;
+        *out_len = s_read_data_len;
+    }
+    return s_read_status;
+}
+
+static esp_gatt_status_t read_descr(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t handle, esp_gatt_auth_req_t auth_req, uint8_t **out, uint16_t *out_len)
+{
+    s_read_data_val = NULL;
+    s_read_data_len = 0;
+    if (esp_ble_gattc_read_char_descr(gattc_if, conn_id, handle, auth_req) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_ble_gattc_read_char failed");
+        return ESP_GATT_ERROR;
+    }
+    WAIT_CB();
+    if (s_read_status == ESP_GATT_OK) {
+        *out = s_read_data_val;
+        *out_len = s_read_data_len;
+    }
+    return s_read_status;
+}
+
+static void read_device_services(esp_gatt_if_t gattc_if, esp_hidh_dev_t *dev)
+{
+    uint16_t suuid, cuuid, duuid;
+    uint16_t chandle, dhandle;
+    esp_hidh_dev_report_t *report = NULL;
+    uint8_t *rdata = 0;
+    uint16_t rlen = 0;
+    esp_hid_report_item_t *r;
+    esp_hid_report_map_t *map;
+
+    esp_gattc_service_elem_t service_result[10];
+    uint16_t dcount = 10;
+    uint8_t hidindex = 0;
+    if (esp_ble_gattc_get_service(gattc_if, dev->ble.conn_id, NULL, service_result, &dcount, 0) == ESP_OK) {
+        ESP_LOGD(TAG, "Found %u HID Services", dev->config.report_maps_len);
+        dev->config.report_maps = (esp_hid_raw_report_map_t *)malloc(dev->config.report_maps_len * sizeof(esp_hid_raw_report_map_t));
+        if (dev->config.report_maps == NULL) {
+            ESP_LOGE(TAG, "malloc report maps failed");
+            return;
+        }
+
+        for (uint16_t s = 0; s < dcount; s++) {
+            suuid = service_result[s].uuid.uuid.uuid16;
+            ESP_LOGV(TAG, "SRV(%d) %s start_handle %d, end_handle %d, uuid: 0x%04x", s, service_result[s].is_primary ? " PRIMARY" : "", service_result[s].start_handle, service_result[s].end_handle, suuid);
+
+            if (suuid != ESP_GATT_UUID_BATTERY_SERVICE_SVC
+                    && suuid != ESP_GATT_UUID_DEVICE_INFO_SVC
+                    && suuid != ESP_GATT_UUID_HID_SVC
+                    && suuid != 0x1800) {//device name?
+                continue;
+            }
+
+            esp_gattc_char_elem_t char_result[20];
+            uint16_t ccount = 20;
+            if (esp_ble_gattc_get_all_char(gattc_if, dev->ble.conn_id, service_result[s].start_handle, service_result[s].end_handle, char_result, &ccount, 0) == ESP_OK) {
+                for (uint16_t c = 0; c < ccount; c++) {
+                    cuuid = char_result[c].uuid.uuid.uuid16;
+                    chandle = char_result[c].char_handle;
+                    ESP_LOGV(TAG, "  CHAR:(%d), handle: %d, perm: 0x%02x, uuid: 0x%04x", c + 1, chandle, char_result[c].properties, cuuid);
+
+                    if (suuid == 0x1800) {
+                        if (dev->config.device_name == NULL && cuuid == 0x2a00 && (char_result[c].properties & ESP_GATT_CHAR_PROP_BIT_READ) != 0) {
+                            if (read_char(gattc_if, dev->ble.conn_id, chandle, ESP_GATT_AUTH_REQ_NO_MITM, &rdata, &rlen) == ESP_GATT_OK && rlen) {
+                                dev->config.device_name = (const char *)rdata;
+                            }
+                        } else {
+                            continue;
+                        }
+                    } else if (suuid == ESP_GATT_UUID_BATTERY_SERVICE_SVC) {
+                        if (cuuid == ESP_GATT_UUID_BATTERY_LEVEL && (char_result[c].properties & ESP_GATT_CHAR_PROP_BIT_READ) != 0) {
+                            dev->ble.battery_handle = chandle;
+                        } else {
+                            continue;
+                        }
+                    } else if (suuid == ESP_GATT_UUID_DEVICE_INFO_SVC) {
+                        if (char_result[c].properties & ESP_GATT_CHAR_PROP_BIT_READ) {
+                            if (cuuid == ESP_GATT_UUID_PNP_ID) {
+                                if (read_char(gattc_if, dev->ble.conn_id, chandle, ESP_GATT_AUTH_REQ_NO_MITM, &rdata, &rlen) == ESP_GATT_OK && rlen == 7) {
+                                    dev->config.vendor_id = *((uint16_t *)&rdata[1]);
+                                    dev->config.product_id = *((uint16_t *)&rdata[3]);
+                                    dev->config.version = *((uint16_t *)&rdata[5]);
+                                }
+                                free(rdata);
+                            } else if (cuuid == ESP_GATT_UUID_MANU_NAME) {
+                                if (read_char(gattc_if, dev->ble.conn_id, chandle, ESP_GATT_AUTH_REQ_NO_MITM, &rdata, &rlen) == ESP_GATT_OK && rlen) {
+                                    dev->config.manufacturer_name = (const char *)rdata;
+                                }
+                            } else if (cuuid == ESP_GATT_UUID_SERIAL_NUMBER_STR) {
+                                if (read_char(gattc_if, dev->ble.conn_id, chandle, ESP_GATT_AUTH_REQ_NO_MITM, &rdata, &rlen) == ESP_GATT_OK && rlen) {
+                                    dev->config.serial_number = (const char *)rdata;
+                                }
+                            }
+                        }
+                        continue;
+                    } else {
+                        if (cuuid == ESP_GATT_UUID_HID_REPORT_MAP) {
+                            if (char_result[c].properties & ESP_GATT_CHAR_PROP_BIT_READ) {
+                                if (read_char(gattc_if, dev->ble.conn_id, chandle, ESP_GATT_AUTH_REQ_NO_MITM, &rdata, &rlen) == ESP_GATT_OK && rlen) {
+                                    dev->config.report_maps[hidindex].data = (const uint8_t *)rdata;
+                                    dev->config.report_maps[hidindex].len = rlen;
+                                }
+                            }
+                            continue;
+                        } else if (cuuid == ESP_GATT_UUID_HID_BT_KB_INPUT || cuuid == ESP_GATT_UUID_HID_BT_KB_OUTPUT || cuuid == ESP_GATT_UUID_HID_BT_MOUSE_INPUT || cuuid == ESP_GATT_UUID_HID_REPORT) {
+                            report = (esp_hidh_dev_report_t *)malloc(sizeof(esp_hidh_dev_report_t));
+                            if (report == NULL) {
+                                ESP_LOGE(TAG, "malloc esp_hidh_dev_report_t failed");
+                                return;
+                            }
+                            report->next = NULL;
+                            report->permissions = char_result[c].properties;
+                            report->handle = chandle;
+                            report->ccc_handle = 0;
+                            report->report_id = 0;
+                            report->map_index = hidindex;
+                            if (cuuid == ESP_GATT_UUID_HID_BT_KB_INPUT) {
+                                report->protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT;
+                                report->report_type = ESP_HID_REPORT_TYPE_INPUT;
+                                report->usage = ESP_HID_USAGE_KEYBOARD;
+                                report->value_len = 8;
+                            } else if (cuuid == ESP_GATT_UUID_HID_BT_KB_OUTPUT) {
+                                report->protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT;
+                                report->report_type = ESP_HID_REPORT_TYPE_OUTPUT;
+                                report->usage = ESP_HID_USAGE_KEYBOARD;
+                                report->value_len = 8;
+                            } else if (cuuid == ESP_GATT_UUID_HID_BT_MOUSE_INPUT) {
+                                report->protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT;
+                                report->report_type = ESP_HID_REPORT_TYPE_INPUT;
+                                report->usage = ESP_HID_USAGE_MOUSE;
+                                report->value_len = 8;
+                            } else {
+                                report->protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT;
+                                report->report_type = 0;
+                                report->usage = ESP_HID_USAGE_GENERIC;
+                                report->value_len = 0;
+                            }
+                        } else {
+                            continue;
+                        }
+                    }
+                    esp_gattc_descr_elem_t descr_result[20];
+                    uint16_t dcount = 20;
+                    if (esp_ble_gattc_get_all_descr(gattc_if, dev->ble.conn_id, char_result[c].char_handle, descr_result, &dcount, 0) == ESP_OK) {
+                        for (uint16_t d = 0; d < dcount; d++) {
+                            duuid = descr_result[d].uuid.uuid.uuid16;
+                            dhandle = descr_result[d].handle;
+                            ESP_LOGV(TAG, "    DESCR:(%d), handle: %d, uuid: 0x%04x", d + 1, dhandle, duuid);
+
+                            if (suuid == ESP_GATT_UUID_BATTERY_SERVICE_SVC) {
+                                if (duuid == ESP_GATT_UUID_CHAR_CLIENT_CONFIG && (char_result[c].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY) != 0) {
+                                    dev->ble.battery_ccc_handle = dhandle;
+                                }
+                            } else if (suuid == ESP_GATT_UUID_HID_SVC && report != NULL) {
+                                if (duuid == ESP_GATT_UUID_CHAR_CLIENT_CONFIG && (report->permissions & ESP_GATT_CHAR_PROP_BIT_NOTIFY) != 0) {
+                                    report->ccc_handle = dhandle;
+                                } else if (duuid == ESP_GATT_UUID_RPT_REF_DESCR) {
+                                    if (read_descr(gattc_if, dev->ble.conn_id, dhandle, ESP_GATT_AUTH_REQ_NO_MITM, &rdata, &rlen) == ESP_GATT_OK && rlen) {
+                                        report->report_id = rdata[0];
+                                        report->report_type = rdata[1];
+                                        free(rdata);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    if (suuid == ESP_GATT_UUID_HID_SVC && report != NULL) {
+                        report->next = dev->reports;
+                        dev->reports = report;
+                        dev->reports_len++;
+                    }
+                }
+                if (suuid == ESP_GATT_UUID_HID_SVC) {
+                    hidindex++;
+                }
+            }
+        }
+
+        for (uint8_t d = 0; d < dev->config.report_maps_len; d++) {
+            if (dev->reports_len && dev->config.report_maps[d].len) {
+                map = esp_hid_parse_report_map(dev->config.report_maps[d].data, dev->config.report_maps[d].len);
+                if (map) {
+                    if (dev->ble.appearance == 0) {
+                        dev->ble.appearance = map->appearance;
+                    }
+                    report = dev->reports;
+                    while (report) {
+                        if (report->map_index == d) {
+                            for (uint8_t i = 0; i < map->reports_len; i++) {
+                                r = &map->reports[i];
+                                if (report->protocol_mode == ESP_HID_PROTOCOL_MODE_BOOT
+                                        && report->protocol_mode == r->protocol_mode
+                                        && report->report_type == r->report_type
+                                        && report->usage == r->usage) {
+                                    report->report_id = r->report_id;
+                                    report->value_len = r->value_len;
+                                } else if (report->protocol_mode == r->protocol_mode
+                                           && report->report_type == r->report_type
+                                           && report->report_id == r->report_id) {
+                                    report->usage = r->usage;
+                                    report->value_len = r->value_len;
+                                }
+                            }
+                        }
+                        report = report->next;
+                    }
+                    free(map->reports);
+                    free(map);
+                    map = NULL;
+                }
+            }
+        }
+    }
+}
+
+static void register_for_notify(esp_gatt_if_t gattc_if, esp_bd_addr_t bda, uint16_t handle)
+{
+    esp_ble_gattc_register_for_notify(gattc_if, bda, handle);
+    WAIT_CB();
+}
+
+static void write_char_descr(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t handle, uint16_t value_len, uint8_t *value, esp_gatt_write_type_t write_type, esp_gatt_auth_req_t auth_req)
+{
+    esp_ble_gattc_write_char_descr(gattc_if, conn_id, handle, value_len, value, write_type, auth_req);
+    WAIT_CB();
+}
+
+static void attach_report_listeners(esp_gatt_if_t gattc_if, esp_hidh_dev_t *dev)
+{
+    if (dev == NULL) {
+        return;
+    }
+    uint16_t ccc_data = 1;
+    esp_hidh_dev_report_t *report = dev->reports;
+
+    //subscribe to battery notifications
+    if (dev->ble.battery_handle) {
+        register_for_notify(gattc_if, dev->bda, dev->ble.battery_handle);
+        if (dev->ble.battery_ccc_handle) {
+            //Write CCC descr to enable notifications
+            write_char_descr(gattc_if, dev->ble.conn_id, dev->ble.battery_ccc_handle, 2, (uint8_t *)&ccc_data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NO_MITM);
+        }
+    }
+
+    while (report) {
+        //subscribe to notifications
+        if ((report->permissions & ESP_GATT_CHAR_PROP_BIT_NOTIFY) != 0 && report->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) {
+            register_for_notify(gattc_if, dev->bda, report->handle);
+            if (report->ccc_handle) {
+                //Write CCC descr to enable notifications
+                write_char_descr(gattc_if, dev->ble.conn_id, report->ccc_handle, 2, (uint8_t *)&ccc_data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NO_MITM);
+            }
+        }
+        report = report->next;
+    }
+}
+
+static esp_gatt_if_t hid_gattc_if = 0;
+
+void esp_hidh_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
+{
+    esp_ble_gattc_cb_param_t *p_data = param;
+    esp_hidh_dev_t *dev = NULL;
+    esp_hidh_dev_report_t *report = NULL;
+
+    switch (event) {
+    case ESP_GATTC_REG_EVT:
+        if (param->reg.status == ESP_GATT_OK) {
+            hid_gattc_if = gattc_if;
+        } else {
+            ESP_LOGE(TAG, "Reg app failed, app_id %04x, status 0x%x", param->reg.app_id,  param->reg.status);
+            return;
+        }
+        SEND_CB();
+        break;
+
+    case ESP_GATTC_OPEN_EVT:
+        ESP_LOGV(TAG, "OPEN bda " ESP_BD_ADDR_STR ", conn_id %d, status 0x%x, mtu %d", ESP_BD_ADDR_HEX(p_data->open.remote_bda), p_data->open.conn_id, p_data->open.status, p_data->open.mtu);
+        dev = esp_hidh_dev_get_by_bda(p_data->open.remote_bda);
+        if (!dev) {
+            ESP_LOGE(TAG, "OPEN received for unknown device");
+            break;
+        }
+        if (p_data->open.status != 0) {
+            //error
+            ESP_LOGE(TAG, "OPEN failed: 0x%x", p_data->open.status);
+            dev->status = p_data->open.status;//ESP_GATT_CONN_FAIL_ESTABLISH;
+            dev->ble.conn_id = -1;
+            SEND_CB();//return from open
+        } else {
+            dev->status = ESP_GATT_NOT_FOUND;//set to not found and clear if HID service is found
+            dev->ble.conn_id = p_data->open.conn_id;
+            esp_ble_gattc_search_service(gattc_if, dev->ble.conn_id, NULL);
+        }
+        break;
+
+    case ESP_GATTC_SEARCH_RES_EVT:
+        dev = esp_hidh_dev_get_by_conn_id(p_data->search_res.conn_id);
+        if (!dev) {
+            ESP_LOGE(TAG, "SEARCH_RES received for unknown device");
+            break;
+        }
+        if (p_data->search_res.srvc_id.uuid.uuid.uuid16 == ESP_GATT_UUID_HID_SVC) {
+            dev->status = ESP_GATT_OK;
+            dev->config.report_maps_len++;
+            ESP_LOGV(TAG, "SEARCH_RES HID Service was found");
+        }
+        break;
+
+    case ESP_GATTC_SEARCH_CMPL_EVT:
+        dev = esp_hidh_dev_get_by_conn_id(p_data->search_cmpl.conn_id);
+        if (!dev) {
+            ESP_LOGE(TAG, "SEARCH_CMPL received for unknown device");
+            break;
+        }
+        if (dev->status == ESP_GATT_NOT_FOUND) {
+            //service not found
+            ESP_LOGE(TAG, "SEARCH_CMPL HID Service was not found on the device");
+            dev->status = ESP_GATT_CONN_NONE;
+        } else if (p_data->search_cmpl.status) {
+            //error
+            dev->status = p_data->search_cmpl.status;
+        }
+        if (dev->status) {
+            esp_ble_gattc_close(gattc_if, dev->ble.conn_id);
+            dev->ble.conn_id = -1;
+        }
+        dev->connected = true;
+        SEND_CB();//return from open
+        break;
+
+
+    case ESP_GATTC_READ_CHAR_EVT:
+    case ESP_GATTC_READ_DESCR_EVT: {
+        dev = esp_hidh_dev_get_by_conn_id(p_data->read.conn_id);
+        if (!dev) {
+            ESP_LOGE(TAG, "READ received for unknown device");
+            break;
+        }
+        dev->status = p_data->read.status;
+        s_read_status = p_data->read.status;
+        s_read_data_len = 0;
+        s_read_data_val = NULL;
+        if (s_read_status == 0 && p_data->read.value_len > 0) {
+            s_read_data_len = p_data->read.value_len;
+            s_read_data_val = (uint8_t *)malloc(s_read_data_len + 1);
+            if (s_read_data_val) {
+                memcpy(s_read_data_val, p_data->read.value, s_read_data_len);
+                s_read_data_val[s_read_data_len] = 0;
+            }
+        }
+        SEND_CB();
+        break;
+    }
+
+    case ESP_GATTC_WRITE_DESCR_EVT: {
+        dev = esp_hidh_dev_get_by_conn_id(p_data->write.conn_id);
+        if (!dev) {
+            ESP_LOGE(TAG, "WRITE_DESCR received for unknown device");
+            break;
+        }
+        dev->status = p_data->write.status;
+        SEND_CB();
+        break;
+    }
+
+    case ESP_GATTC_WRITE_CHAR_EVT: {
+        dev = esp_hidh_dev_get_by_conn_id(p_data->write.conn_id);
+        if (!dev) {
+            ESP_LOGE(TAG, "WRITE_CHAR received for unknown device");
+            break;
+        }
+        dev->status = p_data->write.status;
+        if (p_data->write.status) {
+            ESP_LOGE(TAG, "WRITE_CHAR: conn_id %d, handle %d, status 0x%x", p_data->write.conn_id, p_data->write.handle, p_data->write.status);
+        }
+        break;
+    }
+
+    case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
+        SEND_CB();
+        break;
+    }
+
+    case ESP_GATTC_DISCONNECT_EVT: {
+        ESP_LOGV(TAG, "DISCONNECT: bda " ESP_BD_ADDR_STR ", conn_id %u, reason 0x%x", ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.conn_id, p_data->disconnect.reason);
+        break;
+    }
+
+    case ESP_GATTC_NOTIFY_EVT: {
+        dev = esp_hidh_dev_get_by_conn_id(p_data->notify.conn_id);
+        if (!dev) {
+            ESP_LOGE(TAG, "NOTIFY received for unknown device");
+            break;
+        }
+        if (event_loop_handle) {
+            esp_hidh_event_data_t p = {0};
+            if (p_data->notify.handle == dev->ble.battery_handle) {
+                p.battery.dev = dev;
+                p.battery.level = p_data->notify.value[0];
+                esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_BATTERY_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
+            } else {
+                report = esp_hidh_dev_get_report_by_handle(dev, p_data->notify.handle);
+                if (report) {
+                    if (report->report_type == ESP_HID_REPORT_TYPE_FEATURE) {
+                        p.feature.dev = dev;
+                        p.feature.map_index = report->map_index;
+                        p.feature.report_id = report->report_id;
+                        p.feature.usage = report->usage;
+                        p.feature.data = p_data->notify.value;
+                        p.feature.length = p_data->notify.value_len;
+                        esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_INPUT_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
+                    } else {
+                        p.input.dev = dev;
+                        p.input.map_index = report->map_index;
+                        p.input.report_id = report->report_id;
+                        p.input.usage = report->usage;
+                        p.input.data = p_data->notify.value;
+                        p.input.length = p_data->notify.value_len;
+                        esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_INPUT_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
+                    }
+                }
+            }
+        }
+        break;
+    }
+
+    case ESP_GATTC_CLOSE_EVT: {
+        ESP_LOGV(TAG, "CLOSE bda " ESP_BD_ADDR_STR ", conn_id %d, status 0x%x, reason 0x%x", ESP_BD_ADDR_HEX(p_data->close.remote_bda), p_data->close.conn_id, p_data->close.status, p_data->close.reason);
+        dev = esp_hidh_dev_get_by_bda(p_data->open.remote_bda);
+        if (!dev) {
+            ESP_LOGE(TAG, "CLOSE received for unknown device");
+            break;
+        }
+        if (!dev->connected) {
+            dev->status = p_data->close.reason;
+            dev->ble.conn_id = -1;
+            SEND_CB();//return from open
+        } else {
+            dev->connected = false;
+            dev->status = p_data->close.status;
+            if (event_loop_handle) {
+                esp_hidh_event_data_t p = {0};
+                p.close.dev = dev;
+                p.close.reason = p_data->close.reason;
+                esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_CLOSE_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
+            } else {
+                esp_hidh_dev_free(dev);
+            }
+        }
+        break;
+    }
+
+    default:
+        ESP_LOGV(TAG, "GATTC EVENT %s", gattc_evt_str(event));
+        break;
+    }
+}
+
+/*
+ * Public Functions
+ * */
+
+static esp_err_t esp_ble_hidh_dev_close(esp_hidh_dev_t *dev)
+{
+    return esp_ble_gattc_close(hid_gattc_if, dev->ble.conn_id);
+}
+
+static esp_err_t esp_ble_hidh_dev_report_write(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, uint8_t *value, size_t value_len)
+{
+    esp_hidh_dev_report_t *report = esp_hidh_dev_get_report_by_id_and_type(dev, map_index, report_id, report_type);
+    if (!report) {
+        ESP_LOGE(TAG, "%s report %d not found", esp_hid_report_type_str(report_type), report_id);
+        return ESP_FAIL;
+    }
+    if (value_len > report->value_len) {
+        ESP_LOGE(TAG, "%s report %d takes maximum %d bytes. you have provided %d", esp_hid_report_type_str(report_type), report_id, report->value_len, value_len);
+        return ESP_FAIL;
+    }
+    return esp_ble_gattc_write_char(hid_gattc_if, dev->ble.conn_id, report->handle, value_len, value, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NO_MITM);
+}
+
+static esp_err_t esp_ble_hidh_dev_report_read(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, size_t max_length, uint8_t *value, size_t *value_len)
+{
+    esp_hidh_dev_report_t *report = esp_hidh_dev_get_report_by_id_and_type(dev, map_index, report_id, report_type);
+    if (!report) {
+        ESP_LOGE(TAG, "%s report %d not found", esp_hid_report_type_str(report_type), report_id);
+        return ESP_FAIL;
+    }
+    uint16_t len = max_length;
+    uint8_t *v = NULL;
+    esp_gatt_status_t s = read_char(hid_gattc_if, dev->ble.conn_id, report->handle, ESP_GATT_AUTH_REQ_NO_MITM, &v, &len);
+    if (s == ESP_GATT_OK) {
+        if (len > max_length) {
+            len = max_length;
+        }
+        *value_len = len;
+        memcpy(value, v, len);
+        return ESP_OK;
+    }
+    ESP_LOGE(TAG, "%s report %d read failed: 0x%x", esp_hid_report_type_str(report_type), report_id, s);
+    return ESP_FAIL;
+}
+
+static void esp_ble_hidh_dev_dump(esp_hidh_dev_t *dev, FILE *fp)
+{
+    fprintf(fp, "BDA:" ESP_BD_ADDR_STR ", Appearance: 0x%04x, Connection ID: %d\n", ESP_BD_ADDR_HEX(dev->bda), dev->ble.appearance, dev->ble.conn_id);
+    fprintf(fp, "Name: %s, Manufacturer: %s, Serial Number: %s\n", dev->config.device_name ? dev->config.device_name : "", dev->config.manufacturer_name ? dev->config.manufacturer_name : "", dev->config.serial_number ? dev->config.serial_number : "");
+    fprintf(fp, "PID: 0x%04x, VID: 0x%04x, VERSION: 0x%04x\n", dev->config.product_id, dev->config.vendor_id, dev->config.version);
+    fprintf(fp, "Battery: Handle: %u, CCC Handle: %u\n", dev->ble.battery_handle, dev->ble.battery_ccc_handle);
+    fprintf(fp, "Report Maps: %d\n", dev->config.report_maps_len);
+    for (uint8_t d = 0; d < dev->config.report_maps_len; d++) {
+        fprintf(fp, "  Report Map Length: %d\n", dev->config.report_maps[d].len);
+        esp_hidh_dev_report_t *report = dev->reports;
+        while (report) {
+            if (report->map_index == d) {
+                fprintf(fp, "    %8s %7s %6s, ID: %2u, Length: %3u, Permissions: 0x%02x, Handle: %3u, CCC Handle: %3u\n",
+                       esp_hid_usage_str(report->usage), esp_hid_report_type_str(report->report_type), esp_hid_protocol_mode_str(report->protocol_mode),
+                       report->report_id, report->value_len, report->permissions, report->handle, report->ccc_handle);
+            }
+            report = report->next;
+        }
+    }
+
+}
+
+esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config)
+{
+    esp_err_t ret;
+    if (config == NULL) {
+        ESP_LOGE(TAG, "Config is NULL");
+        return ESP_FAIL;
+    }
+    if (s_ble_hidh_cb_semaphore != NULL) {
+        ESP_LOGE(TAG, "Already initialised");
+        return ESP_FAIL;
+    }
+    s_ble_hidh_cb_semaphore = xSemaphoreCreateBinary();
+    if (s_ble_hidh_cb_semaphore == NULL) {
+        ESP_LOGE(TAG, "xSemaphoreCreateMutex failed!");
+        return ESP_FAIL;
+    }
+    esp_event_loop_args_t event_task_args = {
+        .queue_size = 5,
+        .task_name = "esp_ble_hidh_events",
+        .task_priority = uxTaskPriorityGet(NULL),
+        .task_stack_size = 2048,
+        .task_core_id = tskNO_AFFINITY
+    };
+    ret = esp_event_loop_create(&event_task_args, &event_loop_handle);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "esp_event_loop_create failed!");
+        return ESP_FAIL;
+    }
+
+
+    ret = esp_ble_gattc_app_register(0);
+    if (ret != ESP_OK) {
+        vSemaphoreDelete(s_ble_hidh_cb_semaphore);
+        s_ble_hidh_cb_semaphore = NULL;
+        return ret;
+    }
+    WAIT_CB();
+    esp_event_handler_register_with(event_loop_handle, ESP_HIDH_EVENTS, ESP_EVENT_ANY_ID, config->callback, NULL);
+    return ESP_OK;
+}
+
+esp_err_t esp_ble_hidh_deinit(void)
+{
+    if (s_ble_hidh_cb_semaphore == NULL) {
+        ESP_LOGE(TAG, "Already deinitialised");
+        return ESP_FAIL;
+    }
+
+    esp_err_t err = esp_ble_gattc_app_unregister(hid_gattc_if);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "App Unregister Failed");
+        return err;
+    }
+
+    if (event_loop_handle) {
+        esp_event_loop_delete(event_loop_handle);
+    }
+    vSemaphoreDelete(s_ble_hidh_cb_semaphore);
+    s_ble_hidh_cb_semaphore = NULL;
+    return err;
+}
+
+esp_hidh_dev_t *esp_ble_hidh_dev_open(esp_bd_addr_t bda, esp_ble_addr_type_t address_type)
+{
+    esp_err_t ret;
+
+    esp_hidh_dev_t *dev = esp_hidh_dev_malloc();
+    if (dev == NULL) {
+        ESP_LOGE(TAG, "malloc esp_hidh_dev_t failed");
+        return NULL;
+    }
+
+    dev->transport = ESP_HID_TRANSPORT_BLE;
+    memcpy(dev->bda, bda, sizeof(esp_bd_addr_t));
+    dev->ble.address_type = address_type;
+    dev->ble.appearance = ESP_HID_APPEARANCE_GENERIC;
+
+    ret = esp_ble_gattc_open(hid_gattc_if, dev->bda, dev->ble.address_type, true);
+    if (ret) {
+        esp_hidh_dev_free(dev);
+        ESP_LOGE(TAG, "esp_ble_gattc_open failed: %d", ret);
+        return NULL;
+    }
+    WAIT_CB();
+    if (dev->ble.conn_id < 0) {
+        ret = dev->status;
+        ESP_LOGE(TAG, "dev open failed! status: 0x%x", dev->status);
+        esp_hidh_dev_free(dev);
+        return NULL;
+    }
+
+    dev->close = esp_ble_hidh_dev_close;
+    dev->report_write = esp_ble_hidh_dev_report_write;
+    dev->report_read = esp_ble_hidh_dev_report_read;
+    dev->dump = esp_ble_hidh_dev_dump;
+
+    read_device_services(hid_gattc_if, dev);
+
+    if (event_loop_handle) {
+        esp_hidh_event_data_t p = {0};
+        p.open.dev = dev;
+        esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_OPEN_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
+    }
+
+    attach_report_listeners(hid_gattc_if, dev);
+    return dev;
+}
+
+#endif /* CONFIG_GATTC_ENABLE */

+ 445 - 0
components/esp_hid/src/bt_hidh.c

@@ -0,0 +1,445 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "bt_hidh.h"
+#if CONFIG_BT_HID_HOST_ENABLED
+#include "esp_hidh_private.h"
+#include <string.h>
+#include <stdbool.h>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+
+#include "esp_bt_hh_api.h"
+
+static const char *TAG = "BT_HIDH";
+
+static esp_event_loop_handle_t event_loop_handle;
+
+static const char *s_bta_hh_evt_names[] = {"ENABLE", "DISABLE", "OPEN", "CLOSE", "GET_RPT", "SET_RPT", "GET_PROTO", "SET_PROTO", "GET_IDLE", "SET_IDLE", "GET_DSCP", "ADD_DEV", "RMV_DEV", "VC_UNPLUG", "DATA", "API_ERR", "UPDATE_SCPP"};
+static const char *s_bta_hh_status_names[] = {"OK", "HS_HID_NOT_READY", "HS_INVALID_RPT_ID", "HS_TRANS_NOT_SPT", "HS_INVALID_PARAM", "HS_ERROR", "ERR", "ERR_SDP", "ERR_PROTO", "ERR_DB_FULL", "ERR_TOD_UNSPT", "ERR_NO_RES", "ERR_AUTH_FAILED", "ERR_HDL", "ERR_SEC"};
+
+static inline void WAIT_DEV(esp_hidh_dev_t *dev)
+{
+    xSemaphoreTake(dev->semaphore, portMAX_DELAY);
+}
+
+static inline void SEND_DEV(esp_hidh_dev_t *dev)
+{
+    xSemaphoreGive(dev->semaphore);
+}
+
+static void bta_hh_cb(tBTA_HH_EVT event, tBTA_HH *p_data)
+{
+    static esp_hidh_dev_t *descr_dev = NULL;
+    esp_hidh_dev_t *dev = NULL;
+    switch (event) {
+    case BTA_HH_ENABLE_EVT: {
+        if (p_data->status) {
+            ESP_LOGE(TAG, "ENABLE ERROR: %s", s_bta_hh_status_names[p_data->status]);
+        }
+    } break;
+    case BTA_HH_OPEN_EVT: {
+        dev = esp_hidh_dev_get_by_handle(p_data->conn.handle);
+        if (dev == NULL) {
+            ESP_LOGE(TAG, "OPEN ERROR: Device Not Found");
+            return;
+        }
+        dev->status = p_data->conn.status;
+        memcpy(dev->bda, p_data->conn.bda, sizeof(esp_bd_addr_t));
+        if (dev->status == BTA_HH_OK) {
+            descr_dev = dev;
+            BTA_HhGetDscpInfo(dev->bt.handle);
+        } else {
+            ESP_LOGE(TAG, "OPEN ERROR: %s", s_bta_hh_status_names[dev->status]);
+            if (dev->opened) {
+                SEND_DEV(dev);
+            } else {
+                esp_hidh_dev_free(dev);
+            }
+        }
+
+    } break;
+    case BTA_HH_GET_DSCP_EVT: {
+        ESP_LOGV(TAG, "DESCRIPTOR: PID: 0x%04x, VID: 0x%04x, VERSION: 0x%04x, REPORT_LEN: %u", p_data->dscp_info.product_id, p_data->dscp_info.vendor_id, p_data->dscp_info.version, p_data->dscp_info.descriptor.dl_len);
+        if (descr_dev == NULL) {
+            ESP_LOGE(TAG, "Device Not Found");
+            return;
+        }
+        dev = descr_dev;
+        dev->config.product_id = p_data->dscp_info.product_id;
+        dev->config.vendor_id = p_data->dscp_info.vendor_id;
+        dev->config.version = p_data->dscp_info.version;
+
+
+        dev->config.report_maps_len = 1;
+        dev->config.report_maps = (esp_hid_raw_report_map_t *)malloc(dev->config.report_maps_len * sizeof(esp_hid_raw_report_map_t));
+        if (dev->config.report_maps == NULL) {
+            ESP_LOGE(TAG, "malloc report maps failed");
+            return;
+        }
+
+        dev->config.report_maps[0].data = (uint8_t *)malloc(p_data->dscp_info.descriptor.dl_len);
+        if (dev->config.report_maps[0].data == NULL) {
+            ESP_LOGE(TAG, "Malloc Report Map Failed");
+            dev->status = BTA_HH_ERR_NO_RES;
+        } else {
+            dev->config.report_maps[0].len = p_data->dscp_info.descriptor.dl_len;
+            memcpy((uint8_t *)dev->config.report_maps[0].data, p_data->dscp_info.descriptor.dsc_list, dev->config.report_maps[0].len);
+            //generate reports
+
+            if (dev->config.report_maps[0].len && dev->config.report_maps[0].data) {
+                esp_hid_report_map_t *map;
+                esp_hidh_dev_report_t *report;
+                esp_hid_report_item_t *r;
+                map = esp_hid_parse_report_map(dev->config.report_maps[0].data, dev->config.report_maps[0].len);
+                if (map) {
+                    if (dev->usage == 0) {
+                        dev->usage = map->usage;
+                    }
+                    dev->connected = true;
+                    dev->reports = NULL;
+                    for (uint8_t i = 0; i < map->reports_len; i++) {
+                        r = &map->reports[i];
+                        report = (esp_hidh_dev_report_t *)malloc(sizeof(esp_hidh_dev_report_t));
+                        if (report == NULL) {
+                            ESP_LOGE(TAG, "Malloc Report Failed");
+                            dev->status = BTA_HH_ERR_NO_RES;
+                            dev->connected = false;
+                            break;
+                        }
+                        report->map_index = 0;
+                        report->protocol_mode = r->protocol_mode;
+                        report->report_type = r->report_type;
+                        report->report_id = r->report_id;
+                        report->value_len = r->value_len;
+                        report->usage = r->usage;
+                        report->next = dev->reports;
+                        dev->reports = report;
+                    }
+                    dev->reports_len = map->reports_len;
+                    free(map->reports);
+                    free(map);
+                    map = NULL;
+                } else {
+                    ESP_LOGE(TAG, "Parse Report Map Failed");
+                    dev->status = BTA_HH_ERR;
+                }
+            }
+
+        }
+        descr_dev = NULL;
+        if (dev->status == BTA_HH_OK) {
+            BTA_HhAddDev(dev->bda, dev->bt.attr_mask, dev->bt.sub_class, dev->bt.app_id, p_data->dscp_info);
+        } else {
+            ESP_LOGE(TAG, "Read Report Map Failed, status: %s", s_bta_hh_status_names[dev->status]);
+            if (dev->opened) {
+                SEND_DEV(dev);
+            } else {
+                esp_hidh_dev_free(dev);
+            }
+        }
+    } break;
+    case BTA_HH_ADD_DEV_EVT: {
+        ESP_LOGV(TAG, "ADD_DEV: BDA: " ESP_BD_ADDR_STR ", handle: %d, status: %s", ESP_BD_ADDR_HEX(p_data->dev_info.bda), p_data->dev_info.handle, s_bta_hh_status_names[p_data->dev_info.status]);
+        dev = esp_hidh_dev_get_by_handle(p_data->conn.handle);
+        if (dev == NULL) {
+            ESP_LOGE(TAG, "Device Not Found");
+            return;
+        }
+        dev->status = p_data->conn.status;
+        if (dev->status == BTA_HH_OK) {
+            esp_hidh_event_data_t p;
+            p.open.dev = dev;
+            esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_OPEN_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
+        } else {
+            ESP_LOGE(TAG, "Device Add Failed, status: %s", s_bta_hh_status_names[dev->status]);
+        }
+        if (dev->opened) {
+            SEND_DEV(dev);
+        } else if (dev->status != BTA_HH_OK) {
+            esp_hidh_dev_free(dev);
+        }
+    } break;
+    case BTA_HH_CLOSE_EVT: {
+        ESP_LOGV(TAG, "CLOSE: handle: %d, status: %s", p_data->dev_status.handle, s_bta_hh_status_names[p_data->dev_status.status]);
+        dev = esp_hidh_dev_get_by_handle(p_data->dev_status.handle);
+        if (dev == NULL) {
+            ESP_LOGE(TAG, "Device Not Found");
+            return;
+        }
+        dev->status = p_data->dev_status.status;
+        esp_hidh_event_data_t p;
+        p.close.dev = dev;
+        p.close.reason = 0;
+        esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_CLOSE_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
+    } break;
+    case BTA_HH_SET_RPT_EVT: {
+        dev = esp_hidh_dev_get_by_handle(p_data->dev_status.handle);
+        if (dev == NULL) {
+            ESP_LOGE(TAG, "SET_RPT ERROR: hDevice Not Found");
+            return;
+        }
+        if (p_data->dev_status.status) {
+            ESP_LOGE(TAG, "SET_RPT ERROR: handle: %d, status: %s", p_data->dev_status.handle, s_bta_hh_status_names[p_data->dev_status.status]);
+        }
+        dev->status = p_data->dev_status.status;
+        SEND_DEV(dev);
+    } break;
+    case BTA_HH_GET_RPT_EVT: {
+        dev = esp_hidh_dev_get_by_handle(p_data->hs_data.handle);
+        if (dev == NULL) {
+            ESP_LOGE(TAG, "Device Not Found");
+            return;
+        }
+        if (p_data->hs_data.status) {
+            ESP_LOGE(TAG, "GET_RPT ERROR: handle: %d, status: %s", p_data->hs_data.handle, s_bta_hh_status_names[p_data->hs_data.status]);
+        }
+        dev->status = p_data->hs_data.status;
+        BT_HDR *rpt = p_data->hs_data.rsp_data.p_rpt_data;
+        dev->tmp = rpt->data + rpt->offset;
+        dev->tmp_len = rpt->len;
+        SEND_DEV(dev);
+    } break;
+    default:
+        ESP_LOGV(TAG, "BTA_HH EVENT: %s", s_bta_hh_evt_names[event]);
+        break;
+    }
+}
+
+/*
+ * Public Functions
+ * */
+
+static esp_err_t esp_bt_hidh_dev_close(esp_hidh_dev_t *dev)
+{
+    BTA_HhClose(dev->bt.handle);
+    return ESP_OK;
+}
+
+static esp_err_t esp_bt_hidh_dev_report_write(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, uint8_t *data, size_t len)
+{
+    esp_hidh_dev_report_t *report = esp_hidh_dev_get_report_by_id_and_type(dev, map_index, report_id, report_type);
+    if (!report) {
+        ESP_LOGE(TAG, "%s report %d not found", esp_hid_report_type_str(report_type), report_id);
+        return ESP_FAIL;
+    }
+    if (len > report->value_len) {
+        ESP_LOGE(TAG, "%s report %d takes maximum %d bytes. you have provided %d", esp_hid_report_type_str(report_type), report_id, report->value_len, len);
+        return ESP_FAIL;
+    }
+
+    uint8_t *pbuf_data;
+    BT_HDR *p_buf = (BT_HDR *)malloc((uint16_t) (len + 14 + sizeof(BT_HDR)));
+
+    if (p_buf != NULL) {
+        p_buf->len = len + 1;
+        p_buf->offset = 14;
+
+        pbuf_data = (uint8_t *) (p_buf + 1) + p_buf->offset;
+        pbuf_data[0] = report_id;
+        memcpy(pbuf_data + 1, data, len);
+
+        if (report_type == ESP_HID_REPORT_TYPE_OUTPUT) {
+            p_buf->layer_specific = BTA_HH_RPTT_OUTPUT;
+            BTA_HhSendData(dev->bt.handle, dev->bda, p_buf);
+        } else {
+            BTA_HhSetReport(dev->bt.handle, report_type, p_buf);
+            WAIT_DEV(dev);
+        }
+        if (dev->status) {
+            ESP_LOGE(TAG, "Write %s: %s", esp_hid_report_type_str(report_type), s_bta_hh_status_names[dev->status]);
+            return ESP_FAIL;
+        }
+    }
+    return ESP_OK;
+}
+
+static esp_err_t esp_bt_hidh_dev_report_read(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, size_t max_length, uint8_t *value, size_t *value_len)
+{
+    esp_hidh_dev_report_t *report = esp_hidh_dev_get_report_by_id_and_type(dev, map_index, report_id, report_type);
+    if (!report) {
+        ESP_LOGE(TAG, "%s report %d not found", esp_hid_report_type_str(report_type), report_id);
+        return ESP_FAIL;
+    }
+    BTA_HhGetReport(dev->bt.handle, report_type, report_id, max_length);
+    if (xSemaphoreTake(dev->semaphore, 500 / portTICK_PERIOD_MS) != pdTRUE) {
+        ESP_LOGE(TAG, "Read Timeout %s", esp_hid_report_type_str(report_type));
+        return ESP_FAIL;
+    }
+    if (dev->status) {
+        ESP_LOGE(TAG, "Read %s: %s", esp_hid_report_type_str(report_type), s_bta_hh_status_names[dev->status]);
+        return ESP_FAIL;
+    }
+    if (report_id) {
+        dev->tmp++;
+        dev->tmp_len--;
+    }
+    if (dev->tmp_len > max_length) {
+        dev->tmp_len = max_length;
+    }
+    *value_len = dev->tmp_len;
+    memcpy(value, dev->tmp, dev->tmp_len);
+    return ESP_OK;
+}
+
+static void esp_bt_hidh_dev_dump(esp_hidh_dev_t *dev, FILE *fp)
+{
+    fprintf(fp, "BDA:" ESP_BD_ADDR_STR ", Status: %s, Connected: %s, Handle: %d, Usage: %s\n", ESP_BD_ADDR_HEX(dev->bda), s_bta_hh_status_names[dev->status], dev->connected ? "YES" : "NO", dev->bt.handle, esp_hid_usage_str(dev->usage));
+    fprintf(fp, "Name: %s, Manufacturer: %s, Serial Number: %s\n", dev->config.device_name ? dev->config.device_name : "", dev->config.manufacturer_name ? dev->config.manufacturer_name : "", dev->config.serial_number ? dev->config.serial_number : "");
+    fprintf(fp, "PID: 0x%04x, VID: 0x%04x, VERSION: 0x%04x\n", dev->config.product_id, dev->config.vendor_id, dev->config.version);
+    fprintf(fp, "Report Map Length: %d\n", dev->config.report_maps[0].len);
+    esp_hidh_dev_report_t *report = dev->reports;
+    while (report) {
+        fprintf(fp, "  %8s %7s %6s, ID: %3u, Length: %3u\n",
+               esp_hid_usage_str(report->usage), esp_hid_report_type_str(report->report_type), esp_hid_protocol_mode_str(report->protocol_mode),
+               report->report_id, report->value_len);
+        report = report->next;
+    }
+}
+
+esp_err_t esp_bt_hidh_init(const esp_hidh_config_t *config)
+{
+    if (config == NULL) {
+        ESP_LOGE(TAG, "Config is NULL");
+        return ESP_FAIL;
+    }
+    esp_event_loop_args_t event_task_args = {
+        .queue_size = 5,
+        .task_name = "esp_bt_hidh_events",
+        .task_priority = uxTaskPriorityGet(NULL),
+        .task_stack_size = 2048,
+        .task_core_id = tskNO_AFFINITY
+    };
+    esp_err_t ret = esp_event_loop_create(&event_task_args, &event_loop_handle);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "esp_event_loop_create failed!");
+        return ret;
+    }
+    esp_event_handler_register_with(event_loop_handle, ESP_HIDH_EVENTS, ESP_EVENT_ANY_ID, config->callback, NULL);
+    BTA_HhEnable(0, bta_hh_cb);
+    return ESP_OK;
+}
+
+esp_err_t esp_bt_hidh_deinit(void)
+{
+    if (event_loop_handle) {
+        esp_event_loop_delete(event_loop_handle);
+    }
+    BTA_HhDisable();
+    return ESP_OK;
+}
+
+esp_hidh_dev_t *esp_bt_hidh_dev_open(esp_bd_addr_t bda)
+{
+    esp_hidh_dev_t *dev = esp_hidh_dev_malloc();
+    if (dev == NULL) {
+        ESP_LOGE(TAG, "malloc esp_hidh_dev_t failed");
+        return NULL;
+    }
+
+    dev->transport = ESP_HID_TRANSPORT_BT;
+    memcpy(dev->bda, bda, sizeof(esp_bd_addr_t));
+    dev->bt.handle = -1;
+
+    dev->opened = true;
+    BTA_HhOpen(dev->bda, 0, BTA_HH_PROTO_RPT_MODE, (BTA_SEC_AUTHENTICATE | BTA_SEC_ENCRYPT));
+    WAIT_DEV(dev);
+    if (dev->status != BTA_HH_OK) {
+        esp_hidh_dev_free(dev);
+        return NULL;
+    }
+    dev->close = esp_bt_hidh_dev_close;
+    dev->report_write = esp_bt_hidh_dev_report_write;
+    dev->report_read = esp_bt_hidh_dev_report_read;
+    dev->dump = esp_bt_hidh_dev_dump;
+    return dev;
+}
+
+/*
+ * BlueDroid BT HIDH Stack Callbacks
+ * */
+
+/* This callback function is executed by BTA_HH when data is received on an interrupt channel. */
+void bta_hh_co_data(uint8_t handle, uint8_t *p_rpt, uint16_t len, tBTA_HH_PROTO_MODE  mode, uint8_t sub_class, uint8_t country_code, esp_bd_addr_t bda, uint8_t app_id)
+{
+    if (len < 2) {
+        ESP_LOGE(TAG, "Not Enough Data");
+        return;
+    }
+    esp_hidh_dev_t *dev = NULL;
+    esp_hidh_dev_report_t *report = NULL;
+    dev = esp_hidh_dev_get_by_handle(handle);
+    if (dev == NULL) {
+        ESP_LOGE(TAG, "Device Not Found: handle %u", handle);
+        return;
+    }
+    report = esp_hidh_dev_get_input_report_by_id_and_proto(dev, p_rpt[0], mode ? ESP_HID_PROTOCOL_MODE_BOOT : ESP_HID_PROTOCOL_MODE_REPORT);
+    if (report == NULL) {
+        ESP_LOGE(TAG, "Report Not Found: %d mode: %s", p_rpt[0], mode ? "BOOT" : "REPORT");
+        return;
+    }
+    if (len != (report->value_len + 1)) {
+        ESP_LOGW(TAG, "Wrong Data Len: %u != %u", len, (report->value_len + 1));
+    }
+
+    if (event_loop_handle) {
+        esp_hidh_event_data_t p = {0};
+        if (report->report_type == ESP_HID_REPORT_TYPE_FEATURE) {
+            p.feature.dev = dev;
+            p.feature.report_id = report->report_id;
+            p.feature.usage = report->usage;
+            p.feature.data = p_rpt + 1;
+            p.feature.length = len - 1;
+            esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_FEATURE_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
+        } else {
+            p.input.dev = dev;
+            p.input.report_id = report->report_id;
+            p.input.usage = report->usage;
+            p.input.data = p_rpt + 1;
+            p.input.length = len - 1;
+            esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_INPUT_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY);
+        }
+    }
+}
+
+/* This callback function is executed by BTA_HH when connection is opened, and application may do some device specific initialization. */
+void bta_hh_co_open(uint8_t handle, uint8_t sub_class, uint16_t attr_mask, uint8_t app_id)
+{
+    esp_hidh_dev_t *dev = NULL;
+    dev = esp_hidh_dev_get_by_handle(-1);
+    if (dev == NULL) {
+        ESP_LOGI(TAG, "Device Not Found? It's probably a reconnect.");
+        dev = esp_hidh_dev_malloc();
+        if (dev == NULL) {
+            ESP_LOGE(TAG, "DEV Malloc Failed");
+            return;
+        }
+        dev->transport = ESP_HID_TRANSPORT_BT;
+        dev->close = esp_bt_hidh_dev_close;
+        dev->report_write = esp_bt_hidh_dev_report_write;
+        dev->report_read = esp_bt_hidh_dev_report_read;
+        dev->dump = esp_bt_hidh_dev_dump;
+    }
+    dev->bt.attr_mask = attr_mask;
+    dev->bt.app_id = app_id;
+    dev->bt.sub_class = sub_class;
+    dev->bt.handle = handle;
+}
+
+/* This callback function is executed by BTA_HH when connection is closed, and device specific finalization may be needed. */
+void bta_hh_co_close(uint8_t dev_handle, uint8_t app_id) {}
+
+#endif /* CONFIG_BT_HID_HOST_ENABLED */

+ 519 - 0
components/esp_hid/src/esp_hid_common.c

@@ -0,0 +1,519 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include "esp_log.h"
+#include "esp_err.h"
+#include "esp_hid_common.h"
+#if (CONFIG_GATTS_ENABLE || CONFIG_GATTC_ENABLE)
+#include "esp_gatt_defs.h"
+#endif
+const char *TAG = "hid_parser";
+
+typedef struct {
+    uint16_t appearance;
+    uint8_t usage_mask;
+    uint8_t reports_len;
+    esp_hid_report_item_t reports[64];
+} temp_hid_report_map_t;
+
+typedef struct {
+    uint8_t cmd;
+    uint8_t len;
+    union {
+        uint32_t value;
+        uint8_t data[4];
+    };
+} hid_report_cmd_t;
+
+typedef struct {
+    uint16_t usage_page;
+    uint16_t usage;
+    uint16_t inner_usage_page;
+    uint16_t inner_usage;
+    uint8_t report_id;
+    uint16_t input_len;
+    uint16_t output_len;
+    uint16_t feature_len;
+} hid_report_params_t;
+
+typedef enum {
+    PARSE_WAIT_USAGE_PAGE, PARSE_WAIT_USAGE, PARSE_WAIT_COLLECTION_APPLICATION, PARSE_WAIT_END_COLLECTION
+} s_parse_step_t;
+
+
+static s_parse_step_t s_parse_step = PARSE_WAIT_USAGE_PAGE;
+static uint8_t s_collection_depth = 0;
+static hid_report_params_t s_report_params = {0,};
+static uint16_t s_report_size = 0;
+static uint16_t s_report_count = 0;
+
+static bool s_new_map = false;
+static temp_hid_report_map_t *s_temp_hid_report_map;
+
+static int add_report(temp_hid_report_map_t *map, esp_hid_report_item_t *item)
+{
+    if (map->reports_len >= 64) {
+        ESP_LOGE(TAG, "reports overflow");
+        return -1;
+    }
+    memcpy(&(map->reports[map->reports_len]), item, sizeof(esp_hid_report_item_t));
+    map->reports_len++;
+    return 0;
+}
+
+static int handle_report(hid_report_params_t *report, bool first)
+{
+    if (s_temp_hid_report_map == NULL) {
+        s_temp_hid_report_map = (temp_hid_report_map_t *)calloc(1, sizeof(temp_hid_report_map_t));
+        if (s_temp_hid_report_map == NULL) {
+            ESP_LOGE(TAG, "malloc failed");
+            return -1;
+        }
+    }
+    temp_hid_report_map_t *map = s_temp_hid_report_map;
+    if (first) {
+        memset(map, 0, sizeof(temp_hid_report_map_t));
+    }
+
+    if (report->usage_page == HID_USAGE_PAGE_GENERIC_DESKTOP && report->usage == HID_USAGE_KEYBOARD) {
+        //Keyboard
+        map->usage_mask |= ESP_HID_USAGE_KEYBOARD;
+        if (report->input_len > 0) {
+            esp_hid_report_item_t item = {
+                .usage = ESP_HID_USAGE_KEYBOARD,
+                .report_id = report->report_id,
+                .report_type = ESP_HID_REPORT_TYPE_INPUT,
+                .protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT,
+                .value_len = report->input_len / 8,
+            };
+            if (add_report(map, &item) != 0) {
+                return -1;
+            }
+
+            item.protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT;
+            item.value_len = 8;
+            if (add_report(map, &item) != 0) {
+                return -1;
+            }
+        }
+        if (report->output_len > 0) {
+            esp_hid_report_item_t item = {
+                .usage = ESP_HID_USAGE_KEYBOARD,
+                .report_id = report->report_id,
+                .report_type = ESP_HID_REPORT_TYPE_OUTPUT,
+                .protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT,
+                .value_len = report->output_len / 8,
+            };
+            if (add_report(map, &item) != 0) {
+                return -1;
+            }
+
+            item.protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT;
+            item.value_len = 1;
+            if (add_report(map, &item) != 0) {
+                return -1;
+            }
+        }
+    } else if (report->usage_page == HID_USAGE_PAGE_GENERIC_DESKTOP && report->usage == HID_USAGE_MOUSE) {
+        //Mouse
+        map->usage_mask |= ESP_HID_USAGE_MOUSE;
+        if (report->input_len > 0) {
+            esp_hid_report_item_t item = {
+                .usage = ESP_HID_USAGE_MOUSE,
+                .report_id = report->report_id,
+                .report_type = ESP_HID_REPORT_TYPE_INPUT,
+                .protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT,
+                .value_len = report->input_len / 8,
+            };
+            if (add_report(map, &item) != 0) {
+                return -1;
+            }
+
+            item.protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT;
+            item.value_len = 4;
+            if (add_report(map, &item) != 0) {
+                return -1;
+            }
+        }
+    } else {
+        esp_hid_usage_t cusage = ESP_HID_USAGE_GENERIC;
+        if (report->usage_page == HID_USAGE_PAGE_GENERIC_DESKTOP) {
+            if (report->usage == HID_USAGE_JOYSTICK) {
+                //Joystick
+                map->usage_mask |= ESP_HID_USAGE_JOYSTICK;
+                cusage = ESP_HID_USAGE_JOYSTICK;
+            } else if (report->usage == HID_USAGE_GAMEPAD) {
+                //Gamepad
+                map->usage_mask |= ESP_HID_USAGE_GAMEPAD;
+                cusage = ESP_HID_USAGE_GAMEPAD;
+            }
+        } else if (report->usage_page == HID_USAGE_PAGE_CONSUMER_DEVICE && report->usage == HID_USAGE_CONSUMER_CONTROL) {
+            //Consumer Control
+            map->usage_mask |= ESP_HID_USAGE_CCONTROL;
+            cusage = ESP_HID_USAGE_CCONTROL;
+        } else if (report->usage_page >= 0xFF) {
+            //Vendor
+            map->usage_mask |= ESP_HID_USAGE_VENDOR;
+            cusage = ESP_HID_USAGE_VENDOR;
+        }
+        //Generic
+        esp_hid_report_item_t item = {
+            .usage = cusage,
+            .report_id = report->report_id,
+            .report_type = ESP_HID_REPORT_TYPE_INPUT,
+            .protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT,
+            .value_len = report->input_len / 8,
+        };
+        if (report->input_len > 0) {
+            if (add_report(map, &item) != 0) {
+                return -1;
+            }
+        }
+        if (report->output_len > 0) {
+            item.report_type = ESP_HID_REPORT_TYPE_OUTPUT;
+            item.value_len = report->output_len / 8;
+            if (add_report(map, &item) != 0) {
+                return -1;
+            }
+        }
+        if (report->feature_len > 0) {
+            item.report_type = ESP_HID_REPORT_TYPE_FEATURE;
+            item.value_len = report->feature_len / 8;
+            if (add_report(map, &item) != 0) {
+                return -1;
+            }
+        }
+    }
+    return 0;
+}
+
+
+static int parse_cmd(const uint8_t *data, size_t len, size_t index, hid_report_cmd_t **out)
+{
+    if (index == len) {
+        return 0;
+    }
+    hid_report_cmd_t *cmd = (hid_report_cmd_t *)malloc(sizeof(hid_report_cmd_t));
+    if (cmd == NULL) {
+        return -1;
+    }
+    const uint8_t *dp = data + index;
+    cmd->cmd = *dp & 0xFC;
+    cmd->len = *dp & 0x03;
+    cmd->value = 0;
+    if (cmd->len == 3) {
+        cmd->len = 4;
+    }
+    if ((len - index - 1) < cmd->len) {
+        ESP_LOGE(TAG, "not enough bytes! cmd: 0x%02x, len: %u, index: %u", cmd->cmd, cmd->len, index);
+        free(cmd);
+        return -1;
+    }
+    memcpy(cmd->data, dp + 1, cmd->len);
+    *out = cmd;
+    return cmd->len + 1;
+}
+
+static int handle_cmd(hid_report_cmd_t *cmd)
+{
+    switch (s_parse_step) {
+    case PARSE_WAIT_USAGE_PAGE: {
+        if (cmd->cmd != HID_RM_USAGE_PAGE) {
+            ESP_LOGE(TAG, "expected USAGE_PAGE, but got 0x%02x", cmd->cmd);
+            return -1;
+        }
+        s_report_size = 0;
+        s_report_count = 0;
+        memset(&s_report_params, 0, sizeof(hid_report_params_t));
+        s_report_params.usage_page = cmd->value;
+        s_parse_step = PARSE_WAIT_USAGE;
+        break;
+    }
+    case PARSE_WAIT_USAGE: {
+        if (cmd->cmd != HID_RM_USAGE) {
+            ESP_LOGE(TAG, "expected USAGE, but got 0x%02x", cmd->cmd);
+            s_parse_step = PARSE_WAIT_USAGE_PAGE;
+            return -1;
+        }
+        s_report_params.usage = cmd->value;
+        s_parse_step = PARSE_WAIT_COLLECTION_APPLICATION;
+        break;
+    }
+    case PARSE_WAIT_COLLECTION_APPLICATION: {
+        if (cmd->cmd != HID_RM_COLLECTION) {
+            ESP_LOGE(TAG, "expected COLLECTION, but got 0x%02x", cmd->cmd);
+            s_parse_step = PARSE_WAIT_USAGE_PAGE;
+            return -1;
+        }
+        if (cmd->value != 1) {
+            ESP_LOGE(TAG, "expected APPLICATION, but got 0x%02x", cmd->value);
+            s_parse_step = PARSE_WAIT_USAGE_PAGE;
+            return -1;
+        }
+        s_report_params.report_id = 0;
+        s_collection_depth = 1;
+        s_parse_step = PARSE_WAIT_END_COLLECTION;
+        break;
+    }
+    case PARSE_WAIT_END_COLLECTION: {
+        if (cmd->cmd == HID_RM_REPORT_ID) {
+            if (s_report_params.report_id && s_report_params.report_id != cmd->value) {
+                //report id changed mid collection
+                if (s_report_params.input_len & 0x7) {
+                    ESP_LOGE(TAG, "ERROR: INPUT report does not amount to full bytes! %d (%d)", s_report_params.input_len, s_report_params.input_len & 0x7);
+                } else if (s_report_params.output_len & 0x7) {
+                    ESP_LOGE(TAG, "ERROR: OUTPUT report does not amount to full bytes! %d (%d)", s_report_params.output_len, s_report_params.output_len & 0x7);
+                } else if (s_report_params.feature_len & 0x7) {
+                    ESP_LOGE(TAG, "ERROR: FEATURE report does not amount to full bytes! %d (%d)", s_report_params.feature_len, s_report_params.feature_len & 0x7);
+                } else {
+                    //SUCCESS!!!
+                    int res = handle_report(&s_report_params, s_new_map);
+                    if (res != 0) {
+                        s_parse_step = PARSE_WAIT_USAGE_PAGE;
+                        return -1;
+                    }
+                    s_new_map = false;
+
+                    s_report_params.input_len = 0;
+                    s_report_params.output_len = 0;
+                    s_report_params.feature_len = 0;
+                    s_report_params.usage = s_report_params.inner_usage;
+                    s_report_params.usage_page = s_report_params.inner_usage_page;
+                }
+            }
+            s_report_params.report_id = cmd->value;
+        } else if (cmd->cmd == HID_RM_USAGE_PAGE) {
+            s_report_params.inner_usage_page = cmd->value;
+        } else if (cmd->cmd == HID_RM_USAGE) {
+            s_report_params.inner_usage = cmd->value;
+        } else if (cmd->cmd == HID_RM_REPORT_SIZE) {
+            s_report_size = cmd->value;
+        } else if (cmd->cmd == HID_RM_REPORT_COUNT) {
+            s_report_count = cmd->value;
+        } else if (cmd->cmd == HID_RM_INPUT) {
+            s_report_params.input_len += (s_report_size * s_report_count);
+        } else if (cmd->cmd == HID_RM_OUTPUT) {
+            s_report_params.output_len += (s_report_size * s_report_count);
+        } else if (cmd->cmd == HID_RM_FEATURE) {
+            s_report_params.feature_len += (s_report_size * s_report_count);
+        } else if (cmd->cmd == HID_RM_COLLECTION) {
+            s_collection_depth += 1;
+        } else if (cmd->cmd == HID_RM_END_COLLECTION) {
+            s_collection_depth -= 1;
+            if (s_collection_depth == 0) {
+                if (s_report_params.input_len & 0x7) {
+                    ESP_LOGE(TAG, "ERROR: INPUT report does not amount to full bytes! %d (%d)", s_report_params.input_len, s_report_params.input_len & 0x7);
+                } else if (s_report_params.output_len & 0x7) {
+                    ESP_LOGE(TAG, "ERROR: OUTPUT report does not amount to full bytes! %d (%d)", s_report_params.output_len, s_report_params.output_len & 0x7);
+                } else if (s_report_params.feature_len & 0x7) {
+                    ESP_LOGE(TAG, "ERROR: FEATURE report does not amount to full bytes! %d (%d)", s_report_params.feature_len, s_report_params.feature_len & 0x7);
+                } else {
+                    //SUCCESS!!!
+                    int res = handle_report(&s_report_params, s_new_map);
+                    if (res != 0) {
+                        s_parse_step = PARSE_WAIT_USAGE_PAGE;
+                        return -1;
+                    }
+                    s_new_map = false;
+                }
+                s_parse_step = PARSE_WAIT_USAGE_PAGE;
+            }
+        }
+
+        break;
+    }
+    default:
+        s_parse_step = PARSE_WAIT_USAGE_PAGE;
+        break;
+    }
+    return 0;
+}
+
+
+esp_hid_report_map_t *esp_hid_parse_report_map(const uint8_t *hid_rm, size_t hid_rm_len)
+{
+    size_t index = 0;
+    int res;
+    s_new_map = true;
+
+    while (index < hid_rm_len) {
+        hid_report_cmd_t *cmd;
+        res = parse_cmd(hid_rm, hid_rm_len, index, &cmd);
+        if (res < 0) {
+            ESP_LOGE(TAG, "Failed parsing the descriptor at index: %u", index);
+            return NULL;
+        }
+        index += res;
+        res = handle_cmd(cmd);
+        free(cmd);
+        if (res != 0) {
+            return NULL;
+        }
+    }
+
+    esp_hid_report_map_t *out = (esp_hid_report_map_t *)calloc(1, sizeof(esp_hid_report_map_t));
+    if (out == NULL) {
+        ESP_LOGE(TAG, "hid_report_map malloc failed");
+        free(s_temp_hid_report_map);
+        s_temp_hid_report_map = NULL;
+        return NULL;
+    }
+
+    temp_hid_report_map_t *map = s_temp_hid_report_map;
+
+    esp_hid_report_item_t *reports = (esp_hid_report_item_t *)calloc(1, map->reports_len * sizeof(esp_hid_report_item_t));
+    if (reports == NULL) {
+        ESP_LOGE(TAG, "hid_report_items malloc failed! %u maps", map->reports_len);
+        free(out);
+        free(s_temp_hid_report_map);
+        s_temp_hid_report_map = NULL;
+        return NULL;
+    }
+
+    if (map->usage_mask & ESP_HID_USAGE_KEYBOARD) {
+        out->usage = ESP_HID_USAGE_KEYBOARD;
+        out->appearance = ESP_HID_APPEARANCE_KEYBOARD;
+    } else if (map->usage_mask & ESP_HID_USAGE_MOUSE) {
+        out->usage = ESP_HID_USAGE_MOUSE;
+        out->appearance = ESP_HID_APPEARANCE_MOUSE;
+    } else if (map->usage_mask & ESP_HID_USAGE_JOYSTICK) {
+        out->usage = ESP_HID_USAGE_JOYSTICK;
+        out->appearance = ESP_HID_APPEARANCE_JOYSTICK;
+    } else if (map->usage_mask & ESP_HID_USAGE_GAMEPAD) {
+        out->usage = ESP_HID_USAGE_GAMEPAD;
+        out->appearance = ESP_HID_APPEARANCE_GAMEPAD;
+    } else if (map->usage_mask & ESP_HID_USAGE_CCONTROL) {
+        out->usage = ESP_HID_USAGE_CCONTROL;
+        out->appearance = ESP_HID_APPEARANCE_KEYBOARD;
+    } else {
+        out->usage = ESP_HID_USAGE_GENERIC;
+        out->appearance = ESP_HID_APPEARANCE_GENERIC;
+    }
+    out->reports_len = map->reports_len;
+    memcpy(reports, map->reports, map->reports_len * sizeof(esp_hid_report_item_t));
+    out->reports = reports;
+    free(s_temp_hid_report_map);
+    s_temp_hid_report_map = NULL;
+
+    return out;
+}
+
+void esp_hid_free_report_map(esp_hid_report_map_t *map)
+{
+    if (map != NULL){
+        free(map->reports);
+        free(map);
+    }
+}
+
+esp_hid_usage_t esp_hid_usage_from_appearance(uint16_t appearance)
+{
+    return ESP_HID_USAGE_GENERIC;
+}
+
+esp_hid_usage_t esp_hid_usage_from_cod(uint32_t cod)
+{
+    return ESP_HID_USAGE_GENERIC;
+}
+
+static const char *s_unknown_str = "UNKNOWN";
+static const char *s_hid_protocol_names[] = {"BOOT", "REPORT"};
+static const char *s_hid_report_type_names[] = {"NULL", "INPUT", "OUTPUT", "FEATURE"};
+static const char *s_hid_cod_major_names[] = {"MISC", "COMPUTER", "PHONE", "LAN_NAP", "AV", "PERIPHERAL", "IMAGING", "WEARABLE", "TOY", "HEALTH"};
+static const char *s_hid_cod_minor_names[7] = {"GENERIC", "JOYSTICK", "GAMEPAD", "REMOTE", "SENSOR", "TABLET", "CARD_READER"};
+
+const char *esp_hid_usage_str(esp_hid_usage_t usage)
+{
+    switch (usage) {
+    case ESP_HID_USAGE_GENERIC: return "GENERIC";
+    case ESP_HID_USAGE_KEYBOARD: return "KEYBOARD";
+    case ESP_HID_USAGE_MOUSE: return "MOUSE";
+    case ESP_HID_USAGE_JOYSTICK: return "JOYSTICK";
+    case ESP_HID_USAGE_GAMEPAD: return "GAMEPAD";
+    case ESP_HID_USAGE_CCONTROL: return "CCONTROL";
+    case ESP_HID_USAGE_VENDOR: return "VENDOR";
+    default: break;
+    }
+    return s_unknown_str;
+}
+
+const char *esp_hid_protocol_mode_str(uint8_t protocol)
+{
+    if (protocol >= (sizeof(s_hid_protocol_names)/sizeof(s_hid_protocol_names[0]))) {
+        return s_unknown_str;
+    }
+    return s_hid_protocol_names[protocol];
+}
+
+const char *esp_hid_report_type_str(uint8_t report_type)
+{
+    if (report_type >= (sizeof(s_hid_report_type_names)/sizeof(s_hid_report_type_names[0]))) {
+        return s_unknown_str;
+    }
+    return s_hid_report_type_names[report_type];
+}
+
+const char *esp_hid_cod_major_str(uint8_t cod_major)
+{
+    if (cod_major >= (sizeof(s_hid_cod_major_names)/sizeof(s_hid_cod_major_names[0]))) {
+        return s_unknown_str;
+    }
+    return s_hid_cod_major_names[cod_major];
+}
+
+void esp_hid_cod_minor_print(uint8_t cod_min, FILE *fp)
+{
+    if (cod_min & ESP_HID_COD_MIN_KEYBOARD) {
+        fputs("KEYBOARD", fp);
+    }
+    if (cod_min & ESP_HID_COD_MIN_MOUSE) {
+        if (cod_min & ESP_HID_COD_MIN_KEYBOARD) {
+            fputs("+", fp);
+        }
+        fputs("MOUSE", fp);
+    }
+    if (cod_min & 0xF0) {
+        if (cod_min & 0x0F) {
+            fputs("+", fp);
+        } else {
+            return;
+        }
+    }
+    cod_min &= 0x0F;
+    if (cod_min < ESP_HID_COD_MIN_MAX) {
+        fprintf(fp, "%s", s_hid_cod_minor_names[cod_min]);
+    }
+}
+
+const char *esp_hid_disconnect_reason_str(esp_hid_transport_t transport, int reason)
+{
+    if (transport == ESP_HID_TRANSPORT_BLE) {
+#if (CONFIG_GATTS_ENABLE || CONFIG_GATTC_ENABLE)
+        switch ((esp_gatt_conn_reason_t)reason) {
+        case ESP_GATT_CONN_L2C_FAILURE: return "L2C_FAILURE";
+        case ESP_GATT_CONN_TIMEOUT: return "TIMEOUT";
+        case ESP_GATT_CONN_TERMINATE_PEER_USER: return "TERMINATE_PEER_USER";
+        case ESP_GATT_CONN_TERMINATE_LOCAL_HOST: return "TERMINATE_LOCAL_HOST";
+        case ESP_GATT_CONN_LMP_TIMEOUT: return "LMP_TIMEOUT";
+        case ESP_GATT_CONN_FAIL_ESTABLISH: return "FAIL_ESTABLISH";
+        case ESP_GATT_CONN_CONN_CANCEL: return "CONN_CANCEL";
+        case ESP_GATT_CONN_NONE: return "NONE";
+        default: break;
+        }
+#endif /* CONFIG_GATTS_ENABLE || CONFIG_GATTC_ENABLE */
+    }
+    return s_unknown_str;
+}
+

+ 121 - 0
components/esp_hid/src/esp_hidd.c

@@ -0,0 +1,121 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "esp_hidd.h"
+#include "esp_hidd_private.h"
+#include "esp_event_base.h"
+
+#if CONFIG_GATTS_ENABLE
+#include "ble_hidd.h"
+#endif /* CONFIG_GATTS_ENABLE */
+
+ESP_EVENT_DEFINE_BASE(ESP_HIDD_EVENTS);
+
+esp_err_t esp_hidd_dev_init(const esp_hid_device_config_t *config, esp_hid_transport_t transport, esp_event_handler_t callback, esp_hidd_dev_t **dev_out)
+{
+
+    esp_err_t ret = ESP_OK;
+    esp_hidd_dev_t *dev = (esp_hidd_dev_t *)calloc(1, sizeof(esp_hidd_dev_t));
+    if (dev == NULL) {
+        return ESP_FAIL;
+    }
+
+    switch (transport) {
+#if CONFIG_GATTS_ENABLE
+    case ESP_HID_TRANSPORT_BLE:
+        ret = esp_ble_hidd_dev_init(dev, config, callback);
+        break;
+#endif /* CONFIG_GATTS_ENABLE */
+    default:
+        ret = ESP_FAIL;
+        break;
+    }
+
+    if (ret != ESP_OK) {
+        free(dev);
+        return ret;
+    }
+    dev->transport = transport;
+    *dev_out = dev;
+    return ret;
+}
+
+esp_err_t esp_hidd_dev_deinit(esp_hidd_dev_t *dev)
+{
+    if (dev == NULL) {
+        return ESP_FAIL;
+    }
+    esp_err_t ret = dev->deinit(dev->dev);
+    if (ret != ESP_OK) {
+        return ret;
+    }
+    free(dev);
+    return ret;
+}
+
+esp_hid_transport_t esp_hidd_dev_transport_get(esp_hidd_dev_t *dev)
+{
+    if (dev == NULL) {
+        return ESP_HID_TRANSPORT_MAX;
+    }
+    return dev->transport;
+}
+
+bool esp_hidd_dev_connected(esp_hidd_dev_t *dev)
+{
+    if (dev == NULL) {
+        return false;
+    }
+    return dev->connected(dev->dev);
+}
+
+esp_err_t esp_hidd_dev_battery_set(esp_hidd_dev_t *dev, uint8_t level)
+{
+    if (dev == NULL) {
+        return ESP_FAIL;
+    }
+    return dev->battery_set(dev->dev, level);
+}
+
+esp_err_t esp_hidd_dev_input_set(esp_hidd_dev_t *dev, size_t map_index, size_t report_id, uint8_t *data, size_t length)
+{
+    if (dev == NULL) {
+        return ESP_FAIL;
+    }
+    return dev->input_set(dev->dev, map_index, report_id, data, length);
+}
+
+esp_err_t esp_hidd_dev_feature_set(esp_hidd_dev_t *dev, size_t map_index, size_t report_id, uint8_t *data, size_t length)
+{
+    if (dev == NULL) {
+        return ESP_FAIL;
+    }
+    return dev->feature_set(dev->dev, map_index, report_id, data, length);
+}
+
+esp_err_t esp_hidd_dev_event_handler_register(esp_hidd_dev_t *dev, esp_event_handler_t callback, esp_hidd_event_t event)
+{
+    if (dev == NULL) {
+        return ESP_FAIL;
+    }
+    return dev->event_handler_register(dev->dev, callback, event);
+}
+
+esp_err_t esp_hidd_dev_event_handler_unregister(esp_hidd_dev_t *dev, esp_event_handler_t callback, esp_hidd_event_t event)
+{
+    if (dev == NULL) {
+        return ESP_FAIL;
+    }
+    return dev->event_handler_unregister(dev->dev, callback, event);
+}

+ 488 - 0
components/esp_hid/src/esp_hidh.c

@@ -0,0 +1,488 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "sys/queue.h"
+#include "esp_hidh_private.h"
+#include "bt_hidh.h"
+#include "ble_hidh.h"
+#include <string.h>
+#include <stdbool.h>
+#include "esp_event_base.h"
+
+ESP_EVENT_DEFINE_BASE(ESP_HIDH_EVENTS);
+
+static const char *TAG = "ESP_HIDH";
+
+static esp_hidh_dev_head_t s_esp_hidh_devices;
+
+static xSemaphoreHandle s_esp_hidh_devices_semaphore = NULL;
+
+static inline void lock_devices(void)
+{
+    if (s_esp_hidh_devices_semaphore != NULL) {
+        xSemaphoreTake(s_esp_hidh_devices_semaphore, portMAX_DELAY);
+    }
+}
+
+static inline void unlock_devices(void)
+{
+    if (s_esp_hidh_devices_semaphore != NULL) {
+        xSemaphoreGive(s_esp_hidh_devices_semaphore);
+    }
+}
+
+static bool esp_hidh_dev_exists(esp_hidh_dev_t *dev)
+{
+    if (dev == NULL) {
+        return false;
+    }
+    esp_hidh_dev_t * d = NULL;
+    lock_devices();
+    TAILQ_FOREACH(d, &s_esp_hidh_devices, devices) {
+        if (d == dev) {
+            unlock_devices();
+            return true;
+        }
+    }
+    unlock_devices();
+    return false;
+}
+
+/*
+ * Public Functions
+ * */
+
+esp_err_t esp_hidh_init(const esp_hidh_config_t *config)
+{
+    esp_err_t err = ESP_FAIL;
+    if (config == NULL) {
+        ESP_LOGE(TAG, "Config is NULL");
+        return err;
+    }
+
+    if (s_esp_hidh_devices_semaphore != NULL) {
+        ESP_LOGE(TAG, "Already initialized");
+        return err;
+    }
+
+    s_esp_hidh_devices_semaphore = xSemaphoreCreateBinary();
+    if (s_esp_hidh_devices_semaphore == NULL) {
+        ESP_LOGE(TAG, "xSemaphoreCreateMutex failed!");
+        return err;
+    }
+
+    err = ESP_OK;
+
+#if CONFIG_BT_HID_HOST_ENABLED
+    if (err == ESP_OK) {
+        err = esp_bt_hidh_init(config);
+    }
+#endif /* CONFIG_BT_HID_HOST_ENABLED */
+
+#if CONFIG_GATTC_ENABLE
+    if (err == ESP_OK) {
+        err = esp_ble_hidh_init(config);
+    }
+#endif /* CONFIG_GATTC_ENABLE */
+
+    if (err == ESP_OK) {
+        TAILQ_INIT(&s_esp_hidh_devices);
+        unlock_devices();
+    } else {
+        vSemaphoreDelete(s_esp_hidh_devices_semaphore);
+        s_esp_hidh_devices_semaphore = NULL;
+    }
+
+    return err;
+}
+
+esp_err_t esp_hidh_deinit(void)
+{
+    esp_err_t err = ESP_FAIL;
+    if (s_esp_hidh_devices_semaphore == NULL) {
+        ESP_LOGE(TAG, "Already uninitialized");
+        return err;
+    }
+
+    if (!TAILQ_EMPTY(&s_esp_hidh_devices)) {
+        ESP_LOGE(TAG, "Please disconnect all devices first!");
+        return err;
+    }
+
+    err = ESP_OK;
+
+#if CONFIG_BT_HID_HOST_ENABLED
+    if (err == ESP_OK) {
+        err = esp_bt_hidh_deinit();
+    }
+#endif /* CONFIG_BT_HID_HOST_ENABLED */
+
+#if CONFIG_GATTC_ENABLE
+    if (err == ESP_OK) {
+        err = esp_ble_hidh_deinit();
+    }
+#endif /* CONFIG_GATTC_ENABLE */
+
+    if (err == ESP_OK) {
+        TAILQ_INIT(&s_esp_hidh_devices);
+        vSemaphoreDelete(s_esp_hidh_devices_semaphore);
+        s_esp_hidh_devices_semaphore = NULL;
+    }
+    return err;
+}
+
+#if CONFIG_BLUEDROID_ENABLED
+esp_hidh_dev_t *esp_hidh_dev_open(esp_bd_addr_t bda, esp_hid_transport_t transport, uint8_t remote_addr_type)
+{
+    if (esp_hidh_dev_get_by_bda(bda) != NULL) {
+        ESP_LOGE(TAG, "Already Connected");
+        return NULL;
+    }
+    esp_hidh_dev_t *dev = NULL;
+#if CONFIG_GATTC_ENABLE
+    if (transport == ESP_HID_TRANSPORT_BLE) {
+        dev = esp_ble_hidh_dev_open(bda, (esp_ble_addr_type_t)remote_addr_type);
+    }
+#endif /* CONFIG_GATTC_ENABLE */
+#if CONFIG_BT_HID_HOST_ENABLED
+    if (transport == ESP_HID_TRANSPORT_BT) {
+        dev = esp_bt_hidh_dev_open(bda);
+    }
+#endif /* CONFIG_BT_HID_HOST_ENABLED */
+    return dev;
+}
+#endif /* CONFIG_BLUEDROID_ENABLED */
+
+esp_err_t esp_hidh_dev_close(esp_hidh_dev_t *dev)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return ESP_FAIL;
+    }
+    return dev->close(dev);
+}
+
+void esp_hidh_dev_dump(esp_hidh_dev_t *dev, FILE *fp)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return;
+    }
+    dev->dump(dev, fp);
+}
+
+esp_err_t esp_hidh_dev_output_set(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, uint8_t *value, size_t value_len)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return ESP_FAIL;
+    }
+    return dev->report_write(dev, map_index, report_id, ESP_HID_REPORT_TYPE_OUTPUT, value, value_len);
+}
+
+esp_err_t esp_hidh_dev_feature_set(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, uint8_t *value, size_t value_len)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return ESP_FAIL;
+    }
+    return dev->report_write(dev, map_index, report_id, ESP_HID_REPORT_TYPE_FEATURE, value, value_len);
+}
+
+esp_err_t esp_hidh_dev_feature_get(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, size_t max_length, uint8_t *value, size_t *value_len)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return ESP_FAIL;
+    }
+    return dev->report_read(dev, map_index, report_id, ESP_HID_REPORT_TYPE_FEATURE, max_length, value, value_len);
+}
+
+const uint8_t *esp_hidh_dev_bda_get(esp_hidh_dev_t *dev)
+{
+#if CONFIG_BLUEDROID_ENABLED
+    if (esp_hidh_dev_exists(dev)) {
+        return dev->bda;
+    }
+#endif /* CONFIG_BLUEDROID_ENABLED */
+    return NULL;
+}
+
+esp_hid_transport_t esp_hidh_dev_transport_get(esp_hidh_dev_t *dev)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return ESP_HID_TRANSPORT_MAX;
+    }
+    return dev->transport;
+}
+
+const esp_hid_device_config_t *esp_hidh_dev_config_get(esp_hidh_dev_t *dev)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return NULL;
+    }
+    return &dev->config;
+}
+
+const char *esp_hidh_dev_name_get(esp_hidh_dev_t *dev)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return NULL;
+    }
+    return dev->config.device_name ? dev->config.device_name : "";
+}
+
+const char *esp_hidh_dev_manufacturer_get(esp_hidh_dev_t *dev)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return NULL;
+    }
+    return dev->config.manufacturer_name ? dev->config.manufacturer_name : "";
+}
+
+const char *esp_hidh_dev_serial_get(esp_hidh_dev_t *dev)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return NULL;
+    }
+    return dev->config.serial_number ? dev->config.serial_number : "";
+}
+
+uint16_t esp_hidh_dev_vendor_id_get(esp_hidh_dev_t *dev)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return 0;
+    }
+    return dev->config.vendor_id;
+}
+
+uint16_t esp_hidh_dev_product_id_get(esp_hidh_dev_t *dev)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return 0;
+    }
+    return dev->config.product_id;
+}
+
+uint16_t esp_hidh_dev_version_get(esp_hidh_dev_t *dev)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return 0;
+    }
+    return dev->config.version;
+}
+
+esp_hid_usage_t esp_hidh_dev_usage_get(esp_hidh_dev_t *dev)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return ESP_HID_USAGE_GENERIC;
+    }
+    return dev->usage;
+}
+
+esp_err_t esp_hidh_dev_reports_get(esp_hidh_dev_t *dev, size_t *num_reports, esp_hid_report_item_t **reports)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return ESP_FAIL;
+    }
+
+    esp_hid_report_item_t *r = (esp_hid_report_item_t *)malloc(sizeof(esp_hid_report_item_t) * dev->reports_len);
+    if (r == NULL) {
+        return ESP_FAIL;
+    }
+
+    esp_hidh_dev_report_t *dr = dev->reports;
+    for (uint8_t i = 0; i < dev->reports_len; i++) {
+        if (dr == NULL) {
+            //error
+            return ESP_FAIL;
+        }
+        r[i].map_index = dr->map_index;
+        r[i].protocol_mode = dr->protocol_mode;
+        r[i].usage = dr->usage;
+        r[i].report_id = dr->report_id;
+        r[i].report_type = dr->report_type;
+        r[i].value_len = dr->value_len;
+
+        dr = dr->next;
+    }
+    *reports = r;
+    *num_reports = dev->reports_len;
+    return ESP_OK;
+}
+
+esp_err_t esp_hidh_dev_report_maps_get(esp_hidh_dev_t *dev, size_t *num_maps, esp_hid_raw_report_map_t **maps)
+{
+    if (!esp_hidh_dev_exists(dev)) {
+        return ESP_FAIL;
+    }
+    *num_maps = dev->config.report_maps_len;
+    *maps = dev->config.report_maps;
+    return ESP_OK;
+}
+
+
+/*
+ * Private Functions
+ * */
+
+esp_hidh_dev_report_t *esp_hidh_dev_get_report_by_handle(esp_hidh_dev_t *dev, uint16_t handle)
+{
+    esp_hidh_dev_report_t *r = dev->reports;
+    while (r) {
+        if (r->handle == handle) {
+            return r;
+        }
+        r = r->next;
+    }
+    return NULL;
+}
+
+esp_hidh_dev_report_t *esp_hidh_dev_get_report_by_id_and_type(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type)
+{
+    esp_hidh_dev_report_t *r = dev->reports;
+    while (r) {
+        if (r->map_index == map_index && r->report_id == report_id && r->report_type == report_type && r->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) {
+            return r;
+        }
+        r = r->next;
+    }
+    return NULL;
+}
+
+esp_hidh_dev_report_t *esp_hidh_dev_get_input_report_by_id_and_proto(esp_hidh_dev_t *dev, size_t report_id, int protocol_mode)
+{
+    esp_hidh_dev_report_t *r = dev->reports;
+    while (r) {
+        if (r->report_id == report_id && (r->report_type & 1) && r->protocol_mode == protocol_mode) {
+            return r;
+        }
+        r = r->next;
+    }
+    return NULL;
+}
+
+static void esp_hidh_dev_resources_free(esp_hidh_dev_t *dev)
+{
+    if (dev->semaphore) {
+        vSemaphoreDelete(dev->semaphore);
+    }
+    free((void *)dev->config.device_name);
+    free((void *)dev->config.manufacturer_name);
+    free((void *)dev->config.serial_number);
+    for (uint8_t d = 0; d < dev->config.report_maps_len; d++) {
+        free((void *)dev->config.report_maps[d].data);
+    }
+    free((void *)dev->config.report_maps);
+    esp_hidh_dev_report_t *r;
+    while (dev->reports) {
+        r = dev->reports;
+        dev->reports = dev->reports->next;
+        free(r);
+    }
+    free(dev);
+}
+
+esp_hidh_dev_t *esp_hidh_dev_malloc()
+{
+    esp_hidh_dev_t *dev = (esp_hidh_dev_t *)calloc(1, sizeof(esp_hidh_dev_t));
+    if (dev == NULL) {
+        ESP_LOGE(TAG, "malloc esp_hidh_dev_t failed");
+        return NULL;
+    }
+
+    dev->semaphore = xSemaphoreCreateBinary();
+    if (dev->semaphore == NULL) {
+        ESP_LOGE(TAG, "malloc semaphore failed");
+        esp_hidh_dev_resources_free(dev);
+        return NULL;
+    }
+
+    lock_devices();
+    TAILQ_INSERT_TAIL(&s_esp_hidh_devices, dev, devices);
+    unlock_devices();
+
+    return dev;
+}
+
+esp_err_t esp_hidh_dev_free(esp_hidh_dev_t *dev)
+{
+    esp_err_t ret = ESP_FAIL;
+
+    if (dev == NULL) {
+        return ret;
+    }
+
+    esp_hidh_dev_t *d = NULL;
+    esp_hidh_dev_t *next = NULL;
+    lock_devices();
+    TAILQ_FOREACH_SAFE(d, &s_esp_hidh_devices, devices, next) {
+        if (d == dev) {
+            TAILQ_REMOVE(&s_esp_hidh_devices, d, devices);
+            esp_hidh_dev_resources_free(d);
+            ret = ESP_OK;
+            break;
+        }
+    }
+    unlock_devices();
+    if (ret != ESP_OK) {
+        ESP_LOGW(TAG, "device not found");
+    } else {
+        ESP_LOGD(TAG, "device removed");
+    }
+    return ret;
+}
+
+#if CONFIG_BLUEDROID_ENABLED
+esp_hidh_dev_t *esp_hidh_dev_get_by_bda(esp_bd_addr_t bda)
+{
+    esp_hidh_dev_t * d = NULL;
+    lock_devices();
+    TAILQ_FOREACH(d, &s_esp_hidh_devices, devices) {
+        if (memcmp(bda, d->bda, sizeof(esp_bd_addr_t)) == 0) {
+            unlock_devices();
+            return d;
+        }
+    }
+    unlock_devices();
+    return NULL;
+}
+
+esp_hidh_dev_t *esp_hidh_dev_get_by_handle(int handle)
+{
+#if CONFIG_BT_HID_HOST_ENABLED
+    esp_hidh_dev_t * d = NULL;
+    lock_devices();
+    TAILQ_FOREACH(d, &s_esp_hidh_devices, devices) {
+        if (d->transport == ESP_HID_TRANSPORT_BT && d->bt.handle == handle) {
+            unlock_devices();
+            return d;
+        }
+    }
+    unlock_devices();
+#endif /* CONFIG_BT_HID_HOST_ENABLED */
+    return NULL;
+}
+
+esp_hidh_dev_t *esp_hidh_dev_get_by_conn_id(uint16_t conn_id)
+{
+#if CONFIG_GATTC_ENABLE
+    esp_hidh_dev_t * d = NULL;
+    lock_devices();
+    TAILQ_FOREACH(d, &s_esp_hidh_devices, devices) {
+        if (d->transport == ESP_HID_TRANSPORT_BLE && d->ble.conn_id == conn_id) {
+            unlock_devices();
+            return d;
+        }
+    }
+    unlock_devices();
+#endif /* CONFIG_GATTC_ENABLE */
+    return NULL;
+}
+#endif /* CONFIG_BLUEDROID_ENABLED */

+ 3 - 0
components/esp_hid/test/CMakeLists.txt

@@ -0,0 +1,3 @@
+idf_component_register(SRC_DIRS "."
+                    INCLUDE_DIRS "."
+                    REQUIRES unity test_utils esp_hid)

+ 40 - 0
components/esp_hid/test/README.md

@@ -0,0 +1,40 @@
+### Tests have been generated with the following code
+
+```c
+
+void dump_report_map(const uint8_t *data, size_t len){
+    esp_hid_report_map_t * report_map = esp_hid_parse_report_map(data, len);
+    printf("    TEST_ASSERT_NOT_NULL(report_map);\n");
+    printf("    TEST_ASSERT(report_map->usage == ESP_HID_USAGE_%s);\n", esp_hid_usage_str(report_map->usage));
+    printf("    TEST_ASSERT(report_map->appearance == 0x%04X);\n", report_map->appearance);
+    printf("    TEST_ASSERT(report_map->reports_len == %u);\n", report_map->reports_len);
+    for(uint8_t i=0; i<report_map->reports_len; i++){
+        printf("    TEST_ASSERT(report_map->reports[%u].report_id == %u);\n", i, report_map->reports[i].report_id);
+        printf("    TEST_ASSERT(report_map->reports[%u].report_type == ESP_HID_REPORT_TYPE_%s);\n", i, esp_hid_report_type_str(report_map->reports[i].report_type));
+        printf("    TEST_ASSERT(report_map->reports[%u].protocol_mode == ESP_HID_PROTOCOL_MODE_%s);\n", i, esp_hid_protocol_mode_str(report_map->reports[i].protocol_mode));
+        printf("    TEST_ASSERT(report_map->reports[%u].usage == ESP_HID_USAGE_%s);\n", i, esp_hid_usage_str(report_map->reports[i].usage));
+        printf("    TEST_ASSERT(report_map->reports[%u].value_len == %u);\n", i, report_map->reports[i].value_len);
+    }
+    printf("    esp_hid_free_report_map(report_map);\n");
+}
+
+#define _str(a) #a
+#define xstr(a) _str(a)
+#define TEST_DUMP(map) \
+    printf("TEST_CASE(\"can parse " xstr(map) "\", \"[esp_hid]\")\n{\n"); \
+    printf("    esp_hid_report_map_t * report_map = esp_hid_parse_report_map(" xstr(map) ", sizeof(" xstr(map) "));\n"); \
+    dump_report_map(map, sizeof(map)); \
+    printf("}\n\n");
+
+void generate_tests(){
+    TEST_DUMP(hidReportMap);
+    TEST_DUMP(relMouseReportMap);
+    TEST_DUMP(absMouseReportMap);
+    TEST_DUMP(keyboardReportMap);
+    TEST_DUMP(joystickReportMap);
+    TEST_DUMP(mediaReportMap);
+    TEST_DUMP(mediaReportMap2);
+    TEST_DUMP(hidapiReportMap);
+}
+
+```

+ 1 - 0
components/esp_hid/test/component.mk

@@ -0,0 +1 @@
+COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

+ 403 - 0
components/esp_hid/test/hid_descriptor.h

@@ -0,0 +1,403 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#pragma once
+
+const unsigned char hidReportMap[] = {
+    0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
+    0x09, 0x02,        // Usage (Mouse)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x01,        //   Report ID (1)
+    0x09, 0x01,        //   Usage (Pointer)
+    0xA1, 0x00,        //   Collection (Physical)
+    0x05, 0x09,        //     Usage Page (Button)
+    0x19, 0x01,        //     Usage Minimum (0x01)
+    0x29, 0x03,        //     Usage Maximum (0x03)
+    0x15, 0x00,        //     Logical Minimum (0)
+    0x25, 0x01,        //     Logical Maximum (1)
+    0x75, 0x01,        //     Report Size (1)
+    0x95, 0x03,        //     Report Count (3)
+    0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x75, 0x05,        //     Report Size (5)
+    0x95, 0x01,        //     Report Count (1)
+    0x81, 0x01,        //     Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
+    0x09, 0x30,        //     Usage (X)
+    0x09, 0x31,        //     Usage (Y)
+    0x09, 0x38,        //     Usage (Wheel)
+    0x15, 0x81,        //     Logical Minimum (-127)
+    0x25, 0x7F,        //     Logical Maximum (127)
+    0x75, 0x08,        //     Report Size (8)
+    0x95, 0x03,        //     Report Count (3)
+    0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              //   End Collection
+    0xC0,              // End Collection
+
+    0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
+    0x09, 0x06,        // Usage (Keyboard)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x02,        //   Report ID (2)
+    0x05, 0x07,        //   Usage Page (Kbrd/Keypad)
+    0x19, 0xE0,        //   Usage Minimum (0xE0)
+    0x29, 0xE7,        //   Usage Maximum (0xE7)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x25, 0x01,        //   Logical Maximum (1)
+    0x75, 0x01,        //   Report Size (1)
+    0x95, 0x08,        //   Report Count (8)
+    0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x95, 0x01,        //   Report Count (1)
+    0x75, 0x08,        //   Report Size (8)
+    0x81, 0x01,        //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x95, 0x05,        //   Report Count (5)
+    0x75, 0x01,        //   Report Size (1)
+    0x05, 0x08,        //   Usage Page (LEDs)
+    0x19, 0x01,        //   Usage Minimum (Num Lock)
+    0x29, 0x05,        //   Usage Maximum (Kana)
+    0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+    0x95, 0x01,        //   Report Count (1)
+    0x75, 0x03,        //   Report Size (3)
+    0x91, 0x01,        //   Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+    0x95, 0x06,        //   Report Count (6)
+    0x75, 0x08,        //   Report Size (8)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x25, 0x65,        //   Logical Maximum (101)
+    0x05, 0x07,        //   Usage Page (Kbrd/Keypad)
+    0x19, 0x00,        //   Usage Minimum (0x00)
+    0x29, 0x65,        //   Usage Maximum (0x65)
+    0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              // End Collection
+
+    0x05, 0x0C,        // Usage Page (Consumer)
+    0x09, 0x01,        // Usage (Consumer Control)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x03,        //   Report ID (3)
+    0x09, 0x02,        //   Usage (Numeric Key Pad)
+    0xA1, 0x02,        //   Collection (Logical)
+    0x05, 0x09,        //     Usage Page (Button)
+    0x19, 0x01,        //     Usage Minimum (0x01)
+    0x29, 0x0A,        //     Usage Maximum (0x0A)
+    0x15, 0x01,        //     Logical Minimum (1)
+    0x25, 0x0A,        //     Logical Maximum (10)
+    0x75, 0x04,        //     Report Size (4)
+    0x95, 0x01,        //     Report Count (1)
+    0x81, 0x00,        //     Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              //   End Collection
+    0x05, 0x0C,        //   Usage Page (Consumer)
+    0x09, 0x86,        //   Usage (Channel)
+    0x15, 0xFF,        //   Logical Minimum (-1)
+    0x25, 0x01,        //   Logical Maximum (1)
+    0x75, 0x02,        //   Report Size (2)
+    0x95, 0x01,        //   Report Count (1)
+    0x81, 0x46,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,Null State)
+    0x09, 0xE9,        //   Usage (Volume Increment)
+    0x09, 0xEA,        //   Usage (Volume Decrement)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x75, 0x01,        //   Report Size (1)
+    0x95, 0x02,        //   Report Count (2)
+    0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x09, 0xE2,        //   Usage (Mute)
+    0x09, 0x30,        //   Usage (Power)
+    0x09, 0x83,        //   Usage (Recall Last)
+    0x09, 0x81,        //   Usage (Assign Selection)
+    0x09, 0xB0,        //   Usage (Play)
+    0x09, 0xB1,        //   Usage (Pause)
+    0x09, 0xB2,        //   Usage (Record)
+    0x09, 0xB3,        //   Usage (Fast Forward)
+    0x09, 0xB4,        //   Usage (Rewind)
+    0x09, 0xB5,        //   Usage (Scan Next Track)
+    0x09, 0xB6,        //   Usage (Scan Previous Track)
+    0x09, 0xB7,        //   Usage (Stop)
+    0x15, 0x01,        //   Logical Minimum (1)
+    0x25, 0x0C,        //   Logical Maximum (12)
+    0x75, 0x04,        //   Report Size (4)
+    0x95, 0x01,        //   Report Count (1)
+    0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x09, 0x80,        //   Usage (Selection)
+    0xA1, 0x02,        //   Collection (Logical)
+    0x05, 0x09,        //     Usage Page (Button)
+    0x19, 0x01,        //     Usage Minimum (0x01)
+    0x29, 0x03,        //     Usage Maximum (0x03)
+    0x15, 0x01,        //     Logical Minimum (1)
+    0x25, 0x03,        //     Logical Maximum (3)
+    0x75, 0x02,        //     Report Size (2)
+    0x81, 0x00,        //     Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              //   End Collection
+    0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              // End Collection
+
+    0x06, 0xFF, 0xFF,  // Usage Page (Vendor Defined 0xFFFF)
+    0x09, 0xA5,        // Usage (0xA5)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x04,        //   Report ID (4)
+    0x09, 0xA6,        //   Usage (0xA6)
+    0x09, 0xA9,        //   Usage (0xA9)
+    0x75, 0x08,        //   Report Size (8)
+    0x95, 0x7F,        //   Report Count (127)
+    0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+    0xC0,              // End Collection
+
+    // 250 bytes
+};
+
+const unsigned char relMouseReportMap[] = { //4 bytes (btns,x,y,wheel)
+    0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
+    0x09, 0x02,        // Usage (Mouse)
+    0xA1, 0x01,        // Collection (Application)
+    0x09, 0x01,        //   Usage (Pointer)
+    0xA1, 0x00,        //   Collection (Physical)
+    0x85, 0x01,        //     Report ID (1)
+    0x05, 0x09,        //     Usage Page (Button)
+    0x19, 0x01,        //     Usage Minimum (0x01)
+    0x29, 0x05,        //     Usage Maximum (0x05)
+    0x15, 0x00,        //     Logical Minimum (0)
+    0x25, 0x01,        //     Logical Maximum (1)
+    0x95, 0x05,        //     Report Count (5)
+    0x75, 0x01,        //     Report Size (1)
+    0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x95, 0x01,        //     Report Count (1)
+    0x75, 0x03,        //     Report Size (3)
+    0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
+    0x09, 0x30,        //     Usage (X)
+    0x09, 0x31,        //     Usage (Y)
+    0x09, 0x38,        //     Usage (Wheel)
+    0x15, 0x81,        //     Logical Minimum (-127)
+    0x25, 0x7F,        //     Logical Maximum (127)
+    0x75, 0x08,        //     Report Size (8)
+    0x95, 0x03,        //     Report Count (3)
+    0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              //   End Collection
+    0xC0,              // End Collection
+
+    // 54 bytes
+};
+
+const unsigned char absMouseReportMap[] = { //6 bytes (btns,x*2,y*2,wheel)
+    0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
+    0x09, 0x02,        // Usage (Mouse)
+    0xA1, 0x01,        // Collection (Application)
+    0x09, 0x01,        //   Usage (Pointer)
+    0xA1, 0x00,        //   Collection (Physical)
+    0x85, 0x01,        //     Report ID (1)
+    0x05, 0x09,        //     Usage Page (Button)
+    0x19, 0x01,        //     Usage Minimum (0x01)
+    0x29, 0x05,        //     Usage Maximum (0x05)
+    0x15, 0x00,        //     Logical Minimum (0)
+    0x25, 0x01,        //     Logical Maximum (1)
+    0x95, 0x05,        //     Report Count (5)
+    0x75, 0x01,        //     Report Size (1)
+    0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x95, 0x01,        //     Report Count (1)
+    0x75, 0x03,        //     Report Size (3)
+    0x81, 0x01,        //     Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
+    0x09, 0x30,        //     Usage (X)
+    0x09, 0x31,        //     Usage (Y)
+    0x15, 0x00,        //     Logical Minimum (0)
+    0x26, 0xFF, 0x7F,  //     Logical Maximum (32767)
+    0x75, 0x10,        //     Report Size (16)
+    0x95, 0x02,        //     Report Count (2)
+    0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
+    0x09, 0x38,        //     Usage (Wheel)
+    0x15, 0x81,        //     Logical Minimum (-127)
+    0x25, 0x7F,        //     Logical Maximum (127)
+    0x75, 0x08,        //     Report Size (8)
+    0x95, 0x01,        //     Report Count (1)
+    0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              //   End Collection
+    0xC0,              // End Collection
+
+    // 67 bytes
+};
+
+const unsigned char keyboardReportMap[] = { //7 bytes input (modifiers, resrvd, keys*5), 1 byte output
+    0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
+    0x09, 0x06,        // Usage (Keyboard)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x01,        //   Report ID (1)
+    0x05, 0x07,        //   Usage Page (Kbrd/Keypad)
+    0x19, 0xE0,        //   Usage Minimum (0xE0)
+    0x29, 0xE7,        //   Usage Maximum (0xE7)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x25, 0x01,        //   Logical Maximum (1)
+    0x75, 0x01,        //   Report Size (1)
+    0x95, 0x08,        //   Report Count (8)
+    0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x95, 0x01,        //   Report Count (1)
+    0x75, 0x08,        //   Report Size (8)
+    0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x95, 0x05,        //   Report Count (5)
+    0x75, 0x01,        //   Report Size (1)
+    0x05, 0x08,        //   Usage Page (LEDs)
+    0x19, 0x01,        //   Usage Minimum (Num Lock)
+    0x29, 0x05,        //   Usage Maximum (Kana)
+    0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+    0x95, 0x01,        //   Report Count (1)
+    0x75, 0x03,        //   Report Size (3)
+    0x91, 0x03,        //   Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+    0x95, 0x05,        //   Report Count (5)
+    0x75, 0x08,        //   Report Size (8)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x25, 0x65,        //   Logical Maximum (101)
+    0x05, 0x07,        //   Usage Page (Kbrd/Keypad)
+    0x19, 0x00,        //   Usage Minimum (0x00)
+    0x29, 0x65,        //   Usage Maximum (0x65)
+    0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              // End Collection
+
+    // 65 bytes
+};
+
+const unsigned char joystickReportMap[] = { // 8bytes (8but, 4but+hat, r, y, rx, ry, z, rz)
+    0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
+    0x09, 0x04,        // Usage (Joystick)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x01,        //   Report ID (1)
+    0x05, 0x09,        //   Usage Page (Button)
+    0x19, 0x01,        //   Usage Minimum (0x01)
+    0x29, 0x0C,        //   Usage Maximum (0x0C)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x25, 0x01,        //   Logical Maximum (1)
+    0x75, 0x01,        //   Report Size (1)
+    0x95, 0x0C,        //   Report Count (12)
+    0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
+    0x09, 0x39,        //   Usage (Hat switch)
+    0x15, 0x01,        //   Logical Minimum (1)
+    0x25, 0x08,        //   Logical Maximum (8)
+    0x95, 0x01,        //   Report Count (1)
+    0x75, 0x04,        //   Report Size (4)
+    0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
+    0xA1, 0x00,        //   Collection (Physical)
+    0x09, 0x30,        //     Usage (X)
+    0x09, 0x31,        //     Usage (Y)
+    0x09, 0x33,        //     Usage (Rx)
+    0x09, 0x34,        //     Usage (Ry)
+    0x15, 0x80,        //     Logical Minimum (-128)
+    0x25, 0x7F,        //     Logical Maximum (127)
+    0x75, 0x08,        //     Report Size (8)
+    0x95, 0x04,        //     Report Count (4)
+    0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x09, 0x32,        //     Usage (Z)
+    0x09, 0x35,        //     Usage (Rz)
+    0x15, 0x80,        //     Logical Minimum (-128)
+    0x25, 0x7F,        //     Logical Maximum (127)
+    0x75, 0x08,        //     Report Size (8)
+    0x95, 0x02,        //     Report Count (2)
+    0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              //   End Collection
+    0xC0,              // End Collection
+
+    // 76 bytes
+};
+
+const unsigned char mediaReportMap[] = { //6 bytes (3 x 16bit)
+    0x05, 0x0C,        // Usage Page (Consumer)
+    0x09, 0x01,        // Usage (Consumer Control)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x03,        //   Report ID (3)
+    0x19, 0x00,        //   Usage Minimum (Unassigned)
+    0x2A, 0xFF, 0x03,  //   Usage Maximum (0x03FF)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x26, 0xFF, 0x03,  //   Logical Maximum (1023)
+    0x95, 0x03,        //   Report Count (3)
+    0x75, 0x10,        //   Report Size (16)
+    0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              // End Collection
+
+    // 25 bytes
+};
+
+const unsigned char mediaReportMap2[] = {
+    0x05, 0x0C,        // Usage Page (Consumer)
+    0x09, 0x01,        // Usage (Consumer Control)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x03,        //   Report ID (3)
+    0x09, 0x02,        //   Usage (Numeric Key Pad)
+    0xA1, 0x02,        //   Collection (Logical)
+    0x05, 0x09,        //     Usage Page (Button)
+    0x19, 0x01,        //     Usage Minimum (0x01)
+    0x29, 0x0A,        //     Usage Maximum (0x0A)
+    0x15, 0x01,        //     Logical Minimum (1)
+    0x25, 0x0A,        //     Logical Maximum (10)
+    0x75, 0x04,        //     Report Size (4)
+    0x95, 0x01,        //     Report Count (1)
+    0x81, 0x00,        //     Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              //   End Collection
+    0x05, 0x0C,        //   Usage Page (Consumer)
+    0x09, 0x86,        //   Usage (Channel)
+    0x15, 0xFF,        //   Logical Minimum (-1)
+    0x25, 0x01,        //   Logical Maximum (1)
+    0x75, 0x02,        //   Report Size (2)
+    0x95, 0x01,        //   Report Count (1)
+    0x81, 0x46,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,Null State)
+    0x09, 0xE9,        //   Usage (Volume Increment)
+    0x09, 0xEA,        //   Usage (Volume Decrement)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x75, 0x01,        //   Report Size (1)
+    0x95, 0x02,        //   Report Count (2)
+    0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x09, 0xE2,        //   Usage (Mute)
+    0x09, 0x30,        //   Usage (Power)
+    0x09, 0x83,        //   Usage (Recall Last)
+    0x09, 0x81,        //   Usage (Assign Selection)
+    0x09, 0xB0,        //   Usage (Play)
+    0x09, 0xB1,        //   Usage (Pause)
+    0x09, 0xB2,        //   Usage (Record)
+    0x09, 0xB3,        //   Usage (Fast Forward)
+    0x09, 0xB4,        //   Usage (Rewind)
+    0x09, 0xB5,        //   Usage (Scan Next Track)
+    0x09, 0xB6,        //   Usage (Scan Previous Track)
+    0x09, 0xB7,        //   Usage (Stop)
+    0x15, 0x01,        //   Logical Minimum (1)
+    0x25, 0x0C,        //   Logical Maximum (12)
+    0x75, 0x04,        //   Report Size (4)
+    0x95, 0x01,        //   Report Count (1)
+    0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x09, 0x80,        //   Usage (Selection)
+    0xA1, 0x02,        //   Collection (Logical)
+    0x05, 0x09,        //     Usage Page (Button)
+    0x19, 0x01,        //     Usage Minimum (0x01)
+    0x29, 0x03,        //     Usage Maximum (0x03)
+    0x15, 0x01,        //     Logical Minimum (1)
+    0x25, 0x03,        //     Logical Maximum (3)
+    0x75, 0x02,        //     Report Size (2)
+    0x81, 0x00,        //     Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              //   End Collection
+    0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              // End Collection
+};
+
+const unsigned char hidapiReportMap[] = { //8 bytes input, 8 bytes feature
+    0x06, 0x00, 0xFF,  // Usage Page (Vendor Defined 0xFF00)
+    0x0A, 0x00, 0x01,  // Usage (0x0100)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x01,        //   Report ID (1)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x26, 0xFF, 0x00,  //   Logical Maximum (255)
+    0x75, 0x08,        //   Report Size (8)
+    0x95, 0x08,        //   Report Count (8)
+    0x09, 0x01,        //   Usage (0x01)
+    0x82, 0x02, 0x01,  //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes)
+    0x95, 0x08,        //   Report Count (8)
+    0x09, 0x02,        //   Usage (0x02)
+    0xB2, 0x02, 0x01,  //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile,Buffered Bytes)
+    0x95, 0x08,        //   Report Count (8)
+    0x09, 0x03,        //   Usage (0x03)
+    0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+    0xC0,              // End Collection
+
+    // 38 bytes
+};

+ 214 - 0
components/esp_hid/test/test_esp_hid.c

@@ -0,0 +1,214 @@
+// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/unistd.h>
+#include "unity.h"
+#include "unity_test_runner.h"
+#include "test_utils.h"
+#include "esp_log.h"
+#include "esp_hid_common.h"
+#include "hid_descriptor.h"
+
+TEST_CASE("can parse hidReportMap", "[esp_hid]")
+{
+    esp_hid_report_map_t * report_map = esp_hid_parse_report_map(hidReportMap, sizeof(hidReportMap));
+    TEST_ASSERT_NOT_NULL(report_map);
+    TEST_ASSERT(report_map->usage == ESP_HID_USAGE_KEYBOARD);
+    TEST_ASSERT(report_map->appearance == 0x03C1);
+    TEST_ASSERT(report_map->reports_len == 8);
+    TEST_ASSERT(report_map->reports[0].report_id == 1);
+    TEST_ASSERT(report_map->reports[0].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[0].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[0].usage == ESP_HID_USAGE_MOUSE);
+    TEST_ASSERT(report_map->reports[0].value_len == 4);
+    TEST_ASSERT(report_map->reports[1].report_id == 1);
+    TEST_ASSERT(report_map->reports[1].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[1].protocol_mode == ESP_HID_PROTOCOL_MODE_BOOT);
+    TEST_ASSERT(report_map->reports[1].usage == ESP_HID_USAGE_MOUSE);
+    TEST_ASSERT(report_map->reports[1].value_len == 4);
+    TEST_ASSERT(report_map->reports[2].report_id == 2);
+    TEST_ASSERT(report_map->reports[2].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[2].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[2].usage == ESP_HID_USAGE_KEYBOARD);
+    TEST_ASSERT(report_map->reports[2].value_len == 8);
+    TEST_ASSERT(report_map->reports[3].report_id == 2);
+    TEST_ASSERT(report_map->reports[3].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[3].protocol_mode == ESP_HID_PROTOCOL_MODE_BOOT);
+    TEST_ASSERT(report_map->reports[3].usage == ESP_HID_USAGE_KEYBOARD);
+    TEST_ASSERT(report_map->reports[3].value_len == 8);
+    TEST_ASSERT(report_map->reports[4].report_id == 2);
+    TEST_ASSERT(report_map->reports[4].report_type == ESP_HID_REPORT_TYPE_OUTPUT);
+    TEST_ASSERT(report_map->reports[4].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[4].usage == ESP_HID_USAGE_KEYBOARD);
+    TEST_ASSERT(report_map->reports[4].value_len == 1);
+    TEST_ASSERT(report_map->reports[5].report_id == 2);
+    TEST_ASSERT(report_map->reports[5].report_type == ESP_HID_REPORT_TYPE_OUTPUT);
+    TEST_ASSERT(report_map->reports[5].protocol_mode == ESP_HID_PROTOCOL_MODE_BOOT);
+    TEST_ASSERT(report_map->reports[5].usage == ESP_HID_USAGE_KEYBOARD);
+    TEST_ASSERT(report_map->reports[5].value_len == 1);
+    TEST_ASSERT(report_map->reports[6].report_id == 3);
+    TEST_ASSERT(report_map->reports[6].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[6].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[6].usage == ESP_HID_USAGE_CCONTROL);
+    TEST_ASSERT(report_map->reports[6].value_len == 2);
+    TEST_ASSERT(report_map->reports[7].report_id == 4);
+    TEST_ASSERT(report_map->reports[7].report_type == ESP_HID_REPORT_TYPE_OUTPUT);
+    TEST_ASSERT(report_map->reports[7].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[7].usage == ESP_HID_USAGE_VENDOR);
+    TEST_ASSERT(report_map->reports[7].value_len == 127);
+    esp_hid_free_report_map(report_map);
+}
+
+TEST_CASE("can parse relMouseReportMap", "[esp_hid]")
+{
+    esp_hid_report_map_t * report_map = esp_hid_parse_report_map(relMouseReportMap, sizeof(relMouseReportMap));
+    TEST_ASSERT_NOT_NULL(report_map);
+    TEST_ASSERT(report_map->usage == ESP_HID_USAGE_MOUSE);
+    TEST_ASSERT(report_map->appearance == 0x03C2);
+    TEST_ASSERT(report_map->reports_len == 2);
+    TEST_ASSERT(report_map->reports[0].report_id == 1);
+    TEST_ASSERT(report_map->reports[0].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[0].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[0].usage == ESP_HID_USAGE_MOUSE);
+    TEST_ASSERT(report_map->reports[0].value_len == 4);
+    TEST_ASSERT(report_map->reports[1].report_id == 1);
+    TEST_ASSERT(report_map->reports[1].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[1].protocol_mode == ESP_HID_PROTOCOL_MODE_BOOT);
+    TEST_ASSERT(report_map->reports[1].usage == ESP_HID_USAGE_MOUSE);
+    TEST_ASSERT(report_map->reports[1].value_len == 4);
+    esp_hid_free_report_map(report_map);
+}
+
+TEST_CASE("can parse absMouseReportMap", "[esp_hid]")
+{
+    esp_hid_report_map_t * report_map = esp_hid_parse_report_map(absMouseReportMap, sizeof(absMouseReportMap));
+    TEST_ASSERT_NOT_NULL(report_map);
+    TEST_ASSERT(report_map->usage == ESP_HID_USAGE_MOUSE);
+    TEST_ASSERT(report_map->appearance == 0x03C2);
+    TEST_ASSERT(report_map->reports_len == 2);
+    TEST_ASSERT(report_map->reports[0].report_id == 1);
+    TEST_ASSERT(report_map->reports[0].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[0].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[0].usage == ESP_HID_USAGE_MOUSE);
+    TEST_ASSERT(report_map->reports[0].value_len == 6);
+    TEST_ASSERT(report_map->reports[1].report_id == 1);
+    TEST_ASSERT(report_map->reports[1].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[1].protocol_mode == ESP_HID_PROTOCOL_MODE_BOOT);
+    TEST_ASSERT(report_map->reports[1].usage == ESP_HID_USAGE_MOUSE);
+    TEST_ASSERT(report_map->reports[1].value_len == 4);
+    esp_hid_free_report_map(report_map);
+}
+
+TEST_CASE("can parse keyboardReportMap", "[esp_hid]")
+{
+    esp_hid_report_map_t * report_map = esp_hid_parse_report_map(keyboardReportMap, sizeof(keyboardReportMap));
+    TEST_ASSERT_NOT_NULL(report_map);
+    TEST_ASSERT(report_map->usage == ESP_HID_USAGE_KEYBOARD);
+    TEST_ASSERT(report_map->appearance == 0x03C1);
+    TEST_ASSERT(report_map->reports_len == 4);
+    TEST_ASSERT(report_map->reports[0].report_id == 1);
+    TEST_ASSERT(report_map->reports[0].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[0].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[0].usage == ESP_HID_USAGE_KEYBOARD);
+    TEST_ASSERT(report_map->reports[0].value_len == 7);
+    TEST_ASSERT(report_map->reports[1].report_id == 1);
+    TEST_ASSERT(report_map->reports[1].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[1].protocol_mode == ESP_HID_PROTOCOL_MODE_BOOT);
+    TEST_ASSERT(report_map->reports[1].usage == ESP_HID_USAGE_KEYBOARD);
+    TEST_ASSERT(report_map->reports[1].value_len == 8);
+    TEST_ASSERT(report_map->reports[2].report_id == 1);
+    TEST_ASSERT(report_map->reports[2].report_type == ESP_HID_REPORT_TYPE_OUTPUT);
+    TEST_ASSERT(report_map->reports[2].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[2].usage == ESP_HID_USAGE_KEYBOARD);
+    TEST_ASSERT(report_map->reports[2].value_len == 1);
+    TEST_ASSERT(report_map->reports[3].report_id == 1);
+    TEST_ASSERT(report_map->reports[3].report_type == ESP_HID_REPORT_TYPE_OUTPUT);
+    TEST_ASSERT(report_map->reports[3].protocol_mode == ESP_HID_PROTOCOL_MODE_BOOT);
+    TEST_ASSERT(report_map->reports[3].usage == ESP_HID_USAGE_KEYBOARD);
+    TEST_ASSERT(report_map->reports[3].value_len == 1);
+    esp_hid_free_report_map(report_map);
+}
+
+TEST_CASE("can parse joystickReportMap", "[esp_hid]")
+{
+    esp_hid_report_map_t * report_map = esp_hid_parse_report_map(joystickReportMap, sizeof(joystickReportMap));
+    TEST_ASSERT_NOT_NULL(report_map);
+    TEST_ASSERT(report_map->usage == ESP_HID_USAGE_JOYSTICK);
+    TEST_ASSERT(report_map->appearance == 0x03C3);
+    TEST_ASSERT(report_map->reports_len == 1);
+    TEST_ASSERT(report_map->reports[0].report_id == 1);
+    TEST_ASSERT(report_map->reports[0].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[0].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[0].usage == ESP_HID_USAGE_JOYSTICK);
+    TEST_ASSERT(report_map->reports[0].value_len == 8);
+    esp_hid_free_report_map(report_map);
+}
+
+TEST_CASE("can parse mediaReportMap", "[esp_hid]")
+{
+    esp_hid_report_map_t * report_map = esp_hid_parse_report_map(mediaReportMap, sizeof(mediaReportMap));
+    TEST_ASSERT_NOT_NULL(report_map);
+    TEST_ASSERT(report_map->usage == ESP_HID_USAGE_CCONTROL);
+    TEST_ASSERT(report_map->appearance == 0x03C1);
+    TEST_ASSERT(report_map->reports_len == 1);
+    TEST_ASSERT(report_map->reports[0].report_id == 3);
+    TEST_ASSERT(report_map->reports[0].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[0].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[0].usage == ESP_HID_USAGE_CCONTROL);
+    TEST_ASSERT(report_map->reports[0].value_len == 6);
+    esp_hid_free_report_map(report_map);
+}
+
+TEST_CASE("can parse mediaReportMap2", "[esp_hid]")
+{
+    esp_hid_report_map_t * report_map = esp_hid_parse_report_map(mediaReportMap2, sizeof(mediaReportMap2));
+    TEST_ASSERT_NOT_NULL(report_map);
+    TEST_ASSERT(report_map->usage == ESP_HID_USAGE_CCONTROL);
+    TEST_ASSERT(report_map->appearance == 0x03C1);
+    TEST_ASSERT(report_map->reports_len == 1);
+    TEST_ASSERT(report_map->reports[0].report_id == 3);
+    TEST_ASSERT(report_map->reports[0].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[0].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[0].usage == ESP_HID_USAGE_CCONTROL);
+    TEST_ASSERT(report_map->reports[0].value_len == 2);
+    esp_hid_free_report_map(report_map);
+}
+
+TEST_CASE("can parse hidapiReportMap", "[esp_hid]")
+{
+    esp_hid_report_map_t * report_map = esp_hid_parse_report_map(hidapiReportMap, sizeof(hidapiReportMap));
+    TEST_ASSERT_NOT_NULL(report_map);
+    TEST_ASSERT(report_map->usage == ESP_HID_USAGE_GENERIC);
+    TEST_ASSERT(report_map->appearance == 0x03C0);
+    TEST_ASSERT(report_map->reports_len == 3);
+    TEST_ASSERT(report_map->reports[0].report_id == 1);
+    TEST_ASSERT(report_map->reports[0].report_type == ESP_HID_REPORT_TYPE_INPUT);
+    TEST_ASSERT(report_map->reports[0].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[0].usage == ESP_HID_USAGE_VENDOR);
+    TEST_ASSERT(report_map->reports[0].value_len == 8);
+    TEST_ASSERT(report_map->reports[1].report_id == 1);
+    TEST_ASSERT(report_map->reports[1].report_type == ESP_HID_REPORT_TYPE_OUTPUT);
+    TEST_ASSERT(report_map->reports[1].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[1].usage == ESP_HID_USAGE_VENDOR);
+    TEST_ASSERT(report_map->reports[1].value_len == 8);
+    TEST_ASSERT(report_map->reports[2].report_id == 1);
+    TEST_ASSERT(report_map->reports[2].report_type == ESP_HID_REPORT_TYPE_FEATURE);
+    TEST_ASSERT(report_map->reports[2].protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT);
+    TEST_ASSERT(report_map->reports[2].usage == ESP_HID_USAGE_VENDOR);
+    TEST_ASSERT(report_map->reports[2].value_len == 8);
+    esp_hid_free_report_map(report_map);
+}

+ 7 - 0
examples/bluetooth/esp_hid_device/CMakeLists.txt

@@ -0,0 +1,7 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+set(SUPPORTED_TARGETS esp32)
+project(esp_hid_device)

+ 8 - 0
examples/bluetooth/esp_hid_device/Makefile

@@ -0,0 +1,8 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := esp_hid_device
+
+include $(IDF_PATH)/make/project.mk

+ 5 - 0
examples/bluetooth/esp_hid_device/main/CMakeLists.txt

@@ -0,0 +1,5 @@
+set(COMPONENT_SRCS "esp_hid_device_main.c"
+                   "esp_hid_gap.c")
+set(COMPONENT_ADD_INCLUDEDIRS ".")
+
+register_component()

+ 3 - 0
examples/bluetooth/esp_hid_device/main/component.mk

@@ -0,0 +1,3 @@
+#
+# Main Makefile. This is basically the same as a component makefile.
+#

+ 385 - 0
examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c

@@ -0,0 +1,385 @@
+/* This example code is in the Public Domain (or CC0 licensed, at your option.)
+   Unless required by applicable law or agreed to in writing, this software is
+   distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_system.h"
+#include "esp_wifi.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "nvs_flash.h"
+#include "esp_bt.h"
+#include "esp_bt_defs.h"
+#include "esp_gap_ble_api.h"
+#include "esp_gatts_api.h"
+#include "esp_gatt_defs.h"
+#include "esp_bt_main.h"
+#include "esp_bt_device.h"
+
+#include "esp_hidd.h"
+#include "esp_hid_gap.h"
+
+static const char *TAG = "HID_DEV_DEMO";
+
+const unsigned char hidapiReportMap[] = { //8 bytes input, 8 bytes feature
+    0x06, 0x00, 0xFF,  // Usage Page (Vendor Defined 0xFF00)
+    0x0A, 0x00, 0x01,  // Usage (0x0100)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x01,        //   Report ID (1)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x26, 0xFF, 0x00,  //   Logical Maximum (255)
+    0x75, 0x08,        //   Report Size (8)
+    0x95, 0x08,        //   Report Count (8)
+    0x09, 0x01,        //   Usage (0x01)
+    0x82, 0x02, 0x01,  //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes)
+    0x95, 0x08,        //   Report Count (8)
+    0x09, 0x02,        //   Usage (0x02)
+    0xB2, 0x02, 0x01,  //   Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile,Buffered Bytes)
+    0x95, 0x08,        //   Report Count (8)
+    0x09, 0x03,        //   Usage (0x03)
+    0x91, 0x02,        //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+    0xC0,              // End Collection
+
+    // 38 bytes
+};
+
+const unsigned char mediaReportMap[] = {
+    0x05, 0x0C,        // Usage Page (Consumer)
+    0x09, 0x01,        // Usage (Consumer Control)
+    0xA1, 0x01,        // Collection (Application)
+    0x85, 0x03,        //   Report ID (3)
+    0x09, 0x02,        //   Usage (Numeric Key Pad)
+    0xA1, 0x02,        //   Collection (Logical)
+    0x05, 0x09,        //     Usage Page (Button)
+    0x19, 0x01,        //     Usage Minimum (0x01)
+    0x29, 0x0A,        //     Usage Maximum (0x0A)
+    0x15, 0x01,        //     Logical Minimum (1)
+    0x25, 0x0A,        //     Logical Maximum (10)
+    0x75, 0x04,        //     Report Size (4)
+    0x95, 0x01,        //     Report Count (1)
+    0x81, 0x00,        //     Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              //   End Collection
+    0x05, 0x0C,        //   Usage Page (Consumer)
+    0x09, 0x86,        //   Usage (Channel)
+    0x15, 0xFF,        //   Logical Minimum (-1)
+    0x25, 0x01,        //   Logical Maximum (1)
+    0x75, 0x02,        //   Report Size (2)
+    0x95, 0x01,        //   Report Count (1)
+    0x81, 0x46,        //   Input (Data,Var,Rel,No Wrap,Linear,Preferred State,Null State)
+    0x09, 0xE9,        //   Usage (Volume Increment)
+    0x09, 0xEA,        //   Usage (Volume Decrement)
+    0x15, 0x00,        //   Logical Minimum (0)
+    0x75, 0x01,        //   Report Size (1)
+    0x95, 0x02,        //   Report Count (2)
+    0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x09, 0xE2,        //   Usage (Mute)
+    0x09, 0x30,        //   Usage (Power)
+    0x09, 0x83,        //   Usage (Recall Last)
+    0x09, 0x81,        //   Usage (Assign Selection)
+    0x09, 0xB0,        //   Usage (Play)
+    0x09, 0xB1,        //   Usage (Pause)
+    0x09, 0xB2,        //   Usage (Record)
+    0x09, 0xB3,        //   Usage (Fast Forward)
+    0x09, 0xB4,        //   Usage (Rewind)
+    0x09, 0xB5,        //   Usage (Scan Next Track)
+    0x09, 0xB6,        //   Usage (Scan Previous Track)
+    0x09, 0xB7,        //   Usage (Stop)
+    0x15, 0x01,        //   Logical Minimum (1)
+    0x25, 0x0C,        //   Logical Maximum (12)
+    0x75, 0x04,        //   Report Size (4)
+    0x95, 0x01,        //   Report Count (1)
+    0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0x09, 0x80,        //   Usage (Selection)
+    0xA1, 0x02,        //   Collection (Logical)
+    0x05, 0x09,        //     Usage Page (Button)
+    0x19, 0x01,        //     Usage Minimum (0x01)
+    0x29, 0x03,        //     Usage Maximum (0x03)
+    0x15, 0x01,        //     Logical Minimum (1)
+    0x25, 0x03,        //     Logical Maximum (3)
+    0x75, 0x02,        //     Report Size (2)
+    0x81, 0x00,        //     Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              //   End Collection
+    0x81, 0x03,        //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+    0xC0,              // End Collection
+};
+
+static esp_hid_raw_report_map_t report_maps[] = {
+    {
+        .data = hidapiReportMap,
+        .len = sizeof(hidapiReportMap)
+    },
+    {
+        .data = mediaReportMap,
+        .len = sizeof(mediaReportMap)
+    }
+};
+
+static esp_hid_device_config_t hid_config = {
+    .vendor_id          = 0x16C0,
+    .product_id         = 0x05DF,
+    .version            = 0x0100,
+    .device_name        = "ESP BLE HID2",
+    .manufacturer_name  = "Espressif",
+    .serial_number      = "1234567890",
+    .report_maps        = report_maps,
+    .report_maps_len    = 2
+};
+
+static esp_hidd_dev_t *hid_dev = NULL;
+static bool dev_connected = false;
+
+#define HID_CC_RPT_MUTE                 1
+#define HID_CC_RPT_POWER                2
+#define HID_CC_RPT_LAST                 3
+#define HID_CC_RPT_ASSIGN_SEL           4
+#define HID_CC_RPT_PLAY                 5
+#define HID_CC_RPT_PAUSE                6
+#define HID_CC_RPT_RECORD               7
+#define HID_CC_RPT_FAST_FWD             8
+#define HID_CC_RPT_REWIND               9
+#define HID_CC_RPT_SCAN_NEXT_TRK        10
+#define HID_CC_RPT_SCAN_PREV_TRK        11
+#define HID_CC_RPT_STOP                 12
+
+#define HID_CC_RPT_CHANNEL_UP           0x10
+#define HID_CC_RPT_CHANNEL_DOWN         0x30
+#define HID_CC_RPT_VOLUME_UP            0x40
+#define HID_CC_RPT_VOLUME_DOWN          0x80
+
+// HID Consumer Control report bitmasks
+#define HID_CC_RPT_NUMERIC_BITS         0xF0
+#define HID_CC_RPT_CHANNEL_BITS         0xCF
+#define HID_CC_RPT_VOLUME_BITS          0x3F
+#define HID_CC_RPT_BUTTON_BITS          0xF0
+#define HID_CC_RPT_SELECTION_BITS       0xCF
+
+// Macros for the HID Consumer Control 2-byte report
+#define HID_CC_RPT_SET_NUMERIC(s, x)    (s)[0] &= HID_CC_RPT_NUMERIC_BITS;   (s)[0] = (x)
+#define HID_CC_RPT_SET_CHANNEL(s, x)    (s)[0] &= HID_CC_RPT_CHANNEL_BITS;   (s)[0] |= ((x) & 0x03) << 4
+#define HID_CC_RPT_SET_VOLUME_UP(s)     (s)[0] &= HID_CC_RPT_VOLUME_BITS;    (s)[0] |= 0x40
+#define HID_CC_RPT_SET_VOLUME_DOWN(s)   (s)[0] &= HID_CC_RPT_VOLUME_BITS;    (s)[0] |= 0x80
+#define HID_CC_RPT_SET_BUTTON(s, x)     (s)[1] &= HID_CC_RPT_BUTTON_BITS;    (s)[1] |= (x)
+#define HID_CC_RPT_SET_SELECTION(s, x)  (s)[1] &= HID_CC_RPT_SELECTION_BITS; (s)[1] |= ((x) & 0x03) << 4
+
+// HID Consumer Usage IDs (subset of the codes available in the USB HID Usage Tables spec)
+#define HID_CONSUMER_POWER          48  // Power
+#define HID_CONSUMER_RESET          49  // Reset
+#define HID_CONSUMER_SLEEP          50  // Sleep
+
+#define HID_CONSUMER_MENU           64  // Menu
+#define HID_CONSUMER_SELECTION      128 // Selection
+#define HID_CONSUMER_ASSIGN_SEL     129 // Assign Selection
+#define HID_CONSUMER_MODE_STEP      130 // Mode Step
+#define HID_CONSUMER_RECALL_LAST    131 // Recall Last
+#define HID_CONSUMER_QUIT           148 // Quit
+#define HID_CONSUMER_HELP           149 // Help
+#define HID_CONSUMER_CHANNEL_UP     156 // Channel Increment
+#define HID_CONSUMER_CHANNEL_DOWN   157 // Channel Decrement
+
+#define HID_CONSUMER_PLAY           176 // Play
+#define HID_CONSUMER_PAUSE          177 // Pause
+#define HID_CONSUMER_RECORD         178 // Record
+#define HID_CONSUMER_FAST_FORWARD   179 // Fast Forward
+#define HID_CONSUMER_REWIND         180 // Rewind
+#define HID_CONSUMER_SCAN_NEXT_TRK  181 // Scan Next Track
+#define HID_CONSUMER_SCAN_PREV_TRK  182 // Scan Previous Track
+#define HID_CONSUMER_STOP           183 // Stop
+#define HID_CONSUMER_EJECT          184 // Eject
+#define HID_CONSUMER_RANDOM_PLAY    185 // Random Play
+#define HID_CONSUMER_SELECT_DISC    186 // Select Disk
+#define HID_CONSUMER_ENTER_DISC     187 // Enter Disc
+#define HID_CONSUMER_REPEAT         188 // Repeat
+#define HID_CONSUMER_STOP_EJECT     204 // Stop/Eject
+#define HID_CONSUMER_PLAY_PAUSE     205 // Play/Pause
+#define HID_CONSUMER_PLAY_SKIP      206 // Play/Skip
+
+#define HID_CONSUMER_VOLUME         224 // Volume
+#define HID_CONSUMER_BALANCE        225 // Balance
+#define HID_CONSUMER_MUTE           226 // Mute
+#define HID_CONSUMER_BASS           227 // Bass
+#define HID_CONSUMER_VOLUME_UP      233 // Volume Increment
+#define HID_CONSUMER_VOLUME_DOWN    234 // Volume Decrement
+
+#define HID_RPT_ID_CC_IN        3   // Consumer Control input report ID
+#define HID_CC_IN_RPT_LEN       2   // Consumer Control input report Len
+void esp_hidd_send_consumer_value(uint8_t key_cmd, bool key_pressed)
+{
+    uint8_t buffer[HID_CC_IN_RPT_LEN] = {0, 0};
+    if (key_pressed) {
+        switch (key_cmd) {
+        case HID_CONSUMER_CHANNEL_UP:
+            HID_CC_RPT_SET_CHANNEL(buffer, HID_CC_RPT_CHANNEL_UP);
+            break;
+
+        case HID_CONSUMER_CHANNEL_DOWN:
+            HID_CC_RPT_SET_CHANNEL(buffer, HID_CC_RPT_CHANNEL_DOWN);
+            break;
+
+        case HID_CONSUMER_VOLUME_UP:
+            HID_CC_RPT_SET_VOLUME_UP(buffer);
+            break;
+
+        case HID_CONSUMER_VOLUME_DOWN:
+            HID_CC_RPT_SET_VOLUME_DOWN(buffer);
+            break;
+
+        case HID_CONSUMER_MUTE:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_MUTE);
+            break;
+
+        case HID_CONSUMER_POWER:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_POWER);
+            break;
+
+        case HID_CONSUMER_RECALL_LAST:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_LAST);
+            break;
+
+        case HID_CONSUMER_ASSIGN_SEL:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_ASSIGN_SEL);
+            break;
+
+        case HID_CONSUMER_PLAY:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_PLAY);
+            break;
+
+        case HID_CONSUMER_PAUSE:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_PAUSE);
+            break;
+
+        case HID_CONSUMER_RECORD:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_RECORD);
+            break;
+
+        case HID_CONSUMER_FAST_FORWARD:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_FAST_FWD);
+            break;
+
+        case HID_CONSUMER_REWIND:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_REWIND);
+            break;
+
+        case HID_CONSUMER_SCAN_NEXT_TRK:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_SCAN_NEXT_TRK);
+            break;
+
+        case HID_CONSUMER_SCAN_PREV_TRK:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_SCAN_PREV_TRK);
+            break;
+
+        case HID_CONSUMER_STOP:
+            HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_STOP);
+            break;
+
+        default:
+            break;
+        }
+    }
+    esp_hidd_dev_input_set(hid_dev, 1, HID_RPT_ID_CC_IN, buffer, HID_CC_IN_RPT_LEN);
+    return;
+}
+
+static void hidd_event_callback(void *handler_args, esp_event_base_t base, int32_t id, void *event_data)
+{
+    esp_hidd_event_t event = (esp_hidd_event_t)id;
+    esp_hidd_event_data_t *param = (esp_hidd_event_data_t *)event_data;
+
+    switch (event) {
+    case ESP_HIDD_START_EVENT: {
+        ESP_LOGI(TAG, "START");
+        esp_hid_ble_gap_adv_start();
+        break;
+    }
+    case ESP_HIDD_CONNECT_EVENT: {
+        ESP_LOGI(TAG, "CONNECT");
+        dev_connected = true;//todo: this should be on auth_complete (in GAP)
+        break;
+    }
+    case ESP_HIDD_PROTOCOL_MODE_EVENT: {
+        ESP_LOGI(TAG, "PROTOCOL MODE[%u]: %s", param->protocol_mode.map_index, param->protocol_mode.protocol_mode ? "REPORT" : "BOOT");
+        break;
+    }
+    case ESP_HIDD_CONTROL_EVENT: {
+        ESP_LOGI(TAG, "CONTROL[%u]: %sSUSPEND", param->control.map_index, param->control.control ? "EXIT_" : "");
+        break;
+    }
+    case ESP_HIDD_OUTPUT_EVENT: {
+        ESP_LOGI(TAG, "OUTPUT[%u]: %8s ID: %2u, Len: %d, Data:", param->output.map_index, esp_hid_usage_str(param->output.usage), param->output.report_id, param->output.length);
+        ESP_LOG_BUFFER_HEX(TAG, param->output.data, param->output.length);
+        break;
+    }
+    case ESP_HIDD_FEATURE_EVENT: {
+        ESP_LOGI(TAG, "FEATURE[%u]: %8s ID: %2u, Len: %d, Data:", param->feature.map_index, esp_hid_usage_str(param->feature.usage), param->feature.report_id, param->feature.length);
+        ESP_LOG_BUFFER_HEX(TAG, param->feature.data, param->feature.length);
+        break;
+    }
+    case ESP_HIDD_DISCONNECT_EVENT: {
+        ESP_LOGI(TAG, "DISCONNECT: %s", esp_hid_disconnect_reason_str(esp_hidd_dev_transport_get(param->disconnect.dev), param->disconnect.reason));
+        dev_connected = false;
+        esp_hid_ble_gap_adv_start();
+        break;
+    }
+    case ESP_HIDD_STOP_EVENT: {
+        ESP_LOGI(TAG, "STOP");
+        break;
+    }
+    default:
+        break;
+    }
+    return;
+}
+
+void hid_demo_task(void *pvParameters)
+{
+    static bool send_volum_up = false;
+    while (1) {
+        if (dev_connected) {
+            ESP_LOGI(TAG, "Send the volume");
+            if (send_volum_up) {
+                esp_hidd_send_consumer_value(HID_CONSUMER_VOLUME_UP, true);
+                vTaskDelay(100 / portTICK_PERIOD_MS);
+                esp_hidd_send_consumer_value(HID_CONSUMER_VOLUME_UP, false);
+            } else {
+                esp_hidd_send_consumer_value(HID_CONSUMER_VOLUME_DOWN, true);
+                vTaskDelay(100 / portTICK_PERIOD_MS);
+                esp_hidd_send_consumer_value(HID_CONSUMER_VOLUME_DOWN, false);
+            }
+            send_volum_up = !send_volum_up;
+        }
+        vTaskDelay(2000 / portTICK_PERIOD_MS);
+    }
+}
+
+void app_main(void)
+{
+    esp_err_t ret;
+    ret = nvs_flash_init();
+    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
+        ESP_ERROR_CHECK(nvs_flash_erase());
+        ret = nvs_flash_init();
+    }
+    ESP_ERROR_CHECK( ret );
+
+    ret = esp_hid_gap_init(ESP_BT_MODE_BTDM);
+    ESP_ERROR_CHECK( ret );
+
+    ret = esp_hid_ble_gap_adv_init(ESP_HID_APPEARANCE_GENERIC, hid_config.device_name);
+    ESP_ERROR_CHECK( ret );
+
+    if ((ret = esp_ble_gatts_register_callback(esp_hidd_gatts_event_handler)) != ESP_OK) {
+        ESP_LOGE(TAG, "GATTS register callback failed: %d", ret);
+        return;
+    }
+
+    ESP_ERROR_CHECK( esp_hidd_dev_init(&hid_config, ESP_HID_TRANSPORT_BLE, hidd_event_callback, &hid_dev) );
+    xTaskCreate(&hid_demo_task, "hid_task", 2048, NULL, 2, NULL);
+
+}

+ 807 - 0
examples/bluetooth/esp_hid_device/main/esp_hid_gap.c

@@ -0,0 +1,807 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include <stdbool.h>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+
+#include "esp_hid_gap.h"
+
+static const char *TAG = "ESP_HID_GAP";
+
+// uncomment to print all devices that were seen during a scan
+#define GAP_DBG_PRINTF(...) //printf(__VA_ARGS__)
+//static const char * gap_bt_prop_type_names[5] = {"","BDNAME","COD","RSSI","EIR"};
+
+static esp_hid_scan_result_t *bt_scan_results = NULL;
+static size_t num_bt_scan_results = 0;
+
+static esp_hid_scan_result_t *ble_scan_results = NULL;
+static size_t num_ble_scan_results = 0;
+
+static xSemaphoreHandle bt_hidh_cb_semaphore = NULL;
+#define WAIT_BT_CB() xSemaphoreTake(bt_hidh_cb_semaphore, portMAX_DELAY)
+#define SEND_BT_CB() xSemaphoreGive(bt_hidh_cb_semaphore)
+
+static xSemaphoreHandle ble_hidh_cb_semaphore = NULL;
+#define WAIT_BLE_CB() xSemaphoreTake(ble_hidh_cb_semaphore, portMAX_DELAY)
+#define SEND_BLE_CB() xSemaphoreGive(ble_hidh_cb_semaphore)
+
+#define SIZEOF_ARRAY(a) (sizeof(a)/sizeof(*a))
+
+static const char *ble_gap_evt_names[] = { "ADV_DATA_SET_COMPLETE", "SCAN_RSP_DATA_SET_COMPLETE", "SCAN_PARAM_SET_COMPLETE", "SCAN_RESULT", "ADV_DATA_RAW_SET_COMPLETE", "SCAN_RSP_DATA_RAW_SET_COMPLETE", "ADV_START_COMPLETE", "SCAN_START_COMPLETE", "AUTH_CMPL", "KEY", "SEC_REQ", "PASSKEY_NOTIF", "PASSKEY_REQ", "OOB_REQ", "LOCAL_IR", "LOCAL_ER", "NC_REQ", "ADV_STOP_COMPLETE", "SCAN_STOP_COMPLETE", "SET_STATIC_RAND_ADDR", "UPDATE_CONN_PARAMS", "SET_PKT_LENGTH_COMPLETE", "SET_LOCAL_PRIVACY_COMPLETE", "REMOVE_BOND_DEV_COMPLETE", "CLEAR_BOND_DEV_COMPLETE", "GET_BOND_DEV_COMPLETE", "READ_RSSI_COMPLETE", "UPDATE_WHITELIST_COMPLETE"};
+static const char *bt_gap_evt_names[] = { "DISC_RES", "DISC_STATE_CHANGED", "RMT_SRVCS", "RMT_SRVC_REC", "AUTH_CMPL", "PIN_REQ", "CFM_REQ", "KEY_NOTIF", "KEY_REQ", "READ_RSSI_DELTA"};
+static const char *ble_addr_type_names[] = {"PUBLIC", "RANDOM", "RPA_PUBLIC", "RPA_RANDOM"};
+
+const char *ble_addr_type_str(esp_ble_addr_type_t ble_addr_type)
+{
+    if (ble_addr_type > BLE_ADDR_TYPE_RPA_RANDOM) {
+        return "UNKNOWN";
+    }
+    return ble_addr_type_names[ble_addr_type];
+}
+
+const char *ble_gap_evt_str(uint8_t event)
+{
+    if (event >= SIZEOF_ARRAY(ble_gap_evt_names)) {
+        return "UNKNOWN";
+    }
+    return ble_gap_evt_names[event];
+}
+
+const char *bt_gap_evt_str(uint8_t event)
+{
+    if (event >= SIZEOF_ARRAY(bt_gap_evt_names)) {
+        return "UNKNOWN";
+    }
+    return bt_gap_evt_names[event];
+}
+
+const char *esp_ble_key_type_str(esp_ble_key_type_t key_type)
+{
+    const char *key_str = NULL;
+    switch (key_type) {
+    case ESP_LE_KEY_NONE:
+        key_str = "ESP_LE_KEY_NONE";
+        break;
+    case ESP_LE_KEY_PENC:
+        key_str = "ESP_LE_KEY_PENC";
+        break;
+    case ESP_LE_KEY_PID:
+        key_str = "ESP_LE_KEY_PID";
+        break;
+    case ESP_LE_KEY_PCSRK:
+        key_str = "ESP_LE_KEY_PCSRK";
+        break;
+    case ESP_LE_KEY_PLK:
+        key_str = "ESP_LE_KEY_PLK";
+        break;
+    case ESP_LE_KEY_LLK:
+        key_str = "ESP_LE_KEY_LLK";
+        break;
+    case ESP_LE_KEY_LENC:
+        key_str = "ESP_LE_KEY_LENC";
+        break;
+    case ESP_LE_KEY_LID:
+        key_str = "ESP_LE_KEY_LID";
+        break;
+    case ESP_LE_KEY_LCSRK:
+        key_str = "ESP_LE_KEY_LCSRK";
+        break;
+    default:
+        key_str = "INVALID BLE KEY TYPE";
+        break;
+
+    }
+    return key_str;
+}
+
+void esp_hid_scan_results_free(esp_hid_scan_result_t *results)
+{
+    esp_hid_scan_result_t *r = NULL;
+    while (results) {
+        r = results;
+        results = results->next;
+        if (r->name != NULL) {
+            free((char *)r->name);
+        }
+        free(r);
+    }
+}
+
+static esp_hid_scan_result_t *find_scan_result(esp_bd_addr_t bda, esp_hid_scan_result_t *results)
+{
+    esp_hid_scan_result_t *r = results;
+    while (r) {
+        if (memcmp(bda, r->bda, sizeof(esp_bd_addr_t)) == 0) {
+            return r;
+        }
+        r = r->next;
+    }
+    return NULL;
+}
+
+static void add_bt_scan_result(esp_bd_addr_t bda, esp_bt_cod_t *cod, esp_bt_uuid_t *uuid, uint8_t *name, uint8_t name_len, int rssi)
+{
+    esp_hid_scan_result_t *r = find_scan_result(bda, bt_scan_results);
+    if (r) {
+        //Some info may come later
+        if (r->name == NULL && name && name_len) {
+            char *name_s = (char *)malloc(name_len + 1);
+            if (name_s == NULL) {
+                ESP_LOGE(TAG, "Malloc result name failed!");
+                return;
+            }
+            memcpy(name_s, name, name_len);
+            name_s[name_len] = 0;
+            r->name = (const char *)name_s;
+        }
+        if (r->bt.uuid.len == 0 && uuid->len) {
+            memcpy(&r->bt.uuid, uuid, sizeof(esp_bt_uuid_t));
+        }
+        if (rssi != 0) {
+            r->rssi = rssi;
+        }
+        return;
+    }
+
+    r = (esp_hid_scan_result_t *)malloc(sizeof(esp_hid_scan_result_t));
+    if (r == NULL) {
+        ESP_LOGE(TAG, "Malloc bt_hidh_scan_result_t failed!");
+        return;
+    }
+    r->transport = ESP_HID_TRANSPORT_BT;
+    memcpy(r->bda, bda, sizeof(esp_bd_addr_t));
+    memcpy(&r->bt.cod, cod, sizeof(esp_bt_cod_t));
+    memcpy(&r->bt.uuid, uuid, sizeof(esp_bt_uuid_t));
+    r->usage = esp_hid_usage_from_cod((uint32_t)cod);
+    r->rssi = rssi;
+    r->name = NULL;
+    if (name_len && name) {
+        char *name_s = (char *)malloc(name_len + 1);
+        if (name_s == NULL) {
+            free(r);
+            ESP_LOGE(TAG, "Malloc result name failed!");
+            return;
+        }
+        memcpy(name_s, name, name_len);
+        name_s[name_len] = 0;
+        r->name = (const char *)name_s;
+    }
+    r->next = bt_scan_results;
+    bt_scan_results = r;
+    num_bt_scan_results++;
+}
+
+static void add_ble_scan_result(esp_bd_addr_t bda, esp_ble_addr_type_t addr_type, uint16_t appearance, uint8_t *name, uint8_t name_len, int rssi)
+{
+    if (find_scan_result(bda, ble_scan_results)) {
+        ESP_LOGW(TAG, "Result already exists!");
+        return;
+    }
+    esp_hid_scan_result_t *r = (esp_hid_scan_result_t *)malloc(sizeof(esp_hid_scan_result_t));
+    if (r == NULL) {
+        ESP_LOGE(TAG, "Malloc ble_hidh_scan_result_t failed!");
+        return;
+    }
+    r->transport = ESP_HID_TRANSPORT_BLE;
+    memcpy(r->bda, bda, sizeof(esp_bd_addr_t));
+    r->ble.appearance = appearance;
+    r->ble.addr_type = addr_type;
+    r->usage = esp_hid_usage_from_appearance(appearance);
+    r->rssi = rssi;
+    r->name = NULL;
+    if (name_len && name) {
+        char *name_s = (char *)malloc(name_len + 1);
+        if (name_s == NULL) {
+            free(r);
+            ESP_LOGE(TAG, "Malloc result name failed!");
+            return;
+        }
+        memcpy(name_s, name, name_len);
+        name_s[name_len] = 0;
+        r->name = (const char *)name_s;
+    }
+    r->next = ble_scan_results;
+    ble_scan_results = r;
+    num_ble_scan_results++;
+}
+
+void print_uuid(esp_bt_uuid_t *uuid)
+{
+    if (uuid->len == ESP_UUID_LEN_16) {
+        GAP_DBG_PRINTF("UUID16: 0x%04x", uuid->uuid.uuid16);
+    } else if (uuid->len == ESP_UUID_LEN_32) {
+        GAP_DBG_PRINTF("UUID32: 0x%08x", uuid->uuid.uuid32);
+    } else if (uuid->len == ESP_UUID_LEN_128) {
+        GAP_DBG_PRINTF("UUID128: %02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x", uuid->uuid.uuid128[0],
+                       uuid->uuid.uuid128[1], uuid->uuid.uuid128[2], uuid->uuid.uuid128[3],
+                       uuid->uuid.uuid128[4], uuid->uuid.uuid128[5], uuid->uuid.uuid128[6],
+                       uuid->uuid.uuid128[7], uuid->uuid.uuid128[8], uuid->uuid.uuid128[9],
+                       uuid->uuid.uuid128[10], uuid->uuid.uuid128[11], uuid->uuid.uuid128[12],
+                       uuid->uuid.uuid128[13], uuid->uuid.uuid128[14], uuid->uuid.uuid128[15]);
+    }
+}
+
+static void handle_bt_device_result(struct disc_res_param *disc_res)
+{
+    GAP_DBG_PRINTF("BT : " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(disc_res->bda));
+    uint32_t codv = 0;
+    esp_bt_cod_t *cod = (esp_bt_cod_t *)&codv;
+    int8_t rssi = 0;
+    uint8_t *name = NULL;
+    uint8_t name_len = 0;
+    esp_bt_uuid_t uuid;
+
+    uuid.len = ESP_UUID_LEN_16;
+    uuid.uuid.uuid16 = 0;
+
+    for (int i = 0; i < disc_res->num_prop; i++) {
+        esp_bt_gap_dev_prop_t *prop = &disc_res->prop[i];
+        if (prop->type != ESP_BT_GAP_DEV_PROP_EIR) {
+            GAP_DBG_PRINTF(", %s: ", gap_bt_prop_type_names[prop->type]);
+        }
+        if (prop->type == ESP_BT_GAP_DEV_PROP_BDNAME) {
+            name = (uint8_t *)prop->val;
+            name_len = strlen((const char *)name);
+            GAP_DBG_PRINTF("%s", (const char *)name);
+        } else if (prop->type == ESP_BT_GAP_DEV_PROP_RSSI) {
+            rssi = *((int8_t *)prop->val);
+            GAP_DBG_PRINTF("%d", rssi);
+        } else if (prop->type == ESP_BT_GAP_DEV_PROP_COD) {
+            memcpy(&codv, prop->val, sizeof(uint32_t));
+            GAP_DBG_PRINTF("major: %s, minor: %d, service: 0x%03x", esp_hid_cod_major_str(cod->major), cod->minor, cod->service);
+        } else if (prop->type == ESP_BT_GAP_DEV_PROP_EIR) {
+            uint8_t len = 0;
+            uint8_t *data = 0;
+
+            data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_CMPL_16BITS_UUID, &len);
+            if (data == NULL) {
+                data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_INCMPL_16BITS_UUID, &len);
+            }
+            if (data && len == ESP_UUID_LEN_16) {
+                uuid.len = ESP_UUID_LEN_16;
+                uuid.uuid.uuid16 = data[0] + (data[1] << 8);
+                GAP_DBG_PRINTF(", "); print_uuid(&uuid);
+                continue;
+            }
+
+            data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_CMPL_32BITS_UUID, &len);
+            if (data == NULL) {
+                data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_INCMPL_32BITS_UUID, &len);
+            }
+            if (data && len == ESP_UUID_LEN_32) {
+                uuid.len = len;
+                memcpy(&uuid.uuid.uuid32, data, sizeof(uint32_t));
+                GAP_DBG_PRINTF(", "); print_uuid(&uuid);
+                continue;
+            }
+
+            data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_CMPL_128BITS_UUID, &len);
+            if (data == NULL) {
+                data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_INCMPL_128BITS_UUID, &len);
+            }
+            if (data && len == ESP_UUID_LEN_128) {
+                uuid.len = len;
+                memcpy(uuid.uuid.uuid128, (uint8_t *)data, len);
+                GAP_DBG_PRINTF(", "); print_uuid(&uuid);
+                continue;
+            }
+
+            //try to find a name
+            if (name == NULL) {
+                data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &len);
+                if (data == NULL) {
+                    data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &len);
+                }
+                if (data && len) {
+                    name = data;
+                    name_len = len;
+                    GAP_DBG_PRINTF(", NAME: ");
+                    for (int x = 0; x < len; x++) {
+                        GAP_DBG_PRINTF("%c", (char)data[x]);
+                    }
+                }
+            }
+        }
+    }
+    GAP_DBG_PRINTF("\n");
+
+    if (cod->major == ESP_BT_COD_MAJOR_DEV_PERIPHERAL || (find_scan_result(disc_res->bda, bt_scan_results) != NULL)) {
+        add_bt_scan_result(disc_res->bda, cod, &uuid, name, name_len, rssi);
+    }
+}
+
+static void handle_ble_device_result(struct ble_scan_result_evt_param *scan_rst)
+{
+
+    uint16_t uuid = 0;
+    uint16_t appearance = 0;
+    char name[64] = {0};
+
+    uint8_t uuid_len = 0;
+    uint8_t *uuid_d = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_16SRV_CMPL, &uuid_len);
+    if (uuid_d != NULL && uuid_len) {
+        uuid = uuid_d[0] + (uuid_d[1] << 8);
+    }
+
+    uint8_t appearance_len = 0;
+    uint8_t *appearance_d = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_APPEARANCE, &appearance_len);
+    if (appearance_d != NULL && appearance_len) {
+        appearance = appearance_d[0] + (appearance_d[1] << 8);
+    }
+
+    uint8_t adv_name_len = 0;
+    uint8_t *adv_name = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
+
+    if (adv_name == NULL) {
+        adv_name = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_NAME_SHORT, &adv_name_len);
+    }
+
+    if (adv_name != NULL && adv_name_len) {
+        memcpy(name, adv_name, adv_name_len);
+        name[adv_name_len] = 0;
+    }
+
+    GAP_DBG_PRINTF("BLE: " ESP_BD_ADDR_STR ", ", ESP_BD_ADDR_HEX(scan_rst->bda));
+    GAP_DBG_PRINTF("RSSI: %d, ", scan_rst->rssi);
+    GAP_DBG_PRINTF("UUID: 0x%04x, ", uuid);
+    GAP_DBG_PRINTF("APPEARANCE: 0x%04x, ", appearance);
+    GAP_DBG_PRINTF("ADDR_TYPE: '%s'", ble_addr_type_str(scan_rst->ble_addr_type));
+    if (adv_name_len) {
+        GAP_DBG_PRINTF(", NAME: '%s'", name);
+    }
+    GAP_DBG_PRINTF("\n");
+
+    if (uuid == ESP_GATT_UUID_HID_SVC) {
+        add_ble_scan_result(scan_rst->bda, scan_rst->ble_addr_type, appearance, adv_name, adv_name_len, scan_rst->rssi);
+    }
+}
+
+
+/*
+ * BT GAP
+ * */
+
+static void bt_gap_event_handler(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
+{
+    static bool scan_running = false;
+    static bool scan_wait_stop = false;
+    switch (event) {
+    case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
+        ESP_LOGV(TAG, "BT GAP DISC_STATE %s", (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) ? "START" : "STOP");
+        if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
+            scan_running = true;
+            scan_wait_stop = true;
+        } else if (scan_wait_stop) {
+            scan_wait_stop = false;
+        } else if (scan_running) {
+            scan_running = false;
+            SEND_BT_CB();
+        }
+        break;
+    }
+    case ESP_BT_GAP_DISC_RES_EVT: {
+        handle_bt_device_result(&param->disc_res);
+        break;
+    }
+    case ESP_BT_GAP_KEY_NOTIF_EVT:
+        ESP_LOGI(TAG, "BT GAP KEY_NOTIF passkey:%d", param->key_notif.passkey);
+        break;
+    default:
+        ESP_LOGV(TAG, "BT GAP EVENT %s", bt_gap_evt_str(event));
+        break;
+    }
+}
+
+static esp_err_t init_bt_gap(void)
+{
+    esp_err_t ret;
+    esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
+    esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
+    esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
+    /*
+     * Set default parameters for Legacy Pairing
+     * Use fixed pin code
+     */
+    esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED;
+    esp_bt_pin_code_t pin_code;
+    pin_code[0] = '1';
+    pin_code[1] = '2';
+    pin_code[2] = '3';
+    pin_code[3] = '4';
+    esp_bt_gap_set_pin(pin_type, 4, pin_code);
+
+    if ((ret = esp_bt_gap_register_callback(bt_gap_event_handler)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_bt_gap_register_callback failed: %d", ret);
+        return ret;
+    }
+
+    // Allow BT devices to connect back to us
+    if ((ret = esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_NON_DISCOVERABLE)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_bt_gap_set_scan_mode failed: %d", ret);
+        return ret;
+    }
+    return ret;
+}
+
+static esp_err_t start_bt_scan(uint32_t seconds)
+{
+    esp_err_t ret = ESP_OK;
+    if ((ret = esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, (int)(seconds / 1.28), 0)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_bt_gap_start_discovery failed: %d", ret);
+        return ret;
+    }
+    return ret;
+}
+
+/*
+ * BLE GAP
+ * */
+
+static void ble_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
+{
+    switch (event) {
+    /*
+     * SCAN
+     * */
+    case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
+        ESP_LOGV(TAG, "BLE GAP EVENT SCAN_PARAM_SET_COMPLETE");
+        SEND_BLE_CB();
+        break;
+    }
+    case ESP_GAP_BLE_SCAN_RESULT_EVT: {
+        esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
+        switch (scan_result->scan_rst.search_evt) {
+        case ESP_GAP_SEARCH_INQ_RES_EVT: {
+            handle_ble_device_result(&scan_result->scan_rst);
+            break;
+        }
+        case ESP_GAP_SEARCH_INQ_CMPL_EVT:
+            ESP_LOGV(TAG, "BLE GAP EVENT SCAN DONE: %d", scan_result->scan_rst.num_resps);
+            SEND_BLE_CB();
+            break;
+        default:
+            break;
+        }
+        break;
+    }
+    case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: {
+        ESP_LOGV(TAG, "BLE GAP EVENT SCAN CANCELED");
+        break;
+    }
+
+    /*
+     * ADVERTISEMENT
+     * */
+    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
+        ESP_LOGV(TAG, "BLE GAP ADV_DATA_SET_COMPLETE");
+        break;
+
+    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
+        ESP_LOGV(TAG, "BLE GAP ADV_START_COMPLETE");
+        break;
+
+    /*
+     * AUTHENTICATION
+     * */
+    case ESP_GAP_BLE_AUTH_CMPL_EVT:
+        if (!param->ble_security.auth_cmpl.success) {
+            ESP_LOGE(TAG, "BLE GAP AUTH ERROR: 0x%x", param->ble_security.auth_cmpl.fail_reason);
+        } else {
+            ESP_LOGI(TAG, "BLE GAP AUTH SUCCESS");
+        }
+        break;
+
+    case ESP_GAP_BLE_KEY_EVT: //shows the ble key info share with peer device to the user.
+        ESP_LOGI(TAG, "BLE GAP KEY type = %s", esp_ble_key_type_str(param->ble_security.ble_key.key_type));
+        break;
+
+    case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // ESP_IO_CAP_OUT
+        // The app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
+        // Show the passkey number to the user to input it in the peer device.
+        ESP_LOGI(TAG, "BLE GAP PASSKEY_NOTIF passkey:%d", param->ble_security.key_notif.passkey);
+        break;
+
+    case ESP_GAP_BLE_NC_REQ_EVT: // ESP_IO_CAP_IO
+        // The app will receive this event when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
+        // show the passkey number to the user to confirm it with the number displayed by peer device.
+        ESP_LOGI(TAG, "BLE GAP NC_REQ passkey:%d", param->ble_security.key_notif.passkey);
+        esp_ble_confirm_reply(param->ble_security.key_notif.bd_addr, true);
+        break;
+
+    case ESP_GAP_BLE_PASSKEY_REQ_EVT: // ESP_IO_CAP_IN
+        // The app will receive this evt when the IO has Input capability and the peer device IO has Output capability.
+        // See the passkey number on the peer device and send it back.
+        ESP_LOGI(TAG, "BLE GAP PASSKEY_REQ");
+        //esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, 1234);
+        break;
+
+    case ESP_GAP_BLE_SEC_REQ_EVT:
+        ESP_LOGI(TAG, "BLE GAP SEC_REQ");
+        // Send the positive(true) security response to the peer device to accept the security request.
+        // If not accept the security request, should send the security response with negative(false) accept value.
+        esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
+        break;
+
+    default:
+        ESP_LOGV(TAG, "BLE GAP EVENT %s", ble_gap_evt_str(event));
+        break;
+    }
+}
+
+static esp_err_t init_ble_gap(void)
+{
+    esp_err_t ret;
+
+    if ((ret = esp_ble_gap_register_callback(ble_gap_event_handler)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", ret);
+        return ret;
+    }
+    return ret;
+}
+
+static esp_ble_scan_params_t hid_scan_params = {
+    .scan_type              = BLE_SCAN_TYPE_ACTIVE,
+    .own_addr_type          = BLE_ADDR_TYPE_PUBLIC,
+    .scan_filter_policy     = BLE_SCAN_FILTER_ALLOW_ALL,
+    .scan_interval          = 0x50,
+    .scan_window            = 0x30,
+    .scan_duplicate         = BLE_SCAN_DUPLICATE_ENABLE,
+};
+
+static esp_err_t start_ble_scan(uint32_t seconds)
+{
+    esp_err_t ret = ESP_OK;
+    if ((ret = esp_ble_gap_set_scan_params(&hid_scan_params)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", ret);
+        return ret;
+    }
+    WAIT_BLE_CB();
+
+    if ((ret = esp_ble_gap_start_scanning(seconds)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", ret);
+        return ret;
+    }
+    return ret;
+}
+
+esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name)
+{
+
+    esp_err_t ret;
+
+    const uint8_t hidd_service_uuid128[] = {
+        0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x12, 0x18, 0x00, 0x00,
+    };
+
+    esp_ble_adv_data_t ble_adv_data = {
+        .set_scan_rsp = false,
+        .include_name = true,
+        .include_txpower = true,
+        .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
+        .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec
+        .appearance = appearance,
+        .manufacturer_len = 0,
+        .p_manufacturer_data =  NULL,
+        .service_data_len = 0,
+        .p_service_data = NULL,
+        .service_uuid_len = sizeof(hidd_service_uuid128),
+        .p_service_uuid = (uint8_t *)hidd_service_uuid128,
+        .flag = 0x6,
+    };
+
+    esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND;
+    //esp_ble_io_cap_t iocap = ESP_IO_CAP_OUT;//you have to enter the key on the host
+    //esp_ble_io_cap_t iocap = ESP_IO_CAP_IN;//you have to enter the key on the device
+    esp_ble_io_cap_t iocap = ESP_IO_CAP_IO;//you have to agree that key matches on both
+    //esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;//device is not capable of input or output, unsecure
+    uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
+    uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
+    uint8_t key_size = 16; //the key size should be 7~16 bytes
+    uint32_t passkey = 1234;//ESP_IO_CAP_OUT
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, 1)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param AUTHEN_REQ_MODE failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, 1)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param IOCAP_MODE failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, 1)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param SET_INIT_KEY failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, 1)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param SET_RSP_KEY failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, 1)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param MAX_KEY_SIZE failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t))) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param SET_STATIC_PASSKEY failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_device_name(device_name)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_device_name failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_config_adv_data(&ble_adv_data)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP config_adv_data failed: %d", ret);
+        return ret;
+    }
+
+    return ret;
+}
+
+esp_err_t esp_hid_ble_gap_adv_start(void)
+{
+    static esp_ble_adv_params_t hidd_adv_params = {
+        .adv_int_min        = 0x20,
+        .adv_int_max        = 0x30,
+        .adv_type           = ADV_TYPE_IND,
+        .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,
+        .channel_map        = ADV_CHNL_ALL,
+        .adv_filter_policy  = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
+    };
+    return esp_ble_gap_start_advertising(&hidd_adv_params);
+}
+
+/*
+ * CONTROLLER INIT
+ * */
+
+static esp_err_t init_low_level(uint8_t mode)
+{
+    esp_err_t ret;
+    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
+    if (mode & ESP_BT_MODE_CLASSIC_BT) {
+        bt_cfg.mode = mode;
+        bt_cfg.bt_max_acl_conn = 3;
+        bt_cfg.bt_max_sync_conn = 3;
+    } else {
+        ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
+        if (ret) {
+            ESP_LOGE(TAG, "esp_bt_controller_mem_release failed: %d", ret);
+            return ret;
+        }
+    }
+    ret = esp_bt_controller_init(&bt_cfg);
+    if (ret) {
+        ESP_LOGE(TAG, "esp_bt_controller_init failed: %d", ret);
+        return ret;
+    }
+
+    ret = esp_bt_controller_enable(mode);
+    if (ret) {
+        ESP_LOGE(TAG, "esp_bt_controller_enable failed: %d", ret);
+        return ret;
+    }
+
+    ret = esp_bluedroid_init();
+    if (ret) {
+        ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", ret);
+        return ret;
+    }
+
+    ret = esp_bluedroid_enable();
+    if (ret) {
+        ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", ret);
+        return ret;
+    }
+
+    if (mode & ESP_BT_MODE_CLASSIC_BT) {
+        ret = init_bt_gap();
+        if (ret) {
+            return ret;
+        }
+    }
+
+    if (mode & ESP_BT_MODE_BLE) {
+        ret = init_ble_gap();
+        if (ret) {
+            return ret;
+        }
+    }
+    return ret;
+}
+
+
+
+
+esp_err_t esp_hid_gap_init(uint8_t mode)
+{
+    esp_err_t ret;
+    if (!mode || mode > ESP_BT_MODE_BTDM) {
+        ESP_LOGE(TAG, "Invalid mode given!");
+        return ESP_FAIL;
+    }
+
+    if (bt_hidh_cb_semaphore != NULL) {
+        ESP_LOGE(TAG, "Already initialised");
+        return ESP_FAIL;
+    }
+
+    bt_hidh_cb_semaphore = xSemaphoreCreateBinary();
+    if (bt_hidh_cb_semaphore == NULL) {
+        ESP_LOGE(TAG, "xSemaphoreCreateMutex failed!");
+        return ESP_FAIL;
+    }
+
+    ble_hidh_cb_semaphore = xSemaphoreCreateBinary();
+    if (ble_hidh_cb_semaphore == NULL) {
+        ESP_LOGE(TAG, "xSemaphoreCreateMutex failed!");
+        vSemaphoreDelete(bt_hidh_cb_semaphore);
+        bt_hidh_cb_semaphore = NULL;
+        return ESP_FAIL;
+    }
+
+    ret = init_low_level(mode);
+    if (ret != ESP_OK) {
+        vSemaphoreDelete(bt_hidh_cb_semaphore);
+        bt_hidh_cb_semaphore = NULL;
+        vSemaphoreDelete(ble_hidh_cb_semaphore);
+        ble_hidh_cb_semaphore = NULL;
+        return ret;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_result_t **results)
+{
+    if (num_bt_scan_results || bt_scan_results || num_ble_scan_results || ble_scan_results) {
+        ESP_LOGE(TAG, "There are old scan results. Free them first!");
+        return ESP_FAIL;
+    }
+
+    if (start_ble_scan(seconds) == ESP_OK) {
+        if (start_bt_scan(seconds) == ESP_OK) {
+            WAIT_BT_CB();
+        }
+        WAIT_BLE_CB();
+    } else {
+        return ESP_FAIL;
+    }
+
+    *num_results = num_bt_scan_results + num_ble_scan_results;
+    *results = bt_scan_results;
+    if (num_bt_scan_results) {
+        while (bt_scan_results->next != NULL) {
+            bt_scan_results = bt_scan_results->next;
+        }
+        bt_scan_results->next = ble_scan_results;
+    } else {
+        *results = ble_scan_results;
+    }
+
+    num_bt_scan_results = 0;
+    bt_scan_results = NULL;
+    num_ble_scan_results = 0;
+    ble_scan_results = NULL;
+    return ESP_OK;
+}

+ 68 - 0
examples/bluetooth/esp_hid_device/main/esp_hid_gap.h

@@ -0,0 +1,68 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _ESP_HID_GAP_H_
+#define _ESP_HID_GAP_H_
+
+#include "esp_err.h"
+#include "esp_log.h"
+
+#include "esp_bt.h"
+#include "esp_bt_defs.h"
+#include "esp_bt_main.h"
+#include "esp_gattc_api.h"
+#include "esp_gatt_defs.h"
+#include "esp_gap_ble_api.h"
+#include "esp_gap_bt_api.h"
+#include "esp_hid_common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct esp_hidh_scan_result_s {
+    struct esp_hidh_scan_result_s *next;
+
+    esp_bd_addr_t bda;
+    const char *name;
+    int8_t rssi;
+    esp_hid_usage_t usage;
+    esp_hid_transport_t transport; //BT, BLE or USB
+    union {
+        struct {
+            esp_bt_cod_t cod;
+            esp_bt_uuid_t uuid;
+        } bt;
+        struct {
+            esp_ble_addr_type_t addr_type;
+            uint16_t appearance;
+        } ble;
+    };
+} esp_hid_scan_result_t;
+
+esp_err_t esp_hid_gap_init(uint8_t mode);
+esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_result_t **results);
+void esp_hid_scan_results_free(esp_hid_scan_result_t *results);
+
+esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name);
+esp_err_t esp_hid_ble_gap_adv_start(void);
+
+void print_uuid(esp_bt_uuid_t *uuid);
+const char *ble_addr_type_str(esp_ble_addr_type_t ble_addr_type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ESP_HIDH_GAP_H_ */

+ 6 - 0
examples/bluetooth/esp_hid_device/sdkconfig.defaults

@@ -0,0 +1,6 @@
+CONFIG_BT_ENABLED=y
+CONFIG_BTDM_CTRL_MODE_BTDM=y
+CONFIG_BTDM_CTRL_HCI_MODE_VHCI=y
+CONFIG_BT_BLUEDROID_ENABLED=y
+CONFIG_BT_CLASSIC_ENABLED=y
+CONFIG_BT_HID_HOST_ENABLED=y

+ 7 - 0
examples/bluetooth/esp_hid_host/CMakeLists.txt

@@ -0,0 +1,7 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+set(SUPPORTED_TARGETS esp32)
+project(esp_hid_host)

+ 8 - 0
examples/bluetooth/esp_hid_host/Makefile

@@ -0,0 +1,8 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := esp_hid_host
+
+include $(IDF_PATH)/make/project.mk

+ 5 - 0
examples/bluetooth/esp_hid_host/main/CMakeLists.txt

@@ -0,0 +1,5 @@
+set(COMPONENT_SRCS "esp_hid_host_main.c"
+                   "esp_hid_gap.c")
+set(COMPONENT_ADD_INCLUDEDIRS ".")
+
+register_component()

+ 3 - 0
examples/bluetooth/esp_hid_host/main/component.mk

@@ -0,0 +1,3 @@
+#
+# Main Makefile. This is basically the same as a component makefile.
+#

+ 807 - 0
examples/bluetooth/esp_hid_host/main/esp_hid_gap.c

@@ -0,0 +1,807 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string.h>
+#include <stdbool.h>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+
+#include "esp_hid_gap.h"
+
+static const char *TAG = "ESP_HID_GAP";
+
+// uncomment to print all devices that were seen during a scan
+#define GAP_DBG_PRINTF(...) //printf(__VA_ARGS__)
+//static const char * gap_bt_prop_type_names[5] = {"","BDNAME","COD","RSSI","EIR"};
+
+static esp_hid_scan_result_t *bt_scan_results = NULL;
+static size_t num_bt_scan_results = 0;
+
+static esp_hid_scan_result_t *ble_scan_results = NULL;
+static size_t num_ble_scan_results = 0;
+
+static xSemaphoreHandle bt_hidh_cb_semaphore = NULL;
+#define WAIT_BT_CB() xSemaphoreTake(bt_hidh_cb_semaphore, portMAX_DELAY)
+#define SEND_BT_CB() xSemaphoreGive(bt_hidh_cb_semaphore)
+
+static xSemaphoreHandle ble_hidh_cb_semaphore = NULL;
+#define WAIT_BLE_CB() xSemaphoreTake(ble_hidh_cb_semaphore, portMAX_DELAY)
+#define SEND_BLE_CB() xSemaphoreGive(ble_hidh_cb_semaphore)
+
+#define SIZEOF_ARRAY(a) (sizeof(a)/sizeof(*a))
+
+static const char *ble_gap_evt_names[] = { "ADV_DATA_SET_COMPLETE", "SCAN_RSP_DATA_SET_COMPLETE", "SCAN_PARAM_SET_COMPLETE", "SCAN_RESULT", "ADV_DATA_RAW_SET_COMPLETE", "SCAN_RSP_DATA_RAW_SET_COMPLETE", "ADV_START_COMPLETE", "SCAN_START_COMPLETE", "AUTH_CMPL", "KEY", "SEC_REQ", "PASSKEY_NOTIF", "PASSKEY_REQ", "OOB_REQ", "LOCAL_IR", "LOCAL_ER", "NC_REQ", "ADV_STOP_COMPLETE", "SCAN_STOP_COMPLETE", "SET_STATIC_RAND_ADDR", "UPDATE_CONN_PARAMS", "SET_PKT_LENGTH_COMPLETE", "SET_LOCAL_PRIVACY_COMPLETE", "REMOVE_BOND_DEV_COMPLETE", "CLEAR_BOND_DEV_COMPLETE", "GET_BOND_DEV_COMPLETE", "READ_RSSI_COMPLETE", "UPDATE_WHITELIST_COMPLETE"};
+static const char *bt_gap_evt_names[] = { "DISC_RES", "DISC_STATE_CHANGED", "RMT_SRVCS", "RMT_SRVC_REC", "AUTH_CMPL", "PIN_REQ", "CFM_REQ", "KEY_NOTIF", "KEY_REQ", "READ_RSSI_DELTA"};
+static const char *ble_addr_type_names[] = {"PUBLIC", "RANDOM", "RPA_PUBLIC", "RPA_RANDOM"};
+
+const char *ble_addr_type_str(esp_ble_addr_type_t ble_addr_type)
+{
+    if (ble_addr_type > BLE_ADDR_TYPE_RPA_RANDOM) {
+        return "UNKNOWN";
+    }
+    return ble_addr_type_names[ble_addr_type];
+}
+
+const char *ble_gap_evt_str(uint8_t event)
+{
+    if (event >= SIZEOF_ARRAY(ble_gap_evt_names)) {
+        return "UNKNOWN";
+    }
+    return ble_gap_evt_names[event];
+}
+
+const char *bt_gap_evt_str(uint8_t event)
+{
+    if (event >= SIZEOF_ARRAY(bt_gap_evt_names)) {
+        return "UNKNOWN";
+    }
+    return bt_gap_evt_names[event];
+}
+
+const char *esp_ble_key_type_str(esp_ble_key_type_t key_type)
+{
+    const char *key_str = NULL;
+    switch (key_type) {
+    case ESP_LE_KEY_NONE:
+        key_str = "ESP_LE_KEY_NONE";
+        break;
+    case ESP_LE_KEY_PENC:
+        key_str = "ESP_LE_KEY_PENC";
+        break;
+    case ESP_LE_KEY_PID:
+        key_str = "ESP_LE_KEY_PID";
+        break;
+    case ESP_LE_KEY_PCSRK:
+        key_str = "ESP_LE_KEY_PCSRK";
+        break;
+    case ESP_LE_KEY_PLK:
+        key_str = "ESP_LE_KEY_PLK";
+        break;
+    case ESP_LE_KEY_LLK:
+        key_str = "ESP_LE_KEY_LLK";
+        break;
+    case ESP_LE_KEY_LENC:
+        key_str = "ESP_LE_KEY_LENC";
+        break;
+    case ESP_LE_KEY_LID:
+        key_str = "ESP_LE_KEY_LID";
+        break;
+    case ESP_LE_KEY_LCSRK:
+        key_str = "ESP_LE_KEY_LCSRK";
+        break;
+    default:
+        key_str = "INVALID BLE KEY TYPE";
+        break;
+
+    }
+    return key_str;
+}
+
+void esp_hid_scan_results_free(esp_hid_scan_result_t *results)
+{
+    esp_hid_scan_result_t *r = NULL;
+    while (results) {
+        r = results;
+        results = results->next;
+        if (r->name != NULL) {
+            free((char *)r->name);
+        }
+        free(r);
+    }
+}
+
+static esp_hid_scan_result_t *find_scan_result(esp_bd_addr_t bda, esp_hid_scan_result_t *results)
+{
+    esp_hid_scan_result_t *r = results;
+    while (r) {
+        if (memcmp(bda, r->bda, sizeof(esp_bd_addr_t)) == 0) {
+            return r;
+        }
+        r = r->next;
+    }
+    return NULL;
+}
+
+static void add_bt_scan_result(esp_bd_addr_t bda, esp_bt_cod_t *cod, esp_bt_uuid_t *uuid, uint8_t *name, uint8_t name_len, int rssi)
+{
+    esp_hid_scan_result_t *r = find_scan_result(bda, bt_scan_results);
+    if (r) {
+        //Some info may come later
+        if (r->name == NULL && name && name_len) {
+            char *name_s = (char *)malloc(name_len + 1);
+            if (name_s == NULL) {
+                ESP_LOGE(TAG, "Malloc result name failed!");
+                return;
+            }
+            memcpy(name_s, name, name_len);
+            name_s[name_len] = 0;
+            r->name = (const char *)name_s;
+        }
+        if (r->bt.uuid.len == 0 && uuid->len) {
+            memcpy(&r->bt.uuid, uuid, sizeof(esp_bt_uuid_t));
+        }
+        if (rssi != 0) {
+            r->rssi = rssi;
+        }
+        return;
+    }
+
+    r = (esp_hid_scan_result_t *)malloc(sizeof(esp_hid_scan_result_t));
+    if (r == NULL) {
+        ESP_LOGE(TAG, "Malloc bt_hidh_scan_result_t failed!");
+        return;
+    }
+    r->transport = ESP_HID_TRANSPORT_BT;
+    memcpy(r->bda, bda, sizeof(esp_bd_addr_t));
+    memcpy(&r->bt.cod, cod, sizeof(esp_bt_cod_t));
+    memcpy(&r->bt.uuid, uuid, sizeof(esp_bt_uuid_t));
+    r->usage = esp_hid_usage_from_cod((uint32_t)cod);
+    r->rssi = rssi;
+    r->name = NULL;
+    if (name_len && name) {
+        char *name_s = (char *)malloc(name_len + 1);
+        if (name_s == NULL) {
+            free(r);
+            ESP_LOGE(TAG, "Malloc result name failed!");
+            return;
+        }
+        memcpy(name_s, name, name_len);
+        name_s[name_len] = 0;
+        r->name = (const char *)name_s;
+    }
+    r->next = bt_scan_results;
+    bt_scan_results = r;
+    num_bt_scan_results++;
+}
+
+static void add_ble_scan_result(esp_bd_addr_t bda, esp_ble_addr_type_t addr_type, uint16_t appearance, uint8_t *name, uint8_t name_len, int rssi)
+{
+    if (find_scan_result(bda, ble_scan_results)) {
+        ESP_LOGW(TAG, "Result already exists!");
+        return;
+    }
+    esp_hid_scan_result_t *r = (esp_hid_scan_result_t *)malloc(sizeof(esp_hid_scan_result_t));
+    if (r == NULL) {
+        ESP_LOGE(TAG, "Malloc ble_hidh_scan_result_t failed!");
+        return;
+    }
+    r->transport = ESP_HID_TRANSPORT_BLE;
+    memcpy(r->bda, bda, sizeof(esp_bd_addr_t));
+    r->ble.appearance = appearance;
+    r->ble.addr_type = addr_type;
+    r->usage = esp_hid_usage_from_appearance(appearance);
+    r->rssi = rssi;
+    r->name = NULL;
+    if (name_len && name) {
+        char *name_s = (char *)malloc(name_len + 1);
+        if (name_s == NULL) {
+            free(r);
+            ESP_LOGE(TAG, "Malloc result name failed!");
+            return;
+        }
+        memcpy(name_s, name, name_len);
+        name_s[name_len] = 0;
+        r->name = (const char *)name_s;
+    }
+    r->next = ble_scan_results;
+    ble_scan_results = r;
+    num_ble_scan_results++;
+}
+
+void print_uuid(esp_bt_uuid_t *uuid)
+{
+    if (uuid->len == ESP_UUID_LEN_16) {
+        GAP_DBG_PRINTF("UUID16: 0x%04x", uuid->uuid.uuid16);
+    } else if (uuid->len == ESP_UUID_LEN_32) {
+        GAP_DBG_PRINTF("UUID32: 0x%08x", uuid->uuid.uuid32);
+    } else if (uuid->len == ESP_UUID_LEN_128) {
+        GAP_DBG_PRINTF("UUID128: %02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x", uuid->uuid.uuid128[0],
+                       uuid->uuid.uuid128[1], uuid->uuid.uuid128[2], uuid->uuid.uuid128[3],
+                       uuid->uuid.uuid128[4], uuid->uuid.uuid128[5], uuid->uuid.uuid128[6],
+                       uuid->uuid.uuid128[7], uuid->uuid.uuid128[8], uuid->uuid.uuid128[9],
+                       uuid->uuid.uuid128[10], uuid->uuid.uuid128[11], uuid->uuid.uuid128[12],
+                       uuid->uuid.uuid128[13], uuid->uuid.uuid128[14], uuid->uuid.uuid128[15]);
+    }
+}
+
+static void handle_bt_device_result(struct disc_res_param *disc_res)
+{
+    GAP_DBG_PRINTF("BT : " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(disc_res->bda));
+    uint32_t codv = 0;
+    esp_bt_cod_t *cod = (esp_bt_cod_t *)&codv;
+    int8_t rssi = 0;
+    uint8_t *name = NULL;
+    uint8_t name_len = 0;
+    esp_bt_uuid_t uuid;
+
+    uuid.len = ESP_UUID_LEN_16;
+    uuid.uuid.uuid16 = 0;
+
+    for (int i = 0; i < disc_res->num_prop; i++) {
+        esp_bt_gap_dev_prop_t *prop = &disc_res->prop[i];
+        if (prop->type != ESP_BT_GAP_DEV_PROP_EIR) {
+            GAP_DBG_PRINTF(", %s: ", gap_bt_prop_type_names[prop->type]);
+        }
+        if (prop->type == ESP_BT_GAP_DEV_PROP_BDNAME) {
+            name = (uint8_t *)prop->val;
+            name_len = strlen((const char *)name);
+            GAP_DBG_PRINTF("%s", (const char *)name);
+        } else if (prop->type == ESP_BT_GAP_DEV_PROP_RSSI) {
+            rssi = *((int8_t *)prop->val);
+            GAP_DBG_PRINTF("%d", rssi);
+        } else if (prop->type == ESP_BT_GAP_DEV_PROP_COD) {
+            memcpy(&codv, prop->val, sizeof(uint32_t));
+            GAP_DBG_PRINTF("major: %s, minor: %d, service: 0x%03x", esp_hid_cod_major_str(cod->major), cod->minor, cod->service);
+        } else if (prop->type == ESP_BT_GAP_DEV_PROP_EIR) {
+            uint8_t len = 0;
+            uint8_t *data = 0;
+
+            data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_CMPL_16BITS_UUID, &len);
+            if (data == NULL) {
+                data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_INCMPL_16BITS_UUID, &len);
+            }
+            if (data && len == ESP_UUID_LEN_16) {
+                uuid.len = ESP_UUID_LEN_16;
+                uuid.uuid.uuid16 = data[0] + (data[1] << 8);
+                GAP_DBG_PRINTF(", "); print_uuid(&uuid);
+                continue;
+            }
+
+            data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_CMPL_32BITS_UUID, &len);
+            if (data == NULL) {
+                data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_INCMPL_32BITS_UUID, &len);
+            }
+            if (data && len == ESP_UUID_LEN_32) {
+                uuid.len = len;
+                memcpy(&uuid.uuid.uuid32, data, sizeof(uint32_t));
+                GAP_DBG_PRINTF(", "); print_uuid(&uuid);
+                continue;
+            }
+
+            data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_CMPL_128BITS_UUID, &len);
+            if (data == NULL) {
+                data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_INCMPL_128BITS_UUID, &len);
+            }
+            if (data && len == ESP_UUID_LEN_128) {
+                uuid.len = len;
+                memcpy(uuid.uuid.uuid128, (uint8_t *)data, len);
+                GAP_DBG_PRINTF(", "); print_uuid(&uuid);
+                continue;
+            }
+
+            //try to find a name
+            if (name == NULL) {
+                data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &len);
+                if (data == NULL) {
+                    data = esp_bt_gap_resolve_eir_data((uint8_t *)prop->val, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &len);
+                }
+                if (data && len) {
+                    name = data;
+                    name_len = len;
+                    GAP_DBG_PRINTF(", NAME: ");
+                    for (int x = 0; x < len; x++) {
+                        GAP_DBG_PRINTF("%c", (char)data[x]);
+                    }
+                }
+            }
+        }
+    }
+    GAP_DBG_PRINTF("\n");
+
+    if (cod->major == ESP_BT_COD_MAJOR_DEV_PERIPHERAL || (find_scan_result(disc_res->bda, bt_scan_results) != NULL)) {
+        add_bt_scan_result(disc_res->bda, cod, &uuid, name, name_len, rssi);
+    }
+}
+
+static void handle_ble_device_result(struct ble_scan_result_evt_param *scan_rst)
+{
+
+    uint16_t uuid = 0;
+    uint16_t appearance = 0;
+    char name[64] = {0};
+
+    uint8_t uuid_len = 0;
+    uint8_t *uuid_d = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_16SRV_CMPL, &uuid_len);
+    if (uuid_d != NULL && uuid_len) {
+        uuid = uuid_d[0] + (uuid_d[1] << 8);
+    }
+
+    uint8_t appearance_len = 0;
+    uint8_t *appearance_d = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_APPEARANCE, &appearance_len);
+    if (appearance_d != NULL && appearance_len) {
+        appearance = appearance_d[0] + (appearance_d[1] << 8);
+    }
+
+    uint8_t adv_name_len = 0;
+    uint8_t *adv_name = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
+
+    if (adv_name == NULL) {
+        adv_name = esp_ble_resolve_adv_data(scan_rst->ble_adv, ESP_BLE_AD_TYPE_NAME_SHORT, &adv_name_len);
+    }
+
+    if (adv_name != NULL && adv_name_len) {
+        memcpy(name, adv_name, adv_name_len);
+        name[adv_name_len] = 0;
+    }
+
+    GAP_DBG_PRINTF("BLE: " ESP_BD_ADDR_STR ", ", ESP_BD_ADDR_HEX(scan_rst->bda));
+    GAP_DBG_PRINTF("RSSI: %d, ", scan_rst->rssi);
+    GAP_DBG_PRINTF("UUID: 0x%04x, ", uuid);
+    GAP_DBG_PRINTF("APPEARANCE: 0x%04x, ", appearance);
+    GAP_DBG_PRINTF("ADDR_TYPE: '%s'", ble_addr_type_str(scan_rst->ble_addr_type));
+    if (adv_name_len) {
+        GAP_DBG_PRINTF(", NAME: '%s'", name);
+    }
+    GAP_DBG_PRINTF("\n");
+
+    if (uuid == ESP_GATT_UUID_HID_SVC) {
+        add_ble_scan_result(scan_rst->bda, scan_rst->ble_addr_type, appearance, adv_name, adv_name_len, scan_rst->rssi);
+    }
+}
+
+
+/*
+ * BT GAP
+ * */
+
+static void bt_gap_event_handler(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
+{
+    static bool scan_running = false;
+    static bool scan_wait_stop = false;
+    switch (event) {
+    case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
+        ESP_LOGV(TAG, "BT GAP DISC_STATE %s", (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) ? "START" : "STOP");
+        if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
+            scan_running = true;
+            scan_wait_stop = true;
+        } else if (scan_wait_stop) {
+            scan_wait_stop = false;
+        } else if (scan_running) {
+            scan_running = false;
+            SEND_BT_CB();
+        }
+        break;
+    }
+    case ESP_BT_GAP_DISC_RES_EVT: {
+        handle_bt_device_result(&param->disc_res);
+        break;
+    }
+    case ESP_BT_GAP_KEY_NOTIF_EVT:
+        ESP_LOGI(TAG, "BT GAP KEY_NOTIF passkey:%d", param->key_notif.passkey);
+        break;
+    default:
+        ESP_LOGV(TAG, "BT GAP EVENT %s", bt_gap_evt_str(event));
+        break;
+    }
+}
+
+static esp_err_t init_bt_gap(void)
+{
+    esp_err_t ret;
+    esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
+    esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
+    esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
+    /*
+     * Set default parameters for Legacy Pairing
+     * Use fixed pin code
+     */
+    esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED;
+    esp_bt_pin_code_t pin_code;
+    pin_code[0] = '1';
+    pin_code[1] = '2';
+    pin_code[2] = '3';
+    pin_code[3] = '4';
+    esp_bt_gap_set_pin(pin_type, 4, pin_code);
+
+    if ((ret = esp_bt_gap_register_callback(bt_gap_event_handler)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_bt_gap_register_callback failed: %d", ret);
+        return ret;
+    }
+
+    // Allow BT devices to connect back to us
+    if ((ret = esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_NON_DISCOVERABLE)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_bt_gap_set_scan_mode failed: %d", ret);
+        return ret;
+    }
+    return ret;
+}
+
+static esp_err_t start_bt_scan(uint32_t seconds)
+{
+    esp_err_t ret = ESP_OK;
+    if ((ret = esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, (int)(seconds / 1.28), 0)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_bt_gap_start_discovery failed: %d", ret);
+        return ret;
+    }
+    return ret;
+}
+
+/*
+ * BLE GAP
+ * */
+
+static void ble_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
+{
+    switch (event) {
+    /*
+     * SCAN
+     * */
+    case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
+        ESP_LOGV(TAG, "BLE GAP EVENT SCAN_PARAM_SET_COMPLETE");
+        SEND_BLE_CB();
+        break;
+    }
+    case ESP_GAP_BLE_SCAN_RESULT_EVT: {
+        esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
+        switch (scan_result->scan_rst.search_evt) {
+        case ESP_GAP_SEARCH_INQ_RES_EVT: {
+            handle_ble_device_result(&scan_result->scan_rst);
+            break;
+        }
+        case ESP_GAP_SEARCH_INQ_CMPL_EVT:
+            ESP_LOGV(TAG, "BLE GAP EVENT SCAN DONE: %d", scan_result->scan_rst.num_resps);
+            SEND_BLE_CB();
+            break;
+        default:
+            break;
+        }
+        break;
+    }
+    case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: {
+        ESP_LOGV(TAG, "BLE GAP EVENT SCAN CANCELED");
+        break;
+    }
+
+    /*
+     * ADVERTISEMENT
+     * */
+    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
+        ESP_LOGV(TAG, "BLE GAP ADV_DATA_SET_COMPLETE");
+        break;
+
+    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
+        ESP_LOGV(TAG, "BLE GAP ADV_START_COMPLETE");
+        break;
+
+    /*
+     * AUTHENTICATION
+     * */
+    case ESP_GAP_BLE_AUTH_CMPL_EVT:
+        if (!param->ble_security.auth_cmpl.success) {
+            ESP_LOGE(TAG, "BLE GAP AUTH ERROR: 0x%x", param->ble_security.auth_cmpl.fail_reason);
+        } else {
+            ESP_LOGI(TAG, "BLE GAP AUTH SUCCESS");
+        }
+        break;
+
+    case ESP_GAP_BLE_KEY_EVT: //shows the ble key info share with peer device to the user.
+        ESP_LOGI(TAG, "BLE GAP KEY type = %s", esp_ble_key_type_str(param->ble_security.ble_key.key_type));
+        break;
+
+    case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: // ESP_IO_CAP_OUT
+        // The app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
+        // Show the passkey number to the user to input it in the peer device.
+        ESP_LOGI(TAG, "BLE GAP PASSKEY_NOTIF passkey:%d", param->ble_security.key_notif.passkey);
+        break;
+
+    case ESP_GAP_BLE_NC_REQ_EVT: // ESP_IO_CAP_IO
+        // The app will receive this event when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
+        // show the passkey number to the user to confirm it with the number displayed by peer device.
+        ESP_LOGI(TAG, "BLE GAP NC_REQ passkey:%d", param->ble_security.key_notif.passkey);
+        esp_ble_confirm_reply(param->ble_security.key_notif.bd_addr, true);
+        break;
+
+    case ESP_GAP_BLE_PASSKEY_REQ_EVT: // ESP_IO_CAP_IN
+        // The app will receive this evt when the IO has Input capability and the peer device IO has Output capability.
+        // See the passkey number on the peer device and send it back.
+        ESP_LOGI(TAG, "BLE GAP PASSKEY_REQ");
+        //esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, 1234);
+        break;
+
+    case ESP_GAP_BLE_SEC_REQ_EVT:
+        ESP_LOGI(TAG, "BLE GAP SEC_REQ");
+        // Send the positive(true) security response to the peer device to accept the security request.
+        // If not accept the security request, should send the security response with negative(false) accept value.
+        esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
+        break;
+
+    default:
+        ESP_LOGV(TAG, "BLE GAP EVENT %s", ble_gap_evt_str(event));
+        break;
+    }
+}
+
+static esp_err_t init_ble_gap(void)
+{
+    esp_err_t ret;
+
+    if ((ret = esp_ble_gap_register_callback(ble_gap_event_handler)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", ret);
+        return ret;
+    }
+    return ret;
+}
+
+static esp_ble_scan_params_t hid_scan_params = {
+    .scan_type              = BLE_SCAN_TYPE_ACTIVE,
+    .own_addr_type          = BLE_ADDR_TYPE_PUBLIC,
+    .scan_filter_policy     = BLE_SCAN_FILTER_ALLOW_ALL,
+    .scan_interval          = 0x50,
+    .scan_window            = 0x30,
+    .scan_duplicate         = BLE_SCAN_DUPLICATE_ENABLE,
+};
+
+static esp_err_t start_ble_scan(uint32_t seconds)
+{
+    esp_err_t ret = ESP_OK;
+    if ((ret = esp_ble_gap_set_scan_params(&hid_scan_params)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_ble_gap_set_scan_params failed: %d", ret);
+        return ret;
+    }
+    WAIT_BLE_CB();
+
+    if ((ret = esp_ble_gap_start_scanning(seconds)) != ESP_OK) {
+        ESP_LOGE(TAG, "esp_ble_gap_start_scanning failed: %d", ret);
+        return ret;
+    }
+    return ret;
+}
+
+esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name)
+{
+
+    esp_err_t ret;
+
+    const uint8_t hidd_service_uuid128[] = {
+        0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x12, 0x18, 0x00, 0x00,
+    };
+
+    esp_ble_adv_data_t ble_adv_data = {
+        .set_scan_rsp = false,
+        .include_name = true,
+        .include_txpower = true,
+        .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
+        .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec
+        .appearance = appearance,
+        .manufacturer_len = 0,
+        .p_manufacturer_data =  NULL,
+        .service_data_len = 0,
+        .p_service_data = NULL,
+        .service_uuid_len = sizeof(hidd_service_uuid128),
+        .p_service_uuid = (uint8_t *)hidd_service_uuid128,
+        .flag = 0x6,
+    };
+
+    esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND;
+    //esp_ble_io_cap_t iocap = ESP_IO_CAP_OUT;//you have to enter the key on the host
+    //esp_ble_io_cap_t iocap = ESP_IO_CAP_IN;//you have to enter the key on the device
+    esp_ble_io_cap_t iocap = ESP_IO_CAP_IO;//you have to agree that key matches on both
+    //esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;//device is not capable of input or output, unsecure
+    uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
+    uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
+    uint8_t key_size = 16; //the key size should be 7~16 bytes
+    uint32_t passkey = 1234;//ESP_IO_CAP_OUT
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, 1)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param AUTHEN_REQ_MODE failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, 1)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param IOCAP_MODE failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, 1)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param SET_INIT_KEY failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, 1)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param SET_RSP_KEY failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, 1)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param MAX_KEY_SIZE failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t))) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_security_param SET_STATIC_PASSKEY failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_set_device_name(device_name)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP set_device_name failed: %d", ret);
+        return ret;
+    }
+
+    if ((ret = esp_ble_gap_config_adv_data(&ble_adv_data)) != ESP_OK) {
+        ESP_LOGE(TAG, "GAP config_adv_data failed: %d", ret);
+        return ret;
+    }
+
+    return ret;
+}
+
+esp_err_t esp_hid_ble_gap_adv_start(void)
+{
+    static esp_ble_adv_params_t hidd_adv_params = {
+        .adv_int_min        = 0x20,
+        .adv_int_max        = 0x30,
+        .adv_type           = ADV_TYPE_IND,
+        .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,
+        .channel_map        = ADV_CHNL_ALL,
+        .adv_filter_policy  = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
+    };
+    return esp_ble_gap_start_advertising(&hidd_adv_params);
+}
+
+/*
+ * CONTROLLER INIT
+ * */
+
+static esp_err_t init_low_level(uint8_t mode)
+{
+    esp_err_t ret;
+    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
+    if (mode & ESP_BT_MODE_CLASSIC_BT) {
+        bt_cfg.mode = mode;
+        bt_cfg.bt_max_acl_conn = 3;
+        bt_cfg.bt_max_sync_conn = 3;
+    } else {
+        ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
+        if (ret) {
+            ESP_LOGE(TAG, "esp_bt_controller_mem_release failed: %d", ret);
+            return ret;
+        }
+    }
+    ret = esp_bt_controller_init(&bt_cfg);
+    if (ret) {
+        ESP_LOGE(TAG, "esp_bt_controller_init failed: %d", ret);
+        return ret;
+    }
+
+    ret = esp_bt_controller_enable(mode);
+    if (ret) {
+        ESP_LOGE(TAG, "esp_bt_controller_enable failed: %d", ret);
+        return ret;
+    }
+
+    ret = esp_bluedroid_init();
+    if (ret) {
+        ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", ret);
+        return ret;
+    }
+
+    ret = esp_bluedroid_enable();
+    if (ret) {
+        ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", ret);
+        return ret;
+    }
+
+    if (mode & ESP_BT_MODE_CLASSIC_BT) {
+        ret = init_bt_gap();
+        if (ret) {
+            return ret;
+        }
+    }
+
+    if (mode & ESP_BT_MODE_BLE) {
+        ret = init_ble_gap();
+        if (ret) {
+            return ret;
+        }
+    }
+    return ret;
+}
+
+
+
+
+esp_err_t esp_hid_gap_init(uint8_t mode)
+{
+    esp_err_t ret;
+    if (!mode || mode > ESP_BT_MODE_BTDM) {
+        ESP_LOGE(TAG, "Invalid mode given!");
+        return ESP_FAIL;
+    }
+
+    if (bt_hidh_cb_semaphore != NULL) {
+        ESP_LOGE(TAG, "Already initialised");
+        return ESP_FAIL;
+    }
+
+    bt_hidh_cb_semaphore = xSemaphoreCreateBinary();
+    if (bt_hidh_cb_semaphore == NULL) {
+        ESP_LOGE(TAG, "xSemaphoreCreateMutex failed!");
+        return ESP_FAIL;
+    }
+
+    ble_hidh_cb_semaphore = xSemaphoreCreateBinary();
+    if (ble_hidh_cb_semaphore == NULL) {
+        ESP_LOGE(TAG, "xSemaphoreCreateMutex failed!");
+        vSemaphoreDelete(bt_hidh_cb_semaphore);
+        bt_hidh_cb_semaphore = NULL;
+        return ESP_FAIL;
+    }
+
+    ret = init_low_level(mode);
+    if (ret != ESP_OK) {
+        vSemaphoreDelete(bt_hidh_cb_semaphore);
+        bt_hidh_cb_semaphore = NULL;
+        vSemaphoreDelete(ble_hidh_cb_semaphore);
+        ble_hidh_cb_semaphore = NULL;
+        return ret;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_result_t **results)
+{
+    if (num_bt_scan_results || bt_scan_results || num_ble_scan_results || ble_scan_results) {
+        ESP_LOGE(TAG, "There are old scan results. Free them first!");
+        return ESP_FAIL;
+    }
+
+    if (start_ble_scan(seconds) == ESP_OK) {
+        if (start_bt_scan(seconds) == ESP_OK) {
+            WAIT_BT_CB();
+        }
+        WAIT_BLE_CB();
+    } else {
+        return ESP_FAIL;
+    }
+
+    *num_results = num_bt_scan_results + num_ble_scan_results;
+    *results = bt_scan_results;
+    if (num_bt_scan_results) {
+        while (bt_scan_results->next != NULL) {
+            bt_scan_results = bt_scan_results->next;
+        }
+        bt_scan_results->next = ble_scan_results;
+    } else {
+        *results = ble_scan_results;
+    }
+
+    num_bt_scan_results = 0;
+    bt_scan_results = NULL;
+    num_ble_scan_results = 0;
+    ble_scan_results = NULL;
+    return ESP_OK;
+}

+ 68 - 0
examples/bluetooth/esp_hid_host/main/esp_hid_gap.h

@@ -0,0 +1,68 @@
+// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef _ESP_HID_GAP_H_
+#define _ESP_HID_GAP_H_
+
+#include "esp_err.h"
+#include "esp_log.h"
+
+#include "esp_bt.h"
+#include "esp_bt_defs.h"
+#include "esp_bt_main.h"
+#include "esp_gattc_api.h"
+#include "esp_gatt_defs.h"
+#include "esp_gap_ble_api.h"
+#include "esp_gap_bt_api.h"
+#include "esp_hid_common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct esp_hidh_scan_result_s {
+    struct esp_hidh_scan_result_s *next;
+
+    esp_bd_addr_t bda;
+    const char *name;
+    int8_t rssi;
+    esp_hid_usage_t usage;
+    esp_hid_transport_t transport; //BT, BLE or USB
+    union {
+        struct {
+            esp_bt_cod_t cod;
+            esp_bt_uuid_t uuid;
+        } bt;
+        struct {
+            esp_ble_addr_type_t addr_type;
+            uint16_t appearance;
+        } ble;
+    };
+} esp_hid_scan_result_t;
+
+esp_err_t esp_hid_gap_init(uint8_t mode);
+esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_result_t **results);
+void esp_hid_scan_results_free(esp_hid_scan_result_t *results);
+
+esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name);
+esp_err_t esp_hid_ble_gap_adv_start(void);
+
+void print_uuid(esp_bt_uuid_t *uuid);
+const char *ble_addr_type_str(esp_ble_addr_type_t ble_addr_type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ESP_HIDH_GAP_H_ */

+ 134 - 0
examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c

@@ -0,0 +1,134 @@
+/* This example code is in the Public Domain (or CC0 licensed, at your option.)
+   Unless required by applicable law or agreed to in writing, this software is
+   distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_system.h"
+#include "esp_wifi.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "nvs_flash.h"
+#include "esp_bt.h"
+#include "esp_bt_defs.h"
+#include "esp_gap_ble_api.h"
+#include "esp_gatts_api.h"
+#include "esp_gatt_defs.h"
+#include "esp_bt_main.h"
+#include "esp_bt_device.h"
+
+#include "esp_hidh.h"
+#include "esp_hid_gap.h"
+
+static const char *TAG = "ESP_HIDH_DEMO";
+
+void hidh_callback(void *handler_args, esp_event_base_t base, int32_t id, void *event_data)
+{
+    esp_hidh_event_t event = (esp_hidh_event_t)id;
+    esp_hidh_event_data_t *param = (esp_hidh_event_data_t *)event_data;
+
+    switch (event) {
+    case ESP_HIDH_OPEN_EVENT: {
+        const uint8_t *bda = esp_hidh_dev_bda_get(param->open.dev);
+        ESP_LOGI(TAG, ESP_BD_ADDR_STR " OPEN: %s", ESP_BD_ADDR_HEX(bda), esp_hidh_dev_name_get(param->open.dev));
+        esp_hidh_dev_dump(param->open.dev, stdout);
+        break;
+    }
+    case ESP_HIDH_BATTERY_EVENT: {
+        const uint8_t *bda = esp_hidh_dev_bda_get(param->battery.dev);
+        ESP_LOGI(TAG, ESP_BD_ADDR_STR " BATTERY: %d%%", ESP_BD_ADDR_HEX(bda), param->battery.level);
+        break;
+    }
+    case ESP_HIDH_INPUT_EVENT: {
+        const uint8_t *bda = esp_hidh_dev_bda_get(param->input.dev);
+        ESP_LOGI(TAG, ESP_BD_ADDR_STR " INPUT: %8s, MAP: %2u, ID: %3u, Len: %d, Data:", ESP_BD_ADDR_HEX(bda), esp_hid_usage_str(param->input.usage), param->input.map_index, param->input.report_id, param->input.length);
+        ESP_LOG_BUFFER_HEX(TAG, param->input.data, param->input.length);
+        break;
+    }
+    case ESP_HIDH_FEATURE_EVENT: {
+        const uint8_t *bda = esp_hidh_dev_bda_get(param->feature.dev);
+        ESP_LOGI(TAG, ESP_BD_ADDR_STR " FEATURE: %8s, MAP: %2u, ID: %3u, Len: %d", ESP_BD_ADDR_HEX(bda), esp_hid_usage_str(param->feature.usage), param->feature.map_index, param->feature.report_id, param->feature.length);
+        ESP_LOG_BUFFER_HEX(TAG, param->feature.data, param->feature.length);
+        break;
+    }
+    case ESP_HIDH_CLOSE_EVENT: {
+        const uint8_t *bda = esp_hidh_dev_bda_get(param->close.dev);
+        ESP_LOGI(TAG, ESP_BD_ADDR_STR " CLOSE: '%s' %s", ESP_BD_ADDR_HEX(bda), esp_hidh_dev_name_get(param->close.dev), esp_hid_disconnect_reason_str(esp_hidh_dev_transport_get(param->close.dev), param->close.reason));
+        //MUST call this function to free all allocated memory by this device
+        esp_hidh_dev_free(param->close.dev);
+        break;
+    }
+    default:
+        ESP_LOGI(TAG, "EVENT: %d", event);
+        break;
+    }
+}
+
+#define SCAN_DURATION_SECONDS 5
+
+void hid_demo_task(void *pvParameters)
+{
+    size_t results_len = 0;
+    esp_hid_scan_result_t *results = NULL;
+    ESP_LOGI(TAG, "SCAN...");
+    //start scan for HID devices
+    esp_hid_scan(SCAN_DURATION_SECONDS, &results_len, &results);
+    ESP_LOGI(TAG, "SCAN: %u results", results_len);
+    if (results_len) {
+        esp_hid_scan_result_t *r = results;
+        esp_hid_scan_result_t *cr = NULL;
+        while (r) {
+            printf("  %s: " ESP_BD_ADDR_STR ", ", (r->transport == ESP_HID_TRANSPORT_BLE) ? "BLE" : "BT ", ESP_BD_ADDR_HEX(r->bda));
+            printf("RSSI: %d, ", r->rssi);
+            printf("USAGE: %s, ", esp_hid_usage_str(r->usage));
+            if (r->transport == ESP_HID_TRANSPORT_BLE) {
+                cr = r;
+                printf("APPEARANCE: 0x%04x, ", r->ble.appearance);
+                printf("ADDR_TYPE: '%s', ", ble_addr_type_str(r->ble.addr_type));
+            } else {
+                cr = r;
+                printf("COD: %s[", esp_hid_cod_major_str(r->bt.cod.major));
+                esp_hid_cod_minor_print(r->bt.cod.minor, stdout);
+                printf("] srv 0x%03x, ", r->bt.cod.service);
+                print_uuid(&r->bt.uuid);
+                printf(", ");
+            }
+            printf("NAME: %s ", r->name ? r->name : "");
+            printf("\n");
+            r = r->next;
+        }
+        if (cr) {
+            //open the last result
+            esp_hidh_dev_open(cr->bda, cr->transport, cr->ble.addr_type);
+        }
+        //free the results
+        esp_hid_scan_results_free(results);
+    }
+    vTaskDelete(NULL);
+}
+
+void app_main(void)
+{
+    esp_err_t ret;
+    ret = nvs_flash_init();
+    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
+        ESP_ERROR_CHECK(nvs_flash_erase());
+        ret = nvs_flash_init();
+    }
+    ESP_ERROR_CHECK( ret );
+    ESP_ERROR_CHECK( esp_hid_gap_init(ESP_BT_MODE_BTDM) );
+    ESP_ERROR_CHECK( esp_ble_gattc_register_callback(esp_hidh_gattc_event_handler) );
+    esp_hidh_config_t config = {
+        .callback = hidh_callback,
+    };
+    ESP_ERROR_CHECK( esp_hidh_init(&config) );
+
+    xTaskCreate(&hid_demo_task, "hid_task", 6 * 1024, NULL, 2, NULL);
+}

+ 6 - 0
examples/bluetooth/esp_hid_host/sdkconfig.defaults

@@ -0,0 +1,6 @@
+CONFIG_BT_ENABLED=y
+CONFIG_BTDM_CTRL_MODE_BTDM=y
+CONFIG_BTDM_CTRL_HCI_MODE_VHCI=y
+CONFIG_BT_BLUEDROID_ENABLED=y
+CONFIG_BT_CLASSIC_ENABLED=y
+CONFIG_BT_HID_HOST_ENABLED=y

+ 4 - 4
tools/ci/config/target-test.yml

@@ -341,7 +341,7 @@ example_test_012:
 
 UT_001:
   extends: .unit_test_template
-  parallel: 37
+  parallel: 38
   tags:
     - ESP32_IDF
     - UT_T1_1
@@ -350,7 +350,7 @@ UT_001:
 
 UT_002:
   extends: .unit_test_template
-  parallel: 14
+  parallel: 15
   tags:
     - ESP32_IDF
     - UT_T1_1
@@ -424,7 +424,7 @@ UT_017:
 
 UT_018:
   extends: .unit_test_template
-  parallel: 4
+  parallel: 5
   tags:
     - ESP32_IDF
     - UT_T1_1
@@ -501,7 +501,7 @@ UT_034:
 
 UT_035:
   extends: .unit_test_s2_template
-  parallel: 33
+  parallel: 34
   tags:
     - ESP32S2_IDF
     - UT_T1_1