Kaynağa Gözat

Merge branch 'main' into copilot/add-ps3-mode-gyro-haptics

Jari 4 ay önce
ebeveyn
işleme
97fe35b27a
5 değiştirilmiş dosya ile 57 ekleme ve 25 silme
  1. 10 7
      OpenPuck/config.cpp
  2. 6 0
      OpenPuck/config.h
  3. 7 0
      OpenPuck/haptics.cpp
  4. 15 10
      OpenPuck/webusb_config.cpp
  5. 19 8
      docs/index.html

+ 10 - 7
OpenPuck/config.cpp

@@ -21,12 +21,12 @@ int g_mDiv = 64, g_mFric = 94;
 
 // Per-type button config. back default {5,6,7,8} = L4->LB R4->RB L5->L3 R5->R3 (0..11 buttons, 12..15 D-pad,
 // 16/17 PS touch/mute, 18 Switch Capture). Switch differs: QAM defaults to Capture(18), A/B swap on, and
-// trackpad haptics off. qamMap 0 = unmapped (hardcoded per-mode behavior).
+// trackpad haptics off. qamMap 0 = unmapped (hardcoded per-mode behavior). ledBright 0 = no override.
 TypeCfg g_type[ET_COUNT] = {
-	/* ET_XBOX   */ { { 5, 6, 7, 8 }, 0, 0, 1 },
-	/* ET_SWITCH */ { { 5, 6, 7, 8 }, 18, 1, 0 },
-	/* ET_DS4    */ { { 5, 6, 7, 8 }, 0, 0, 1 },
-	/* ET_DS5    */ { { 5, 6, 7, 8 }, 0, 0, 1 },
+	/* ET_XBOX   */ { { 5, 6, 7, 8 }, 0, 0, 1, 0 },
+	/* ET_SWITCH */ { { 5, 6, 7, 8 }, 18, 1, 0, 0 },
+	/* ET_DS4    */ { { 5, 6, 7, 8 }, 0, 0, 1, 0 },
+	/* ET_DS5    */ { { 5, 6, 7, 8 }, 0, 0, 1, 0 },
 };
 uint8_t g_etype = ET_NONE;
 
@@ -35,6 +35,7 @@ uint8_t g_abSwap = 0;
 uint8_t g_back[4] = { 5, 6, 7, 8 };
 uint8_t g_qamMap = 0;
 uint8_t g_padHaptics = 1;
+uint8_t g_ledBright = 0;
 
 void applyActiveType()
 {
@@ -48,6 +49,7 @@ void applyActiveType()
 		g_qamMap = 0;
 		g_abSwap = 0;
 		g_padHaptics = 1;
+		g_ledBright = 0;
 		return;
 	}
 	const TypeCfg &t = g_type[g_etype];
@@ -56,6 +58,7 @@ void applyActiveType()
 	g_qamMap = t.qamMap;
 	g_abSwap = t.abSwap;
 	g_padHaptics = t.padHaptics;
+	g_ledBright = t.ledBright;
 }
 // rumble strength % (200 = double); adjustable from the WebUSB panel
 uint8_t g_rumbleScale = 200;
@@ -65,8 +68,8 @@ uint8_t g_rumbleScale = 200;
 const uint32_t g_pollUs = POLL_US_DEFAULT;
 
 #define CFG_FILE "/cfg.bin"
