Skip to content

Commit 53711c6

Browse files
authored
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
1 parent c35dc84 commit 53711c6

File tree

3 files changed

+166
-0
lines changed

3 files changed

+166
-0
lines changed

components/hid-rp/example/main/hid_rp_example.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ extern "C" void app_main(void) {
1414

1515
//! [hid rp example]
1616
static constexpr uint8_t input_report_id = 1;
17+
static constexpr uint8_t battery_report_id = 4;
1718
static constexpr size_t num_buttons = 15;
1819
static constexpr int joystick_min = 0;
1920
static constexpr int joystick_max = 65534;
@@ -25,6 +26,9 @@ extern "C" void app_main(void) {
2526
joystick_max, trigger_min, trigger_max, input_report_id>;
2627
GamepadInput gamepad_input_report;
2728

29+
using BatteryReport = espp::XboxBatteryInputReport<battery_report_id>;
30+
BatteryReport battery_input_report;
31+
2832
static constexpr uint8_t output_report_id = 2;
2933
static constexpr size_t num_leds = 4;
3034
using GamepadLeds = espp::GamepadLedOutputReport<num_leds, output_report_id>;
@@ -34,6 +38,7 @@ extern "C" void app_main(void) {
3438
using namespace hid::rdf;
3539
auto raw_descriptor = descriptor(usage_page<generic_desktop>(), usage(generic_desktop::GAMEPAD),
3640
collection::application(gamepad_input_report.get_descriptor(),
41+
battery_input_report.get_descriptor(),
3742
gamepad_leds_report.get_descriptor()));
3843

3944
// Generate the report descriptor for the gamepad
@@ -47,6 +52,7 @@ extern "C" void app_main(void) {
4752
int button_index = 5;
4853
float angle = 2.0f * M_PI * button_index / num_buttons;
4954

55+
// update the gamepad input report
5056
gamepad_input_report.reset();
5157
gamepad_input_report.set_hat(hat);
5258
gamepad_input_report.set_button(button_index, true);
@@ -64,5 +70,20 @@ extern "C" void app_main(void) {
6470
logger.info("Input report:");
6571
logger.info(" Size: {}", report.size());
6672
logger.info(" Data: {::#02x}", report);
73+
74+
// update the battery input report
75+
battery_input_report.reset();
76+
battery_input_report.set_rechargeable(true);
77+
battery_input_report.set_charging(false);
78+
battery_input_report.set_rechargeable(true);
79+
// note: it can only show 5, 40, 70, 100 so this will be rounded to 40
80+
battery_input_report.set_battery_level(50);
81+
82+
// send a battery report
83+
report = battery_input_report.get_report();
84+
logger.info("Battery report:");
85+
logger.info(" Size: {}", report.size());
86+
logger.info(" Data: {::#02x}", report);
87+
6788
//! [hid rp example]
6889
}

components/hid-rp/include/hid-rp-gamepad.hpp

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,140 @@ class GamepadInputReport : public hid::report::base<hid::report::type::INPUT, RE
229229
}
230230
};
231231

232+
/// HID Xbox Battery Input Report
233+
/// This class implements a copy of the Xbox Battery input report. It is a
234+
/// single byte input which contains information about the type of the battery,
235+
/// its battery level, as well as a few other things.
236+
///
237+
/// \section hid_rp_ex1 HID-RP Example
238+
/// \snippet hid_rp_example.cpp hid rp example
239+
template <uint8_t REPORT_ID = 4>
240+
class XboxBatteryInputReport : public hid::report::base<hid::report::type::INPUT, REPORT_ID> {
241+
public:
242+
protected:
243+
static constexpr uint8_t battery_min{0};
244+
static constexpr uint8_t battery_max{255};
245+
static constexpr uint8_t num_data_bytes{1};
246+
247+
uint8_t battery_status{0}; ///< The battery status byte
248+
249+
public:
250+
/// The possible errors for the battery
251+
enum class Error {
252+
NONE, ///< No error
253+
BATTERY_LOW, ///< The battery is low
254+
BATTERY_CRITICAL, ///< The battery is critically low
255+
};
256+
257+
/// Reset the battery status
258+
constexpr void reset() { battery_status = 0; }
259+
260+
/// Set whether the battery is rechargeable
261+
/// \param rechargeable True if the battery is rechargeable, false otherwise.
262+
constexpr void set_rechargeable(bool rechargeable) {
263+
if (rechargeable) {
264+
battery_status |= 0x04;
265+
} else {
266+
battery_status &= ~0x04;
267+
}
268+
}
269+
270+
/// Set the battery level
271+
/// \param level The battery level as a percentage, from 0 to 100.
272+
constexpr void set_battery_level(int level) {
273+
if (level > 70) {
274+
battery_status |= 0x03;
275+
} else if (level > 40) {
276+
battery_status |= 0x02;
277+
} else if (level > 5) {
278+
battery_status |= 0x01;
279+
}
280+
}
281+
282+
/// Set whether the battery is connected to a cable
283+
/// \param connected True if the battery is connected to a cable, false otherwise.
284+
constexpr void set_cable_connected(bool connected) {
285+
if (connected) {
286+
battery_status |= 0x10;
287+
} else {
288+
battery_status &= ~0x10;
289+
}
290+
}
291+
292+
/// Set whether the battery is charging
293+
/// \param charging True if the battery is charging, false otherwise.
294+
constexpr void set_charging(bool charging) {
295+
if (charging) {
296+
battery_status |= 0x20;
297+
} else {
298+
battery_status &= ~0x20;
299+
}
300+
}
301+
302+
/// Set the error state of the battery
303+
/// \param error The error state of the battery.
304+
constexpr void set_error(Error error) {
305+
switch (error) {
306+
case Error::BATTERY_LOW:
307+
battery_status |= 0x80;
308+
break;
309+
case Error::BATTERY_CRITICAL:
310+
battery_status |= 0xC0;
311+
break;
312+
default:
313+
break;
314+
}
315+
}
316+
317+
/// Get the input report as a vector of bytes
318+
/// \return The input report as a vector of bytes.
319+
/// \note The report id is not included in the returned vector.
320+
constexpr auto get_report() {
321+
// the first byte is the id, which we don't want...
322+
size_t offset = 1;
323+
auto report_data = this->data() + offset;
324+
auto report_size = num_data_bytes;
325+
return std::vector<uint8_t>(report_data, report_data + report_size);
326+
}
327+
328+
/// Get the report descriptor as a hid::rdf::descriptor
329+
/// \return The report descriptor as a hid::rdf::descriptor.
330+
/// \note This is an incomplete descriptor, you will need to add it to a
331+
/// collection::application descriptor to create a complete report descriptor.
332+
/// \code{.cpp}
333+
/// using namespace hid::page;
334+
/// using namespace hid::rdf;
335+
/// auto gamepad_descriptor = gamepad_input_report.get_descriptor();
336+
/// auto rdf_descriptor = descriptor(
337+
/// usage_page<generic_desktop>(),
338+
/// usage(generic_desktop::GAMEPAD),
339+
/// collection::application(
340+
/// gamepad_descriptor
341+
/// )
342+
/// );
343+
/// auto descriptor = std::vector<uint8_t>(rdf_descriptor.begin(), rdf_descriptor.end());
344+
/// \endcode
345+
static constexpr auto get_descriptor() {
346+
using namespace hid::page;
347+
using namespace hid::rdf;
348+
349+
// clang-format off
350+
return descriptor(
351+
conditional_report_id<REPORT_ID>(),
352+
353+
// Battery status
354+
usage(generic_device::BATTERY_STRENGTH),
355+
conditional_report_id<4>(),
356+
logical_limits<1,2>(battery_min, battery_max),
357+
report_size(8),
358+
report_count(1),
359+
usage(generic_device::BATTERY_STRENGTH),
360+
input::absolute_variable()
361+
);
362+
// clang-format on
363+
}
364+
};
365+
232366
/// HID Gamepad LED Output Report
233367
/// This class implements a HID Gamepad with a configurable number of LEDs.
234368
/// It supports setting the LEDs, as well as serializing the output report and

components/hid_service/example/main/hid_service_example.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ extern "C" void app_main(void) {
7373
hid_service.set_info(country_code, hid_info_flags);
7474

7575
static constexpr uint8_t input_report_id = 1;
76+
static constexpr uint8_t battery_report_id = 4;
7677
static constexpr size_t num_buttons = 15;
7778
static constexpr int joystick_min = 0;
7879
static constexpr int joystick_max = 65534;
@@ -84,6 +85,9 @@ extern "C" void app_main(void) {
8485
joystick_max, trigger_min, trigger_max, input_report_id>;
8586
GamepadInput gamepad_input_report;
8687

88+
using BatteryReport = espp::XboxBatteryInputReport<battery_report_id>;
89+
BatteryReport battery_input_report;
90+
8791
static constexpr uint8_t output_report_id = 2;
8892
static constexpr size_t num_leds = 4;
8993
using GamepadLeds = espp::GamepadLedOutputReport<num_leds, output_report_id>;
@@ -93,6 +97,7 @@ extern "C" void app_main(void) {
9397
using namespace hid::rdf;
9498
auto raw_descriptor = descriptor(usage_page<generic_desktop>(), usage(generic_desktop::GAMEPAD),
9599
collection::application(gamepad_input_report.get_descriptor(),
100+
battery_input_report.get_descriptor(),
96101
gamepad_leds_report.get_descriptor()));
97102

98103
// Generate the report descriptor for the gamepad
@@ -107,6 +112,7 @@ extern "C" void app_main(void) {
107112

108113
// use the HID service to make an input report characteristic
109114
[[maybe_unused]] auto input_report = hid_service.input_report(input_report_id);
115+
[[maybe_unused]] auto battery_report = hid_service.input_report(battery_report_id);
110116

111117
// use the HID service to make an output report characteristic
112118
[[maybe_unused]] auto output_report = hid_service.output_report(output_report_id);
@@ -188,6 +194,11 @@ extern "C" void app_main(void) {
188194

189195
// update the battery level
190196
battery_service.set_battery_level(battery_level);
197+
battery_input_report.reset();
198+
battery_input_report.set_rechargeable(true);
199+
battery_input_report.set_charging(false);
200+
battery_input_report.set_battery_level(battery_level);
201+
battery_report->notify(battery_input_report.get_report());
191202
battery_level = (battery_level % 100) + 1;
192203

193204
// cycle through the possible d-pad states

0 commit comments

Comments
 (0)