From 96ed1ae7a7980ff38b3e780de9ee00925f7ce811 Mon Sep 17 00:00:00 2001 From: Roshan Bangar Date: Mon, 31 Jul 2023 15:32:07 +0530 Subject: [PATCH] feat(nimble): added HID over Gatt profile support --- components/bt/CMakeLists.txt | 4 + components/bt/host/nimble/Kconfig.in | 23 + components/bt/host/nimble/nimble | 2 +- .../host/nimble/port/include/esp_nimble_cfg.h | 23 + components/esp_hid/CMakeLists.txt | 5 + components/esp_hid/include/esp_hidh_nimble.h | 28 + .../esp_hid/include/esp_hidh_transport.h | 22 +- components/esp_hid/private/ble_hidd.h | 20 +- components/esp_hid/private/ble_hidh.h | 24 +- components/esp_hid/private/esp_hidh_private.h | 25 +- components/esp_hid/src/esp_hid_common.c | 18 +- components/esp_hid/src/esp_hidd.c | 4 +- components/esp_hid/src/esp_hidh.c | 111 +- components/esp_hid/src/nimble_hidd.c | 710 ++++++++++++ components/esp_hid/src/nimble_hidh.c | 1020 +++++++++++++++++ .../esp_hid_device/main/Kconfig.projbuild | 29 + .../esp_hid_device/main/esp_hid_device_main.c | 328 +++++- .../esp_hid_device/main/esp_hid_gap.c | 260 ++++- .../esp_hid_device/main/esp_hid_gap.h | 16 +- .../esp_hid_host/main/Kconfig.projbuild | 8 + .../bluetooth/esp_hid_host/main/esp_hid_gap.c | 318 ++++- .../bluetooth/esp_hid_host/main/esp_hid_gap.h | 25 +- .../esp_hid_host/main/esp_hid_host_main.c | 53 +- tools/ci/check_copyright_ignore.txt | 4 - 24 files changed, 3002 insertions(+), 78 deletions(-) create mode 100644 components/esp_hid/include/esp_hidh_nimble.h create mode 100644 components/esp_hid/src/nimble_hidd.c create mode 100644 components/esp_hid/src/nimble_hidh.c diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index a1a02cb41a..b16b8a3e94 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -621,6 +621,8 @@ if(CONFIG_BT_ENABLED) host/nimble/nimble/nimble/host/services/prox/include host/nimble/nimble/nimble/host/services/cts/include host/nimble/nimble/nimble/host/services/tps/include + host/nimble/nimble/nimble/host/services/hid/include + host/nimble/nimble/nimble/host/services/sps/include host/nimble/nimble/nimble/host/util/include host/nimble/nimble/nimble/host/store/ram/include host/nimble/nimble/nimble/host/store/config/include @@ -641,6 +643,8 @@ if(CONFIG_BT_ENABLED) "host/nimble/nimble/nimble/host/services/lls/src/ble_svc_lls.c" "host/nimble/nimble/nimble/host/services/prox/src/ble_svc_prox.c" "host/nimble/nimble/nimble/host/services/cts/src/ble_svc_cts.c" + "host/nimble/nimble/nimble/host/services/hid/src/ble_svc_hid.c" + "host/nimble/nimble/nimble/host/services/sps/src/ble_svc_sps.c" "host/nimble/nimble/nimble/host/src/ble_hs_conn.c" "host/nimble/nimble/nimble/host/src/ble_store_util.c" "host/nimble/nimble/nimble/host/src/ble_sm.c" diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 38f956e543..64204c6ea0 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -818,6 +818,29 @@ menu "GAP Service" endmenu +menu "BLE Services" + menuconfig BT_NIMBLE_HID_SERVICE + bool "HID service" + depends on BT_NIMBLE_ENABLED + default n + help + Enable HID service support + + config BT_NIMBLE_SVC_HID_MAX_INSTANCES + depends on BT_NIMBLE_HID_SERVICE + int "Maximum HID service instances" + default 2 + help + Defines maximum number of HID service instances + + config BT_NIMBLE_SVC_HID_MAX_RPTS + depends on BT_NIMBLE_HID_SERVICE + int "Maximum HID Report characteristics per service instance" + default 3 + help + Defines maximum number of report characteristics per service instance +endmenu + config BT_NIMBLE_VS_SUPPORT bool "Enable support for VSC and VSE" help diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 08ac3ca677..34e3842741 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 08ac3ca677be417e0087159629d0359c4a9a1585 +Subproject commit 34e3842741f598c00082ef7d2cea5f22615366f6 diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index dff3469ba3..ea4efc7fb8 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -908,6 +908,20 @@ #ifndef MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_READ_PERM #define MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_READ_PERM (0) #endif + +/*** nimble/host/services/hid */ +#ifndef MYNEWT_VAL_BLE_SVC_HID_SERVICE +#define MYNEWT_VAL_BLE_SVC_HID_SERVICE CONFIG_BT_NIMBLE_HID_SERVICE +#endif + +#ifndef MYNEWT_VAL_BLE_SVC_HID_MAX_RPTS +#define MYNEWT_VAL_BLE_SVC_HID_MAX_RPTS CONFIG_BT_NIMBLE_SVC_HID_MAX_RPTS +#endif + +#ifndef MYNEWT_VAL_BLE_SVC_HID_MAX_SVC_INSTANCES +#define MYNEWT_VAL_BLE_SVC_HID_MAX_SVC_INSTANCES CONFIG_BT_NIMBLE_SVC_HID_MAX_INSTANCES +#endif + #ifndef MYNEWT_VAL_BLE_MESH_ADV_TASK_PRIO #define MYNEWT_VAL_BLE_MESH_ADV_TASK_PRIO (9) #endif @@ -1570,6 +1584,15 @@ #define MYNEWT_VAL_BLE_SVC_DIS_SYSTEM_ID_READ_PERM (-1) #endif +#ifndef MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_DEFAULT +#define MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_DEFAULT (NULL) +#endif + +/* Value copied from BLE_SVC_DIS_DEFAULT_READ_PERM */ +#ifndef MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_READ_PERM +#define MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_READ_PERM (-1) +#endif + /*** @apache-mynewt-nimble/nimble/host/services/gap */ #ifndef MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE #define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE diff --git a/components/esp_hid/CMakeLists.txt b/components/esp_hid/CMakeLists.txt index a2edf17a16..d91bfd4e79 100644 --- a/components/esp_hid/CMakeLists.txt +++ b/components/esp_hid/CMakeLists.txt @@ -19,6 +19,11 @@ if(CONFIG_BT_ENABLED) "src/bt_hidh.c" "src/bt_hidd.c") endif() + if(CONFIG_BT_NIMBLE_ENABLED) + list(APPEND srcs + "src/nimble_hidd.c" + "src/nimble_hidh.c") + endif() endif() idf_component_register(SRCS "${srcs}" diff --git a/components/esp_hid/include/esp_hidh_nimble.h b/components/esp_hid/include/esp_hidh_nimble.h new file mode 100644 index 0000000000..b9f95d05a1 --- /dev/null +++ b/components/esp_hid/include/esp_hidh_nimble.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif +#include "sdkconfig.h" + +#if CONFIG_BT_NIMBLE_ENABLED +/** + * @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(uint8_t *bda, esp_hid_transport_t transport, uint8_t remote_addr_type); +#endif /* CONFIG_BT_NIMBLE_ENABLED */ + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hid/include/esp_hidh_transport.h b/components/esp_hid/include/esp_hidh_transport.h index 22138aef8e..626005bb42 100644 --- a/components/esp_hid/include/esp_hidh_transport.h +++ b/components/esp_hid/include/esp_hidh_transport.h @@ -1,16 +1,8 @@ -// 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. +/* + * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once @@ -28,6 +20,10 @@ extern "C" { #include "esp_hidh_bluedroid.h" #endif +#if CONFIG_BT_NIMBLE_ENABLED +#include "esp_hidh_nimble.h" +#endif + #ifdef __cplusplus } #endif diff --git a/components/esp_hid/private/ble_hidd.h b/components/esp_hid/private/ble_hidd.h index 087d816c03..7bcd732732 100644 --- a/components/esp_hid/private/ble_hidd.h +++ b/components/esp_hid/private/ble_hidd.h @@ -1,16 +1,8 @@ -// 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. +/* + * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once @@ -22,7 +14,7 @@ extern "C" { #endif -#if CONFIG_GATTS_ENABLE +#if CONFIG_GATTS_ENABLE || CONFIG_BT_NIMBLE_ENABLED 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); diff --git a/components/esp_hid/private/ble_hidh.h b/components/esp_hid/private/ble_hidh.h index 131f064805..331579bd21 100644 --- a/components/esp_hid/private/ble_hidh.h +++ b/components/esp_hid/private/ble_hidh.h @@ -1,16 +1,8 @@ -// 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. +/* + * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once @@ -20,12 +12,16 @@ extern "C" { #endif -#if CONFIG_GATTC_ENABLE +#if CONFIG_GATTC_ENABLE || CONFIG_BT_NIMBLE_ENABLED esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config); esp_err_t esp_ble_hidh_deinit(void); +#if CONFIG_BT_NIMBLE_ENABLED +esp_hidh_dev_t *esp_ble_hidh_dev_open(uint8_t *bda, uint8_t address_type); +#else esp_hidh_dev_t *esp_ble_hidh_dev_open(esp_bd_addr_t bda, esp_ble_addr_type_t address_type); +#endif #endif /* CONFIG_GATTC_ENABLE */ diff --git a/components/esp_hid/private/esp_hidh_private.h b/components/esp_hid/private/esp_hidh_private.h index a6ffda9443..f567508d61 100644 --- a/components/esp_hid/private/esp_hidh_private.h +++ b/components/esp_hid/private/esp_hidh_private.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -17,6 +17,9 @@ #include "esp_event.h" #include "sys/queue.h" #include "esp_timer.h" +#if CONFIG_BT_NIMBLE_ENABLED +#include "nimble/ble.h" +#endif #ifdef __cplusplus extern "C" { @@ -52,7 +55,11 @@ struct esp_hidh_dev_s { esp_timer_handle_t trans_timer; //transactiion timer uint8_t report_type; //Get_Report tansaction report_type uint8_t report_id; //Get_Report tansaction report_id +#if CONFIG_BT_NIMBLE_ENABLED + uint8_t *protocol_mode; // protocol mode is unique for each hid service instance +#else uint8_t protocol_mode; //device protocol mode +#endif bool connected; //we have all required data to communicate bool opened; //we opened the device manually, else the device connected to us bool added; //If lower layer has added the device @@ -82,6 +89,9 @@ struct esp_hidh_dev_s { #if CONFIG_BLUEDROID_ENABLED esp_bd_addr_t bda; #endif /* CONFIG_BLUEDROID_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED + uint8_t bda[6]; +#endif union { #if CONFIG_BT_HID_HOST_ENABLED @@ -102,6 +112,15 @@ struct esp_hidh_dev_s { uint16_t battery_ccc_handle; } ble; #endif /* CONFIG_GATTC_ENABLE */ +#if CONFIG_BT_NIMBLE_ENABLED + struct { + uint8_t address_type; + int conn_id; + uint16_t appearance; + uint16_t battery_handle; + uint16_t battery_ccc_handle; + } ble; +#endif }; TAILQ_ENTRY(esp_hidh_dev_s) devices; }; @@ -115,6 +134,10 @@ 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(uint8_t handle); //Classic Bluetooth Only esp_hidh_dev_t *esp_hidh_dev_get_by_conn_id(uint16_t conn_id); //BLE Only #endif /* CONFIG_BLUEDROID_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED +esp_hidh_dev_t *esp_hidh_dev_get_by_bda(uint8_t* bda); // BLE Only +esp_hidh_dev_t *esp_hidh_dev_get_by_conn_id(uint16_t conn_id); //BLE Only +#endif esp_hidh_dev_report_t *esp_hidh_dev_get_report_by_id_type_proto(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, uint8_t protocol_mode); 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); diff --git a/components/esp_hid/src/esp_hid_common.c b/components/esp_hid/src/esp_hid_common.c index b549d00663..69d987fcbf 100644 --- a/components/esp_hid/src/esp_hid_common.c +++ b/components/esp_hid/src/esp_hid_common.c @@ -1,16 +1,8 @@ -// 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. +/* + * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include "esp_log.h" diff --git a/components/esp_hid/src/esp_hidd.c b/components/esp_hid/src/esp_hidd.c index b3216023cc..571f963d65 100644 --- a/components/esp_hid/src/esp_hidd.c +++ b/components/esp_hid/src/esp_hidd.c @@ -8,7 +8,7 @@ #include "esp_hidd_private.h" #include "esp_event_base.h" -#if CONFIG_GATTS_ENABLE +#if CONFIG_GATTS_ENABLE || CONFIG_BT_NIMBLE_ENABLED #include "ble_hidd.h" #endif /* CONFIG_GATTS_ENABLE */ @@ -27,7 +27,7 @@ esp_err_t esp_hidd_dev_init(const esp_hid_device_config_t *config, esp_hid_trans } switch (transport) { -#if CONFIG_GATTS_ENABLE +#if CONFIG_GATTS_ENABLE || CONFIG_BT_NIMBLE_ENABLED case ESP_HID_TRANSPORT_BLE: ret = esp_ble_hidd_dev_init(dev, config, callback); break; diff --git a/components/esp_hid/src/esp_hidh.c b/components/esp_hid/src/esp_hidh.c index 5102105d6d..c4e18e0291 100644 --- a/components/esp_hid/src/esp_hidh.c +++ b/components/esp_hid/src/esp_hidh.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -88,7 +88,7 @@ esp_err_t esp_hidh_init(const esp_hidh_config_t *config) } #endif /* CONFIG_BT_HID_HOST_ENABLED */ -#if CONFIG_GATTC_ENABLE +#if CONFIG_GATTC_ENABLE || CONFIG_BT_NIMBLE_ENABLED if (err == ESP_OK) { err = esp_ble_hidh_init(config); } @@ -123,7 +123,7 @@ esp_err_t esp_hidh_deinit(void) } #endif /* CONFIG_BT_HID_HOST_ENABLED */ -#if CONFIG_GATTC_ENABLE +#if CONFIG_GATTC_ENABLE || CONFIG_BT_NIMBLE_ENABLED if (err == ESP_OK) { err = esp_ble_hidh_deinit(); } @@ -150,6 +150,11 @@ esp_hidh_dev_t *esp_hidh_dev_open(esp_bd_addr_t bda, esp_hid_transport_t transpo dev = esp_ble_hidh_dev_open(bda, (esp_ble_addr_type_t)remote_addr_type); } #endif /* CONFIG_GATTC_ENABLE */ +#if CONFIG_BT_NIMBLE_ENABLED + if (transport == ESP_HID_TRANSPORT_BLE) { + dev = esp_ble_hidh_dev_open(bda, remote_addr_type); + } +#endif /* CONFIG_BT_NIMBLE_ENABLED */ #if CONFIG_BT_HID_HOST_ENABLED if (transport == ESP_HID_TRANSPORT_BT) { dev = esp_bt_hidh_dev_open(bda); @@ -159,6 +164,19 @@ esp_hidh_dev_t *esp_hidh_dev_open(esp_bd_addr_t bda, esp_hid_transport_t transpo } #endif /* CONFIG_BLUEDROID_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED +esp_hidh_dev_t *esp_hidh_dev_open(uint8_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; + dev = esp_ble_hidh_dev_open(bda, remote_addr_type); + return dev; +} +#endif /* CONFIG_BT_NIMBLE_ENABLED */ + esp_err_t esp_hidh_dev_close(esp_hidh_dev_t *dev) { esp_err_t ret = ESP_OK; @@ -329,6 +347,14 @@ const uint8_t *esp_hidh_dev_bda_get(esp_hidh_dev_t *dev) esp_hidh_dev_unlock(dev); } #endif /* CONFIG_BLUEDROID_ENABLED */ + +#if CONFIG_BT_NIMBLE_ENABLED + if (esp_hidh_dev_exists(dev)) { + esp_hidh_dev_lock(dev); + ret = dev->bda; + esp_hidh_dev_unlock(dev); + } +#endif /* CONFIG_BT_NIMBLE_ENABLED */ return ret; } @@ -823,3 +849,82 @@ void esp_hidh_post_process_event_handler(void *event_handler_arg, esp_event_base } } #endif /* CONFIG_BLUEDROID_ENABLED */ + +#if CONFIG_BT_NIMBLE_ENABLED +esp_hidh_dev_t *esp_hidh_dev_get_by_bda(uint8_t *bda) +{ + esp_hidh_dev_t * d = NULL; + lock_devices(); + TAILQ_FOREACH(d, &s_esp_hidh_devices, devices) { + if (memcmp(bda, d->bda, sizeof(uint8_t) * 6) == 0) { + unlock_devices(); + return d; + } + } + unlock_devices(); + return NULL; +} + +esp_hidh_dev_t *esp_hidh_dev_get_by_conn_id(uint16_t conn_id) +{ + esp_hidh_dev_t * d = NULL; + lock_devices(); + TAILQ_FOREACH(d, &s_esp_hidh_devices, devices) { + if (d->ble.conn_id == conn_id) { + unlock_devices(); + return d; + } + } + unlock_devices(); + return NULL; +} + +/** + * The deep copy data append the end of the esp_hidh_event_data_t, move the data pointer to the correct address. This is + * a workaround way, it's better to use flexible array in the interface. + */ +void esp_hidh_preprocess_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, + void *event_data) +{ + esp_hidh_event_t event = (esp_hidh_event_t)event_id; + esp_hidh_event_data_t *param = (esp_hidh_event_data_t *)event_data; + + switch (event) { + case ESP_HIDH_INPUT_EVENT: + if (param->input.length && param->input.data) { + param->input.data = (uint8_t *)param + sizeof(esp_hidh_event_data_t); + } + break; + case ESP_HIDH_FEATURE_EVENT: + if (param->feature.length && param->feature.data) { + param->feature.data = (uint8_t *)param + sizeof(esp_hidh_event_data_t); + } + break; + default: + break; + } +} + +void esp_hidh_post_process_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, + void *event_data) +{ + esp_hidh_event_t event = (esp_hidh_event_t)event_id; + esp_hidh_event_data_t *param = (esp_hidh_event_data_t *)event_data; + + switch (event) { + case ESP_HIDH_OPEN_EVENT: + if (param->open.status != ESP_OK) { + esp_hidh_dev_t *dev = param->open.dev; + if (dev) { + esp_hidh_dev_free_inner(dev); + } + } + break; + case ESP_HIDH_CLOSE_EVENT: + esp_hidh_dev_free_inner(param->close.dev); + break; + default: + break; + } +} +#endif /* CONFIG_BT_NIMBLE_ENABLED */ diff --git a/components/esp_hid/src/nimble_hidd.c b/components/esp_hid/src/nimble_hidd.c new file mode 100644 index 0000000000..53631d980e --- /dev/null +++ b/components/esp_hid/src/nimble_hidd.c @@ -0,0 +1,710 @@ +/* + * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "ble_hidd.h" +#include "esp_hidd_private.h" +#include "esp_log.h" + +#include +#include +#include +#include "nimble/nimble_opt.h" +#include "host/ble_hs.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "host/ble_hs_hci.h" +#include "host/ble_att.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" +#include "services/bas/ble_svc_bas.h" +#include "services/hid/ble_svc_hid.h" +#include "services/dis/ble_svc_dis.h" +#include "services/sps/ble_svc_sps.h" + +#if CONFIG_BT_NIMBLE_HID_SERVICE + +static const char *TAG = "NIMBLE_HIDD"; +#define BLE_SVC_BAS_UUID16 0x180F + + +typedef struct esp_ble_hidd_dev_s esp_ble_hidd_dev_t; +// there can be only one BLE HID device +static esp_ble_hidd_dev_t *s_dev = NULL; + +typedef hidd_report_item_t hidd_le_report_item_t; + +typedef struct { + esp_hid_raw_report_map_t reports_map; + uint8_t reports_len; + hidd_le_report_item_t *reports; + uint16_t hid_svc; + uint16_t hid_control_handle; + uint16_t hid_protocol_handle; +} hidd_dev_map_t; + + + +struct esp_ble_hidd_dev_s { + esp_hidd_dev_t *dev; + esp_event_loop_handle_t event_loop_handle; + esp_hid_device_config_t config; + uint16_t appearance; + + bool connected; + uint16_t conn_id; + + uint8_t control; // 0x00 suspend, 0x01 suspend off + uint8_t protocol; // 0x00 boot, 0x01 report + + uint16_t bat_svc_handle; + uint16_t info_svc_handle; + struct ble_gatt_svc hid_incl_svc; + + uint16_t bat_level_handle; + uint8_t pnp[7]; /* something related to device info service */ + hidd_dev_map_t *devices; + uint8_t devices_len; +}; + +// HID Information characteristic value +static const uint8_t hidInfo[4] = { + 0x11, 0x01, // bcdHID (USB HID version) + 0x00, // bCountryCode + ESP_HID_FLAGS_REMOTE_WAKE | ESP_HID_FLAGS_NORMALLY_CONNECTABLE // Flags +}; + +static int create_hid_db(int device_index) +{ + int rc = 0; + struct ble_svc_hid_params hparams = {0}; + int report_mode_rpts = 0; + + /* fill hid info */ + memcpy(&hparams.hid_info, hidInfo, sizeof hparams.hid_info); + + /* fill report map */ + memcpy(&hparams.report_map, (uint8_t *)s_dev->devices[device_index].reports_map.data, s_dev->devices[device_index].reports_map.len); + hparams.report_map_len = s_dev->devices[device_index].reports_map.len; + hparams.external_rpt_ref = BLE_SVC_BAS_UUID16; + + /* fill protocol mode */ + hparams.proto_mode_present = 1; + hparams.proto_mode = s_dev->protocol; + + for (uint8_t i = 0; i < s_dev->devices[device_index].reports_len; i++) { + hidd_le_report_item_t *report = &s_dev->devices[device_index].reports[i]; + if (report->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) { + /* only consider report mode reports, all boot mode reports will be registered by default */ + if (report->report_type == ESP_HID_REPORT_TYPE_INPUT) { + /* Input Report */ + hparams.rpts[report_mode_rpts].type = ESP_HID_REPORT_TYPE_INPUT; + } else if (report->report_type == ESP_HID_REPORT_TYPE_OUTPUT) { + /* Output Report */ + hparams.rpts[report_mode_rpts].type = ESP_HID_REPORT_TYPE_OUTPUT; + } else { + /* Feature Report */ + hparams.rpts[report_mode_rpts].type = ESP_HID_REPORT_TYPE_FEATURE; + } + hparams.rpts[report_mode_rpts].id = report->report_id; + report_mode_rpts++; + } else { + if (report->report_type == ESP_HID_REPORT_TYPE_INPUT) { + /* Boot mode reports */ + if (report->usage == ESP_HID_USAGE_KEYBOARD) { //Boot Keyboard Input + hparams.kbd_inp_present = 1; + } else { //Boot Mouse Input + hparams.mouse_inp_present = 1; + } + } else { //Boot Keyboard Output + hparams.kbd_out_present = 1; + } + } + } + hparams.rpts_len = report_mode_rpts; + /* Add service */ + rc = ble_svc_hid_add(hparams); + if(rc != 0) { + return rc; + } + return rc; +} + + +static int ble_hid_create_info_db() { + int rc; + + rc = 0; + ble_svc_dis_init(); + uint8_t pnp_val[7] = { + 0x02, //0x1=BT, 0x2=USB + s_dev->config.vendor_id & 0xFF, (s_dev->config.vendor_id >> 8) & 0xFF, //VID + s_dev->config.product_id & 0xFF, (s_dev->config.product_id >> 8) & 0xFF, //PID + s_dev->config.version & 0xFF, (s_dev->config.version >> 8) & 0xFF //VERSION + }; + memcpy(s_dev->pnp, pnp_val, 7); + ble_svc_dis_pnp_id_set((char *)s_dev->pnp); + if (s_dev->config.manufacturer_name && s_dev->config.manufacturer_name[0]) { + rc = ble_svc_dis_manufacturer_name_set(s_dev->config.manufacturer_name); + } + if (s_dev->config.serial_number && s_dev->config.serial_number[0]) { + rc = ble_svc_dis_serial_number_set(s_dev->config.serial_number); + } + return rc; +} + +static int nimble_hid_start_gatts(void) +{ + int rc = ESP_OK; + + ble_svc_gap_init(); + ble_svc_gatt_init(); + ble_svc_sps_init(0, 0); // initialize with 0 + ble_svc_bas_init(); + ble_hid_create_info_db(); + + for (uint8_t d = 0; d < s_dev->devices_len; d++) { + rc = create_hid_db(d); + if(rc != 0) { + return rc; + } + } + /* init the hid svc */ + ble_svc_hid_init(); + + return rc; +} + +static int nimble_hid_stop_gatts(esp_ble_hidd_dev_t *dev) +{ + int rc = ESP_OK; + + /* stop gatt database */ + ble_gatts_stop(); + return rc; +} + +/* Identify the reports using the report map */ +static int ble_hid_init_config(esp_ble_hidd_dev_t *dev, const esp_hid_device_config_t *config) +{ + memset((uint8_t *)(&dev->config), 0, sizeof(esp_hid_device_config_t)); + dev->config.vendor_id = config->vendor_id; + dev->config.product_id = config->product_id; + dev->config.version = config->version; + if (config->device_name != NULL) { + dev->config.device_name = strdup(config->device_name); + } + if (config->manufacturer_name != NULL) { + dev->config.manufacturer_name = strdup(config->manufacturer_name); + } + if (config->serial_number != NULL) { + dev->config.serial_number = strdup(config->serial_number); + } + dev->appearance = ESP_HID_APPEARANCE_GENERIC; + + if (config->report_maps_len) { + dev->devices = (hidd_dev_map_t *)malloc(config->report_maps_len * sizeof(hidd_dev_map_t)); + if (dev->devices == NULL) { + ESP_LOGE(TAG, "devices malloc(%d) failed", config->report_maps_len); + return ESP_FAIL; + } + memset(dev->devices, 0, config->report_maps_len * sizeof(hidd_dev_map_t)); + dev->devices_len = config->report_maps_len; + for (uint8_t d = 0; d < dev->devices_len; d++) { + + //raw report map + uint8_t *map = (uint8_t *)malloc(config->report_maps[d].len); + if (map == NULL) { + ESP_LOGE(TAG, "report map malloc(%d) failed", config->report_maps[d].len); + return ESP_FAIL; + } + memcpy(map, config->report_maps[d].data, config->report_maps[d].len); + + dev->devices[d].reports_map.data = (const uint8_t *)map; + dev->devices[d].reports_map.len = config->report_maps[d].len; + + esp_hid_report_map_t *rmap = esp_hid_parse_report_map(config->report_maps[d].data, config->report_maps[d].len); + if (rmap == NULL) { + ESP_LOGE(TAG, "hid_parse_report_map[%d](%d) failed", d, config->report_maps[d].len); + return ESP_FAIL; + } + dev->appearance = rmap->appearance; + dev->devices[d].reports_len = rmap->reports_len; + dev->devices[d].reports = (hidd_le_report_item_t *)malloc(rmap->reports_len * sizeof(hidd_le_report_item_t)); + if (dev->devices[d].reports == NULL) { + ESP_LOGE(TAG, "reports malloc(%d) failed", rmap->reports_len * sizeof(hidd_le_report_item_t)); + free(rmap); + return ESP_FAIL; + } + for (uint8_t r = 0; r < rmap->reports_len; r++) { + dev->devices[d].reports[r].map_index = d; + dev->devices[d].reports[r].report_id = rmap->reports[r].report_id; + dev->devices[d].reports[r].protocol_mode = rmap->reports[r].protocol_mode; + dev->devices[d].reports[r].report_type = rmap->reports[r].report_type; + dev->devices[d].reports[r].usage = rmap->reports[r].usage; + dev->devices[d].reports[r].value_len = rmap->reports[r].value_len; + } + free(rmap->reports); + free(rmap); + } + } + return ESP_OK; +} + +static int ble_hid_free_config(esp_ble_hidd_dev_t *dev) +{ + for (uint8_t d = 0; d < dev->devices_len; d++) { + free((void *)dev->devices[d].reports); + free((void *)dev->devices[d].reports_map.data); + } + + free((void *)dev->devices); + free((void *)dev->config.device_name); + free((void *)dev->config.manufacturer_name); + free((void *)dev->config.serial_number); + if (dev->event_loop_handle != NULL) { + esp_event_loop_delete(dev->event_loop_handle); + } + return ESP_OK; +} + +static int nimble_hidd_dev_deinit(void *devp) { + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + if (!s_dev) { + ESP_LOGE(TAG, "HID device profile already uninitialized"); + return ESP_OK; + } + + if (s_dev != dev) { + ESP_LOGE(TAG, "Wrong HID device provided"); + return ESP_FAIL; + } + s_dev = NULL; + + nimble_hid_stop_gatts(dev); + esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_STOP_EVENT, NULL, 0, portMAX_DELAY); + ble_hid_free_config(dev); + free(dev); + return ESP_OK; +} + +static bool nimble_hidd_dev_connected(void *devp) +{ + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + return (dev != NULL && s_dev == dev && dev->connected); +} + +static int nimble_hidd_dev_battery_set(void *devp, uint8_t level) +{ + int rc; + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + if (!dev || s_dev != dev) { + return ESP_FAIL; + } + + if (!dev->connected) { + /* Return success if not yet connected */ + return ESP_OK; + } + + rc = ble_svc_bas_battery_level_set(level); + if (rc) { + ESP_LOGE(TAG, "esp_ble_gatts_send_notify failed: %d", rc); + return ESP_FAIL; + } + + return ESP_OK; +} + +/* if mode is NULL, find the first matching report */ +static hidd_le_report_item_t* find_report(uint8_t id, uint8_t type, uint8_t *mode) { + hidd_le_report_item_t *rpt; + for (uint8_t d = 0; d < s_dev->devices_len; d++) { + for (uint8_t i = 0; i < s_dev->devices[d].reports_len; i++) { + rpt = &s_dev->devices[d].reports[i]; + if(rpt->report_id == id && rpt->report_type == type && (!mode || (mode && *mode == rpt->protocol_mode))) { + return rpt; + } + } + } + return NULL; +} +static hidd_le_report_item_t* find_report_by_usage_and_type(uint8_t usage, uint8_t type, uint8_t *mode) { + hidd_le_report_item_t *rpt; + for (uint8_t d = 0; d < s_dev->devices_len; d++) { + for (uint8_t i = 0; i < s_dev->devices[d].reports_len; i++) { + rpt = &s_dev->devices[d].reports[i]; + if(rpt->usage == usage && rpt->report_type == type && (!mode || (mode && *mode == rpt->protocol_mode))) { + return rpt; + } + } + } + return NULL; +} + +static int nimble_hidd_dev_input_set(void *devp, size_t index, size_t id, uint8_t *data, size_t length) +{ + hidd_le_report_item_t *p_rpt; + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + int rc; + struct os_mbuf *om; + if (!dev || s_dev != dev) { + return ESP_FAIL; + } + + if (!dev->connected) { + ESP_LOGE(TAG, "%s Device Not Connected", __func__); + return ESP_FAIL; + } + + /* check the protocol mode */ + /* as the protocol mode is always present, its safe to read the characteristic */ + rc = ble_att_svr_read_local(s_dev->devices[index].hid_protocol_handle, &om); + if(rc != 0) { + ESP_LOGE(TAG, "Unable to fetch protocol_mode\n"); + return ESP_FAIL; + } + rc = ble_hs_mbuf_to_flat(om, &dev->protocol, sizeof(dev->protocol), NULL); + if(rc != 0) { + return ESP_FAIL; + } + /* free the mbuf */ + os_mbuf_free_chain(om); + om = NULL; + + p_rpt = find_report(id, ESP_HID_REPORT_TYPE_INPUT, &dev->protocol); + assert(p_rpt != NULL); + om = ble_hs_mbuf_from_flat((void*)data, length); + assert(om != NULL); + /* NOTE : om is freed by stack */ + rc = ble_att_svr_write_local(p_rpt->handle, om); + if (rc != 0) { + ESP_LOGE(TAG, "Write Input Report Failed: %d", rc); + return ESP_FAIL; + } + return ESP_OK; +} + +static int nimble_hidd_dev_feature_set(void *devp, size_t index, size_t id, uint8_t *data, size_t length) +{ + /* This function is a no-op for now */ + hidd_le_report_item_t *p_rpt; + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + int rc; + struct os_mbuf *om; + if (!dev || s_dev != dev) { + return ESP_FAIL; + } + + if (!dev->connected) { + ESP_LOGE(TAG, "%s Device Not Connected", __func__); + return ESP_FAIL; + } + + /* check the protocol mode */ + /* as the protocol mode is always present, its safe to read the characteristic */ + rc = ble_att_svr_read_local(s_dev->devices[index].hid_protocol_handle, &om); + if(rc != 0) { + ESP_LOGE(TAG, "Unable to fetch protocol_mode\n"); + return ESP_FAIL; + } + rc = ble_hs_mbuf_to_flat(om, &dev->protocol, sizeof(dev->protocol), NULL); + if(rc != 0) { + return ESP_FAIL; + } + /* free the mbuf */ + os_mbuf_free_chain(om); + om = NULL; + + p_rpt = find_report(id, ESP_HID_REPORT_TYPE_FEATURE, &dev->protocol); + assert(p_rpt != NULL); + om = ble_hs_mbuf_from_flat((void*)data, length); + assert(om != NULL); + /* NOTE : om is freed by stack*/ + rc = ble_att_svr_write_local(p_rpt->handle, om); + if (rc != 0) { + ESP_LOGE(TAG, "Set Feature Report Failed: %d", rc); + return ESP_FAIL; + } + return ESP_OK; +} + +static int nimble_hidd_dev_event_handler_register(void *devp, esp_event_handler_t callback, esp_hidd_event_t event) +{ + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + if (!dev || s_dev != dev) { + return ESP_FAIL; + } + return esp_event_handler_register_with(dev->event_loop_handle, ESP_HIDD_EVENTS, event, callback, dev->dev); +} + +static int esp_ble_hidd_dev_event_handler_unregister(void *devp, esp_event_handler_t callback, esp_hidd_event_t event) +{ + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + if (!dev || s_dev != dev) { + return ESP_FAIL; + } + return esp_event_handler_unregister_with(dev->event_loop_handle, ESP_HIDD_EVENTS, event, callback); +} + +static void ble_hidd_dev_free(void) +{ + if (s_dev) { + ble_hid_free_config(s_dev); + free(s_dev); + s_dev = NULL; + } +} + +static int nimble_hid_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + struct os_mbuf *om; + uint8_t data; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + ESP_LOGD(TAG, "connection %s; status=%d", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + + /* save connection handle */ + s_dev->connected = true; + s_dev->conn_id = event->connect.conn_handle; + esp_hidd_event_data_t cb_param = { + .connect.dev = s_dev->dev, + .connect.status = event->connect.status + }; + + /* reset the protocol mode value */ + data = ESP_HID_PROTOCOL_MODE_REPORT; + om = ble_hs_mbuf_from_flat(&data, 1); + if(om == NULL) { + ESP_LOGD(TAG, "No memory to allocate mbuf"); + } + /* NOTE : om is freed by stack */ + for(int i = 0; i < s_dev->devices_len; i++) { + rc = ble_att_svr_write_local(s_dev->devices[i].hid_protocol_handle, om); + if (rc != 0) { + ESP_LOGE(TAG, "Write on Protocol Mode Failed: %d", rc); + } + } + + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_CONNECT_EVENT, + &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY); + return 0; + break; + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGD(TAG, "disconnect; reason=%d", event->disconnect.reason); + + if (s_dev->connected) { + s_dev->connected = false; + esp_hidd_event_data_t cb_param = {0}; + cb_param.disconnect.dev = s_dev->dev; + cb_param.disconnect.reason = event->disconnect.reason; + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_DISCONNECT_EVENT, + &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY); + } + return 0; + } + return 0; +} + +/** service index is used to identify the hid service instance + of the registered characteristic. + Assuming the first instance of the hid service is registered first. + Increment service index as the hid services get registered */ +static int service_index = -1; +static void nimble_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) +{ + char buf[BLE_UUID_STR_LEN]; + hidd_le_report_item_t *rpt = NULL; + struct os_mbuf *om; + uint16_t uuid16; + uint16_t report_info; + uint8_t report_type, report_id; + uint16_t report_handle; + uint8_t protocol_mode; + int rc; + switch (ctxt->op) { + case BLE_GATT_REGISTER_OP_SVC: + ESP_LOGD(TAG, "registered service %s with handle=%d", + ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), + ctxt->svc.handle); + uuid16 = ble_uuid_u16(ctxt->svc.svc_def->uuid); + if(uuid16 == BLE_SVC_HID_UUID16) { + ++service_index; + s_dev->devices[service_index].hid_svc = ctxt->svc.handle; + } + + break; + + case BLE_GATT_REGISTER_OP_CHR: + ESP_LOGD(TAG, "registering characteristic %s with " + "def_handle=%d val_handle=%d\n", + ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), + ctxt->chr.def_handle, + ctxt->chr.val_handle); + uuid16 = ble_uuid_u16(ctxt->chr.chr_def->uuid); + if(uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT) { + /* assuming this characteristic is from the last registered hid service */ + s_dev->devices[service_index].hid_control_handle = ctxt->chr.val_handle; + } + if(uuid16 == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE) { + /* assuming this characteristic is from the last registered hid service */ + s_dev->devices[service_index].hid_protocol_handle = ctxt->chr.val_handle; + } + if(uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_INP) { + protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; + rpt = find_report_by_usage_and_type(ESP_HID_USAGE_KEYBOARD, ESP_HID_REPORT_TYPE_INPUT, &protocol_mode); + if(rpt == NULL) { + ESP_LOGE(TAG, "Unknown boot kbd input report registration"); + return; + } + rpt->handle = ctxt->chr.val_handle; + } + if(uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_OUT) { + protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; + rpt = find_report_by_usage_and_type(ESP_HID_USAGE_KEYBOARD, ESP_HID_REPORT_TYPE_OUTPUT, &protocol_mode); + if(rpt == NULL) { + ESP_LOGE(TAG, "Unknown boot kbd output report registration"); + return; + } + rpt->handle = ctxt->chr.val_handle; + } + if(uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_MOUSE_INP) { + protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; + rpt = find_report_by_usage_and_type(ESP_HID_USAGE_MOUSE, ESP_HID_REPORT_TYPE_INPUT, &protocol_mode); + if(rpt == NULL) { + ESP_LOGE(TAG, "Unknown boot mouse input report registration"); + return; + } + rpt->handle = ctxt->chr.val_handle; + } + break; + + case BLE_GATT_REGISTER_OP_DSC: + ESP_LOGD(TAG, "registering descriptor %s with handle=%d", + ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), + ctxt->dsc.handle); + uuid16 = ble_uuid_u16(ctxt->dsc.dsc_def->uuid); + if(uuid16 == BLE_SVC_HID_DSC_UUID16_RPT_REF) { + rc = ble_att_svr_read_local(ctxt->dsc.handle, &om); + assert(rc == 0); + + ble_hs_mbuf_to_flat(om, &report_info, sizeof report_info, NULL); + report_type = (uint8_t)((report_info & 0xFF00) >> 8); + report_id = report_info & 0x00FF; + report_handle = (*(uint16_t*)(ctxt->dsc.dsc_def->arg)); + protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT; + rpt = find_report(report_id, report_type, &protocol_mode); + assert(rpt != NULL); + rpt->handle = report_handle; + /* free the mbuf */ + os_mbuf_free_chain(om); + om = NULL; + } + break; + + default: + assert(0); + break; + } +} + +static void nimble_host_synced(void) { + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_START_EVENT, NULL, 0, portMAX_DELAY); +} + +void nimble_host_reset(int reason) +{ + MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); +} + +static struct ble_gap_event_listener nimble_gap_event_listener; +esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_config_t *config, esp_event_handler_t callback) +{ + int rc; + + if (s_dev) { + ESP_LOGE(TAG, "HID device profile already initialized"); + return ESP_FAIL; + } + + s_dev = (esp_ble_hidd_dev_t *)calloc(1, sizeof(esp_ble_hidd_dev_t)); + if (s_dev == NULL) { + ESP_LOGE(TAG, "HID device could not be allocated"); + return ESP_FAIL; + } + + // Reset the hid device target environment + s_dev->control = ESP_HID_CONTROL_EXIT_SUSPEND; + s_dev->protocol = ESP_HID_PROTOCOL_MODE_REPORT; + s_dev->event_loop_handle = NULL; + s_dev->dev = dev_p; + + esp_event_loop_args_t event_task_args = { + .queue_size = 5, + .task_name = "ble_hidd_events", + .task_priority = uxTaskPriorityGet(NULL), + .task_stack_size = 2048, + .task_core_id = tskNO_AFFINITY + }; + rc = esp_event_loop_create(&event_task_args, &s_dev->event_loop_handle); + if (rc != ESP_OK) { + ESP_LOGE(TAG, "HID device event loop could not be created"); + ble_hidd_dev_free(); + return rc; + } + + rc = ble_hid_init_config(s_dev, config); + if (rc != ESP_OK) { + ble_hidd_dev_free(); + return rc; + } + + dev_p->dev = s_dev; + dev_p->connected = nimble_hidd_dev_connected; + dev_p->deinit = nimble_hidd_dev_deinit; + dev_p->battery_set = nimble_hidd_dev_battery_set; + dev_p->input_set = nimble_hidd_dev_input_set; + dev_p->feature_set = nimble_hidd_dev_feature_set; + dev_p->event_handler_register = nimble_hidd_dev_event_handler_register; + dev_p->event_handler_unregister = esp_ble_hidd_dev_event_handler_unregister; + + rc = nimble_hidd_dev_event_handler_register(s_dev, esp_hidd_process_event_data_handler, ESP_EVENT_ANY_ID); + if (rc != ESP_OK) { + ble_hidd_dev_free(); + return rc; + } + + if (callback != NULL) { + rc = nimble_hidd_dev_event_handler_register(s_dev, callback, ESP_EVENT_ANY_ID); + if (rc != ESP_OK) { + ble_hidd_dev_free(); + return rc; + } + } + + ble_hs_cfg.reset_cb = nimble_host_reset; + ble_hs_cfg.sync_cb = nimble_host_synced; + ble_hs_cfg.gatts_register_cb = nimble_gatt_svr_register_cb; + rc = nimble_hid_start_gatts(); + if(rc != ESP_OK) { + return rc; + } + ble_gap_event_listener_register(&nimble_gap_event_listener, + nimble_hid_gap_event, NULL); + + return rc; +} +#endif // CONFIG_BT_NIMBLE_HID_SERVICE diff --git a/components/esp_hid/src/nimble_hidh.c b/components/esp_hid/src/nimble_hidh.c new file mode 100644 index 0000000000..d1881159b5 --- /dev/null +++ b/components/esp_hid/src/nimble_hidh.c @@ -0,0 +1,1020 @@ +/* + * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ble_hidh.h" +#include "esp_hidh_private.h" +#include "esp_err.h" +#include "esp_log.h" + +#include "esp_hid_common.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include +#include +#include +#include "nimble/nimble_opt.h" +#include "host/ble_hs.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "host/ble_hs_hci.h" +#include "host/ble_att.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" +#include "services/bas/ble_svc_bas.h" +#include "services/hid/ble_svc_hid.h" +#include "services/dis/ble_svc_dis.h" + +#if CONFIG_BT_NIMBLE_HID_SERVICE + +#define ESP_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" +#define ESP_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] + +static const char *TAG = "NIMBLE_HIDH"; +static SemaphoreHandle_t s_ble_hidh_cb_semaphore = NULL; + +/* variables used for attribute discovery */ +static int services_discovered; +static int chrs_discovered; +static int dscs_discovered; +static int status = 0; + +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 int s_read_status = 0; +static esp_event_handler_t s_event_callback; + +/** + * Utility function to log an array of bytes. + */ +void +print_bytes(const uint8_t *bytes, int len) +{ + int i; + + for (i = 0; i < len; i++) { + MODLOG_DFLT(DEBUG, "%s0x%02x", i != 0 ? ":" : "", bytes[i]); + } +} + +static void +print_mbuf(const struct os_mbuf *om) +{ + int colon; + + colon = 0; + while (om != NULL) { + if (colon) { + MODLOG_DFLT(DEBUG, ":"); + } else { + colon = 1; + } + print_bytes(om->om_data, om->om_len); + om = SLIST_NEXT(om, om_next); + } +} + +static int +nimble_on_read(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) +{ + int old_offset; + MODLOG_DFLT(INFO, "Read complete; status=%d conn_handle=%d", error->status, + conn_handle); + s_read_status = error->status; + switch(s_read_status) { + case 0: + MODLOG_DFLT(DEBUG, " attr_handle=%d value=", attr->handle); + old_offset = s_read_data_len; + s_read_data_len += OS_MBUF_PKTLEN(attr->om); + s_read_data_val = realloc(s_read_data_val, s_read_data_len + 1); // 1 extra byte to store null char + ble_hs_mbuf_to_flat(attr->om, s_read_data_val + old_offset, OS_MBUF_PKTLEN(attr->om), NULL); + print_mbuf(attr->om); + return 0; + case BLE_HS_EDONE: + s_read_data_val[s_read_data_len] = 0; // to insure strings are ended with \0 */ + s_read_status = 0; + SEND_CB(); + return 0; + } + return 0; +} + +static int read_char(uint16_t conn_handle, uint16_t handle, uint8_t **out, uint16_t *out_len) +{ + s_read_data_val = NULL; + s_read_data_len = 0; + int rc; + + /* read long because the server may not support the large enough mtu */ + rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "read_char failed"); + return rc; + } + WAIT_CB(); + if (s_read_status == 0) { + *out = s_read_data_val; + *out_len = s_read_data_len; + } + return s_read_status; +} + +static int read_descr(uint16_t conn_handle, uint16_t handle, uint8_t **out, uint16_t *out_len) +{ + int rc; + + s_read_data_val = NULL; + s_read_data_len = 0; + + rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "read_descr failed"); + return rc; + } + WAIT_CB(); + if (s_read_status == 0) { + *out = s_read_data_val; + *out_len = s_read_data_len; + } + return s_read_status; +} + +static int +svc_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_svc *service, void *arg) +{ + int rc; + struct ble_gatt_svc *service_result; + uint16_t uuid16; + esp_hidh_dev_t *dev; + + service_result = arg; + rc = 0; + status = error->status; + switch (error->status) { + case 0: + memcpy(service_result + services_discovered, service, sizeof(struct ble_gatt_svc)); + services_discovered++; + uuid16 = ble_uuid_u16(&service->uuid.u); + dev = esp_hidh_dev_get_by_conn_id(conn_handle); + if (!dev) { + ESP_LOGE(TAG, "Service discovery received for unknown device"); + break; + } + if (uuid16 == BLE_SVC_HID_UUID16) { + dev->status = 0; + dev->config.report_maps_len++; + ESP_LOGD(TAG, "HID Service is Discovered"); + } + break; + + case BLE_HS_EDONE: + rc = 0; + status = 0; + /* release the sem now */ + SEND_CB(); + break; + + default: + rc = error->status; + break; + } + + if (rc != 0) { + /* Error; abort discovery. */ + SEND_CB(); + ESP_LOGE(TAG, "Error in service discovery %d\n", rc); + } + + return rc; +} + +static int +chr_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_chr *chr, void *arg) +{ + struct ble_gatt_chr *chrs; + int rc; + rc = 0; + chrs = arg; + + status = error->status; + switch (error->status) { + case 0: + ESP_LOGD(TAG,"Char discovered : def handle : %04x, val_handle : %04x, properties : %02x\n, uuid : %04x", + chr->def_handle, chr->val_handle,chr->properties, ble_uuid_u16(&chr->uuid.u)); + memcpy(chrs + chrs_discovered, chr, sizeof(struct ble_gatt_chr)); + chrs_discovered++; + break; + + case BLE_HS_EDONE: + status = 0; + /* release when discovery is complete */ + SEND_CB(); + rc = 0; + break; + + default: + rc = error->status; + break; + } + + if (rc != 0) { + /* Error; abort discovery. */ + SEND_CB(); + } + + return rc; +} + +static int +desc_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc, + void *arg) +{ + int rc; + struct ble_gatt_dsc *dscr; + dscr = arg; + + rc = 0; + status = error->status; + switch (error->status) { + case 0: + ESP_LOGD(TAG,"DISC discovered : handle : %04x, uuid : %04x", + dsc->handle, ble_uuid_u16(&dsc->uuid.u)); + memcpy(dscr + dscs_discovered, dsc, sizeof(struct ble_gatt_dsc)); + dscs_discovered++; + break; + + case BLE_HS_EDONE: + /* All descriptors in this characteristic discovered */ + rc = 0; + status = 0; + /* release the sem as the discovery is complete */ + SEND_CB(); + break; + + default: + /* Error; abort discovery. */ + rc = error->status; + break; + } + + if (rc != 0) { + /* Error; abort discovery. */ + SEND_CB(); + } + + return rc; +} + +/* this api does the following things : +** does service, characteristic and discriptor discovery and +** fills the hid device information accordingly in dev */ +static void read_device_services(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; + + struct ble_gatt_svc service_result[10]; + uint16_t dcount = 10; + uint8_t hidindex = 0; + int rc; + + rc = ble_gattc_disc_all_svcs(dev->ble.conn_id, svc_disced, service_result); + if(rc != 0) { + ESP_LOGD(TAG, "Error discovering services : %d", rc); + assert(rc != 0); + } + WAIT_CB(); + if(status != 0) { + ESP_LOGE(TAG, "failed to find services"); + assert(status == 0); + } + dcount = services_discovered; /* fatal if services are more than 10 */ + + if (rc == 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; + } + dev->protocol_mode = (uint8_t *)malloc(dev->config.report_maps_len * sizeof(uint8_t)); + if (dev->protocol_mode == NULL) { + ESP_LOGE(TAG, "malloc protocol_mode failed"); + return; + } + + /* read characteristic value may fail, so we should init report maps */ + memset(dev->config.report_maps, 0, dev->config.report_maps_len * sizeof(esp_hid_raw_report_map_t)); + + for (uint16_t s = 0; s < dcount; s++) { + suuid = ble_uuid_u16(&service_result[s].uuid.u); + ESP_LOGD(TAG, "Service discovered : start_handle : %d, end handle : %d, uuid: 0x%04x", + service_result[s].start_handle, service_result[s].end_handle, suuid); + + if (suuid != BLE_SVC_BAS_UUID16 + && suuid != BLE_SVC_DIS_UUID16 + && suuid != BLE_SVC_HID_UUID16 + && suuid != 0x1800) {//device name? + continue; + } + + struct ble_gatt_chr char_result[20]; + uint16_t ccount = 20; + rc = ble_gattc_disc_all_chrs(dev->ble.conn_id, service_result[s].start_handle, + service_result[s].end_handle, chr_disced, char_result); + WAIT_CB(); + if(status != 0) { + ESP_LOGE(TAG, "failed to find chars for service : %d",s); + assert(status == 0); + } + ccount = chrs_discovered; + if (rc == ESP_OK) { + for (uint16_t c = 0; c < ccount; c++) { + cuuid = ble_uuid_u16(&char_result[c].uuid.u); + chandle = char_result[c].val_handle; + ESP_LOGD(TAG, " CHAR:(%d), handle: %d, perm: 0x%02x, uuid: 0x%04x", + c + 1, chandle, char_result[c].properties, cuuid); + + if (suuid == BLE_SVC_GAP_UUID16) { + if (dev->config.device_name == NULL && cuuid == BLE_SVC_GAP_CHR_UUID16_DEVICE_NAME + && (char_result[c].properties & BLE_GATT_CHR_PROP_READ) != 0) { + if (read_char(dev->ble.conn_id, chandle,&rdata, &rlen) == 0 && rlen) { + dev->config.device_name = (const char *)rdata; + } + } else { + continue; + } + } else if (suuid == BLE_SVC_BAS_UUID16) { + if (cuuid == BLE_SVC_BAS_CHR_UUID16_BATTERY_LEVEL && + (char_result[c].properties & BLE_GATT_CHR_PROP_READ) != 0) { + dev->ble.battery_handle = chandle; + } else { + continue; + } + } else if (suuid == BLE_SVC_DIS_UUID16) { + if (char_result[c].properties & BLE_GATT_CHR_PROP_READ) { + if (cuuid == BLE_SVC_DIS_CHR_UUID16_PNP_ID) { + if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && 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 == BLE_SVC_DIS_CHR_UUID16_MANUFACTURER_NAME) { + if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { + dev->config.manufacturer_name = (const char *)rdata; + } + } else if (cuuid == BLE_SVC_DIS_CHR_UUID16_SERIAL_NUMBER) { + if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { + dev->config.serial_number = (const char *)rdata; + } + } + } + continue; + } else { + if(cuuid == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE) { + if (char_result[c].properties & BLE_GATT_CHR_PROP_READ) { + if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { + dev->protocol_mode[hidindex] = *((uint8_t *)rdata); + } + } + continue; + } + if (cuuid == BLE_SVC_HID_CHR_UUID16_REPORT_MAP) { + if (char_result[c].properties & BLE_GATT_CHR_PROP_READ) { + if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { + dev->config.report_maps[hidindex].data = (const uint8_t *)rdata; + dev->config.report_maps[hidindex].len = rlen; + } + } + continue; + } else if (cuuid == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_INP || cuuid == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_OUT + || cuuid == BLE_SVC_HID_CHR_UUID16_BOOT_MOUSE_INP || cuuid == BLE_SVC_HID_CHR_UUID16_RPT) { + 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 == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_INP) { + 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 == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_OUT) { + 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 == BLE_SVC_HID_CHR_UUID16_BOOT_MOUSE_INP) { + 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; + } + } + struct ble_gatt_dsc descr_result[20]; + uint16_t dcount = 20; + uint16_t chr_end_handle; + if(c + 1 < ccount) { + chr_end_handle = char_result[c + 1].def_handle; + } + else { + chr_end_handle = service_result[s].end_handle; + } + rc = ble_gattc_disc_all_dscs(dev->ble.conn_id, char_result[c].val_handle, + chr_end_handle, desc_disced, descr_result); + WAIT_CB(); + if(status != 0) { + ESP_LOGE(TAG, "failed to find discriptors for characteristic : %d",c); + assert(status == 0); + } + dcount = dscs_discovered; + if (rc == ESP_OK) { + for (uint16_t d = 0; d < dcount; d++) { + duuid = ble_uuid_u16(&descr_result[d].uuid.u); + dhandle = descr_result[d].handle; + ESP_LOGD(TAG, " DESCR:(%d), handle: %d, uuid: 0x%04x", d + 1, dhandle, duuid); + + if (suuid == BLE_SVC_BAS_UUID16) { + if (duuid == BLE_GATT_DSC_CLT_CFG_UUID16 && + (char_result[c].properties & BLE_GATT_CHR_PROP_NOTIFY) != 0) { + dev->ble.battery_ccc_handle = dhandle; + } + } else if (suuid == BLE_SVC_HID_UUID16 && report != NULL) { + if (duuid == BLE_GATT_DSC_CLT_CFG_UUID16 && (report->permissions & BLE_GATT_CHR_PROP_NOTIFY) != 0) { + report->ccc_handle = dhandle; + } else if (duuid == BLE_SVC_HID_DSC_UUID16_RPT_REF) { + if (read_descr(dev->ble.conn_id, dhandle, &rdata, &rlen) == 0 && rlen) { + report->report_id = rdata[0]; + report->report_type = rdata[1]; + free(rdata); + } + } + } + } + } + if (suuid == BLE_SVC_HID_UUID16 && report != NULL) { + report->next = dev->reports; + dev->reports = report; + dev->reports_len++; + } + dscs_discovered = 0; + } + if (suuid == BLE_SVC_HID_UUID16) { + hidindex++; + } + } + chrs_discovered = 0; // reset the chars array for the next service + } + + 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 int +on_subscribe(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) +{ + uint16_t conn_id; + conn_id =*((uint16_t*) arg); + + assert(conn_id == conn_handle); + + MODLOG_DFLT(INFO, "Subscribe complete; status=%d conn_handle=%d " + "attr_handle=%d\n", + error->status, conn_handle, attr->handle); + SEND_CB(); + + return 0; +} + +static void register_for_notify(uint16_t conn_handle, uint16_t handle) +{ + uint8_t value[2]; + int rc; + value[0] = 1; + value[1] = 0; + rc = ble_gattc_write_flat(conn_handle, handle, value, sizeof value, on_subscribe,(void *)&conn_handle); + if (rc != 0) { + ESP_LOGE(TAG, "Error: Failed to subscribe to characteristic; " + "rc=%d\n", rc); + } + WAIT_CB(); +} + +static int +on_write(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) +{ + uint16_t conn_id; + conn_id =*((uint16_t*) arg); + + assert(conn_id == conn_handle); + + MODLOG_DFLT(DEBUG, "write complete; status=%d conn_handle=%d " + "attr_handle=%d\n", + error->status, conn_handle, attr->handle); + SEND_CB(); + + return 0; +} +static void write_char_descr(uint16_t conn_id, uint16_t handle, uint16_t value_len, uint8_t *value) +{ + ble_gattc_write_flat(conn_id, handle, value, value_len, on_write, &conn_id); + WAIT_CB(); +} + +static void attach_report_listeners(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(dev->ble.conn_id, dev->ble.battery_handle); + if (dev->ble.battery_ccc_handle) { + //Write CCC descr to enable notifications + write_char_descr(dev->ble.conn_id, dev->ble.battery_ccc_handle, 2, (uint8_t *)&ccc_data); + } + } + + while (report) { + /* subscribe to notifications */ + if ((report->permissions & BLE_GATT_CHR_PROP_NOTIFY) != 0 && report->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) { + register_for_notify(dev->ble.conn_id, report->handle); + if (report->ccc_handle) { + /* Write CCC descr to enable notifications */ + write_char_descr(dev->ble.conn_id, report->ccc_handle, 2, (uint8_t *)&ccc_data); + } + } + report = report->next; + } +} + +static int +esp_hidh_gattc_event_handler(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int rc; + esp_hidh_dev_t *dev = NULL; + esp_hidh_dev_report_t *report = NULL; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + if (event->connect.status == 0) { + /* Connection successfully established. */ + MODLOG_DFLT(INFO, "Connection established "); + + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + dev = esp_hidh_dev_get_by_bda(desc.peer_ota_addr.val); + if(!dev) { + ESP_LOGE(TAG, "Connect received for unknown device"); + } + dev->status = -1; // set to not found and clear if HID service is found + dev->ble.conn_id = event->connect.conn_handle; + + /* Try to set the mtu to the max value */ + rc = ble_att_set_preferred_mtu(BLE_ATT_MTU_MAX); + if(rc != 0) { + ESP_LOGE(TAG, "att preferred mtu set failed"); + } + rc = ble_gattc_exchange_mtu(event->connect.conn_handle, NULL, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Failed to negotiate MTU; rc = %d", rc); + } + SEND_CB(); + } else { + MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n", + event->connect.status); + dev->status = event->connect.status; // ESP_GATT_CONN_FAIL_ESTABLISH; + dev->ble.conn_id = -1; + SEND_CB(); // return from connection + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + /* Connection terminated. */ + MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason); + dev = esp_hidh_dev_get_by_conn_id(event->disconnect.conn.conn_handle); + if (!dev) { + ESP_LOGE(TAG, "CLOSE received for unknown device"); + break; + } + if (!dev->connected) { + dev->status = event->disconnect.reason; + dev->ble.conn_id = -1; + } else { + dev->connected = false; + dev->status = event->disconnect.reason; + // free the device in the wrapper event handler + dev->in_use = false; + if (event_loop_handle) { + esp_hidh_event_data_t p = {0}; + p.close.dev = dev; + p.close.reason = event->disconnect.reason; + p.close.status = ESP_OK; + 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_inner(dev); + } + } + + return 0; + + case BLE_GAP_EVENT_NOTIFY_RX: + /* Peer sent us a notification or indication. */ + MODLOG_DFLT(DEBUG, "received %s; conn_handle=%d attr_handle=%d " + "attr_len=%d\n", + event->notify_rx.indication ? + "indication" : + "notification", + event->notify_rx.conn_handle, + event->notify_rx.attr_handle, + OS_MBUF_PKTLEN(event->notify_rx.om)); + + /* Attribute data is contained in event->notify_rx.om. Use + * `os_mbuf_copydata` to copy the data received in notification mbuf */ + + dev = esp_hidh_dev_get_by_conn_id(event->notify_rx.conn_handle); + if (!dev) { + ESP_LOGE(TAG, "NOTIFY received for unknown device"); + break; + } + if (event_loop_handle) { + esp_hidh_event_data_t p = {0}; + if (event->notify_rx.attr_handle == dev->ble.battery_handle) { + p.battery.dev = dev; + ble_hs_mbuf_to_flat(event->notify_rx.om, &p.battery.level, sizeof(p.battery.level), NULL); + 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, event->notify_rx.attr_handle); + if (report) { + esp_hidh_event_data_t *p_param = NULL; + size_t event_data_size = sizeof(esp_hidh_event_data_t); + + if(report->protocol_mode != dev->protocol_mode[report->map_index]) { + /* only pass the notifications in the current protocol mode */ + ESP_LOGD(TAG, "Wrong protocol mode, dropping notification"); + return 0; + } + if (OS_MBUF_PKTLEN(event->notify_rx.om)) { + event_data_size += OS_MBUF_PKTLEN(event->notify_rx.om); + } + + if ((p_param = (esp_hidh_event_data_t *)malloc(event_data_size)) == NULL) { + ESP_LOGE(TAG, "%s malloc event data failed!", __func__); + break; + } + memset(p_param, 0, event_data_size); + if (OS_MBUF_PKTLEN(event->notify_rx.om) && event->notify_rx.om) { + ble_hs_mbuf_to_flat(event->notify_rx.om, ((uint8_t *)p_param) + sizeof(esp_hidh_event_data_t), + OS_MBUF_PKTLEN(event->notify_rx.om), NULL); + } + + if (report->report_type == ESP_HID_REPORT_TYPE_FEATURE) { + p_param->feature.dev = dev; + p_param->feature.map_index = report->map_index; + p_param->feature.report_id = report->report_id; + p_param->feature.usage = report->usage; + p_param->feature.length = OS_MBUF_PKTLEN(event->notify_rx.om); + p_param->feature.data = ((uint8_t *)p_param) + sizeof(esp_hidh_event_data_t); + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_FEATURE_EVENT, p_param, event_data_size, portMAX_DELAY); + } else { + p_param->input.dev = dev; + p_param->input.map_index = report->map_index; + p_param->input.report_id = report->report_id; + p_param->input.usage = report->usage; + p_param->input.length = OS_MBUF_PKTLEN(event->notify_rx.om); + p_param->input.data = ((uint8_t *)p_param) + sizeof(esp_hidh_event_data_t); + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_INPUT_EVENT, p_param, event_data_size, portMAX_DELAY); + } + + if (p_param) { + free(p_param); + p_param = NULL; + } + } + } + } + break; + return 0; + + case BLE_GAP_EVENT_MTU: + MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + default: + return 0; + } + return 0; +} + +/* + * Public Functions + * */ + +static esp_err_t esp_ble_hidh_dev_close(esp_hidh_dev_t *dev) +{ + return ble_gap_terminate(dev->ble.conn_id, BLE_ERR_REM_USER_CONN_TERM); +} + +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) +{ + int rc; + 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; + } + rc = ble_gattc_write_flat(dev->ble.conn_id, report->handle, value, value_len, on_write, &dev->ble.conn_id); + WAIT_CB();// this is not blocking in bluedroid code + return rc; +} + +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; + int s = read_char(dev->ble.conn_id, report->handle, &v, &len); + if (s == 0) { + 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; + } + } + +} + +static void esp_ble_hidh_event_handler_wrapper(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, + void *event_data) +{ + esp_hidh_preprocess_event_handler(event_handler_arg, event_base, event_id, event_data); + + if (s_event_callback) { + s_event_callback(event_handler_arg, event_base, event_id, event_data); + } + + esp_hidh_post_process_event_handler(event_handler_arg, event_base, event_id, event_data); +} + +static void nimble_host_synced(void) { +/* + no need to perform anything here +*/ +} + +static void nimble_host_reset(int reason) +{ + MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); +} + +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 = config->event_stack_size > 0 ? config->event_stack_size : 2048, + .task_core_id = tskNO_AFFINITY + }; + + do { + ret = esp_event_loop_create(&event_task_args, &event_loop_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "%s esp_event_loop_create failed!", __func__); + break; + } + + s_event_callback = config->callback; + ret = esp_event_handler_register_with(event_loop_handle, ESP_HIDH_EVENTS, ESP_EVENT_ANY_ID, + esp_ble_hidh_event_handler_wrapper, config->callback_arg); + } while (0); + + if (ret != ESP_OK) { + if (event_loop_handle) { + esp_event_loop_delete(event_loop_handle); + } + + if (s_ble_hidh_cb_semaphore) { + vSemaphoreDelete(s_ble_hidh_cb_semaphore); + s_ble_hidh_cb_semaphore = NULL; + } + } + + ble_hs_cfg.reset_cb = nimble_host_reset; + ble_hs_cfg.sync_cb = nimble_host_synced; + return ret; +} + +esp_err_t esp_ble_hidh_deinit(void) +{ + if (s_ble_hidh_cb_semaphore == NULL) { + ESP_LOGE(TAG, "Already deinitialised"); + return ESP_FAIL; + } + + if (event_loop_handle) { + esp_event_loop_delete(event_loop_handle); + } + vSemaphoreDelete(s_ble_hidh_cb_semaphore); + s_ble_hidh_cb_semaphore = NULL; + s_event_callback = NULL; + + return 0; +} + +esp_hidh_dev_t *esp_ble_hidh_dev_open(uint8_t *bda, uint8_t address_type) +{ + esp_err_t ret; + ble_addr_t addr; + uint8_t own_addr_type; + + own_addr_type = 0; // set to public for now + esp_hidh_dev_t *dev = esp_hidh_dev_malloc(); + if (dev == NULL) { + ESP_LOGE(TAG, "malloc esp_hidh_dev_t failed"); + return NULL; + } + + dev->in_use = true; + dev->transport = ESP_HID_TRANSPORT_BLE; + memcpy(dev->bda, bda, sizeof(dev->bda)); + dev->ble.address_type = address_type; + dev->ble.appearance = ESP_HID_APPEARANCE_GENERIC; + + memcpy(addr.val, bda, sizeof(addr.val)); + addr.type = address_type; + + ret = ble_gap_connect(own_addr_type, &addr, 30000, NULL, esp_hidh_gattc_event_handler, NULL); + if (ret) { + esp_hidh_dev_free_inner(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_inner(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; + + /* perform service discovery and fill the report maps */ + read_device_services(dev); + + if (event_loop_handle) { + esp_hidh_event_data_t p = {0}; + p.open.status = ESP_OK; + 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(dev); + return dev; +} +#endif // CONFIG_BT_NIMBLE_HID_SERVICE diff --git a/examples/bluetooth/esp_hid_device/main/Kconfig.projbuild b/examples/bluetooth/esp_hid_device/main/Kconfig.projbuild index 568ef5ae62..37da169d7b 100644 --- a/examples/bluetooth/esp_hid_device/main/Kconfig.projbuild +++ b/examples/bluetooth/esp_hid_device/main/Kconfig.projbuild @@ -6,4 +6,33 @@ menu "HID Example Configuration" help This enables the Secure Simple Pairing. If disable this option, Bluedroid will only support Legacy Pairing + + choice EXAMPLE_HID_DEVICE_ROLE + prompt "HID Device Role" + depends on BT_NIMBLE_ENABLED + default EXAMPLE_MEDIA_ENABLE + help + Three Supported Roles for Device + - Media Device + - Keyboard + - Mouse + + config EXAMPLE_MEDIA_ENABLE + select BT_NIMBLE_HID_SERVICE + bool "Enable Media Device" + + config EXAMPLE_KBD_ENABLE + select BT_NIMBLE_HID_SERVICE + bool "Enable Keyboard Device" + + config EXAMPLE_MOUSE_ENABLE + select BT_NIMBLE_HID_SERVICE + bool "Enable Mouse Device" + endchoice + + config EXAMPLE_HID_DEVICE_ROLE + int + default 1 if EXAMPLE_MEDIA_ENABLE + default 2 if EXAMPLE_KBD_ENABLE + default 3 if EXAMPLE_MOUSE_ENABLE endmenu diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c b/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c index 7e95cdb65f..7f32ddb446 100644 --- a/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c +++ b/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c @@ -18,6 +18,12 @@ #include "esp_log.h" #include "nvs_flash.h" #include "esp_bt.h" + +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#else #include "esp_bt_defs.h" #if CONFIG_BT_BLE_ENABLED #include "esp_gap_ble_api.h" @@ -26,6 +32,7 @@ #endif #include "esp_bt_main.h" #include "esp_bt_device.h" +#endif #include "esp_hidd.h" #include "esp_hid_gap.h" @@ -40,7 +47,7 @@ typedef struct uint8_t *buffer; } local_param_t; -#if CONFIG_BT_BLE_ENABLED +#if CONFIG_BT_BLE_ENABLED || CONFIG_BT_NIMBLE_ENABLED static local_param_t s_ble_hid_param = {0}; const unsigned char mediaReportMap[] = { @@ -102,19 +109,287 @@ const unsigned char mediaReportMap[] = { 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, // End Collection }; +#if CONFIG_EXAMPLE_HID_DEVICE_ROLE && CONFIG_EXAMPLE_HID_DEVICE_ROLE == 3 +const unsigned char mouseReportMap[] = { + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x02, // USAGE (Mouse) + 0xa1, 0x01, // COLLECTION (Application) + 0x09, 0x01, // USAGE (Pointer) + 0xa1, 0x00, // COLLECTION (Physical) + + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x03, // USAGE_MAXIMUM (Button 3) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x03, // REPORT_COUNT (3) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x05, // REPORT_SIZE (5) + 0x81, 0x03, // INPUT (Cnst,Var,Abs) + + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 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) + + 0xc0, // END_COLLECTION + 0xc0 // END_COLLECTION +}; +// send the buttons, change in x, and change in y +void send_mouse(uint8_t buttons, char dx, char dy, char wheel) +{ + static uint8_t buffer[4] = {0}; + buffer[0] = buttons; + buffer[1] = dx; + buffer[2] = dy; + buffer[3] = wheel; + esp_hidd_dev_input_set(s_ble_hid_param.hid_dev, 0, 0, buffer, 4); +} + +void ble_hid_demo_task_mouse(void *pvParameters) +{ + static const char* help_string = "########################################################################\n"\ + "BT hid mouse demo usage:\n"\ + "You can input these value to simulate mouse: 'q', 'w', 'e', 'a', 's', 'd', 'h'\n"\ + "q -- click the left key\n"\ + "w -- move up\n"\ + "e -- click the right key\n"\ + "a -- move left\n"\ + "s -- move down\n"\ + "d -- move right\n"\ + "h -- show the help\n"\ + "########################################################################\n"; + printf("%s\n", help_string); + char c; + while (1) { + c = fgetc(stdin); + switch (c) { + case 'q': + send_mouse(1, 0, 0, 0); + break; + case 'w': + send_mouse(0, 0, -10, 0); + break; + case 'e': + send_mouse(2, 0, 0, 0); + break; + case 'a': + send_mouse(0, -10, 0, 0); + break; + case 's': + send_mouse(0, 0, 10, 0); + break; + case 'd': + send_mouse(0, 10, 0, 0); + break; + case 'h': + printf("%s\n", help_string); + break; + default: + break; + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} +#endif + +#if CONFIG_EXAMPLE_HID_DEVICE_ROLE && CONFIG_EXAMPLE_HID_DEVICE_ROLE == 2 +#define CASE(a, b, c) \ + case a: \ + buffer[0] = b; \ + buffer[2] = c; \ + break;\ + +// USB keyboard codes +#define USB_HID_MODIFIER_LEFT_CTRL 0x01 +#define USB_HID_MODIFIER_LEFT_SHIFT 0x02 +#define USB_HID_MODIFIER_LEFT_ALT 0x04 +#define USB_HID_MODIFIER_RIGHT_CTRL 0x10 +#define USB_HID_MODIFIER_RIGHT_SHIFT 0x20 +#define USB_HID_MODIFIER_RIGHT_ALT 0x40 + +#define USB_HID_SPACE 0x2C +#define USB_HID_DOT 0x37 +#define USB_HID_NEWLINE 0x28 +#define USB_HID_FSLASH 0x38 +#define USB_HID_BSLASH 0x31 +#define USB_HID_COMMA 0x36 +#define USB_HID_DOT 0x37 + +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 +}; + +static void char_to_code(uint8_t *buffer, char ch) +{ + // Check if lower or upper case + if(ch >= 'a' && ch <= 'z') + { + buffer[0] = 0; + // convert ch to HID letter, starting at a = 4 + buffer[2] = (uint8_t)(4 + (ch - 'a')); + } + else if(ch >= 'A' && ch <= 'Z') + { + // Add left shift + buffer[0] = USB_HID_MODIFIER_LEFT_SHIFT; + // convert ch to lower case + ch = ch - ('A'-'a'); + // convert ch to HID letter, starting at a = 4 + buffer[2] = (uint8_t)(4 + (ch - 'a')); + } + else if(ch >= '0' && ch <= '9') // Check if number + { + buffer[0] = 0; + // convert ch to HID number, starting at 1 = 30, 0 = 39 + if(ch == '0') + { + buffer[2] = 39; + } + else + { + buffer[2] = (uint8_t)(30 + (ch - '1')); + } + } + else // not a letter nor a number + { + switch(ch) + { + CASE(' ', 0, USB_HID_SPACE); + CASE('.', 0,USB_HID_DOT); + CASE('\n', 0, USB_HID_NEWLINE); + CASE('?', USB_HID_MODIFIER_LEFT_SHIFT, USB_HID_FSLASH); + CASE('/', 0 ,USB_HID_FSLASH); + CASE('\\', 0, USB_HID_BSLASH); + CASE('|', USB_HID_MODIFIER_LEFT_SHIFT, USB_HID_BSLASH); + CASE(',', 0, USB_HID_COMMA); + CASE('<', USB_HID_MODIFIER_LEFT_SHIFT, USB_HID_COMMA); + CASE('>', USB_HID_MODIFIER_LEFT_SHIFT, USB_HID_COMMA); + CASE('@', USB_HID_MODIFIER_LEFT_SHIFT, 31); + CASE('!', USB_HID_MODIFIER_LEFT_SHIFT, 30); + CASE('#', USB_HID_MODIFIER_LEFT_SHIFT, 32); + CASE('$', USB_HID_MODIFIER_LEFT_SHIFT, 33); + CASE('%', USB_HID_MODIFIER_LEFT_SHIFT, 34); + CASE('^', USB_HID_MODIFIER_LEFT_SHIFT,35); + CASE('&', USB_HID_MODIFIER_LEFT_SHIFT, 36); + CASE('*', USB_HID_MODIFIER_LEFT_SHIFT, 37); + CASE('(', USB_HID_MODIFIER_LEFT_SHIFT, 38); + CASE(')', USB_HID_MODIFIER_LEFT_SHIFT, 39); + CASE('-', 0, 0x2D); + CASE('_', USB_HID_MODIFIER_LEFT_SHIFT, 0x2D); + CASE('=', 0, 0x2E); + CASE('+', USB_HID_MODIFIER_LEFT_SHIFT, 39); + CASE(8, 0, 0x2A); // backspace + CASE('\t', 0, 0x2B); + default: + buffer[0] = 0; + buffer[2] = 0; + } + } +} + +void send_keyboard(char c) +{ + static uint8_t buffer[8] = {0}; + char_to_code(buffer, c); + esp_hidd_dev_input_set(s_ble_hid_param.hid_dev, 0, 1, buffer, 8); + /* send the keyrelease event with sufficient delay */ + vTaskDelay(50 / portTICK_PERIOD_MS); + memset(buffer, 0, sizeof(uint8_t) * 8); + esp_hidd_dev_input_set(s_ble_hid_param.hid_dev, 0, 1, buffer, 8); +} + +void ble_hid_demo_task_kbd(void *pvParameters) +{ + static const char* help_string = "########################################################################\n"\ + "BT hid keyboard demo usage:\n"\ + "########################################################################\n"; + /* TODO : Add support for function keys and ctrl, alt, esc, etc. */ + printf("%s\n", help_string); + char c; + while (1) { + c = fgetc(stdin); + + if(c != 255) { + send_keyboard(c); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} +#endif static esp_hid_raw_report_map_t ble_report_maps[] = { +#if !CONFIG_BT_NIMBLE_ENABLED || CONFIG_EXAMPLE_HID_DEVICE_ROLE == 1 + /* This block is compiled for bluedroid as well */ { .data = mediaReportMap, .len = sizeof(mediaReportMap) } +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE && CONFIG_EXAMPLE_HID_DEVICE_ROLE == 2 + { + .data = keyboardReportMap, + .len = sizeof(keyboardReportMap) + }, +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE && CONFIG_EXAMPLE_HID_DEVICE_ROLE == 3 + { + .data = mouseReportMap, + .len = sizeof(mouseReportMap) + }, +#endif }; static esp_hid_device_config_t ble_hid_config = { .vendor_id = 0x16C0, .product_id = 0x05DF, .version = 0x0100, +#if CONFIG_EXAMPLE_HID_DEVICE_ROLE == 2 + .device_name = "ESP Keyboard", +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE == 3 + .device_name = "ESP Mouse", +#else .device_name = "ESP BLE HID2", +#endif .manufacturer_name = "Espressif", .serial_number = "1234567890", .report_maps = ble_report_maps, @@ -272,6 +547,7 @@ void esp_hidd_send_consumer_value(uint8_t key_cmd, bool key_pressed) return; } +#if !CONFIG_BT_NIMBLE_ENABLED || CONFIG_EXAMPLE_HID_DEVICE_ROLE == 1 void ble_hid_demo_task(void *pvParameters) { static bool send_volum_up = false; @@ -290,6 +566,7 @@ void ble_hid_demo_task(void *pvParameters) vTaskDelay(2000 / portTICK_PERIOD_MS); } } +#endif void ble_hid_task_start_up(void) { @@ -297,8 +574,23 @@ void ble_hid_task_start_up(void) // Task already exists return; } +#if !CONFIG_BT_NIMBLE_ENABLED + /* Executed for bluedroid */ xTaskCreate(ble_hid_demo_task, "ble_hid_demo_task", 2 * 1024, NULL, configMAX_PRIORITIES - 3, &s_ble_hid_param.task_hdl); +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE == 1 + xTaskCreate(ble_hid_demo_task, "ble_hid_demo_task", 3 * 1024, NULL, configMAX_PRIORITIES - 3, + &s_ble_hid_param.task_hdl); + +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE == 2 + /* Nimble Specific */ + xTaskCreate(ble_hid_demo_task_kbd, "ble_hid_demo_task_kbd", 3 * 1024, NULL, configMAX_PRIORITIES - 3, + &s_ble_hid_param.task_hdl); +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE == 3 + /* Nimble Specific */ + xTaskCreate(ble_hid_demo_task_mouse, "ble_hid_demo_task_mouse", 3 * 1024, NULL, configMAX_PRIORITIES - 3, + &s_ble_hid_param.task_hdl); +#endif } void ble_hid_task_shut_down(void) @@ -557,6 +849,18 @@ static void bt_hidd_event_callback(void *handler_args, esp_event_base_t base, in } #endif +#if CONFIG_BT_NIMBLE_ENABLED +void ble_hid_device_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} +void ble_store_config_init(void); +#endif + void app_main(void) { esp_err_t ret; @@ -575,14 +879,21 @@ void app_main(void) ret = esp_hid_gap_init(HID_DEV_MODE); ESP_ERROR_CHECK( ret ); -#if CONFIG_BT_BLE_ENABLED +#if CONFIG_BT_BLE_ENABLED || CONFIG_BT_NIMBLE_ENABLED +#if CONFIG_EXAMPLE_HID_DEVICE_ROLE == 2 + ret = esp_hid_ble_gap_adv_init(ESP_HID_APPEARANCE_KEYBOARD, ble_hid_config.device_name); +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE == 3 + ret = esp_hid_ble_gap_adv_init(ESP_HID_APPEARANCE_MOUSE, ble_hid_config.device_name); +#else ret = esp_hid_ble_gap_adv_init(ESP_HID_APPEARANCE_GENERIC, ble_hid_config.device_name); +#endif ESP_ERROR_CHECK( ret ); - +#if CONFIG_BT_BLE_ENABLED if ((ret = esp_ble_gatts_register_callback(esp_hidd_gatts_event_handler)) != ESP_OK) { ESP_LOGE(TAG, "GATTS register callback failed: %d", ret); return; } +#endif ESP_LOGI(TAG, "setting ble device"); ESP_ERROR_CHECK( esp_hidd_dev_init(&ble_hid_config, ESP_HID_TRANSPORT_BLE, ble_hidd_event_callback, &s_ble_hid_param.hid_dev)); @@ -600,4 +911,15 @@ void app_main(void) ESP_ERROR_CHECK( esp_hidd_dev_init(&bt_hid_config, ESP_HID_TRANSPORT_BT, bt_hidd_event_callback, &s_bt_hid_param.hid_dev)); #endif +#if CONFIG_BT_NIMBLE_ENABLED + /* XXX Need to have template for store */ + ble_store_config_init(); + + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + /* Starting nimble task after gatts is initialized*/ + ret = esp_nimble_enable(ble_hid_device_host_task); + if (ret) { + ESP_LOGE(TAG, "esp_nimble_enable failed: %d", ret); + } +#endif } diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c index 1f0d2a460b..472fc6e71d 100644 --- a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c +++ b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c @@ -15,17 +15,28 @@ #include "esp_hid_gap.h" +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#include "nimble/nimble_port.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "nimble/ble.h" +#include "host/ble_sm.h" +#endif + 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"}; +#if !CONFIG_BT_NIMBLE_ENABLED 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; +#endif static SemaphoreHandle_t bt_hidh_cb_semaphore = NULL; #define WAIT_BT_CB() xSemaphoreTake(bt_hidh_cb_semaphore, portMAX_DELAY) @@ -37,6 +48,7 @@ static SemaphoreHandle_t ble_hidh_cb_semaphore = NULL; #define SIZEOF_ARRAY(a) (sizeof(a)/sizeof(*a)) +#if !CONFIG_BT_NIMBLE_ENABLED 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"}; @@ -64,6 +76,7 @@ const char *bt_gap_evt_str(uint8_t event) } return bt_gap_evt_names[event]; } +#endif #if CONFIG_BT_BLE_ENABLED const char *esp_ble_key_type_str(esp_ble_key_type_t key_type) @@ -106,6 +119,7 @@ const char *esp_ble_key_type_str(esp_ble_key_type_t key_type) } #endif /* CONFIG_BT_BLE_ENABLED */ +#if !CONFIG_BT_NIMBLE_ENABLED void esp_hid_scan_results_free(esp_hid_scan_result_t *results) { esp_hid_scan_result_t *r = NULL; @@ -118,6 +132,7 @@ void esp_hid_scan_results_free(esp_hid_scan_result_t *results) free(r); } } +#endif #if (CONFIG_BT_HID_DEVICE_ENABLED || CONFIG_BT_BLE_ENABLED) static esp_hid_scan_result_t *find_scan_result(esp_bd_addr_t bda, esp_hid_scan_result_t *results) @@ -223,6 +238,7 @@ static void add_ble_scan_result(esp_bd_addr_t bda, esp_ble_addr_type_t addr_type } #endif /* CONFIG_BT_BLE_ENABLED */ +#if !CONFIG_BT_NIMBLE_ENABLED void print_uuid(esp_bt_uuid_t *uuid) { if (uuid->len == ESP_UUID_LEN_16) { @@ -238,6 +254,7 @@ void print_uuid(esp_bt_uuid_t *uuid) uuid->uuid.uuid128[13], uuid->uuid.uuid128[14], uuid->uuid.uuid128[15]); } } +#endif #if CONFIG_BT_HID_DEVICE_ENABLED static void handle_bt_device_result(struct disc_res_param *disc_res) @@ -616,6 +633,7 @@ static esp_err_t start_ble_scan(uint32_t seconds) return ret; } +#if !CONFIG_BT_NIMBLE_ENABLED esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name) { @@ -693,7 +711,7 @@ esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name) return ret; } - +#endif esp_err_t esp_hid_ble_gap_adv_start(void) { static esp_ble_adv_params_t hidd_adv_params = { @@ -707,11 +725,212 @@ esp_err_t esp_hid_ble_gap_adv_start(void) return esp_ble_gap_start_advertising(&hidd_adv_params); } #endif /* CONFIG_BT_BLE_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED +static struct ble_hs_adv_fields fields; +#define GATT_SVR_SVC_HID_UUID 0x1812 +esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name) +{ + ble_uuid16_t *uuid16, *uuid16_1; + /** + * Set the advertisement data included in our advertisements: + * o Flags (indicates advertisement type and other general info). + * o Advertising tx power. + * o Device name. + * o 16-bit service UUIDs (HID). + */ + + memset(&fields, 0, sizeof fields); + + /* Advertise two flags: + * o Discoverability in forthcoming advertisement (general) + * o BLE-only (BR/EDR unsupported). + */ + fields.flags = BLE_HS_ADV_F_DISC_GEN | + BLE_HS_ADV_F_BREDR_UNSUP; + + /* Indicate that the TX power level field should be included; have the + * stack fill this value automatically. This is done by assigning the + * special value BLE_HS_ADV_TX_PWR_LVL_AUTO. + */ + fields.tx_pwr_lvl_is_present = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + + fields.name = (uint8_t *)device_name; + fields.name_len = strlen(device_name); + fields.name_is_complete = 1; + + uuid16 = (ble_uuid16_t *)malloc(sizeof(ble_uuid16_t)); + uuid16_1 = (ble_uuid16_t[]) { + BLE_UUID16_INIT(GATT_SVR_SVC_HID_UUID) + }; + memcpy(uuid16, uuid16_1, sizeof(ble_uuid16_t)); + fields.uuids16 = uuid16; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; + + /* Initialize the security configuration */ + ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_ONLY; + ble_hs_cfg.sm_bonding = 1; + ble_hs_cfg.sm_mitm = 1; + ble_hs_cfg.sm_sc = 1; + ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ID | BLE_SM_PAIR_KEY_DIST_ENC; + ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID | BLE_SM_PAIR_KEY_DIST_ENC; + + return ESP_OK; + +} + +static int +nimble_hid_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + ESP_LOGI(TAG, "connection %s; status=%d", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + return 0; + break; + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "disconnect; reason=%d", event->disconnect.reason); + + return 0; + case BLE_GAP_EVENT_CONN_UPDATE: + /* The central has updated the connection parameters. */ + ESP_LOGI(TAG, "connection updated; status=%d", + event->conn_update.status); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + ESP_LOGI(TAG, "advertise complete; reason=%d", + event->adv_complete.reason); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + ESP_LOGI(TAG, "subscribe event; conn_handle=%d attr_handle=%d " + "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", + event->subscribe.conn_handle, + event->subscribe.attr_handle, + event->subscribe.reason, + event->subscribe.prev_notify, + event->subscribe.cur_notify, + event->subscribe.prev_indicate, + event->subscribe.cur_indicate); + return 0; + + case BLE_GAP_EVENT_MTU: + ESP_LOGI(TAG, "mtu update event; conn_handle=%d cid=%d mtu=%d", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + /* Encryption has been enabled or disabled for this connection. */ + MODLOG_DFLT(INFO, "encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + return 0; + + case BLE_GAP_EVENT_NOTIFY_TX: + MODLOG_DFLT(INFO, "notify_tx event; conn_handle=%d attr_handle=%d " + "status=%d is_indication=%d", + event->notify_tx.conn_handle, + event->notify_tx.attr_handle, + event->notify_tx.status, + event->notify_tx.indication); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ + + /* Delete the old bond. */ + rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + assert(rc == 0); + ble_store_util_delete_peer(&desc.peer_id_addr); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + + case BLE_GAP_EVENT_PASSKEY_ACTION: + ESP_LOGI(TAG, "PASSKEY_ACTION_EVENT started"); + struct ble_sm_io pkey = {0}; + int key = 0; + + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; // This is the passkey to be entered on peer + ESP_LOGI(TAG, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey); + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + ESP_LOGI(TAG, "Accepting passkey.."); + pkey.action = event->passkey.params.action; + pkey.numcmp_accept = key; + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { + static uint8_t tem_oob[16] = {0}; + pkey.action = event->passkey.params.action; + for (int i = 0; i < 16; i++) { + pkey.oob[i] = tem_oob[i]; + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { + ESP_LOGI(TAG, "Input not supported passing -> 123456"); + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } + return 0; + } + return 0; +} +esp_err_t esp_hid_ble_gap_adv_start(void) +{ + int rc; + struct ble_gap_adv_params adv_params; + /* maximum possible duration for hid device(180s) */ + int32_t adv_duration_ms = 180000; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc); + return rc; + } + /* Begin advertising. */ + memset(&adv_params, 0, sizeof adv_params); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + adv_params.itvl_min = BLE_GAP_ADV_ITVL_MS(30);/* Recommended interval 30ms to 50ms */ + adv_params.itvl_max = BLE_GAP_ADV_ITVL_MS(50); + rc = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, adv_duration_ms, + &adv_params, nimble_hid_gap_event, NULL); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc); + return rc; + } + return rc; +} +#endif + /* * CONTROLLER INIT * */ +#if !CONFIG_BT_NIMBLE_ENABLED static esp_err_t init_low_level(uint8_t mode) { esp_err_t ret; @@ -777,6 +996,43 @@ static esp_err_t init_low_level(uint8_t mode) #endif /* CONFIG_BT_BLE_ENABLED */ return ret; } +#endif + +#if CONFIG_BT_NIMBLE_ENABLED +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 CONFIG_IDF_TARGET_ESP32 + bt_cfg.mode = mode; +#endif + 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_nimble_init(); + if (ret) { + ESP_LOGE(TAG, "esp_nimble_init failed: %d", ret); + return ret; + } + + + return ret; +} +#endif esp_err_t esp_hid_gap_init(uint8_t mode) { @@ -817,6 +1073,7 @@ esp_err_t esp_hid_gap_init(uint8_t mode) return ESP_OK; } +#if !CONFIG_BT_NIMBLE_ENABLED 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) { @@ -857,3 +1114,4 @@ esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_resul ble_scan_results = NULL; return ESP_OK; } +#endif diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.h b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.h index 8e2cf3897b..5211c90c83 100644 --- a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.h +++ b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -20,6 +20,8 @@ #endif #elif CONFIG_BT_BLE_ENABLED #define HID_DEV_MODE HIDD_BLE_MODE +#elif CONFIG_BT_NIMBLE_ENABLED +#define HID_DEV_MODE HIDD_BLE_MODE #else #define HID_DEV_MODE HIDD_IDLE_MODE #endif @@ -28,9 +30,11 @@ #include "esp_log.h" #include "esp_bt.h" +#if !CONFIG_BT_NIMBLE_ENABLED #include "esp_bt_defs.h" #include "esp_bt_main.h" #include "esp_gap_bt_api.h" +#endif #include "esp_hid_common.h" #if CONFIG_BT_BLE_ENABLED #include "esp_gattc_api.h" @@ -42,6 +46,7 @@ extern "C" { #endif +#if !CONFIG_BT_NIMBLE_ENABLED typedef struct esp_hidh_scan_result_s { struct esp_hidh_scan_result_s *next; @@ -62,16 +67,17 @@ typedef struct esp_hidh_scan_result_s { }; } 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); +const char *ble_addr_type_str(esp_ble_addr_type_t ble_addr_type); +void print_uuid(esp_bt_uuid_t *uuid); +#endif + +esp_err_t esp_hid_gap_init(uint8_t mode); 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 diff --git a/examples/bluetooth/esp_hid_host/main/Kconfig.projbuild b/examples/bluetooth/esp_hid_host/main/Kconfig.projbuild index 568ef5ae62..5b0fa17ed0 100644 --- a/examples/bluetooth/esp_hid_host/main/Kconfig.projbuild +++ b/examples/bluetooth/esp_hid_host/main/Kconfig.projbuild @@ -6,4 +6,12 @@ menu "HID Example Configuration" help This enables the Secure Simple Pairing. If disable this option, Bluedroid will only support Legacy Pairing + + config EXAMPLE_HID_HOST_ENABLED + bool "Enable Example HID Host" + depends on BT_NIMBLE_ENABLED + default y + select BT_NIMBLE_HID_SERVICE + help + This enables Nimble HID Host endmenu diff --git a/examples/bluetooth/esp_hid_host/main/esp_hid_gap.c b/examples/bluetooth/esp_hid_host/main/esp_hid_gap.c index ca16f6191f..0299b37d11 100644 --- a/examples/bluetooth/esp_hid_host/main/esp_hid_gap.c +++ b/examples/bluetooth/esp_hid_host/main/esp_hid_gap.c @@ -14,6 +14,15 @@ #include "freertos/semphr.h" #include "esp_hid_gap.h" +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#include "nimble/nimble_port.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "nimble/ble.h" +#include "host/ble_sm.h" +#define BLE_HID_SVC_UUID 0x1812 /* HID Service*/ +#endif static const char *TAG = "ESP_HID_GAP"; @@ -40,6 +49,7 @@ static SemaphoreHandle_t ble_hidh_cb_semaphore = NULL; #define SIZEOF_ARRAY(a) (sizeof(a)/sizeof(*a)) +#if !CONFIG_BT_NIMBLE_ENABLED 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"}; @@ -67,7 +77,7 @@ const char *bt_gap_evt_str(uint8_t event) } return bt_gap_evt_names[event]; } - +#endif #if CONFIG_BT_BLE_ENABLED const char *esp_ble_key_type_str(esp_ble_key_type_t key_type) { @@ -136,6 +146,19 @@ static esp_hid_scan_result_t *find_scan_result(esp_bd_addr_t bda, esp_hid_scan_r } #endif /* (CONFIG_BT_HID_HOST_ENABLED || CONFIG_BT_BLE_ENABLED) */ +#if (CONFIG_BT_NIMBLE_ENABLED) +static esp_hid_scan_result_t *find_scan_result(const uint8_t *bda, esp_hid_scan_result_t *results) +{ + esp_hid_scan_result_t *r = results; + while (r) { + if (memcmp(bda, r->bda, sizeof(r->bda)) == 0) { + return r; + } + r = r->next; + } + return NULL; +} +#endif #if CONFIG_BT_HID_HOST_ENABLED 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) { @@ -226,6 +249,43 @@ static void add_ble_scan_result(esp_bd_addr_t bda, esp_ble_addr_type_t addr_type } #endif /* CONFIG_BT_BLE_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED +static void add_ble_scan_result(const uint8_t *bda, uint8_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(r->bda)); + 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++; +} +#endif /* CONFIG_BT_BLE_ENABLED */ + +#if !CONFIG_BT_NIMBLE_ENABLED void print_uuid(esp_bt_uuid_t *uuid) { if (uuid->len == ESP_UUID_LEN_16) { @@ -575,6 +635,7 @@ static void ble_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_p break; } } +#endif static esp_err_t init_ble_gap(void) { @@ -708,6 +769,252 @@ esp_err_t esp_hid_ble_gap_adv_start(void) * CONTROLLER INIT * */ +#if CONFIG_BT_NIMBLE_ENABLED +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 CONFIG_IDF_TARGET_ESP32 + bt_cfg.mode = mode; +#endif + 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_nimble_init(); + if (ret) { + ESP_LOGE(TAG, "esp_nimble_init failed: %d", ret); + return ret; + } + + return ret; +} + +static void handle_ble_device_result(const struct ble_gap_disc_desc *disc) +{ + int rc; + struct ble_hs_adv_fields fields; + uint16_t appearance; + uint8_t adv_name[BLE_HS_ADV_MAX_SZ]; + uint8_t adv_name_len = 0; + + appearance = 0; /* silent warnings for now */ + rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); + if(rc != 0) { + return; + } + + if (fields.name != NULL) { + assert(fields.name_len < sizeof adv_name - 1); + memcpy(adv_name, fields.name, fields.name_len); + adv_name[fields.name_len] = '\0'; + adv_name_len = fields.name_len; + MODLOG_DFLT(DEBUG, " name(%scomplete)=%s\n", + fields.name_is_complete ? "" : "in", adv_name); + } + + if (fields.appearance_is_present) { + MODLOG_DFLT(DEBUG, " appearance=0x%04x\n", fields.appearance); + appearance = fields.appearance; + } + + for (int i = 0; i < fields.num_uuids16; i++) { + if (ble_uuid_u16(&fields.uuids16[i].u) == BLE_HID_SVC_UUID && + ((adv_name_len > 0 && memcmp("ESP BLE HID2", adv_name, adv_name_len) == 0) || + (adv_name_len > 0 && memcmp("ESP Mouse", adv_name, adv_name_len) == 0) || + (adv_name_len > 0 && memcmp("ESP Keyboard", adv_name, adv_name_len) == 0))) { + add_ble_scan_result(disc->addr.val, disc->addr.type, appearance, adv_name, adv_name_len, disc->rssi); + break; + } + } +} +#endif + +#if CONFIG_BT_NIMBLE_ENABLED +static int +nimble_hid_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_DISC: + handle_ble_device_result(&event->disc); + if (rc != 0) { + return 0; + } + + /* An advertisment report was received during GAP discovery. */ + return 0; + break; + case BLE_GAP_EVENT_DISC_COMPLETE: + MODLOG_DFLT(INFO, "discovery complete; reason=%d\n", + event->disc_complete.reason); + SEND_BLE_CB(); + return 0; + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + ESP_LOGI(TAG, "connection %s; status=%d", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + return 0; + break; + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "disconnect; reason=%d", event->disconnect.reason); + + return 0; + case BLE_GAP_EVENT_CONN_UPDATE: + /* The central has updated the connection parameters. */ + ESP_LOGI(TAG, "connection updated; status=%d", + event->conn_update.status); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + ESP_LOGI(TAG, "advertise complete; reason=%d", + event->adv_complete.reason); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + ESP_LOGI(TAG, "subscribe event; conn_handle=%d attr_handle=%d " + "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", + event->subscribe.conn_handle, + event->subscribe.attr_handle, + event->subscribe.reason, + event->subscribe.prev_notify, + event->subscribe.cur_notify, + event->subscribe.prev_indicate, + event->subscribe.cur_indicate); + return 0; + + case BLE_GAP_EVENT_MTU: + ESP_LOGI(TAG, "mtu update event; conn_handle=%d cid=%d mtu=%d", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + /* Encryption has been enabled or disabled for this connection. */ + MODLOG_DFLT(INFO, "encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + return 0; + + case BLE_GAP_EVENT_NOTIFY_TX: + MODLOG_DFLT(INFO, "notify_tx event; conn_handle=%d attr_handle=%d " + "status=%d is_indication=%d", + event->notify_tx.conn_handle, + event->notify_tx.attr_handle, + event->notify_tx.status, + event->notify_tx.indication); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ + + /* Delete the old bond. */ + rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + assert(rc == 0); + ble_store_util_delete_peer(&desc.peer_id_addr); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + + case BLE_GAP_EVENT_PASSKEY_ACTION: + ESP_LOGI(TAG, "PASSKEY_ACTION_EVENT started"); + struct ble_sm_io pkey = {0}; + int key = 0; + + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; // This is the passkey to be entered on peer + ESP_LOGI(TAG, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey); + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + ESP_LOGI(TAG, "Accepting passkey.."); + pkey.action = event->passkey.params.action; + pkey.numcmp_accept = key; + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { + static uint8_t tem_oob[16] = {0}; + pkey.action = event->passkey.params.action; + for (int i = 0; i < 16; i++) { + pkey.oob[i] = tem_oob[i]; + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { + ESP_LOGI(TAG, "Input not supported passing -> 123456"); + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } + return 0; + } + return 0; +} + +static esp_err_t start_nimble_scan(uint32_t seconds) +{ + uint8_t own_addr_type; + struct ble_gap_disc_params disc_params; + int rc; + + /* Figure out address to use while advertising (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc); + return rc; + } + + /* Tell the controller to filter duplicates; we don't want to process + * repeated advertisements from the same device. + */ + disc_params.filter_duplicates = 1; + + /** + * Perform active scan. + */ + disc_params.passive = 0; + + /* Use defaults for the rest of the parameters. */ + disc_params.itvl = 0x50; + disc_params.window = 0x30; + disc_params.filter_policy = 0; + disc_params.limited = 0; + + rc = ble_gap_disc(own_addr_type, seconds * 1000, &disc_params, + nimble_hid_gap_event, NULL); + if (rc != 0) { + MODLOG_DFLT(ERROR, "Error initiating GAP discovery procedure; rc=%d\n", + rc); + } + return rc; +} + +#else static esp_err_t init_low_level(uint8_t mode) { esp_err_t ret; @@ -773,6 +1080,7 @@ static esp_err_t init_low_level(uint8_t mode) #endif /* CONFIG_BT_BLE_ENABLED */ return ret; } +#endif esp_err_t esp_hid_gap_init(uint8_t mode) { @@ -827,6 +1135,14 @@ esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_resul return ESP_FAIL; } #endif /* CONFIG_BT_BLE_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED + if (start_nimble_scan(seconds) == ESP_OK) { + WAIT_BLE_CB(); + } else { + return ESP_FAIL; + } +#endif /* CONFIG_BT_BLE_ENABLED */ + #if CONFIG_BT_HID_HOST_ENABLED if (start_bt_scan(seconds) == ESP_OK) { diff --git a/examples/bluetooth/esp_hid_host/main/esp_hid_gap.h b/examples/bluetooth/esp_hid_host/main/esp_hid_gap.h index de46b2fd31..33bad93103 100644 --- a/examples/bluetooth/esp_hid_host/main/esp_hid_gap.h +++ b/examples/bluetooth/esp_hid_host/main/esp_hid_gap.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -21,6 +21,8 @@ #endif #elif CONFIG_BT_BLE_ENABLED #define HID_HOST_MODE HIDH_BLE_MODE +#elif CONFIG_BT_NIMBLE_ENABLED +#define HID_HOST_MODE HIDH_BLE_MODE #else #define HID_HOST_MODE HIDH_IDLE_MODE #endif @@ -29,9 +31,11 @@ #include "esp_log.h" #include "esp_bt.h" +#if !CONFIG_BT_NIMBLE_ENABLED #include "esp_bt_defs.h" #include "esp_bt_main.h" #include "esp_gap_bt_api.h" +#endif #include "esp_hid_common.h" #if CONFIG_BT_BLE_ENABLED #include "esp_gattc_api.h" @@ -39,19 +43,28 @@ #include "esp_gap_ble_api.h" #endif +#if CONFIG_BT_NIMBLE_ENABLED +#include "nimble/ble.h" +#endif + #ifdef __cplusplus extern "C" { #endif typedef struct esp_hidh_scan_result_s { struct esp_hidh_scan_result_s *next; - +#if CONFIG_BT_NIMBLE_ENABLED + uint8_t bda[6]; +#else esp_bd_addr_t bda; +#endif + const char *name; int8_t rssi; esp_hid_usage_t usage; esp_hid_transport_t transport; //BT, BLE or USB union { + #if !CONFIG_BT_NIMBLE_ENABLED struct { esp_bt_cod_t cod; esp_bt_uuid_t uuid; @@ -60,6 +73,12 @@ typedef struct esp_hidh_scan_result_s { esp_ble_addr_type_t addr_type; uint16_t appearance; } ble; + #else + struct { + uint8_t addr_type; + uint16_t appearance; + } ble; + #endif }; } esp_hid_scan_result_t; @@ -70,8 +89,10 @@ 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); +#if !CONFIG_BT_NIMBLE_ENABLED void print_uuid(esp_bt_uuid_t *uuid); const char *ble_addr_type_str(esp_ble_addr_type_t ble_addr_type); +#endif #ifdef __cplusplus } diff --git a/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c b/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c index 81d31ea512..420ff9d72b 100644 --- a/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c +++ b/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -17,12 +17,34 @@ #include "esp_log.h" #include "nvs_flash.h" #include "esp_bt.h" + +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#else #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" +#endif + +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#define ESP_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" +#define ESP_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] +#else +#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" +#endif #include "esp_hidh.h" #include "esp_hid_gap.h" @@ -99,6 +121,13 @@ void hid_demo_task(void *pvParameters) printf("ADDR_TYPE: '%s', ", ble_addr_type_str(r->ble.addr_type)); } #endif /* CONFIG_BT_BLE_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED + if (r->transport == ESP_HID_TRANSPORT_BLE) { + cr = r; + printf("APPEARANCE: 0x%04x, ", r->ble.appearance); + printf("ADDR_TYPE: '%d', ", r->ble.addr_type); + } +#endif /* CONFIG_BT_BLE_ENABLED */ #if CONFIG_BT_HID_HOST_ENABLED if (r->transport == ESP_HID_TRANSPORT_BT) { cr = r; @@ -123,6 +152,17 @@ void hid_demo_task(void *pvParameters) vTaskDelete(NULL); } +#if CONFIG_BT_NIMBLE_ENABLED +void ble_hid_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} +void ble_store_config_init(void); +#endif void app_main(void) { esp_err_t ret; @@ -148,5 +188,16 @@ void app_main(void) }; ESP_ERROR_CHECK( esp_hidh_init(&config) ); +#if CONFIG_BT_NIMBLE_ENABLED + /* XXX Need to have template for store */ + ble_store_config_init(); + + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + /* Starting nimble task after gatts is initialized*/ + ret = esp_nimble_enable(ble_hid_host_task); + if (ret) { + ESP_LOGE(TAG, "esp_nimble_enable failed: %d", ret); + } +#endif xTaskCreate(&hid_demo_task, "hid_task", 6 * 1024, NULL, 2, NULL); } diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index 13fb791f2c..b0ef4234ad 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -412,13 +412,9 @@ components/esp_hid/include/esp_hidd_transport.h components/esp_hid/include/esp_hidh.h components/esp_hid/include/esp_hidh_bluedroid.h components/esp_hid/include/esp_hidh_gattc.h -components/esp_hid/include/esp_hidh_transport.h -components/esp_hid/private/ble_hidd.h -components/esp_hid/private/ble_hidh.h components/esp_hid/private/bt_hidd.h components/esp_hid/private/bt_hidh.h components/esp_hid/private/esp_hidd_private.h -components/esp_hid/src/esp_hid_common.c components/esp_local_ctrl/src/esp_local_ctrl_handler.c components/esp_local_ctrl/src/esp_local_ctrl_priv.h components/esp_local_ctrl/src/esp_local_ctrl_transport_ble.c