-// bumped (per-type TypeCfg blob replaces scalar abSwap/back/qamMap): old cfg ignored -> clean defaults on first boot
-#define CFG_MAGIC 0xCA
+// TypeCfg size changed; old flash format is incompatible -> clean defaults on first boot after upgrade
+#define CFG_MAGIC 0xCB
 struct Cfg {
 	uint8_t magic, mode, mDiv, mFric, rsvd0, pollU100, persistMode,
 		bootMode, chordBtn[3], rumbleScale;

+ 6 - 0
OpenPuck/config.h

@@ -116,11 +116,15 @@ extern int g_mDiv, g_mFric; // xbox/lizard mouse sensitivity divisor / friction%
 // (0..15 standard, 16=PS Touch Click, 17=PS5 Mute, 18=Switch Capture/Screenshot). qamMap = QAM (3 dots)
 // physical button -> same code space (0 = default/unmapped). abSwap = swap A/B and X/Y (Nintendo layout).
 // padHaptics = 1 keeps the controller's autonomous trackpad haptics, 0 disables them for this type.
+// ledBright = LED brightness sent to the controller on connect: 0 = no override (controller default),
+// 1-100 = brightness %. Steam sets brightness each session; emulated modes never do, so the controller
+// comes up at full brightness. Setting a value here preserves the preferred brightness across mode switches.
 struct TypeCfg {
 	uint8_t back[4];
 	uint8_t qamMap;
 	uint8_t abSwap;
 	uint8_t padHaptics;
+	uint8_t ledBright;
 };
 extern TypeCfg g_type[ET_COUNT];
 extern uint8_t
@@ -133,6 +137,8 @@ extern uint8_t g_back[4];
 extern uint8_t g_qamMap;
 extern uint8_t
 	g_padHaptics; // 1 = trackpad haptics on (default), 0 = disabled for the active type
+// LED brightness for the active emulated type (0 = no override, 1-100 = brightness %)
+extern uint8_t g_ledBright;
 
 // Copy g_type[g_etype] into the live mirrors above (safe defaults when g_etype == ET_NONE).
 void applyActiveType();

+ 7 - 0
OpenPuck/haptics.cpp

@@ -432,6 +432,13 @@ void hapticReinit(uint8_t slot)
 	// above, which would otherwise re-enable it). Default-on types send "enable"; Switch (padHaptics=0)
 	// disables. Inert until the setting id is captured.
 	hapticSetPadEnabled(slot, g_padHaptics != 0);
+	// Apply the configured LED brightness for the active emulated type. Steam sets the
+	// brightness each session; emulated modes never do, so the controller comes up at
+	// full brightness. 0 = no override (preserve controller default).
+	if (g_etype < ET_COUNT && g_ledBright > 0) {
+		uint8_t pl[3] = { 0x2D, g_ledBright, 0x00 };
+		relayEnqueue(0x87, pl, sizeof pl, slot);
+	}
 }
 void hapticInit()
 {

+ 15 - 10
OpenPuck/webusb_config.cpp

@@ -11,17 +11,17 @@
 
 Adafruit_USBD_WebUSB usb_web;
 
-// blob payload = [ver=9][mode][mDiv][mFric][qamMap(active)][abSwap(active)][back0..3(active)][connSlot(0xFF=none)][linkUp]
+// blob payload = [ver=10][mode][mDiv][mFric][qamMap(active)][abSwap(active)][back0..3(active)][connSlot(0xFF=none)][linkUp]
 //                [f1ps_lo][f1ps_hi][pollU100][newps_lo][newps_hi][e7b][relayOp][relaySub][fwdNewOnly]
 //                [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][rumbleScale][swPro120][swGyroScale10][raw accel ax ay az 3x s16 LE]
 //                [bondedCount][slot0_up][slot0_batt][slot0_rssi]...[slot3_up][slot3_batt][slot3_rssi]
-//                [v9: per-type cfg, 4x7B: ET_XBOX/SWITCH/DS4/DS5 each {back0..3, qam, abSwap, padHaptics}]
-// p[6]/p[7]/p[8..11] mirror the ACTIVE type (legacy display). v9 extends to 101 bytes (103 total incl header);
+//                [v10: per-type cfg, 4x8B: ET_XBOX/SWITCH/DS4/DS5 each {back0..3, qam, abSwap, padHaptics, ledBright}]
+// p[6]/p[7]/p[8..11] mirror the ACTIVE type (legacy display). v10 extends to 105 bytes (107 total incl header);
 // browser reads with transferIn(128) to span the two USB-FS packets.
-#define WB_PAYLEN 101
+#define WB_PAYLEN 105
 static void webusbSendBlob()
 {
 	if (!usb_web.connected())
@@ -35,8 +35,8 @@ static void webusbSendBlob()
 	p[0] = 0xA5;
 	p[1] = WB_PAYLEN;
 
-	// protocol version (9 = +per-type cfg; 8 = +per-slot link status; 7 = +raw accel; 6 = +swPro120/gyroScale)
-	p[2] = 9;
+	// protocol version (10 = +ledBright per type; 9 = +per-type cfg; 8 = +per-slot link status; 7 = +raw accel; 6 = +swPro120/gyroScale)
+	p[2] = 10;
 	p[3] = g_usbMode;
 	p[4] = (uint8_t)g_mDiv;
 	p[5] = (uint8_t)g_mFric;
@@ -115,9 +115,9 @@ static void webusbSendBlob()
 			p[65 + s * 3] = g_linkRssi[s];
 		}
 	}
