feat(hid): device-path addressing to flash a specific bootloader among identical ones#275
Conversation
…g identical ones Multiple PIC32 HID bootloaders are identical (same VID/PID, and the bootloader exposes no serial), so VID/PID first-match cannot target a specific one. Add path-based addressing so a caller can hold/flash one exact device: - IHidTransport.ConnectByPathAsync/ConnectByPath open the one device at a given path. HidLibraryTransport refactors the shared enumerate/select/open body into ConnectMatchingDeviceAsync; ConnectAsync's behavior is preserved. - FirmwareUpdateService.UpdateFirmwareAsync gains an optional targetDevicePath (additive, after CancellationToken, mirroring UpdateWifiModuleAsync's skipVersionCheck/CA1068 trade-off). When set, WaitForBootloader matches that exact path and the connect uses ConnectByPath; null preserves first-match. DevicePath is sourced identically (HidSharp) by the discovery enumerator and the transport, so a path from one matches the other. Enables the desktop app-global watcher to hold ALL detected bootloaders and flash the one the user selected. Tests: 3 transport (match-by-path, no-match IOException, empty-path ArgumentException) + 1 flash path-targeting; full suite 1276 passed / 2 skipped. Claude-Session: https://claude.ai/code/session_01V5TeJuikExLKXzxvYcQNxB
PR Summary by QodoAdd HID device-path targeting for flashing a specific bootloader Description
Diagram
High-Level Assessment
Files changed (6)
|
Code Review by Qodo
1.
|
…idation & diagnostics Addresses all 4 review findings on PR #275: - Bugs 1 & 2 (breaking public API): keep the original 4-parameter IFirmwareUpdateService.UpdateFirmwareAsync UNCHANGED and add targeting as a non-breaking default-interface-method overload; make IHidTransport.ConnectByPathAsync/ConnectByPath default interface methods (throwing default) so existing implementers and callers of the prior surface are unaffected. Side benefit: the desktop's existing 4-arg Moq setups stay valid (no CS0854). - Bug 3 (unvalidated target): fast-fail ArgumentException on a whitespace targetDevicePath instead of polling to the WaitingForBootloader timeout. - Bug 4 (diagnosability): include the requested target path in the WaitingForBootloader timeout message. Tests: +1 (whitespace validation); full suite 1277 passed / 2 skipped. Claude-Session: https://claude.ai/code/session_01V5TeJuikExLKXzxvYcQNxB
Addressed all 4 review findings (commit 3683475)1 & 2 — Breaking public API (
3 — Unvalidated target path: 4 — Timeout hides target path: the Tests: +1 (whitespace validation); full suite 1277 passed / 2 skipped / 0 failed, clean build under |
|
/improve |
|
/agentic_review |
PR Code Suggestions ✨Warning
|
|||||||||||
|
Code review by qodo was updated up to the latest commit 3683475 |
Windows HID device paths are case-insensitive and casing can vary across enumerations; match paths with OrdinalIgnoreCase in both ConnectByPathAsync and the bootloader wait (consistent with the serial-filter comparison). Real device paths never differ only by case, so this cannot mis-target. Declined the redundant path re-validation suggestion (importance 2): the enumerated device is already matched by targetDevicePath in WaitForBootloaderDeviceAsync, so re-checking before connect adds nothing. Tests: +1 (case-insensitive match). Claude-Session: https://claude.ai/code/session_01V5TeJuikExLKXzxvYcQNxB
/improve pass 2 (commit baf1a3c)
|
|
/improve |
|
/agentic_review |
PR Code Suggestions ✨Warning
No code suggestions found for the PR. |
|
Code review by qodo was updated up to the latest commit baf1a3c |
agentic_review flagged the OrdinalIgnoreCase added in pass 2 as imposing Windows-specific casing semantics on the cross-platform HID layer. Revert to Ordinal in both ConnectByPathAsync and the bootloader wait: a device path is an OS identifier (case-sensitive on Linux/macOS) and in this flow always originates from the same in-process HidSharp enumeration the caller used to obtain it, so casing is consistent and case-sensitive matching is correct. Removed the case-insensitive test accordingly. Claude-Session: https://claude.ai/code/session_01V5TeJuikExLKXzxvYcQNxB
pass 3 ($(SHA))
This supersedes the pass-2 |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit b47a7e3 |
…omment The Ordinal path-matching comments said the path comes from 'the same in-process HidSharp enumeration'; on macOS enumeration is IOKit via IHidPlatform. Reword to 'the same in-process HID enumeration (via IHidPlatform)' and drop 'always'. No behavior change. Claude-Session: https://claude.ai/code/session_01V5TeJuikExLKXzxvYcQNxB
|
pass 4 (eaabb15): ✅ Fixed the misleading HidSharp comment — reworded to 'the same in-process HID enumeration (via IHidPlatform)' (macOS uses IOKit, not HidSharp) and dropped 'always'. Comment-only; Ordinal matching unchanged. |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit eaabb15 |
What
Adds path-based HID addressing so a caller can hold and flash one specific PIC32 bootloader when several identical ones are connected.
Multiple bootloaders are indistinguishable by VID/PID (all
04D8:003C) and the bootloader exposes no serial, soConnectAsync's VID/PID first-match cannot target a particular device. The only discriminator is the USB device path (already carried onHidDeviceInfo.DevicePath).Changes
IHidTransport.ConnectByPathAsync/ConnectByPath— open the one device at a given path.HidLibraryTransportrefactors the shared enumerate → select → open body intoConnectMatchingDeviceAsync;ConnectAsync's behavior (exception types, ordering, disposed/cancellation checks, already-connected early-return, unused-candidate disposal) is preserved.FirmwareUpdateService.UpdateFirmwareAsyncgains an optionaltargetDevicePath(additive, afterCancellationToken, mirroringUpdateWifiModuleAsync'sskipVersionCheckCA1068 trade-off). When set,WaitForBootloaderDeviceAsyncmatches that exact path and the connect usesConnectByPathAsync; when null, single-device first-match behavior is unchanged.DevicePathis sourced identically (HidSharpdevice.DevicePath) by the discovery enumerator (HidLibraryDeviceEnumerator) and the transport (HidLibraryPlatform), so a path obtained from discovery matchesConnectByPath.Why
Enables the daqifi-desktop app-global bootloader watcher to hold all detected bootloaders at once (each held exclusively by path) and flash the one the user selects.
Tests
IOException; empty/whitespace path →ArgumentException.targetDevicePath→ connects by that exact path, not first-match.TreatWarningsAsErrors.Backward-compatible:
targetDevicePathdefaults to null, the interface additions are consumed (not implemented) by daqifi-desktop, and no in-repoIHidTransportcaller changes behavior. Reviewed adversarially (3 lenses → per-finding verification); no blocking findings.🤖 Generated with Claude Code