refactor(device): extract the USB stream-interface routing decision into a collaborator (part of #344) - #442
Conversation
…nto a collaborator (part of #344) DaqifiStreamingDevice.OnDeviceInitializingAsync carried the whole USB stream-routing policy inline: whether to route at all, how many times to retry a transient SCPI rejection, how long to settle between attempts, and how to turn a persistent rejection into a typed exception. None of that needed a device to decide. Move the decision into a new host-free collaborator, Device/Internal/UsbStreamInterfaceInitializer. It takes the two connection facts the decision depends on (is this USB, is this an observe-only session) plus a delegate that performs the send, so the policy is now testable without a device, a transport, or a wire. The effect stays on the device: the hook still builds the ExecuteTextCommandAsync call so the command goes out in text mode with the protobuf consumer stopped. No public surface changes and no IDeviceOperationHost additions. The existing DaqifiDeviceInitializeTests are left untouched on purpose -- they are the evidence that the extraction changed nothing end to end. DaqifiStreamingDevice.cs: 1196 -> 1144 lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
PR Summary by QodoRefactor: extract USB stream routing policy into UsbStreamInterfaceInitializer
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. Not merging — leaving the merge to you. State at head
This is the third of three open |
Part of #344. Another incremental slice of the
DaqifiStreamingDevicedecomposition — same shape as #439 and #441: host-free, noIDeviceOperationHostadditions, no public surface change.Problem
OnDeviceInitializingAsynccarried the entire USB stream-routing policy inline, mixed in with the send itself:ScpiInitializationErrorExceptionNone of that needs a device to decide, but all of it was only reachable through a connected device with a transport, so every test of the policy had to stand up a fake device end to end.
Fix
New
Device/Internal/UsbStreamInterfaceInitializer. It takes the two connection facts the decision depends on (isUsbConnection,preserveActiveStream) plus a delegate that performs the send.Decision moves out. Route-or-skip, retry budget, settle delay, error classification, and the typed throw.
Effect stays on the device. The hook still builds the
ExecuteTextCommandAsynccall, so the command goes out in text mode with the protobuf consumer stopped and any SCPI error response is captured rather than garbling the protobuf stream. The 500 ms response window stayed on the device too — it is a call-shape detail of the device's own helper, not part of the routing policy — but it is now a named constant instead of an inline literal.The documented deliberate behavior of not observing the cancellation token on the skip path is preserved and is now pinned by a test rather than only by a comment.
DaqifiStreamingDevice.cs: 1196 → 1144 lines.Conflict surface
Deliberately chosen to be disjoint from the two open slices. This PR's hunks are at L33-44 and L224-309; #440's are at L15-23 / L196-203 / L633-700 / L942, and #441's are at L515-629. No overlap, and no shared new files.
Testing
UsbStreamInterfaceInitializerTests. Full suite green on net9 + net10: 2653 → 2667 passed / 2 skipped on each (+23 Daqifi.Mcp.Tests on net9; Mcp is net9-only). 0 warnings. The baseline was measured on a cleanorigin/mainworktree, so the +14 is exact and nothing was lost.DaqifiDeviceInitializeTestsleft untouched on purpose — they are the evidence that the extraction changed nothing end to end.MaxRetriesto 0 and droppingpreserveActiveStreamfrom the gate made 7 of the 14 new tests fail, including the retry and Second connection to a streaming device silently stops the first session's stream (InitializeAsync sends StopStreaming unconditionally) #385 skip rules; restoring from a byte-identical backup was verified before committing. One test that had derived its expectation fromMaxRetries + 1(and so survived the mutation) now asserts a literal 2.Bench (real Nq1, fw 3.7.2, USB, non-destructive)
Not just "it still streams" — the device was first put into the exact state this code exists to recover from.
SYSTem:STReam:INTerface 1was sent from the shell (accepted,0,"No error"), routing the device's stream to WiFi. The example CLI was then built against this branch's Core and run over USB:--min-samples 300passed, exit 0.With the routing broken, that run would have received nothing at all — so the extracted initializer is what put the data back on the serial port. 158 Hz effective vs 200 Hz requested is the known fw 3.7.2 clock mismatch, not a regression. The bench was explicitly returned to
SYSTem:STReam:INTerface 0afterwards and confirmed error-free.Not merging — for review.