Mercurial > ~darius > hgwebdir.cgi > agl
view agl.py @ 0:8d6ba11c1b76
Fetch & graphing code basically works.
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Fri, 08 Sep 2017 17:51:41 +0930 |
parents | |
children | 6e3ca5bfda04 |
line wrap: on
line source
#!/usr/bin/env python import ConfigParser import datetime import exceptions import json import os import pytz import requests import sqlite3 import sys loginurl = 'https://command.aglsolar.com.au/api/v2/Account/LoginUser' dataurl = 'https://command.aglsolar.com.au/api/v2/graph/b8e08afb-818f-4d2d-9d28-5afe8fc76a32' # ?endDate=2017-08-23&granularity=Minute&metrics=read&startDate=2017-08-23&units=W' logouturl = 'https://command.aglsolar.com.au/api/v2/Account/Logout' class UTC(datetime.tzinfo): def utcoffset(self, dt): return datetime.timedelta(0) def dst(self, dt): return datetime.timedelta(0) def tzname(self, dt): return "UTC" def main(): conf = ConfigParser.ConfigParser() confname = os.environ['HOME'] + '/.agl.ini' conf.read(confname) username = conf.get('DEFAULT', 'username') 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 len(sys.argv) > 1: date = sys.argv[1] else: date = datetime.datetime.now().strftime('%Y-%m-%d') 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() def mkdb(cur): cur.execute(''' CREATE TABLE IF NOT EXISTS agl ( t_stamp TIMESTAMP PRIMARY KEY, battery_charge NUMBER, battery_power NUMBER, power_consumed NUMBER, power_expected NUMBER, power_exported NUMBER, power_generated NUMBER, power_imported NUMBER, estimated_savings NUMBER, pv_forecast NUMBER, pv_gen_battery NUMBER, pv_gen_grid NUMBER, pv_gen_site NUMBER, site_cons_battery NUMBER, site_cons_grid NUMBER, site_cons_pv NUMBER )''') units = { 'battery_charge' : '%', 'battery_power' : 'Watt', 'power_consumed' : 'Watt', 'power_expected' : 'Watt', 'power_exported' : 'Watt', 'power_generated' : 'Watt', 'power_imported' : 'Watt', 'estimated_savings' : '$', 'pv_forecast' : 'Watt', 'pv_gen_battery' : 'Watt', 'pv_gen_grid' : 'Watt', 'pv_gen_site' : 'Watt', 'site_cons_battery' : 'Watt', 'site_cons_grid' : 'Watt', 'site_cons_pv' : 'Watt' } def graph(cur, cols, start, end): import numpy import matplotlib import matplotlib.dates import matplotlib.pylab #matplotlib.rcParams['timezone'] = pytz.timezone('Australia/Adelaide') colourlist = ['b','g','r','c','m','y','k'] yaxisunits1 = None yaxisunits2 = None ax1lines = [] ax2lines = [] colouridx = 0 for col in cols: unit = units[col] if yaxisunits1 == None: yaxisunits1 = unit if yaxisunits2 == None: if unit != yaxisunits1: yaxisunits2 = unit else: if unit != yaxisunits1 and unit != yaxisunits2: raise exceptions.Exception('Asked to graph >2 different units') cur.execute('SELECT t_stamp, ' + reduce(lambda a, b: a + ', ' + b, cols) + ' FROM agl WHERE t_stamp > ? AND t_stamp < ? ORDER BY t_stamp', (start, end)) ary = numpy.array(cur.fetchall()) for idx in range(len(cols)): if units[cols[idx]] == yaxisunits1: ax1lines.append([ary[:,0], ary[:,idx + 1], cols[idx], colourlist[colouridx]]) else: ax2lines.append([ary[:,0], ary[:,idx + 1], cols[idx], colourlist[colouridx]]) colouridx += 1 fig = matplotlib.pylab.figure() ax1 = fig.add_subplot(111) ax1.set_ylabel(yaxisunits1) for line in ax1lines: ax1.plot(line[0], line[1], label = line[2]) ax1.legend(loc = 'upper left') if yaxisunits2 != None: ax2 = ax1.twinx() ax2.set_ylabel(yaxisunits2) for line in ax2lines: ax2.plot(line[0], line[1], label = line[2], color = line[3]) ax2.legend(loc = 'upper right') # Rotate X axis labels for ax in fig.get_axes(): ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M')) ax.xaxis.set_major_locator(matplotlib.dates.HourLocator(interval = 2)) ax.xaxis.set_minor_locator(matplotlib.dates.MinuteLocator(interval = 5)) for label in ax.get_xticklabels(): label.set_ha('right') label.set_rotation(30) # Fudge margins to give more graph and less space fig.subplots_adjust(left = 0.10, right = 0.88, top = 0.95, bottom = 0.15) matplotlib.pyplot.show() def updatedb(cur, data): mkdb(cur) for d in data['reads']['data']: ts = datetime.datetime.strptime(d['t_stamp'], '%Y-%m-%dT%H:%M:%SZ') # Note we rename *energy* to *power* here to match what it actually means vals = [ts, d['battery_charge'], d['battery_energy'], d['energy_consumed'], d['energy_expected'], d['energy_exported'], d['energy_generated'], d['energy_imported'], d['estimated_savings'], d['pv_forecast'], d['pv_generation']['battery_energy'], d['pv_generation']['grid_energy'], d['pv_generation']['site_energy'], d['site_consumption']['battery_energy'], d['site_consumption']['grid_energy'], d['site_consumption']['pv_energy']] skip = True for v in vals[1:]: if v != None: skip = False break if skip: print('Skipping empty record at ' + str(ts)) continue cur.execute('INSERT OR IGNORE INTO agl(t_stamp, battery_charge, battery_power, power_consumed, power_expected, power_exported, power_generated, power_imported, estimated_savings, pv_forecast, pv_gen_battery, pv_gen_grid, pv_gen_site, site_cons_battery, site_cons_grid, site_cons_pv) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', vals) def gettoken(username, password): authblob = json.encoder.JSONEncoder().encode({'email' : username, 'password' : password}) reply = requests.request('POST', loginurl, data = authblob, headers = {'Content-Type' : 'application/json'}) if reply.status_code != 200: return None return json.decoder.JSONDecoder().decode(reply.content)['access_token'] def getdata(token, startdate, enddate): reply = requests.request('GET', dataurl, params = { 'startDate' : startdate, 'endDate' : enddate, 'granularity' : 'Minute', 'metrics' : 'read', 'units' : 'W', }, headers = { 'Authorization' : 'Bearer ' + token}) if reply.status_code != 200: return None return json.decoder.JSONDecoder().decode(reply.content) def logout(token): reply = requests.request('GET', logouturl, headers = { 'Authorization' : 'Bearer ' + token}) return reply.status_code == 200 if __name__ == '__main__': main()