You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: profile access:'RO' now withholds the control (#374)
v1.6.0 added the VPP registers to the MOD profile as 'RO' and created five
writable controls anyway - two selects, two numbers, and control_authority
arriving 8 seconds later through the deferred path. One of them is a -100..+100%
slider on register 30409, which #373 measured climbing toward its setpoint and
importing 912 W from the grid with allow_grid_charge off. That is precisely the
exposure #373 said would not ship.
Two mistakes, both mine, both avoidable from what I already knew.
The first: `access` was never read anywhere outside profiles/. The exploration
for #371-373 told me so in as many words, and I wrote it into the #371 reply -
"profile membership is the only gate number.py and select.py consult, so an RO
entry would still create the entity" - and then relied on the flag in #373
regardless.
The second: I believed the is_wit gate confined the VPP controls. It returns
only INSIDE the WIT branch; non-WIT profiles fall through to the generic loop
that creates a control for any WRITABLE_REGISTERS entry whose register is in the
profile. The gate confines the WIT-specific classes and nothing else, so adding
the registers to MOD was sufficient on its own.
`access` now means what everyone assumes. is_read_only_register() in const.py is
consulted by both generic loops and by the deferred VPP path in select.py - that
last one matters, because gating only the setup loop fixes four of the five and
leaves control_authority to arrive seconds later.
Of 517 control/profile pairs, six change: the five above, plus SPE register 117,
which the profile already documents as "firmware-determined, writes may be
rejected". That is the same defect in miniature and removing the control is
correct.
The stale-control cleanup now treats read-only as stale too. Membership alone
would have left the five entities behind as unavailable on every install that
already has them, since their registers are still in the profile.
On the tests. Two of them passed while this shipped, and both were the wrong
shape rather than merely thin:
test_mod_vpp_registers_are_read_only asserted the flag was 'RO'. It was.
Nothing read it, so the assertion was
true and meaningless.
test_the_wit_gate_still_confines_... asserted the gate kept its shape. It
did. The gate never confined the path
that created these entities.
Both tested a declaration, or a mechanism believed to enforce it, instead of the
outcome. Replaced with tests on what the user gets. The model-based one is
explicitly paired with a source-level check, because _controls_created_for()
applies the read-only filter itself and would otherwise pass whatever the
platforms actually do - a model agreeing with itself, which is how this class of
error survives.
Verified by stashing only number.py and select.py: the two load-bearing tests
fail against the shipped code and pass with the fix.
Confirmed from the reporter's diagnostics that the rest of v1.6.0 is sound - the
peak-shaving values read 7.5/7.5/50/7.5/100 kW/%/kW/% against his portal, so the
#372 mappings and scaling are right on hardware, 30474 reads -20 so the signed
handling works, and vpp_control_authority_available is True, which is independent
confirmation of the capability #373 reported.
Co-Authored-By: Claude <noreply@anthropic.com>
0 commit comments