Skip to content

Commit

Permalink
feat(hid): Add xbox battery input report (#361)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
finger563 authored Jan 21, 2025
1 parent c35dc84 commit 53711c6
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 0 deletions.
21 changes: 21 additions & 0 deletions components/hid-rp/example/main/hid_rp_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<battery_report_id>;
BatteryReport battery_input_report;

static constexpr uint8_t output_report_id = 2;
static constexpr size_t num_leds = 4;
using GamepadLeds = espp::GamepadLedOutputReport<num_leds, output_report_id>;
Expand All @@ -34,6 +38,7 @@ extern "C" void app_main(void) {
using namespace hid::rdf;
auto raw_descriptor = descriptor(usage_page<generic_desktop>(), 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
Expand All @@ -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);
Expand All @@ -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]
}
134 changes: 134 additions & 0 deletions components/hid-rp/include/hid-rp-gamepad.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,140 @@ class GamepadInputReport : public hid::report::base<hid::report::type::INPUT, RE
}
};

/// HID Xbox Battery Input Report
/// This class implements a copy of the Xbox Battery input report. It is a
/// single byte input which contains information about the type of the battery,
/// its battery level, as well as a few other things.
///
/// \section hid_rp_ex1 HID-RP Example
/// \snippet hid_rp_example.cpp hid rp example
template <uint8_t REPORT_ID = 4>
class XboxBatteryInputReport : public hid::report::base<hid::report::type::INPUT, REPORT_ID> {
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<uint8_t>(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<generic_desktop>(),
/// usage(generic_desktop::GAMEPAD),
/// collection::application(
/// gamepad_descriptor
/// )
/// );
/// auto descriptor = std::vector<uint8_t>(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<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
Expand Down
11 changes: 11 additions & 0 deletions components/hid_service/example/main/hid_service_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<battery_report_id>;
BatteryReport battery_input_report;

static constexpr uint8_t output_report_id = 2;
static constexpr size_t num_leds = 4;
using GamepadLeds = espp::GamepadLedOutputReport<num_leds, output_report_id>;
Expand All @@ -93,6 +97,7 @@ extern "C" void app_main(void) {
using namespace hid::rdf;
auto raw_descriptor = descriptor(usage_page<generic_desktop>(), 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
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 53711c6

Please sign in to comment.