From 7965751cc9309a0be46052ca7ef8aaf9fc284fc3 Mon Sep 17 00:00:00 2001 From: lbgos <285507185+lbgos@users.noreply.github.com> Date: Tue, 13 Oct 2026 07:21:07 +0300 Subject: [PATCH] Exercise production haptic reset and shutdown radio bypass --- src/xrt/drivers/rift_s/rift_s.c | 14 +----- src/xrt/drivers/rift_s/rift_s_haptics.c | 14 ++++++ src/xrt/drivers/rift_s/rift_s_haptics.h | 4 ++ tests/tests_rift_s_radio.cpp | 65 +++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/xrt/drivers/rift_s/rift_s.c b/src/xrt/drivers/rift_s/rift_s.c index 443c0acb3..9a9fb6d61 100644 --- a/src/xrt/drivers/rift_s/rift_s.c +++ b/src/xrt/drivers/rift_s/rift_s.c @@ -802,19 +802,7 @@ close_transport(struct rift_s_system *sys) for (unsigned i = 0; i < MAX_TRACKED_DEVICES; i++) { if (sys->controllers[i] != NULL) rift_s_controller_device_lost(sys->controllers[i]); - // The raw register latches amplitude and the transport is gone, so a stop - // can no longer be sent. Keep the possibly-latched identity so the first - // update after reconnect turns it off instead of forgetting it. - uint64_t latched_id = sys->haptics[i].applied_device_id; - uint8_t latched_amp = sys->haptics[i].applied_amplitude; - if (latched_amp == 0 && sys->haptics[i].sent_amplitude != 0) { - // The write reached the HMD but the ack never came back. - latched_id = sys->haptics[i].sent_device_id; - latched_amp = sys->haptics[i].sent_amplitude; - } - sys->haptics[i] = (struct rift_s_haptic_radio){.sys = sys, .hand = i}; - sys->haptics[i].applied_device_id = latched_id; - sys->haptics[i].applied_amplitude = latched_amp; + rift_s_haptic_transport_lost(&sys->haptics[i]); rift_s_motion_reset_hand(sys->motion, i); } if (sys->hmd != NULL) { diff --git a/src/xrt/drivers/rift_s/rift_s_haptics.c b/src/xrt/drivers/rift_s/rift_s_haptics.c index ed37b295b..3f37b7fc9 100644 --- a/src/xrt/drivers/rift_s/rift_s_haptics.c +++ b/src/xrt/drivers/rift_s/rift_s_haptics.c @@ -31,6 +31,20 @@ rift_s_haptic_duration(const struct xrt_output_value_vibration *value) } +void +rift_s_haptic_transport_lost(struct rift_s_haptic_radio *h) +{ + uint64_t id = h->applied_device_id; + uint8_t amplitude = h->applied_amplitude; + if (amplitude == 0 && h->sent_amplitude != 0) { + id = h->sent_device_id; + amplitude = h->sent_amplitude; + } + *h = (struct rift_s_haptic_radio){.sys = h->sys, .hand = h->hand, + .applied_device_id = id, .applied_amplitude = amplitude}; +} + + bool rift_s_haptic_next(const struct rift_s_haptic_radio *h, timepoint_ns now, uint64_t *id, uint8_t *amplitude) { diff --git a/src/xrt/drivers/rift_s/rift_s_haptics.h b/src/xrt/drivers/rift_s/rift_s_haptics.h index 4c975dcb4..f2e0842e9 100644 --- a/src/xrt/drivers/rift_s/rift_s_haptics.h +++ b/src/xrt/drivers/rift_s/rift_s_haptics.h @@ -17,6 +17,10 @@ struct rift_s_haptic_radio bool in_flight; }; +//! Discard requests while retaining the identity that may still have a latched motor. +void +rift_s_haptic_transport_lost(struct rift_s_haptic_radio *h); + //! Select the next serialized write, stopping an old identity before its replacement. bool rift_s_haptic_next(const struct rift_s_haptic_radio *h, timepoint_ns now, uint64_t *id, uint8_t *amplitude); diff --git a/tests/tests_rift_s_radio.cpp b/tests/tests_rift_s_radio.cpp index cc5bea0b9..ead82520f 100644 --- a/tests/tests_rift_s_radio.cpp +++ b/tests/tests_rift_s_radio.cpp @@ -391,3 +391,68 @@ TEST_CASE("Haptic latched stop survives a transport reset") amp = 0xFF; CHECK_FALSE(rift_s_haptic_next(&idle, T0, &id, &)); } + +TEST_CASE("Transport loss discards requests and preserves unacknowledged motor writes") +{ + rift_s_haptic_radio h = {}; + h.hand = 1; + h.device_id = h.sent_device_id = 42; + h.sent_amplitude = 200; + h.amplitude = 200; + h.end_ns = T0 + 1000 * MS; + h.in_flight = true; + h.retry_ns = T0 + 10 * MS; + rift_s_haptic_transport_lost(&h); + CHECK(h.hand == 1); + CHECK(h.device_id == 0); + CHECK(h.end_ns == 0); + CHECK_FALSE(h.in_flight); + uint64_t id = 0; + uint8_t amp = 255; + REQUIRE(rift_s_haptic_next(&h, T0, &id, &)); + CHECK(id == 42); + CHECK(amp == 0); + // A reconnect can fail again before the stop is sent. + rift_s_haptic_transport_lost(&h); + REQUIRE(rift_s_haptic_next(&h, T0, &id, &)); + CHECK(id == 42); + h.device_id = 99; + h.amplitude = 100; + h.end_ns = T0 + 100 * MS; + REQUIRE(rift_s_haptic_next(&h, T0, &id, &)); + CHECK(id == 42); + CHECK(amp == 0); + // The pending zero ack on loss must not erase the last acknowledged ON. + h.sent_device_id = 42; + h.sent_amplitude = 0; + h.in_flight = true; + rift_s_haptic_transport_lost(&h); + REQUIRE(rift_s_haptic_next(&h, T0, &id, &)); + CHECK(id == 42); + CHECK(amp == 0); +} + +TEST_CASE("Shutdown zero-only queue can send after a failed radio epoch") +{ + rift_s_radio_state st; + rift_s_radio_state_init(&st); + FakeHid hid = make_fake(); + Cb a; + queue_one(&st, &a); + rift_s_radio_update_at(&st, &hid.base, T0); + rift_s_radio_update_at(&st, &hid.base, T0 + RIFT_S_RADIO_CMD_TIMEOUT_MS * MS); + REQUIRE(st.recovery_required); + rift_s_radio_state_clear(&st); + uint8_t stop[5]; + rift_s_encode_haptic_raw(0, stop); + rift_s_radio_queue_report(&st, RIFT_S_RADIO_REPORT_WRITE, 42, stop, sizeof(stop), nullptr, nullptr); + hid.get_fn = [](uint8_t *data, size_t size) { + uint8_t payload[] = {0, 0, 0, 0}; + return fill_response(data, size, 11, 0, payload, sizeof(payload)); + }; + st.recovery_required = false; + rift_s_radio_update_at(&st, &hid.base, T0 + 2000 * MS); + CHECK(hid.set_calls == 2); + CHECK(st.pending_commands == nullptr); + rift_s_radio_state_clear(&st); +}