UX-09 review: short start-up timeout, latch rows pinned in shadow, plainer rows
The Supervisor questions for the Details link are asked together with a 3 s budget instead of one after the other at 10 s each, so a hung Supervisor cannot hold control start for 20 s. In shadow mode the circulating and site-direction latches are pinned to keep their own red row that says how they clear. Plainer inverter and battery wording. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5370,27 +5370,32 @@ def control_task_done(task: asyncio.Task, *, controller, stop: asyncio.Event,
|
||||
stop.set()
|
||||
|
||||
|
||||
DOCS_LINK_TIMEOUT_S = 3
|
||||
|
||||
|
||||
async def docs_link_facts(session, token: str) -> dict:
|
||||
"""`{slug, ha_version}` from the Supervisor, each None when unreadable.
|
||||
|
||||
`GET /addons/self/info` and `GET /info` are on the Supervisor's bypass
|
||||
list for an add-on's own token, so they need no role (see selfconfig.py).
|
||||
Asked together with a 3 s budget: a link may never hold up control start.
|
||||
"""
|
||||
out = {"slug": None, "ha_version": None}
|
||||
for key, url, field in (("slug", "http://supervisor/addons/self/info",
|
||||
"slug"),
|
||||
("ha_version", "http://supervisor/info",
|
||||
"homeassistant")):
|
||||
async def ask(url, field):
|
||||
try:
|
||||
async with session.get(url, timeout=10, headers={
|
||||
async with session.get(url, timeout=DOCS_LINK_TIMEOUT_S, headers={
|
||||
"Authorization": f"Bearer {token}"}) as r:
|
||||
body = await r.json(content_type=None)
|
||||
data = body.get("data", body) if isinstance(body, dict) else {}
|
||||
out[key] = data.get(field) if isinstance(data, dict) else None
|
||||
return data.get(field) if isinstance(data, dict) else None
|
||||
except Exception as err: # noqa: BLE001 - a link, never a stop
|
||||
_LOG.info("docs link: %s unreadable (%s)", url, err)
|
||||
_LOG.info("docs link: slug %s, Home Assistant %s", out["slug"],
|
||||
out["ha_version"])
|
||||
return None
|
||||
|
||||
slug, ha = await asyncio.gather(
|
||||
ask("http://supervisor/addons/self/info", "slug"),
|
||||
ask("http://supervisor/info", "homeassistant"))
|
||||
out = {"slug": slug, "ha_version": ha}
|
||||
_LOG.info("docs link: slug %s, Home Assistant %s", slug, ha)
|
||||
return out
|
||||
|
||||
|
||||
|
||||
@@ -146,9 +146,9 @@ LIMIT_WORDS = {
|
||||
def loop_bound() -> Notice:
|
||||
"""SAFETY-40: the integrator bound. Normal in short bursts; not a fault."""
|
||||
return Notice(ID_LOOP_BOUND, WARNING, T_LOOP_BOUND,
|
||||
"The battery does less than asked, which is normal briefly, "
|
||||
"but if this keeps happening, check that it is on and not "
|
||||
"empty or full.")
|
||||
"The battery gives less than asked, which is normal for a "
|
||||
"moment, but if it lasts, check it is on and not empty "
|
||||
"or full.")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -358,7 +358,7 @@ def banner_shadow(latched: bool) -> str:
|
||||
clears.
|
||||
"""
|
||||
if latched:
|
||||
return ("Shadow mode, battery not controlled. A safety stop stays on, "
|
||||
return ("Shadow mode: battery not controlled. A safety stop stays on, "
|
||||
"see below.")
|
||||
return "Shadow mode: the battery is not controlled. Inverters stay at 0 W."
|
||||
|
||||
@@ -491,8 +491,8 @@ def row_no_setpoint(name) -> str:
|
||||
|
||||
def row_not_controllable(name, held_s, limit_s) -> str:
|
||||
"""Red. The inverter is dropped and the others carry its share."""
|
||||
return (f"{name} cannot be controlled. Every command was refused for "
|
||||
f"{seconds(held_s)}, limit {seconds(limit_s)}.")
|
||||
return (f"{name} cannot be controlled. It refused every command for "
|
||||
f"{seconds(held_s)}, so control leaves it out.")
|
||||
|
||||
|
||||
def row_commands_refused(name, held_s, limit_s) -> str:
|
||||
|
||||
Reference in New Issue
Block a user