annotate service/powerEagle/reader.py @ 1702:ac1ae0c21bb0

formatting
author drewp@bigasterisk.com
date Sat, 23 Oct 2021 13:14:07 -0700
parents e8654a3bd1c7
children 4cbe3df8f48f
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
1 import binascii
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
2 import json
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
3 import time
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
4 import traceback
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
5 from typing import Dict
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
6
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
7 from cyclone.httpclient import fetch
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
8 import cyclone.web
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
9 from patchablegraph import (
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
10 CycloneGraphEventsHandler,
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
11 CycloneGraphHandler,
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
12 PatchableGraph,
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
13 )
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
14 from prometheus_client import Counter, Gauge, Summary
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
15 from prometheus_client.exposition import generate_latest
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
16 from prometheus_client.registry import REGISTRY
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
17 from rdflib import Literal, Namespace
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
18 from standardservice.logsetup import log, verboseLogging
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
19 from twisted.internet import reactor
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
20 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
21
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
22 from docopt import docopt
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
23 from private_config import cloudId, deviceIp, installId, macId, periodSec
734
e9366f73e612 serve current usage as a graph
drewp@bigasterisk.com
parents: 723
diff changeset
24 ROOM = Namespace("http://projects.bigasterisk.com/room/")
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
25
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
26 authPlain = cloudId + ':' + installId
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
27 auth = binascii.b2a_base64(authPlain.encode('ascii')).strip(b'=\n')
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
28
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
29 POLL = Summary('poll', 'Time in HTTP poll requests')
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
30 POLL_SUCCESSES = Counter('poll_successes', 'poll success count')
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
31 POLL_ERRORS = Counter('poll_errors', 'poll error count')
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
32
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
33
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
34 class Poller(object):
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
35
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
36 def __init__(self, out: Dict[str, Gauge], graph):
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
37 self.out = out
734
e9366f73e612 serve current usage as a graph
drewp@bigasterisk.com
parents: 723
diff changeset
38 self.graph = graph
212
a7dd996617ef LoopingCall sets the interview between calls, but I want the period of calls
drewp@bigasterisk.com
parents: 211
diff changeset
39 reactor.callLater(0, self.poll)
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
40
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
41 @POLL.time()
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
42 @inlineCallbacks
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
43 def poll(self):
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
44 ret = None
212
a7dd996617ef LoopingCall sets the interview between calls, but I want the period of calls
drewp@bigasterisk.com
parents: 211
diff changeset
45 startTime = time.time()
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
46 try:
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
47 url = (f'http://{deviceIp}/cgi-bin/cgi_manager').encode('ascii')
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
48 resp = yield fetch(url,
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
49 method=b'POST',
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
50 headers={b'Authorization': [b'Basic %s' % auth]},
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
51 postdata=(f'''<LocalCommand>
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
52 <Name>get_usage_data</Name>
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
53 <MacId>0x{macId}</MacId>
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
54 </LocalCommand>
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
55 <LocalCommand>
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
56 <Name>get_price_blocks</Name>
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
57 <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
58 </LocalCommand>''').encode('ascii'),
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
59 timeout=10)
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
60 ret = json.loads(resp.body)
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
61 log.debug(f"response body {ret}")
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
62 if ret['demand_units'] != 'kW':
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
63 raise ValueError
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
64 if ret['summation_units'] != 'kWh':
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
65 raise ValueError
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
66
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
67 demandW = float(ret['demand']) * 1000
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
68 self.out['w'].set(demandW)
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
69
416
655a11cde0ab add request timeout. don't send NaN to influxdb. crash on failures.
drewp@bigasterisk.com
parents: 311
diff changeset
70 sd = float(ret['summation_delivered'])
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
71 if sd > 0: # Sometimes nan
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
72 self.out['kwh'].set(sd)
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
73
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
74 if 'price' in ret:
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
75 self.out['price'].set(float(ret['price']))
734
e9366f73e612 serve current usage as a graph
drewp@bigasterisk.com
parents: 723
diff changeset
76
e9366f73e612 serve current usage as a graph
drewp@bigasterisk.com
parents: 723
diff changeset
77 self.graph.patchObject(context=ROOM['powerEagle'],
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
78 subject=ROOM['housePower'],
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
79 predicate=ROOM['instantDemandWatts'],
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
80 newObject=Literal(demandW))
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
81 POLL_SUCCESSES.inc()
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
82 except Exception as e:
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
83 POLL_ERRORS.inc()
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
84 traceback.print_exc()
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
85 log.error("failed: %r", e)
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
86 log.error(repr(ret))
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
87
212
a7dd996617ef LoopingCall sets the interview between calls, but I want the period of calls
drewp@bigasterisk.com
parents: 211
diff changeset
88 now = time.time()
216
7a1e9ef4c8b2 fudge timing to not miss data points
drewp@bigasterisk.com
parents: 212
diff changeset
89 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
90 reactor.callLater(max(1, goal - now), self.poll)
211
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
91
7d3645c2157e rainforest eagle data -> carbon
drewp@bigasterisk.com
parents:
diff changeset
92
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
93 class Metrics(cyclone.web.RequestHandler):
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
94
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
95 def get(self):
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
96 self.add_header('content-type', 'text/plain')
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
97 self.write(generate_latest(REGISTRY))
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
98
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
99
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
100 if __name__ == '__main__':
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
101 arg = docopt("""
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
102 Usage: reader.py [options]
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
103
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
104 -v Verbose
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
105 --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
106 """)
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
107 verboseLogging(arg['-v'])
311
ec6451f15ae5 powereagle reader writes to influxdb
drewp@bigasterisk.com
parents: 216
diff changeset
108
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
109 out = {
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
110 'w': Gauge('house_power_w', 'house power demand'),
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
111 'kwh': Gauge('house_power_kwh', 'house power sum delivered'),
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
112 'price': Gauge('house_power_price', 'house power price'),
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
113 }
734
e9366f73e612 serve current usage as a graph
drewp@bigasterisk.com
parents: 723
diff changeset
114 masterGraph = PatchableGraph()
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
115 p = Poller(out, masterGraph)
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
116
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
117 reactor.listenTCP(
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
118 int(arg['--port']),
786
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
119 cyclone.web.Application([
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
120 (r'/metrics', Metrics),
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
121 (r"/graph/power", CycloneGraphHandler, {
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
122 'masterGraph': masterGraph
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
123 }),
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
124 (r"/graph/power/events", CycloneGraphEventsHandler, {
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
125 'masterGraph': masterGraph
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
126 }),
e8654a3bd1c7 update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents: 734
diff changeset
127 ],))
532
71aa55cd8433 update powereagle to py3, be a server with /stats/, save 'price' field
drewp@bigasterisk.com
parents: 416
diff changeset
128 reactor.run()