-	// per-emulated-type button config (protocol v9): 4 types x 7 bytes from p[75]
+	// per-emulated-type button config (protocol v10): 4 types x 8 bytes from p[75]
 	for (int et = 0; et < ET_COUNT; et++) {
-		uint8_t *q = &p[75 + et * 7];
+		uint8_t *q = &p[75 + et * 8];
 		q[0] = g_type[et].back[0];
 		q[1] = g_type[et].back[1];
 		q[2] = g_type[et].back[2];
@@ -125,6 +125,7 @@ static void webusbSendBlob()
 		q[4] = g_type[et].qamMap;
 		q[5] = g_type[et].abSwap;
 		q[6] = g_type[et].padHaptics;
+		q[7] = g_type[et].ledBright;
 	}
 	usb_web.write(p, sizeof p);
 	usb_web.flush();
@@ -254,8 +255,8 @@ void webusbPoll()
 
 				// every settable field persists (poll rate is no longer settable)
 				bool persist = true;
-				// per-type cfg writes (protocol v9): field = 40 + et*8 + k, k: 0..3 back, 4 qam, 5 abSwap,
-				// 6 padHaptics. Edits g_type[et]; refresh the live mirrors if it's the active type.
+				// per-type cfg writes (protocol v10): field = 40 + et*8 + k, k: 0..3 back, 4 qam, 5 abSwap,
+				// 6 padHaptics, 7 ledBright. Edits g_type[et]; refresh the live mirrors if it's the active type.
 				if (f >= 40 && f < 40 + ET_COUNT * 8) {
 					uint8_t et = (uint8_t)((f - 40) / 8),
 						k = (uint8_t)((f - 40) % 8);
@@ -270,6 +271,10 @@ void webusbPoll()
 						else if (k == 6)
 							g_type[et].padHaptics =
 								v ? 1 : 0;
+						else if (k == 7)
+							g_type[et].ledBright =
+								v > 100 ? 100 :
+									  v;
 						if (et == g_etype)
 							applyActiveType();
 					}

+ 19 - 8
docs/index.html

