From d9f94d3f2824836dcab6a38e6e94de41789ce051 Mon Sep 17 00:00:00 2001 From: retr0 <42kdesigners@gmail.com> Date: Tue, 26 May 2026 13:31:46 +0200 Subject: [PATCH] =?UTF-8?q?ShineBridge=20v1.8.28=20=E2=80=94=20history.py:?= =?UTF-8?q?=20load=5Frecent=20Fix=20+=20periodisches=20Cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - load_recent(): Window-Funktion durch pro-Sensor-Indexabfragen ersetzt (SELECT ... ORDER BY ts DESC LIMIT N per sensor_id) — nutzt Index optimal, kein Full-Table-Scan mehr auf 1M+ Zeilen beim Start - Periodisches Cleanup: täglich via Daemon-Thread statt nur beim Start — DB bleibt dauerhaft auf RETENTION_DAYS beschränkt - RETENTION_DAYS: 7 → 14 (explizites Maximum per Konfiguration) Co-Authored-By: Claude Sonnet 4.6 --- haos-addon/config.yaml | 2 +- haos-addon/src/history.py | 39 +++++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/haos-addon/config.yaml b/haos-addon/config.yaml index 88ef316..1e68634 100644 --- a/haos-addon/config.yaml +++ b/haos-addon/config.yaml @@ -1,5 +1,5 @@ name: ShineBridge -version: "1.8.27" +version: "1.8.28" slug: shinebridge description: Growatt Wechselrichter lokal in Home Assistant — Modbus TCP via ShineLAN-X, MQTT Discovery, Web UI url: https://gitea.bitfire.work/retr0/shinebridge diff --git a/haos-addon/src/history.py b/haos-addon/src/history.py index d4d1b34..ad7af1e 100644 --- a/haos-addon/src/history.py +++ b/haos-addon/src/history.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Tuple log = logging.getLogger(__name__) DB_PATH = "/data/history.db" -RETENTION_DAYS = 7 +RETENTION_DAYS = 14 _lock = threading.Lock() _conn: sqlite3.Connection | None = None @@ -59,9 +59,19 @@ def init_db(): """) c.commit() cleanup_old() + _start_cleanup_scheduler() log.info("History DB initialisiert: %s", DB_PATH) +def _start_cleanup_scheduler(): + def _loop(): + while True: + time.sleep(86400) + cleanup_old() + t = threading.Thread(target=_loop, daemon=True, name="history-cleanup") + t.start() + + def period_key(period_type: str, billing_day: int = 1, billing_month: int = 1) -> str: import datetime today = datetime.date.today() @@ -117,21 +127,18 @@ def write_batch(inv_id: str, ts: float, values: Dict[str, float]): def load_recent(inv_id: str, limit: int = 300) -> Dict[str, List[Tuple[float, float]]]: """Letzte `limit` Messpunkte pro Sensor — zum Befüllen der In-Memory-Deque beim Start.""" with _lock: - rows = _get_conn().execute(""" - SELECT sensor_id, ts, value - FROM ( - SELECT sensor_id, ts, value, - ROW_NUMBER() OVER (PARTITION BY sensor_id ORDER BY ts DESC) AS rn - FROM measurements - WHERE inv_id = ? - ) - WHERE rn <= ? - ORDER BY ts ASC - """, (inv_id, limit)).fetchall() - - result: Dict[str, List[Tuple[float, float]]] = {} - for sensor_id, ts, value in rows: - result.setdefault(sensor_id, []).append((ts, value)) + c = _get_conn() + sensors = [r[0] for r in c.execute( + "SELECT DISTINCT sensor_id FROM measurements WHERE inv_id = ?", (inv_id,) + ).fetchall()] + result: Dict[str, List[Tuple[float, float]]] = {} + for sid in sensors: + rows = c.execute( + "SELECT ts, value FROM measurements " + "WHERE inv_id = ? AND sensor_id = ? ORDER BY ts DESC LIMIT ?", + (inv_id, sid, limit), + ).fetchall() + result[sid] = [(ts, val) for ts, val in reversed(rows)] return result