SMA-2 round 2: keep the driver keys out of the schema, translations and labels
checks / unit (pull_request) Successful in 23m13s
infra-pin / fresh (pull_request) Successful in 1s

Owner decision 2026-11-07: clients see nothing new until SMA-4. config.yaml,
translations, labels and the page layout are back to 23ff9d0's. The validation
of the keys stays for callers that skip the schema.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-11-07 13:09:31 +01:00
co-authored by Claude
parent 0b20f53cf8
commit c015230452
13 changed files with 79 additions and 154 deletions
+19 -24
View File
@@ -592,22 +592,29 @@ ent = sch[sch.index("\n inverters:"):]
ent = ent[:ent.index("\n\n")]
decl = dict(ln.strip().lstrip("- ").split(": ", 1)
for ln in ent.splitlines()[1:] if ": " in ln)
check("setpoint_entity is optional per entry, beside driver, host, port, "
"unit_id and maintenance; name, max_w and bms_soc_floor stay required",
decl["setpoint_entity"] == "str?" and decl["driver"] == "list(goodwe_esphome)?"
and decl["host"] == "str?" and decl["port"] == "port?"
and decl["unit_id"] == "int(0,247)?" and decl["maintenance"] == "bool?"
and not any(decl[k].endswith("?") for k in ("name", "max_w",
"bms_soc_floor")))
check("config.yaml lists exactly the selectable drivers",
decl["driver"] == f"list({'|'.join(SELECTABLE)})?")
NEW_KEYS = ("driver", "host", "port", "unit_id", "maintenance")
check("HIDDEN FROM CLIENTS (owner, 2026-11-07): config.yaml's per-entry schema "
"is what it was on 23ff9d0 - the five driver keys are not in it, and "
"setpoint_entity is still required - so the Supervisor form shows "
"nothing new",
not set(NEW_KEYS) & set(decl) and not decl["setpoint_entity"].endswith("?")
and set(decl) == {"name", "setpoint_entity", "max_w", "bms_soc_floor",
"soc_entity", "batt_entity", "batt_invert",
"capacity_kwh"})
_tr = {lg: io.open(HERE / "translations" / f"{lg}.yaml", encoding="utf-8").read()
for lg in ("en", "nl")}
from app import messages as _msgs # noqa: E402
check("...and no translation, label or description was added for them",
not any(f" {k}:\n" in t.split(" estop_fitted:")[0].split(" inverters:")[1]
for t in _tr.values() for k in NEW_KEYS)
and not set(NEW_KEYS) & (set(_msgs.LABELS) | set(_msgs.LABELS_NL)))
# ==============================================================================
print()
print("6. round 2: census parity, setters, rule 9(d), NaN ports, hidden keys")
import copy, itertools, math # noqa: E402
from app import uilayout as L, uimodel # noqa: E402
from app import uimodel # noqa: E402
from app.validate import ACTUATOR_WRITE_KEYS # noqa: E402
@@ -708,25 +715,13 @@ for key in ("port", "unit_id"):
got, ok_ = repr(e), False
check(f"{key} = {nm} is refused by key and does not raise", ok_)
keys_cfg = set(decl)
placed = {k.split(".", 1)[1] for _g, _p, f in L.fields()
for k in [f.key] if k.startswith("inverters.")}
check("ROSTER_HIDDEN: every per-entry key in config.yaml is either a card "
"field or hidden, never both and never neither",
all((k in placed) != (k in L.ROSTER_HIDDEN) for k in keys_cfg)
and not (placed & set(L.ROSTER_HIDDEN))
and set(L.ROSTER_HIDDEN) <= keys_cfg)
check("...and a per-entry key that is neither would be flagged (test_uilayout "
"grades the same rule with a made-up key)",
not all((k in placed) != (k in L.ROSTER_HIDDEN)
for k in keys_cfg | {"brand_new_key"}))
ent = dict(name="a", setpoint_entity="number.a", max_w=3000, bms_soc_floor=10,
soc_entity="sensor.s", batt_entity="sensor.b", driver="goodwe_esphome",
host="h", port=502, unit_id=3, maintenance=False)
opts_rt = {"inverters": [ent, dict(ent, name="b", setpoint_entity="number.b")]}
back = uimodel.ui_to_options(uimodel.options_to_ui(copy.deepcopy(opts_rt)))
check("the page model carries the hidden keys through a save untouched "
"(this is how ROSTER_HIDDEN keys survive the settings page)",
check("an entry that carries the driver keys (from a caller that skips the "
"schema) comes back from a save through the page model unchanged",
back.get("inverters") == opts_rt["inverters"])
del REGISTRY["fake"]