-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: Add hid-rp submodule and component with example (#159)
* feat(hid-rp): add hid-rp library * Add hid-rp library as external library * Add hid-rp component with additional generated hid pages and hid-rp-gamepad (espp::GamepadReport) class * Add example showing use of espp::GamepadReport to set input report values, get the serialized input report data, and get the report descriptor. * feat(range_mapper): update range mapper * Allow range mapper to be default initialized * Remove assert and instead exit early if bad config is provided * Ensure default values allow for map/unmap to run without crashing * readme: update * update and rebuild docs, change GamepadReport to be class to be included in docs and update example * update ci * fix static analysis
- Loading branch information
Showing
155 changed files
with
4,832 additions
and
214 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
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 |
---|---|---|
|
@@ -31,3 +31,6 @@ | |
[submodule "components/esp-nimble-cpp"] | ||
path = components/esp-nimble-cpp | ||
url = [email protected]:esp-cpp/esp-nimble-cpp | ||
[submodule "external/hid-rp"] | ||
path = external/hid-rp | ||
url = https://github.com/intergatedcircuits/hid-rp |
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,3 @@ | ||
idf_component_register( | ||
INCLUDE_DIRS "include" "../../external/hid-rp/hid-rp" | ||
) |
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 hid-rp logger" | ||
CACHE STRING | ||
"List of components to include" | ||
) | ||
|
||
project(hid_rp_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 @@ | ||
# HID-RP Example | ||
|
||
This example shows how to use the | ||
[`hid-rp`](https://github.com/intergatedcircuits/hid-rp) library which is | ||
bundled into the hid-rp component within espp. | ||
|
||
It provides an example of a somewhat configurable HID Gamepad using the | ||
`esppp::GamepadReport<>` template class. | ||
|
||
## 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 16 38 11](https://github.com/esp-cpp/espp/assets/213467/d86a5977-5db1-44bc-9df9-fcbb751392a5) |
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 ".") |
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,56 @@ | ||
#include <chrono> | ||
#include <vector> | ||
|
||
#include "logger.hpp" | ||
|
||
#include "hid-rp-gamepad.hpp" | ||
#include "hid-rp.hpp" | ||
|
||
using namespace std::chrono_literals; | ||
|
||
extern "C" void app_main(void) { | ||
espp::Logger logger({.tag = "Hid RP Example", .level = espp::Logger::Verbosity::INFO}); | ||
logger.info("Starting"); | ||
|
||
//! [hid rp example] | ||
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); | ||
|
||
Gamepad::Hat hat = Gamepad::Hat::UP_RIGHT; | ||
int button_index = 5; | ||
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.info("Input report:"); | ||
logger.info(" Size: {}", report.size()); | ||
logger.info(" Data: {::#02x}", report); | ||
//! [hid rp 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,14 @@ | ||
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 |
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,222 @@ | ||
#pragma once | ||
|
||
#include <algorithm> | ||
|
||
#include "hid-rp.hpp" | ||
|
||
namespace espp { | ||
|
||
/// HID Gamepad Report | ||
/// This class implements a HID Gamepad with a configurable number of buttons, a | ||
/// hat switch, 4 joystick axes and two trigger axes. It supports setting the | ||
/// buttons, hat switch, joysticks, and triggers, as well as serializing the | ||
/// input report and getting the report descriptor. | ||
/// | ||
/// \section hid_rp_ex1 HID-RP Example | ||
/// \snippet hid_rp_example.cpp hid rp example | ||
template <size_t BUTTON_COUNT, uint16_t JOYSTICK_MIN = 0, uint16_t JOYSTICK_MAX = 65535, | ||
uint16_t TRIGGER_MIN = 0, uint16_t TRIGGER_MAX = 1023, uint8_t REPORT_ID = 0> | ||
class GamepadReport : public hid::report::base<hid::report::type::INPUT, REPORT_ID> { | ||
public: | ||
/// Possible Hat switch directions | ||
enum class Hat { | ||
CENTERED = 0x0f, ///< Centered, no direction pressed. | ||
UP = 1, | ||
UP_RIGHT, | ||
RIGHT, | ||
DOWN_RIGHT, | ||
DOWN, | ||
DOWN_LEFT, | ||
LEFT, | ||
UP_LEFT | ||
}; | ||
|
||
protected: | ||
static constexpr size_t button_count = BUTTON_COUNT; | ||
static constexpr size_t num_button_padding = BUTTON_COUNT % 8 ? 8 - (BUTTON_COUNT % 8) : 0; | ||
static constexpr uint16_t joystick_min = JOYSTICK_MIN; | ||
static constexpr uint16_t joystick_max = JOYSTICK_MAX; | ||
static constexpr uint16_t joystick_center = (joystick_min + joystick_max) / 2; | ||
static constexpr uint16_t trigger_min = TRIGGER_MIN; | ||
static constexpr uint16_t trigger_max = TRIGGER_MAX; | ||
static constexpr uint16_t trigger_center = TRIGGER_MIN; | ||
static constexpr size_t joystick_value_range = joystick_max - joystick_min; | ||
static constexpr uint16_t joystick_range = joystick_value_range / 2; | ||
static constexpr size_t trigger_range = trigger_max - trigger_min; | ||
static constexpr size_t num_joystick_bits = num_bits(joystick_value_range); | ||
static constexpr size_t num_joystick_padding = | ||
num_joystick_bits % 8 ? 8 - (num_joystick_bits % 8) : 0; | ||
static constexpr size_t num_trigger_bits = num_bits(trigger_range); | ||
static constexpr size_t num_trigger_padding = | ||
num_trigger_bits % 8 ? 8 - (num_trigger_bits % 8) : 0; | ||
|
||
std::array<std::uint16_t, 4> joystick_axes{0}; | ||
std::array<std::uint16_t, 2> trigger_axes{0}; | ||
std::uint8_t hat_switch{0}; | ||
hid::report_bitset<hid::page::button, hid::page::button(1), hid::page::button(BUTTON_COUNT)> | ||
buttons; | ||
|
||
public: | ||
/// Reset the gamepad inputs | ||
constexpr void reset() { | ||
std::fill(joystick_axes.begin(), joystick_axes.end(), joystick_center); | ||
std::fill(trigger_axes.begin(), trigger_axes.end(), trigger_center); | ||
set_hat(Hat::CENTERED); | ||
buttons.reset(); | ||
} | ||
|
||
/// Set the left joystick X and Y axis values | ||
/// @param lx left joystick x axis value, in the range [-1, 1] | ||
/// @param ly left joystick y axis value, in the range [-1, 1] | ||
constexpr void set_left_joystick(float lx, float ly) { | ||
set_joystick_axis(0, lx); | ||
set_joystick_axis(1, ly); | ||
} | ||
/// Set the right joystick X and Y axis values | ||
/// @param rx right joystick x axis value, in the range [-1, 1] | ||
/// @param ry right joystick y axis value, in the range [-1, 1] | ||
constexpr void set_right_joystick(float rx, float ry) { | ||
set_joystick_axis(2, rx); | ||
set_joystick_axis(3, ry); | ||
} | ||
/// Set the brake trigger value | ||
/// @param value brake trigger value, in the range [0, 1] | ||
constexpr void set_brake(float value) { set_trigger_axis(0, value); } | ||
/// Set the accelerator trigger value | ||
/// @param value accelerator trigger value, in the range [0, 1] | ||
constexpr void set_accelerator(float value) { set_trigger_axis(1, value); } | ||
/// Set the hat switch (d-pad) value | ||
/// @param hat Hat enum / direction to set | ||
constexpr void set_hat(Hat hat) { set_hat_switch(uint8_t(hat)); } | ||
/// Set the button value | ||
/// \param button_index The button for which you want to set the value. | ||
/// Should be between 1 and BUTTON_COUNT | ||
/// \param value The true/false value you want to se the button to. | ||
constexpr void set_button(int button_index, bool value) { | ||
// buttons[button_index] = value; | ||
buttons.set(hid::page::button(button_index), value); | ||
} | ||
|
||
constexpr void set_joystick_axis(size_t index, std::uint16_t value) { | ||
if (index < 4) { | ||
joystick_axes[index] = std::clamp(value, joystick_min, joystick_max); | ||
} | ||
} | ||
constexpr void set_joystick_axis(size_t index, float value) { | ||
if (index < 4) { | ||
joystick_axes[index] = | ||
std::clamp(static_cast<std::uint16_t>(value * joystick_range + joystick_center), | ||
joystick_min, joystick_max); | ||
} | ||
} | ||
constexpr void set_trigger_axis(size_t index, std::uint16_t value) { | ||
if (index < 2) { | ||
trigger_axes[index] = std::clamp(value, trigger_min, trigger_max); | ||
} | ||
} | ||
constexpr void set_trigger_axis(size_t index, float value) { | ||
if (index < 2) { | ||
trigger_axes[index] = | ||
std::clamp(static_cast<std::uint16_t>(value * trigger_range + trigger_center), | ||
trigger_min, trigger_max); | ||
} | ||
} | ||
constexpr void set_hat_switch(std::uint8_t value) { hat_switch = (value & 0xf); } | ||
|
||
/// Get the input report as a vector of bytes | ||
/// \return The input report as a vector of bytes. | ||
constexpr auto get_report() { | ||
// the first two bytes are the id and size, which we don't want... | ||
size_t offset = 2; | ||
auto report_data = this->data() + offset; | ||
auto report_size = sizeof(*this) - offset; | ||
return std::vector<uint8_t>(report_data, report_data + report_size); | ||
} | ||
|
||
/// Get the report descriptor as a vector of bytes | ||
/// \return The report descriptor as a vector of bytes. | ||
static constexpr auto get_descriptor() { | ||
using namespace hid::page; | ||
using namespace hid::rdf; | ||
|
||
// clang-format off | ||
auto desc = descriptor( | ||
usage_page<generic_desktop>(), | ||
usage(generic_desktop::GAMEPAD), | ||
collection::application( | ||
report_id(REPORT_ID), | ||
usage(generic_desktop::POINTER), | ||
|
||
// left joystick | ||
collection::physical( | ||
usage(generic_desktop::X), | ||
logical_limits<1, 4>(JOYSTICK_MIN, JOYSTICK_MAX), | ||
report_count(1), | ||
report_size(num_joystick_bits), | ||
input::absolute_variable(), | ||
usage(generic_desktop::Y), | ||
logical_limits<1, 4>(JOYSTICK_MIN, JOYSTICK_MAX), | ||
report_count(1), | ||
report_size(num_joystick_bits), | ||
input::absolute_variable() | ||
), | ||
|
||
// right joystick | ||
usage(generic_desktop::POINTER), | ||
collection::physical( | ||
usage(generic_desktop::Z), | ||
logical_limits<1, 4>(JOYSTICK_MIN, JOYSTICK_MAX), | ||
report_count(1), | ||
report_size(num_joystick_bits), | ||
input::absolute_variable(), | ||
usage(generic_desktop::RZ), | ||
logical_limits<1, 4>(JOYSTICK_MIN, JOYSTICK_MAX), | ||
report_count(1), | ||
report_size(num_joystick_bits), | ||
input::absolute_variable() | ||
), | ||
|
||
// left trigger | ||
usage_page<simulation>(), | ||
usage(simulation::BRAKE), | ||
logical_limits<1, 2>(TRIGGER_MIN, TRIGGER_MAX), | ||
report_size(num_trigger_bits), | ||
report_count(1), | ||
input::absolute_variable(), | ||
input::padding(num_trigger_padding), | ||
|
||
// right trigger | ||
usage_page<simulation>(), | ||
usage(simulation::ACCELERATOR), | ||
logical_limits<1, 2>(TRIGGER_MIN, TRIGGER_MAX), | ||
report_size(num_trigger_bits), | ||
report_count(1), | ||
input::absolute_variable(), | ||
input::padding(num_trigger_padding), | ||
|
||
// hat switch | ||
usage_page<generic_desktop>(), | ||
usage(generic_desktop::HAT_SWITCH), | ||
logical_limits<1, 1>(1, 8), | ||
physical_limits<2, 2>(0, 315), | ||
unit::unit_item<2>(0x0014), // system: english rotation, length: centimeter | ||
report_size(4), | ||
report_count(1), | ||
input::absolute_variable(static_cast<main::field_flags>(main::field_flags::NULL_STATE)), | ||
input::padding(4), | ||
|
||
// buttons | ||
usage_page<button>(), | ||
usage_limits(button(1), button(BUTTON_COUNT)), | ||
logical_limits<1, 1>(0, 1), | ||
report_size(1), | ||
report_count(BUTTON_COUNT), | ||
input::absolute_variable(), | ||
input::padding(num_button_padding) | ||
) | ||
); | ||
// clang-format on | ||
return std::vector<uint8_t>(desc.begin(), desc.end()); | ||
} | ||
}; | ||
} // namespace espp |
Oops, something went wrong.