Mercurial > code > home > repos > homeauto
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 |
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 | 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 | 24 ROOM = Namespace("http://projects.bigasterisk.com/room/") |
211 | 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 | 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 | 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 | 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 | 40 |
786
e8654a3bd1c7
update powereagle for k8s and prometheus
drewp@bigasterisk.com
parents:
734
diff
changeset
|
41 @POLL.time() |
211 | 42 @inlineCallbacks |
43 def poll(self): | |
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 | 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 | 52 <Name>get_usage_data</Name> |
53 <MacId>0x{macId}</MacId> | |
54 </LocalCommand> | |
55 <LocalCommand> | |
56 <Name>get_price_blocks</Name> | |
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 | 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 | 62 if ret['demand_units'] != 'kW': |
63 raise ValueError | |
64 if ret['summation_units'] != 'kWh': | |
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 | 76 |
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 | 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 | 85 log.error("failed: %r", e) |
86 log.error(repr(ret)) | |
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 | 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 | 91 |
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 | 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 | 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() |