comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:8d6ba11c1b76
1 #!/usr/bin/env python
2
3 import ConfigParser
4 import datetime
5 import exceptions
6 import json
7 import os
8 import pytz
9 import requests
10 import sqlite3
11 import sys
12
13 loginurl = 'https://command.aglsolar.com.au/api/v2/Account/LoginUser'
14 dataurl = 'https://command.aglsolar.com.au/api/v2/graph/b8e08afb-818f-4d2d-9d28-5afe8fc76a32'
15 # ?endDate=2017-08-23&granularity=Minute&metrics=read&startDate=2017-08-23&units=W'
16 logouturl = 'https://command.aglsolar.com.au/api/v2/Account/Logout'
17
18 class UTC(datetime.tzinfo):
19 def utcoffset(self, dt):
20 return datetime.timedelta(0)
21
22 def dst(self, dt):
23 return datetime.timedelta(0)
24
25 def tzname(self, dt):
26 return "UTC"
27
28
29 def main():
30 conf = ConfigParser.ConfigParser()
31 confname = os.environ['HOME'] + '/.agl.ini'
32 conf.read(confname)
33 username = conf.get('DEFAULT', 'username')
34 password = conf.get('DEFAULT', 'password')
35 dbfn = conf.get('DEFAULT', 'db')
36
37 if conf.has_option('DEFAULT', 'token'):
38 token = conf.get('DEFAULT', 'token')
39 else:
40 token = gettoken(username, password)
41 conf.set('DEFAULT', 'token', token)
42 conf.write(file(confname, 'w'))
43
44 if len(sys.argv) > 1:
45 date = sys.argv[1]
46 else:
47 date = datetime.datetime.now().strftime('%Y-%m-%d')
48
49 dbh = sqlite3.connect(dbfn, detect_types = sqlite3.PARSE_DECLTYPES)
50 cur = dbh.cursor()
51 data = getdata(token, date, date)
52 if data == None:
53 token = gettoken(username, password)
54 data = getdata(token, date, date)
55 if data == None:
56 print('Unable to fetch data')
57 updatedb(cur, data)
58 dbh.commit()
59
60 def mkdb(cur):
61 cur.execute('''
62 CREATE TABLE IF NOT EXISTS agl (
63 t_stamp TIMESTAMP PRIMARY KEY,
64 battery_charge NUMBER,
65 battery_power NUMBER,
66 power_consumed NUMBER,
67 power_expected NUMBER,
68 power_exported NUMBER,
69 power_generated NUMBER,
70 power_imported NUMBER,
71 estimated_savings NUMBER,
72 pv_forecast NUMBER,
73 pv_gen_battery NUMBER,
74 pv_gen_grid NUMBER,
75 pv_gen_site NUMBER,
76 site_cons_battery NUMBER,
77 site_cons_grid NUMBER,
78 site_cons_pv NUMBER
79 )''')
80
81 units = {
82 'battery_charge' : '%',
83 'battery_power' : 'Watt',
84 'power_consumed' : 'Watt',
85 'power_expected' : 'Watt',
86 'power_exported' : 'Watt',
87 'power_generated' : 'Watt',
88 'power_imported' : 'Watt',
89 'estimated_savings' : '$',
90 'pv_forecast' : 'Watt',
91 'pv_gen_battery' : 'Watt',
92 'pv_gen_grid' : 'Watt',
93 'pv_gen_site' : 'Watt',
94 'site_cons_battery' : 'Watt',
95 'site_cons_grid' : 'Watt',
96 'site_cons_pv' : 'Watt'
97 }
98 def graph(cur, cols, start, end):
99 import numpy
100 import matplotlib
101 import matplotlib.dates
102 import matplotlib.pylab
103
104 #matplotlib.rcParams['timezone'] = pytz.timezone('Australia/Adelaide')
105
106 colourlist = ['b','g','r','c','m','y','k']
107 yaxisunits1 = None
108 yaxisunits2 = None
109 ax1lines = []
110 ax2lines = []
111 colouridx = 0
112 for col in cols:
113 unit = units[col]
114 if yaxisunits1 == None:
115 yaxisunits1 = unit
116 if yaxisunits2 == None:
117 if unit != yaxisunits1:
118 yaxisunits2 = unit
119 else:
120 if unit != yaxisunits1 and unit != yaxisunits2:
121 raise exceptions.Exception('Asked to graph >2 different units')
122
123 cur.execute('SELECT t_stamp, ' + reduce(lambda a, b: a + ', ' + b, cols) + ' FROM agl WHERE t_stamp > ? AND t_stamp < ? ORDER BY t_stamp',
124 (start, end))
125 ary = numpy.array(cur.fetchall())
126 for idx in range(len(cols)):
127 if units[cols[idx]] == yaxisunits1:
128 ax1lines.append([ary[:,0], ary[:,idx + 1], cols[idx], colourlist[colouridx]])
129 else:
130 ax2lines.append([ary[:,0], ary[:,idx + 1], cols[idx], colourlist[colouridx]])
131 colouridx += 1
132
133 fig = matplotlib.pylab.figure()
134 ax1 = fig.add_subplot(111)
135 ax1.set_ylabel(yaxisunits1)
136
137 for line in ax1lines:
138 ax1.plot(line[0], line[1], label = line[2])
139
140 ax1.legend(loc = 'upper left')
141
142 if yaxisunits2 != None:
143 ax2 = ax1.twinx()
144 ax2.set_ylabel(yaxisunits2)
145
146 for line in ax2lines:
147 ax2.plot(line[0], line[1], label = line[2], color = line[3])
148 ax2.legend(loc = 'upper right')
149
150 # Rotate X axis labels
151 for ax in fig.get_axes():
152 ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
153 ax.xaxis.set_major_locator(matplotlib.dates.HourLocator(interval = 2))
154 ax.xaxis.set_minor_locator(matplotlib.dates.MinuteLocator(interval = 5))
155 for label in ax.get_xticklabels():
156 label.set_ha('right')
157 label.set_rotation(30)
158
159 # Fudge margins to give more graph and less space
160 fig.subplots_adjust(left = 0.10, right = 0.88, top = 0.95, bottom = 0.15)
161 matplotlib.pyplot.show()
162
163 def updatedb(cur, data):
164 mkdb(cur)
165 for d in data['reads']['data']:
166 ts = datetime.datetime.strptime(d['t_stamp'], '%Y-%m-%dT%H:%M:%SZ')
167 # Note we rename *energy* to *power* here to match what it actually means
168 vals = [ts, d['battery_charge'], d['battery_energy'], d['energy_consumed'], d['energy_expected'], d['energy_exported'], d['energy_generated'],
169 d['energy_imported'], d['estimated_savings'], d['pv_forecast'], d['pv_generation']['battery_energy'],
170 d['pv_generation']['grid_energy'], d['pv_generation']['site_energy'], d['site_consumption']['battery_energy'],
171 d['site_consumption']['grid_energy'], d['site_consumption']['pv_energy']]
172 skip = True
173 for v in vals[1:]:
174 if v != None:
175 skip = False
176 break
177 if skip:
178 print('Skipping empty record at ' + str(ts))
179 continue
180 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)
181
182 def gettoken(username, password):
183 authblob = json.encoder.JSONEncoder().encode({'email' : username, 'password' : password})
184 reply = requests.request('POST', loginurl, data = authblob, headers = {'Content-Type' : 'application/json'})
185 if reply.status_code != 200:
186 return None
187 return json.decoder.JSONDecoder().decode(reply.content)['access_token']
188
189 def getdata(token, startdate, enddate):
190 reply = requests.request('GET', dataurl, params = {
191 'startDate' : startdate,
192 'endDate' : enddate,
193 'granularity' : 'Minute',
194 'metrics' : 'read',
195 'units' : 'W',
196 }, headers = { 'Authorization' : 'Bearer ' + token})
197
198 if reply.status_code != 200:
199 return None
200
201 return json.decoder.JSONDecoder().decode(reply.content)
202
203 def logout(token):
204 reply = requests.request('GET', logouturl, headers = { 'Authorization' : 'Bearer ' + token})
205 return reply.status_code == 200
206
207 if __name__ == '__main__':
208 main()