@@ -190,7 +190,7 @@ function log(m){ const l=$("#log"); l.textContent=(new Date().toLocaleTimeString
 
 // Per-emulated-type config (must match firmware ET_* order: Xbox=0, Switch=1, DS4=2, DS5=3). Each type lists
 // only the remap targets that exist on that controller. Field id sent to firmware = 40 + et*8 + k
-// (k: 0..3 back paddles, 4 QAM, 5 A/B-swap, 6 trackpad-haptics). Blob v9 reads them back at p[75 + et*7 + ...].
+// (k: 0..3 back paddles, 4 QAM, 5 A/B-swap, 6 trackpad-haptics, 7 LED brightness). Blob v10 reads them back at p[73 + et*8 + ...].
 const DPAD = {12:"D-pad Up",13:"D-pad Down",14:"D-pad Left",15:"D-pad Right"};
 const TYPE_DEFS = [
   {key:"XBOX", name:"Xbox", labels:{1:"A",2:"B",3:"X",4:"Y",5:"LB",6:"RB",7:"L3",8:"R3",9:"Back",10:"Start",11:"Guide",19:"LT",20:"RT",...DPAD}},
@@ -201,7 +201,7 @@ const TYPE_DEFS = [
 // Which emulated type a USB mode belongs to (mirror of firmware etypeForMode); -1 = puck mode (no type).
 function etypeForMode(m){ switch(m){case 1:return 0; case 2:case 4:return 1; case 6:case 8:return 2; case 5:case 7:return 3; default:return -1;} }
 const BACK_LABELS = ["L4 (back upper-left)","R4 (back upper-right)","L5 (back lower-left)","R5 (back lower-right)"];
-const typeEls = []; // per-type {sec, abSwap, pad, back[4], qam}
+const typeEls = []; // one entry per TYPE_DEFS: {sec, tab, activeDot, back[], qam, abSwap, pad, led, ledV}
 function mkSelect(def, includeNone){
   const sel=document.createElement("select");
   if(includeNone){ const o=document.createElement("option"); o.value=0; o.textContent="— none —"; sel.appendChild(o); }
@@ -229,7 +229,7 @@ function setTab(et){
     tab.onclick=()=>setTab(et); bar.appendChild(tab);
     // panel
     const sec=document.createElement("div"); sec.style.cssText="background:#0e1017;border:1px solid #232a3a;border-radius:8px;padding:12px 14px;display:none";
-    const rec={sec,tab,activeDot:tab.querySelector(".active-dot"),back:[],qam:null,abSwap:null,pad:null};
+    const rec={sec,tab,activeDot:tab.querySelector(".active-dot"),back:[],qam:null,abSwap:null,pad:null,led:null,ledV:null};
     // A/B swap + trackpad haptics toggles
     const tog=document.createElement("div"); tog.className="row";
     tog.innerHTML='<label>A/B + X/Y swap</label>';
@@ -254,6 +254,16 @@ function setTab(et){
       const sel=mkSelect(def,false); row.appendChild(sel); sec.appendChild(row);
       sel.addEventListener("change",()=>setField(40+et*8+4, +sel.value));
       rec.qam=sel; }
+    // LED brightness
+    { const row=document.createElement("div"); row.className="row";
+      const lab=document.createElement("label"); lab.textContent="LED brightness"; row.appendChild(lab);
+      const sl=document.createElement("input"); sl.type="range"; sl.min=0; sl.max=100; sl.step=5; row.appendChild(sl);
+      const vspan=document.createElement("span"); vspan.className="val"; row.appendChild(vspan);
+      function fmtLed(v){ return (+v===0)?"Auto":v+"%"; }
+      sl.addEventListener("input",()=>{ vspan.textContent=fmtLed(sl.value); });
+      sl.addEventListener("change",()=>setField(40+et*8+7, +sl.value));
+      sec.appendChild(row);
+      rec.led=sl; rec.ledV=vspan; }
     host.appendChild(sec); typeEls.push(rec);
   });
   setTab(0);
@@ -467,17 +477,18 @@ function applyBlob(p){
     : "—";
   setSlider("mDiv",mDiv); setSlider("mFric",mFric); setSlider("rumble",rumbleScale);
   $("#persistMode").textContent = persistMode? "on":"off"; $("#persistMode").classList.toggle("active",!!persistMode);
-  // per-type button config (blob v9): 4 types x 7 bytes. readBlob() strips the 2-byte [0xA5][len] header, so
-  // the firmware's payload byte 75 lands at JS index 73 here.
-  if(p.length>=73+TYPE_DEFS.length*7){
+  // per-type button config (blob v10): 4 types x 8 bytes. readBlob() strips the 2-byte [0xA5][len] header, so
+  // the firmware's buffer index 75 lands at JS index 73 here.
+  if(p.length>=73+TYPE_DEFS.length*8){
     const activeEt=etypeForMode(mode);
     typeEls.forEach((rec,et)=>{
-      const q=73+et*7;
-      const back=[p[q],p[q+1],p[q+2],p[q+3]], qam=p[q+4], ab=p[q+5], pad=p[q+6];
+      const q=73+et*8;
+      const back=[p[q],p[q+1],p[q+2],p[q+3]], qam=p[q+4], ab=p[q+5], pad=p[q+6], led=p[q+7];
       rec.back.forEach((sel,i)=>{ if(document.activeElement!==sel) sel.value=back[i]; });
       if(document.activeElement!==rec.qam) rec.qam.value=qam;
       rec.abSwap.textContent=ab?"on":"off"; rec.abSwap.classList.toggle("active",!!ab);
       rec.pad.textContent=pad?"on":"off"; rec.pad.classList.toggle("active",!!pad);
+      if(document.activeElement!==rec.led){ rec.led.value=led; rec.ledV.textContent=(led===0)?"Auto":led+"%"; }
       // dot marks the type matching the current puck mode
       rec.activeDot.style.display = (et===activeEt) ? "" : "none";
     });