annotate service/powerEagle/reader.py @ 712:d98c3ffe7144

new graph output for browsers, with autorefresh Ignore-this: e4ff4dbed311b238d90988a1891ef640
author drewp@bigasterisk.com
date Mon, 03 Feb 2020 23:47:23 -0800
parents 71aa55cd8433
children a93fbf0d0daa
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
1 #!bin/python
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
2 import json, time, os, binascii, traceback
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
3
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
4 from cyclone.httpclient import fetch
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
5 from docopt import docopt
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
6 from greplin import scales
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
7 from greplin.scales.cyclonehandler import StatsHandler
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
8 from influxdb import InfluxDBClient
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
9 from twisted.internet import reactor
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
10 from twisted.internet.defer import inlineCallbacks
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
11 import cyclone.web
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
12
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
13 from standardservice.logsetup import log, verboseLogging
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
14
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
15 from private_config import deviceIp, cloudId, installId, macId, periodSec
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
16
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
17 STATS = scales.collection('/root',
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
18 scales.PmfStat('poll'),
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
19 )
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
20
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
21 authPlain = cloudId + ':' + installId
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
22 auth = binascii.b2a_base64(authPlain.encode('ascii')).strip(b'=\n')
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
23
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
24 class Poller(object):
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
25 def __init__(self, influx):
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
26 self.influx = influx
212
a7dd996617ef LoopingCall sets the interview between calls, but I want the period of calls
drewp@bigasterisk.com
parents: 211
diff changeset
27 reactor.callLater(0, self.poll)
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
28
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
29 @STATS.poll.time()
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
30 @inlineCallbacks
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
31 def poll(self):
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
32 ret = None
212
a7dd996617ef LoopingCall sets the interview between calls, but I want the period of calls
drewp@bigasterisk.com
parents: 211
diff changeset
33 startTime = time.time()
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
34 try:
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
35 url = (f'http://{deviceIp}/cgi-bin/cgi_manager').encode('ascii')
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
36 resp = yield fetch(
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
37 url,
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
38 method=b'POST',
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
39 headers={b'Authorization': [b'Basic %s' % auth]},
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
40 postdata=(f'''<LocalCommand>
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
41 <Name>get_usage_data</Name>
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
42 <MacId>0x{macId}</MacId>
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
43 </LocalCommand>
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
44 <LocalCommand>
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
45 <Name>get_price_blocks</Name>
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
46 <MacId>0x{macId}</MacId>
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
47 </LocalCommand>''').encode('ascii'),
416
655a11cde0ab add request timeout. don't send NaN to influxdb. crash on failures.
drewp@bigasterisk.com
parents: 311
diff changeset
48 timeout=10)
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
49 ret = json.loads(resp.body)
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
50 log.debug(ret)
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
51 if ret['demand_units'] != 'kW':
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
52 raise ValueError
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
53 if ret['summation_units'] != 'kWh':
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
54 raise ValueError
416
655a11cde0ab add request timeout. don't send NaN to influxdb. crash on failures.
drewp@bigasterisk.com
parents: 311
diff changeset
55 pts = [
311
ec6451f15ae5 powereagle reader writes to influxdb
drewp@bigasterisk.com
parents: 216
diff changeset
56 dict(measurement='housePowerW',
ec6451f15ae5 powereagle reader writes to influxdb
drewp@bigasterisk.com
parents: 216
diff changeset
57 fields=dict(value=float(ret['demand']) * 1000),
ec6451f15ae5 powereagle reader writes to influxdb
drewp@bigasterisk.com
parents: 216
diff changeset
58 tags=dict(house='berkeley'),
416
655a11cde0ab add request timeout. don't send NaN to influxdb. crash on failures.
drewp@bigasterisk.com
parents: 311
diff changeset
59 time=int(startTime))]
655a11cde0ab add request timeout. don't send NaN to influxdb. crash on failures.
drewp@bigasterisk.com
parents: 311
diff changeset
60 sd = float(ret['summation_delivered'])
655a11cde0ab add request timeout. don't send NaN to influxdb. crash on failures.
drewp@bigasterisk.com
parents: 311
diff changeset
61 if sd > 0: # Sometimes nan
655a11cde0ab add request timeout. don't send NaN to influxdb. crash on failures.
drewp@bigasterisk.com
parents: 311
diff changeset
62 pts.append(dict(measurement='housePowerSumDeliveredKwh',
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
63 fields=dict(value=float()),
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
64 tags=dict(house='berkeley'),
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
65 time=int(startTime)))
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
66 if 'price' in ret:
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
67 pts.append(dict(
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
68 measurement='price',
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
69 fields=dict(price=float(ret['price']),
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
70 price_units=float(ret['price_units'])),
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
71 tags=dict(house='berkeley'),
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
72 time=int(startTime),
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
73 ))
416
655a11cde0ab add request timeout. don't send NaN to influxdb. crash on failures.
drewp@bigasterisk.com
parents: 311
diff changeset
74
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
75 self.influx.write_points(pts, time_precision='s')
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
76 except Exception as e:
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
77 traceback.print_exc()
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
78 log.error("failed: %r", e)
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
79 log.error(repr(ret))
416
655a11cde0ab add request timeout. don't send NaN to influxdb. crash on failures.
drewp@bigasterisk.com
parents: 311
diff changeset
80 os.abort()
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
81
212
a7dd996617ef LoopingCall sets the interview between calls, but I want the period of calls
drewp@bigasterisk.com
parents: 211
diff changeset
82 now = time.time()
216
7a1e9ef4c8b2 fudge timing to not miss data points
drewp@bigasterisk.com
parents: 212
diff changeset
83 goal = startTime + periodSec - .2
212
a7dd996617ef LoopingCall sets the interview between calls, but I want the period of calls
drewp@bigasterisk.com
parents: 211
diff changeset
84 reactor.callLater(max(1, goal - now), self.poll)
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
85
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
86
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
87 if __name__ == '__main__':
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
88 arg = docopt("""
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
89 Usage: reader.py [options]
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
90
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
91 -v Verbose
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
92 --port PORT Serve on port [default: 10016].
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
93 """)
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
94 verboseLogging(arg['-v'])
311
ec6451f15ae5 powereagle reader writes to influxdb
drewp@bigasterisk.com
parents: 216
diff changeset
95
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
96 influx = InfluxDBClient('bang', 9060, 'root', 'root', 'main')
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
97 p = Poller(influx)
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
98
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
99 reactor.listenTCP(
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
100 int(arg['--port']),
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
101 cyclone.web.Application(
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
102 [
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
103 (r'/stats/(.*)', StatsHandler, {'serverName': 'powerEagle'}),
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
104 ],
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
105 ))
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
106 reactor.run()