Bläddra i källkod

Merge remote-tracking branch 'origin/main' into disabling-haptics-in-emulated-modes

copilot-swe-agent[bot] 3 månader sedan
förälder
incheckning
0d05b655c5
4 ändrade filer med 87 tillägg och 30 borttagningar
  1. 46 26
      .github/workflows/build.yml
  2. 3 3
      .github/workflows/release.yml
  3. 9 1
      OpenPuck/radio.cpp
  4. 29 0
      OpenPuck/rf_link.cpp

+ 46 - 26
.github/workflows/build.yml

@@ -6,18 +6,18 @@ on:
       - main
     tags:
       - '[0-9]*.[0-9]*.[0-9]*'
-  # PRs get the same gate -- without this the checks only ran after merge.
+  # Build every PR head commit and publish UF2 artifacts for reviewers.
   pull_request:
-  pull_request_review:
-    types:
-      - submitted
+
+concurrency:
+  group: build-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
 
 permissions:
   contents: read
 
 jobs:
   compile:
-    if: github.event_name != 'pull_request_review' || github.event.review.state == 'approved'
     runs-on: ubuntu-latest
 
     steps:
@@ -25,7 +25,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           fetch-depth: 0
-          ref: ${{ github.event.pull_request.head.sha || github.event.review.pull_request.head.sha || github.sha }}
+          ref: ${{ github.event.pull_request.head.sha || github.sha }}
 
       - name: Set up Arduino CLI
         uses: arduino/setup-arduino-cli@v2
@@ -50,8 +50,8 @@ jobs:
       - name: Resolve build version
         id: build_version
         env:
-          PR_NUMBER: ${{ github.event.pull_request.number || github.event.review.pull_request.number || '' }}
-          HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.review.pull_request.head.sha || github.sha }}
+          PR_NUMBER: ${{ github.event.pull_request.number || '' }}
+          HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
         run: |
           set -eu
           SEMVER_RE='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
@@ -64,14 +64,21 @@ jobs:
           if [ -z "$BASE_VERSION" ]; then
             BASE_VERSION="0.9.1"
           fi
+          SHORT_SHA="$(git rev-parse --short=8 "${HEAD_SHA}")"
+          PR_NUMBER_OR_ZERO="${PR_NUMBER:-0}"
 
-          if [ "${{ github.event_name }}" = "pull_request" ] || [ "${{ github.event_name }}" = "pull_request_review" ]; then
-            SHORT_SHA="$(git rev-parse --short=8 "${HEAD_SHA}")"
-            VERSION="${BASE_VERSION}-pr${PR_NUMBER}+${SHORT_SHA}"
+          if [ "${{ github.event_name }}" = "pull_request" ]; then
+            VERSION="${BASE_VERSION}-pr${PR_NUMBER_OR_ZERO}+${SHORT_SHA}"
+            ARTIFACT_VERSION="${BASE_VERSION}-pr${PR_NUMBER_OR_ZERO}-${SHORT_SHA}"
           else
             VERSION="${BASE_VERSION}"
+            ARTIFACT_VERSION="${BASE_VERSION}-${SHORT_SHA}"
           fi
 
+          echo "base_version=$BASE_VERSION" >> "$GITHUB_OUTPUT"
+          echo "pr_number=$PR_NUMBER_OR_ZERO" >> "$GITHUB_OUTPUT"
+          echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
+          echo "artifact_version=$ARTIFACT_VERSION" >> "$GITHUB_OUTPUT"
           echo "version=$VERSION" >> "$GITHUB_OUTPUT"
 
       - name: Generate version header
@@ -114,21 +121,34 @@ jobs:
         run: |
           find build/openpuck build/reversepuck -maxdepth 1 -type f -print | sort
 
-      - name: Upload build artifacts
+      - name: Upload OpenPuck UF2 artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: OpenPuck-${{ steps.build_version.outputs.artifact_version }}-standard.uf2
+          retention-days: 7
+          if-no-files-found: error
+          path: build/openpuck/OpenPuck.ino.uf2
+
+      - name: Upload OpenPuck HEX artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: OpenPuck-${{ steps.build_version.outputs.artifact_version }}-standard.hex
+          retention-days: 7
+          if-no-files-found: error
+          path: build/openpuck/OpenPuck.ino.hex
+
+      - name: Upload ReversePuckFirmware UF2 artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: ReversePuckFirmware-${{ steps.build_version.outputs.artifact_version }}.uf2
+          retention-days: 7
+          if-no-files-found: error
+          path: build/reversepuck/ReversePuckFirmware.ino.uf2
+
+      - name: Upload ReversePuckFirmware HEX artifact
         uses: actions/upload-artifact@v4
         with:
-          name: openpuck-firmware
+          name: ReversePuckFirmware-${{ steps.build_version.outputs.artifact_version }}.hex
           retention-days: 7
