fix(mcp): stop PwmResult fabricating uncommanded PWM state - #470
Conversation
PwmResult.DutyCyclePercent/FrequencyHz read straight out of Core's session-default seeds (DigitalChannel.PwmDutyCyclePercent = 50, ChannelControlOperations.PwmFrequencyHz = 1000) whenever nothing had actually been commanded, so a caller of set_pwm_output/disable_pwm could not tell "this is the device's PWM configuration" from "this is a constant Core made up". The DTO's own doc promised a 0 sentinel for "none set" that was unreachable. - PwmResult.DutyCyclePercent/FrequencyHz are now nullable and report null until a value has actually been commanded this session via set_pwm_output, tracked in DaqifiAgent with two ConditionalWeakTable<,> keyed by channel/device identity so a fresh connection (fresh channel/device instances) starts clean with no explicit eviction needed. - disable_pwm no longer sends PWM:CHannel:ENable to a channel that isn't IsPwmCapable: such a channel can never have had PWM armed (the half-armed state the command exists to recover from is only reachable on capable channels), so the send only cost the device a spurious -200 execution error for no effect. Verified end-to-end against the bench Nq1 (fw 3.7.2): disable_pwm on an uncommanded channel now reports null/null; set_pwm_output commands real values that persist correctly (including across channels sharing the device-wide frequency); a non-capable channel's disable_pwm no longer touches the wire. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PR Summary by QodoFix PWM result reporting when duty/frequency were never commanded
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit fe7f64e |
Qodo caught a real regression: gating streaming.SetPwmEnabled(false) behind IsPwmCapable removed the only MCP-level recovery command for a non-capable channel the firmware flagged PWM-active before failing its capability check (e.g. via a raw command outside Core's guard). Core's own SetPwmEnabled contract accepts disabling on any digital channel specifically for that reason. Revert to always sending the disable command; document the tradeoff (a spurious device-side execution error on a channel that was never actually armed) instead of trying to suppress it client-side. Also reverts the out-var-to-discard change in DisablePwmAsync: `out _` inside device.RunExclusiveAsync(_ => ...) resolves to the lambda's own `_` parameter (a CancellationToken) rather than a discard, which doesn't compile — named out locals are correct here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 1c300ef |
Qodo caught that "may return a device-side error" overstated what a caller of disable_pwm can actually observe: Core sends the PWM-disable command fire-and-forget (no confirming read of the device's error queue), so a rejection on a never-armed channel neither throws nor shows up in the returned PwmResult. Reworded the tool description and XML doc to say the call always succeeds from the caller's point of view instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit bbd4015 |
Summary
Fixes #450.
PwmResult(returned byset_pwm_output/disable_pwm) reportedDutyCyclePercent/FrequencyHzstraight out of Core's session-default seeds (DigitalChannel.PwmDutyCyclePercent = 50,ChannelControlOperations.PwmFrequencyHz = 1000) whenever nothing had actually been commanded, so a caller couldn't tell "this is the device's PWM configuration" from "this is a constant Core made up". The DTO's own doc promised a0sentinel for "none set" that was unreachable (the field is always seeded with a commandable value).Changes
PwmResult.DutyCyclePercent/FrequencyHzare nowint?,nulluntil a value has actually been commanded this session viaset_pwm_output. Tracked inDaqifiAgentwith twoConditionalWeakTable<,>keyed by channel/device identity, so a fresh connection (fresh channel/device instances) starts clean with no explicit eviction logic needed.disable_pwmno longer sendsPWM:CHannel:ENableto a channel that isn'tIsPwmCapable: such a channel can never have had PWM armed (the half-armed state the command exists to recover from is only reachable on capable channels), so the send only cost the device a spurious-200,"Execution error"for no effect.Testing
dotnet test Daqifi.Core.sln— all 2870 tests pass.DaqifiAgentdirectly against the local build:disable_pwm(channel=1)(non-capable, never commanded):enabled=False duty=null freq=null(wasduty=50 freq=1000fabricated before the fix; no-200sent now).disable_pwm(channel=5)(capable, never commanded):duty=null freq=null.set_pwm_output(channel=5, duty=30, freq=25):duty=30 freq=25(real commanded values).disable_pwm(channel=5)after commanding:duty=30 freq=25(correctly still reported — it was actually commanded).disable_pwm(channel=4)(capable, own duty never commanded, but frequency is device-wide and was committed by channel 5):duty=null freq=25— confirms duty is tracked per-channel and frequency per-device, matching the hardware's shared-timer semantics.Related
Split out of the same bench pass as #449.
🤖 Generated with Claude Code