From 53711c66fb0fd7f4bd044ce92b71190c9f979cd0 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Tue, 21 Jan 2025 14:28:05 -0600 Subject: [PATCH] feat(hid): Add xbox battery input report (#361) * feat(hid): Add xbox battery input report * Add `espp::XboxBatteryInputReport` to `hid-rp-gamepad.hpp` * Update `hid-rp/example` to check compilation * Update `hid_service/example` to actually use and test the battery report * fix missing copy-paste --- .../hid-rp/example/main/hid_rp_example.cpp | 21 +++ components/hid-rp/include/hid-rp-gamepad.hpp | 134 ++++++++++++++++++ .../example/main/hid_service_example.cpp | 11 ++ 3 files changed, 166 insertions(+) diff --git a/components/hid-rp/example/main/hid_rp_example.cpp b/components/hid-rp/example/main/hid_rp_example.cpp index a41fe1b52..7e9f7dd7d 100644 --- a/components/hid-rp/example/main/hid_rp_example.cpp +++ b/components/hid-rp/example/main/hid_rp_example.cpp @@ -14,6 +14,7 @@ extern "C" void app_main(void) { //! [hid rp example] static constexpr uint8_t input_report_id = 1; + static constexpr uint8_t battery_report_id = 4; static constexpr size_t num_buttons = 15; static constexpr int joystick_min = 0; static constexpr int joystick_max = 65534; @@ -25,6 +26,9 @@ extern "C" void app_main(void) { joystick_max, trigger_min, trigger_max, input_report_id>; GamepadInput gamepad_input_report; + using BatteryReport = espp::XboxBatteryInputReport; + BatteryReport battery_input_report; + static constexpr uint8_t output_report_id = 2; static constexpr size_t num_leds = 4; using GamepadLeds = espp::GamepadLedOutputReport; @@ -34,6 +38,7 @@ extern "C" void app_main(void) { using namespace hid::rdf; auto raw_descriptor = descriptor(usage_page(), usage(generic_desktop::GAMEPAD), collection::application(gamepad_input_report.get_descriptor(), + battery_input_report.get_descriptor(), gamepad_leds_report.get_descriptor())); // Generate the report descriptor for the gamepad @@ -47,6 +52,7 @@ extern "C" void app_main(void) { int button_index = 5; float angle = 2.0f * M_PI * button_index / num_buttons; + // update the gamepad input report gamepad_input_report.reset(); gamepad_input_report.set_hat(hat); gamepad_input_report.set_button(button_index, true); @@ -64,5 +70,20 @@ extern "C" void app_main(void) { logger.info("Input report:"); logger.info(" Size: {}", report.size()); logger.info(" Data: {::#02x}", report); + + // update the battery input report + battery_input_report.reset(); + battery_input_report.set_rechargeable(true); + battery_input_report.set_charging(false); + battery_input_report.set_rechargeable(true); + // note: it can only show 5, 40, 70, 100 so this will be rounded to 40 + battery_input_report.set_battery_level(50); + + // send a battery report + report = battery_input_report.get_report(); + logger.info("Battery report:"); + logger.info(" Size: {}", report.size()); + logger.info(" Data: {::#02x}", report); + //! [hid rp example] } diff --git a/components/hid-rp/include/hid-rp-gamepad.hpp b/components/hid-rp/include/hid-rp-gamepad.hpp index 736124d38..73e6da689 100644 --- a/components/hid-rp/include/hid-rp-gamepad.hpp +++ b/components/hid-rp/include/hid-rp-gamepad.hpp @@ -229,6 +229,140 @@ class GamepadInputReport : public hid::report::base +class XboxBatteryInputReport : public hid::report::base { +public: +protected: + static constexpr uint8_t battery_min{0}; + static constexpr uint8_t battery_max{255}; + static constexpr uint8_t num_data_bytes{1}; + + uint8_t battery_status{0}; ///< The battery status byte + +public: + /// The possible errors for the battery + enum class Error { + NONE, ///< No error + BATTERY_LOW, ///< The battery is low + BATTERY_CRITICAL, ///< The battery is critically low + }; + + /// Reset the battery status + constexpr void reset() { battery_status = 0; } + + /// Set whether the battery is rechargeable + /// \param rechargeable True if the battery is rechargeable, false otherwise. + constexpr void set_rechargeable(bool rechargeable) { + if (rechargeable) { + battery_status |= 0x04; + } else { + battery_status &= ~0x04; + } + } + + /// Set the battery level + /// \param level The battery level as a percentage, from 0 to 100. + constexpr void set_battery_level(int level) { + if (level > 70) { + battery_status |= 0x03; + } else if (level > 40) { + battery_status |= 0x02; + } else if (level > 5) { + battery_status |= 0x01; + } + } + + /// Set whether the battery is connected to a cable + /// \param connected True if the battery is connected to a cable, false otherwise. + constexpr void set_cable_connected(bool connected) { + if (connected) { + battery_status |= 0x10; + } else { + battery_status &= ~0x10; + } + } + + /// Set whether the battery is charging + /// \param charging True if the battery is charging, false otherwise. + constexpr void set_charging(bool charging) { + if (charging) { + battery_status |= 0x20; + } else { + battery_status &= ~0x20; + } + } + + /// Set the error state of the battery + /// \param error The error state of the battery. + constexpr void set_error(Error error) { + switch (error) { + case Error::BATTERY_LOW: + battery_status |= 0x80; + break; + case Error::BATTERY_CRITICAL: + battery_status |= 0xC0; + break; + default: + break; + } + } + + /// Get the input report as a vector of bytes + /// \return The input report as a vector of bytes. + /// \note The report id is not included in the returned vector. + constexpr auto get_report() { + // the first byte is the id, which we don't want... + size_t offset = 1; + auto report_data = this->data() + offset; + auto report_size = num_data_bytes; + return std::vector(report_data, report_data + report_size); + } + + /// Get the report descriptor as a hid::rdf::descriptor + /// \return The report descriptor as a hid::rdf::descriptor. + /// \note This is an incomplete descriptor, you will need to add it to a + /// collection::application descriptor to create a complete report descriptor. + /// \code{.cpp} + /// using namespace hid::page; + /// using namespace hid::rdf; + /// auto gamepad_descriptor = gamepad_input_report.get_descriptor(); + /// auto rdf_descriptor = descriptor( + /// usage_page(), + /// usage(generic_desktop::GAMEPAD), + /// collection::application( + /// gamepad_descriptor + /// ) + /// ); + /// auto descriptor = std::vector(rdf_descriptor.begin(), rdf_descriptor.end()); + /// \endcode + static constexpr auto get_descriptor() { + using namespace hid::page; + using namespace hid::rdf; + + // clang-format off + return descriptor( + conditional_report_id(), + + // Battery status + usage(generic_device::BATTERY_STRENGTH), + conditional_report_id<4>(), + logical_limits<1,2>(battery_min, battery_max), + report_size(8), + report_count(1), + usage(generic_device::BATTERY_STRENGTH), + input::absolute_variable() + ); + // clang-format on + } +}; + /// HID Gamepad LED Output Report /// This class implements a HID Gamepad with a configurable number of LEDs. /// It supports setting the LEDs, as well as serializing the output report and diff --git a/components/hid_service/example/main/hid_service_example.cpp b/components/hid_service/example/main/hid_service_example.cpp index d0c6b1e86..408a50846 100644 --- a/components/hid_service/example/main/hid_service_example.cpp +++ b/components/hid_service/example/main/hid_service_example.cpp @@ -73,6 +73,7 @@ extern "C" void app_main(void) { hid_service.set_info(country_code, hid_info_flags); static constexpr uint8_t input_report_id = 1; + static constexpr uint8_t battery_report_id = 4; static constexpr size_t num_buttons = 15; static constexpr int joystick_min = 0; static constexpr int joystick_max = 65534; @@ -84,6 +85,9 @@ extern "C" void app_main(void) { joystick_max, trigger_min, trigger_max, input_report_id>; GamepadInput gamepad_input_report; + using BatteryReport = espp::XboxBatteryInputReport; + BatteryReport battery_input_report; + static constexpr uint8_t output_report_id = 2; static constexpr size_t num_leds = 4; using GamepadLeds = espp::GamepadLedOutputReport; @@ -93,6 +97,7 @@ extern "C" void app_main(void) { using namespace hid::rdf; auto raw_descriptor = descriptor(usage_page(), usage(generic_desktop::GAMEPAD), collection::application(gamepad_input_report.get_descriptor(), + battery_input_report.get_descriptor(), gamepad_leds_report.get_descriptor())); // Generate the report descriptor for the gamepad @@ -107,6 +112,7 @@ extern "C" void app_main(void) { // use the HID service to make an input report characteristic [[maybe_unused]] auto input_report = hid_service.input_report(input_report_id); + [[maybe_unused]] auto battery_report = hid_service.input_report(battery_report_id); // use the HID service to make an output report characteristic [[maybe_unused]] auto output_report = hid_service.output_report(output_report_id); @@ -188,6 +194,11 @@ extern "C" void app_main(void) { // update the battery level battery_service.set_battery_level(battery_level); + battery_input_report.reset(); + battery_input_report.set_rechargeable(true); + battery_input_report.set_charging(false); + battery_input_report.set_battery_level(battery_level); + battery_report->notify(battery_input_report.get_report()); battery_level = (battery_level % 100) + 1; // cycle through the possible d-pad states