FUE-T09: grace-shift *_today daily reset past local midnight + dedicated 00:00:10 publish
This commit is contained in:
@@ -27,6 +27,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
from typing import Any, Callable, Optional, Protocol
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -369,6 +370,10 @@ register_provider(_modbus_provider)
|
||||
# Energy Cost provider
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# *_today 当日窗口翻天的宽限:本地午夜后 5 秒才切到新的一天,避免在 00:00:0x 把
|
||||
# 归零后的值发出去、被慢几秒的 HA 钟记成前一天的 23:59:59(归错小时桶)。
|
||||
_TODAY_RESET_GRACE = timedelta(seconds=5)
|
||||
|
||||
|
||||
def _energy_cost_provider(session: Session) -> list[ExposableEntity]:
|
||||
"""Enumerate ExposableEntity objects for the energy cost subsystem.
|
||||
@@ -735,7 +740,11 @@ def _energy_cost_provider(session: Session) -> list[ExposableEntity]:
|
||||
return None
|
||||
|
||||
# Today's window in UTC, using monkeypatch-safe module attribute calls.
|
||||
today_local = _tz_mod.local_now().date()
|
||||
# Grace: subtract _TODAY_RESET_GRACE so that in the first 5 seconds after
|
||||
# local midnight the getter still returns yesterday's window. This prevents
|
||||
# a "归零后的值" from being published while HA's clock (which may lag a few
|
||||
# seconds) would stamp it as 23:59:59 of the previous day.
|
||||
today_local = (_tz_mod.local_now() - _TODAY_RESET_GRACE).date()
|
||||
tomorrow_local = today_local + _td(days=1)
|
||||
today_start_utc = _tz_mod.local_midnight_utc(today_local)
|
||||
tomorrow_start_utc = _tz_mod.local_midnight_utc(tomorrow_local)
|
||||
@@ -772,7 +781,8 @@ def _energy_cost_provider(session: Session) -> list[ExposableEntity]:
|
||||
if not versions:
|
||||
return None
|
||||
|
||||
today_local = _tz_mod.local_now().date()
|
||||
# Grace: same logic as import_cost_today — see that getter's comment.
|
||||
today_local = (_tz_mod.local_now() - _TODAY_RESET_GRACE).date()
|
||||
tomorrow_local = today_local + _td(days=1)
|
||||
today_start_utc = _tz_mod.local_midnight_utc(today_local)
|
||||
tomorrow_start_utc = _tz_mod.local_midnight_utc(tomorrow_local)
|
||||
|
||||
+27
@@ -7,6 +7,7 @@ from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -36,6 +37,7 @@ from app.services.modbus_poll import poll_all_enabled_devices, BASE_POLL_TICK_SE
|
||||
from app.services.ha_discovery import publish_discovery, publish_states
|
||||
from app.services.tibber_prices import refresh_prices
|
||||
from app.services.energy_cost import compute_closed_periods
|
||||
from app.services.timezone import local_tz
|
||||
from scripts.app_db_adopt import AppDatabaseAdoptionError, validate_app_runtime_db
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -159,6 +161,20 @@ def _run_scheduled_ha_state_publish() -> None:
|
||||
session.close()
|
||||
|
||||
|
||||
def _run_midnight_state_publish() -> None:
|
||||
"""本地午夜后不久专门发布一次状态,让 *_today 的每日归零稳稳落在午夜之后
|
||||
(对 HA 钟慢几秒鲁棒)。best-effort:失败仅记日志,不影响调度器。"""
|
||||
session_local = get_session_local()
|
||||
session = session_local()
|
||||
try:
|
||||
from app.services.ha_discovery import publish_states
|
||||
publish_states(session)
|
||||
except Exception:
|
||||
logger.exception("_run_midnight_state_publish: failed (non-fatal)")
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
def ensure_auth_db_ready() -> None:
|
||||
session_local = get_session_local()
|
||||
session: Session = session_local()
|
||||
@@ -233,6 +249,17 @@ async def lifespan(_: FastAPI):
|
||||
max_instances=1,
|
||||
coalesce=True,
|
||||
)
|
||||
# Dedicated midnight publish: fire at local 00:00:10 so *_today grace (5 s) has
|
||||
# already elapsed and the day-rolled value is pushed to HA immediately, rather
|
||||
# than waiting for the next 60-second ha-state-publish sweep.
|
||||
scheduler.add_job(
|
||||
_run_midnight_state_publish,
|
||||
trigger=CronTrigger(hour=0, minute=0, second=10, timezone=local_tz()),
|
||||
id="midnight-today-publish",
|
||||
replace_existing=True,
|
||||
max_instances=1,
|
||||
coalesce=True,
|
||||
)
|
||||
scheduler.start()
|
||||
|
||||
# MQTT: connect using DB-merged runtime settings so broker configured via UI
|
||||
|
||||
Reference in New Issue
Block a user