Procházet zdrojové kódy

Delay haptic shutdown; add Steam chords & DFU docs

Introduce a delayed host-suspend shutdown timer (g_shutdownAt) and HAPTIC_SHUTDOWN_DELAY_MS to avoid transient suspend/resume from powering off the controller immediately; cancel timer on resume and send shutdown only after the delay. Add Steam-mode chord handling in mode_lizard (Steam+L5/R5 -> volume down/up via consumer control reports; Steam+X -> Win+Ctrl+O). Extend the HID descriptor to include a small consumer control report. Update BUILD_AND_DEPLOY.md to document adafruit-nrfutil and DFU (UF2/serial DFU) upload instructions.
Jariullah Safi před 4 měsíci
rodič
revize
6858aa61fa

+ 9 - 6
OpenPuck/haptics.cpp

@@ -6,6 +6,8 @@
 #include <Arduino.h>
 #include <string.h>
 
+static unsigned long g_shutdownAt = 0;   // delayed host-suspend shutdown; 0 = none pending
+
 uint8_t          g_relayOp  = 0xE3;   // E3 poll
 // relay sub-TLV TYPE byte. Vestigial now: rfConnFlushRelay derives the on-air type from the report id (see the
 // CONFIRMED split there -- 0x05 for actuators/haptics <0x87, 0x01 for config/settings/LED >=0x87). Still exposed
@@ -254,15 +256,16 @@ void hapticTask(){
     hapticReinit();
     g_reinitAt = (g_reinitLeft && --g_reinitLeft) ? (millis()+HAPTIC_REINIT_GAP_MS) : 0;
   }
