Compare commits

..

1 Commits

Author SHA1 Message Date
bd087728e1 Limit rollback snapshots and relax HA timeouts
Some checks failed
quality / test (3.11) (push) Has been cancelled
quality / test (3.13) (push) Has been cancelled
2026-06-17 23:44:21 +02:00
9 changed files with 66 additions and 13 deletions

View File

@@ -27,6 +27,8 @@ nach einer ausdrücklichen Freigabe ausführen.
[`docs/V1_5_0_OPERATING_GUIDE.md`](docs/V1_5_0_OPERATING_GUIDE.md)
- Version 1.5.1 Stabilisierung der Dashboard-Ladepfade:
[`docs/V1_5_1_OPERATING_GUIDE.md`](docs/V1_5_1_OPERATING_GUIDE.md)
- Version 1.5.2 Rollback-Speicher und HA-Timeouts:
[`docs/V1_5_2_OPERATING_GUIDE.md`](docs/V1_5_2_OPERATING_GUIDE.md)
- Arbeitsregeln für Coding-Agenten: [`AGENTS.md`](AGENTS.md)
## Reifegrad

View File

@@ -1,5 +1,5 @@
name: SillyHome Next
version: "1.5.1"
version: "1.5.2"
slug: sillyhome_next
description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren
url: http://192.168.6.31:3000/pino/sillyhome-next

View File

@@ -447,7 +447,7 @@ def get_actuator_detail(actuator_entity_id: str, request: Request) -> ActuatorRe
}
compact_snapshots = [
snapshot.model_copy(update={"patterns": []})
for snapshot in record.behavior.model_snapshots[-5:]
for snapshot in record.behavior.model_snapshots[-3:]
]
compact_behavior = record.behavior.model_copy(
update={

View File

@@ -32,6 +32,8 @@ from app.ha.models import HaEntitySummary
from app.ha.reader import HaReader
_MAX_PATTERNS = 500
_MAX_MODEL_SNAPSHOTS = 3
_MAX_SNAPSHOT_PATTERNS = 120
_MAX_EXECUTION_EVENTS = 100
_ACTION_LOGBOOK_TOLERANCE = timedelta(seconds=10)
_CONTEXT_TRIGGER_TOLERANCE = timedelta(seconds=3)
@@ -937,6 +939,11 @@ class BehaviorEngine:
record: ActuatorRecord,
behavior: BehaviorState,
) -> ActuatorRecord:
behavior = behavior.model_copy(
update={
"model_snapshots": _compact_model_snapshots(behavior.model_snapshots),
}
)
updated = record.model_copy(
update={
"behavior": behavior,
@@ -1132,10 +1139,19 @@ def _next_model_snapshots(
high_confidence_sample_count=trusted_actions,
average_confidence=round(average_confidence, 4),
incorrect_feedback_count=incorrect_feedback_count,
patterns=patterns,
patterns=patterns[-_MAX_SNAPSHOT_PATTERNS:],
reason=reason,
)
return [*existing, snapshot][-10:]
return _compact_model_snapshots([*existing, snapshot])
def _compact_model_snapshots(existing: list[ModelSnapshot]) -> list[ModelSnapshot]:
return [
snapshot.model_copy(
update={"patterns": snapshot.patterns[-_MAX_SNAPSHOT_PATTERNS:]}
)
for snapshot in existing[-_MAX_MODEL_SNAPSHOTS:]
]
def _average(values: list[float]) -> float:

View File

@@ -21,6 +21,7 @@ class Settings:
prediction_interval_seconds: int = 60
execution_cooldown_seconds: int = 900
timezone: str = "Europe/Berlin"
ha_timeout_seconds: int = 25
@property
def ha_configured(self) -> bool:
@@ -55,4 +56,5 @@ def load_settings() -> Settings:
60, int(os.getenv("SILLYHOME_EXECUTION_COOLDOWN_SECONDS", "900"))
),
timezone=os.getenv("SILLYHOME_TIMEZONE", "Europe/Berlin"),
ha_timeout_seconds=max(5, int(os.getenv("SILLYHOME_HA_TIMEOUT_SECONDS", "25"))),
)

View File

@@ -60,6 +60,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
settings=HaClientSettings(
url=cast(str, settings.ha_url),
token=cast(str, settings.ha_token),
timeout_seconds=settings.ha_timeout_seconds,
)
)
app.state.ha_reader = HaReader(client=client)
@@ -105,7 +106,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
app = FastAPI(
title="SillyHome Next API",
description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.",
version="1.5.1",
version="1.5.2",
lifespan=lifespan,
)
app.state.settings = load_settings()
@@ -213,8 +214,8 @@ async def _ha_event_listener(app: FastAPI, client: HaClient) -> None:
try:
async with websockets.connect(
ws_url,
ping_interval=20,
ping_timeout=10,
ping_interval=30,
ping_timeout=30,
) as websocket:
auth_required_msg = await websocket.recv()
auth_required_data = json.loads(auth_required_msg)

View File

@@ -0,0 +1,32 @@
# SillyHome Next v1.5.2 Operating Guide
v1.5.2 begrenzt den Rollback-Speicher und entschärft Home-Assistant-Timeouts,
die in den Add-on-Logs sichtbar wurden.
## Rollback-Speicher
- Pro Aktor bleiben maximal 3 Modell-Snapshots erhalten.
- Pro Snapshot bleiben maximal 120 Muster erhalten.
- Beim Speichern eines Aktors werden ältere oder zu große Snapshots automatisch
gekappt.
- Der kompakte Detail-Endpunkt liefert ebenfalls maximal 3 Rollback-Snapshots
und keine Musterlisten.
Damit bleibt Rollback nutzbar, ohne dass die JSON-Dateien mit alten Modellen
stark wachsen.
## Home-Assistant-Zugriffe
- REST-Zugriffe auf Home Assistant haben jetzt standardmäßig 25 Sekunden
Timeout statt 10 Sekunden.
- Der Wert ist über `SILLYHOME_HA_TIMEOUT_SECONDS` konfigurierbar.
- WebSocket-Keepalive wurde auf 30 Sekunden Ping-Intervall und 30 Sekunden
Ping-Timeout entschärft.
## Log-Einordnung
- `GET ... HTTP/1.1` ist bei Uvicorn/HA-Ingress normal und kein Fehler.
- `Zeitüberschreitung beim Zugriff auf Home Assistant` bedeutet, dass HA selbst
zu langsam geantwortet hat oder der Ingress/Netzpfad verzögert war.
- `keepalive ping timeout` bedeutet, dass die HA-WebSocket-Verbindung nicht
rechtzeitig geantwortet hat. SillyHome reconnectet automatisch.

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "sillyhome-next"
version = "1.5.1"
version = "1.5.2"
description = "Lokales Smart-Home-Intelligenzsystem für Home Assistant"
requires-python = ">=3.11"
dependencies = [

View File

@@ -92,11 +92,11 @@ def test_ha_event_listener_processes_state_change(tmp_path: Path) -> None:
except asyncio.CancelledError:
pass
connect.assert_called_once_with(
"ws://homeassistant:8123/api/websocket",
ping_interval=20,
ping_timeout=10,
)
connect.assert_called_once_with(
"ws://homeassistant:8123/api/websocket",
ping_interval=30,
ping_timeout=30,
)
assert fake_ws.sent == [
{"type": "auth", "access_token": "test-token"},
{"id": 1, "type": "subscribe_events", "event_type": "state_changed"},