-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(hid_service): Added BLE HID Service (#160)
* feat(hid_service): Added BLE HID Service * Added BLE HID service component and associated example * Update and rebuild docs * Update CI * readme: update * add video to readme as well * update how the hat value is sent so it uses the button index - which helps test invalid values and shows the center value
- Loading branch information
Showing
113 changed files
with
1,411 additions
and
199 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
idf_component_register( | ||
INCLUDE_DIRS "include" | ||
REQUIRES "esp-nimble-cpp" "base_component" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# The following lines of boilerplate have to be in your project's CMakeLists | ||
# in this exact order for cmake to work correctly | ||
cmake_minimum_required(VERSION 3.5) | ||
|
||
include($ENV{IDF_PATH}/tools/cmake/project.cmake) | ||
|
||
|
||
# add the component directories that we want to use | ||
set(EXTRA_COMPONENT_DIRS | ||
"../../../components/" | ||
) | ||
|
||
set( | ||
COMPONENTS | ||
"main esptool_py ble_gatt_server hid_service hid-rp" | ||
CACHE STRING | ||
"List of components to include" | ||
) | ||
|
||
project(hid_service_example) | ||
|
||
set(CMAKE_CXX_STANDARD 20) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# BLE HID Service Example | ||
|
||
This example shows how to use the `espp::HidService` class together with the | ||
`espp::BleGattServer` class to create and manage a BLE GATT server that provides | ||
an HID service. It uses the `hid-rp` component's `espp::GamepadReport<>` to | ||
define a HID gamepad report descriptor and generate input reports. | ||
|
||
https://github.com/esp-cpp/espp/assets/213467/20ff49e3-42e2-4e69-9c91-d1926071f665 | ||
|
||
## How to use example | ||
|
||
### Hardware Required | ||
|
||
This example should run on any ESP32s3 development board as it requires no | ||
peripheral connections. | ||
|
||
### Build and Flash | ||
|
||
Build the project and flash it to the board, then run monitor tool to view serial output: | ||
|
||
``` | ||
idf.py -p PORT flash monitor | ||
``` | ||
|
||
(Replace PORT with the name of the serial port to use.) | ||
|
||
(To exit the serial monitor, type ``Ctrl-]``.) | ||
|
||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. | ||
|
||
## Example Output | ||
|
||
![CleanShot 2024-02-28 at 17 23 49](https://github.com/esp-cpp/espp/assets/213467/80199bb6-15e8-4396-af4a-d9a4b8b95ace) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
idf_component_register(SRC_DIRS "." | ||
INCLUDE_DIRS ".") |
157 changes: 157 additions & 0 deletions
157
components/hid_service/example/main/hid_service_example.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
#include <chrono> | ||
#include <vector> | ||
|
||
#include "ble_gatt_server.hpp" | ||
#include "hid_service.hpp" | ||
|
||
#include "hid-rp-gamepad.hpp" | ||
|
||
using namespace std::chrono_literals; | ||
|
||
extern "C" void app_main(void) { | ||
espp::Logger logger({.tag = "Hid Service Example", .level = espp::Logger::Verbosity::INFO}); | ||
logger.info("Starting"); | ||
|
||
//! [hid service example] | ||
|
||
// NOTE: esp-nimble-cpp already depends on nvs_flash and initializes | ||
// nvs_flash in the NimBLEDevice::init(), so we don't have to do that | ||
// to store bonding info | ||
|
||
// create the GATT server | ||
espp::BleGattServer ble_gatt_server; | ||
std::string device_name = "ESP++ HID"; | ||
ble_gatt_server.set_log_level(espp::Logger::Verbosity::INFO); | ||
ble_gatt_server.set_callbacks({ | ||
.connect_callback = [&](NimBLEConnInfo &conn_info) { logger.info("Device connected"); }, | ||
.disconnect_callback = [&](NimBLEConnInfo &conn_info) { logger.info("Device disconnected"); }, | ||
.authentication_complete_callback = | ||
[&](NimBLEConnInfo &conn_info) { logger.info("Device authenticated"); }, | ||
}); | ||
ble_gatt_server.init(device_name); | ||
ble_gatt_server.set_advertise_on_disconnect(true); | ||
|
||
// for HID we need to set some security | ||
bool bonding = true; | ||
bool mitm = false; | ||
bool secure_connections = true; | ||
ble_gatt_server.set_security(bonding, mitm, secure_connections); | ||
// and some i/o and key config | ||
ble_gatt_server.set_io_capabilities(BLE_HS_IO_NO_INPUT_OUTPUT); | ||
ble_gatt_server.set_init_key_distribution(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); | ||
ble_gatt_server.set_resp_key_distribution(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); | ||
|
||
// let's create a HID service | ||
espp::HidService hid_service; | ||
hid_service.init(ble_gatt_server.server()); | ||
|
||
// configure it some | ||
uint8_t country_code = 0x00; | ||
uint8_t hid_info_flags = 0x01; | ||
hid_service.set_info(country_code, hid_info_flags); | ||
|
||
static constexpr uint8_t report_id = 1; | ||
static constexpr size_t num_buttons = 15; | ||
static constexpr int joystick_min = 0; | ||
static constexpr int joystick_max = 65535; | ||
static constexpr int trigger_min = 0; | ||
static constexpr int trigger_max = 1024; | ||
|
||
using Gamepad = espp::GamepadReport<num_buttons, joystick_min, joystick_max, trigger_min, | ||
trigger_max, report_id>; | ||
Gamepad gamepad_input_report; | ||
|
||
// Generate the report descriptor for the gamepad | ||
auto descriptor = gamepad_input_report.get_descriptor(); | ||
|
||
logger.info("Report Descriptor:"); | ||
logger.info(" Size: {}", descriptor.size()); | ||
logger.info(" Data: {::#02x}", descriptor); | ||
|
||
// set the report map (vector of bytes) | ||
hid_service.set_report_map(descriptor); | ||
|
||
// use the HID service to make an input report characteristic | ||
auto input_report = hid_service.input_report(report_id); | ||
|
||
// now that we've made the input characteristic, we can start the service | ||
hid_service.start(); | ||
// starts the device info service and battery service, see | ||
// hid_service_example for more info | ||
ble_gatt_server.start_services(); | ||
|
||
// now start the gatt server | ||
ble_gatt_server.start(); | ||
|
||
// let's set some of the service data | ||
auto &battery_service = ble_gatt_server.battery_service(); | ||
battery_service.set_battery_level(99); | ||
|
||
auto &device_info_service = ble_gatt_server.device_info_service(); | ||
uint8_t vendor_source = 0x02; // USB | ||
uint16_t vid = 0x045E; // Microsoft | ||
uint16_t pid = 0x02FD; // Xbox One Controller | ||
uint16_t product_version = 0x0100; | ||
device_info_service.set_pnp_id(vendor_source, vid, pid, product_version); | ||
device_info_service.set_manufacturer_name("ESP-CPP"); | ||
device_info_service.set_model_number("esp-hid-01"); | ||
device_info_service.set_serial_number("1234567890"); | ||
device_info_service.set_software_version("1.0.0"); | ||
device_info_service.set_firmware_version("1.0.0"); | ||
device_info_service.set_hardware_version("1.0.0"); | ||
|
||
// now lets start advertising | ||
espp::BleGattServer::AdvertisingData adv_data = { | ||
.name = device_name, | ||
.appearance = 0x03C4, // Gamepad | ||
.services = | ||
{ | ||
// these are the services that we want to advertise | ||
hid_service.uuid(), // hid service | ||
}, | ||
.service_data = | ||
{ | ||
// these are the service data that we want to advertise | ||
}, | ||
}; | ||
espp::BleGattServer::AdvertisingParameters adv_params = {}; | ||
ble_gatt_server.start_advertising(adv_data, adv_params); | ||
|
||
// now lets update the battery level and send an input report every second | ||
uint8_t battery_level = 99; | ||
// change the gamepad inputs every second | ||
int button_index = 1; | ||
while (true) { | ||
auto start = std::chrono::steady_clock::now(); | ||
|
||
// update the battery level | ||
battery_service.set_battery_level(battery_level); | ||
battery_level = (battery_level % 100) + 1; | ||
|
||
// cycle through the possible d-pad states | ||
Gamepad::Hat hat = (Gamepad::Hat)button_index; | ||
// use the button index to set the position of the right joystick | ||
float angle = 2.0f * M_PI * button_index / num_buttons; | ||
|
||
gamepad_input_report.reset(); | ||
gamepad_input_report.set_hat(hat); | ||
gamepad_input_report.set_button(button_index, true); | ||
// joystick inputs are in the range [-1, 1] float | ||
gamepad_input_report.set_right_joystick(cos(angle), sin(angle)); | ||
gamepad_input_report.set_left_joystick(sin(angle), cos(angle)); | ||
// trigger inputs are in the range [0, 1] float | ||
gamepad_input_report.set_accelerator(std::abs(sin(angle))); | ||
gamepad_input_report.set_brake(std::abs(cos(angle))); | ||
|
||
button_index = (button_index % num_buttons) + 1; | ||
|
||
// send an input report | ||
auto report = gamepad_input_report.get_report(); | ||
logger.debug("Sending report data ({}): {::#02x}", report.size(), report); | ||
input_report->notify(report); | ||
|
||
// sleep | ||
std::this_thread::sleep_until(start + 1s); | ||
} | ||
//! [hid service example] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Name, Type, SubType, Offset, Size | ||
nvs, data, nvs, 0x9000, 0x6000 | ||
phy_init, data, phy, 0xf000, 0x1000 | ||
factory, app, factory, 0x10000, 2M | ||
littlefs, data, spiffs, , 1M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
CONFIG_IDF_TARGET="esp32s3" | ||
|
||
# on the ESP32S3, which has native USB, we need to set the console so that the | ||
# CLI can be configured correctly: | ||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y | ||
|
||
# Common ESP-related | ||
# | ||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 | ||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 | ||
|
||
CONFIG_FREERTOS_HZ=1000 | ||
|
||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y | ||
|
||
# | ||
# BT config | ||
# | ||
CONFIG_BT_ENABLED=y | ||
CONFIG_BT_BLUEDROID_ENABLED=n | ||
CONFIG_BT_NIMBLE_ENABLED=y | ||
CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y | ||
CONFIG_BT_NIMBLE_NVS_PERSIST=y | ||
CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=100 | ||
CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192 | ||
|
||
# NOTE: we can support extended advertising (longer advertisement packets) by | ||
# enabling the following: | ||
# CONFIG_BT_NIMBLE_EXT_ADV=y | ||
# | ||
# HOWEVER: we don't currently support this as the API to ble_gatt_server will | ||
# need to support this compile-time definition. | ||
|
||
# Set the default Tx power level (P9 = +9dBm = the default) | ||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y | ||
|
||
# Support modem sleep (low power mode) | ||
# CONFIG_BT_CTRL_MODEM_SLEEP=y | ||
|
||
# Set the ESP-NIMBLE-CPP Config | ||
CONFIG_NIMBLE_CPP_LOG_LEVEL_NONE=y |
Oops, something went wrong.