ソースを参照

Rumble should be fixed

Jariullah Safi 4 ヶ月 前
親
コミット
0b3281d054

+ 27 - 4
OpenPuck/haptics.cpp

@@ -28,6 +28,8 @@ void hapticSendShutdown(){
 
 static unsigned long g_haptic82Ms = 0;     // millis of last 0x82 haptic OUTPUT relayed (Steam mode)
 static bool          g_haptic82On = false; // a non-zero 0x82 haptic is currently active (awaiting host stop)
+static unsigned long g_rumble80Ms = 0;     // millis of last translated host rumble (0x80)
+static bool          g_rumble80On = false; // Steam/Triton rumble is latched on until an explicit zero report
 static unsigned long g_reinitAt = 0;       // when to fire the next post-reconnect haptic re-init (0 = none scheduled)
 static uint8_t       g_reinitLeft = 0;     // how many re-init shots remain in this connect window
 static bool          g_hapClearArmed=false;// haptic activity happened -> arm a clear once it goes idle (catches a
@@ -58,7 +60,7 @@ bool relayEnqueue(uint8_t rid, const uint8_t* payload, uint8_t plen){
   g_rqHead = nx;
   // Any haptic relay (Steam OR Xbox rumble OR test) arms the idle-clear and refreshes its timer -- so the
   // during-use buzz gets cleared in EVERY USB mode, not just Steam. The re-init's own 0x81/0x87 don't match.
-  if (rid==0x82){ g_haptic82Ms = millis(); g_hapClearArmed = true; }
+  if (rid==0x82 || rid==0x80){ g_haptic82Ms = millis(); g_hapClearArmed = true; }
   __set_PRIMASK(pm); return true;
 }
 
@@ -126,11 +128,12 @@ static bool haptic82PayloadOn(const uint8_t* p, uint16_t n){
   for(uint16_t i=2;i<n;i++) if(p[i]) return true;   // observed form is [01 01 gain], but treat any trailing non-zero as active
   return false;
 }
-static void hapticCancelPendingOn(){   // void queued 0x82-ON entries (stale Steam haptics across a reconnect)
+static void hapticCancelPendingOn(){   // void queued ON entries (stale Steam haptics / translated rumble across a reconnect)
   uint32_t pm = __get_PRIMASK(); __disable_irq();
   for(uint8_t i=g_rqTail; i!=g_rqHead; i=rqNext(i)){
     RelayMsg &m = g_rq[i];
     if(m.rid==0x82){ bool on=false; for(uint8_t j=2;j<m.len;j++) if(m.data[j]){on=true;break;} if(on) m.rid=0; }
+    if(m.rid==0x80){ bool on=false; for(uint8_t j=0;j<m.len;j++) if(m.data[j]){on=true;break;} if(on) m.rid=0; }
   }
   __set_PRIMASK(pm);
 }
@@ -144,6 +147,25 @@ void haptic82HostReport(const uint8_t* p, uint16_t n){
   // "click"/buzz that the real puck never produces. (Connect-time clearing still runs in hapticTask().)
   g_haptic82On = haptic82PayloadOn(p,n);
 }
+bool hapticSteamRumble(uint16_t lowFreq, uint16_t highFreq){
+  bool on = lowFreq || highFreq;
+  if(on && haptic82Blocked()) return false;       // same settle gate as native Steam haptics
+  if(!on && !hapticLinkUp()) return false;
+
+  // SDL's current Steam/Triton structs define output report 0x80 as:
+  //   type, uint16 intensity, {uint16 speed, int8 gain} left/right.
+  // We map conventional gamepad low/high-frequency motors to left/right speeds and use max as intensity.
+  uint16_t intensity = lowFreq > highFreq ? lowFreq : highFreq;
+  uint8_t p[9];
+  p[0] = on ? 0x04 : 0x00;   // haptic_type_t::HAPTIC_TYPE_RUMBLE; 0 is the off/zero report
+  p[1] = (uint8_t)(intensity & 0xFF); p[2] = (uint8_t)(intensity >> 8);
+  p[3] = (uint8_t)(lowFreq   & 0xFF); p[4] = (uint8_t)(lowFreq   >> 8); p[5] = 0;
+  p[6] = (uint8_t)(highFreq  & 0xFF); p[7] = (uint8_t)(highFreq  >> 8); p[8] = 0;
+  if(!relayEnqueue(0x80, p, sizeof p)) return false;
+  g_rumble80Ms = millis();
+  g_rumble80On = on;
+  return true;
+}
 // Queue a pending test-haptic / stop relay (runs inside the poll cadence -- never at raw loop rate).
 void rfConnQueueHapticRelay(){
   if(relayPending()) return;            // host relays first; injectables wait for an idle cycle
@@ -217,7 +239,7 @@ void hapticReinit(){
   relayEnqueue(0x81, T81B, sizeof T81B);
 }
 void hapticInit(){
-  g_rqHead = g_rqTail = 0; g_haptic82On=false;
+  g_rqHead = g_rqTail = 0; g_haptic82On=false; g_rumble80On=false;
   g_hapticBlockUntil = millis() + HAPTIC_RECONNECT_BLOCK_MS;   // boot: block stale Steam 0x82 until link stable
   // NO fabricated stop burst. USB capture proves Steam only ever sends 0x82 [01 01 f7] pulses -- never a
   // zero-gain [01 01 00] "stop". Injecting our invented stop frame (which the real puck never sends) at
@@ -231,7 +253,7 @@ void hapticInit(){
 // link-up edge. Idempotent -- safe to call repeatedly.
 void hapticOnReconnect(){
   g_hapticBlockUntil = millis() + HAPTIC_RECONNECT_BLOCK_MS;   // no haptics relayed for the next 3s
-  g_haptic82On = false;
+  g_haptic82On = false; g_rumble80On = false;
   hapticCancelPendingOn();                                     // drop any haptic ON queued before the link came up
   // Be PROACTIVE: start re-initing the haptic engine right after the link is up and repeat across the whole
   // settle window, instead of waiting for the block to end. The brief connect buzz engages early (during the
@@ -268,6 +290,7 @@ void hapticTask(){
   // are one-shot pulses, so firing a 0x82-zero ~HAPTIC_QUIET_MS after a swipe ends is the extra end-of-movement
   // click the real puck doesn't make. Steam forwards its own stop for any sustained haptic.
   if (!g_xbox && g_haptic82On && millis()-g_haptic82Ms > HAPTIC_QUIET_MS) g_haptic82On=false;
+  if (g_rumble80On && millis()-g_rumble80Ms > 2500u) hapticSteamRumble(0, 0);
   // Haptic activity has gone idle for a while -> fire one re-init to clear any latch it left behind (the buzz
   // that engages during/after use and won't self-clear, incl. after a mode switch). Fires only after a quiet
   // gap, so it never interrupts active haptics; the brightness-less re-init is silent (settings, no play, no

+ 3 - 2
OpenPuck/haptics.h

@@ -1,6 +1,6 @@
 // haptics.h -- host -> controller relay queue (haptics + settings) + the watchdogs that stop a stuck buzz.
 //
-// Steam (and legacy XInput rumble) send OUTPUT/feature reports the real dongle forwards to the controller as
+// Steam and translated host rumble send OUTPUT/feature reports the real dongle forwards to the controller as
 // a SET sub-TLV inside the E3 poll. We do the same: handleSet()/the console enqueue with relayEnqueue(), and
 // rfConnFlushRelay() emits ONE entry per poll cycle (rf_link.cpp), never at raw loop rate.
 //
@@ -43,7 +43,7 @@
 // cmd 0x9F, payload "off!" -- captured from the real puck). Sent as a small burst because the RF relay is NO-ACK.
 #define HAPTIC_SHUTDOWN_SHOTS 3u
 
-// ---- relay queue (written by puck_hid.cpp, mode_xinput.cpp, serial_console.cpp; drained by rf_link.cpp) ----
+// ---- relay queue (written by puck_hid.cpp, mode_*.cpp, serial_console.cpp; drained by rf_link.cpp) ----
 // Enqueue one host->controller report: rid = report/command id, payload = the bytes AFTER [cmd][len] (what
 // goes on the air). ISR-safe (brief PRIMASK critical section). Returns false (dropped) when the ring is full.
 bool relayEnqueue(uint8_t rid, const uint8_t* payload, uint8_t plen);
@@ -75,6 +75,7 @@ bool hapticLinkUp();
 bool haptic82Blocked();
 bool hapticRelaySlotOk(int slot);
 void haptic82HostReport(const uint8_t* p, uint16_t n);
+bool hapticSteamRumble(uint16_t lowFreq, uint16_t highFreq);   // queue Steam/Triton output report 0x80 rumble
 
 // queue + flush the pending host/test/stop relay inside the poll cadence (called from rf_link).
 // rfConnFlushRelay's s1 must carry a PID distinct from the GET poll that follows it (rf_link cycles the shared

+ 12 - 0
OpenPuck/mode_hidgyro.cpp

@@ -2,6 +2,7 @@
 #include "triton.h"
 #include "gamepad_util.h"
 #include "config.h"
+#include "haptics.h"
 #include <Adafruit_TinyUSB.h>
 #include <Arduino.h>
 #include <string.h>
@@ -24,6 +25,16 @@ static const uint8_t GYRO_HID_DESC[]={
 static unsigned long g_gyroLastMs=0;
 static Adafruit_USBD_HID g_hidGyro;
 
+static void hidGyroSet(uint8_t rid, hid_report_type_t type, uint8_t const* b, uint16_t n){
+  if(type!=HID_REPORT_TYPE_OUTPUT || n<1) return;
+  uint8_t id; const uint8_t* p; uint16_t pn;
+  if(rid==0){ id=b[0]; p=b+1; pn=(uint16_t)(n-1); }
+  else      { id=rid;  p=b;   pn=n;               }
+  // DS4 USB effects: report 0x05, magic at byte 0, effects block starts at byte 3.
+  if(id!=0x05 || pn<5) return;
+  hapticSteamRumble((uint16_t)p[4]*257u, (uint16_t)p[3]*257u);   // DS4: left=low, right=high
+}
+
 static void hidGyroBuild(uint8_t out[63]){
   uint32_t b=psButtonsFromSteam(g_in.buttons);
   bool lTouch=(b&TB_LPADT)||(b&TB_LPADC), rTouch=(b&TB_RPADT)||(b&TB_RPADC);
@@ -59,6 +70,7 @@ void HidGyroController::begin(){
   USBDevice.setManufacturerDescriptor("Sony Computer Entertainment");
   USBDevice.setProductDescriptor("Wireless Controller");
   g_hidGyro.enableOutEndpoint(true);
+  g_hidGyro.setReportCallback(NULL, hidGyroSet);
   g_hidGyro.setReportDescriptor(GYRO_HID_DESC, sizeof GYRO_HID_DESC);
   g_hidGyro.setPollInterval(4);
   g_hidGyro.begin();

+ 11 - 0
OpenPuck/mode_ps5.cpp

@@ -2,6 +2,7 @@
 #include "triton.h"
 #include "gamepad_util.h"
 #include "config.h"
+#include "haptics.h"
 #include <Adafruit_TinyUSB.h>
 #include <Arduino.h>
 #include <string.h>
@@ -32,6 +33,15 @@ static const uint8_t PS5_HID_DESC[]={
 static unsigned long g_ps5LastMs=0;
 static Adafruit_USBD_HID g_ps5;
 
+static void ps5Set(uint8_t rid, hid_report_type_t type, uint8_t const* b, uint16_t n){
+  if(type!=HID_REPORT_TYPE_OUTPUT || n<1) return;
+  uint8_t id; const uint8_t* p; uint16_t pn;
+  if(rid==0){ id=b[0]; p=b+1; pn=(uint16_t)(n-1); }
+  else      { id=rid;  p=b;   pn=n;               }
+  if(id!=0x02 || pn<4) return;
+  hapticSteamRumble((uint16_t)p[3]*257u, (uint16_t)p[2]*257u);   // DualSense: left=low, right=high
+}
+
 static void ps5Build(uint8_t out[63]){
   uint32_t b=psButtonsFromSteam(g_in.buttons);
   bool lTouch=(b&TB_LPADT)||(b&TB_LPADC), rTouch=(b&TB_RPADT)||(b&TB_RPADC);
@@ -61,6 +71,7 @@ void Ps5Controller::begin(){
   USBDevice.setManufacturerDescriptor("Sony Interactive Entertainment");
   USBDevice.setProductDescriptor("DualSense Wireless Controller");
   g_ps5.enableOutEndpoint(true);
+  g_ps5.setReportCallback(NULL, ps5Set);
   g_ps5.setReportDescriptor(PS5_HID_DESC, sizeof PS5_HID_DESC);
   g_ps5.setPollInterval(4);
   g_ps5.begin();

+ 16 - 1
OpenPuck/mode_switch_pro.cpp

@@ -2,6 +2,7 @@
 #include "triton.h"
 #include "gamepad_util.h"
 #include "config.h"
+#include "haptics.h"
 #include <Adafruit_TinyUSB.h>
 #include <Arduino.h>
 #include <string.h>
@@ -50,6 +51,17 @@ static unsigned long g_swProLastMs=0;
 static Adafruit_USBD_HID g_swPro;
 static uint8_t g_swProReportMode=0;   // 0 until the host's subcommand 0x03 selects 0x30 -> THEN we stream input
 static const uint8_t JC_MAC[6]={0x7C,0xBB,0x8A,0x00,0x00,0x01};   // synthetic but stable controller MAC
+static uint16_t jcRumbleAmp(const uint8_t r[4]){
+  // Nintendo's 4-byte rumble encoding is frequency+amplitude packed through tables. For translation to the
+  // Steam controller, use the amplitude-bearing fields directly and scale them into a 16-bit motor speed.
+  if((r[0]==0x00 && r[1]==0x01 && r[2]==0x40 && r[3]==0x40) || (r[0]==0x00 && r[1]==0x00 && r[2]==0x01 && r[3]==0x40)) return 0;
+  uint16_t amp = ((uint16_t)(r[1] & 0xFE) << 8) | r[3];
+  return amp ? amp : 0x0800;
+}
+static void jcRumble(const uint8_t* p, uint16_t pn){
+  if(pn<9) return;                       // [timer][left rumble x4][right rumble x4]
+  hapticSteamRumble(jcRumbleAmp(p+1), jcRumbleAmp(p+5));
+}
 static int jcStick12(int16_t v, bool inv){   // steam int16 (center 0) -> 12-bit (center 0x800), clamped
   int a = 2048 + (inv ? -((int)v>>4) : ((int)v>>4));
   return a<0?0:(a>4095?4095:a);
@@ -199,9 +211,12 @@ static void jcSet(uint8_t rid, hid_report_type_t type, uint8_t const* b, uint16_
   }
   if(id==0x01){                        // [timer][rumble x8][subcmd][args...]
     if(pn<10) return;
+    jcRumble(p, pn);
     jcSubcmd(p[9], p+10, (pn>10)?(uint16_t)(pn-10):0);
+    return;
   }
-  // report 0x10 (rumble only) / 0x82: ignored
+  if(id==0x10){ jcRumble(p, pn); return; }
+  // report 0x82: ignored
 }
 
 void SwitchProController::begin(){

+ 10 - 13
OpenPuck/mode_xinput.cpp

@@ -25,8 +25,8 @@ enum { XB_DUP=0x0001,XB_DDOWN=0x0002,XB_DLEFT=0x0004,XB_DRIGHT=0x0008, XB_START=
 static uint8_t g_xiItf=0xFF, g_xiEpIn=0, g_xiEpOut=0;
 static uint8_t g_xiInBuf[32], g_xiOutBuf[32];
 
-// legacy XInput rumble strength from the host's OUT packet (0=off); drives the haptic relay
-static volatile uint8_t g_rumble = 0;
+// XInput rumble strengths from the host's OUT packet; translated to Steam/Triton report 0x80.
+static volatile uint16_t g_rumbleLow = 0, g_rumbleHigh = 0;
 static volatile unsigned long g_rumbleMs = 0;   // millis of last rumble OUT packet (stuck-rumble watchdog)
 #define RUMBLE_STUCK_MS 2500u   // release a held rumble if no OUT packet refreshes it for this long (covers a lost stop without cutting normal short rumbles)
 
@@ -54,7 +54,9 @@ static bool xi_xfer(uint8_t rhport,uint8_t ep,xfer_result_t res,uint32_t n){(voi
   if(ep==g_xiEpOut){
     // XInput rumble packet: [00][08][00][bigMotor][smallMotor][00][00][00]; LED pkt is [01][03][led]
     if(n>=5 && g_xiOutBuf[0]==0x00 && g_xiOutBuf[1]==0x08){
-      uint8_t big=g_xiOutBuf[3], sml=g_xiOutBuf[4]; g_rumble = big>sml?big:sml; g_rumbleMs = millis();   // stamp for the stuck-rumble watchdog
+      uint8_t big=g_xiOutBuf[3], sml=g_xiOutBuf[4];
+      g_rumbleLow=(uint16_t)big*257u; g_rumbleHigh=(uint16_t)sml*257u; g_rumbleMs = millis();   // stamp for the stuck-rumble watchdog
+      hapticSteamRumble(g_rumbleLow, g_rumbleHigh);
     }
     usbd_edpt_xfer(rhport,g_xiEpOut,g_xiOutBuf,sizeof g_xiOutBuf);    // re-arm OUT
   }
@@ -143,7 +145,7 @@ static void rfXboxMouse(const uint8_t* r){
 
 // ===================== IController =====================
 void XboxController::begin(){
-  g_rumble=0;
+  g_rumbleLow=g_rumbleHigh=0;
   USBDevice.setID(0x045E, 0x028E);   // device-level 045E:028E match -> Windows xusb / SDL / Linux xpad all bind it
   // bcdDevice 0x0115 (was 0x0114): Windows caches the config descriptor by VID:PID:bcdDevice, so any change to
   // this mode's interfaces (here: the wake-mouse interface) MUST bump bcdDevice or Windows serves a stale
@@ -160,15 +162,10 @@ void XboxController::onReport45(const uint8_t* rep, bool fresh, uint8_t bodyTlen
   (void)fresh; (void)bodyTlen;
   rfXboxGamepad(rep); rfXboxMouse(rep);   // standard gamepad + right-pad mouse (2nd interface)
 }
-// Legacy XInput rumble -> relay the haptic (0x82 [01 01 gain]) to the controller while the host commands it,
-// re-queued ~40/s like Steam's glide haptic so it sustains. Not active in the current HID Xbox presentation.
+// Lost-stop watchdog: Steam/Triton rumble is latched, so force a zero report if the host stops refreshing.
 void XboxController::task(){
-  if (g_rumble && millis()-g_rumbleMs > RUMBLE_STUCK_MS) g_rumble=0;
-  if (g_rumble && !relayPending() && g_connSlot>=0) {
-    static unsigned long lastRumble=0;
-    if (millis()-lastRumble>=25) { lastRumble=millis();
-      uint8_t gain = g_rumble<0x30?0x30:g_rumble;   // floor so low rumble is still feelable
-      uint8_t pl[3]={0x01,0x01,gain}; relayEnqueue(0x82,pl,3);
-    }
+  if ((g_rumbleLow || g_rumbleHigh) && millis()-g_rumbleMs > RUMBLE_STUCK_MS) {
+    g_rumbleLow=g_rumbleHigh=0;
+    hapticSteamRumble(0, 0);
   }
 }