Mercurial > ~darius > hgwebdir.cgi > pw2log
diff pw2log.py @ 0:a5a196b3ba63
Initial version of powerwall logger
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Wed, 20 Nov 2019 13:12:45 +1030 |
parents | |
children | 7edf54ec37f2 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pw2log.py Wed Nov 20 13:12:45 2019 +1030 @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 + +import configparser +import daemon +import daemon.pidfile +import datetime +import logging +from logging.handlers import RotatingFileHandler +import psycopg2 +import requests +import sys +import tesla_powerwall # https://github.com/jrester/tesla_powerwall +import time + +# Standard in 3.7.. +class NullContextManager(object): + def __init__(self, dummy_resource=None): + self.dummy_resource = dummy_resource + def __enter__(self): + return self.dummy_resource + def __exit__(self, *args): + pass + +# Otherwise it's very noisy +logging.getLogger('tesla_powerwall').setLevel(logging.WARN) + +def main(): + if len(sys.argv) != 2: + print('Bad usage', file = sys.stderr) + print('\t%s conf.ini' % (sys.argv[0]), file = sys.stderr) + sys.exit(1) + + cp = configparser.ConfigParser() + cp.read(sys.argv[1]) + if not cp.has_section('db'): + print('Config file missing db section', file = sys.stderr) + sys.exit(1) + if not cp.has_option('db', 'dsn'): + print('db section missing dsn parameter', file = sys.stderr) + sys.exit(1) + if not cp.has_option('db', 'logtime'): + print('db section missing logtime parameter', file = sys.stderr) + sys.exit(1) + + if not cp.has_section('pw'): + print('Config file missing pw section', file = sys.stderr) + sys.exit(1) + if not cp.has_option('pw', 'ip'): + print('pw section missing ip parameter', file = sys.stderr) + sys.exit(1) + + if cp.has_option('pw2log', 'logfile'): + logfile = cp.get('pw2log', 'logfile') + else: + logfile = None + if cp.has_option('pw2log', 'pidfile'): + pidfile = cp.get('pw2log', 'pidfile') + else: + pidfile = None + + global logger + logger = logging.getLogger('pw2log') + logger.setLevel(logging.WARN) + fmt = logging.Formatter('%(asctime)s: %(message)s', datefmt = '%Y/%m/%d %H:%M:%S') + if logfile == None: + ch = logging.StreamHandler() + ch.setFormatter(fmt) + logger.addHandler(ch) + else: + fh = RotatingFileHandler(logfile, maxBytes = 2000, backupCount = 10) + fh.setFormatter(fmt) + logger.addHandler(fh) + + if pidfile == None: + ctx = NullContextManager() + else: + ctx = daemon.DaemonContext(pidfile = daemon.pidfile.PIDLockFile(pidfile)) + + with ctx: + logger.critical('Starting') + collectdata(cp.get('pw', 'ip'), cp.get('db', 'dsn'), cp.getint('db', 'logtime')) + +def collectdata(pwip, dsn, logtime): + dbh = psycopg2.connect(dsn) + cur = dbh.cursor() + + pw = tesla_powerwall.PowerWall(pwip) + + while True: + try: + # As per.. https://github.com/vloschiavo/powerwall2 + # | | Load | Grid | Battery | Solar | + # |==========+==============+===================+======================+==================| + # | Positive | Supply house | Drawing from grid | Drawing from battery | Solar generation | + # | Negative | n/a | Feeding grid | Charging battery | n/a | + # + grid = pw.grid + load = pw.load + battery = pw.battery + solar = pw.solar + charge = pw.charge + except requests.ConnectionError as e: + logger.error('Error communicating with Powerwall: ' + str(e)) + time.sleep(300) + continue + try: + cur.execute('INSERT INTO pw2 (date, grid_voltage, grid_freq, grid_power, load_power, battery_power, battery_charge, solar_power) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)', + (datetime.datetime.now(), grid.instant_average_voltage, grid.frequency, grid.instant_power, load.instant_power, battery.instant_power, charge, solar.instant_power)) + dbh.commit() + except psycopg2.OperationalError as e: + logger.error('Reconnecting after database error:' + str(e)) + time.sleep(60) + dbh = psycopg2.connect(dsn) + cur = dbh.cursor() + continue + + time.sleep(logtime) + +def createdb(dbh): + cur = dbh.cursor() + cur.execute(''' +CREATE TABLE pw2 ( + date TIMESTAMP WITH TIME ZONE PRIMARY KEY, + grid_voltage REAL, + grid_freq REAL, + grid_power REAL, + load_power REAL, + battery_power REAL, + battery_charge REAL, + solar_power REAL +); +''') + cur.execute(''' +CREATE INDEX IF NOT EXISTS pw2_date_brin_idx ON pw2 USING brin (date); +''') + +if __name__ == '__main__': + main()