fafca4c265
Phase 4 foundation, independent of the presentation mechanism: - hid/hid_report.hpp: standard Xbox One report layout (64-byte input: buttons/guide/triggers/sticks; 64-byte output: motors), the HID report descriptor, and GIP state to report mapping. - make_input_report(): remaps GIP button bits to the standard Xbox bitmask, scales 10-bit triggers to 8-bit, sticks pass through. - parse_output_report(): extracts motor intensities from output reports for the rumble relay; malformed reports stop the motors. - test_hid: unit tests for the mapping, scaling, and descriptor. Report packing verified with a descriptor walker: input and output are exactly 64 bytes each. Next step is presentation via DriverKit, which needs a Developer ID with the DriverKit entitlement. Co-Authored-By: qwen3.8-27b@q3_k_xl: implemented HID report layer
177 lines
4.9 KiB
C++
177 lines
4.9 KiB
C++
#include "unity.h"
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
#include "hid/hid_report.hpp"
|
|
|
|
void setUp() {}
|
|
void tearDown() {}
|
|
|
|
namespace {
|
|
|
|
using xone::hid::gamepad_input;
|
|
using xone::hid::make_input_report;
|
|
using xone::hid::parse_output_report;
|
|
using xone::hid::xbox_one_report_descriptor;
|
|
|
|
// GIP bitmask with every button but the guide pressed.
|
|
constexpr std::uint16_t k_gip_all_buttons =
|
|
0x0004 | // start
|
|
0x0008 | // select
|
|
0x0010 | // a
|
|
0x0020 | // b
|
|
0x0040 | // x
|
|
0x0080 | // y
|
|
0x0100 | // dpad up
|
|
0x0200 | // dpad down
|
|
0x0400 | // dpad left
|
|
0x0800 | // dpad right
|
|
0x1000 | // bumper left
|
|
0x2000 | // bumper right
|
|
0x4000 | // stick left
|
|
0x8000; // stick right
|
|
|
|
} // namespace
|
|
|
|
void test_input_report_idle(void)
|
|
{
|
|
auto report = make_input_report(gamepad_input{});
|
|
TEST_ASSERT_EQUAL_UINT8(1, report[0]);
|
|
for (std::size_t i = 1; i < report.size(); ++i)
|
|
TEST_ASSERT_EQUAL_UINT8(0, report[i]);
|
|
}
|
|
|
|
void test_input_report_all_buttons(void)
|
|
{
|
|
gamepad_input in;
|
|
in.buttons = k_gip_all_buttons;
|
|
in.guide_down = true;
|
|
auto report = make_input_report(in);
|
|
|
|
// Every HID button bit (0..13) set: LE16 0x3FFF.
|
|
TEST_ASSERT_EQUAL_UINT8(0xff, report[1]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x3f, report[2]);
|
|
TEST_ASSERT_EQUAL_UINT8(1, report[3]);
|
|
}
|
|
|
|
void test_input_report_button_remap(void)
|
|
{
|
|
// GIP A (bit 4) -> HID bit 0.
|
|
gamepad_input in;
|
|
in.buttons = 0x0010;
|
|
auto report = make_input_report(in);
|
|
TEST_ASSERT_EQUAL_UINT8(0x01, report[1]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x00, report[2]);
|
|
|
|
// GIP Select (bit 3) -> HID Back (bit 8).
|
|
in.buttons = 0x0008;
|
|
report = make_input_report(in);
|
|
TEST_ASSERT_EQUAL_UINT8(0x00, report[1]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x01, report[2]);
|
|
|
|
// GIP Dpad right (bit 11) -> HID bit 13.
|
|
in.buttons = 0x0800;
|
|
report = make_input_report(in);
|
|
TEST_ASSERT_EQUAL_UINT8(0x00, report[1]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x20, report[2]);
|
|
}
|
|
|
|
void test_input_report_triggers(void)
|
|
{
|
|
gamepad_input in;
|
|
in.trigger_left = 511;
|
|
in.trigger_right = 512;
|
|
auto report = make_input_report(in);
|
|
TEST_ASSERT_EQUAL_UINT8(127, report[4]);
|
|
TEST_ASSERT_EQUAL_UINT8(128, report[5]);
|
|
|
|
in.trigger_left = 0;
|
|
in.trigger_right = 1023;
|
|
report = make_input_report(in);
|
|
TEST_ASSERT_EQUAL_UINT8(0, report[4]);
|
|
TEST_ASSERT_EQUAL_UINT8(255, report[5]);
|
|
|
|
// Values above the 10-bit range clamp to full.
|
|
in.trigger_left = 0xffff;
|
|
in.trigger_right = 0;
|
|
report = make_input_report(in);
|
|
TEST_ASSERT_EQUAL_UINT8(255, report[4]);
|
|
TEST_ASSERT_EQUAL_UINT8(0, report[5]);
|
|
}
|
|
|
|
void test_input_report_sticks(void)
|
|
{
|
|
gamepad_input in;
|
|
in.stick_left_x = -1;
|
|
in.stick_left_y = 32767;
|
|
in.stick_right_x = -32768;
|
|
in.stick_right_y = 0;
|
|
auto report = make_input_report(in);
|
|
|
|
TEST_ASSERT_EQUAL_UINT8(0xff, report[6]);
|
|
TEST_ASSERT_EQUAL_UINT8(0xff, report[7]);
|
|
TEST_ASSERT_EQUAL_UINT8(0xff, report[8]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x7f, report[9]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x00, report[10]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x80, report[11]);
|
|
}
|
|
|
|
void test_output_report_parse(void)
|
|
{
|
|
std::array<std::uint8_t, 64> report{};
|
|
report[0] = 1;
|
|
report[2] = 0x80;
|
|
report[3] = 0xc0;
|
|
auto motors = parse_output_report(report);
|
|
TEST_ASSERT_EQUAL_UINT8(0x80, motors.left);
|
|
TEST_ASSERT_EQUAL_UINT8(0xc0, motors.right);
|
|
}
|
|
|
|
void test_output_report_rejects_malformed(void)
|
|
{
|
|
std::array<std::uint8_t, 64> report{};
|
|
report[0] = 2; // wrong report ID
|
|
report[2] = 0x80;
|
|
auto motors = parse_output_report(report);
|
|
TEST_ASSERT_EQUAL_UINT8(0, motors.left);
|
|
TEST_ASSERT_EQUAL_UINT8(0, motors.right);
|
|
|
|
std::array<std::uint8_t, 10> short_report{};
|
|
short_report[0] = 1;
|
|
short_report[2] = 0x80;
|
|
motors = parse_output_report(short_report);
|
|
TEST_ASSERT_EQUAL_UINT8(0, motors.left);
|
|
TEST_ASSERT_EQUAL_UINT8(0, motors.right);
|
|
}
|
|
|
|
void test_descriptor_sanity(void)
|
|
{
|
|
auto desc = xbox_one_report_descriptor();
|
|
TEST_ASSERT_EQUAL_INT(167, static_cast<int>(desc.size()));
|
|
// Generic Desktop / Game Pad / Application collection.
|
|
TEST_ASSERT_EQUAL_UINT8(0x05, desc[0]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x01, desc[1]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x09, desc[2]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x04, desc[3]);
|
|
TEST_ASSERT_EQUAL_UINT8(0xa1, desc[4]);
|
|
TEST_ASSERT_EQUAL_UINT8(0x01, desc[5]);
|
|
// Ends with End Collection.
|
|
TEST_ASSERT_EQUAL_UINT8(0xc0, desc.back());
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_input_report_idle);
|
|
RUN_TEST(test_input_report_all_buttons);
|
|
RUN_TEST(test_input_report_button_remap);
|
|
RUN_TEST(test_input_report_triggers);
|
|
RUN_TEST(test_input_report_sticks);
|
|
RUN_TEST(test_output_report_parse);
|
|
RUN_TEST(test_output_report_rejects_malformed);
|
|
RUN_TEST(test_descriptor_sanity);
|
|
return UNITY_END();
|
|
}
|