Mercurial > ~darius > hgwebdir.cgi > pw2log
view pw2log.py @ 5:c02b765b4eb3 default tip
Add grafana dashboard configuration.
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Fri, 21 Jul 2023 19:57:53 +0930 |
parents | 8990981c60a0 |
children |
line wrap: on
line source
#!/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 # https://github.com/jrester/tesla_powerwall from tesla_powerwall import APIError, 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) for opt in ('ip', 'username', 'password'): if not cp.has_option('pw', opt): print('pw section missing %s parameter' % (opt,), 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) keepfh = None else: fh = RotatingFileHandler(logfile, maxBytes = 2000, backupCount = 10) fh.setFormatter(fmt) logger.addHandler(fh) keepfhs = [fh.stream.fileno()] # XXX: gross if pidfile == None: ctx = NullContextManager() else: try: #fh = open('/tmp/pw2errs.log', 'a') fh = None ctx = daemon.DaemonContext(pidfile = daemon.pidfile.PIDLockFile(pidfile), stdout = fh, stderr = fh, files_preserve = keepfhs) except Exception as e: logger.critical('Unable to get daemon context') try: with ctx: logger.critical('Starting') try: collectdata(cp.get('pw', 'ip'), cp.get('pw', 'username'), cp.get('pw', 'password'), cp.get('db', 'dsn'), cp.getint('db', 'logtime')) except Exception as e: logger.critical('Unable to collect data: ' + str(e)) except Exception as e: logger.critical('Unable to enter daemon context: ' + str(e)) def collectdata(pwip, username, password, dsn, logtime): dbh = psycopg2.connect(dsn) cur = dbh.cursor() pw = None 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 | # if not pw: first = True pw = Powerwall(pwip) pw.login(password, username) meters = pw.get_meters() grid_volts = meters.site.average_voltage grid_freq = meters.site.frequency grid_power = meters.site.instant_power load_power = meters.load.instant_power battery_power = meters.battery.instant_power solar_power = meters.solar.instant_power charge = pw.get_charge() except Exception as e: pw = None 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_volts, grid_freq, grid_power, load_power, battery_power, charge, solar_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 if first: logger.error('Logged OK') first = False 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()