haptics.h 5.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. // haptics.h -- host -> controller relay queue (haptics + settings) + the watchdogs that stop a stuck buzz.
  2. //
  3. // Steam (and legacy XInput rumble) send OUTPUT/feature reports the real dongle forwards to the controller as
  4. // a SET sub-TLV inside the E3 poll. We do the same: handleSet()/the console enqueue with relayEnqueue(), and
  5. // rfConnFlushRelay() emits ONE entry per poll cycle (rf_link.cpp), never at raw loop rate.
  6. //
  7. // The queue is a small ring, NOT a single buffer: producers run in USB-ISR context while the consumer may be
  8. // mid-flush in loop context, and Steam writes settings/calibration as bursts of back-to-back feature reports.
  9. // The old single g_relayBuf both LOST every report that arrived before the previous one flushed and could be
  10. // TORN mid-copy by the ISR (a half-old-half-new command on the air) -- the settings-never-save and the
  11. // corrupted-haptic-latch bugs. Entries carry up to RELAY_MAXP payload bytes (the RF frame fits 60: [E3][len]
  12. // [05][rid] + payload <= MAXLEN 64); the old path truncated everything to 18, which chopped any multi-register
  13. // 0x87 settings block (LED brightness) and calibration write.
  14. //
  15. // The controller's haptic LATCHES until told to stop. If the host's stop is lost over RF (or the link drops
  16. // mid-buzz) the actuator whines forever -- so we send 0x82-zero stop bursts on reconnect, but ONLY when a
  17. // haptic was actually active when the link dropped: every extra 0x82 frame is an audible click on the
  18. // controller (captured), so the old unconditional burst on each link-up edge was itself a buzz source on a
  19. // flapping link. The g_hapLog ring captures recent OUTPUT reports for the 'H' dump.
  20. #pragma once
  21. #include <stdint.h>
  22. #include "config.h" // OPK_LOG
  23. // after this much host silence, consider the current 0x82 haptic stream inactive
  24. #define HAPTIC_QUIET_MS 300u
  25. // Block ALL haptic relays for this long after a (re)connect: a just-powered-on controller's haptic engine
  26. // isn't ready, and feeding it haptics in that window leaves it in a degraded/latched state (slow/stuck/missing
  27. // haptics until a re-init). 3s gives it time to initialize. (Was 1500.)
  28. #define HAPTIC_RECONNECT_BLOCK_MS 3000u
  29. // 0x82-zero relays per stop event (sent at poll cadence -- not loop rate)
  30. #define HAPTIC_STOP_BURST 4u
  31. // max relayed payload bytes per entry: RF frame = [E3][len][05][rid][payload] and MAXLEN=64 -> 60
  32. #define RELAY_MAXP 60u
  33. // Proactive post-(re)connect haptic re-init: this many shots, this far apart, starting ~200ms after the link
  34. // comes up -- covers ~0.2s..3s so the brief controller-side connect buzz gets reset before it can sustain.
  35. #define HAPTIC_REINIT_SHOTS 8u
  36. #define HAPTIC_REINIT_GAP_MS 350u
  37. // ---- relay queue (written by puck_hid.cpp, mode_xinput.cpp, serial_console.cpp; drained by rf_link.cpp) ----
  38. // Enqueue one host->controller report: rid = report/command id, payload = the bytes AFTER [cmd][len] (what
  39. // goes on the air). ISR-safe (brief PRIMASK critical section). Returns false (dropped) when the ring is full.
  40. bool relayEnqueue(uint8_t rid, const uint8_t* payload, uint8_t plen);
  41. bool relayPending(); // anything still queued (xinput uses it to pace rumble re-queues)
  42. extern uint8_t g_relayOp; // relay frame opcode (E3 poll)
  43. extern uint8_t g_relaySub; // relay sub-TLV type byte = SET
  44. extern volatile uint8_t g_testHaptic;// 't<n>' injects n test haptics for the buzz hunt
  45. extern volatile uint8_t g_hapticStop;// pending haptic-STOP frames to relay (kill a latched whine)
  46. extern unsigned long g_hapticBlockUntil;
  47. // ---- diagnostic capture (compiled in only when OPK_LOG): a ring of recent host->controller commands +
  48. // link/TX markers, dumped over WebUSB. No-ops in a production build so call sites vanish. ----
  49. #if OPK_LOG
  50. void hapLogAdd(uint8_t slot, uint8_t rid, const uint8_t* b, uint16_t n);
  51. void hapticDumpLog(); // 'H' console dump of the recent OUTPUT-report history
  52. // WebUSB capture drain: resetDrain(true) rewinds to the OLDEST entry (dump the whole ring from boot);
  53. // resetDrain(false) starts at "now" (live only). pull yields each entry once, oldest->newest, skipping empties.
  54. void hapLogResetDrain(bool fromBoot);
  55. bool hapLogPull(uint32_t* logMs, uint8_t* slot, uint8_t* rid, uint8_t* n, uint8_t bytes16[16]);
  56. #else
  57. static inline void hapLogAdd(uint8_t, uint8_t, const uint8_t*, uint16_t){}
  58. static inline void hapticDumpLog(){}
  59. static inline void hapLogResetDrain(bool){}
  60. static inline bool hapLogPull(uint32_t*, uint8_t*, uint8_t*, uint8_t*, uint8_t*){ return false; }
  61. #endif
  62. bool hapticLinkUp();
  63. bool haptic82Blocked();
  64. bool hapticRelaySlotOk(int slot);
  65. void haptic82HostReport(const uint8_t* p, uint16_t n);
  66. // queue + flush the pending host/test/stop relay inside the poll cadence (called from rf_link).
  67. // rfConnFlushRelay's s1 must carry a PID distinct from the GET poll that follows it (rf_link cycles the shared
  68. // PID counter), so the controller doesn't dedup the GET as a retransmit of the relay.
  69. void rfConnQueueHapticRelay();
  70. void rfConnFlushRelay(uint8_t ch, uint8_t s1);
  71. void hapticInit(); // boot reset: clear relay/active flags, arm the reconnect block
  72. void hapticTask(); // per-loop upkeep: link-edge markers + steam 0x82 quiet timeout + fires the scheduled re-init
  73. void hapticReinit(); // replay Steam's haptic-subsystem re-init to the controller -> clears a latched/stuck buzz
  74. // Called from rf_link the instant a controller (re)connect is detected (an F-reply after a gap): blocks haptic
  75. // relays for HAPTIC_RECONNECT_BLOCK_MS and schedules a re-init just after, to keep the freshly-booted
  76. // controller out of the degraded/latched haptic state. Reliable -- independent of hapticTask's link heuristic.
  77. void hapticOnReconnect();