-          path: |
-            build/openpuck/*.uf2
-            build/openpuck/*.bin
-            build/openpuck/*.hex
-            build/openpuck/*.elf
-            build/openpuck/*.map
-            build/openpuck/*.zip
-            build/reversepuck/*.uf2
-            build/reversepuck/*.bin
-            build/reversepuck/*.hex
-            build/reversepuck/*.elf
-            build/reversepuck/*.map
-            build/reversepuck/*.zip
+          if-no-files-found: error
+          path: build/reversepuck/ReversePuckFirmware.ino.hex

+ 3 - 3
.github/workflows/release.yml

@@ -157,7 +157,7 @@ jobs:
 
           find build/openpuck build/openpuck-recovery \
             -maxdepth 1 -type f \
-            \( -name '*.uf2' -o -name '*.zip' \) \
+            \( -name '*.uf2' -o -name '*.hex' \) \
             -print | while read -r f; do
             # Keep only the extension; the arduino base name (OpenPuck.ino.*)
             # is redundant once we prefix with OpenPuck-<version>-<flavor>.
@@ -173,7 +173,7 @@ jobs:
 
           find build/reversepuck \
             -maxdepth 1 -type f \
-            \( -name '*.uf2' -o -name '*.zip' \) \
+            \( -name '*.uf2' -o -name '*.hex' \) \
             -print | while read -r f; do
             ext="${f##*.}"
             cp "$f" "release_assets/ReversePuckFirmware-${VERSION}.${ext}"
@@ -199,7 +199,7 @@ jobs:
             - `OpenPuck-*-standard.*` — puck firmware (standard)
             - `OpenPuck-*-factory-reset.*` — puck firmware (factory reset)
             - `ReversePuckFirmware-*.*` — reverse-puck firmware (controller emulator)
-            - `.zip` files for Adafruit DFU tool
             - `.uf2` files for drag-and-drop flashing (BOOT/UF2 drive)
+            - `.hex` files for direct flashing workflows
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 9 - 1
OpenPuck/radio.cpp

@@ -126,8 +126,16 @@ void rfConfig(uint8_t ch)
 	NRF_RADIO->EVENTS_DISABLED = 0;
 	NRF_RADIO->MODE = ((uint32_t)g_mode << RADIO_MODE_MODE_Pos);
 	NRF_RADIO->FREQUENCY = ch;
+	// +8dBm = the nRF52840's max TX power (was 0dBm). The link margin is thin on
+	// the Pro Micro's PCB-trace antenna (RSSI reads ~20dB below a real puck), and
+	// 2.4GHz is crowded, so weak polls the controller can't hear -> no reply ->
+	// the reply gaps that read as intermittent disconnects. This only lifts the
+	// puck->controller side (poll/beacon reach); the controller's own TX (the F1
+	// reply) is fixed -- but the poll MUST land for the controller to answer, so
+	// the extra 8dB of headroom directly cuts missed-poll dropouts. USB-powered,
+	// so the higher current draw is free.
 	NRF_RADIO->TXPOWER =
-		(RADIO_TXPOWER_TXPOWER_0dBm << RADIO_TXPOWER_TXPOWER_Pos);
+		(RADIO_TXPOWER_TXPOWER_Pos8dBm << RADIO_TXPOWER_TXPOWER_Pos);
 #if defined(RADIO_MODECNF0_RU_Fast)
 	NRF_RADIO->MODECNF0 = (RADIO_MODECNF0_RU_Fast << RADIO_MODECNF0_RU_Pos);
 #endif

+ 29 - 0
OpenPuck/rf_link.cpp

@@ -941,6 +941,35 @@ void rfLinkTask()
 		rfConnStep();
 	} // connected-mode: poll controller, read input
 
+	// Release stale input on a per-slot link-drop edge. g_in[s] is refreshed ONLY
+	// by the 0x45 decode on a fresh reply, so once a controller goes silent the
+	// last-decoded input is frozen in place: STREAM modes (Switch Hori/Pro, PS5,
+	// DS4-gyro) keep emitting it every cycle, and PUSH modes (Steam/puck, Xbox)
+	// leave the host holding their last report -- a feathered trigger, or a
+	// "release" frame that was the one lost in the gap, stays asserted for the
+	// whole outage (the stuck / false-trigger symptom, most visible on the analog
+	// triggers). On the up->down edge, zero g_in[s] (neutralizes the stream modes)
+	// and push one synthetic neutral 0x45 through the active push mode's normal
+	// build path (neutralizes Steam/puck -- including held lizard keys/mouse --
+	// and Xbox; a no-op for stream modes, which don't override onReport45). The
+	// next real 0x45 on reconnect refills everything. PS3 already streams its own
+	// neutral when no slot is live, so it needs nothing here.
+	{
+		static bool wasUp[NSLOT] = {};
+		static const uint8_t neutral45[46] = { 0x45 };
+		for (int s = 0; s < NSLOT; s++) {
+			bool up = g_slot[s].used && g_connReplyMs[s] != 0 &&
+				  (millis() - g_connReplyMs[s] < 300u);
+			if (wasUp[s] && !up) {
+				memset(&g_in[s], 0, sizeof g_in[s]);
+				if (g_active)
+					g_active->onReport45(s, neutral45, true,
+							     sizeof neutral45);
+			}
+			wasUp[s] = up;
+		}
+	}
+
 	// ---- RF stall self-heal -------------------------------------------------------------------------------
 	// Field wedge: the controllers stay powered and show a SOLID (connected) light, the puck keeps issuing E3
 	// polls + E1 beacons (radio TX is provably alive -- a power-cycled controller still re-adopts the session