Bläddra i källkod

hopefully all gyro stuck fixed

Jariullah Safi 4 månader sedan
förälder
incheckning
7faf817d3b
5 ändrade filer med 61 tillägg och 24 borttagningar
  1. 38 17
      OpenPuck/haptics.cpp
  2. 2 1
      OpenPuck/haptics.h
  3. 5 2
      OpenPuck/rf_link.cpp
  4. 7 3
      OpenPuck/webusb_config.cpp
  5. 9 1
      docs/index.html

+ 38 - 17
OpenPuck/haptics.cpp

@@ -194,20 +194,35 @@ void rfConnFlushRelay(uint8_t ch, uint8_t s1){
 // haptic registers (30/07/08/31/52, 18/2e/34/35). We replay it to recover from the latched-buzz the
 // controller falls into across a reconnect. Brightness (0x87 reg 2d) is deliberately OMITTED so we don't
 // stomp the LED. Enqueued onto the normal relay (drains in the poll cadence).
+// Buzz-clear, replayed by the 30s FLOOD at 10Hz. 0x81 resets ONLY -- NO 0x87. Flooding the 0x34/0x35=0xffff
+// haptic amplitude writes at 10Hz is what spiked the connect buzz, so the flood must stay 0x87-free. The
+// gyro-enabling 0x87 config is sent separately by hapticConnectInit (a few connect shots, not the flood).
 void hapticReinit(){
-  // Buzz-clear re-init, replayed by the flood at 10Hz/30s on connect. Deliver ONLY the 0x81 reset frames --
-  // EXACTLY what actually reached the controller on `main`, where connect buzzes were RARE. On `main` the 0x87
-  // writes in this sequence were sent with the wrong sub-TLV type and the controller DISCARDED them, so the
-  // flood was effectively just 0x81 reset spam. The framing fix (bf49c5f) made 0x87 writes LAND -- and flooding
-  // the haptic-config registers (07/08/31/52, 18/2e/34/35, incl. 0x34/0x35=0xffff) at 10Hz, which the real puck
-  // NEVER does (it sends them once at connect, if at all), is what spiked the connect buzz to ~every other
-  // connection. So the flooded re-init drops the 0x87 block and sends only the 0x81 resets, matching `main`.
   static const uint8_t T81A[]={0x00,0x00,0x00,0x00,0x00,0x00,0x00};
   static const uint8_t T81B[]={0x01,0x00,0x00,0x00,0x00,0x00,0x00};
   relayEnqueue(0x81, nullptr, 0);            // reset action (FUN_0001f554) -- Steam sends this first
   relayEnqueue(0x81, T81A, sizeof T81A);
   relayEnqueue(0x81, T81B, sizeof T81B);
 }
+// Connect-time GYRO-ENABLE + haptic config. The full 0x87 config block (07/08/31/52, 18/2e, 34/35) is what makes
+// the controller STREAM gyro -- A/B confirmed: with the full block the gyro works; without it the IMU freezes,
+// and dropping just 34/35 made it INTERMITTENT (so 34/35 is part of the enable). The freeze is tied to connect
+// (when streaming starts it stays on), so we send this as a few CONNECT SHOTS (HAPTIC_REINIT_SHOTS, ~350ms apart)
+// -- NOT in the 10Hz/30s flood, because flooding 0x34/0x35=0xffff is the buzz source. A handful of shots at
+// connect = reliable gyro enable + only a few amplitude writes (minimal buzz). NO 0x30 (any 0x30 freezes gyro).
+void hapticConnectInit(){
+  static const uint8_t H30[]={0x07,0x07,0x00,0x08,0x07,0x00,0x31,0x02,0x00,0x52,0x03,0x00};
+  static const uint8_t H18[]={0x18,0x00,0x00,0x2e,0x00,0x00,0x34,0xff,0xff,0x35,0xff,0xff,0x34,0xff,0xff};
+  static const uint8_t H35[]={0x35,0xff,0xff,0x2e,0x00,0x00};
+  static const uint8_t T81A[]={0x00,0x00,0x00,0x00,0x00,0x00,0x00};
+  static const uint8_t T81B[]={0x01,0x00,0x00,0x00,0x00,0x00,0x00};
+  relayEnqueue(0x81, nullptr, 0);
+  relayEnqueue(0x87, H30, sizeof H30);       // 07/08/31/52
+  relayEnqueue(0x87, H18, sizeof H18);       // 18/2e/34/35 -- includes the writes that enable gyro streaming
+  relayEnqueue(0x87, H35, sizeof H35);
+  relayEnqueue(0x81, T81A, sizeof T81A);
+  relayEnqueue(0x81, T81B, sizeof T81B);
+}
 void hapticInit(){
   g_rqHead = g_rqTail = 0; g_haptic82On=false;
   if(g_buzzFlood) hapticArmBuzzFlood();   // flood the buzz-clear for the first HAPTIC_FLOOD_MS (g_buzzFlood: default ON)
@@ -243,16 +258,22 @@ void hapticTask(){
   if(up && !wasHapticLinkUp){ uint8_t mk=1; hapLogAdd(0xFD,0xEE,&mk,1); hapticOnReconnect(); }
   if(!up && wasHapticLinkUp){ uint8_t mk=0; hapLogAdd(0xFD,0xEE,&mk,1); }
   wasHapticLinkUp=up;
-  if(g_reinitAt && up && (int32_t)(millis()-g_reinitAt) >= 0){   // proactive haptic re-init across the connect window
-    hapticReinit();
-    g_reinitAt = (g_reinitLeft && --g_reinitLeft) ? (millis()+HAPTIC_REINIT_GAP_MS) : 0;
+  // HAPTIC-ENGINE INJECTION (connect config + buzz flood + idle re-init) runs ONLY in puck/Steam mode (!g_xbox).
+  // In the EMULATED controller modes (Xbox/Switch/HORI/PS5/DS4) there is no Steam driving haptics, and writing
+  // the controller's 0x81/0x87 config registers there is what was FREEZING the gyro -- the RF comms for emulated
+  // modes must stay a pure poll+transform, leaving the controller's own (default-on) IMU streaming untouched.
+  // Real 0x82 rumble (e.g. Xbox) is relayed separately via relayEnqueue and is NOT gated here.
+  if(!g_xbox){
+    if(g_reinitAt && up && (int32_t)(millis()-g_reinitAt) >= 0){   // connect SHOTS: full gyro-enable config (not the flood)
+      hapticConnectInit();
+      g_reinitAt = (g_reinitLeft && --g_reinitLeft) ? (millis()+HAPTIC_REINIT_GAP_MS) : 0;
+    }
+    // Buzz-clear FLOOD (g_buzzFlood, default ON): replay the 0x81 re-init at 10Hz across the armed window.
+    static unsigned long floodMs=0;
+    if (g_buzzFlood && (int32_t)(g_buzzFloodUntil - millis()) > 0 && up && millis()-floodMs >= HAPTIC_FLOOD_GAP_MS){ floodMs=millis(); hapticReinit(); }
+  } else {
+    g_reinitAt = 0;   // emulated modes: never inject -- just poll + transform
   }
-  // Buzz-clear FLOOD (g_buzzFlood, default ON): while the armed window is open (-> g_buzzFloodUntil) and the link
-  // is up, replay the haptic re-init every HAPTIC_FLOOD_GAP_MS (10Hz) to hammer the stuck-buzz latch. Auto-stops
-  // after 30s; re-armed on every connect and from the WebUSB panel. This is the PRIMARY buzz mitigation -- the
-  // connect config alone did not reliably stop it. Can be disabled from the panel for testing.
-  static unsigned long floodMs=0;
-  if (g_buzzFlood && (int32_t)(g_buzzFloodUntil - millis()) > 0 && up && millis()-floodMs >= HAPTIC_FLOOD_GAP_MS){ floodMs=millis(); hapticReinit(); }
   // Controller power-off on host SLEEP: send the power-off command (0x9F "off!") the instant the USB bus
   // suspends, like the real puck. BUT only when USB power (VBUS) is still present -- i.e. a genuine host sleep,
   // NOT a cable unplug. Pulling the dongle ALSO trips the suspend edge (in the brief window it runs on residual
@@ -270,5 +291,5 @@ void hapticTask(){
   // OPT-IN (g_buzzFlood): after haptic activity goes idle, fire one re-init to clear any latch it left behind.
   // Off by default along with the flood; the connect config is the primary defense. Always disarm so it doesn't
   // accumulate while disabled.
-  if (g_hapClearArmed && (millis()-g_haptic82Ms) > HAPTIC_CLEAR_IDLE_MS){ g_hapClearArmed=false; if(g_buzzFlood) hapticReinit(); }
+  if (g_hapClearArmed && (millis()-g_haptic82Ms) > HAPTIC_CLEAR_IDLE_MS){ g_hapClearArmed=false; if(!g_xbox && g_buzzFlood) hapticReinit(); }
 }

+ 2 - 1
OpenPuck/haptics.h

@@ -96,7 +96,8 @@ void rfConnFlushRelay(uint8_t ch, uint8_t s1);
 
 void hapticInit();   // boot reset: clear relay/active flags, arm the reconnect block
 void hapticTask();   // per-loop upkeep: link-edge markers + steam 0x82 quiet timeout + fires the scheduled re-init
-void hapticReinit(); // replay Steam's haptic-subsystem re-init to the controller -> clears a latched/stuck buzz
+void hapticReinit();      // 0x81-only buzz-clear, replayed by the 30s flood
+void hapticConnectInit(); // full 0x87 gyro-enable + haptic config, sent as a few connect shots (NOT flooded)
 // Called from rf_link the instant a controller (re)connect is detected (an F-reply after a gap): blocks haptic
 // relays for HAPTIC_RECONNECT_BLOCK_MS and schedules a re-init just after, to keep the freshly-booted
 // controller out of the degraded/latched haptic state. Reliable -- independent of hapticTask's link heuristic.

+ 5 - 2
OpenPuck/rf_link.cpp

@@ -49,6 +49,7 @@ static uint8_t  g_e3pid = 0;
 static uint32_t g_stPoll=0, g_stF1=0, g_stF3=0; static unsigned long g_stMs=0;
 static uint8_t  g_lastSeq=0; static uint32_t g_stNew=0;
 static uint32_t g_stCrc=0, g_stNoRx=0;
+static uint32_t g_imuDec=0, g_imuSkip=0;   // DIAG: per-second IMU-decode vs guard-skip counts
 static uint32_t g_chF1[3]={0,0,0};
 static uint32_t g_lastPollUs=0;
 static uint32_t g_connRx = 0;
@@ -197,8 +198,10 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t* payload, uint8_t plen, u
             // imuFrom45 would read STALE bytes past the received data and clobber g_in's gyro/accel -- the "gyro
             // randomly stops/glitches" bug, only visible in the stream modes (PS5/Switch) that emit g_in's IMU.
             // On a short frame we leave the last good IMU in place rather than injecting garbage.
-            if(tlen>=46 && (size_t)(idx+2)+46<=(size_t)end)
+            if(tlen>=46 && (size_t)(idx+2)+46<=(size_t)end){
               imuFrom45(rep, &g_in.ax,&g_in.ay,&g_in.az,&g_in.gx,&g_in.gy,&g_in.gz);
+              g_imuDec++;            // DIAG: IMU decoded this frame
+            } else g_imuSkip++;      // DIAG: 0x45 seen but IMU guard skipped (short frame) -> g_in gyro held
             // Mode-switch chord (all 4 back + face): don't leak the face press to the host. g_in.buttons stays
             // intact so the chord detector still fires; per-mode builders mask the same bits while back-4 held.
             if((bb&CHORD_BACK4)==CHORD_BACK4)
@@ -331,5 +334,5 @@ void rfLinkTask(){
     }
   }
   if (g_connOn && millis()-g_stMs>=1000){ g_f1ps=g_stF1; g_newps=g_stNew; g_pollsps=(uint16_t)g_stPoll;
-    g_pollPeriodUs = g_pollDtCnt ? (uint16_t)(g_pollDtSum/g_pollDtCnt) : 0; g_pollDtSum=0; g_pollDtCnt=0; if(Serial.availableForWrite()>70) Serial.printf("# stat polls=%lu/s F1=%lu/s new=%lu/s F3=%lu/s(v%d) e7b=%u crcfail=%lu noRx=%lu slot=%d\n",(unsigned long)g_stPoll,(unsigned long)g_stF1,(unsigned long)g_stNew,(unsigned long)g_stF3,(int8_t)g_connF3v,g_e7b,(unsigned long)g_stCrc,(unsigned long)g_stNoRx,g_connSlot); g_stPoll=0; g_stF1=0; g_stNew=0; g_stF3=0; g_stCrc=0; g_stNoRx=0; g_chF1[0]=g_chF1[1]=g_chF1[2]=0; g_stMs=millis(); }
+    g_pollPeriodUs = g_pollDtCnt ? (uint16_t)(g_pollDtSum/g_pollDtCnt) : 0; g_pollDtSum=0; g_pollDtCnt=0; if(Serial.availableForWrite()>70) Serial.printf("# stat polls=%lu/s F1=%lu/s new=%lu/s F3=%lu/s(v%d) e7b=%u crcfail=%lu noRx=%lu slot=%d | IMU dec=%lu skip=%lu gxyz=%d,%d,%d\n",(unsigned long)g_stPoll,(unsigned long)g_stF1,(unsigned long)g_stNew,(unsigned long)g_stF3,(int8_t)g_connF3v,g_e7b,(unsigned long)g_stCrc,(unsigned long)g_stNoRx,g_connSlot,(unsigned long)g_imuDec,(unsigned long)g_imuSkip,g_in.gx,g_in.gy,g_in.gz); g_stPoll=0; g_stF1=0; g_stNew=0; g_stF3=0; g_stCrc=0; g_stNoRx=0; g_imuDec=0; g_imuSkip=0; g_chF1[0]=g_chF1[1]=g_chF1[2]=0; g_stMs=millis(); }
 }

+ 7 - 3
OpenPuck/webusb_config.cpp

@@ -5,6 +5,7 @@
 #include "haptics.h"
 #include "puck_hid.h"
 #include "build_info.h"
+#include "triton.h"   // g_in -- DIAG: surface decoded IMU to the panel
 #include <Arduino.h>
 #include <string.h>
 
@@ -15,14 +16,14 @@ Adafruit_USBD_WebUSB usb_web;
 //                [qos][persistMode][chordBtn B][chordBtn X][chordBtn Y][pollsps_lo][pollsps_hi]
 //                [loopPeriod_lo][loopPeriod_hi][loopWorstIdx][loopWorstUs_lo][loopWorstUs_hi]
 //                [pollPeriod_lo][pollPeriod_hi][logEnabled][battery%][rssi|dBm|]
-//                [gitDirty][gitHash 12B ASCII, NUL-padded][buzzFlood]
-#define WB_PAYLEN 52
+//                [gitDirty][gitHash 12B ASCII, NUL-padded][buzzFlood][g_in.gx s16][g_in.gy s16][g_in.gz s16]
+#define WB_PAYLEN 58
 static void webusbSendBlob(){
   if(!usb_web.connected()) return;
   bool up = (g_connSlot>=0 && (millis()-g_connReplyMs) < 300);
   uint8_t p[2+WB_PAYLEN];
   p[0]=0xA5; p[1]=WB_PAYLEN;
-  p[2]=5;                          // protocol version (5 = +buzzFlood at [53]; 4 = git provenance; 3 = battery/rssi)
+  p[2]=6;                          // protocol version (6 = +decoded IMU gyro at [54..59]; 5 = buzzFlood; 4 = git)
   p[3]=g_usbMode; p[4]=(uint8_t)g_mDiv; p[5]=(uint8_t)g_mFric; p[6]=0 /*rsvd: ex-padSmooth*/; p[7]=g_abSwap;
   p[8]=g_back[0]; p[9]=g_back[1]; p[10]=g_back[2]; p[11]=g_back[3];
   p[12]=(g_connSlot>=0)?(uint8_t)g_connSlot:0xFF;
@@ -47,6 +48,9 @@ static void webusbSendBlob(){
   memset(&p[41],0,12);                                                 // 12B ASCII git hash, NUL-padded
   { const char* h=OPK_GIT_HASH; for(uint8_t i=0;i<12 && h[i];i++) p[41+i]=(uint8_t)h[i]; }
   p[53]=g_buzzFlood?1:0;                                               // connect-time buzz-clear flood enabled?
+  p[54]=(uint8_t)g_in.gx; p[55]=(uint8_t)(g_in.gx>>8);                 // DIAG: decoded gyro (does g_in update in PS5/Switch?)
+  p[56]=(uint8_t)g_in.gy; p[57]=(uint8_t)(g_in.gy>>8);
+  p[58]=(uint8_t)g_in.gz; p[59]=(uint8_t)(g_in.gz>>8);
   usb_web.write(p,sizeof p); usb_web.flush();
 }
 #if OPK_LOG

+ 9 - 1
docs/index.html

@@ -89,6 +89,7 @@
         <div class="stat logonly"><div class="k">Slowest stage</div><div class="v" id="stWorst">—</div></div>
         <div class="stat logonly"><div class="k">Poll period act/want</div><div class="v" id="stPollPer">—</div></div>
         <div class="stat"><div class="k">Build (git)</div><div class="v" id="stBuild">—</div></div>
+        <div class="stat"><div class="k">Gyro (g_in)</div><div class="v" id="stGyro">—</div></div>
       </div>
       <p class="note logonly">Polls/s ≈ 250 expected; it's capped by Loop period (≈4000 µs needed). "Slowest stage" is the loop section eating the most time per iteration.</p>
       <div class="row" style="margin:10px 0 0"><button id="hapClear">Clear stuck buzz</button>
@@ -97,7 +98,7 @@
       <div class="row" style="margin:10px 0 0"><label>Connect-time flood</label><button id="buzzFlood">off</button>
         <span class="note" style="flex:1">Floods the buzz-clear re-init at 10&nbsp;Hz for 30&nbsp;s after every connect — the proven fix for the connect-time stuck buzz. <strong>On by default</strong>; only turn it off if you're testing. Persists across reboots.</span>
       </div>
-      <div class="row" style="margin:10px 0 0"><button id="ctlrOff">Turn off controller (test)</button>
+      <div class="row" style="margin:10px 0 0"><button id="ctlrOff">Turn off controller</button>
         <span class="note" style="flex:1">Fires the controller power-off (0x9F "off!", x3) — the same path host-suspend uses. Watch the capture / link status to see if the controller actually disconnects.</span>
       </div>
       <div class="row" style="margin:18px 0 0;border-top:1px solid #4a2030;padding-top:14px">
@@ -249,6 +250,13 @@ function applyBlob(p){
   $("#stBuild").innerHTML = gitHash
     ? (gitHash + (gitDirty ? ' <span class="pill dn">dirty</span>' : ' <span class="pill up">clean</span>'))
     : "—";
+  // DIAG (protocol v6+): decoded g_in gyro at p[52..57]. If these change when you rotate the controller, g_in is
+  // updating (so a dead PS5 gyro is downstream: DS5 report packing / calibration). If frozen, the decode path is.
+  if(p.length>57){
+    const s16=(lo,hi)=>{let v=lo|(hi<<8);return v&0x8000?v-0x10000:v;};
+    const gx=s16(p[52],p[53]), gy=s16(p[54],p[55]), gz=s16(p[56],p[57]);
+    $("#stGyro").textContent = `${gx}, ${gy}, ${gz}`;
+  }
   // sliders (only if not being dragged)
   setSlider("mDiv",mDiv); setSlider("mFric",mFric);
   $("#abSwap").textContent = abSwap? "on":"off"; $("#abSwap").classList.toggle("active",!!abSwap);