Repository navigation
fix(layering): a view-model was hand-writing SCPI bytes at the transport - #189
Conversation
The Stream tab powered the device's acquisition subsystem by hand-writing
`"SYSTem:POWer:STATe 1\r\n"` into `AbstractStreamingDevice.Write(string)`, a
raw-bytes escape hatch on the device's public surface. The view-model owned the
command mnemonic AND the line terminator, and eleven lines of comment above the
call explained the framing rule it had to get right.
`Write` had exactly one caller in this repo — that line. Upstream `daqifi-desktop`
has zero. Every transport still implemented it: serial wrote ASCII straight at
`_transport.Stream`, USB the same, and WiFi threw `NotSupportedException`, which
is why the call site is wrapped in a bare `catch` — on the mobile shell's own
primary transport the power-on step did nothing at all.
Replace it with `AbstractStreamingDevice.TurnDeviceOn()`, which goes through the
existing `SendMessage` path with Core's `ScpiMessageProducer.TurnDeviceOn` like
every other command this class sends, and delete `Write` — the abstract member,
its three transport implementations and its three test-double stubs.
`ScpiMessage.GetBytes()` is `ASCII(Data + "\r\n")` and `TurnDeviceOn` is
`"SYSTem:POWer:STATe 1"`, so serial and USB put byte-identical traffic on the
wire; three tests pin those bytes. WiFi now sends the command instead of throwing.
Also: the connect dialog's USB tile bound `{Binding Port.PortName}`, walking from
the view into the `System.IO.Ports.SerialPort` the device owns. It binds the
device's own `PortName` projection now, and `Port`'s setter raises it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
PR Summary by QodoRoute device power-on through the messaging abstraction
AI Description
Diagram
High-Level Assessment
Files changed (11)
|
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 reviewTip of the day💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history |
|
Qodo-clean, CI green — ready for review Swept at head Three extra checks, because this touches a device path: Serial/USB byte equivalence — established by reading, not only by test. The WiFi behaviour change is real, is an improvement, and is UNVERIFIED ON HARDWARE. The app's WiFi device connects with XAML binding audit. Bindings here are not compile-checked — Verification only — not merged, nothing changed. |
This branch deletes `AbstractStreamingDevice.Write(string)` and updated every test double that overrode it — every one that existed at 4a8c338, its branch point. #178 landed on main afterwards and added a fourth: `ConnectionDialogFirmwarePauseTests.TestDevice`, whose `public override bool Write(string command) => throw new NotSupportedException();` had nothing left to override once the two were merged. Git called the merge CLEAN because it is — textually. The two changes touch disjoint files and only fail together at compile time, so the merge queue was the first thing to see it: CS0115 in the `Test` step of run 33671611557. The double is a placeholder for `ConnectionManager.DeviceBeingUpdated`; the six tests that construct it never write, send or connect. Deleting the override leaves it satisfying the base's two remaining abstract members (`ConnectionType`, `SendMessage`), exactly like the three doubles this branch already updated. #178's subject — discovery staying paused across a manual HID flash — is untouched. Also merges current origin/main into the branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit ae5bcaa |
Ready — the merge-queue failure is fixedWhat ejected it. This PR deletes had nothing left to override — Worth naming, because it will happen again: git was right to call this What changed. Merged current Swept the whole tree, not just the file the compiler named (compilation stops early, so the first error is not the list):
Gate, on the merged result: desktop head clean; iOS restore + Release The rest of the queue is clear. Every other open PR (#195, #196, #198, #199, #200, #202) was test-merged onto |
Not merging — for review.
The violation
MobileShellViewModel.StreamToggle— a view-model — hand-wrote the SCPI command"SYSTem:POWer:STATe 1\r\n"and pushed it throughAbstractStreamingDevice.Write(string), araw-bytes escape hatch on the device's public surface. The UI owned the command mnemonic and
the wire terminator, and said so in eleven lines of comment:
That is the app's rule broken in one line: the UI talks to Core's public surface and to the
app's own device layer, and does not know about SCPI or transports.
Writeexisted only to serve that line. It had exactly one caller in this repo — that one —and zero in upstream
daqifi-desktop, which carries the same abstract member. Everytransport still implemented it: serial wrote ASCII straight at
_transport.Streamwith aSerialPortfallback, USB did the same, and WiFi threwNotSupportedException— which is whatthe bare
catchis for. On the mobile shell's own primary transport the power-on step didnothing at all.
The fix
AbstractStreamingDevice.TurnDeviceOn()— one concrete method on the base class, sending Core'sScpiMessageProducer.TurnDeviceOnthrough theSendMessagepath that already carries everyother command this class issues (
Reboot,AddChannel,RemoveAllChannels, the SD interfaceswitch). The view-model calls
device.TurnDeviceOn()and knows nothing else.Then
Writegoes: the abstract member, its three transport implementations, and its threetest-double stubs.
Second, smaller one in the same family: the connect dialog's USB tile bound
{Binding Port.PortName}, walking from the view into theSystem.IO.Ports.SerialPortobjectthe device owns. It binds the device's own
PortNameprojection now.What got flatter
git diff --numstat origin/main...HEAD, production files only:Device/AbstractStreamingDevice.csDevice/SerialDevice/SerialStreamingDevice.csDevice/UsbDevice/UsbStreamingDevice.csDevice/WiFiDevice/DaqifiStreamingDevice.csView/ConnectionDialog.axamlViews/MobileShellViewModel.csOne abstract member and four implementations of it become one concrete method. No new interface,
no new service, no new indirection —
SendMessagewas already there and already correct.Wire behaviour
ScpiMessage.GetBytes()isEncoding.ASCII.GetBytes(Data + "\r\n")andScpiMessageProducer.TurnDeviceOnis"SYSTem:POWer:STATe 1"(the literal is present in thepinned Daqifi.Core 1.7.0 assembly), so:
producer supplies the framing the view-model used to append by hand. Three tests assert the
exact byte array.
threw
NotSupportedExceptionand was swallowed, so nothing was sent. Now the command goes out.That is what the code's own comment says the step is for ("a device that associated on WiFi but
never powered its ADC front-end streams NO data"), and it is the standard connect handshake —
Core sends the same command itself during a connect with
InitializeDevice=true, and the appreaches for it on the serial side in
SerialStreamingDevice.PowerOnWifiModule. It isidempotent. Not bench-verified over WiFi: the bench board is USB, so this path was reasoned
and unit-tested, not measured.
{Binding PortName}vs{Binding Port.PortName}: identical wheneverPortis set, whichit is for every device in that list (both constructors assign it). When
Portis null the newbinding falls back to
Namewhere the old one rendered blank.Port's setter now raisesPropertyChangedforPortNameas well, so the binding refreshes exactly as the old path did.XAML in this repo is not compile-checked —
ConnectionDialog.axamlhas nox:DataType, so thatbinding resolves by reflection and a wrong name fails silently at runtime with a blank cell.
PortNameis a public instance property onSerialStreamingDevice(the element type ofAvailableSerialDevices), it is the onlyPort.binding path in any.axamlin the repo, andSerialStreamingDevicePortNameTestspins it so a future rename fails a test instead of blankingthe tile. No member reachable from XAML was renamed or removed by this PR.
Tests
139 passed, 0 failed (
mainis at 133; this adds 6).AbstractStreamingDeviceTurnDeviceOnTests(3) — one message sent, the right mnemonic, and theexact framed byte array. The last one is the regression guard for the terminator the view-model
used to own.
SerialStreamingDevicePortNameTests(3) —PortNamereports the port even once discovery hasrenamed the device, falls back to
Name, and notifies whenPortis replaced.Gate
Release/iossimulator-arm64: 0 errors.dotnet test: 139 / 139.third_party/oxyplot-avalonia/objcleared each time,third_party/excluded and(line,col)normalised out: 180 distinct first-party warnings on the merge base, 180 on the branch,
diff-identical. Raw: desktop 500 lines, iOS 502, both sides.packages.lock.jsonunchanged; iOS restore run without-r.(3m39s), so that gap is closed rather than assumed. Every deleted name was grepped across
Daqifi.Avalonia.Android/,.iOS/,.Desktop/and.Tests/: noWritecall site outside theone removed.
machine. The one XAML change was verified statically and pinned with a test instead — see above.