refactor(device): extract raw-command session interpretation into a collaborator (part of #344) - #439
Conversation
…ollaborator (part of #344) The #379 session tracking — recognizing a streaming session driven through the raw Send path rather than the typed API — sat inline in DaqifiStreamingDevice as three private methods and three private constants, mixing the decision (what does this command text mean?) with the effects (re-anchor the session, flip the flag, assign channel state under the device's lock). The decision is pure text-and-arithmetic, so it moves to SessionCommandInterpreter: a command plus the sampling ceiling in, a typed SessionCommandEffect out. The effects stay on the device, which is the only thing that owns them. No public API change, no behavior change. The ceiling is still read exactly once per tracked command and handed in, preserving the property that tracking can never throw out of a Send whose command has already reached the device. Pure refactor: DaqifiStreamingDevice.cs drops from 1385 to 1310 lines.
|
/agentic_review |
PR Summary by QodoExtract raw SCPI session-command interpretation into SessionCommandInterpreter
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTo customize comments, go to the Qodo configuration screen, or learn more in the docs. |
|
Ready for review. Qodo came back clean (0 bugs, 0 rule violations, 0 requirement gaps, no inline threads). CI Worth noting alongside #436: that PR extracted the device-administration block from the same file, and this one extracts the session-command interpretation. They touch disjoint regions, so the two composed without conflict. With both in, Not merging — leaving this for your review. |
Problem
DaqifiStreamingDeviceis one of the two god-classes #344 exists to decompose. The block this PR takes on is the #379 session tracking — the code that keeps Core's view of a streaming session in step when the session is driven through the rawSendpath instead of the typed API (which is exactly what the example CLI does).It sat inline as three private methods and three private constants, and it mixed two different kinds of thing:
IsStreaming, assignIChannel.IsEnabledunder the device's channels lock.Only the second kind actually needs a device. The first is pure text-and-arithmetic, but because it lived on the device it could only be tested by constructing a scripted device and driving
Sendthrough it.Fix
Device/Internal/SessionCommandInterpreternow makes the decision: a command string plus the device's sampling ceiling go in, a typedSessionCommandEffectcomes out (None/StopStreaming/StartStreaming(frequency)/UnusableStreamingStart(rejectedRate)/SetAdcEnableMask(mask)).DaqifiStreamingDevice.TrackSessionCommandbecomes a switch that applies the effect.UnusableStreamingStartis its own kind rather than folded intoNoneso the device can still trace the rejection with the text that caused it — the diagnostic that existed before is preserved, not dropped.The single-read property is preserved deliberately.
MaxSamplingRateis a mutable public property, so the ceiling is read once by the device and handed to the interpreter, and the validated value comes back to be assigned to the backing field rather than through the validatingStreamingFrequencysetter. Validating against one read and assigning through a setter that takes another is what would let a concurrent capabilities update throw out of aSendwhose command has already reached the device — the existingTrackingARate_NeverThrowsOutOfSend_WhileCapabilitiesChangeUnderneathtest drives that window and stays green.No public API change and no behavior change.
DaqifiStreamingDevice.csdrops from 1385 to 1310 lines.Tests
SessionCommandInterpreterTests, covering the decision directly: blank and unrelated commands, case-insensitive matching (SCPI short/long forms differ only in case), surrounding whitespace, the stop-vs-start prefix ambiguity (both startSYSTem:St, so the check order is load-bearing), every unusable-rate shape, an inclusive ceiling, theMath.Max(1, ...)fallback for an uninitializedMaxSamplingRate, mask parsing includinguint.MaxValueand the unparseable cases, and a guard that the constants still match whatScpiMessageProduceractually emits.DeviceReconnectTestscoverage of the effects is unchanged and green — it is what pins that the device still applies the decision correctly.Daqifi.Mcp.Tests).Bench validation
Run on the real Nyquist over USB (fw 3.7.2). The example CLI drives
ENAble:VOLTage:DC,SYSTem:StartStreamDataandSYSTem:StopStreamDatathrough rawSend, so a bench stream exercises this interpreter end to end rather than only in unit tests:3at 200 Hz → two analog values per frame; mask1at 100 Hz → one. The raw enable mask is still being applied to the channel set.--min-samples 300gate passed, exit 0. Clean start and stop, no discarded frames or decode failures reported.Non-destructive throughout: no SD, firmware, reboot or power-cycle operations.
Part of #344.
Not merging — for review.