view 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 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
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()