Hausmeister: Vitalchecks Zigbee-Heizung CT152
API/z2m-Erreichbarkeit, erwartete TRVs online, Batterie <20%, Abwesenheit ohne Backup — Telegram wie übrige Monitoring-Alarme.
This commit is contained in:
parent
f0abeb1255
commit
03099808f0
1 changed files with 127 additions and 0 deletions
|
|
@ -24,6 +24,7 @@ ALERT_COOLDOWN_SECONDS = {
|
|||
"default": 3600,
|
||||
"error_rate": 1800,
|
||||
"hermes": 7200,
|
||||
"heizung": 7200,
|
||||
"backup": 21600,
|
||||
"mirror_sync": 21600,
|
||||
}
|
||||
|
|
@ -102,6 +103,117 @@ def _is_host_suppressed(host: str, suppressed_hosts: set) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
|
||||
# Zigbee-Heizung CT152 (heizsteuerung) — erwartet online; Platzhalter ohne TRV weglassen
|
||||
HEIZ_API_BASES = [
|
||||
os.environ.get("HEIZSTEUERUNG_API_BASE", "").rstrip("/"),
|
||||
"http://100.105.175.123:8090", # Tailscale CT152
|
||||
"http://192.168.178.30:8090", # LAN (nur wenn erreichbar)
|
||||
]
|
||||
HEIZ_API_BASES = [u for u in HEIZ_API_BASES if u]
|
||||
HEIZ_Z2M_URLS = [
|
||||
"http://100.105.175.123:8080",
|
||||
"http://192.168.178.30:8080",
|
||||
]
|
||||
HEIZ_EXPECTED_ONLINE = {
|
||||
"wohnstube_links",
|
||||
"wohnstube_rechts",
|
||||
"schlafzimmer",
|
||||
"flur_unten",
|
||||
"trockenraum",
|
||||
"buero",
|
||||
"ankleideraum_oben",
|
||||
"wc_unten",
|
||||
}
|
||||
HEIZ_BATTERY_WARN = 20
|
||||
|
||||
|
||||
def check_heizkoerper() -> list[str]:
|
||||
"""Vitalfunktionen Zigbee-Heizung CT152: API, z2m, TRV-Online, Batterie."""
|
||||
alerts: list[str] = []
|
||||
headers = {"User-Agent": "Mozilla/5.0 (Hausmeister-Bot/1.0 heizung-check)"}
|
||||
|
||||
zones = None
|
||||
last_err = None
|
||||
api_base = None
|
||||
for base in HEIZ_API_BASES:
|
||||
try:
|
||||
r = requests.get(base + "/zones", timeout=8, headers=headers)
|
||||
if r.status_code != 200:
|
||||
last_err = f"HTTP {r.status_code}"
|
||||
continue
|
||||
zones = r.json()
|
||||
api_base = base
|
||||
break
|
||||
except Exception as e: # noqa: BLE001
|
||||
last_err = str(e)[:80]
|
||||
if zones is None:
|
||||
alerts.append(
|
||||
f"🔴 Heizung: heizsteuerung API nicht erreichbar ({HEIZ_API_BASES[0]}): {last_err}"
|
||||
)
|
||||
return alerts
|
||||
if not isinstance(zones, list):
|
||||
alerts.append("🔴 Heizung: /zones liefert unerwartete Antwort")
|
||||
return alerts
|
||||
|
||||
by_id = {z.get("id"): z for z in zones if isinstance(z, dict) and z.get("id")}
|
||||
offline = sorted(
|
||||
zid for zid in HEIZ_EXPECTED_ONLINE
|
||||
if zid not in by_id or not by_id[zid].get("online")
|
||||
)
|
||||
if offline:
|
||||
alerts.append(
|
||||
"🔴 Heizung: TRV offline (erwartet online): " + ", ".join(offline)
|
||||
)
|
||||
|
||||
low_batt = []
|
||||
for zid in sorted(HEIZ_EXPECTED_ONLINE):
|
||||
z = by_id.get(zid) or {}
|
||||
if not z.get("online"):
|
||||
continue
|
||||
batt = z.get("battery")
|
||||
if batt is None:
|
||||
continue
|
||||
try:
|
||||
if float(batt) < HEIZ_BATTERY_WARN:
|
||||
low_batt.append(f"{zid} {int(float(batt))}%")
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if low_batt:
|
||||
alerts.append(
|
||||
f"⚠️ Heizung: Batterie <{HEIZ_BATTERY_WARN}%: " + ", ".join(low_batt)
|
||||
)
|
||||
|
||||
# z2m Frontend — Coordinator/UI tot?
|
||||
z2m_ok = False
|
||||
z2m_err = None
|
||||
for url in HEIZ_Z2M_URLS:
|
||||
try:
|
||||
r = requests.get(url, timeout=6, headers=headers, allow_redirects=True)
|
||||
if r.status_code < 500:
|
||||
z2m_ok = True
|
||||
break
|
||||
z2m_err = f"HTTP {r.status_code}"
|
||||
except Exception as e: # noqa: BLE001
|
||||
z2m_err = str(e)[:80]
|
||||
if not z2m_ok:
|
||||
alerts.append(f"🔴 Heizung: zigbee2mqtt Frontend nicht erreichbar: {z2m_err}")
|
||||
|
||||
# Abwesenheit laeuft, aber Backup leer = riskanter Zustand
|
||||
try:
|
||||
r = requests.get(api_base + "/abwesenheit", timeout=6, headers=headers)
|
||||
if r.status_code == 200:
|
||||
away = r.json()
|
||||
if away.get("active") and not (away.get("backup_setpoints") or {}):
|
||||
alerts.append(
|
||||
"⚠️ Heizung: Abwesenheit AN ohne Backup-Sollwerte"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return alerts
|
||||
|
||||
|
||||
def check_hermes() -> list[str]:
|
||||
"""Prueft Hermes (CT151) als kritischen Dienst ueber HTTP und Loki."""
|
||||
alerts = []
|
||||
|
|
@ -176,6 +288,7 @@ def check_all(state: dict | None = None) -> list[str]:
|
|||
suppressed_names = config.get_suppressed_container_names(cfg)
|
||||
alerts = []
|
||||
alerts.extend(check_hermes())
|
||||
alerts.extend(check_heizkoerper())
|
||||
alerts.extend(loki_client.check_wp_mirror_sync())
|
||||
|
||||
containers = proxmox_client.get_all_containers(
|
||||
|
|
@ -426,6 +539,18 @@ def _alert_key(alert_text: str) -> str:
|
|||
m = re.search(r"([\w.\-]+\.service)", alert_text)
|
||||
unit = m.group(1) if m else "generic"
|
||||
return hashlib.md5(("hermes|log|" + unit).encode()).hexdigest()
|
||||
if "Heizung:" in alert_text:
|
||||
if "API nicht erreichbar" in alert_text:
|
||||
return hashlib.md5(b"heizung|api").hexdigest()
|
||||
if "zigbee2mqtt Frontend" in alert_text:
|
||||
return hashlib.md5(b"heizung|z2m").hexdigest()
|
||||
if "TRV offline" in alert_text:
|
||||
return hashlib.md5(b"heizung|trv_offline").hexdigest()
|
||||
if "Batterie" in alert_text:
|
||||
return hashlib.md5(b"heizung|battery").hexdigest()
|
||||
if "Abwesenheit AN ohne Backup" in alert_text:
|
||||
return hashlib.md5(b"heizung|away_backup").hexdigest()
|
||||
return hashlib.md5(("heizung|" + alert_text[:60]).encode()).hexdigest()
|
||||
return hashlib.md5(alert_text.encode()).hexdigest()
|
||||
|
||||
|
||||
|
|
@ -450,6 +575,8 @@ def _alert_category(alert_text: str) -> str:
|
|||
return "restart"
|
||||
if "Hermes" in alert_text:
|
||||
return "hermes"
|
||||
if "Heizung:" in alert_text:
|
||||
return "heizung"
|
||||
if "Backup" in alert_text or "PBS" in alert_text:
|
||||
return "backup"
|
||||
if "Memory läuft ab" in alert_text:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue