SMA-2 round 2: census keeps ACTUATOR_WRITE_KEYS, setters, NaN/inf port refused, tests

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-11-07 12:20:56 +01:00
co-authored by Claude
parent e56eeef8f0
commit 0b20f53cf8
5 changed files with 185 additions and 9 deletions
+25
View File
@@ -366,6 +366,31 @@ class Inverter:
def batt_invert(self) -> bool:
return bool(getattr(self.driver, "batt_invert", False))
# Assigning one writes through to the driver, as the attributes did before
# SMA-2 (nothing in the repo assigns them today; this keeps it working).
# A driver that has no such field refuses, rather than losing the value.
def _set_driver_field(self, name, value):
if not hasattr(self.driver, name):
raise AttributeError(
f"{type(self.driver).__name__} has no {name}")
setattr(self.driver, name, value)
@setpoint_entity.setter
def setpoint_entity(self, v):
self._set_driver_field("setpoint_entity", str(v or ""))
@soc_entity.setter
def soc_entity(self, v):
self._set_driver_field("soc_entity", str(v or ""))
@batt_entity.setter
def batt_entity(self, v):
self._set_driver_field("batt_entity", str(v or ""))
@batt_invert.setter
def batt_invert(self, v):
self._set_driver_field("batt_invert", bool(v))
# -- store ---------------------------------------------------------------
def key(self, name: str) -> str:
"""This unit's namespaced Store key for `name`. See store.unit_key()."""
+15 -2
View File
@@ -328,6 +328,7 @@ WHAT IS DELIBERATELY *NOT* REFUSED, because it is untidy rather than unsafe:
"""
import logging
import math
import re
from typing import NamedTuple
@@ -466,6 +467,15 @@ def _collect_writers(node, path: str, out: list) -> None:
cls = drivers.driver_class(node)
for k, v in node.items():
here = f"{path}.{k}" if path else str(k)
# Every key in ACTUATOR_WRITE_KEYS declares a writer, as before
# SMA-2 and whatever the entry's driver; PLUS the entry driver's
# own writer key. The second is added to the first, never in place
# of it (census parity with 23ff9d0 is graded in test_sma2_driver).
if k in ACTUATOR_WRITE_KEYS:
entity = str(v or "").strip()
if entity:
out.append((here, entity))
continue
if k == cls.writer_key:
actuator = cls.actuator_id_of(node)
if actuator:
@@ -793,9 +803,12 @@ def _whole(raw, lo: int, hi: int):
return None
try:
val = float(raw)
except (TypeError, ValueError):
# NaN and infinity: int() raises on them, so refuse them first.
if not math.isfinite(val) or val != int(val):
return None
except (TypeError, ValueError, OverflowError):
return None
return int(val) if val == int(val) and lo <= val <= hi else None
return int(val) if lo <= val <= hi else None
def _driver_problems(entry: dict, who: str) -> list[str]: