|
@@ -0,0 +1,194 @@
|
|
1
|
+---
|
|
2
|
+layout: post
|
|
3
|
+title: Monitoring power draw with WeMo Insight Switches
|
|
4
|
+strapline: Fun with SOAP and rrdtool
|
|
5
|
+---
|
|
6
|
+
|
|
7
|
+<div class="image right">
|
|
8
|
+ <img src="/res/images/wemo/switch.jpg" alt="WeMo Insight Switch">
|
|
9
|
+</div>
|
|
10
|
+
|
|
11
|
+I recently picked up a couple of <a href="http://www.belkin.com/uk/p/P-F7C029/">Belkin's WeMo
|
|
12
|
+Insight Switches</a> to monitor power usage for my PC and networking equipment. WeMo is Belkin's
|
|
13
|
+home automation brand, and the switches allow you to toggle power on and off with an app, and
|
|
14
|
+monitor power usage.
|
|
15
|
+
|
|
16
|
+The WeMo Android app is pretty dismal. It's slow, doesn't look great, and crashed about a dozen
|
|
17
|
+times during the setup process for each of my two switches. It also doesn't provide much
|
|
18
|
+information at all about power: you can see average power draw and current power draw, and that's
|
|
19
|
+basically it.
|
|
20
|
+
|
|
21
|
+Belkin has provided an option to e-mail yourself a spreadsheet with historical power data, and can
|
|
22
|
+even do it on a regularly scheduled basis, but that's not really a nice solution if you want
|
|
23
|
+up-to-date power stats. Even if you were happy with data arriving in batch, having to get hold
|
|
24
|
+of an e-mail attachment and parse out a weirdly formatted spreadsheet doesn't make for easy
|
|
25
|
+automation. It also relies on Belkin supporting the service indefinitely, which isn't necessarily
|
|
26
|
+going to happen.
|
|
27
|
+
|
|
28
|
+<!--more-->
|
|
29
|
+
|
|
30
|
+### Enter the SOAP API
|
|
31
|
+
|
|
32
|
+Fortunately, the devices themselves expose an API to get the data. Once a switch is setup with the
|
|
33
|
+WeMo app, it connects to a WiFi network. You can discover WeMo devices on the network using a UPnP
|
|
34
|
+broadcast, and then from there a list of supported services. Each service has a number of SOAP
|
|
35
|
+actions, with nice obvious names, and parameters documented in the XML service description.
|
|
36
|
+
|
|
37
|
+For the insight switches, there's a service at `/upnp/control/insight1` which has a number of
|
|
38
|
+actions including `GetPower` and `GetTodayKWH`. Sending a SOAP request isn't too difficult
|
|
39
|
+(albeit nowhere near as nice as a REST JSON API) — here's a sample request I made using
|
|
40
|
+the <a href="https://chrome.google.com/webstore/detail/dhc-rest-client/aejoelaoggembcahagimdiliamlcdmfm/">DHC chrome extension</a>:
|
|
41
|
+
|
|
42
|
+{% highlight http %}
|
|
43
|
+POST /upnp/control/insight1 HTTP/1.1
|
|
44
|
+Accept: */*
|
|
45
|
+Accept-Encoding: gzip, deflate
|
|
46
|
+Accept-Language: en-GB,en;q=0.8
|
|
47
|
+Content-Type: text/xml
|
|
48
|
+Origin: chrome-extension://aejoelaoggembcahagimdiliamlcdmfm
|
|
49
|
+SOAPACTION: "urn:Belkin:service:insight:1#GetPower"
|
|
50
|
+User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.29 Safari/537.36
|
|
51
|
+
|
|
52
|
+<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
53
|
+ <s:Body>
|
|
54
|
+ <u:GetPower xmlns:u="urn:Belkin:service:insight:1"/>
|
|
55
|
+ </s:Body>
|
|
56
|
+</s:Envelope>
|
|
57
|
+{% endhighlight %}
|
|
58
|
+
|
|
59
|
+The name of the action (in the SOAPACTION header) and the XML namespace in the body are both
|
|
60
|
+constructed from information in the service definition. The result that comes back is:
|
|
61
|
+
|
|
62
|
+{% highlight xml %}
|
|
63
|
+<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
64
|
+ <s:Body>
|
|
65
|
+ <u:GetPowerResponse xmlns:u="urn:Belkin:service:insight:1">
|
|
66
|
+ <InstantPower>92170</InstantPower>
|
|
67
|
+ </u:GetPowerResponse>
|
|
68
|
+ </s:Body>
|
|
69
|
+</s:Envelope>
|
|
70
|
+{% endhighlight %}
|
|
71
|
+
|
|
72
|
+The current power draw in milliwatts is returned in the `<InstantPower>` argument.
|
|
73
|
+
|
|
74
|
+### Putting the data to work
|
|
75
|
+
|
|
76
|
+So now we have a way to pull the current power draw, a nice thing to do would be to plot a graph
|
|
77
|
+of it to see the variation over time. This is the short of thing you'd expect to see in the Belkin
|
|
78
|
+app, but it's sadly missing. After a bit of research I settled on the tried and true
|
|
79
|
+<a href="http://oss.oetiker.ch/rrdtool/">rrdtool</a> to store data and generate graphs. I created a
|
|
80
|
+new database to store the values from the two switches:
|
|
81
|
+
|
|
82
|
+{% highlight bash %}
|
|
83
|
+rrdtool create power.rrd \
|
|
84
|
+ --start now \
|
|
85
|
+ --step 60 \
|
|
86
|
+ DS:wemoComputer:GAUGE:120:U:U \
|
|
87
|
+ DS:wemoNetworking:GAUGE:120:U:U \
|
|
88
|
+ RRA:AVERAGE:0.5:1:1440 \
|
|
89
|
+ RRA:AVERAGE:0.5:10:1008 \
|
|
90
|
+ RRA:AVERAGE:0.5:30:1488 \
|
|
91
|
+ RRA:AVERAGE:0.5:120:1488 \
|
|
92
|
+ RRA:AVERAGE:0.5:360:1488 \
|
|
93
|
+ RRA:AVERAGE:0.5:1440:36500
|
|
94
|
+{% endhighlight %}
|
|
95
|
+
|
|
96
|
+This creates a database file which expects values to be given every 60 seconds for two data series:
|
|
97
|
+'wemoComputer' and 'wemoNetworking'. These are gauge types (i.e., a value we read off a gauge,
|
|
98
|
+rather than a counter that keeps going up) with no minimum and maximum ('U').
|
|
99
|
+
|
|
100
|
+It then defines a series of round-robin archives. Each of these stores a fixed number of entries,
|
|
101
|
+and the oldest is overwritten when they become full. The interesting arguments are the step count
|
|
102
|
+(second to last) and number of samples (last argument). The first one takes every sample and keeps
|
|
103
|
+1,440 of them (i.e., a full day at 1-minute resolution); the last one has a step of 1,440 and keeps
|
|
104
|
+36500 of them (i.e., 100 years at 1-day resolution). The use of RRAs ensures that rrdtool can retain
|
|
105
|
+enough data to create historical graphs, while providing a fixed, finite maximum file size. With
|
|
106
|
+sufficient RRA coverage you can graph most timescales and have data available at a reasonable
|
|
107
|
+resolution.
|
|
108
|
+
|
|
109
|
+Next, I created a small python script to retrieve the power using SOAP:
|
|
110
|
+
|
|
111
|
+{% highlight python %}
|
|
112
|
+#!/usr/bin/python3
|
|
113
|
+
|
|
114
|
+import requests
|
|
115
|
+from xml.etree import ElementTree
|
|
116
|
+
|
|
117
|
+def get_power(ip):
|
|
118
|
+ headers = {'Content-type': 'text/xml', 'SOAPACTION': '"urn:Belkin:service:insight:1#GetPower"'}
|
|
119
|
+ payload = '''<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
|
|
120
|
+ s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
121
|
+ <s:Body>
|
|
122
|
+ <u:GetPower xmlns:u="urn:Belkin:service:insight:1"/>
|
|
123
|
+ </s:Body>
|
|
124
|
+ </s:Envelope>'''
|
|
125
|
+ r = requests.post("http://%s:49153/upnp/control/insight1" % ip, headers=headers, data=payload)
|
|
126
|
+ et = ElementTree.fromstring(r.text)
|
|
127
|
+ return et.find('.//InstantPower').text
|
|
128
|
+{% endhighlight %}
|
|
129
|
+
|
|
130
|
+I then have a dictionary of IP addresses to data series names, and the script polls each one in
|
|
131
|
+turn and then executes an `rrdtool update` query to add the items to the database. I have this
|
|
132
|
+script running every minute via cron.
|
|
133
|
+
|
|
134
|
+After leaving the script to run for a bit and gather data, it's time to make some graphs. I use
|
|
135
|
+the following to create a graph with a background gradient:
|
|
136
|
+
|
|
137
|
+{% highlight bash %}
|
|
138
|
+rrdtool graph desk-1d.png
|
|
139
|
+ -o -X0 -w800 -h500 \
|
|
140
|
+ -u 2000 -l 20 -r \
|
|
141
|
+ DEF:raw=power.rrd:wemoComputer:AVERAGE \
|
|
142
|
+ CDEF:power=raw,1000,/ \
|
|
143
|
+ CDEF:powerz=power,2000,LT,power,2000,IF CDEF:powerzNoUnk=power,UN,0,powerz,IF AREA:powerzNoUnk#ff0000 \
|
|
144
|
+ CDEF:powery=power,1500,LT,power,1500,IF CDEF:poweryNoUnk=power,UN,0,powery,IF AREA:poweryNoUnk#ff0000 \
|
|
145
|
+ CDEF:powerx=power,1000,LT,power,1000,IF CDEF:powerxNoUnk=power,UN,0,powerx,IF AREA:powerxNoUnk#ff0000 \
|
|
146
|
+ CDEF:powerw=power,900,LT,power,900,IF CDEF:powerwNoUnk=power,UN,0,powerw,IF AREA:powerwNoUnk#ff0000 \
|
|
147
|
+ CDEF:powerv=power,800,LT,power,800,IF CDEF:powervNoUnk=power,UN,0,powerv,IF AREA:powervNoUnk#ff1b00 \
|
|
148
|
+ CDEF:poweru=power,700,LT,power,700,IF CDEF:poweruNoUnk=power,UN,0,poweru,IF AREA:poweruNoUnk#ff4100 \
|
|
149
|
+ CDEF:powert=power,600,LT,power,600,IF CDEF:powertNoUnk=power,UN,0,powert,IF AREA:powertNoUnk#ff6600 \
|
|
150
|
+ CDEF:powers=power,400,LT,power,400,IF CDEF:powersNoUnk=power,UN,0,powers,IF AREA:powersNoUnk#ff8e00 \
|
|
151
|
+ CDEF:powerr=power,200,LT,power,200,IF CDEF:powerrNoUnk=power,UN,0,powerr,IF AREA:powerrNoUnk#ffb500 \
|
|
152
|
+ CDEF:powerq=power,180,LT,power,180,IF CDEF:powerqNoUnk=power,UN,0,powerq,IF AREA:powerqNoUnk#ffdb00 \
|
|
153
|
+ CDEF:powerp=power,160,LT,power,160,IF CDEF:powerpNoUnk=power,UN,0,powerp,IF AREA:powerpNoUnk#fdff00 \
|
|
154
|
+ CDEF:powero=power,140,LT,power,140,IF CDEF:poweroNoUnk=power,UN,0,powero,IF AREA:poweroNoUnk#d7ff00 \
|
|
155
|
+ CDEF:powern=power,120,LT,power,120,IF CDEF:powernNoUnk=power,UN,0,powern,IF AREA:powernNoUnk#b0ff00 \
|
|
156
|
+ CDEF:powerm=power,100,LT,power,100,IF CDEF:powermNoUnk=power,UN,0,powerm,IF AREA:powermNoUnk#8aff00 \
|
|
157
|
+ CDEF:powerl=power,90,LT,power,90,IF CDEF:powerlNoUnk=power,UN,0,powerl,IF AREA:powerlNoUnk#65ff00 \
|
|
158
|
+ CDEF:powerk=power,80,LT,power,80,IF CDEF:powerkNoUnk=power,UN,0,powerk,IF AREA:powerkNoUnk#3eff00 \
|
|
159
|
+ CDEF:powerj=power,70,LT,power,70,IF CDEF:powerjNoUnk=power,UN,0,powerj,IF AREA:powerjNoUnk#17ff00 \
|
|
160
|
+ CDEF:poweri=power,60,LT,power,60,IF CDEF:poweriNoUnk=power,UN,0,poweri,IF AREA:poweriNoUnk#00ff10 \
|
|
161
|
+ CDEF:powerh=power,50,LT,power,50,IF CDEF:powerhNoUnk=power,UN,0,powerh,IF AREA:powerhNoUnk#00ff36 \
|
|
162
|
+ CDEF:powerg=power,40,LT,power,40,IF CDEF:powergNoUnk=power,UN,0,powerg,IF AREA:powergNoUnk#00ff5c \
|
|
163
|
+ CDEF:powerf=power,30,LT,power,30,IF CDEF:powerfNoUnk=power,UN,0,powerf,IF AREA:powerfNoUnk#00ff83 \
|
|
164
|
+ CDEF:powere=power,20,LT,power,20,IF CDEF:powereNoUnk=power,UN,0,powere,IF AREA:powereNoUnk#00ffa8 \
|
|
165
|
+ CDEF:powerd=power,0,LT,power,0,IF CDEF:powerdNoUnk=power,UN,0,powerd,IF AREA:powerdNoUnk#00ffd0 \
|
|
166
|
+ LINE:power#080
|
|
167
|
+{% endhighlight %}
|
|
168
|
+
|
|
169
|
+This seems a bit unweildy, but it's fairly straight forward. The options tell rrdtool to create
|
|
170
|
+a graph with a canvas size of 800x500 pixels, a lower limit of 20W, upper limit of 2kW, and a
|
|
171
|
+log scale. The first `CDEF` divides our raw value (which was in millwatts) by 1000 to get a value
|
|
172
|
+in watts. rrdtool uses reverse polish notation like some calculators, so you provide the arguments
|
|
173
|
+before the operator.
|
|
174
|
+
|
|
175
|
+The big block of `CDEF`/`CDEF`/`AREA` parameters creates a series of area plots in different colours
|
|
176
|
+according to the power level. They're in descending order so the smaller areas are drawn on top of
|
|
177
|
+the larger layers. This results in a graph that looks like this:
|
|
178
|
+
|
|
179
|
+<img src="/res/images/wemo/desk-1d.png" alt="Graph of power usage over a day">
|
|
180
|
+
|
|
181
|
+This graph shows the total power for all the things plugged in at my desk. You can see the idle
|
|
182
|
+power draw is around 60W. When I'm using the computer it jumps up to around 130W, and when the
|
|
183
|
+computer is under heavy load (playing games, for example) it goes up even further to the 200W mark.
|
|
184
|
+With a couple of small tweaks to the rrdtool command, I also have a graph showing the entire week:
|
|
185
|
+
|
|
186
|
+<img src="/res/images/wemo/desk-1w.png" alt="Graph of power usage over a week">
|
|
187
|
+
|
|
188
|
+The two huge spikes near the start of the data are caused by a heater under my desk. They're also
|
|
189
|
+one of the main reasons I chose to plot the graphs with a logarithmic scale. With a linear scale
|
|
190
|
+between 20W and 2kW, the graph would be completely dominated by these large spikes — in fact
|
|
191
|
+the difference in height between the two spikes would take almost half the graph! The
|
|
192
|
+relative difference between the normal values of 60-200W and the heater's value of 1.8kW isn't
|
|
193
|
+actually that interesting, and certainly not worth using about 80% of the graph to demonstrate. The
|
|
194
|
+log scale helps to compress this, and emphasises the difference in the smaller values more.
|