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
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
1016
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
16
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
26
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
34 @inlineCallbacks
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
35 def poll(self):
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
45 <Name>get_usage_data</Name>
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
46 <MacId>0x{macId}</MacId>
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
47 </LocalCommand>
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
48 <LocalCommand>
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
49 <Name>get_price_blocks</Name>
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
55 if ret['demand_units'] != 'kW':
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
56 raise ValueError
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
57 if ret['summation_units'] != 'kWh':
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
13b7e4de3824 whitespace
drewp <drewp@bigasterisk.com>
parents: 1523
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
87 log.error("failed: %r", e)
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
94
1c5ff4c16f1e rainforest eagle data -> carbon
drewp <drewp@bigasterisk.com>
parents:
diff changeset
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()