Files
SnarfCode/scripts/setup_homeassistant_notifications.py

246 lines
12 KiB
Python

#!/usr/bin/env python3
"""
Configure Home Assistant (14.4.4.20:8123) Notification Forwarding & Infrastructure Automations:
1. Updates gotify-discord-bridge on 140.44.4.71 with Home Assistant logbook.log watcher.
2. Creates 'script.snarfnet_broadcast' in Home Assistant for unified Gotify + Discord + Mobile App alerts.
3. Creates 'Snarfnet UPS Power & Exhaust Protocol' and 'Snarfnet Server Room Thermal Guard' automations.
4. Enhances the 4 Bambu H2D 3D Printer automations to also broadcast to Gotify & Discord #home-assistant.
5. Triggers a live end-to-end verification notification with real-time Server Room & UPS telemetry.
"""
import json
import sys
import time
import urllib.request
import paramiko
sys.stdout.reconfigure(encoding="utf-8")
HA_URL = "http://14.4.4.20:8123"
HA_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkZWNlMWI5ZjJhZDQ0MTRmODU4NGYzN2I1YjgwZDQxMiIsImlhdCI6MTc5MzQ1MjQ2MywiZXhwIjoyMTA4ODEyNDYzfQ.JznpOqsCSnHz6aTVa8K9pQCz1816DC8SrmS1xn1Sm2k"
def ha_req(path, method="GET", data=None):
req = urllib.request.Request(
f"{HA_URL}{path}",
data=json.dumps(data).encode("utf-8") if data is not None else None,
headers={
"Authorization": f"Bearer {HA_TOKEN}",
"Content-Type": "application/json",
},
method=method,
)
with urllib.request.urlopen(req, timeout=12) as r:
body = r.read().decode("utf-8")
return json.loads(body) if body else {}
def main():
# 1. Update discord_bridge.py on 140.44.4.71 and restart bridge container
with open(r"c:\Users\Snarf\OneDrive\Desktop\code\SnarfCode\scripts\gotify_discord_bridge.py", "r", encoding="utf-8") as f:
bridge_code = f.read()
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect("140.44.4.71", username="snarf", password="Katzir476!", timeout=10)
sftp = ssh.open_sftp()
with sftp.file("/tmp/discord_bridge.py", "w") as rf:
rf.write(bridge_code)
sftp.close()
cmd = r"""echo 'Katzir476!' | sudo -S bash -c '
rm -f /var/lib/docker/volumes/gotify_gotify_data/_data/ha_last_logbook_when.txt /mnt/swarm_shared/legacy_volumes/gotify/ha_last_logbook_when.txt
cp /tmp/discord_bridge.py /var/lib/docker/volumes/gotify_gotify_data/_data/discord_bridge.py
cp /tmp/discord_bridge.py /mnt/swarm_shared/legacy_volumes/gotify/discord_bridge.py
chmod 755 /var/lib/docker/volumes/gotify_gotify_data/_data/discord_bridge.py /mnt/swarm_shared/legacy_volumes/gotify/discord_bridge.py
docker service scale gotify_gotify-discord-bridge=0
docker rm -f $(docker ps -aq -f name=gotify_gotify-discord-bridge) 2>/dev/null || true
docker service scale gotify_gotify-discord-bridge=1
'"""
stdin, stdout, stderr = ssh.exec_command(cmd)
stdout.read()
print("Updated and restarted gotify_gotify-discord-bridge on 140.44.4.71.")
# 2. Provision script.snarfnet_broadcast in Home Assistant
broadcast_script = {
"alias": "Snarfnet Broadcast Notification",
"description": "Broadcasts alerts to Gotify, Discord #home-assistant webhook, and Pixel Companion Apps",
"mode": "parallel",
"fields": {
"title": {"description": "Notification title", "example": "Smart Home Alert"},
"message": {"description": "Notification body", "example": "Event details"},
"notification_id": {"description": "Unique notification key", "example": "snarfnet_alert"},
},
"sequence": [
{
"action": "logbook.log",
"data": {
"name": "{{ title | default('Home Assistant Alert') }}",
"message": "{{ message }}",
"entity_id": "script.snarfnet_broadcast",
},
},
{
"action": "persistent_notification.create",
"data": {
"title": "{{ title | default('Home Assistant Alert') }}",
"message": "{{ message }}",
"notification_id": "{{ notification_id | default('snarfnet_' ~ (now().timestamp() | int)) }}",
},
},
{
"action": "notify.mobile_app_pixel_10_pro_fold",
"data": {
"title": "{{ title | default('Home Assistant Alert') }}",
"message": "{{ message }}",
},
},
{
"action": "notify.mobile_app_pixel_9_pro_fold",
"data": {
"title": "{{ title | default('Home Assistant Alert') }}",
"message": "{{ message }}",
},
},
],
}
ha_req("/api/config/script/config/snarfnet_broadcast", method="POST", data=broadcast_script)
print("Configured script.snarfnet_broadcast in Home Assistant.")
# 3. Provision UPS Power State & Exhaust Fan Automation
ups_automation = {
"id": "1793453000001",
"alias": "Snarfnet UPS Power & Exhaust Protocol",
"description": "Broadcasts UPS state transitions to Gotify/Discord/Mobile and engages Server Closet exhaust fan on battery",
"triggers": [
{
"trigger": "state",
"entity_id": ["sensor.server_closet_status_data"],
}
],
"conditions": [
{
"condition": "template",
"value_template": "{{ trigger.from_state.state != trigger.to_state.state and trigger.to_state.state not in ['unknown', 'unavailable'] }}",
}
],
"actions": [
{
"action": "script.snarfnet_broadcast",
"data": {
"title": "⚡ UPS Status Changed: {{ states('sensor.server_closet_status') }} ({{ states('sensor.server_closet_status_data') }})",
"message": "Server Closet UPS transitioned from {{ trigger.from_state.state }} to {{ trigger.to_state.state }}. Battery: {{ states('sensor.server_closet_battery_charge') }}% | Load: {{ states('sensor.server_closet_load') }}% | Input: {{ states('sensor.server_closet_input_voltage') }}V | Closet Temp: {{ states('sensor.server_room_temperature_temperature') }}°F.",
"notification_id": "snarfnet_ups_state",
},
},
{
"choose": [
{
"conditions": [
{
"condition": "template",
"value_template": "{{ 'OB' in states('sensor.server_closet_status_data') or 'LB' in states('sensor.server_closet_status_data') }}",
}
],
"sequence": [
{
"action": "fan.set_percentage",
"target": {"entity_id": "fan.server_closet_switch"},
"data": {"percentage": 100},
}
],
}
]
},
],
"mode": "queued",
}
ha_req("/api/config/automation/config/1793453000001", method="POST", data=ups_automation)
print("Configured automation 'Snarfnet UPS Power & Exhaust Protocol' (1793453000001).")
# 4. Provision Server Room Thermal Guard Automation
thermal_automation = {
"id": "1793453000002",
"alias": "Snarfnet Server Room Thermal Guard",
"description": "Automatically engages Server Closet exhaust fan and broadcasts alert if Server Room exceeds 82°F",
"triggers": [
{
"trigger": "numeric_state",
"entity_id": ["sensor.server_room_temperature_temperature"],
"above": 82,
"for": {"hours": 0, "minutes": 2, "seconds": 0},
}
],
"conditions": [],
"actions": [
{
"action": "fan.set_percentage",
"target": {"entity_id": "fan.server_closet_switch"},
"data": {"percentage": 100},
},
{
"action": "script.snarfnet_broadcast",
"data": {
"title": "🌡️ High Server Room Temperature Warning",
"message": "Server Room temperature has reached {{ states('sensor.server_room_temperature_temperature') }}°F (Humidity: {{ states('sensor.server_room_temperature_humidity') }}%). Server Closet exhaust fan engaged at 100%.",
"notification_id": "snarfnet_thermal_warning",
},
},
],
"mode": "single",
}
ha_req("/api/config/automation/config/1793453000002", method="POST", data=thermal_automation)
print("Configured automation 'Snarfnet Server Room Thermal Guard' (1793453000002).")
# 5. Enhance the 4 existing 3D Printer automations to call script.snarfnet_broadcast
printer_autos = [
("1788367985433", "🖨️ 3D Print Started", "A 3D print has started on Bambu H2D.", "h2d_print_start"),
("1788368709567", "✅ 3D Print Completed", "A 3D print has finished on Bambu H2D.", "h2d_print_complete"),
("1788370272217", "⚠️ 3D Print Stalled", "A 3D print has stalled/paused on Bambu H2D!", "h2d_print_stalled"),
("1788370321997", "🚨 3D Print Failed", "A 3D print has failed on Bambu H2D!", "h2d_print_failed"),
]
for aid, title, msg, nid in printer_autos:
cfg = ha_req(f"/api/config/automation/config/{aid}")
# Filter out old persistent_notification/snarfnet_broadcast actions and append clean script.snarfnet_broadcast
actions = [a for a in cfg.get("actions", []) if a.get("action") not in ("persistent_notification.create", "script.snarfnet_broadcast")]
actions.append(
{
"action": "script.snarfnet_broadcast",
"metadata": {},
"data": {
"title": title,
"message": msg,
"notification_id": nid,
},
}
)
cfg["actions"] = actions
ha_req(f"/api/config/automation/config/{aid}", method="POST", data=cfg)
print(f"Enhanced 3D Printer automation [{aid}] '{cfg.get('alias')}' with script.snarfnet_broadcast.")
ha_req("/api/services/script/reload", method="POST", data={})
ha_req("/api/services/automation/reload", method="POST", data={})
# Wait 5s for restarted bridge container to finish [INIT-HA]
time.sleep(5)
# 6. Trigger live test via script.snarfnet_broadcast
ha_req(
"/api/services/script/snarfnet_broadcast",
method="POST",
data={
"title": "Home Assistant <-> Gotify <-> Discord Linked",
"message": "Live Telemetry: Server Room {{ states('sensor.server_room_temperature_temperature') }}°F ({{ states('sensor.server_room_temperature_humidity') }}% RH) | UPS: {{ states('sensor.server_closet_status') }} ({{ states('sensor.server_closet_battery_charge') }}% Battery, {{ states('sensor.server_closet_load') }}% Load, {{ states('sensor.server_closet_input_voltage') }}V).",
"notification_id": "snarfnet_ha_bridge_verified",
},
)
print("Triggered live script.snarfnet_broadcast test in Home Assistant.")
time.sleep(5)
stdin, stdout, stderr = ssh.exec_command("echo 'Katzir476!' | sudo -S docker logs $(docker ps -q -f name=gotify_gotify-discord-bridge) --tail 25 2>&1")
print("Bridge logs:\n", stdout.read().decode("utf-8", errors="ignore"))
ssh.close()
if __name__ == "__main__":
main()