Skip to content

Add USB hot-plug AutoFlasher with bootloader interception + portable mode#3720

Open
slinax wants to merge 1 commit into
ArduPilot:masterfrom
slinax:feature/auto-flasher-bootloader-intercept
Open

Add USB hot-plug AutoFlasher with bootloader interception + portable mode#3720
slinax wants to merge 1 commit into
ArduPilot:masterfrom
slinax:feature/auto-flasher-bootloader-intercept

Conversation

@slinax
Copy link
Copy Markdown

@slinax slinax commented May 7, 2026

Summary

Adds a QGroundControl-style firmware flasher that intercepts the STM32 bootloader on USB plug instead of requiring the user to pre-select a COM port. The user picks the firmware first; on plug, a WM_DEVICECHANGE listener (plus a 250 ms WMI safety poll) races to open the virtual COM port and run px4uploader.Uploader.identify() before the user firmware boots and grabs the link.

The motivating use case is the recurring "comms timeout" failure mode on Cube Black / Pixhawk 2.1 where the existing flow can't catch the bootloader window reliably.

Also adds a portable mode that redirects all user-writable state to <exe>\PortableData\ so this build can coexist with a regular Mission Planner install without polluting Documents\Mission Planner, %APPDATA%, or %PROGRAMDATA%.

What changed

New files (auto-included by the SDK-style csproj, no .csproj edit needed):

  • GCSViews/ConfigurationView/AutoFlasher/BootloaderTargets.cs — VID/PID table (3DR PX4, Cube Black/Orange/Orange+, ArduPilot generic, STM32 DFU)
  • GCSViews/ConfigurationView/AutoFlasher/UsbPlugListener.cs — native WM_DEVICECHANGE via hidden NativeWindow + 250 ms WMI safety poll, callbacks dispatched on the captured SynchronizationContext
  • GCSViews/ConfigurationView/AutoFlasher/AutoFlasherService.cs — orchestrator: on plug → 8-second bootloader window with port race + Uploader.identify() + Uploader.upload(fw)
  • GCSViews/ConfigurationView/AutoFlasher/AutoFlasherForm.cs — code-only WinForms UI (no Designer.cs / .resx churn)

Modified:

  • Program.cs — sets Settings.CustomUserDataDirectory and overrides APPDATA/LOCALAPPDATA at the very top of Start(), before any Settings.* call
  • GCSViews/ConfigurationView/ConfigFirmware.cs — adds an Auto-Flash (USB hot-plug) LinkLabel via the constructor

Why

The current Firmware.UploadPX4 path depends on AttemptRebootToBootloader() succeeding, which fails when MAVLink is already broken. By inverting the flow (firmware first, then watch for the plug event), we can catch the bootloader during the natural fresh-boot window after a physical replug — the same approach QGC uses.

Reviewer notes (please read)

  • This has not been tested against real hardware. The build compiles cleanly via MSBuild MissionPlanner.csproj /p:Configuration=Release, but I do not have a Pixhawk on hand to validate the timing of the bootloader race. Consider this an RFC/draft until someone with hardware can verify the 8-second window is enough for Cube Black specifically.
  • The MissionPlanner.sln build still depends on the mono submodule; this PR builds the main MissionPlanner.csproj directly, which is sufficient for the produced exe.
  • The LinkLabel is added at (8, 8) in code; on some screen sizes it may overlap an existing ImageLabel. Trivial to reposition once the layout is reviewed.
  • Defensive coding choices motivated by past Sequence contains no elements issues: FirstOrDefault on every VID/PID match, TryIdentify swallows UnauthorizedAccessException / IOException and retries other ports, WMI scans run on Task.Run (never on the message pump).
  • Portable mode is always on in this branch. If merged as-is it would change behavior for everyone — the maintainer may want to gate it behind a sentinel file (e.g. portable.txt in startup dir) before merging.

Test plan

  • Compile: MSBuild MissionPlanner.csproj /p:Configuration=Release (verified locally, exit 0)
  • Smoke launch: GUI starts, PortableData\Mission Planner\ is created next to the exe
  • Open Initial Setup → Install Firmware: confirm the new Auto-Flash (USB hot-plug) link is visible
  • Hardware test (not yet done): pick a .apj, click the link, replug a Cube Black, verify bootloader sync + flash within 8 s

🤖 Generated with Claude Code

Adds a QGroundControl-style firmware flasher that intercepts the STM32
bootloader on USB plug instead of requiring a pre-selected COM port.
The user picks the firmware first; on plug, a WM_DEVICECHANGE listener
(plus a 250ms WMI safety poll) triggers a race to open the virtual COM
port and run px4uploader.Uploader.identify() before user firmware boots.

Also forces portable mode at startup so this build cannot touch the
regular Mission Planner install (Documents\Mission Planner, AppData,
ProgramData) — all writes go to <exe>\PortableData.

New files (auto-included by the SDK-style csproj):
  GCSViews/ConfigurationView/AutoFlasher/BootloaderTargets.cs
  GCSViews/ConfigurationView/AutoFlasher/UsbPlugListener.cs
  GCSViews/ConfigurationView/AutoFlasher/AutoFlasherService.cs
  GCSViews/ConfigurationView/AutoFlasher/AutoFlasherForm.cs

Modified:
  Program.cs            - Settings.CustomUserDataDirectory + APPDATA
                          override at the very top of Start()
  ConfigFirmware.cs     - LinkLabel "Auto-Flash (USB hot-plug)" added
                          via constructor (no Designer/.resx churn)

Defensive coding choices:
- FirstOrDefault everywhere on VID/PID matching (avoids the recurring
  "Sequence contains no elements" exception).
- TryIdentify swallows UnauthorizedAccessException/IOException and
  retries other ports during the 8s bootloader window.
- WMI scans run on Task.Run, never on the message pump.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant