MAINT-20 part 2 r2: app/main.py comments, lines 6001-6185 (amain .. main)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-11-08 18:06:01 +01:00
co-authored by Claude
parent 40ef27c367
commit f5714248f6
+25 -18
View File
@@ -6021,7 +6021,8 @@ async def amain() -> None:
hass = HomeAssistant(session)
# Startup self-check: prove we can actually reach the Core API before
# anything tries to control an inverter with it.
# anything tries to control an inverter with it. A 401 here is a
# permissions/token problem, not a configuration mistake.
# (DECISIONS/MAIN-orchestration-archive.md)
import os as _os
_tok = _os.environ.get("SUPERVISOR_TOKEN", "")
@@ -6037,13 +6038,14 @@ async def amain() -> None:
_LOG.error("core api probe failed: %s", _e)
# UX-09: the Details link in every notification needs this add-on's
# slug and Home Assistant's version (see messages.docs_url()).
# (DECISIONS/MAIN-orchestration-archive.md)
# slug and Home Assistant's version (see messages.docs_url()); both are
# best effort. (DECISIONS/MAIN-orchestration-archive.md)
messages.set_site(**await docs_link_facts(session, _tok))
# ⚠️ THE STARTUP GATE (CFG-01). Nothing below this line may run on a
# configuration validate.py rejects - specs §1.5: a config the system
# cannot honour is refused at startup, not survived at runtime.
# configuration validate.py rejects (specs §1.5). It sits HERE, before
# the broker, web server and any task; (None, None) from an ESPHome not
# yet up is not a refusal (run_control() re-checks).
# (DECISIONS/MAIN-orchestration-archive.md)
# SMA-2: the flat options are the GoodWe driver's (no Controller yet).
_sp = str(opts.get("setpoint_entity", "") or "").strip()
@@ -6055,8 +6057,8 @@ async def amain() -> None:
_problems = validate_startup_config(opts, _dev_min, _dev_max)
# UX-15: a new install names no inverter. When that is the ONLY refusal
# it serves the setup wizard in setup-only mode, which builds nothing
# that commands and returns when stopped.
# (DECISIONS/MAIN-orchestration-archive.md)
# that commands and returns when stopped; any other refusal still exits
# 78. DECISIONS/UX-15-setup-only-mode.md
if _problems and setuponly.applies(opts, _problems):
await setuponly.serve(
opts, HomeAssistant(session, read_only=True), store,
@@ -6066,9 +6068,9 @@ async def amain() -> None:
refuse(_problems)
raise ConfigRefused(_problems)
# ⚠️ Status publishing must NEVER be able to stop the controller. A
# broken broker, a missing library, an API change in paho - all of it
# is observability, and the battery does not care.
# ⚠️ Status publishing must NEVER be able to stop the controller:
# caught broadly and on purpose (a paho 1.x/2.x change once crashed the
# add-on and took the control loop with it).
# (DECISIONS/MAIN-orchestration-archive.md)
broker = None
try:
@@ -6084,9 +6086,12 @@ async def amain() -> None:
omit=() if is_enabled(opts) else ("p1_age",),
# TEL-11. ⚠️ THE SHIPPED PREDICATE, NOT A SECOND READING OF THE
# OPTION - `Controller.publish()` gates the row on this same
# call. (DECISIONS/MAIN-orchestration-archive.md)
# call; an install with no PV roster announces exactly what it
# did before. (DECISIONS/MAIN-orchestration-archive.md)
include=optional_keys(opts),
# TEL-07. ⚠️ THE NAMES, NOT A ROSTER.
# TEL-07. ⚠️ THE NAMES, NOT A ROSTER: `build_roster()` here
# would build a SECOND set of `Inverter`s off the Store;
# `roster_names()` is the naming rule on its own.
# (DECISIONS/MAIN-orchestration-archive.md)
units=roster_names(opts),
modes=_modes,
@@ -6122,10 +6127,11 @@ async def amain() -> None:
control_task_done, controller=controller, stop=stop, died=died))
tasks = [control]
# OPT-04.
# STARTED FOR A PROVIDER, FOR ARMED TRADING OR FOR BATTERY MODES: see
# `tariff_refresh_wanted()` above and DECISIONS/TARIFF-10 and TARIFF-24
# for why each term is there.
# OPT-04. The tariff refresh runs as its OWN task (`resolve_tariff()`
# may fetch over HTTPS, and the tick feeds the ESP32 heartbeat).
# Started for a provider, armed trading or battery modes
# (`tariff_refresh_wanted()`). ⚠️ IT DOES NOT ARM ANYTHING:
# `arbitrage.enabled()` in cycle() is the only gate on trading.
# (DECISIONS/MAIN-orchestration-archive.md)
if tariff_refresh_wanted(opts):
tasks.append(asyncio.create_task(controller.run_tariff_refresh()))
@@ -6165,8 +6171,9 @@ def main() -> None:
except KeyboardInterrupt:
pass
except ConfigRefused:
# ⚠️ No traceback, on purpose.
# (DECISIONS/MAIN-orchestration-archive.md)
# ⚠️ No traceback, on purpose: the banner refuse() printed says
# everything. 78 is sysexits EX_CONFIG, so a script can tell a bad
# config from a bug. (DECISIONS/MAIN-orchestration-archive.md)
raise SystemExit(78)
except ControlTaskDied:
# No second traceback: control_task_done() logged it at the death.