MAINT-10: maintenance start hour in local time, default 19:00
The start hour was compared with the UTC hour, so the shipped 10 started at 12:00 in a Belgian summer. It is now read in Home Assistant's time zone (fallback: the container's TZ, then UTC, one log line says which), and the default moves to 19. DST: a missing hour starts at the first hour after the jump; a repeated hour starts only in its first pass. Stored options are not rewritten: a saved 10 now means 10:00 local (DOCS, CHANGELOG). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ import functools
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
@@ -64,7 +65,7 @@ from . import messages
|
||||
from .freeze import (
|
||||
FreezeWatchdog, FreezeWatchdogConfig, looks_frozen, rail_unreachable, rail_w,
|
||||
)
|
||||
from .hass import HomeAssistant
|
||||
from .hass import CORE_API, HomeAssistant
|
||||
# ⚠️ `LINK_STALE_S` IS RE-EXPORTED DELIBERATELY. `live_units()` reasons about it
|
||||
# and two checks read `app.main.LINK_STALE_S` to build a stale link, so the
|
||||
# window an operator would look up here is the one the roster actually uses.
|
||||
@@ -72,7 +73,7 @@ from .inverter import (LINK_STALE_S, UNIT_A, build_roster,
|
||||
roster_entries, roster_names)
|
||||
from .maintenance import (
|
||||
DRAIN, IDLE, POWER_MOVING, MaintConfig, Maintenance, MaintResult,
|
||||
arbitrate as maint_arbitrate,
|
||||
arbitrate as maint_arbitrate, site_zone,
|
||||
)
|
||||
from .mqtt import (MODE_CONFIGS_KEY, MODE_LABELS, USE_OFF, USE_ON,
|
||||
MqttPublisher, announced_units, mode_configs,
|
||||
@@ -402,7 +403,8 @@ def arbitrage_row(*, on: bool, decision, decided_at, cycle_at, now,
|
||||
class Controller:
|
||||
def __init__(self, opts: dict, hass: HomeAssistant, store, mqtt_pub, *, stop,
|
||||
peak_db_path: str = "/data/tariff.db",
|
||||
price_history_path: str = "/data/price_history.json"):
|
||||
price_history_path: str = "/data/price_history.json",
|
||||
site_tz=None):
|
||||
# UX-15: a read-only HomeAssistant belongs to setup-only mode, which
|
||||
# builds no controller. Refused here so no controller can hold one.
|
||||
if getattr(hass, "read_only", False) is True:
|
||||
@@ -643,7 +645,8 @@ class Controller:
|
||||
name=inv.name,
|
||||
# The rollback view, and only ever the first pack's: the flat
|
||||
# keys mean `inverters[0]` on every install that has them.
|
||||
mirror_flat=(not _one_pack and i == 0))
|
||||
mirror_flat=(not _one_pack and i == 0),
|
||||
tz=site_tz) # MAINT-10: the start hour's zone, from amain()
|
||||
for i, inv in enumerate(self.inverters)]
|
||||
|
||||
# SAFETY-02. ONE INSTANCE PER ACTUATOR - which is what watchdog.py's own
|
||||
@@ -6575,6 +6578,37 @@ async def docs_link_facts(session, token: str) -> dict:
|
||||
return out
|
||||
|
||||
|
||||
async def ha_time_zone(session, token: str) -> tuple:
|
||||
"""MAINT-10: `(zone, why)` - Home Assistant's configured time zone, or None
|
||||
and a short reason. Same 3 s budget as the docs link: never holds up start."""
|
||||
try:
|
||||
async with session.get(f"{CORE_API}/config", timeout=DOCS_LINK_TIMEOUT_S,
|
||||
headers={"Authorization": f"Bearer {token}"}) as r:
|
||||
body = await r.json(content_type=None)
|
||||
zone = body.get("time_zone") if isinstance(body, dict) else None
|
||||
return (zone, "") if zone else (None, "no time_zone in its answer")
|
||||
except Exception as err: # noqa: BLE001 - a fallback exists, never a stop
|
||||
text = str(err).strip()
|
||||
return None, f"{type(err).__name__}: {text}" if text else type(err).__name__
|
||||
|
||||
|
||||
async def maint_site_tz(session, token: str):
|
||||
"""MAINT-10: the zone the maintenance start hour is read in, logged once."""
|
||||
zone, why = await ha_time_zone(session, token)
|
||||
tz, name, source, why = site_zone(zone, why, os.environ.get("TZ"))
|
||||
if source == "ha":
|
||||
_LOG.info("Maintenance start hour is local time in %s, from Home "
|
||||
"Assistant.", name)
|
||||
elif source == "tz":
|
||||
_LOG.info("Maintenance start hour is local time in %s, from the "
|
||||
"add-on's time zone. Home Assistant gave none: %s.", name, why)
|
||||
else:
|
||||
_LOG.warning("Maintenance start hour uses UTC, because no time zone "
|
||||
"was found. Set the time zone in Home Assistant.")
|
||||
_LOG.debug("maintenance time zone: %s", why)
|
||||
return tz
|
||||
|
||||
|
||||
async def amain() -> None:
|
||||
opts = load_options()
|
||||
logging.basicConfig(
|
||||
@@ -6684,7 +6718,8 @@ async def amain() -> None:
|
||||
pub = _NoMqtt()
|
||||
|
||||
stop = asyncio.Event()
|
||||
controller = Controller(opts, hass, store, pub, stop=stop)
|
||||
controller = Controller(opts, hass, store, pub, stop=stop,
|
||||
site_tz=await maint_site_tz(session, _tok))
|
||||
if callable(getattr(pub, "bind", None)):
|
||||
pub.bind(asyncio.get_running_loop(), controller.mqtt_command)
|
||||
runner = await web.start(controller, port=INGRESS_PORT)
|
||||
|
||||
Reference in New Issue
Block a user