diff agl.py @ 2:2b7fb26f9114

- Make an actual usable command line program. - Label things better. - Use tzlocal to determine local time zone. - Scale hour/minute ticks with data length.
author Daniel O'Connor <darius@dons.net.au>
date Mon, 11 Sep 2017 10:28:05 +0930
parents 6e3ca5bfda04
children 525a66486282
line wrap: on
line diff
--- 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',