Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd087728e1 |
@@ -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)
|
[`docs/V1_5_0_OPERATING_GUIDE.md`](docs/V1_5_0_OPERATING_GUIDE.md)
|
||||||
- Version 1.5.1 Stabilisierung der Dashboard-Ladepfade:
|
- Version 1.5.1 Stabilisierung der Dashboard-Ladepfade:
|
||||||
[`docs/V1_5_1_OPERATING_GUIDE.md`](docs/V1_5_1_OPERATING_GUIDE.md)
|
[`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)
|
- Arbeitsregeln für Coding-Agenten: [`AGENTS.md`](AGENTS.md)
|
||||||
|
|
||||||
## Reifegrad
|
## Reifegrad
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: SillyHome Next
|
name: SillyHome Next
|
||||||
version: "1.5.1"
|
version: "1.5.2"
|
||||||
slug: sillyhome_next
|
slug: sillyhome_next
|
||||||
description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren
|
description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren
|
||||||
url: http://192.168.6.31:3000/pino/sillyhome-next
|
url: http://192.168.6.31:3000/pino/sillyhome-next
|
||||||
|
|||||||
@@ -447,7 +447,7 @@ def get_actuator_detail(actuator_entity_id: str, request: Request) -> ActuatorRe
|
|||||||
}
|
}
|
||||||
compact_snapshots = [
|
compact_snapshots = [
|
||||||
snapshot.model_copy(update={"patterns": []})
|
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(
|
compact_behavior = record.behavior.model_copy(
|
||||||
update={
|
update={
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ from app.ha.models import HaEntitySummary
|
|||||||
from app.ha.reader import HaReader
|
from app.ha.reader import HaReader
|
||||||
|
|
||||||
_MAX_PATTERNS = 500
|
_MAX_PATTERNS = 500
|
||||||
|
_MAX_MODEL_SNAPSHOTS = 3
|
||||||
|
_MAX_SNAPSHOT_PATTERNS = 120
|
||||||
_MAX_EXECUTION_EVENTS = 100
|
_MAX_EXECUTION_EVENTS = 100
|
||||||
_ACTION_LOGBOOK_TOLERANCE = timedelta(seconds=10)
|
_ACTION_LOGBOOK_TOLERANCE = timedelta(seconds=10)
|
||||||
_CONTEXT_TRIGGER_TOLERANCE = timedelta(seconds=3)
|
_CONTEXT_TRIGGER_TOLERANCE = timedelta(seconds=3)
|
||||||
@@ -937,6 +939,11 @@ class BehaviorEngine:
|
|||||||
record: ActuatorRecord,
|
record: ActuatorRecord,
|
||||||
behavior: BehaviorState,
|
behavior: BehaviorState,
|
||||||
) -> ActuatorRecord:
|
) -> ActuatorRecord:
|
||||||
|
behavior = behavior.model_copy(
|
||||||
|
update={
|
||||||
|
"model_snapshots": _compact_model_snapshots(behavior.model_snapshots),
|
||||||
|
}
|
||||||
|
)
|
||||||
updated = record.model_copy(
|
updated = record.model_copy(
|
||||||
update={
|
update={
|
||||||
"behavior": behavior,
|
"behavior": behavior,
|
||||||
@@ -1132,10 +1139,19 @@ def _next_model_snapshots(
|
|||||||
high_confidence_sample_count=trusted_actions,
|
high_confidence_sample_count=trusted_actions,
|
||||||
average_confidence=round(average_confidence, 4),
|
average_confidence=round(average_confidence, 4),
|
||||||
incorrect_feedback_count=incorrect_feedback_count,
|
incorrect_feedback_count=incorrect_feedback_count,
|
||||||
patterns=patterns,
|
patterns=patterns[-_MAX_SNAPSHOT_PATTERNS:],
|
||||||
reason=reason,
|
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:
|
def _average(values: list[float]) -> float:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class Settings:
|
|||||||
prediction_interval_seconds: int = 60
|
prediction_interval_seconds: int = 60
|
||||||
execution_cooldown_seconds: int = 900
|
execution_cooldown_seconds: int = 900
|
||||||
timezone: str = "Europe/Berlin"
|
timezone: str = "Europe/Berlin"
|
||||||
|
ha_timeout_seconds: int = 25
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ha_configured(self) -> bool:
|
def ha_configured(self) -> bool:
|
||||||
@@ -55,4 +56,5 @@ def load_settings() -> Settings:
|
|||||||
60, int(os.getenv("SILLYHOME_EXECUTION_COOLDOWN_SECONDS", "900"))
|
60, int(os.getenv("SILLYHOME_EXECUTION_COOLDOWN_SECONDS", "900"))
|
||||||
),
|
),
|
||||||
timezone=os.getenv("SILLYHOME_TIMEZONE", "Europe/Berlin"),
|
timezone=os.getenv("SILLYHOME_TIMEZONE", "Europe/Berlin"),
|
||||||
|
ha_timeout_seconds=max(5, int(os.getenv("SILLYHOME_HA_TIMEOUT_SECONDS", "25"))),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|||||||
settings=HaClientSettings(
|
settings=HaClientSettings(
|
||||||
url=cast(str, settings.ha_url),
|
url=cast(str, settings.ha_url),
|
||||||
token=cast(str, settings.ha_token),
|
token=cast(str, settings.ha_token),
|
||||||
|
timeout_seconds=settings.ha_timeout_seconds,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
app.state.ha_reader = HaReader(client=client)
|
app.state.ha_reader = HaReader(client=client)
|
||||||
@@ -105,7 +106,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="SillyHome Next API",
|
title="SillyHome Next API",
|
||||||
description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.",
|
description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.",
|
||||||
version="1.5.1",
|
version="1.5.2",
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
)
|
)
|
||||||
app.state.settings = load_settings()
|
app.state.settings = load_settings()
|
||||||
@@ -213,8 +214,8 @@ async def _ha_event_listener(app: FastAPI, client: HaClient) -> None:
|
|||||||
try:
|
try:
|
||||||
async with websockets.connect(
|
async with websockets.connect(
|
||||||
ws_url,
|
ws_url,
|
||||||
ping_interval=20,
|
ping_interval=30,
|
||||||
ping_timeout=10,
|
ping_timeout=30,
|
||||||
) as websocket:
|
) as websocket:
|
||||||
auth_required_msg = await websocket.recv()
|
auth_required_msg = await websocket.recv()
|
||||||
auth_required_data = json.loads(auth_required_msg)
|
auth_required_data = json.loads(auth_required_msg)
|
||||||
|
|||||||
32
docs/V1_5_2_OPERATING_GUIDE.md
Normal file
32
docs/V1_5_2_OPERATING_GUIDE.md
Normal 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.
|
||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "sillyhome-next"
|
name = "sillyhome-next"
|
||||||
version = "1.5.1"
|
version = "1.5.2"
|
||||||
description = "Lokales Smart-Home-Intelligenzsystem für Home Assistant"
|
description = "Lokales Smart-Home-Intelligenzsystem für Home Assistant"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -92,11 +92,11 @@ def test_ha_event_listener_processes_state_change(tmp_path: Path) -> None:
|
|||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
connect.assert_called_once_with(
|
connect.assert_called_once_with(
|
||||||
"ws://homeassistant:8123/api/websocket",
|
"ws://homeassistant:8123/api/websocket",
|
||||||
ping_interval=20,
|
ping_interval=30,
|
||||||
ping_timeout=10,
|
ping_timeout=30,
|
||||||
)
|
)
|
||||||
assert fake_ws.sent == [
|
assert fake_ws.sent == [
|
||||||
{"type": "auth", "access_token": "test-token"},
|
{"type": "auth", "access_token": "test-token"},
|
||||||
{"id": 1, "type": "subscribe_events", "event_type": "state_changed"},
|
{"id": 1, "type": "subscribe_events", "event_type": "state_changed"},
|
||||||
|
|||||||
Reference in New Issue
Block a user