Mercurial > code > home > repos > homeauto
annotate service/powerEagle/reader.py @ 1534:df80deeef113
serve current usage as a graph
Ignore-this: 76d210d576784acb639762cd6c6e6f13
darcs-hash:263305875758810b8beb38ed8b7dada825f9c4ce
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Mon, 10 Feb 2020 00:01:39 -0800 |
parents | 13b7e4de3824 |
children | e8654a3bd1c7 |
rev | line source |
---|---|
1016 | 1 #!bin/python |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
2 import json, time, os, binascii, traceback |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
3 |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
4 from cyclone.httpclient import fetch |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
5 from docopt import docopt |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
6 from greplin import scales |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
7 from greplin.scales.cyclonehandler import StatsHandler |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
8 from influxdb import InfluxDBClient |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
9 from twisted.internet import reactor |
1016 | 10 from twisted.internet.defer import inlineCallbacks |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
11 import cyclone.web |
1534
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
12 from rdflib import Namespace, Literal |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
13 |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
14 from standardservice.logsetup import log, verboseLogging |
1534
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
15 from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler |
1016 | 16 |
17 from private_config import deviceIp, cloudId, installId, macId, periodSec | |
1534
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
18 ROOM = Namespace("http://projects.bigasterisk.com/room/") |
1016 | 19 |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
20 STATS = scales.collection('/root', |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
21 scales.PmfStat('poll'), |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
22 ) |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
23 |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
24 authPlain = cloudId + ':' + installId |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
25 auth = binascii.b2a_base64(authPlain.encode('ascii')).strip(b'=\n') |
1016 | 26 |
27 class Poller(object): | |
1534
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
28 def __init__(self, influx, graph): |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
29 self.influx = influx |
1534
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
30 self.graph = graph |
1017
3edda1f7a322
LoopingCall sets the interview between calls, but I want the period of calls
drewp <drewp@bigasterisk.com>
parents:
1016
diff
changeset
|
31 reactor.callLater(0, self.poll) |
1016 | 32 |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
33 @STATS.poll.time() |
1016 | 34 @inlineCallbacks |
35 def poll(self): | |
36 ret = None | |
1017
3edda1f7a322
LoopingCall sets the interview between calls, but I want the period of calls
drewp <drewp@bigasterisk.com>
parents:
1016
diff
changeset
|
37 startTime = time.time() |
1016 | 38 try: |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
39 url = (f'http://{deviceIp}/cgi-bin/cgi_manager').encode('ascii') |
1016 | 40 resp = yield fetch( |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
41 url, |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
42 method=b'POST', |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
43 headers={b'Authorization': [b'Basic %s' % auth]}, |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
44 postdata=(f'''<LocalCommand> |
1016 | 45 <Name>get_usage_data</Name> |
46 <MacId>0x{macId}</MacId> | |
47 </LocalCommand> | |
48 <LocalCommand> | |
49 <Name>get_price_blocks</Name> | |
50 <MacId>0x{macId}</MacId> | |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
51 </LocalCommand>''').encode('ascii'), |
1219
bff11263c71e
add request timeout. don't send NaN to influxdb. crash on failures.
drewp <drewp@bigasterisk.com>
parents:
1116
diff
changeset
|
52 timeout=10) |
1016 | 53 ret = json.loads(resp.body) |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
54 log.debug(ret) |
1016 | 55 if ret['demand_units'] != 'kW': |
56 raise ValueError | |
57 if ret['summation_units'] != 'kWh': | |
58 raise ValueError | |
1219
bff11263c71e
add request timeout. don't send NaN to influxdb. crash on failures.
drewp <drewp@bigasterisk.com>
parents:
1116
diff
changeset
|
59 pts = [ |
1116
d22c0c502ff6
powereagle reader writes to influxdb
drewp <drewp@bigasterisk.com>
parents:
1021
diff
changeset
|
60 dict(measurement='housePowerW', |
d22c0c502ff6
powereagle reader writes to influxdb
drewp <drewp@bigasterisk.com>
parents:
1021
diff
changeset
|
61 fields=dict(value=float(ret['demand']) * 1000), |
d22c0c502ff6
powereagle reader writes to influxdb
drewp <drewp@bigasterisk.com>
parents:
1021
diff
changeset
|
62 tags=dict(house='berkeley'), |
1219
bff11263c71e
add request timeout. don't send NaN to influxdb. crash on failures.
drewp <drewp@bigasterisk.com>
parents:
1116
diff
changeset
|
63 time=int(startTime))] |
bff11263c71e
add request timeout. don't send NaN to influxdb. crash on failures.
drewp <drewp@bigasterisk.com>
parents:
1116
diff
changeset
|
64 sd = float(ret['summation_delivered']) |
bff11263c71e
add request timeout. don't send NaN to influxdb. crash on failures.
drewp <drewp@bigasterisk.com>
parents:
1116
diff
changeset
|
65 if sd > 0: # Sometimes nan |
bff11263c71e
add request timeout. don't send NaN to influxdb. crash on failures.
drewp <drewp@bigasterisk.com>
parents:
1116
diff
changeset
|
66 pts.append(dict(measurement='housePowerSumDeliveredKwh', |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
67 fields=dict(value=float()), |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
68 tags=dict(house='berkeley'), |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
69 time=int(startTime))) |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
70 if 'price' in ret: |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
71 pts.append(dict( |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
72 measurement='price', |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
73 fields=dict(price=float(ret['price']), |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
74 price_units=float(ret['price_units'])), |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
75 tags=dict(house='berkeley'), |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
76 time=int(startTime), |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
77 )) |
1524 | 78 |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
79 self.influx.write_points(pts, time_precision='s') |
1534
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
80 |
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
81 self.graph.patchObject(context=ROOM['powerEagle'], |
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
82 subject=ROOM['housePower'], |
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
83 predicate=ROOM['instantDemandWatts'], |
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
84 newObject=Literal(float(ret['demand']) * 1000)) |
1016 | 85 except Exception as e: |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
86 traceback.print_exc() |
1016 | 87 log.error("failed: %r", e) |
88 log.error(repr(ret)) | |
1219
bff11263c71e
add request timeout. don't send NaN to influxdb. crash on failures.
drewp <drewp@bigasterisk.com>
parents:
1116
diff
changeset
|
89 os.abort() |
1016 | 90 |
1017
3edda1f7a322
LoopingCall sets the interview between calls, but I want the period of calls
drewp <drewp@bigasterisk.com>
parents:
1016
diff
changeset
|
91 now = time.time() |
1021
b8540cba5c66
fudge timing to not miss data points
drewp <drewp@bigasterisk.com>
parents:
1017
diff
changeset
|
92 goal = startTime + periodSec - .2 |
1017
3edda1f7a322
LoopingCall sets the interview between calls, but I want the period of calls
drewp <drewp@bigasterisk.com>
parents:
1016
diff
changeset
|
93 reactor.callLater(max(1, goal - now), self.poll) |
1016 | 94 |
95 | |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
96 if __name__ == '__main__': |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
97 arg = docopt(""" |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
98 Usage: reader.py [options] |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
99 |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
100 -v Verbose |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
101 --port PORT Serve on port [default: 10016]. |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
102 """) |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
103 verboseLogging(arg['-v']) |
1116
d22c0c502ff6
powereagle reader writes to influxdb
drewp <drewp@bigasterisk.com>
parents:
1021
diff
changeset
|
104 |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
105 influx = InfluxDBClient('bang', 9060, 'root', 'root', 'main') |
1534
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
106 masterGraph = PatchableGraph() |
df80deeef113
serve current usage as a graph
drewp <drewp@bigasterisk.com>
parents:
1524
diff
changeset
|
107 p = Poller(influx, masterGraph) |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
108 |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
109 reactor.listenTCP( |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
110 int(arg['--port']), |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
111 cyclone.web.Application( |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
112 [ |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
113 (r'/stats/(.*)', StatsHandler, {'serverName': 'powerEagle'}), |
1523
0da337780f22
dep updates; graph url renames; and other build updates
drewp <drewp@bigasterisk.com>
parents:
1335
diff
changeset
|
114 (r"/graph/power", CycloneGraphHandler, {'masterGraph': masterGraph}), |
0da337780f22
dep updates; graph url renames; and other build updates
drewp <drewp@bigasterisk.com>
parents:
1335
diff
changeset
|
115 (r"/graph/power/events", CycloneGraphEventsHandler, {'masterGraph': masterGraph}), |
1335
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
116 ], |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
117 )) |
013af6808aca
update powereagle to py3, be a server with /stats/, save 'price' field
drewp <drewp@bigasterisk.com>
parents:
1219
diff
changeset
|
118 reactor.run() |