Add HID Support to IDF

- Adds HID Host support in Buedroid
- Adds BLE HID Host and Device support
- Adds some general HID utilities and definitions to help integrate with other stacks and native USB
This commit is contained in:
Hristo Gochkov 2020-04-29 17:24:01 +08:00 committed by Jiang Jiang Jian
parent ef47839628
commit 25281ef4de
57 changed files with 9529 additions and 13 deletions

View file

@ -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"

View file

@ -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 \

View file

@ -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

View file

@ -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

View file

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

View file

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

View file

@ -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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

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

View file

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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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_ */

View file

@ -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

View file

@ -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_ */

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)

View file

@ -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

View file

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

View file

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

View file

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

View file

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

View file

@ -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_ */

View file

@ -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

View file

@ -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)

View file

@ -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

View file

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

View file

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

View file

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

View file

@ -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_ */

View file

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

View file

@ -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

View file

@ -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