# HG changeset patch # User Daniel O'Connor # Date 1505091485 -34200 # Node ID 2b7fb26f9114e623e1fe77644843ae1b87b3f43d # Parent 6e3ca5bfda04d3f5fd0ec81d30e92e7a60224dcc - Make an actual usable command line program. - Label things better. - Use tzlocal to determine local time zone. - Scale hour/minute ticks with data length. diff -r 6e3ca5bfda04 -r 2b7fb26f9114 agl.py --- a/agl.py Sun Sep 10 11:25:39 2017 +0930 +++ b/agl.py Mon Sep 11 10:28:05 2017 +0930 @@ -1,5 +1,6 @@ #!/usr/bin/env python +import argparse import ConfigParser import datetime import dateutil @@ -9,6 +10,7 @@ import requests import sqlite3 import sys +import tzlocal loginurl = 'https://command.aglsolar.com.au/api/v2/Account/LoginUser' dataurl = 'https://command.aglsolar.com.au/api/v2/graph/b8e08afb-818f-4d2d-9d28-5afe8fc76a32' @@ -26,7 +28,21 @@ return "UTC" +def valid_date(s): + try: + return datetime.datetime.strptime(s, "%Y-%m-%d") + except ValueError: + raise argparse.ArgumentTypeError("Not a valid date: '{0}'.".format(s)) + def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-u', '--update', help = 'Update data', action="store_true") + parser.add_argument('-g', '--graph', help = 'Produce graph', action="store_true") + parser.add_argument('-s', '--start', help = 'Start date for graph (YYYY-MM-DD)', type = valid_date) + parser.add_argument('-e', '--end', help = 'End date for graph (YYYY-MM-DD)', type = valid_date) + + args = parser.parse_args() + conf = ConfigParser.ConfigParser() confname = os.environ['HOME'] + '/.agl.ini' conf.read(confname) @@ -34,28 +50,47 @@ password = conf.get('DEFAULT', 'password') dbfn = conf.get('DEFAULT', 'db') - if conf.has_option('DEFAULT', 'token'): - token = conf.get('DEFAULT', 'token') - else: - token = gettoken(username, password) - conf.set('DEFAULT', 'token', token) - conf.write(file(confname, 'w')) + if (args.start is None) ^ (args.end is None): + parser.error('Must specify start and end or neither') + + if not args.update and not args.graph: + parser.error('Nothing to do') - if len(sys.argv) > 1: - date = sys.argv[1] - else: - date = datetime.datetime.now().strftime('%Y-%m-%d') + start = args.start + if start is None: + start = datetime.date.today() + start = datetime.datetime(start.year, start.month, start.day) + + end = args.end + if end is None: + end = start + datetime.timedelta(days = 1) + end = datetime.datetime(end.year, end.month, end.day) dbh = sqlite3.connect(dbfn, detect_types = sqlite3.PARSE_DECLTYPES) cur = dbh.cursor() - data = getdata(token, date, date) - if data == None: - token = gettoken(username, password) - data = getdata(token, date, date) - if data == None: - print('Unable to fetch data') - updatedb(cur, data) - dbh.commit() + if args.update: + date = start + while date < end: + if conf.has_option('DEFAULT', 'token'): + token = conf.get('DEFAULT', 'token') + else: + token = gettoken(username, password) + conf.set('DEFAULT', 'token', token) + conf.write(file(confname, 'w')) + + data = getdata(token, date, date) + if data == None: + #print('Getting new token') + token = gettoken(username, password) + data = getdata(token, date, date) + if data == None: + print('Unable to fetch data') + updatedb(cur, data) + dbh.commit() + date += datetime.timedelta(days = 1) + + if args.graph: + graph(cur, ['battery_charge', 'power_imported', 'power_exported', 'power_consumed', 'power_generated'], start, end) def mkdb(cur): cur.execute(''' @@ -94,7 +129,26 @@ 'site_cons_battery' : 'Watt', 'site_cons_grid' : 'Watt', 'site_cons_pv' : 'Watt' - } +} + +names = { + 'battery_charge' : 'Battery Charge', + 'battery_power' : 'Battery Power', + 'power_consumed' : 'Power Consumed', + 'power_expected' : 'Power Expected', + 'power_exported' : 'Power Expected', + 'power_generated' : 'Power Generated', + 'power_imported' : 'Power Imported', + 'estimated_savings' : 'Estimated Savings', + 'pv_forecast' : 'PV Forecast', + 'pv_gen_battery' : 'PV Generation Battery', + 'pv_gen_grid' : 'PV Generation Grid', + 'pv_gen_site' : 'PV Generation Site', + 'site_cons_battery' : 'Site Consumption Batter', + 'site_cons_grid' : 'Site Consumption Grid', + 'site_cons_pv' : 'Site Consumption PV' +} + def graph(cur, cols, start, end): import numpy import matplotlib @@ -120,7 +174,7 @@ if unit != yaxisunits1 and unit != yaxisunits2: raise exceptions.Exception('Asked to graph >2 different units') - ltname = 'Australia/Adelaide' + ltname = tzlocal.get_localzone().zone # Why is this so hard.. lt = dateutil.tz.gettz(ltname) utc = dateutil.tz.gettz('UTC') matplotlib.rcParams['timezone'] = ltname @@ -139,13 +193,16 @@ cur.execute('SELECT t_stamp, ' + colstr + ' FROM agl WHERE t_stamp > ? AND t_stamp < ? ORDER BY t_stamp', (start, end)) ary = numpy.array(cur.fetchall()) + if ary.shape[0] == 0: + print('No data') + return # Convert naive UTC to proper UTC then adjust to local time xdata = map(lambda f: f.replace(tzinfo = utc).astimezone(lt), ary[:,0]) for idx in range(len(cols)): if units[cols[idx]] == yaxisunits1: - ax1lines.append([xdata, ary[:,idx + 1], cols[idx], colourlist[colouridx]]) + ax1lines.append([xdata, ary[:,idx + 1], names[cols[idx]], colourlist[colouridx]]) else: - ax2lines.append([xdata, ary[:,idx + 1], cols[idx], colourlist[colouridx]]) + ax2lines.append([xdata, ary[:,idx + 1], names[cols[idx]], colourlist[colouridx]]) colouridx += 1 fig = matplotlib.pylab.figure() @@ -165,13 +222,13 @@ ax2.plot(line[0], line[1], label = line[2], color = line[3]) ax2.legend(loc = 'upper right') - # Rotate X axis labels + ndays = int(max(1, round(((end - start).total_seconds()) / 86400))) for ax in fig.get_axes(): ax.set_xlim([start, end]) ax.xaxis.grid(True) ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%d %b\n%H:%M')) - ax.xaxis.set_major_locator(matplotlib.dates.HourLocator(interval = 2)) - ax.xaxis.set_minor_locator(matplotlib.dates.MinuteLocator(interval = 5)) + ax.xaxis.set_major_locator(matplotlib.dates.HourLocator(interval = 2 * ndays)) + ax.xaxis.set_minor_locator(matplotlib.dates.MinuteLocator(interval = 5 * ndays)) for label in ax.get_xticklabels(): label.set_ha('center') label.set_rotation(90) @@ -207,9 +264,10 @@ return json.decoder.JSONDecoder().decode(reply.content)['access_token'] def getdata(token, startdate, enddate): + #print('getting ' + startdate.strftime('%Y-%m-%d')) reply = requests.request('GET', dataurl, params = { - 'startDate' : startdate, - 'endDate' : enddate, + 'startDate' : startdate.strftime('%Y-%m-%d'), + 'endDate' : enddate.strftime('%Y-%m-%d'), 'granularity' : 'Minute', 'metrics' : 'read', 'units' : 'W',