246 lines
12 KiB
Python
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()
|