feat: device value semantics — Slice 1 (single-authority ranges, clamp-not-wrap, per-device serialization)#27
Merged
Conversation
…lice 1 foundation) Per-action value semantics as data: kind/min/max/step/unit/label with save-time validation, range-containment for out-of-range read marking, and clamp_to_spec that rejects NaN/inf/non-numeric BEFORE range handling and clamps rather than wraps (fixes the reproducible 70000 & 0xFFFF -> 4464 aliasing and the NaN int-cast crash). Pure module, 30 unit tests. No consumers wired yet. Implements docs/features/device-value-semantics plan decisions D-1, D-3 (core).
BaseDevice.value_spec(action) resolves declared-or-fallback so every consumer reads an action's range/unit through one authority (declared spec wins; base returns None until subclasses provide op-inferred defaults). Add a per-device asyncio.Lock held across the whole execute_action body so commands from any source (flow, function, manual) never interleave their hardware steps; the non-yielding guards keep acquisition order == call order (FIFO). Emergency stop stays exempt (does not acquire it). Tests: serialization proven (no interleave), lock released, accessor precedence. HAL regression baseline stays green. Plan D-1, D-5.
DeclarativeDevice now resolves value_spec() for its actions: an optional per- action 'value' block (kind/min/max/step/unit/label) overrides an op-inferred fallback (write_pwm -> board pwm_resolution, write_byte -> 0xFF, write_word -> 0xFFFF) equal to today's ranges, so existing .gdevice files load unchanged. _execute clamps a value op to its declared range before dispatch and the write_byte/write_word ops clamp to their wire width instead of masking (& 0xFF/ & 0xFFFF) -- fixing the 70000 -> 4464 aliasing. clamp_to_spec rejects NaN/inf before any hardware call; clamp warnings are rate-limited per action. validate_definition rejects value blocks on no-value/multi-arg ops and malformed ranges. 12 tests; full HAL suite green. Plan D-2, D-3, D-16.
Each built-in provides its action ranges through the shared accessor so nodes and the runner read one authority: DigitalOutput 'set' -> switch; PWMOutput 'set' -> 0..2^pwm_resolution-1 and 'set_percent' -> 0..100%; AnalogInput 'read' -> 0..2^analog_resolution-1; Servo 'set_angle' -> instance min/max angle in deg (declared, since the bounds are per-instance). 5 tests; HAL suite green (226). Plan D-1, D-7 (built-in half of the value_spec story).
PWMWriteNode and the generic OutputNode clamp to the bound device's value_spec range instead of a literal 0-255, so a 12-bit PWM device reaches 4095; AnalogReadNode computes voltage from the device's declared read range instead of a hardcoded 10-bit assumption. Nodes query value_spec() lazily at use, keeping the device the single authority. Unbound nodes fall back to neutral defaults. The D11 out-of-range-at-load *flag* is carried to Slice 2, where its display surface (the persistent runner status strip) is built -- logging it to nowhere visible now would be premature. 2 tests; node + HAL suites green (333).
This was referenced Jul 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Slice 1 of the Device Value Semantics & Data-Driven Runner Controls feature (plan in
docs/features/device-value-semantics/, local). This is the foundation the other two slices read from, and it stands alone as a fix for two reproducible-today defects.What's here (5 commits, TDD)
ActionValueSpec+clamp_to_spec(hal/value_spec.py) — a frozen per-action value object (kind/min/max/step/unit/label) with save-time validation, and a clamp that rejects NaN/±inf/non-numeric before range handling and clamps rather than wraps.BaseDevice.value_spec()accessor + per-device command lock (D13) — one authority resolves declared-or-fallback; anasyncio.Lockheld across the wholeexecute_actionbody serializes commands from every source so their hardware steps never interleave (e-stop stays exempt).valueblock overrides an op-inferred fallback (= today's ranges, so old.gdevicefiles load unchanged);_executeclamps before dispatch and the byte/word ops clamp to wire width instead of masking.value_spec()— digital→switch, PWM→0..2^res−1, analog read→0..2^res−1, servo→instance angle bounds.Bugs fixed (verified by tests)
70000 & 0xFFFF == 4464) → now clamp to the ceiling.Tests & regression
value_spec, the command lock (serialization proven), declarative value semantics, built-in specs, and node de-hardcoding.base_device.pyrefactor broke no existing HAL/node tests.Deliberately carried forward
value_spec()/execute_action-lock seams this slice publishes.