210416.1 · 7a550c7a1cc09a9351900c6c2b616b4611dbdf21
bin: add home automation alerts for high voltage
I just had the Sense installed because we're concerned about high voltage. Sense doesn't seem to have a good way to monitor this data unlike wattage, which is quite visible. So we use their undocumented API to record our own time series and alert us if it gets too high.
reading declarations
@@ -0,0 +1,22 @@+#!/bin/zsh++# This script alerts me if the total combined voltage exceeds a certain+# threshold. I wrote this script (and installed Sense), since we had+# collected some very rough and patchy evidence that we were getting voltage+# spikes into the house. While Sense has some support for notifications, it+# doesn't make voltage data easy to track over time or get notified if it+# spikes. Thankfully, they do have an unofficial API that we scrape using+# homeaut-sense-realtime-log. We then read from that log periodically via this+# script, which is meant to be setup as a cronjob.++export HOME=/home/andrew+. $HOME/.zshrc++LOG=/m/log/alert-voltage.log++alerts="$(homeaut-sense-realtime-alert)"+if [ -n "$alerts" ]; then+ date+ echo "$alerts" | cmail --subject "home voltage alert"+ echo ===============================+fi 2>&1 | tee -a "$LOG"
bin/homeaut-sense-realtime-alert
@@ -0,0 +1,26 @@+#!/usr/bin/env python++import csv+import json+import time++LOG_CSV_FILE = '/m/data/home/sense/realtime.csv'+TIME_WINDOW_SECONDS = 90 * 60++VOLTAGE_DIFF_THRESHOLD = 2.0+VOLTAGE_MAX_THRESHOLD = 249.0++if __name__ == '__main__':+ now = time.time()+ with open(LOG_CSV_FILE) as f:+ for row in csv.DictReader(f):+ if now - float(row['timestamp']) > TIME_WINDOW_SECONDS:+ continue+ v1 = float(row['voltage1'])+ v2 = float(row['voltage2'])+ diff = abs(v1 - v2)+ total = v1 + v2+ if diff >= VOLTAGE_DIFF_THRESHOLD:+ print(f'voltage difference {diff} is high')+ if total >= VOLTAGE_MAX_THRESHOLD:+ print(f'combined voltage {total} is high')
bin/homeaut-sense-realtime-log
@@ -0,0 +1,60 @@+#!/usr/bin/env /home/andrew/clones/sense/ve/bin/python++import csv+import json+import subprocess+import time++import sense_energy++LOG_CSV_FILE = '/m/data/home/sense/realtime.csv'+USERNAME = 'jamslam@gmail.com'+PASS_PATH = 'home/sense'+FIELDS = ['timestamp', 'voltage1', 'voltage2', 'hertz', 'watts', 'raw']+LOG_EVERY = 1000+BACKOFF = 300 # seconds to wait if a timeout occurs+DIALECT = csv.unix_dialect+DIALECT.quoting = csv.QUOTE_MINIMAL+++def get_password():+ stdout = subprocess.check_output(['pass', 'home/sense'])+ return stdout.decode('utf-8').splitlines()[0].strip()+++def loop(wtr):+ last_log = time.time()++ sense = sense_energy.Senseable()+ sense.authenticate(USERNAME, get_password())+ sense.update_realtime()+ for i, v in enumerate(sense.get_realtime_stream()):+ voltage = v.get('voltage', [0.0, 0.0])+ wtr.writerow({+ 'timestamp': time.time(),+ 'voltage1': voltage[0],+ 'voltage2': voltage[1],+ 'hertz': v.get('hz', 0.0),+ 'watts': v.get('w', 0.0),+ 'raw': json.dumps(v),+ })+ if i == 0 or i % LOG_EVERY != 0:+ continue+ diff = time.time() - last_log+ print(f'logged {LOG_EVERY} events in {diff:0.03} seconds')+ last_log = time.time()+++if __name__ == '__main__':+ print(f'{LOG_CSV_FILE}: writing Sense realtime data events to log')+ with open(LOG_CSV_FILE, mode='a', buffering=1) as out:+ wtr = csv.DictWriter(+ out,+ fieldnames=FIELDS,+ dialect=DIALECT,+ )+ while True:+ try:+ loop(wtr)+ except sense_energy.sense_exceptions.SenseAPITimeoutException:+ time.sleep(BACKOFF)