feat(channel): say what a reading means, not just what it measures - #523
feat(channel): say what a reading means, not just what it measures#523tylerkron wants to merge 2 commits into
Conversation
Core parsed each channel's unit out of the capability document and then
dropped it, and offered no way to say what a transducer on a terminal
actually measures — so every consumer that wanted engineering units
hand-rolled the conversion.
An analog channel now takes a ChannelScaling (gain, offset, unit). Every
sample decoded from then on carries it: Value stays the volts the device
reported, ScaledValue is the converted reading, Unit says what it is in.
The scaling travels on the sample rather than being read back off the
channel, so reconfiguring never retroactively reinterprets readings that
were already taken, and nothing is mutated in place.
Additive throughout: IScaledChannel is a new capability interface rather
than members on IChannel/IAnalogChannel, and IDataSample's three new
members are defaulted, so implementations outside the library keep
compiling and existing consumers see the same numbers they saw before.
Connect now applies the device's own unit ("V" on a Nyquist) as an
identity scaling — a label, not a conversion — and never overwrites a
scaling a caller configured, including on a capability refresh.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
PR Summary by QodoAdd engineering-unit scaling for analog channels and stamp it onto samples
AI Description
Diagram
High-Level Assessment
Files changed (16)
|
Code Review by Qodo
1.
|
…values The remarks claimed Apply "never returns a non-finite value", which the tests contradict: an overflowing coefficient degrades to the unscaled reading, but a reading that arrives NaN or infinite is handed back unchanged. Apply never *introduces* a non-finite value and never launders one — say both, so a caller does not assume ScaledValue is always finite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 034f095 |
|
Qodo-clean, CI green — ready for review. 2 rounds on head Full suite green on net9.0 + net10.0 (3189 passed / 2 skipped each, +86 Mcp, 0 warnings). Bench re-run on the real Nyquist against this head: all 13 checks pass, including Not merging — for review. |
What was wrong
If you wire a 0-100 PSI transducer to an analog terminal, the library gave you back volts and left the conversion to you. It even knew better: the device's capability document states each channel's unit, Core parsed it, and then dropped it on the floor — no consumer ever read it. So anyone who wanted "12.4 PSI" instead of "2.48 V" wrote their own scaling, and the desktop app duly did, mutating each sample in place as it went.
How it was fixed
An analog channel now takes a
ChannelScaling— a gain, an offset, and a unit label:Every sample decoded from then on carries it. Two decisions a reviewer might want to push on:
Valuekeeps its old meaning. The converted number lives on the newScaledValue, which equalsValuewhen no scaling is set, so no existing consumer's numbers move.The whole change is additive:
IScaledChannelis a new capability interface (if (channel is IScaledChannel scaled)) rather than new members onIChannel/IAnalogChannel, andIDataSample's three new members are defaulted — an implementation written before this existed still compiles and reports "no scaling", which is what it has. There is a test that would stop compiling if that ever stopped being true.Connecting now also copies the device's own unit onto each analog channel as an identity scaling — a label, no arithmetic — and never overwrites a scaling you configured, which matters because the MCP layer re-reads the capability document after every channel-configuration call.
This is Tier 1 of the issue (linear scaling, no new dependency). Tier 2 — expression-based scaling — is deliberately left as the separate decision the issue frames it as.
Verified
Daqifi.Mcp.Tests, 0 warnings.Unit="V"from the device's own capability document on connect, as an identity scaling, with the digital channels correctly left bare. A live channel was then given a 20x + 1 PSI conversion and streamed at 200 Hz: 1,095 samples,ScaledValue == Value * gain + offsetto 0.00E+00 max deviation on real firmware data (0.0085-0.0110 V → 1.1709-1.2197 PSI), with the volts still readable alongside. Reconfiguring the gain mid-stream took effect on the next frame without any channel-set change, the 778 samples already taken kept their original scaling object, the neighbouring channel stayed on plain volts, and a capability re-read did not clobber the configured conversion.closes #501
Not merging — opened for review.