feat(firmware): split WiFi update planning from execution (closes #143)#198
feat(firmware): split WiFi update planning from execution (closes #143)#198cptkoolbeenz wants to merge 7 commits into
Conversation
Adds a public CheckWifiFirmwareStatusAsync planning method and a skipVersionCheck parameter on UpdateWifiModuleAsync so callers (typically desktop UI) can make the version decision once and run the flash without triggering Core's hidden second probe. The previous flow conflated two responsibilities in UpdateWifiModuleAsync: deciding whether the device was already current, then executing the flash. Desktop's workaround was to query the version separately, then route the flash through an adapter that intentionally hid ILanChipInfoProvider so Core would skip its internal probe — an API-shape workaround the issue explicitly called out. API additions: - WifiFirmwareStatus record + WifiFirmwareStatusReason enum exposing the chip info, latest release, and categorical decision so callers can distinguish "definitely up to date" from "couldn't check". - IFirmwareUpdateService.CheckWifiFirmwareStatusAsync — pure planning method that does NOT mutate service state (no Complete transition, no progress events). Callers own their own logging / UI flow. - IFirmwareUpdateService.UpdateWifiModuleAsync gains skipVersionCheck: false default. Existing call sites unchanged; callers that just made the decision pass true to bypass. Implementation pulls the data-gathering out of IsWifiFirmwareUpToDateAsync into CheckWifiFirmwareStatusCoreAsync; the legacy method now delegates to it and only adds the side effects (Complete state + progress) needed by the all-in-one caller path. Test plan: 4 new tests cover the planning method's three primary outcomes (UpToDate / UpdateAvailable / DeviceDoesNotSupportLanQuery) and the skipVersionCheck pathway. 894/896 (+4 net new); 2 skips are real-hardware. All 47 WiFi-related tests pass.
Move skipVersionCheck before cancellationToken in UpdateWifiModuleAsync — .NET CA1068 says CancellationToken should be the last parameter. Update the one test using positional CancellationToken.None to match the new signature (named arg works either way).
|
/improve |
|
/agentic_review |
Code Review by Qodo
Context used 1.
|
Review Summary by QodoSplit WiFi firmware update planning from execution
WalkthroughsDescription• Add public CheckWifiFirmwareStatusAsync planning method for WiFi firmware status inspection • Introduce WifiFirmwareStatus record and WifiFirmwareStatusReason enum for detailed decision reporting • Add skipVersionCheck parameter to UpdateWifiModuleAsync to bypass internal version probe • Refactor version-checking logic into reusable CheckWifiFirmwareStatusCoreAsync helper • Add 4 new tests covering planning method outcomes and skip-check pathway Diagramflowchart LR
A["Caller"] -->|"CheckWifiFirmwareStatusAsync"| B["CheckWifiFirmwareStatusCoreAsync"]
B -->|"returns WifiFirmwareStatus"| C["Caller decides"]
C -->|"UpdateWifiModuleAsync<br/>skipVersionCheck=true"| D["RunWifiUpdateAsync"]
D -->|"skips probe"| E["Flash device"]
E -->|"Complete"| F["Done"]
File Changes1. src/Daqifi.Core/Firmware/WifiFirmwareStatus.cs
|
Code Review by Qodo
1.
|
PR Code Suggestions ✨Latest suggestions up to 2496323 Warning
Previous suggestionsSuggestions up to commit df91d5c
Suggestions up to commit b076e5f
Suggestions up to commit 1ccb31b
✅ Suggestions up to commit f3a37ec
|
|||||||||||||||||||||||||||||||||||||||||||||||
…use parsed Version Two findings, both importance 8: 1. Race condition: CheckWifiFirmwareStatusAsync and UpdateWifiModuleAsync both run SCPI text exchanges on the same transport. Without serialization, two concurrent callers would interleave on the wire and either corrupt the consumer swap or get partial replies. Now acquires _operationLock directly (without going through RunExclusiveAsync's Idle-state check, since a status probe is read-only and should still be available to UI mid-update). 2. Redundant version parsing: latestWifi.Version is already a typed FirmwareVersion populated by FirmwareDownloadService, but the code re-parsed latestWifi.TagName via IsWifiVersionCurrent. Use the typed value directly to avoid the extra parse and any divergence from canonical (e.g., differing tag-prefix conventions). The now- unused IsWifiVersionCurrent helper is dead-coded out.
|
/improve |
|
/agentic_review |
|
Persistent review updated to latest commit 1ccb31b |
|
Persistent suggestions updated to latest commit 1ccb31b |
…oken at 4th position
Earlier code-review pass moved skipVersionCheck before cancellationToken
to satisfy CA1068 ("CancellationToken should be last"). Qodo correctly
flagged this as source-breaking — any existing positional caller passing
CancellationToken as the 4th argument would no longer compile.
Reverted: cancellationToken stays at 4th position; skipVersionCheck
goes after it as a 5th optional. Suppressed CA1068 with a comment
explaining the additivity-over-style trade-off.
|
/improve |
|
/agentic_review |
|
Persistent review updated to latest commit bba154a |
PR Code Suggestions ✨Warning
No code suggestions found for the PR. |
…ncy guard Qodo flagged a real deadlock: UpdateWifiModuleAsync holds _operationLock while synchronously firing progress.Report. If a handler calls CheckWifiFirmwareStatusAsync from inside that callback, WaitAsync would block waiting for the lock its own caller's flow already holds — SemaphoreSlim is not re-entrant. Added AsyncLocal<bool> _isInsideOperation flag, set true by both lock acquisition sites (CheckWifiFirmwareStatusAsync and RunExclusiveAsync). CheckWifiFirmwareStatusAsync now skips the lock acquisition when the flag is already true on the current async flow — safe because the outer flow already provides exclusive device-I/O access. AsyncLocal flows through await resumptions on different threads, so this catches re-entrancy even when the inner call resumes on a different thread than the outer (mirrors the AsyncLocal pattern from PR #196's ExecuteTextCommandAsync hardening). Regression test added: invokes CheckWifiFirmwareStatusAsync from inside a progress.Report callback during UpdateWifiModuleAsync. Without the guard the test hangs on the deadlocked semaphore; with the guard the inner call returns immediately and the update completes normally.
|
/improve |
|
/agentic_review |
|
Persistent review updated to latest commit b076e5f |
|
Persistent suggestions updated to latest commit b076e5f |
|
/improve |
|
/agentic_review |
|
Persistent review updated to latest commit df91d5c |
|
Persistent suggestions updated to latest commit df91d5c |
Wrap the reentrant-callback regression test in WaitAsync(30s) so a regression of the AsyncLocal re-entrancy guard fails fast with a clear "deadlocked" message instead of stalling the xunit run waiting for the global per-test budget.
|
/improve |
|
/agentic_review |
|
Persistent review updated to latest commit 2496323 |
|
Persistent suggestions updated to latest commit 2496323 |
Summary
Adds a public
CheckWifiFirmwareStatusAsyncplanning method and askipVersionCheckparameter onUpdateWifiModuleAsyncso callers (typically desktop UI) can make the version decision once and run the flash without triggering Core's hidden second probe.The previous flow conflated two responsibilities — desktop's workaround was to query the version separately, then route the flash through an adapter that hid
ILanChipInfoProviderso Core would skip its internal probe. The issue explicitly called this out as an API-shape workaround.API additions
WifiFirmwareStatusrecord +WifiFirmwareStatusReasonenum exposing chip info, latest release, and a categorical decision so callers distinguish "definitively up to date" from "couldn't check".IFirmwareUpdateService.CheckWifiFirmwareStatusAsync— pure planning method (does NOT mutate service state, noCompletetransition, no progress events).IFirmwareUpdateService.UpdateWifiModuleAsyncgainsskipVersionCheck: falsedefault. Existing callers unchanged; callers that just made the decision passtrue.Implementation
Pulls data-gathering out of
IsWifiFirmwareUpToDateAsyncintoCheckWifiFirmwareStatusCoreAsync. The legacy method delegates to the new core helper and only adds the side effects (Completestate + progress) needed by the all-in-one caller path. Core remains the source of truth for version comparison (IsWifiVersionCurrent).Test plan
UpToDate/UpdateAvailable/DeviceDoesNotSupportLanQuery) and theskipVersionCheckpathwayUpdateWifiModuleAsynctests still pass (no behavior change for current callers)Closes #143