diff --git a/docs/platform-support.md b/docs/platform-support.md index af11b97..76e2a2c 100644 --- a/docs/platform-support.md +++ b/docs/platform-support.md @@ -138,6 +138,20 @@ and control channels. Numbered control-channel output is normalized before parsing, whether the kernel includes the report number in the payload or provides it separately on the UHID event. +The default DualShock 4 and DualSense profiles use Bluetooth framing, avoiding +the parent-USB checks that can make virtual USB devices appear late in Steam. +Explicit USB and Bluetooth factories remain available for consumers that +require a particular transport. DualShock 4 Bluetooth input reports set the +HID-present header flag required by HIDAPI consumers and include the transport +CRC, so a running consumer can accept live input after hotplug. DualSense motion +packing preserves the public meters-per-second-squared and degrees-per-second +units while applying the same +raw sensor calibration used by Inputtino. Periodic PlayStation reports are +repacked at 100 Hz so their sequence number and sensor timestamp continue to +advance even when controller state is unchanged. Periodic and application +submissions are serialized so a repeated report cannot restore stale motion +state after a newer application report. + The backend opens `/dev/uhid` in nonblocking mode, matching the original asynchronous gamepad registration path. Its event reader is active before device registration begins, and creation does not report success until the @@ -147,9 +161,8 @@ a controller before its kernel HID device has started. On Linux, DualShock 4 and DualSense emit Sony's native `Wireless Controller` product name for Steam HID discovery. The requested USB or Bluetooth bus, -descriptor, and report framing remain unchanged; in particular, the default -DualShock 4 profile stays on its USB report contract. This transport-only name -is confined to the Linux backend; public profile names, Windows names, and VHF +descriptor, and report framing remain unchanged. This transport-only name is +confined to the Linux backend; public profile names, Windows names, and VHF behavior are unchanged. Switch Pro keeps its Nintendo identity on the Linux uinput path. This follows diff --git a/docs/usage.md b/docs/usage.md index 9de3c37..8afc110 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -217,6 +217,10 @@ Consumers may replace `DeviceProfile::name` before creating a gamepad, for example, to prepend an application name while preserving the default controller identity across platform backends. +`profiles::dualshock4()` and `profiles::dualsense()` select Bluetooth framing +for reliable native-controller discovery. Consumers can use the corresponding +`_usb()` or `_bluetooth()` factory when the transport must be explicit. + The platform-neutral Generic HID descriptor reports the D-pad as buttons 13 through 16 in the input report. Linux may still route that profile through `uinput`, where the backend exposes those same logical directions through the diff --git a/src/core/profiles.cpp b/src/core/profiles.cpp index 598da10..67f843f 100644 --- a/src/core/profiles.cpp +++ b/src/core/profiles.cpp @@ -2095,7 +2095,7 @@ namespace lvh::profiles { } DeviceProfile dualshock4() { - return dualshock4_usb(); + return dualshock4_bluetooth(); } DeviceProfile dualshock4_usb() { @@ -2107,7 +2107,7 @@ namespace lvh::profiles { } DeviceProfile dualsense() { - return dualsense_usb(); + return dualsense_bluetooth(); } DeviceProfile dualsense_usb() { diff --git a/src/core/report.cpp b/src/core/report.cpp index 318cf5c..d0fa18b 100644 --- a/src/core/report.cpp +++ b/src/core/report.cpp @@ -6,11 +6,13 @@ // standard includes #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -31,6 +33,8 @@ namespace lvh::reports { constexpr auto dualshock4_bt_input_report_id = std::byte {0x11}; + constexpr auto dualshock4_bt_input_hid_present = std::byte {0x80}; + constexpr auto dualshock4_bt_output_report_id = std::byte {0x11}; constexpr auto dualshock4_output_hwctl_crc32 = std::byte {0x40}; @@ -61,6 +65,10 @@ namespace lvh::reports { constexpr auto dualsense_flag2_compatible_vibration = std::byte {0x04}; + constexpr auto dualsense_acceleration_scale = 9.80665F * 100.0F; + + constexpr auto dualsense_gyroscope_scale = 1145.0F * std::numbers::pi_v / 180.0F; + constexpr std::uint8_t switch_rumble_and_subcommand_output_report_id = 0x01; constexpr std::uint8_t switch_rumble_only_output_report_id = 0x10; @@ -613,6 +621,19 @@ namespace lvh::reports { return static_cast((static_cast(elapsed) * 3U) / 16U); } + std::uint8_t dualsense_sequence_number() { + static std::atomic_uint32_t sequence_number = 0; + return static_cast((sequence_number.fetch_add(1U, std::memory_order_relaxed) + 1U) % 255U); + } + + std::uint32_t dualsense_sensor_timestamp() { + const auto elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() + ) + .count(); + return static_cast(static_cast(elapsed) / 333U); + } + std::vector pack_dualshock4_input_report(const DeviceProfile &profile, const GamepadState &state) { const auto is_bluetooth = profile.bus_type == BusType::bluetooth; const auto payload_offset = is_bluetooth ? 3U : 1U; @@ -627,6 +648,9 @@ namespace lvh::reports { ByteReport report(profile.input_report_size, zero_byte); report[0] = is_bluetooth ? dualshock4_bt_input_report_id : to_byte(profile.report_id); + if (is_bluetooth) { + report[1] = dualshock4_bt_input_hid_present; + } report[payload_offset + 0U] = to_byte(normalize_u8_axis(normalized.left_stick.x)); report[payload_offset + 1U] = to_byte(normalize_u8_axis(-normalized.left_stick.y)); @@ -722,6 +746,7 @@ namespace lvh::reports { report[payload_offset + 3U] = to_byte(normalize_u8_axis(-normalized.right_stick.y)); report[payload_offset + 4U] = to_byte(normalize_trigger(normalized.left_trigger)); report[payload_offset + 5U] = to_byte(normalize_trigger(normalized.right_trigger)); + report[payload_offset + 6U] = to_byte(dualsense_sequence_number()); report[payload_offset + 7U] = to_byte(hat_from_buttons(normalized.buttons)); if (normalized.buttons.test(GamepadButton::x)) { @@ -773,15 +798,16 @@ namespace lvh::reports { } if (normalized.gyroscope) { - write_i16(report, payload_offset + 15U, scale_i16(normalized.gyroscope->x, 1145.0F)); - write_i16(report, payload_offset + 17U, scale_i16(normalized.gyroscope->y, 1145.0F)); - write_i16(report, payload_offset + 19U, scale_i16(normalized.gyroscope->z, 1145.0F)); + write_i16(report, payload_offset + 15U, scale_i16(normalized.gyroscope->x, dualsense_gyroscope_scale)); + write_i16(report, payload_offset + 17U, scale_i16(normalized.gyroscope->y, dualsense_gyroscope_scale)); + write_i16(report, payload_offset + 19U, scale_i16(normalized.gyroscope->z, dualsense_gyroscope_scale)); } if (normalized.acceleration) { - write_i16(report, payload_offset + 21U, scale_i16(normalized.acceleration->x, 100.0F)); - write_i16(report, payload_offset + 23U, scale_i16(normalized.acceleration->y, 100.0F)); - write_i16(report, payload_offset + 25U, scale_i16(normalized.acceleration->z, 100.0F)); + write_i16(report, payload_offset + 21U, scale_i16(normalized.acceleration->x, dualsense_acceleration_scale)); + write_i16(report, payload_offset + 23U, scale_i16(normalized.acceleration->y, dualsense_acceleration_scale)); + write_i16(report, payload_offset + 25U, scale_i16(normalized.acceleration->z, dualsense_acceleration_scale)); } + write_u32(report, payload_offset + 27U, dualsense_sensor_timestamp()); write_dualsense_touch_contact(report, payload_offset + 32U, normalized.touchpad_contacts[0]); write_dualsense_touch_contact(report, payload_offset + 36U, normalized.touchpad_contacts[1]); diff --git a/src/include/libvirtualhid/profiles.hpp b/src/include/libvirtualhid/profiles.hpp index 0ca6a4e..4888cb3 100644 --- a/src/include/libvirtualhid/profiles.hpp +++ b/src/include/libvirtualhid/profiles.hpp @@ -44,7 +44,11 @@ namespace lvh::profiles { /** * @brief Create the PlayStation DualShock 4-compatible gamepad profile. * - * @return Default DualShock 4-compatible device profile. + * The default uses Bluetooth framing because Linux native-controller + * consumers discover virtual DualShock 4 devices more reliably through that + * transport. Use `dualshock4_usb()` when USB framing is explicitly required. + * + * @return Bluetooth DualShock 4-compatible device profile. */ DeviceProfile dualshock4(); @@ -65,7 +69,11 @@ namespace lvh::profiles { /** * @brief Create the PlayStation DualSense-compatible gamepad profile. * - * @return Default DualSense-compatible device profile. + * The default uses Bluetooth framing because Linux native-controller + * consumers discover virtual DualSense devices more reliably through that + * transport. Use `dualsense_usb()` when USB framing is explicitly required. + * + * @return Bluetooth DualSense-compatible device profile. */ DeviceProfile dualsense(); diff --git a/src/platform/linux/uhid_backend.cpp b/src/platform/linux/uhid_backend.cpp index d64f5ab..eaffb3e 100644 --- a/src/platform/linux/uhid_backend.cpp +++ b/src/platform/linux/uhid_backend.cpp @@ -2815,8 +2815,8 @@ namespace lvh::detail { std::memcpy(request.rd_data, options.profile.report_descriptor.data(), options.profile.report_descriptor.size()); profile_ = options.profile; { - std::lock_guard lock {report_mutex_}; - last_report_ = reports::pack_input_report(profile_, {}); + std::lock_guard lock {state_mutex_}; + last_state_ = {}; } { @@ -2848,25 +2848,13 @@ namespace lvh::detail { } OperationStatus submit( - const GamepadState & /*state*/, + const GamepadState &state, const std::vector &report ) override { - if (!open_) { - return OperationStatus::failure(ErrorCode::device_closed, "UHID gamepad is closed"); - } - - uhid_event event {}; - if (report.size() > sizeof(event.u.input2.data)) { - return OperationStatus::failure(ErrorCode::invalid_argument, "HID input report is too large for UHID"); - } - - event.type = UHID_INPUT2; - event.u.input2.size = static_cast(report.size()); - std::memcpy(event.u.input2.data, report.data(), report.size()); - auto status = write_event(event); + std::lock_guard lock {state_mutex_}; + auto status = write_input_report(report); if (status.ok()) { - std::lock_guard lock {report_mutex_}; - last_report_ = report; + last_state_ = state; } return status; } @@ -2922,6 +2910,22 @@ namespace lvh::detail { } private: + OperationStatus write_input_report(const std::vector &report) { + if (!open_) { + return OperationStatus::failure(ErrorCode::device_closed, "UHID gamepad is closed"); + } + + uhid_event event {}; + if (report.size() > sizeof(event.u.input2.data)) { + return OperationStatus::failure(ErrorCode::invalid_argument, "HID input report is too large for UHID"); + } + + event.type = UHID_INPUT2; + event.u.input2.size = static_cast(report.size()); + std::memcpy(event.u.input2.data, report.data(), report.size()); + return write_event(event); + } + OperationStatus write_event(const uhid_event &event) { using enum ErrorCode; @@ -3042,13 +3046,10 @@ namespace lvh::detail { break; } - std::vector report; - { - std::lock_guard lock {report_mutex_}; - report = last_report_; - } + std::lock_guard lock {state_mutex_}; + const auto report = reports::pack_input_report(profile_, last_state_); if (!report.empty()) { - static_cast(submit({}, report)); + static_cast(write_input_report(report)); } } } @@ -3173,7 +3174,7 @@ namespace lvh::detail { std::string physical_id_; std::string unique_id_; std::array playstation_mac_address_ {}; - std::vector last_report_; + GamepadState last_state_; std::atomic_bool open_ = true; std::atomic_bool running_ = false; std::jthread reader_; @@ -3183,7 +3184,7 @@ namespace lvh::detail { bool started_ = false; bool reader_exited_ = false; std::mutex write_mutex_; - std::mutex report_mutex_; + std::mutex state_mutex_; std::mutex callback_mutex_; OutputCallback output_callback_; }; diff --git a/tests/fixtures/include/fixtures/linux_backend_test_hooks.hpp b/tests/fixtures/include/fixtures/linux_backend_test_hooks.hpp index d52fafd..6742901 100644 --- a/tests/fixtures/include/fixtures/linux_backend_test_hooks.hpp +++ b/tests/fixtures/include/fixtures/linux_backend_test_hooks.hpp @@ -212,9 +212,9 @@ namespace lvh::detail::test { bool saw_dualshock4_feature_crc = false; /** - * @brief Whether the peer observed a Bluetooth-framed DualSense input report. + * @brief Whether periodic Bluetooth DualSense reports preserved motion and advanced sensor metadata. */ - bool saw_dualsense_bluetooth_input = false; + bool saw_dualsense_bluetooth_input_with_live_sensor_metadata = false; /** * @brief Whether the peer observed a Bluetooth-framed DualShock 4 input report. diff --git a/tests/fixtures/linux_backend_test_hooks.cpp b/tests/fixtures/linux_backend_test_hooks.cpp index d9b0f31..91345e3 100644 --- a/tests/fixtures/linux_backend_test_hooks.cpp +++ b/tests/fixtures/linux_backend_test_hooks.cpp @@ -1698,13 +1698,42 @@ namespace lvh::detail::test { UhidGamepad gamepad {descriptors[0]}; auto event = create_started_profile_uhid_gamepad(gamepad, 9, options, descriptors[1], BUS_BLUETOOTH, result); + GamepadState motion_state; + motion_state.acceleration = Vector3 {.x = 1.0F, .y = 2.0F, .z = 3.0F}; + motion_state.gyroscope = Vector3 {.x = 4.0F, .y = 5.0F, .z = 6.0F}; + const auto submitted_report = reports::pack_input_report(options.profile, motion_state); + result.submit_status = gamepad.submit(motion_state, submitted_report); + + bool first_input_report_valid = false; + std::uint8_t first_sequence = 0; + std::uint32_t first_sensor_timestamp = 0; + std::array first_sensor_values {}; if (read_uhid_event_type(descriptors[1], UHID_INPUT2, event)) { const auto report_size = static_cast(event.u.input2.size); if (report_size == options.profile.input_report_size && event.u.input2.data[0] == 0x31) { const auto crc_offset = report_size - 4U; const auto expected_crc = crc32(std::span {event.u.input2.data, crc_offset}, playstation_crc_seed(0xA1)); const auto actual_crc = read_u32_le(event.u.input2.data + crc_offset); - result.saw_dualsense_bluetooth_input = expected_crc == actual_crc; + std::copy_n(event.u.input2.data + 17U, first_sensor_values.size(), first_sensor_values.begin()); + first_input_report_valid = expected_crc == actual_crc && std::equal( + first_sensor_values.begin(), + first_sensor_values.end(), + submitted_report.begin() + 17 + ); + first_sequence = event.u.input2.data[8]; + first_sensor_timestamp = read_u32_le(event.u.input2.data + 29U); + } + } + + if (read_uhid_event_type(descriptors[1], UHID_INPUT2, event)) { + const auto report_size = static_cast(event.u.input2.size); + if (report_size == options.profile.input_report_size && event.u.input2.data[0] == 0x31) { + const auto crc_offset = report_size - 4U; + const auto expected_crc = crc32(std::span {event.u.input2.data, crc_offset}, playstation_crc_seed(0xA1)); + const auto actual_crc = read_u32_le(event.u.input2.data + crc_offset); + result.saw_dualsense_bluetooth_input_with_live_sensor_metadata = + first_input_report_valid && expected_crc == actual_crc && event.u.input2.data[8] != first_sequence && + read_u32_le(event.u.input2.data + 29U) != first_sensor_timestamp && std::equal(first_sensor_values.begin(), first_sensor_values.end(), event.u.input2.data + 17U); } } @@ -1732,7 +1761,6 @@ namespace lvh::detail::test { result.close_status = gamepad.close(); static_cast(::close(descriptors[1])); - result.submit_status = OperationStatus::success(); return result; } @@ -1840,7 +1868,11 @@ namespace lvh::detail::test { if (read_uhid_event_type(descriptors[1], UHID_INPUT2, event)) { const auto report_size = static_cast(event.u.input2.size); - if (report_size == options.profile.input_report_size && event.u.input2.data[0] == 0x11) { + if ( + report_size == options.profile.input_report_size && + event.u.input2.data[0] == 0x11 && + (event.u.input2.data[1] & 0x80U) != 0U + ) { const auto crc_offset = report_size - 4U; const auto expected_crc = crc32(std::span {event.u.input2.data, crc_offset}, playstation_crc_seed(0xA1)); const auto actual_crc = read_u32_le(event.u.input2.data + crc_offset); diff --git a/tests/unit/test_linux_backend.cpp b/tests/unit/test_linux_backend.cpp index 9342c2f..70214eb 100644 --- a/tests/unit/test_linux_backend.cpp +++ b/tests/unit/test_linux_backend.cpp @@ -822,11 +822,12 @@ TEST_F(LinuxBackendTest, SocketpairBackedDualSenseRepliesToFeatureReports) { TEST_F(LinuxBackendTest, SocketpairBackedDualSenseBluetoothFramesReports) { const auto result = lvh::detail::test::linux_dualsense_bluetooth_uhid_socketpair_reports(); EXPECT_TRUE(result.create_status.ok()) << result.create_status.message(); + EXPECT_TRUE(result.submit_status.ok()) << result.submit_status.message(); EXPECT_TRUE(result.close_status.ok()) << result.close_status.message(); EXPECT_TRUE(result.creation.saw_create); EXPECT_TRUE(result.creation.waited_for_start); EXPECT_EQ(result.creation.name, "Wireless Controller"); - EXPECT_TRUE(result.saw_dualsense_bluetooth_input); + EXPECT_TRUE(result.saw_dualsense_bluetooth_input_with_live_sensor_metadata); EXPECT_TRUE(result.saw_dualsense_pairing); EXPECT_TRUE(result.saw_dualsense_feature_crc); } diff --git a/tests/unit/test_linux_consumers.cpp b/tests/unit/test_linux_consumers.cpp index 675fa4f..b733de7 100644 --- a/tests/unit/test_linux_consumers.cpp +++ b/tests/unit/test_linux_consumers.cpp @@ -886,7 +886,7 @@ TEST_F(LinuxConsumerTest, SdlSeesDualShock4UsbControllerBehavior) { }); } -TEST_F(LinuxConsumerTest, SdlSeesDualShock4BluetoothControllerDiscovery) { +TEST_F(LinuxConsumerTest, SdlSeesDualShock4BluetoothControllerBehavior) { ASSERT_TRUE(HasReadableWritableDeviceNode("/dev/uhid")); run_sdl_playstation_controller_test({ @@ -895,7 +895,6 @@ TEST_F(LinuxConsumerTest, SdlSeesDualShock4BluetoothControllerDiscovery) { .stable_id = "02:00:00:00:00:04", .minimum_buttons = 10, .minimum_axes = 4, - .expect_live_input = false, }); } diff --git a/tests/unit/test_profiles.cpp b/tests/unit/test_profiles.cpp index 040f0bd..4b603bf 100644 --- a/tests/unit/test_profiles.cpp +++ b/tests/unit/test_profiles.cpp @@ -176,8 +176,10 @@ TEST(ProfileTest, StreamingControllerProfilesArePresent) { EXPECT_EQ(dualshock4.vendor_id, 0x054C); EXPECT_EQ(dualshock4.product_id, 0x05C4); EXPECT_EQ(dualshock4.version, 0x0100); - EXPECT_EQ(dualshock4.input_report_size, 64U); - EXPECT_EQ(dualshock4.output_report_size, 32U); + EXPECT_EQ(dualshock4.bus_type, lvh::BusType::bluetooth); + EXPECT_EQ(dualshock4.report_id, 0x11); + EXPECT_EQ(dualshock4.input_report_size, 78U); + EXPECT_EQ(dualshock4.output_report_size, 78U); EXPECT_TRUE(dualshock4.capabilities.supports_motion); EXPECT_TRUE(dualshock4.capabilities.supports_touchpad); EXPECT_TRUE(dualshock4.capabilities.supports_rgb_led); @@ -193,9 +195,20 @@ TEST(ProfileTest, StreamingControllerProfilesArePresent) { EXPECT_EQ(dualshock4_bluetooth.report_id, 0x11); EXPECT_EQ(dualshock4_bluetooth.input_report_size, 78U); EXPECT_EQ(dualshock4_bluetooth.output_report_size, 78U); - EXPECT_NE(dualshock4_bluetooth.report_descriptor, dualshock4.report_descriptor); + EXPECT_EQ(dualshock4_bluetooth.report_descriptor, dualshock4.report_descriptor); + + const auto dualshock4_usb = lvh::profiles::dualshock4_usb(); + EXPECT_EQ(dualshock4_usb.bus_type, lvh::BusType::usb); + EXPECT_EQ(dualshock4_usb.report_id, 0x01); + EXPECT_EQ(dualshock4_usb.input_report_size, 64U); + EXPECT_EQ(dualshock4_usb.output_report_size, 32U); + EXPECT_NE(dualshock4_usb.report_descriptor, dualshock4.report_descriptor); EXPECT_EQ(dualsense.vendor_id, 0x054C); + EXPECT_EQ(dualsense.bus_type, lvh::BusType::bluetooth); + EXPECT_EQ(dualsense.report_id, 0x31); + EXPECT_EQ(dualsense.input_report_size, 78U); + EXPECT_EQ(dualsense.output_report_size, 78U); EXPECT_TRUE(dualsense.capabilities.supports_motion); EXPECT_TRUE(dualsense.capabilities.supports_touchpad); EXPECT_TRUE(dualsense.capabilities.supports_rgb_led); @@ -210,7 +223,14 @@ TEST(ProfileTest, StreamingControllerProfilesArePresent) { EXPECT_EQ(dualsense_bluetooth.report_id, 0x31); EXPECT_EQ(dualsense_bluetooth.input_report_size, 78U); EXPECT_EQ(dualsense_bluetooth.output_report_size, 78U); - EXPECT_NE(dualsense_bluetooth.report_descriptor, dualsense.report_descriptor); + EXPECT_EQ(dualsense_bluetooth.report_descriptor, dualsense.report_descriptor); + + const auto dualsense_usb = lvh::profiles::dualsense_usb(); + EXPECT_EQ(dualsense_usb.bus_type, lvh::BusType::usb); + EXPECT_EQ(dualsense_usb.report_id, 0x01); + EXPECT_EQ(dualsense_usb.input_report_size, 64U); + EXPECT_EQ(dualsense_usb.output_report_size, 48U); + EXPECT_NE(dualsense_usb.report_descriptor, dualsense.report_descriptor); EXPECT_EQ(switch_pro.vendor_id, 0x057E); EXPECT_EQ(switch_pro.product_id, 0x2009); diff --git a/tests/unit/test_report.cpp b/tests/unit/test_report.cpp index a8f3037..06a73a4 100644 --- a/tests/unit/test_report.cpp +++ b/tests/unit/test_report.cpp @@ -50,6 +50,10 @@ namespace { return static_cast(low | static_cast(high << 8U)); } + std::int16_t read_i16_le(std::span bytes, std::size_t offset) { + return static_cast(read_u16_le(bytes, offset)); + } + lvh::GamepadState make_active_gamepad_state() { using enum lvh::GamepadButton; @@ -232,12 +236,32 @@ TEST(ReportTest, PacksDualSenseUsbReport) { EXPECT_EQ(report[5], 255); EXPECT_EQ(report[8] & 0x20, 0x20); EXPECT_EQ(report[9] & 0x05, 0x05); + EXPECT_EQ(read_i16_le(report, 16U), 80); + EXPECT_EQ(read_i16_le(report, 18U), 100); + EXPECT_EQ(read_i16_le(report, 20U), 120); + EXPECT_EQ(read_i16_le(report, 22U), 981); + EXPECT_EQ(read_i16_le(report, 24U), 1961); + EXPECT_EQ(read_i16_le(report, 26U), 2942); + EXPECT_NE(read_u32_le(report, 28U), 0U); EXPECT_EQ(report[33] & 0x7F, 3); EXPECT_EQ(report[33] & 0x80, 0); EXPECT_EQ(report[53] & 0x0F, 8); EXPECT_EQ(report[53] >> 4, 1); } +TEST(ReportTest, AdvancesDualSenseSensorMetadata) { + const auto profile = lvh::profiles::dualsense_bluetooth(); + + const auto first = lvh::reports::pack_input_report(profile, {}); + const auto second = lvh::reports::pack_input_report(profile, {}); + + ASSERT_EQ(first.size(), profile.input_report_size); + ASSERT_EQ(second.size(), profile.input_report_size); + EXPECT_NE(first[8], second[8]); + EXPECT_NE(read_u32_le(first, 29U), 0U); + EXPECT_GE(read_u32_le(second, 29U), read_u32_le(first, 29U)); +} + TEST(ReportTest, PacksDualSenseBluetoothReportWithCrc) { const auto profile = lvh::profiles::dualsense_bluetooth(); @@ -326,6 +350,7 @@ TEST(ReportTest, PacksDualShock4BluetoothReportWithCrc) { ASSERT_EQ(report.size(), profile.input_report_size); EXPECT_EQ(report[0], 0x11); + EXPECT_EQ(report[1], 0x80); EXPECT_EQ(report[3], 128); EXPECT_EQ(report[4], 128); EXPECT_EQ(report[9], 0x02);