-  // 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
-  // power), and we must NOT kill the controller then; it should only power off on a shutdown command or a real
-  // host sleep. VBUSDETECT is 1 while the cable still delivers 5V, 0 once unplugged. wasSusp starts true so a
+  // Controller power-off on host SLEEP: defer shutdown by HAPTIC_SHUTDOWN_DELAY_MS so a transient
+  // suspend/resume cycle during system wake doesn't kill the controller. If the bus comes back within
+  // the window the timer is cancelled.
+  // VBUSDETECT is 1 while the cable still delivers 5V, 0 once unplugged. wasSusp starts true so a
   // boot-into-suspended state never false-fires.
   static bool wasSusp=true; bool susp=USBDevice.suspended();
   bool vbus = (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
-  if (susp && !wasSusp && vbus) hapticSendShutdown();
+  if (susp && !wasSusp && vbus && !g_shutdownAt) g_shutdownAt = millis() + HAPTIC_SHUTDOWN_DELAY_MS;
+  if (wasSusp && !susp) g_shutdownAt = 0;      // bus resumed -> cancel pending shutdown
+  if (g_shutdownAt && susp && vbus && (int32_t)(millis() - g_shutdownAt) >= 0){ g_shutdownAt = 0; hapticSendShutdown(); }
   wasSusp=susp;
   // Steam-mode: host went quiet -> mark the 0x82 stream inactive. Do NOT synthesize a stop: trackpad haptics
   // are one-shot pulses, so firing a 0x82-zero ~HAPTIC_QUIET_MS after a swipe ends is the extra end-of-movement

+ 3 - 0
OpenPuck/haptics.h

@@ -41,7 +41,10 @@
 #define HAPTIC_CLEAR_IDLE_MS 1200u
 // Controller power-off: hapticSendShutdown() relays Steam's confirmed "turn off controller" command (feature-0x01
 // cmd 0x9F, payload "off!" -- captured from the real puck). Sent as a small burst because the RF relay is NO-ACK.
+// When triggered by host suspend, the shutdown is DELAYED so a transient suspend/resume cycle (common on system
+// wake) doesn't kill the controller -- the timer cancels if the bus resumes within the window.
 #define HAPTIC_SHUTDOWN_SHOTS 3u
+#define HAPTIC_SHUTDOWN_DELAY_MS 3000u
 
 // ---- relay queue (written by puck_hid.cpp, mode_xinput.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

+ 11 - 0
OpenPuck/mode_lizard.cpp

@@ -47,6 +47,17 @@ void rfLizard(const uint8_t* r, Adafruit_USBD_HID* mdev, Adafruit_USBD_HID* kdev
   LZK((b&TB_DLF)||sx<-12000, HID_KEY_ARROW_LEFT);
   LZK((b&TB_DRT)||sx> 12000, HID_KEY_ARROW_RIGHT);
   #undef LZK
+  // --- Steam chords: Steam+L5=vol down, Steam+R5=vol up, Steam+X=Win+Ctrl+O ---
+  { static bool prevL5=false, prevR5=false;
+    bool sh = (b & TB_STEAM) != 0;
+    bool nL5 = sh && (b & TB_L5), nR5 = sh && (b & TB_R5);
+    if (nL5 && !prevL5){ uint8_t cc=0x02; if(mdev->ready()) mdev->sendReport(0x03,&cc,1); }  // VolDown
+    if (nR5 && !prevR5){ uint8_t cc=0x01; if(mdev->ready()) mdev->sendReport(0x03,&cc,1); }  // VolUp
+    if (!nL5 && prevL5){ uint8_t cc=0x00; if(mdev->ready()) mdev->sendReport(0x03,&cc,1); }  // release
+    if (!nR5 && prevR5){ uint8_t cc=0x00; if(mdev->ready()) mdev->sendReport(0x03,&cc,1); }
+    prevL5=nL5; prevR5=nR5;
+    if(sh && (b & TB_X)){ mod=KEYBOARD_MODIFIER_LEFT_GUI|KEYBOARD_MODIFIER_LEFTCTRL; kc[0]=HID_KEY_O; kc[1]=0; kc[2]=0; kc[3]=0; kc[4]=0; kc[5]=0; nk=1; }
+  }
   static uint8_t pmod=0, pkc[6]={0,0,0,0,0,0};
   bool chg=(mod!=pmod); for(int i=0;i<6;i++) if(kc[i]!=pkc[i]) chg=true;
   if(chg){ pmod=mod; for(int i=0;i<6;i++) pkc[i]=kc[i];

+ 3 - 1
OpenPuck/puck_hid.cpp

@@ -39,7 +39,9 @@ static const uint8_t PUCK_HID_DESC[] = {
   0x3F,0x09,0x87,0x91,0x02,0x85,0x89,0x15,0x00,0x26,0xFF,0x00,0x75,0x08,0x95,0x3F,
   0x09,0x89,0x91,0x02,0x85,0x88,0x15,0x00,0x26,0xFF,0x00,0x75,0x08,0x95,0x3F,0x09,
   0x88,0x91,0x02,0x85,0x01,0x95,0x3F,0x09,0x01,0xB1,0x02,0x85,0x02,0x95,0x3F,0x09,
-  0x01,0xB1,0x02,0xC0
+  0x01,0xB1,0x02,0xC0,
+  0x05,0x0C,0x09,0x01,0xA1,0x01,0x85,0x03,0x15,0x00,0x25,0x01,0x09,0xE9,0x09,0xEA,
+  0x75,0x01,0x95,0x02,0x81,0x02,0x75,0x06,0x95,0x01,0x81,0x01,0xC0
 };
 
 static Adafruit_USBD_HID hid[NSLOT];

+ 31 - 0
docs/BUILD_AND_DEPLOY.md

@@ -14,6 +14,7 @@ Software:
 
 - `arduino-cli`
 - Adafruit nRF52 Arduino core
+- `adafruit-nrfutil` (Python package, for DFU packaging — see §4)
 - Chrome or Edge for the WebUSB app
 
 ## 2. Install Arduino CLI
@@ -37,6 +38,7 @@ Use [choco](https://chocolatey.org/) and do a `choco install arduino-cli`. If yo
 Run once on any platform:
 
 ```bash
+pip install adafruit-nrfutil         # DFU packaging helper (required by the Adafruit nRF52 build recipe)
 arduino-cli config init
 arduino-cli core update-index
 arduino-cli core install adafruit:nrf52 --additional-urls https://adafruit.github.io/arduino-board-index/package_adafruit_index.json
@@ -95,6 +97,35 @@ arduino-cli upload `
 
 Replace `COM5` with the actual board port.
 
+## 5b. Upload via DFU (nRF52840 UF2 bootloader)
+
+If the board has the **Adafruit nRF52 UF2 bootloader** (common on Pro Micro nRF52840 boards), you can upload by dragging the compiled `.uf2` file onto the board's mass-storage volume:
+
+1. **Double-tap the RST button** (or use `arduino-cli upload` once via serial to trigger the bootloader, then immediately hold the button). The board mounts as a **UF2BOOT** / **NRF52BOOT** drive.
+2. Locate the compiled `.uf2` file:
+
+   ```bash
+   # After a successful `arduino-cli compile`, find the .uf2 in the build directory:
+   ls /tmp/arduino/cores/adafruit_nrf52_adafruit52840/*.uf2
+   # or, on Windows, look in %TEMP%\arduino\...
+   ```
+
+3. **Copy the `.uf2` file** onto the UF2BOOT drive. The board auto-ejects and reboots with the new firmware.
+
+Alternatively, use the **adafruit-nrfutil DFU** Python tool with the board in DFU mode (bootloader LED pulsing):
+
+```bash
+# Enter DFU mode (double-tap RST). On Linux/macOS:
+adafruit-nrfutil --verbose dfu serial --package OpenPuck/OpenPuck.ino.adafruit_nrf52_feather52840.zip -p /dev/ttyACM0 -b 115200
+
+# On Windows (PowerShell):
+adafruit-nrfutil --verbose dfu serial --package OpenPuck/OpenPuck.ino.adafruit_nrf52_feather52840.zip -p COM5 -b 115200
+```
+
+Replace the port (`/dev/ttyACM0` / `COM5`) with the actual board port.
+
+> **Note:** The `.zip` package is generated automatically by `arduino-cli compile` when the Adafruit nRF52 core is used. If it is missing, ensure `adafruit-nrfutil` is installed and recompile.
+
 ## 6. Factory reset (erase persistent storage)
 
 Re-flashing firmware does **not** erase the board's internal LittleFS. The paired-controller bond (`bonds.bin`) and every saved setting (`cfg.bin`: USB mode, chord assignments, back-paddle map, mouse sensitivity) survive a fresh build and upload. To bring a board up in a truly clean state — a new unit, a hand-me-down with a stale bond, or a corrupted config — wipe the filesystem with one of: