diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index 6221554058..106224f97a 100644 --- a/components/bt/CMakeLists.txt +++ b/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" diff --git a/components/bt/component.mk b/components/bt/component.mk index 5c1a29e6c6..6b99585c00 100644 --- a/components/bt/component.mk +++ b/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 \ diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index 782c3793a3..435688052b 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/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 diff --git a/components/bt/host/bluedroid/bta/hh/bta_hh_act.c b/components/bt/host/bluedroid/bta/hh/bta_hh_act.c index 7b2f50afcc..99d54f284f 100644 --- a/components/bt/host/bluedroid/bta/hh/bta_hh_act.c +++ b/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 diff --git a/components/bt/host/bluedroid/bta/hh/bta_hh_api.c b/components/bt/host/bluedroid/bta/hh/bta_hh_api.c index 43f7542872..157e4b6ac6 100644 --- a/components/bt/host/bluedroid/bta/hh/bta_hh_api.c +++ b/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 diff --git a/components/bt/host/bluedroid/bta/hh/bta_hh_main.c b/components/bt/host/bluedroid/bta/hh/bta_hh_main.c index fd74672780..a5ca868723 100644 --- a/components/bt/host/bluedroid/bta/hh/bta_hh_main.c +++ b/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 diff --git a/components/bt/host/bluedroid/bta/hh/bta_hh_utils.c b/components/bt/host/bluedroid/bta/hh/bta_hh_utils.c index 0b45e8d2de..121e11834b 100644 --- a/components/bt/host/bluedroid/bta/hh/bta_hh_utils.c +++ b/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 diff --git a/components/bt/host/bluedroid/btc/profile/std/hid/hidh_api.c b/components/bt/host/bluedroid/btc/profile/std/hid/hidh_api.c new file mode 100644 index 0000000000..e5a6ab6fcd --- /dev/null +++ b/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 +#include +#include + +#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 diff --git a/components/bt/host/bluedroid/btc/profile/std/hid/hidh_conn.c b/components/bt/host/bluedroid/btc/profile/std/hid/hidh_conn.c new file mode 100644 index 0000000000..83a4c484af --- /dev/null +++ b/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 +#include +#include + + +#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 diff --git a/components/bt/host/bluedroid/btc/profile/std/hid/include/hid_conn.h b/components/bt/host/bluedroid/btc/profile/std/hid/include/hid_conn.h index 320b78fa32..320694c537 100644 --- a/components/bt/host/bluedroid/btc/profile/std/hid/include/hid_conn.h +++ b/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 diff --git a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h index 63da318a86..ebf6ebe99f 100644 --- a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h +++ b/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 diff --git a/components/bt/host/bluedroid/common/include/common/bt_target.h b/components/bt/host/bluedroid/common/include/common/bt_target.h index 309ea6b1d3..9331d919b3 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/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 diff --git a/components/bt/host/bluedroid/common/include/common/bt_trace.h b/components/bt/host/bluedroid/common/include/common/bt_trace.h index 780894887e..7afde68815 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_trace.h +++ b/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 diff --git a/components/esp_hid/CMakeLists.txt b/components/esp_hid/CMakeLists.txt new file mode 100644 index 0000000000..8095aa3fb4 --- /dev/null +++ b/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) diff --git a/components/esp_hid/component.mk b/components/esp_hid/component.mk new file mode 100644 index 0000000000..938edb9017 --- /dev/null +++ b/components/esp_hid/component.mk @@ -0,0 +1,3 @@ +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_PRIV_INCLUDEDIRS := private +COMPONENT_SRCDIRS := src diff --git a/components/esp_hid/include/esp_hid_common.h b/components/esp_hid/include/esp_hid_common.h new file mode 100644 index 0000000000..9fd7a2c637 --- /dev/null +++ b/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 +#include +#include +#include + +/* 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 diff --git a/components/esp_hid/include/esp_hidd.h b/components/esp_hid/include/esp_hidd.h new file mode 100644 index 0000000000..ef596c6b2b --- /dev/null +++ b/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 diff --git a/components/esp_hid/include/esp_hidd_gatts.h b/components/esp_hid/include/esp_hidd_gatts.h new file mode 100644 index 0000000000..732ab1a96a --- /dev/null +++ b/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 diff --git a/components/esp_hid/include/esp_hidd_transport.h b/components/esp_hid/include/esp_hidd_transport.h new file mode 100644 index 0000000000..ee8d681be4 --- /dev/null +++ b/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 diff --git a/components/esp_hid/include/esp_hidh.h b/components/esp_hid/include/esp_hidh.h new file mode 100644 index 0000000000..b1a8264307 --- /dev/null +++ b/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 diff --git a/components/esp_hid/include/esp_hidh_bluedroid.h b/components/esp_hid/include/esp_hidh_bluedroid.h new file mode 100644 index 0000000000..e89d0b4031 --- /dev/null +++ b/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 diff --git a/components/esp_hid/include/esp_hidh_gattc.h b/components/esp_hid/include/esp_hidh_gattc.h new file mode 100644 index 0000000000..e58141dca0 --- /dev/null +++ b/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 diff --git a/components/esp_hid/include/esp_hidh_transport.h b/components/esp_hid/include/esp_hidh_transport.h new file mode 100644 index 0000000000..22138aef8e --- /dev/null +++ b/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 diff --git a/components/esp_hid/private/ble_hidd.h b/components/esp_hid/private/ble_hidd.h new file mode 100644 index 0000000000..087d816c03 --- /dev/null +++ b/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 diff --git a/components/esp_hid/private/ble_hidh.h b/components/esp_hid/private/ble_hidh.h new file mode 100644 index 0000000000..131f064805 --- /dev/null +++ b/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 diff --git a/components/esp_hid/private/bt_hidh.h b/components/esp_hid/private/bt_hidh.h new file mode 100644 index 0000000000..3286c89256 --- /dev/null +++ b/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 diff --git a/components/esp_hid/private/esp_bt_hh_api.h b/components/esp_hid/private/esp_bt_hh_api.h new file mode 100644 index 0000000000..0d438c28b3 --- /dev/null +++ b/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_ */ diff --git a/components/esp_hid/private/esp_hidd_private.h b/components/esp_hid/private/esp_hidd_private.h new file mode 100644 index 0000000000..544fd9b58a --- /dev/null +++ b/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 diff --git a/components/esp_hid/private/esp_hidh_private.h b/components/esp_hid/private/esp_hidh_private.h new file mode 100644 index 0000000000..1cd5e50605 --- /dev/null +++ b/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_ */ diff --git a/components/esp_hid/src/ble_hidd.c b/components/esp_hid/src/ble_hidd.c new file mode 100644 index 0000000000..b62ce623a2 --- /dev/null +++ b/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 +#include + +#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 */ diff --git a/components/esp_hid/src/ble_hidh.c b/components/esp_hid/src/ble_hidh.c new file mode 100644 index 0000000000..5fe54f2fa7 --- /dev/null +++ b/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 +#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 */ diff --git a/components/esp_hid/src/bt_hidh.c b/components/esp_hid/src/bt_hidh.c new file mode 100644 index 0000000000..1a17e6aa35 --- /dev/null +++ b/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 +#include + +#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 */ diff --git a/components/esp_hid/src/esp_hid_common.c b/components/esp_hid/src/esp_hid_common.c new file mode 100644 index 0000000000..c8c7214bed --- /dev/null +++ b/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 +#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; +} + diff --git a/components/esp_hid/src/esp_hidd.c b/components/esp_hid/src/esp_hidd.c new file mode 100644 index 0000000000..e6e3c57988 --- /dev/null +++ b/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); +} diff --git a/components/esp_hid/src/esp_hidh.c b/components/esp_hid/src/esp_hidh.c new file mode 100644 index 0000000000..bcb2c42206 --- /dev/null +++ b/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 +#include +#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 */ diff --git a/components/esp_hid/test/CMakeLists.txt b/components/esp_hid/test/CMakeLists.txt new file mode 100644 index 0000000000..48087e3313 --- /dev/null +++ b/components/esp_hid/test/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRC_DIRS "." + INCLUDE_DIRS "." + REQUIRES unity test_utils esp_hid) diff --git a/components/esp_hid/test/README.md b/components/esp_hid/test/README.md new file mode 100644 index 0000000000..1a3fb7d909 --- /dev/null +++ b/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; ireports_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); +} + +``` diff --git a/components/esp_hid/test/component.mk b/components/esp_hid/test/component.mk new file mode 100644 index 0000000000..ce464a212a --- /dev/null +++ b/components/esp_hid/test/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/esp_hid/test/hid_descriptor.h b/components/esp_hid/test/hid_descriptor.h new file mode 100644 index 0000000000..56142d001a --- /dev/null +++ b/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 +}; diff --git a/components/esp_hid/test/test_esp_hid.c b/components/esp_hid/test/test_esp_hid.c new file mode 100644 index 0000000000..2bdde5b30e --- /dev/null +++ b/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 +#include +#include +#include +#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); +} diff --git a/examples/bluetooth/esp_hid_device/CMakeLists.txt b/examples/bluetooth/esp_hid_device/CMakeLists.txt new file mode 100644 index 0000000000..f3f8b3e237 --- /dev/null +++ b/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) diff --git a/examples/bluetooth/esp_hid_device/Makefile b/examples/bluetooth/esp_hid_device/Makefile new file mode 100644 index 0000000000..4b667be8ca --- /dev/null +++ b/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 diff --git a/examples/bluetooth/esp_hid_device/main/CMakeLists.txt b/examples/bluetooth/esp_hid_device/main/CMakeLists.txt new file mode 100644 index 0000000000..8e350336c8 --- /dev/null +++ b/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() diff --git a/examples/bluetooth/esp_hid_device/main/component.mk b/examples/bluetooth/esp_hid_device/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/bluetooth/esp_hid_device/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c b/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c new file mode 100644 index 0000000000..014849a216 --- /dev/null +++ b/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 +#include +#include + +#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); + +} diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c new file mode 100644 index 0000000000..428a1356a5 --- /dev/null +++ b/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 +#include + +#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(¶m->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; +} diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.h b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.h new file mode 100644 index 0000000000..f2fb240161 --- /dev/null +++ b/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_ */ diff --git a/examples/bluetooth/esp_hid_device/sdkconfig.defaults b/examples/bluetooth/esp_hid_device/sdkconfig.defaults new file mode 100644 index 0000000000..ff04ba6104 --- /dev/null +++ b/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 diff --git a/examples/bluetooth/esp_hid_host/CMakeLists.txt b/examples/bluetooth/esp_hid_host/CMakeLists.txt new file mode 100644 index 0000000000..e95c3aaf67 --- /dev/null +++ b/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) diff --git a/examples/bluetooth/esp_hid_host/Makefile b/examples/bluetooth/esp_hid_host/Makefile new file mode 100644 index 0000000000..d012327d4c --- /dev/null +++ b/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 diff --git a/examples/bluetooth/esp_hid_host/main/CMakeLists.txt b/examples/bluetooth/esp_hid_host/main/CMakeLists.txt new file mode 100644 index 0000000000..2ea7183d61 --- /dev/null +++ b/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() diff --git a/examples/bluetooth/esp_hid_host/main/component.mk b/examples/bluetooth/esp_hid_host/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/bluetooth/esp_hid_host/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/bluetooth/esp_hid_host/main/esp_hid_gap.c b/examples/bluetooth/esp_hid_host/main/esp_hid_gap.c new file mode 100644 index 0000000000..428a1356a5 --- /dev/null +++ b/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 +#include + +#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(¶m->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; +} diff --git a/examples/bluetooth/esp_hid_host/main/esp_hid_gap.h b/examples/bluetooth/esp_hid_host/main/esp_hid_gap.h new file mode 100644 index 0000000000..f2fb240161 --- /dev/null +++ b/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_ */ diff --git a/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c b/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c new file mode 100644 index 0000000000..065aa85ec6 --- /dev/null +++ b/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 +#include +#include + +#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); +} diff --git a/examples/bluetooth/esp_hid_host/sdkconfig.defaults b/examples/bluetooth/esp_hid_host/sdkconfig.defaults new file mode 100644 index 0000000000..ff04ba6104 --- /dev/null +++ b/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 diff --git a/tools/ci/config/target-test.yml b/tools/ci/config/target-test.yml index 78e128abb2..c0d18b625a 100644 --- a/tools/ci/config/target-test.yml +++ b/tools/ci/config/target-test.yml @@ -336,7 +336,7 @@ example_test_012: UT_001: extends: .unit_test_template - parallel: 37 + parallel: 38 tags: - ESP32_IDF - UT_T1_1 @@ -345,7 +345,7 @@ UT_001: UT_002: extends: .unit_test_template - parallel: 14 + parallel: 15 tags: - ESP32_IDF - UT_T1_1 @@ -419,7 +419,7 @@ UT_017: UT_018: extends: .unit_test_template - parallel: 4 + parallel: 5 tags: - ESP32_IDF - UT_T1_1 @@ -495,7 +495,7 @@ UT_034: UT_035: extends: .unit_test_s2_template - parallel: 33 + parallel: 34 tags: - ESP32S2_IDF - UT_T1_1