rf_link.h 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. // rf_link.h -- the operational puck<->controller RF protocol (the "dongle" role).
  2. //
  3. // Built on the radio HW layer (radio.cpp), this is what makes the device act as a puck once a controller is
  4. // bonded: it transmits the host frame the bonded controller reconnects on, runs the connected-mode poll loop
  5. // (E7 awake announce -> E3 + GET-report-0x45 each cycle), and decodes the controller's 0xF1 input reply. The
  6. // decode fills the shared g_in (triton.h) and dispatches to the active IController; it also handles the
  7. // mode-switch chord, USB remote-wakeup on a Steam short-press, adaptive channel hopping (QoS), and the
  8. // host-frame keepalive beacons. RE/calibration scaffolding lives separately in rf_diag.cpp.
  9. //
  10. // RECIPE: a live air capture of a real puck<->controller RECONNECT (sniff1.json) shows the controller streams
  11. // 0xF1 input in response to a BARE 0xE3 poll -- with NO 0xE7 awake-announce and NO GET-report-0x45 sub-TLV.
  12. // So the only thing OpenPuck genuinely needs to (re)establish the stream is connstate==3, which the host frame
  13. // (0xE1) sets up; once connected, bare E3 polls keep F1 flowing. (The earlier RE hypothesis that E7+GET-0x45
  14. // were required is contradicted by the capture -- see g_pollGet/g_e7announce.) The real puck also sends no E1
  15. // on its session channel (its bonded controller resumes on the known per-bond address); OpenPuck still beacons
  16. // E1 only because it runs the SHARED "ibex" address, not the per-bond one (g_e1keepalive).
  17. // F1 reply = type 0xF1 then TLV type4(analog/buttons)+type6(per-module gyro/accel/sticks).
  18. #pragma once
  19. #include <stdint.h>
  20. #include "bonds.h" // NSLOT
  21. // ---- operational toggles + tunables (set from the CDC console / WebUSB; read here + by webusb status) ----
  22. // auto-start host beacon on boot (resumes puck role after a USB replug)
  23. extern bool g_rfHost;
  24. extern bool g_connOn; // auto-start connected-mode poll on boot
  25. extern uint8_t g_connType; // start packet type (E7 handshake, then E3)
  26. // E7 payload B-byte: 0=current(slow/awake), 1=protocol-version-1
  27. extern uint8_t g_e7b;
  28. extern uint8_t g_connLen;
  29. extern uint8_t g_getParam; // GET report 0x45 param byte
  30. // E3-poll PID/S1 mode (0=fixed07, 1=cyclePID+noack1, 2=cyclePID+noack0)
  31. extern uint8_t g_e3mode;
  32. // Real-puck alignment toggles (default to the captured real-puck behavior; flip on the CDC console to A/B):
  33. extern bool
  34. g_pollGet; // 'd': false=bare E3 poll (real puck), true=append GET-report-0x45 TLV (legacy)
  35. extern bool
  36. g_e7announce; // 'n': false=no E7 awake-announce (real puck), true=announce host-awake (legacy)
  37. extern bool
  38. g_e1keepalive; // 'm': session-channel E1 host-frame keepalive (ON by default; needed for shared addr)
  39. // full multi-line debug dump vs compact "I45 <hex>" stream
  40. extern bool g_connVerbose;
  41. // poll RX-window (us): the full window is paid only on a no-reply poll (a reply returns early), so it must
  42. // stay < g_pollUs to fit inside the 250 Hz cycle. FIXED, not configurable (like g_pollUs) -- raising it
  43. // starves the poll cycle, so it's pinned rather than exposed as a footgun knob.
  44. extern const uint32_t g_rxWin;
  45. // set on 0xF2 disconnect; pauses beacon+poll so a powering-off controller can sleep
  46. extern unsigned long g_connCooldown;
  47. // connected-mode state (reset by the 'k' console toggle)
  48. extern uint8_t g_connSt, g_connStep;
  49. extern uint16_t g_connPoll;
  50. extern uint32_t
  51. g_connF1; // count of 0xF1 input reports seen (aggregate across all slots)
  52. // last protocol version the controller reported in an F3 reply (0xFF=none) -- aggregate (last writer wins)
  53. extern uint8_t g_connF3v;
  54. // Slot the poll loop is currently driving (set by rfConnStep before each E7/relay/E3). -1 = idle. Used by
  55. // the decode path (g_in[g_curSlot]), the haptic flush (per-slot session address), the wake detect, and the
  56. // stat dump.
  57. extern int g_curSlot;
  58. // True if ANY bonded slot is currently link-up (heard an F-reply within 300 ms). Used by the haptic
  59. // re-init gating (we only re-init on a real link) and the beacon pacing.
  60. bool anySlotLinkUp();
  61. // QoS adaptive channel hopping
  62. extern uint8_t g_qos; // 0=off (static g_sessCh), 1=auto-hop on degradation
  63. extern uint8_t g_hopIdx;
  64. extern volatile uint16_t g_qosBad;
  65. extern unsigned long g_qosCheckMs, g_qosLastHopMs;
  66. // per-second rate readouts for the WebUSB status blob
  67. extern uint16_t g_f1ps; // last completed second's F1 rate
  68. // genuine new-report rate (report 0x45 seq byte changes)
  69. extern uint16_t g_newps;
  70. // last second's TRUE poll-cycle count (one E3 GET per warm slot per cycle, ~250 for one controller) -- vs F1
  71. // tells starvation from reply-loss.
  72. extern uint16_t g_pollsps;
  73. // last second's relay-frame TX count (host/haptic output reports forwarded to the controller). Each relay TX
  74. // precedes a poll and consumes a reply window, so a high relay rate directly depresses the F1/delivered rate.
  75. extern uint16_t g_relayps;
  76. // last second's CRC-fail and no-reply counts: diagnose a wedge -- polls>0 + noRx high = RX dead / wrong
  77. // channel / controller silent; crc high = corrupt replies (interference); polls==0 = poll loop stopped.
  78. extern uint16_t g_crcps, g_norxps;
  79. // PER-SLOT per-second snapshots (WebUSB blob v13): the aggregates above are sums of these. Each controller's
  80. // panel tab shows its OWN link health, so one slot drowning no longer hides behind another's healthy rate.
  81. extern uint16_t g_slotPollsps[NSLOT], g_slotF1ps[NSLOT], g_slotNewps[NSLOT];
  82. extern uint8_t g_slotCrcps[NSLOT], g_slotNoRxps[NSLOT], g_slotRelayps[NSLOT];
  83. // Count of RF-stall self-heals (radio power-cycle + reconnect) performed since boot. Non-zero means the
  84. // firmware caught and recovered from a full-link wedge that previously needed a manual puck replug.
  85. extern uint16_t g_rfStallRecover;
  86. // MEASURED avg us between GET-poll fires (compare to the intended g_pollUs=4000)
  87. extern uint16_t g_pollPeriodUs;
  88. // Smoothed controller->puck signal strength, sampled by the radio (RSSISAMPLE) on each CRC-good controller
  89. // reply during the poll. Stored as the dBm MAGNITUDE (35 = -35dBm); 0 = no sample yet. Per-slot: each
  90. // controller's RSSI is reported on its own 0x7B byte 8 (puck_hid reports it to Steam, after subtracting
  91. // RSSI_DBM_OFFSET).
  92. extern volatile uint8_t g_linkRssi[NSLOT];
  93. // dB to subtract from our raw RSSI magnitude before reporting, to match the real puck's close-range -35dBm
  94. // (compensates the Pro Micro antenna vs Valve's front-end). Tune against one known-distance reading.
  95. #define RSSI_DBM_OFFSET 20
  96. // Battery percent from the controller's periodic report 0x43 (body[1]); 0 = none yet. Per-slot: each
  97. // controller's battery is read on its own 0x43 reply and stored against its slot index. The WebUSB panel
  98. // reads g_battery[g_curSlot] (the most recently active one). The raw 0x43 report is also forwarded to
  99. // Steam verbatim (puck_hid onAuxReport) so Steam reads it itself.
  100. extern volatile uint8_t g_battery[NSLOT];
  101. // charge state from report 0x43 body[0] (EChargeState: 1=discharging, 2=charging, 4=charging-done; 0=unknown)
  102. extern volatile uint8_t g_batteryState[NSLOT];
  103. // TX one connected packet [LEN][S1][payload] on channel ch, then RX the reply into rfrx; decodes 0xF1.
  104. // rxWinUs overrides the reply-wait window (0 = use g_rxWin). Pass a tiny value for NO-ACK relays that expect
  105. // no reply, so they don't burn a full ~1.2ms window of dead air per haptic.
  106. uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
  107. uint16_t rxWinUs = 0);
  108. // Mid-session channel hop: advertise newCh on the current channel a few times, then move the poll to newCh.
  109. void rfHopTo(uint8_t newCh);
  110. // Per-loop: host-frame beacons + connected-mode poll + remote-wakeup + QoS hop + per-second stats.
  111. void rfLinkTask();