├── Grafana └── Home-Power-Meter-Grafana-Dashboard.json ├── Node_Red ├── PowerMeter_Dashboard_v1.json ├── PowerMeter_Dashboard_v2.json └── README.md ├── PZEM004T-Test ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── lib │ └── readme.txt ├── platformio.ini └── src │ └── main.cpp ├── PowerMeter ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── lib │ ├── AppData │ │ ├── AppData.cpp │ │ └── AppData.hpp │ ├── LogClient │ │ ├── LogClient.hpp │ │ └── LogFile.cpp │ ├── TimeProvider │ │ ├── TimeProvider.cpp │ │ └── TimeProvider.hpp │ ├── WebServer │ │ ├── WebServer.cpp │ │ ├── WebServer.hpp │ │ ├── html_templates.h │ │ ├── layout-css-gz.h │ │ ├── pure-min-css-gz.h │ │ ├── trianglify.min.js │ │ └── trianglify.min.js-gz.h │ └── readme.txt ├── logServer.sh ├── platformio.ini └── src │ ├── main.cpp │ └── secrets.h ├── README.md └── images ├── grafana.png ├── node_red.png └── web_page.png /Grafana/Home-Power-Meter-Grafana-Dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 6, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "aliasColors": {}, 23 | "bars": false, 24 | "dashLength": 10, 25 | "dashes": false, 26 | "datasource": "IDB PowerMeter", 27 | "description": "Home Power Consumption over time", 28 | "fill": 1, 29 | "gridPos": { 30 | "h": 9, 31 | "w": 24, 32 | "x": 0, 33 | "y": 0 34 | }, 35 | "id": 2, 36 | "legend": { 37 | "avg": false, 38 | "current": false, 39 | "max": false, 40 | "min": false, 41 | "show": true, 42 | "total": false, 43 | "values": false 44 | }, 45 | "lines": true, 46 | "linewidth": 1, 47 | "links": [], 48 | "nullPointMode": "connected", 49 | "options": {}, 50 | "percentage": false, 51 | "pointradius": 2, 52 | "points": true, 53 | "renderer": "flot", 54 | "seriesOverrides": [], 55 | "spaceLength": 10, 56 | "stack": false, 57 | "steppedLine": false, 58 | "targets": [ 59 | { 60 | "groupBy": [], 61 | "measurement": "PowerMeter", 62 | "orderByTime": "ASC", 63 | "policy": "default", 64 | "refId": "A", 65 | "resultFormat": "time_series", 66 | "select": [ 67 | [ 68 | { 69 | "params": [ 70 | "P" 71 | ], 72 | "type": "field" 73 | } 74 | ] 75 | ], 76 | "tags": [] 77 | } 78 | ], 79 | "thresholds": [], 80 | "timeFrom": null, 81 | "timeRegions": [], 82 | "timeShift": null, 83 | "title": "Power Consumption", 84 | "tooltip": { 85 | "shared": true, 86 | "sort": 0, 87 | "value_type": "individual" 88 | }, 89 | "type": "graph", 90 | "xaxis": { 91 | "buckets": null, 92 | "mode": "time", 93 | "name": null, 94 | "show": true, 95 | "values": [] 96 | }, 97 | "yaxes": [ 98 | { 99 | "format": "short", 100 | "label": null, 101 | "logBase": 1, 102 | "max": null, 103 | "min": null, 104 | "show": true 105 | }, 106 | { 107 | "format": "short", 108 | "label": null, 109 | "logBase": 1, 110 | "max": null, 111 | "min": null, 112 | "show": true 113 | } 114 | ], 115 | "yaxis": { 116 | "align": false, 117 | "alignLevel": null 118 | } 119 | }, 120 | { 121 | "aliasColors": {}, 122 | "bars": false, 123 | "dashLength": 10, 124 | "dashes": false, 125 | "datasource": "IDB PowerMeter", 126 | "fill": 1, 127 | "gridPos": { 128 | "h": 8, 129 | "w": 24, 130 | "x": 0, 131 | "y": 9 132 | }, 133 | "id": 4, 134 | "legend": { 135 | "avg": false, 136 | "current": false, 137 | "max": false, 138 | "min": false, 139 | "show": true, 140 | "total": false, 141 | "values": false 142 | }, 143 | "lines": true, 144 | "linewidth": 1, 145 | "links": [], 146 | "nullPointMode": "null", 147 | "options": {}, 148 | "percentage": false, 149 | "pointradius": 2, 150 | "points": false, 151 | "renderer": "flot", 152 | "seriesOverrides": [], 153 | "spaceLength": 10, 154 | "stack": false, 155 | "steppedLine": false, 156 | "targets": [ 157 | { 158 | "groupBy": [], 159 | "measurement": "PowerMeter", 160 | "orderByTime": "ASC", 161 | "policy": "default", 162 | "refId": "A", 163 | "resultFormat": "time_series", 164 | "select": [ 165 | [ 166 | { 167 | "params": [ 168 | "I" 169 | ], 170 | "type": "field" 171 | } 172 | ] 173 | ], 174 | "tags": [] 175 | } 176 | ], 177 | "thresholds": [], 178 | "timeFrom": null, 179 | "timeRegions": [], 180 | "timeShift": null, 181 | "title": "Current", 182 | "tooltip": { 183 | "shared": true, 184 | "sort": 0, 185 | "value_type": "individual" 186 | }, 187 | "type": "graph", 188 | "xaxis": { 189 | "buckets": null, 190 | "mode": "time", 191 | "name": null, 192 | "show": true, 193 | "values": [] 194 | }, 195 | "yaxes": [ 196 | { 197 | "format": "short", 198 | "label": null, 199 | "logBase": 1, 200 | "max": null, 201 | "min": null, 202 | "show": true 203 | }, 204 | { 205 | "format": "short", 206 | "label": null, 207 | "logBase": 1, 208 | "max": null, 209 | "min": null, 210 | "show": true 211 | } 212 | ], 213 | "yaxis": { 214 | "align": false, 215 | "alignLevel": null 216 | } 217 | }, 218 | { 219 | "aliasColors": {}, 220 | "bars": false, 221 | "dashLength": 10, 222 | "dashes": false, 223 | "datasource": "IDB PowerMeter", 224 | "fill": 1, 225 | "gridPos": { 226 | "h": 8, 227 | "w": 12, 228 | "x": 0, 229 | "y": 17 230 | }, 231 | "id": 6, 232 | "legend": { 233 | "avg": false, 234 | "current": false, 235 | "max": false, 236 | "min": false, 237 | "show": true, 238 | "total": false, 239 | "values": false 240 | }, 241 | "lines": true, 242 | "linewidth": 1, 243 | "links": [], 244 | "nullPointMode": "null", 245 | "options": {}, 246 | "percentage": false, 247 | "pointradius": 2, 248 | "points": false, 249 | "renderer": "flot", 250 | "seriesOverrides": [], 251 | "spaceLength": 10, 252 | "stack": false, 253 | "steppedLine": false, 254 | "targets": [ 255 | { 256 | "groupBy": [], 257 | "measurement": "PowerMeter", 258 | "orderByTime": "ASC", 259 | "policy": "default", 260 | "refId": "A", 261 | "resultFormat": "time_series", 262 | "select": [ 263 | [ 264 | { 265 | "params": [ 266 | "E" 267 | ], 268 | "type": "field" 269 | } 270 | ] 271 | ], 272 | "tags": [] 273 | } 274 | ], 275 | "thresholds": [], 276 | "timeFrom": null, 277 | "timeRegions": [], 278 | "timeShift": null, 279 | "title": "Consumo de Energia", 280 | "tooltip": { 281 | "shared": true, 282 | "sort": 0, 283 | "value_type": "individual" 284 | }, 285 | "type": "graph", 286 | "xaxis": { 287 | "buckets": null, 288 | "mode": "time", 289 | "name": null, 290 | "show": true, 291 | "values": [] 292 | }, 293 | "yaxes": [ 294 | { 295 | "format": "short", 296 | "label": null, 297 | "logBase": 1, 298 | "max": null, 299 | "min": null, 300 | "show": true 301 | }, 302 | { 303 | "format": "short", 304 | "label": null, 305 | "logBase": 1, 306 | "max": null, 307 | "min": null, 308 | "show": true 309 | } 310 | ], 311 | "yaxis": { 312 | "align": false, 313 | "alignLevel": null 314 | } 315 | } 316 | ], 317 | "refresh": false, 318 | "schemaVersion": 18, 319 | "style": "dark", 320 | "tags": [], 321 | "templating": { 322 | "list": [] 323 | }, 324 | "time": { 325 | "from": "2019-11-22T17:41:28.278Z", 326 | "to": "2019-11-22T18:52:01.336Z" 327 | }, 328 | "timepicker": { 329 | "refresh_intervals": [ 330 | "5s", 331 | "10s", 332 | "30s", 333 | "1m", 334 | "5m", 335 | "15m", 336 | "30m", 337 | "1h", 338 | "2h", 339 | "1d" 340 | ], 341 | "time_options": [ 342 | "5m", 343 | "15m", 344 | "1h", 345 | "6h", 346 | "12h", 347 | "24h", 348 | "2d", 349 | "7d", 350 | "30d" 351 | ] 352 | }, 353 | "timezone": "", 354 | "title": "Home Power Meter", 355 | "uid": "Bq5X7SRRk", 356 | "version": 7 357 | } -------------------------------------------------------------------------------- /Node_Red/PowerMeter_Dashboard_v1.json: -------------------------------------------------------------------------------- 1 | [{"id":"c69b35de.5b1288","type":"tab","label":"Power Meter Dash","disabled":false,"info":"Power Meter DashBoard"},{"id":"11b29614.713a5a","type":"mqtt in","z":"c69b35de.5b1288","name":"PM Telemetry","topic":"iot/device/ESP8266_PowerMeter/telemetry","qos":"2","datatype":"auto","broker":"2a552b3c.de8d2c","x":140,"y":60,"wires":[["4f01e095.a6b468"]]},{"id":"4f01e095.a6b468","type":"json","z":"c69b35de.5b1288","name":"To JSON","property":"payload","action":"","pretty":false,"x":350,"y":60,"wires":[["3afc46aa.19ff9a","b4966155.90ad9","4e1496c4.95576","2cdca24.0a446de","3c7e559f.3df0ea","db4513ed.467ef8"]]},{"id":"3afc46aa.19ff9a","type":"function","z":"c69b35de.5b1288","name":"Voltage (V)","func":"msg.payload = Number(msg.payload.V);\nreturn msg;","outputs":1,"noerr":0,"x":570,"y":60,"wires":[["c2e720c3.2121d","f13ac150.bfe46"]]},{"id":"b4966155.90ad9","type":"function","z":"c69b35de.5b1288","name":"Current (I)","func":"msg.payload = Number(msg.payload.I);\nreturn msg;","outputs":1,"noerr":0,"x":570,"y":120,"wires":[["880f974.d4d29e8","b4e96359.2356"]]},{"id":"4e1496c4.95576","type":"function","z":"c69b35de.5b1288","name":"Power (W)","func":"msg.payload = Number(msg.payload.P);\nreturn msg;","outputs":1,"noerr":0,"x":570,"y":180,"wires":[["a86d0454.7060e","926dfc32.8b45f"]]},{"id":"2cdca24.0a446de","type":"function","z":"c69b35de.5b1288","name":"Energy (E)","func":"msg.payload = Number(msg.payload.E);\nreturn msg;","outputs":1,"noerr":0,"x":570,"y":240,"wires":[["c1a24f6d.1f1c98","9dc65db8.01578","c9d8a4b2.56abf8"]]},{"id":"c2e720c3.2121d","type":"ui_gauge","z":"c69b35de.5b1288","name":"Voltage","group":"ba196e43.b35398","order":0,"width":0,"height":0,"gtype":"gage","title":"Voltage","label":"V","format":"{{value}}","min":0,"max":"250","colors":["#e6e600","#00b500","#ca3838"],"seg1":"215","seg2":"230","x":880,"y":60,"wires":[]},{"id":"f13ac150.bfe46","type":"ui_chart","z":"c69b35de.5b1288","name":"Voltage/Time","group":"ba196e43.b35398","order":0,"width":0,"height":0,"label":"Voltage/Time","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":890,"y":120,"wires":[[],[]]},{"id":"880f974.d4d29e8","type":"ui_gauge","z":"c69b35de.5b1288","name":"Current","group":"ab1cea3.a296a98","order":0,"width":0,"height":0,"gtype":"gage","title":"Current","label":"Amps","format":"{{value}}","min":0,"max":"20","colors":["#00b500","#e6e600","#ca3838"],"seg1":"7","seg2":"10","x":880,"y":180,"wires":[]},{"id":"b4e96359.2356","type":"ui_chart","z":"c69b35de.5b1288","name":"Current/Time","group":"ab1cea3.a296a98","order":0,"width":0,"height":0,"label":"Current/Time","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":890,"y":240,"wires":[[],[]]},{"id":"a86d0454.7060e","type":"ui_gauge","z":"c69b35de.5b1288","name":"Power","group":"c3488338.d22af","order":0,"width":0,"height":0,"gtype":"gage","title":"Power","label":"W","format":"{{value}}","min":0,"max":"4000","colors":["#00b500","#e6e600","#ca3838"],"seg1":"3900","seg2":"3950","x":870,"y":300,"wires":[]},{"id":"926dfc32.8b45f","type":"ui_chart","z":"c69b35de.5b1288","name":"Power Consumption","group":"c3488338.d22af","order":0,"width":0,"height":0,"label":"Power Consumption","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":920,"y":360,"wires":[[],[]]},{"id":"c1a24f6d.1f1c98","type":"ui_gauge","z":"c69b35de.5b1288","name":"Energy","group":"22202c3a.4ce5e4","order":0,"width":0,"height":0,"gtype":"donut","title":"Energy","label":"Wh","format":"{{value}}","min":0,"max":"9999","colors":["#00b500","#e6e600","#ca3838"],"seg1":"49000","seg2":"49500","x":880,"y":420,"wires":[]},{"id":"9dc65db8.01578","type":"ui_chart","z":"c69b35de.5b1288","name":"Energy/Time","group":"22202c3a.4ce5e4","order":0,"width":0,"height":0,"label":"Energy/Time","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":890,"y":480,"wires":[[],[]]},{"id":"c9d8a4b2.56abf8","type":"ui_text","z":"c69b35de.5b1288","group":"22202c3a.4ce5e4","order":0,"width":0,"height":0,"name":"Energy","label":"","format":"{{msg.payload}} Wh","layout":"col-center","x":880,"y":540,"wires":[]},{"id":"3c7e559f.3df0ea","type":"function","z":"c69b35de.5b1288","name":"Power Factor","func":"var power = 0.0;\nvar current = 0.0;\nvar voltage = 0.0;\n\ncurrent = Number(msg.payload.I);\nvoltage = Number(msg.payload.V);\npower = Number(msg.payload.P);\n\nif ( power > 0.0 ) \n msg.payload = Math.floor(power/(voltage * current)*100)/100;\nelse \n msg.payload = 1.0;\n\nreturn msg;","outputs":1,"noerr":0,"x":580,"y":300,"wires":[["33a63786.6e5268","db4513ed.467ef8"]]},{"id":"33a63786.6e5268","type":"ui_text","z":"c69b35de.5b1288","group":"c3488338.d22af","order":0,"width":0,"height":0,"name":"Power Factor","label":"","format":"PF: {{msg.payload}}","layout":"row-center","x":900,"y":600,"wires":[]},{"id":"db4513ed.467ef8","type":"debug","z":"c69b35de.5b1288","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":620,"y":400,"wires":[]},{"id":"2a552b3c.de8d2c","type":"mqtt-broker","z":"","broker":"192.168.1.17","port":"1883","clientid":"NodeRed","usetls":false,"verifyservercert":true,"compatmode":true,"keepalive":"15","cleansession":true,"birthTopic":"iot/devices/NodeRed/attributes","birthQos":"0","birthPayload":"[{\"active\":true}, {\"web\":\"http://192.168.1.17:1880\"}]","willTopic":"iot/devices/NodeRed/attributes","willQos":"0","willPayload":"[{\"active\":false}, {\"web\":\"http://192.168.1.17:1880\"}]"},{"id":"ba196e43.b35398","type":"ui_group","z":"","name":"Voltage","tab":"6f0d0a75.3994fc","disp":true,"width":"6"},{"id":"ab1cea3.a296a98","type":"ui_group","z":"","name":"Current","tab":"6f0d0a75.3994fc","order":2,"disp":true,"width":"6"},{"id":"c3488338.d22af","type":"ui_group","z":"","name":"Power","tab":"6f0d0a75.3994fc","order":3,"disp":true,"width":"6"},{"id":"22202c3a.4ce5e4","type":"ui_group","z":"","name":"Energy","tab":"6f0d0a75.3994fc","order":4,"disp":true,"width":"6"},{"id":"6f0d0a75.3994fc","type":"ui_tab","z":"","name":"Power Meter","icon":"dashboard"}] 2 | -------------------------------------------------------------------------------- /Node_Red/PowerMeter_Dashboard_v2.json: -------------------------------------------------------------------------------- 1 | [{"id":"c69b35de.5b1288","type":"tab","label":"Power Meter Dash","disabled":false,"info":"Power Meter DashBoard"},{"id":"11b29614.713a5a","type":"mqtt in","z":"c69b35de.5b1288","name":"PM Telemetry","topic":"iot/device/ESP8266_PowerMeter/telemetry","qos":"2","datatype":"auto","broker":"2a552b3c.de8d2c","x":140,"y":60,"wires":[["4f01e095.a6b468"]]},{"id":"4f01e095.a6b468","type":"json","z":"c69b35de.5b1288","name":"To JSON","property":"payload","action":"","pretty":false,"x":350,"y":60,"wires":[["3afc46aa.19ff9a","b4966155.90ad9","4e1496c4.95576","2cdca24.0a446de","3c7e559f.3df0ea","db4513ed.467ef8","73203776.015a9"]]},{"id":"3afc46aa.19ff9a","type":"function","z":"c69b35de.5b1288","name":"Voltage (V)","func":"msg.payload = Number(msg.payload.V);\nreturn msg;","outputs":1,"noerr":0,"x":570,"y":60,"wires":[["c2e720c3.2121d","f13ac150.bfe46"]]},{"id":"b4966155.90ad9","type":"function","z":"c69b35de.5b1288","name":"Current (I)","func":"msg.payload = Number(msg.payload.I);\nreturn msg;","outputs":1,"noerr":0,"x":570,"y":120,"wires":[["880f974.d4d29e8","b4e96359.2356"]]},{"id":"4e1496c4.95576","type":"function","z":"c69b35de.5b1288","name":"Power (W)","func":"msg.payload = Number(msg.payload.P);\nreturn msg;","outputs":1,"noerr":0,"x":570,"y":180,"wires":[["a86d0454.7060e","926dfc32.8b45f"]]},{"id":"2cdca24.0a446de","type":"function","z":"c69b35de.5b1288","name":"Energy (E)","func":"msg.payload = Number(msg.payload.E);\nreturn msg;","outputs":1,"noerr":0,"x":570,"y":240,"wires":[["9dc65db8.01578","c9d8a4b2.56abf8","705e9cfc.504c4c"]]},{"id":"c2e720c3.2121d","type":"ui_gauge","z":"c69b35de.5b1288","name":"Voltage","group":"ba196e43.b35398","order":0,"width":0,"height":0,"gtype":"gage","title":"Voltage","label":"V","format":"{{value}}","min":0,"max":"260","colors":["#e6e600","#00b500","#ca3838"],"seg1":"215","seg2":"240","x":880,"y":60,"wires":[]},{"id":"f13ac150.bfe46","type":"ui_chart","z":"c69b35de.5b1288","name":"Voltage/Time","group":"ba196e43.b35398","order":0,"width":0,"height":0,"label":"Voltage/Time","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":890,"y":120,"wires":[[],[]]},{"id":"880f974.d4d29e8","type":"ui_gauge","z":"c69b35de.5b1288","name":"Current","group":"ab1cea3.a296a98","order":0,"width":0,"height":0,"gtype":"gage","title":"Current","label":"Amps","format":"{{value}}","min":0,"max":"16","colors":["#00b500","#e6e600","#ca3838"],"seg1":"7","seg2":"13","x":880,"y":180,"wires":[]},{"id":"b4e96359.2356","type":"ui_chart","z":"c69b35de.5b1288","name":"Current/Time","group":"ab1cea3.a296a98","order":0,"width":0,"height":0,"label":"Current/Time","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":890,"y":240,"wires":[[],[]]},{"id":"a86d0454.7060e","type":"ui_gauge","z":"c69b35de.5b1288","name":"Power","group":"c3488338.d22af","order":0,"width":0,"height":0,"gtype":"gage","title":"Current Power","label":"W","format":"{{value}}","min":0,"max":"4000","colors":["#00b500","#e6e600","#ca3838"],"seg1":"3900","seg2":"3950","x":870,"y":300,"wires":[]},{"id":"926dfc32.8b45f","type":"ui_chart","z":"c69b35de.5b1288","name":"Power Consumption","group":"c3488338.d22af","order":0,"width":0,"height":0,"label":"Power Consumption","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":920,"y":360,"wires":[[],[]]},{"id":"c1a24f6d.1f1c98","type":"ui_gauge","z":"c69b35de.5b1288","name":"Energy","group":"22202c3a.4ce5e4","order":0,"width":0,"height":0,"gtype":"donut","title":"Total Energy Consumption","label":"KWh","format":"{{value | number:1}}","min":0,"max":"9999","colors":["#00b500","#e6e600","#ca3838"],"seg1":"49000","seg2":"49500","x":1100,"y":420,"wires":[]},{"id":"9dc65db8.01578","type":"ui_chart","z":"c69b35de.5b1288","name":"Energy/Time","group":"22202c3a.4ce5e4","order":0,"width":0,"height":0,"label":"Energy/Time","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":890,"y":480,"wires":[[],[]]},{"id":"c9d8a4b2.56abf8","type":"ui_text","z":"c69b35de.5b1288","group":"22202c3a.4ce5e4","order":0,"width":0,"height":0,"name":"Total Energy Consumption","label":"","format":"{{msg.payload}} Wh","layout":"col-center","x":940,"y":540,"wires":[]},{"id":"3c7e559f.3df0ea","type":"function","z":"c69b35de.5b1288","name":"Power Factor","func":"var power = 0.0;\nvar current = 0.0;\nvar voltage = 0.0;\n\ncurrent = Number(msg.payload.I);\nvoltage = Number(msg.payload.V);\npower = Number(msg.payload.P);\n\nif ( power > 0.0 ) \n msg.payload = Math.floor(power/(voltage * current)*100)/100;\nelse \n msg.payload = 1.0;\n\nreturn msg;","outputs":1,"noerr":0,"x":580,"y":300,"wires":[["33a63786.6e5268","db4513ed.467ef8"]]},{"id":"33a63786.6e5268","type":"ui_text","z":"c69b35de.5b1288","group":"c3488338.d22af","order":0,"width":0,"height":0,"name":"Power Factor","label":"","format":"PF: {{msg.payload}}","layout":"row-center","x":900,"y":600,"wires":[]},{"id":"db4513ed.467ef8","type":"debug","z":"c69b35de.5b1288","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":620,"y":400,"wires":[]},{"id":"172263c8.eb721c","type":"mqtt in","z":"c69b35de.5b1288","name":"PM Attributes","topic":"iot/device/ESP8266_PowerMeter/attributes","qos":"2","datatype":"auto","broker":"2a552b3c.de8d2c","x":140,"y":160,"wires":[["db4513ed.467ef8"]]},{"id":"73203776.015a9","type":"influxdb out","z":"c69b35de.5b1288","influxdb":"64abe2a3.d58504","name":"InfluxDB PowerMeter DB","measurement":"PowerMeter","precision":"","retentionPolicy":"","x":490,"y":520,"wires":[]},{"id":"705e9cfc.504c4c","type":"function","z":"c69b35de.5b1288","name":"To KWh","func":"msg.payload = msg.payload /1000;\n\nreturn msg;","outputs":1,"noerr":0,"x":890,"y":420,"wires":[["c1a24f6d.1f1c98"]]},{"id":"2a552b3c.de8d2c","type":"mqtt-broker","z":"","broker":"192.168.1.17","port":"1883","clientid":"NodeRed","usetls":false,"verifyservercert":true,"compatmode":true,"keepalive":"15","cleansession":true,"birthTopic":"iot/devices/NodeRed/attributes","birthQos":"0","birthPayload":"[{\"active\":true}, {\"web\":\"http://192.168.1.17:1880\"}]","willTopic":"iot/devices/NodeRed/attributes","willQos":"0","willPayload":"[{\"active\":false}, {\"web\":\"http://192.168.1.17:1880\"}]"},{"id":"ba196e43.b35398","type":"ui_group","z":"","name":"Voltage","tab":"6f0d0a75.3994fc","disp":true,"width":"6"},{"id":"ab1cea3.a296a98","type":"ui_group","z":"","name":"Current","tab":"6f0d0a75.3994fc","order":2,"disp":true,"width":"6"},{"id":"c3488338.d22af","type":"ui_group","z":"","name":"Power","tab":"6f0d0a75.3994fc","order":3,"disp":true,"width":"6"},{"id":"22202c3a.4ce5e4","type":"ui_group","z":"","name":"Energy","tab":"6f0d0a75.3994fc","order":4,"disp":true,"width":"6"},{"id":"64abe2a3.d58504","type":"influxdb","z":"","hostname":"127.0.0.1","port":"8086","protocol":"http","database":"PowerMeter","name":"InfluxDB PowerMeter DB","usetls":false,"tls":""},{"id":"6f0d0a75.3994fc","type":"ui_tab","z":"","name":"Power Meter","icon":"dashboard"}] 2 | -------------------------------------------------------------------------------- /Node_Red/README.md: -------------------------------------------------------------------------------- 1 | # Node-Red Dashboard 2 | 3 | Node-red flow to show data published from the ESP8266 power meter. 4 | 5 | Must connect to the same MQTT broker used by the power meter. 6 | -------------------------------------------------------------------------------- /PZEM004T-Test/.gitignore: -------------------------------------------------------------------------------- 1 | .pioenvs 2 | .piolibdeps 3 | .clang_complete 4 | .gcc-flags.json 5 | -------------------------------------------------------------------------------- /PZEM004T-Test/.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to by used as a library with examples 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /PZEM004T-Test/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PZEM004T-Test/README.md: -------------------------------------------------------------------------------- 1 | # PZEM004T Test Program 2 | 3 | Test program for the PeaceFair PZEM004T power meter. 4 | 5 | Note: Library versions are not locked here, so it makes possible to test the code to see if it breaks with the latest updates... 6 | -------------------------------------------------------------------------------- /PZEM004T-Test/lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) http://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- readme.txt --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | Then in `src/main.c` you should use: 31 | 32 | #include 33 | #include 34 | 35 | // rest H/C/CPP code 36 | 37 | PlatformIO will find your libraries automatically, configure preprocessor's 38 | include paths and build them. 39 | 40 | More information about PlatformIO Library Dependency Finder 41 | - http://docs.platformio.org/page/librarymanager/ldf.html 42 | -------------------------------------------------------------------------------- /PZEM004T-Test/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; http://docs.platformio.org/page/projectconf.html 10 | 11 | [env:d1_mini] 12 | platform = espressif8266 13 | board = d1_mini_lite 14 | framework = arduino 15 | 16 | lib_deps = PZEM004T 17 | 18 | ; For OTA firmware upload over the air you have to uncommend 19 | ; the following two lines 20 | ; upload_port = 10.0.0.20 21 | ; upload_flags = --auth=OTAFUpdate 22 | -------------------------------------------------------------------------------- /PZEM004T-Test/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | //PZEM004T pzem( 5, 4 ); // RX,TX 4 | PZEM004T pzem( D5, D6 ) ; // Wemos D1 pins to be used for PZEM004T UART communications. 5 | 6 | // The PZEM004T needs an IP to be set. 7 | // We're not using it since we read data from the serial port. 8 | IPAddress ip(192,168,1,1); 9 | 10 | bool isPzemReady = false; 11 | 12 | void setup() { 13 | Serial.begin( 115200 ); 14 | 15 | while ( !isPzemReady ) { 16 | Serial.println("Connecting to PZEM..."); 17 | isPzemReady = pzem.setAddress(ip); 18 | delay(1000); 19 | } 20 | 21 | Serial.println("Connected to PZEM..."); 22 | } 23 | 24 | void loop() { 25 | 26 | delay(5000); 27 | 28 | Serial.println(F("-----------------------------------")); 29 | 30 | float v = pzem.voltage(ip); 31 | if (v < 0.0) 32 | v = 0.0; 33 | 34 | Serial.print("V: "); 35 | Serial.print( v ); 36 | Serial.println("V"); 37 | 38 | float i = pzem.current(ip); 39 | if ( i >= 0.0 ) { 40 | Serial.print("I: "); 41 | Serial.print( i ); 42 | Serial.println("A "); 43 | } 44 | 45 | float p = pzem.power(ip); 46 | if (p >= 0.0 ) { 47 | Serial.print("PWR: "); 48 | Serial.print( p ); 49 | Serial.print("W "); 50 | } 51 | 52 | float e = pzem.energy(ip); 53 | if( e >= 0.0 ) { 54 | Serial.print("Energy: "); 55 | Serial.print( e ); 56 | Serial.println("Wh; "); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PowerMeter/.gitignore: -------------------------------------------------------------------------------- 1 | .pio/ 2 | .piolibdeps/ 3 | .vscode/.browse.c_cpp.db* 4 | .vscode/c_cpp_properties.json 5 | .vscode/launch.json 6 | .vscode/extensions.json 7 | -------------------------------------------------------------------------------- /PowerMeter/.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to by used as a library with examples 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /PowerMeter/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PowerMeter/README.md: -------------------------------------------------------------------------------- 1 | # PZEM004T ESP8266 Power Meter 2 | 3 | Contains the firmware for the ESP8266 with the PZEM-004T based power meter as described in https://primalcortex.wordpress.com/2019/07/06/measuring-home-energy-consumption-with-the-pzem004t-and-esp8266/ and https://primalcortex.wordpress.com/2019/07/25/pzem-004t-esp8266-software/ 4 | 5 | **Master branch** - Code for version 2.0 of the PZEM004-T device. Tested and in "production". 6 | 7 | **v30 branch** - Code for version 3.0 of the PZEM004-T device. Since I don't have this version, it is not tested and know if it really works. 8 | 9 | 10 | This firmware provides the following functionality: 11 | 12 | - Allows to connect to several access points and cycle between them in case of failure 13 | - Sends data to a MQTT broker 14 | - Uses software serial to connect and transfer data between the ESP8266 and the PZEM004T 15 | - Provides an UDP based logging facility (much similar to syslog server, but not compatible) to view logging data without the need of a serial connection to the board 16 | - Over the Air update support 17 | - Web page that shows the current state (Available at the standard port 80): 18 | 19 | ![](/images/web_page.png?raw=true) 20 | 21 | - Integration with Node-Red where among other things we can see the current power factor, (more of interest of industrial applications than home applications) 22 | 23 | ![](/images/node_red.png?raw=true) 24 | 25 | # Configuration 26 | 27 | An initial configuration should be done on the *secrets.h* file, where the available access points are defined, namely the number: *NUMAPS*. 28 | The Wifi configuration array then should be filled with the correct number, SSID and passwords for the available access points. 29 | The recomendation is that the first entry should be the main access point, either the one nearer or stronger. 30 | 31 | The MQTT broker host and credential should also be set on this file. 32 | 33 | Finaly the end point for the UDP log server should also be set. 34 | 35 | On the log server, a Linux machine with the socat package installed, the *logserver.sh* script should be run to view the current log messages from the firmware. 36 | Off course data is only available if the ESP8266 is connected to WIFI and is running. 37 | 38 | # Onboard LED 39 | The Wemos D1 onboard led blinks as follows: 40 | 41 | - Very slow -> Connecting to WIFI 42 | - Very fast -> Connecting to PZEM004T -> We can see the connection state either at the web page or the log output 43 | - Medium speed -> Working 44 | - Lit -> Reading data from the PZEM004T. 45 | 46 | Enjoy! 47 | -------------------------------------------------------------------------------- /PowerMeter/lib/AppData/AppData.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | AppData appData; 7 | 8 | void AppData::setFWVersion( String fwversion ) { 9 | m_FWVersion = fwversion; 10 | } 11 | 12 | void AppData::setLogServerIPInfo( String lsIP ) { 13 | m_logServerIP = lsIP; 14 | } 15 | 16 | void AppData::setPZEMState( uint8_t state ) { 17 | m_PZEMState = state; 18 | } 19 | 20 | String AppData::getPZEMState() { 21 | switch ( m_PZEMState) { 22 | case PZEM_DISCONNECTED: return String("Not Connected"); 23 | case PZEM_CONNECTING: return String("Connecting"); 24 | case PZEM_CONNECTED: return String("Connected"); 25 | case PZEM_CONNECTFAIL: return String("Connect Failed"); 26 | default: return ("State Error"); 27 | } 28 | } 29 | 30 | void AppData::setVoltage(float Vin) { 31 | m_V = Vin; 32 | } 33 | 34 | String AppData::getVoltage() { 35 | return String(m_V); 36 | } 37 | 38 | void AppData::setCurrent(float Iin) { 39 | m_I = Iin; 40 | } 41 | 42 | String AppData::getCurrent() { 43 | return String(m_I); 44 | } 45 | 46 | void AppData::setPower(float Pin) { 47 | m_P = Pin; 48 | } 49 | 50 | String AppData::getPower() { 51 | return String(m_P); 52 | } 53 | 54 | void AppData::setEnergy(float Ein) { 55 | m_E = Ein; 56 | } 57 | 58 | String AppData::getEnergy() { 59 | return String(m_E); 60 | } 61 | 62 | void AppData::setSamplesOK() { 63 | m_samplesOK++; 64 | } 65 | 66 | void AppData::setSamplesNOK() { 67 | m_samplesNOK++; 68 | } 69 | 70 | String AppData::getSamplesOK() { 71 | return String(m_samplesOK); 72 | } 73 | 74 | String AppData::getSamplesNOK() { 75 | return String(m_samplesNOK); 76 | } 77 | 78 | String AppData::getFWVersion() { 79 | return m_FWVersion; 80 | } 81 | 82 | String AppData::getSSID() { 83 | return WiFi.SSID(); 84 | } 85 | 86 | String AppData::getRSSI() { 87 | return String(WiFi.RSSI()); 88 | } 89 | 90 | String AppData::getDevIP() { 91 | IPAddress ipdev = WiFi.localIP(); 92 | 93 | return ipdev.toString(); 94 | } 95 | 96 | String AppData::getGWIP() { 97 | IPAddress ipgw = WiFi.gatewayIP(); 98 | 99 | return ipgw.toString(); 100 | } 101 | 102 | String AppData::getLogServerIPInfo() { 103 | return m_logServerIP; 104 | } 105 | 106 | String AppData::getHeap() { 107 | return String(ESP.getFreeHeap()); 108 | } -------------------------------------------------------------------------------- /PowerMeter/lib/AppData/AppData.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __APP_DATA_H__ 2 | #define __APP_DATA_H__ 3 | 4 | #include 5 | 6 | #define PZEM_DISCONNECTED 0 7 | #define PZEM_CONNECTING 1 8 | #define PZEM_CONNECTED 2 9 | #define PZEM_CONNECTFAIL 3 10 | 11 | class AppData 12 | { 13 | private: 14 | String m_FWVersion; 15 | String m_logServerIP; 16 | float m_V = 0; 17 | float m_I = 0; 18 | float m_P = 0; 19 | float m_E = 0; 20 | 21 | unsigned long m_samplesOK = 0; 22 | unsigned long m_samplesNOK= 0; 23 | 24 | uint8_t m_PZEMState = 0; // 0 - Not Connected, 1 - Connecting, 2 - Connected. 25 | 26 | public: 27 | 28 | void setFWVersion( String); 29 | void setLogServerIPInfo( String ); 30 | 31 | void setVoltage( float ); 32 | void setCurrent( float ); 33 | void setPower( float ); 34 | void setEnergy( float ); 35 | void setSamplesOK(); 36 | void setSamplesNOK(); 37 | 38 | void setPZEMState( uint8_t ); 39 | String getPZEMState(); 40 | 41 | String getFWVersion(); 42 | String getLogServerIPInfo(); 43 | String getDevIP(); 44 | String getGWIP(); 45 | String getSSID(); 46 | String getRSSI(); 47 | String getHeap() ; 48 | 49 | String getVoltage(); 50 | String getCurrent(); 51 | String getPower(); 52 | String getEnergy(); 53 | 54 | String getSamplesOK(); 55 | String getSamplesNOK(); 56 | }; 57 | 58 | extern AppData appData; 59 | 60 | #endif -------------------------------------------------------------------------------- /PowerMeter/lib/LogClient/LogClient.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __LOG_CLIENT_H__ 2 | #define __LOG_CLIENT_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class LogClient 9 | { 10 | private: 11 | bool serialEnabled = true; 12 | bool udpEnabled = false; 13 | 14 | WiFiUDP UDPConnection; 15 | 16 | IPAddress udpServer; 17 | uint16_t udpPort; 18 | uint16_t localPort = 2390; 19 | 20 | String tagname; 21 | 22 | void sendUdpMsg( String); 23 | 24 | public: 25 | void setSerial( bool ); 26 | void setUdp ( bool ); 27 | void setServer( IPAddress, uint16_t); 28 | void setTagName( String ); 29 | 30 | // INFO Messages 31 | void I( const char *); 32 | void I( String ); 33 | 34 | // WARN Messages 35 | void W( const char *); 36 | void W( String ); 37 | 38 | // ERROR Messages 39 | void E( const char *); 40 | void E( String ); 41 | 42 | }; 43 | 44 | extern LogClient Log; 45 | 46 | #endif -------------------------------------------------------------------------------- /PowerMeter/lib/LogClient/LogFile.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Variable used by all modules to output logging. 8 | LogClient Log; 9 | 10 | void LogClient::sendUdpMsg( String msg) 11 | { 12 | // Only send data if we are connected... 13 | if ( WiFi.status() == WL_CONNECTED ) { 14 | unsigned int msg_length = msg.length(); 15 | byte* p = (byte*)malloc(msg_length); 16 | memcpy(p, (char*) msg.c_str(), msg_length); 17 | 18 | UDPConnection.beginPacket( udpServer, udpPort); 19 | UDPConnection.write(p, msg_length); 20 | UDPConnection.endPacket(); 21 | free(p); 22 | } 23 | } 24 | 25 | void LogClient::setTagName(String name ) { 26 | tagname = name; 27 | } 28 | 29 | void LogClient::setSerial(bool enabled ) { 30 | serialEnabled = enabled; 31 | }; 32 | 33 | void LogClient::setUdp( bool enabled ) { 34 | uint8_t tries = 0; 35 | 36 | udpEnabled = enabled; 37 | while ( (WiFi.status() != WL_CONNECTED) && tries < 10 ) { 38 | delay( 100 ); 39 | tries++; 40 | Serial.println("Not connected!!!"); 41 | } 42 | } 43 | 44 | void LogClient::setServer( IPAddress ipa , uint16_t port ) { 45 | udpServer = ipa; 46 | udpPort = port; 47 | } 48 | 49 | void LogClient::I(const char *s ) { 50 | String msg = String(s); 51 | msg = "[" + tagname + "][INFO] " + msg + "\n"; 52 | if ( serialEnabled ) Serial.print( msg ); 53 | if ( udpEnabled) sendUdpMsg( msg ); 54 | } 55 | 56 | void LogClient::I( String s) { 57 | s = "[" + tagname + "][INFO] " + s + "\n"; 58 | if ( serialEnabled ) Serial.print( s ); 59 | if ( udpEnabled ) sendUdpMsg( s ); 60 | } 61 | 62 | void LogClient::W(const char *s ) { 63 | String msg = String(s); 64 | msg = "[" + tagname + "][WARN] " + msg + "\n"; 65 | if ( serialEnabled ) Serial.print( msg ); 66 | if ( udpEnabled) sendUdpMsg( msg ); 67 | } 68 | 69 | void LogClient::W( String s) { 70 | s = "[" + tagname + "][WARN] " + s + "\n"; 71 | if ( serialEnabled ) Serial.print( s ); 72 | if ( udpEnabled ) sendUdpMsg( s ); 73 | } 74 | 75 | void LogClient::E(const char *s ) { 76 | String msg = String(s); 77 | msg = "[" + tagname + "][ERROR] " + msg + "\n"; 78 | if ( serialEnabled ) Serial.print( msg ); 79 | if ( udpEnabled) sendUdpMsg( msg ); 80 | } 81 | 82 | void LogClient::E( String s) { 83 | s = "[" + tagname + "][ERROR] " + s + "\n"; 84 | if ( serialEnabled ) Serial.print( s ); 85 | if ( udpEnabled ) sendUdpMsg( s ); 86 | } 87 | -------------------------------------------------------------------------------- /PowerMeter/lib/TimeProvider/TimeProvider.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | const int NTP_PACKET_SIZE = 48; // Fixed size of NTP record 10 | uint8_t packetBuffer[NTP_PACKET_SIZE]; 11 | WiFiUDP Udp; 12 | IPAddress ntpServer; // IP address of NTP_TIMESERVER from DNS Query 13 | String NTP_TIMESERVER; 14 | int NTP_TIMEZONES; 15 | 16 | TimeProvider timeProvider; 17 | 18 | // Define Time Zones here 19 | TimeChangeRule PST = {"PST", Last, Sun, Mar, 1, 60}; //Portuguese Summer Time - "UTC + 1" or GMT + 1 20 | TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; //Portuguese winter Time - "UTC + 0" or GMT 21 | Timezone tz(PST, GMT); 22 | TimeChangeRule *tcr; 23 | 24 | 25 | // ---------------------------------------------------------------------------- 26 | // Send the request packet to the NTP server. 27 | // 28 | // ---------------------------------------------------------------------------- 29 | // send an NTP request to the time server at the given address 30 | void sendNTPpacket(IPAddress &address) 31 | { 32 | // set all bytes in the buffer to 0 33 | memset(packetBuffer, 0, NTP_PACKET_SIZE); 34 | // Initialize values needed to form NTP request 35 | // (see URL above for details on the packets) 36 | packetBuffer[0] = 0b11100011; // LI, Version, Mode 37 | packetBuffer[1] = 0; // Stratum, or type of clock 38 | packetBuffer[2] = 6; // Polling Interval 39 | packetBuffer[3] = 0xEC; // Peer Clock Precision 40 | // 8 bytes of zero for Root Delay & Root Dispersion 41 | packetBuffer[12] = 49; 42 | packetBuffer[13] = 0x4E; 43 | packetBuffer[14] = 49; 44 | packetBuffer[15] = 52; 45 | // all NTP fields have been given values, now 46 | // you can send a packet requesting a timestamp: 47 | Udp.beginPacket(address, 123); //NTP requests are to port 123 48 | Udp.write(packetBuffer, NTP_PACKET_SIZE); 49 | Udp.endPacket(); 50 | } 51 | 52 | 53 | time_t getNtpTime() 54 | { 55 | while (Udp.parsePacket() > 0) ; // discard any previously received packets 56 | 57 | WiFi.hostByName(NTP_TIMESERVER.c_str() , ntpServer); 58 | 59 | Log.I("NTP Request: " + NTP_TIMESERVER + ": " + ntpServer.toString()); 60 | sendNTPpacket(ntpServer); 61 | uint32_t beginWait = millis(); 62 | while (millis() - beginWait < 1500) { 63 | int size = Udp.parsePacket(); 64 | if (size >= NTP_PACKET_SIZE) { 65 | Log.I("Received NTP Response"); 66 | Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer 67 | unsigned long secsSince1900; 68 | // convert four bytes starting at location 40 to a long integer 69 | secsSince1900 = (unsigned long)packetBuffer[40] << 24; 70 | secsSince1900 |= (unsigned long)packetBuffer[41] << 16; 71 | secsSince1900 |= (unsigned long)packetBuffer[42] << 8; 72 | secsSince1900 |= (unsigned long)packetBuffer[43]; 73 | return secsSince1900 - 2208988800UL + NTP_TIMEZONES * SECS_PER_HOUR; 74 | } 75 | } 76 | Log.E("No NTP Response :-("); 77 | return 0; // return 0 if unable to get the time 78 | } 79 | 80 | int getHour() { 81 | time_t gt,lt; 82 | struct tm *slt; 83 | 84 | gt = now(); 85 | lt = tz.toLocal( gt ); 86 | 87 | slt = localtime( < ); 88 | return slt->tm_hour; 89 | } 90 | 91 | void TimeProvider::setNTPServer(String server , unsigned long syncinterval ) { 92 | NTPServer = server; 93 | NTPSync = syncinterval; 94 | } 95 | 96 | void TimeProvider::setTimeZone( int tz ) { 97 | NTPTZ = tz; 98 | } 99 | 100 | void TimeProvider::setup() { 101 | if ( !initialized ) { 102 | 103 | // Initialize a local UDP port otherwise the NTP request won't work. 104 | Udp.begin(57334); 105 | 106 | NTP_TIMESERVER = NTPServer; 107 | NTP_TIMEZONES = NTPTZ; 108 | setSyncProvider( getNtpTime ); 109 | setSyncInterval( NTPSync ); 110 | initialized = true; 111 | } 112 | } 113 | 114 | String TimeProvider::getFullTime() { 115 | const char * Days [] ={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; 116 | String datetime = String(Days[weekday()-1]) + ", " + day() + "/" + month() + "/" + year() + " " + getHour() + ":"; 117 | 118 | int minutos = minute(); 119 | if ( minutos < 10 ) 120 | datetime = datetime + "0" + minutos + ":"; 121 | else 122 | datetime = datetime + minutos + ":"; 123 | 124 | int segundos = second(); 125 | if ( segundos < 10 ) 126 | datetime = datetime + "0" + segundos; 127 | else 128 | datetime = datetime + segundos; 129 | 130 | return datetime; 131 | } 132 | 133 | void TimeProvider::logTime() { 134 | String datetime = getFullTime(); 135 | Log.I( datetime ); 136 | } 137 | 138 | 139 | String TimeProvider::getTime() { 140 | String datetime; 141 | 142 | if ( getHour() < 10 ) 143 | datetime = "0" + String( getHour() ) + ":"; 144 | else 145 | datetime = String( getHour() ) + ":"; 146 | 147 | int minutos = minute(); 148 | if ( minutos < 10 ) 149 | datetime = datetime + "0" + minutos + ":"; 150 | else 151 | datetime = datetime + minutos + ":"; 152 | 153 | int segundos = second(); 154 | if ( segundos < 10 ) 155 | datetime = datetime + "0" + segundos; 156 | else 157 | datetime = datetime + segundos; 158 | 159 | return datetime; 160 | } 161 | 162 | String TimeProvider::getDate() { 163 | String datetime; 164 | 165 | if ( day() < 10 ) 166 | datetime = "0" + String( day() ) + "/"; 167 | else 168 | datetime = String( day() ) + "/"; 169 | 170 | if ( month() < 10 ) 171 | datetime = datetime + "0" + month() + "/"; 172 | else 173 | datetime = datetime + String( month() ) + "/"; 174 | 175 | datetime = datetime + String(year()); 176 | 177 | return datetime; 178 | } 179 | 180 | String TimeProvider::getHours() { 181 | String datetime; 182 | 183 | if ( getHour() < 10 ) 184 | datetime = "0" + String( getHour() ); 185 | else 186 | datetime = String( getHour() ); 187 | 188 | return datetime; 189 | } 190 | 191 | String TimeProvider::getMinutes() { 192 | String datetime; 193 | 194 | if ( minute() < 10 ) 195 | datetime = "0" + String( minute() ); 196 | else 197 | datetime = String( minute() ); 198 | 199 | return datetime; 200 | } 201 | 202 | String TimeProvider::getSeconds() { 203 | String datetime; 204 | 205 | if ( second() < 10 ) 206 | datetime = "0" + String( second() ); 207 | else 208 | datetime = String( second() ); 209 | 210 | return datetime; 211 | } 212 | 213 | 214 | time_t TimeProvider::getLocalTime() { 215 | time_t local_time = tz.toLocal(now(), &tcr); 216 | 217 | return local_time; 218 | } -------------------------------------------------------------------------------- /PowerMeter/lib/TimeProvider/TimeProvider.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __TIME_PROVIDER_H__ 2 | #define __TIME_PROVIDER_H__ 3 | 4 | #include 5 | #include 6 | 7 | class TimeProvider 8 | { 9 | private: 10 | bool initialized = false ; 11 | String NTPServer = "pt.pool.ntp.org"; 12 | unsigned long NTPSync = 3600; 13 | int NTPTZ = 0 ; // TZ for Lisbon/PT 14 | 15 | public: 16 | time_t getLocalTime(); 17 | void setNTPServer( String, unsigned long ); 18 | void setTimeZone( int ); 19 | void logTime(); 20 | void setup (); 21 | 22 | String getFullTime(); 23 | String getDate(); 24 | String getTime(); 25 | 26 | String getHours(); 27 | String getMinutes(); 28 | String getSeconds(); 29 | //void handle(); 30 | }; 31 | 32 | extern TimeProvider timeProvider; 33 | 34 | #endif -------------------------------------------------------------------------------- /PowerMeter/lib/WebServer/WebServer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "AppData.hpp" 6 | #include "html_templates.h" 7 | #include "trianglify.min.js-gz.h" 8 | 9 | WebServer webServer; 10 | 11 | static AsyncWebServer server(80); 12 | 13 | /* 14 | * Web Server pages: 15 | * 16 | */ 17 | 18 | /* Handle page not founds. */ 19 | void Web_PageNotFound( AsyncWebServerRequest *request ) 20 | { 21 | request->send(404); 22 | } 23 | 24 | /* Root Page: / */ 25 | 26 | void Web_RootPage( AsyncWebServerRequest *request ) 27 | { 28 | AsyncResponseStream *response = request->beginResponseStream("text/html"); 29 | response->printf( TEMPLATE_HEADER, appData.getFWVersion().c_str(), \ 30 | appData.getDevIP().c_str(), \ 31 | appData.getGWIP().c_str(), \ 32 | appData.getSSID().c_str(), \ 33 | appData.getRSSI().c_str(), \ 34 | appData.getLogServerIPInfo().c_str(), \ 35 | appData.getHeap().c_str(), \ 36 | appData.getVoltage().c_str(), \ 37 | appData.getCurrent().c_str(), \ 38 | appData.getPower().c_str(), \ 39 | appData.getEnergy().c_str(), \ 40 | appData.getSamplesOK().c_str(), \ 41 | appData.getSamplesNOK().c_str(), \ 42 | appData.getPZEMState().c_str() ); 43 | 44 | response->print( TEMPLATE_FOOTER); 45 | 46 | request->send(response); 47 | } 48 | 49 | 50 | /* 51 | * WebServer Class functions. 52 | */ 53 | 54 | WebServer::WebServer() 55 | { 56 | initialized = false; 57 | } 58 | 59 | void WebServer::setup() 60 | { 61 | server.on( "/", HTTP_GET, Web_RootPage ); 62 | 63 | server.onNotFound( Web_PageNotFound ); 64 | 65 | server.on("/trianglify.min.js", HTTP_GET, [](AsyncWebServerRequest *request) { 66 | AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", 67 | trianglify_min_js_gz, sizeof(trianglify_min_js_gz)); 68 | response->addHeader("Content-Encoding", "gzip"); 69 | request->send(response); 70 | }); 71 | 72 | server.begin(); 73 | 74 | initialized = true; 75 | 76 | } 77 | 78 | void WebServer::handle() 79 | { 80 | if( ! initialized ) 81 | { 82 | setup(); 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /PowerMeter/lib/WebServer/WebServer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __WEB_SERVER_H__ 2 | #define __WEB_SERVER_H__ 3 | 4 | class WebServer 5 | { 6 | private: 7 | bool initialized; 8 | 9 | public: 10 | WebServer(); 11 | 12 | void setup (); 13 | void handle(); 14 | }; 15 | 16 | extern WebServer webServer; 17 | 18 | #endif -------------------------------------------------------------------------------- /PowerMeter/lib/WebServer/html_templates.h: -------------------------------------------------------------------------------- 1 | const char TEMPLATE_HEADER[] = 2 | "" 3 | "" 4 | "ESP8266 PZEM004T Power Meter IoT Device" 5 | "" 6 | "" 7 | "" 8 | "" 9 | "" 10 | "" 11 | "" 12 | "" 13 | "
" 14 | "

ESP8266 Power Meter

" 15 | "
" 16 | "
" 17 | "
" 18 | "
" 19 | "

ESP8266

" 20 | "
" 21 | " " 22 | " " 26 | "" 30 | "" 34 | "" 38 | "" 42 | "" 46 | 47 | "
ParameterValue
Firmware Version: " 23 | // getFWV() 24 | "%s" 25 | "
ESP8266 IP: " 27 | // getIPAddress() 28 | "%s" 29 | "
ESP8266 GW: " 31 | //getGW() 32 | "%s" 33 | "
Wifi SSID: " 35 | //getSSID() 36 | "%s" 37 | "
Wifi RSSI: " 39 | //String(WiFi.RSSI()) 40 | "%s" 41 | "
Log Server: " 43 | //Log Server IP 44 | "%s" 45 | "
ESP Heap: " 48 | //ESP.getFreeHeap() 49 | "%s" 50 | "
" 51 | "" 52 | "
" 53 | "
" 54 | 55 | "
" 56 | "
" 57 | "
" 58 | "

PZEM004T Data

" 59 | "
" 60 | "" 61 | "" 66 | "" 71 | "" 76 | "" 81 | "
ParameterValue
Voltage: " 62 | // getVoltage() 63 | "%s" 64 | "V" 65 | "
Current: " 67 | // getCurrent() 68 | "%s" 69 | "A" 70 | "
Power: " 72 | // getPower() 73 | "%s" 74 | "W" 75 | "
Energy: " 77 | //getEnergy() 78 | "%s" 79 | "W" 80 | "
" 82 | "
" 83 | "
" 84 | 85 | 86 | "
" 87 | "
" 88 | "
" 89 | "

Samples

" 90 | "
" 91 | "" 92 | "" 96 | "" 100 | "" 104 | "
ParameterValue
OK: " 93 | "%s" 94 | 95 | "
NOK:" 97 | "%s" 98 | 99 | "
PZEM State:" 101 | "%s" 102 | 103 | "
" 105 | "
" 106 | "
" 107 | ; 108 | 109 | const char TEMPLATE_FOOTER[] = 110 | "
" 111 | "
" 112 | "" 134 | "" 135 | ""; 136 | -------------------------------------------------------------------------------- /PowerMeter/lib/WebServer/layout-css-gz.h: -------------------------------------------------------------------------------- 1 | // file name = layout.css.gz, length = 860 2 | #define LAYOUT_CSS_GZ_LEN 860 3 | const uint8_t LAYOUT_CSS_GZ[] PROGMEM = { 4 | 0x1f, 0x8b, 0x08, 0x08, 0xe6, 0x06, 0xa9, 0x5a, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 5 | 0x2e, 0x63, 0x73, 0x73, 0x00, 0xc5, 0x57, 0x51, 0x6f, 0xdb, 0x20, 0x10, 0x7e, 0xcf, 0xaf, 0x60, 6 | 0xad, 0x26, 0xa5, 0x52, 0xb1, 0x9c, 0x2c, 0x99, 0x3a, 0x57, 0xfb, 0x1f, 0x9b, 0xb6, 0x3d, 0x60, 7 | 0xfb, 0x6c, 0xa3, 0x60, 0xf0, 0x00, 0x37, 0x69, 0xab, 0xfd, 0xf7, 0x61, 0xc0, 0x8d, 0x9d, 0xd8, 8 | 0xb8, 0x6f, 0x55, 0xf2, 0x02, 0x77, 0xf7, 0xdd, 0x77, 0x77, 0x70, 0x9c, 0xa3, 0xac, 0x55, 0x5a, 9 | 0xd4, 0xb8, 0x06, 0xde, 0xe2, 0xa3, 0x24, 0x4d, 0x03, 0x12, 0xbd, 0xae, 0x10, 0x4a, 0x49, 0x76, 10 | 0x28, 0xa5, 0x68, 0x79, 0x8e, 0x33, 0xc1, 0x84, 0x4c, 0xd0, 0xed, 0x43, 0xdc, 0xfd, 0x1e, 0x8d, 11 | 0xb0, 0x26, 0xb2, 0xa4, 0x1c, 0xa7, 0x42, 0x1b, 0xdb, 0x04, 0x7d, 0x81, 0xba, 0xdb, 0x3d, 0x56, 12 | 0x54, 0x03, 0x56, 0x0d, 0xc9, 0x20, 0x41, 0x5c, 0x74, 0x68, 0xdd, 0x76, 0x23, 0x14, 0xd5, 0x54, 13 | 0xf0, 0x04, 0x49, 0x60, 0x44, 0xd3, 0x27, 0x78, 0x5c, 0xfd, 0x5b, 0xad, 0xa2, 0x81, 0x67, 0xeb, 14 | 0x31, 0xa7, 0xaa, 0x61, 0xe4, 0x39, 0x41, 0x94, 0x33, 0xca, 0x01, 0xa7, 0x4c, 0x64, 0x07, 0x8b, 15 | 0x4b, 0x73, 0x5d, 0x25, 0x88, 0xb4, 0x5a, 0x74, 0xcb, 0x27, 0x90, 0x9a, 0x66, 0x84, 0x61, 0xc2, 16 | 0x68, 0x69, 0x50, 0x6b, 0x9a, 0xe7, 0x0c, 0x3a, 0x09, 0x3e, 0x42, 0x7a, 0xa0, 0x1a, 0x17, 0x82, 17 | 0x6b, 0xac, 0x6a, 0x21, 0x74, 0x45, 0x79, 0x69, 0x2c, 0xb9, 0xa6, 0x46, 0x99, 0x28, 0xc8, 0xaf, 18 | 0x5d, 0x47, 0x4d, 0x2b, 0xc1, 0xc5, 0x6f, 0xdc, 0x1e, 0xee, 0x67, 0xa5, 0x15, 0x90, 0xdc, 0xc0, 19 | 0x59, 0xae, 0x3e, 0x25, 0x36, 0xe2, 0x45, 0xc8, 0xa4, 0x12, 0x86, 0xf3, 0x22, 0xb0, 0x53, 0x9b, 20 | 0x49, 0xbe, 0x96, 0x84, 0x9b, 0xcc, 0x4a, 0xe0, 0xfa, 0xca, 0x1f, 0xd6, 0xa2, 0xb1, 0x66, 0x53, 21 | 0x99, 0x36, 0xbb, 0x24, 0xef, 0xe0, 0x3b, 0xad, 0x04, 0x45, 0x7b, 0x57, 0xab, 0x7e, 0xb3, 0x2f, 22 | 0xa1, 0xdb, 0xbf, 0x04, 0x4e, 0x8d, 0xd7, 0x7c, 0x5c, 0x9c, 0xb7, 0xaa, 0x68, 0x38, 0xe9, 0xbe, 23 | 0x04, 0x99, 0xa1, 0x05, 0xf2, 0x9d, 0xc5, 0x36, 0x44, 0xca, 0x92, 0x81, 0x85, 0xf5, 0xa5, 0x7d, 24 | 0x88, 0x9b, 0x53, 0x67, 0x5d, 0x01, 0x2d, 0x2b, 0x7d, 0x5e, 0x5f, 0x7b, 0x3d, 0xe3, 0x93, 0x54, 25 | 0x09, 0xd6, 0x6a, 0x1b, 0xa2, 0x0d, 0xed, 0xab, 0xb3, 0x91, 0x0e, 0x22, 0x1e, 0xd9, 0x73, 0xc1, 26 | 0xe7, 0x89, 0x44, 0x29, 0x99, 0x4b, 0xbb, 0x2f, 0xf0, 0x14, 0x13, 0x4f, 0x7d, 0x77, 0x41, 0x7d, 27 | 0xeb, 0x96, 0xa9, 0x90, 0x39, 0x48, 0x2c, 0x4d, 0x65, 0x5b, 0x95, 0xa0, 0x4d, 0xec, 0xd5, 0x02, 28 | 0xfc, 0xb7, 0xdb, 0x51, 0x00, 0x1b, 0xbf, 0xec, 0x0f, 0xb5, 0x3d, 0x01, 0xbd, 0x2d, 0x63, 0x28, 29 | 0x8e, 0xf6, 0xca, 0xca, 0x6b, 0xf1, 0x12, 0x10, 0xaa, 0x59, 0xd9, 0xf4, 0x7e, 0x20, 0x47, 0x49, 30 | 0x41, 0xa5, 0xd2, 0x38, 0xab, 0x28, 0x73, 0x87, 0x62, 0x44, 0xad, 0x10, 0xb2, 0xf6, 0xe7, 0xd4, 31 | 0x14, 0x1e, 0x7e, 0xae, 0xb1, 0x29, 0xc8, 0xdd, 0x98, 0x61, 0x48, 0x47, 0x2d, 0xa9, 0x04, 0xc5, 32 | 0xd3, 0xb4, 0xa3, 0xd3, 0xb9, 0xb8, 0x13, 0x64, 0xa5, 0xd0, 0x06, 0x63, 0xbd, 0xdb, 0xe7, 0x50, 33 | 0x4e, 0x32, 0x9d, 0x50, 0x50, 0x41, 0xf9, 0xbc, 0x2c, 0x48, 0xf0, 0x3d, 0x99, 0xf5, 0x78, 0x78, 34 | 0x91, 0x2d, 0x5e, 0xa2, 0x8b, 0x43, 0x7c, 0xf1, 0x1c, 0x61, 0x95, 0x49, 0x00, 0x3e, 0x73, 0x4f, 35 | 0x64, 0x99, 0x92, 0x75, 0x7c, 0x8f, 0xfc, 0x3f, 0xda, 0xdf, 0x7d, 0xc0, 0xd1, 0x3d, 0x5f, 0xc2, 36 | 0xfe, 0x41, 0x72, 0x57, 0xd4, 0x5c, 0xbe, 0xcf, 0x0b, 0x77, 0xcf, 0x76, 0x8b, 0x17, 0x4c, 0x79, 37 | 0x0e, 0xa7, 0x04, 0xe1, 0xcd, 0x44, 0xbd, 0xda, 0xec, 0x00, 0x39, 0x9a, 0x4b, 0xc9, 0xc2, 0x55, 38 | 0xd8, 0xed, 0xde, 0x71, 0x17, 0x06, 0x4a, 0x6a, 0x51, 0x27, 0x2c, 0x37, 0xec, 0x03, 0x0f, 0xac, 39 | 0xef, 0x61, 0x81, 0xbe, 0x7c, 0xdd, 0xee, 0x46, 0xad, 0xf4, 0xf7, 0xb7, 0x89, 0xb7, 0xc2, 0xbe, 40 | 0x24, 0x17, 0xef, 0xd0, 0x30, 0xd1, 0x17, 0xd5, 0xf0, 0x1d, 0xd2, 0xa6, 0x7f, 0xd3, 0x9c, 0x90, 41 | 0x51, 0xa4, 0x39, 0xba, 0x05, 0xb0, 0xca, 0xb3, 0xe3, 0x47, 0xe7, 0x7a, 0x50, 0x2a, 0x83, 0x36, 42 | 0x4b, 0xe5, 0xe2, 0x19, 0xb6, 0xd4, 0x84, 0x99, 0x4e, 0xa8, 0x36, 0x61, 0x6c, 0x1e, 0x17, 0xaa, 43 | 0xf6, 0x63, 0x1d, 0x87, 0x2b, 0x76, 0x56, 0x50, 0x41, 0x79, 0xd0, 0xf6, 0x63, 0x9b, 0xbb, 0x4b, 44 | 0xd4, 0xe4, 0x39, 0x9f, 0xc8, 0x5d, 0x38, 0x5b, 0x78, 0xd3, 0x3d, 0x85, 0x4b, 0x29, 0x1b, 0x69, 45 | 0xa9, 0x65, 0xa5, 0x45, 0x85, 0xb7, 0x82, 0xc6, 0xa3, 0xd5, 0xa6, 0x3f, 0xa2, 0x83, 0x31, 0x4b, 46 | 0x48, 0xfa, 0x62, 0x26, 0x43, 0xc2, 0xa6, 0x02, 0x7e, 0xbd, 0x6c, 0x00, 0xbe, 0x33, 0xec, 0xf6, 47 | 0xa1, 0x87, 0xbb, 0x9b, 0xd8, 0x0a, 0x26, 0x8e, 0x09, 0xaa, 0xcc, 0x14, 0x0a, 0xdc, 0xf9, 0xac, 48 | 0x09, 0x75, 0x5d, 0xc1, 0xcf, 0xc9, 0x0c, 0x8a, 0x6e, 0x32, 0xe8, 0x27, 0xac, 0xb4, 0x35, 0x59, 49 | 0xe7, 0x58, 0xf0, 0xe1, 0x08, 0xe4, 0xe7, 0x32, 0x6f, 0xd1, 0xcf, 0x00, 0x6e, 0x73, 0xf2, 0x32, 50 | 0x14, 0xc5, 0x43, 0x3c, 0xc6, 0x2b, 0x8a, 0x09, 0xc0, 0x09, 0xdb, 0xce, 0xd2, 0xdb, 0x56, 0xba, 51 | 0x66, 0xab, 0xce, 0x08, 0x21, 0x37, 0x34, 0xd3, 0x17, 0x33, 0xbb, 0x6f, 0xed, 0x15, 0x35, 0x62, 52 | 0x06, 0x25, 0xf0, 0x7c, 0x35, 0x08, 0xc5, 0x4d, 0x2a, 0x0e, 0xda, 0x59, 0xe8, 0x67, 0x66, 0x4c, 53 | 0x44, 0xca, 0xe8, 0xdf, 0x16, 0xce, 0xdb, 0x16, 0x68, 0xb3, 0xf5, 0x40, 0x91, 0x02, 0x06, 0x99, 54 | 0x76, 0x1f, 0x07, 0x72, 0x08, 0xd8, 0x0f, 0xa0, 0x26, 0x54, 0xf4, 0x89, 0xd6, 0x8d, 0x90, 0x9a, 55 | 0xf8, 0x19, 0x97, 0xf2, 0xa6, 0xd5, 0xbf, 0xf4, 0x73, 0x03, 0xdf, 0x6f, 0xb2, 0x0a, 0xb2, 0x43, 56 | 0x2a, 0x4e, 0x37, 0x7f, 0x6c, 0x8c, 0xe7, 0x63, 0xa1, 0xcc, 0x97, 0x00, 0xac, 0xb7, 0xf7, 0x68, 57 | 0x6b, 0xdb, 0xde, 0x7f, 0x9e, 0x5f, 0x1e, 0xdb, 0xd0, 0x0c, 0x00, 0x00, 58 | }; 59 | -------------------------------------------------------------------------------- /PowerMeter/lib/WebServer/pure-min-css-gz.h: -------------------------------------------------------------------------------- 1 | // file name = pure-min.css.gz, length = 3919 2 | #define PURE_MIN_CSS_GZ_LEN 3919 3 | const uint8_t PURE_MIN_CSS_GZ[] PROGMEM = { 4 | 0x1f, 0x8b, 0x08, 0x08, 0x62, 0xaf, 0xa7, 0x5a, 0x02, 0x03, 0x70, 0x75, 0x72, 0x65, 0x2d, 0x6d, 5 | 0x69, 0x6e, 0x2e, 0x63, 0x73, 0x73, 0x00, 0xed, 0x1b, 0x6b, 0x8f, 0xe3, 0xb6, 0xf1, 0xfb, 0xfd, 6 | 0x0a, 0xe5, 0x0e, 0x01, 0x6e, 0x0f, 0x92, 0x57, 0x96, 0x9f, 0x2b, 0x21, 0x87, 0x26, 0xcd, 0xa5, 7 | 0x4d, 0x91, 0x06, 0x05, 0xd2, 0x2f, 0xc5, 0x65, 0x0b, 0xd0, 0x12, 0x6d, 0xab, 0xab, 0x17, 0x24, 8 | 0x79, 0x1f, 0xe7, 0xfa, 0xbf, 0x77, 0xf8, 0x90, 0x34, 0x7c, 0xc8, 0xf6, 0xa6, 0x45, 0xd1, 0x0f, 9 | 0x8d, 0x73, 0x77, 0x12, 0x39, 0x1c, 0x0e, 0xe7, 0xc5, 0x99, 0x21, 0x75, 0xfb, 0xe1, 0xab, 0x37, 10 | 0x7f, 0x39, 0xd4, 0xd4, 0x79, 0x9c, 0x4e, 0xfc, 0x89, 0xff, 0xe6, 0xf7, 0x65, 0xf5, 0x52, 0xa7, 11 | 0xbb, 0x7d, 0xeb, 0x04, 0xfe, 0x74, 0xe6, 0xfc, 0x8d, 0xec, 0xcb, 0xf2, 0xab, 0x37, 0x3f, 0xa5, 12 | 0x31, 0x2d, 0x1a, 0x9a, 0x38, 0x87, 0x22, 0xa1, 0xb5, 0xd3, 0xee, 0xa9, 0xf3, 0xdd, 0x2f, 0xdf, 13 | 0x3b, 0xb2, 0x79, 0xf2, 0x66, 0xdf, 0xb6, 0x55, 0x13, 0xde, 0xde, 0xee, 0xd2, 0x76, 0x7f, 0xd8, 14 | 0x4c, 0xe2, 0x32, 0xbf, 0x7d, 0x61, 0x23, 0x6f, 0x2b, 0x40, 0x7d, 0xbb, 0xc9, 0xca, 0xcd, 0x6d, 15 | 0x4e, 0x9a, 0x96, 0xd6, 0xb7, 0x3f, 0xfd, 0xf8, 0xfb, 0x4f, 0x3f, 0xff, 0xf2, 0x69, 0x92, 0x27, 16 | 0x6f, 0x3e, 0xdc, 0xbe, 0xb9, 0x85, 0xd9, 0x8b, 0xb2, 0xce, 0x49, 0x96, 0x7e, 0xa1, 0x93, 0xb8, 17 | 0x69, 0x9c, 0xc7, 0xbf, 0xcf, 0x26, 0xbe, 0xf3, 0x4f, 0xe7, 0xcf, 0x3f, 0xfe, 0xb5, 0x43, 0x0f, 18 | 0x6f, 0x80, 0x77, 0x92, 0x96, 0xb7, 0x3d, 0x28, 0x22, 0xf3, 0x7d, 0x7c, 0xe3, 0xfc, 0x9c, 0xc6, 19 | 0x65, 0x46, 0x1a, 0xe7, 0x0f, 0x24, 0xcb, 0xc8, 0x6e, 0x0f, 0x14, 0x92, 0x22, 0x71, 0xfe, 0x54, 20 | 0x16, 0xa4, 0xdd, 0x93, 0xc2, 0xf9, 0x99, 0x92, 0x4c, 0xce, 0xe6, 0x68, 0xb3, 0xc1, 0x64, 0x93, 21 | 0x99, 0x6d, 0xba, 0x6e, 0x19, 0x05, 0xe5, 0xa8, 0x6f, 0xd5, 0x71, 0x1f, 0x6e, 0x27, 0x6c, 0x65, 22 | 0xde, 0xe6, 0xd0, 0xb6, 0x65, 0x11, 0x6e, 0xcb, 0xf8, 0xd0, 0xb8, 0x24, 0x24, 0x71, 0x9b, 0x3e, 23 | 0x52, 0x78, 0xd8, 0x97, 0x8f, 0xb4, 0x3e, 0x96, 0x87, 0x36, 0x4b, 0x0b, 0x1a, 0xfa, 0x27, 0x01, 24 | 0xdd, 0x92, 0x4d, 0x46, 0x5d, 0xfe, 0xf7, 0x71, 0x53, 0xd6, 0xc0, 0x49, 0x0f, 0x90, 0x67, 0xa4, 25 | 0x6a, 0x68, 0xd8, 0x3d, 0x44, 0xb2, 0xa3, 0xa9, 0x48, 0x9c, 0x16, 0x3b, 0x18, 0xbb, 0x6f, 0xf3, 26 | 0xec, 0xb8, 0x2d, 0x8b, 0xd6, 0xdb, 0x92, 0x3c, 0xcd, 0x5e, 0xc2, 0x86, 0x14, 0x8d, 0xd7, 0xd0, 27 | 0x3a, 0xdd, 0x46, 0x5e, 0xde, 0x78, 0x2d, 0x7d, 0x6e, 0xbd, 0x06, 0x28, 0xf3, 0x48, 0xf2, 0x8f, 28 | 0x43, 0xd3, 0x86, 0x53, 0xdf, 0xff, 0x3a, 0xf2, 0x9e, 0xe8, 0xe6, 0x21, 0x6d, 0xed, 0xbd, 0xa7, 29 | 0x4d, 0x99, 0xbc, 0x1c, 0x73, 0x52, 0xef, 0xd2, 0x02, 0x66, 0x20, 0x75, 0x9b, 0xc6, 0x40, 0x19, 30 | 0x69, 0xd2, 0x84, 0xba, 0x09, 0x6d, 0x49, 0x9a, 0x35, 0xee, 0x36, 0xdd, 0xc5, 0xa4, 0x6a, 0xd3, 31 | 0xb2, 0x60, 0x8f, 0x40, 0xbf, 0xbb, 0x2d, 0x4b, 0x10, 0xa1, 0xbb, 0xa7, 0x24, 0x61, 0xff, 0xec, 32 | 0xea, 0xf2, 0x50, 0xb9, 0x39, 0x49, 0x0b, 0x37, 0xa7, 0xc5, 0xc1, 0x2d, 0xc8, 0xa3, 0xdb, 0xd0, 33 | 0x98, 0x8f, 0x68, 0x0e, 0x39, 0xa0, 0x7f, 0x39, 0x26, 0x69, 0x53, 0x65, 0xe4, 0x25, 0x04, 0x15, 34 | 0x88, 0x1f, 0x4e, 0xe4, 0x90, 0xa4, 0xa5, 0x1b, 0x93, 0xe2, 0x91, 0x34, 0x6e, 0x55, 0x97, 0xbb, 35 | 0x9a, 0x36, 0x8d, 0xfb, 0x08, 0xb3, 0x96, 0x3d, 0x64, 0x5a, 0x30, 0x9e, 0x79, 0x7c, 0x40, 0x04, 36 | 0x5c, 0x04, 0xd2, 0x48, 0xe6, 0x01, 0xeb, 0x77, 0x45, 0xb8, 0x21, 0x0d, 0x65, 0xbd, 0x02, 0x51, 37 | 0x58, 0x94, 0xed, 0xfb, 0xcf, 0x31, 0x70, 0xa6, 0x2e, 0xb3, 0xe6, 0xfe, 0xa6, 0x47, 0x51, 0x94, 38 | 0x05, 0x8d, 0xf6, 0x94, 0xa9, 0x07, 0xac, 0xee, 0xf3, 0x3e, 0x4d, 0x12, 0x5a, 0xdc, 0xbb, 0x2d, 39 | 0xcd, 0xa1, 0xbb, 0xa5, 0x0a, 0xdc, 0x89, 0x1c, 0x37, 0x24, 0x7e, 0x60, 0x6b, 0x29, 0x12, 0x26, 40 | 0x8e, 0xb2, 0x0e, 0xdb, 0x1a, 0x38, 0x5c, 0x91, 0x9a, 0x16, 0xed, 0x89, 0x6c, 0x36, 0xf5, 0xe7, 41 | 0x36, 0x6d, 0x33, 0x7a, 0xdf, 0xc9, 0x6c, 0x53, 0x82, 0xd0, 0xf3, 0x70, 0x5a, 0x3d, 0x3b, 0x09, 42 | 0x3c, 0xd2, 0xe4, 0xb4, 0x71, 0xcb, 0xaa, 0x15, 0xec, 0x68, 0x80, 0x98, 0x62, 0x27, 0xe4, 0xf5, 43 | 0x24, 0x48, 0x58, 0xf9, 0xfe, 0x29, 0xd9, 0x16, 0xa2, 0xad, 0x69, 0x5f, 0x32, 0x1a, 0xa6, 0x2d, 44 | 0x2c, 0x28, 0x3e, 0xed, 0xa7, 0xb2, 0x11, 0xe4, 0x13, 0x06, 0x34, 0x8f, 0xa4, 0x48, 0x26, 0xcb, 45 | 0x15, 0xcd, 0x1d, 0xff, 0x04, 0xaf, 0x0f, 0x88, 0xbc, 0xf0, 0xdd, 0x76, 0xeb, 0x47, 0x82, 0xc6, 46 | 0x77, 0x3e, 0x60, 0x6d, 0x40, 0x25, 0x33, 0x84, 0x62, 0x0d, 0xa2, 0x6d, 0x0e, 0x1b, 0xe0, 0x7e, 47 | 0x85, 0x5a, 0x57, 0x8b, 0xaf, 0x23, 0xce, 0xd3, 0x8e, 0x25, 0x51, 0x55, 0x36, 0x29, 0x13, 0x53, 48 | 0x58, 0x53, 0x60, 0x08, 0xa8, 0xec, 0x28, 0xa3, 0x19, 0xa6, 0xb6, 0xac, 0x42, 0x6f, 0xb2, 0xa0, 49 | 0x39, 0xc3, 0x7d, 0x94, 0xab, 0xf7, 0x26, 0x01, 0x6b, 0x49, 0xf3, 0x9d, 0x64, 0x0b, 0x70, 0xba, 50 | 0x79, 0xdc, 0x71, 0x99, 0x84, 0x35, 0x28, 0xca, 0xcd, 0x91, 0xd9, 0xc0, 0x36, 0x2b, 0x9f, 0x42, 51 | 0x21, 0x80, 0x93, 0xd0, 0xa2, 0x4e, 0xed, 0xa6, 0xb0, 0xc2, 0xb9, 0x5f, 0x3d, 0x9f, 0xf6, 0x35, 52 | 0x60, 0x78, 0x66, 0xa4, 0x32, 0x7d, 0x67, 0xe2, 0x04, 0xbe, 0x03, 0x93, 0x9f, 0x07, 0x11, 0x56, 53 | 0xa0, 0x7c, 0x4c, 0x91, 0x41, 0x24, 0x64, 0x40, 0x4b, 0x0e, 0x6d, 0x79, 0x8a, 0x4b, 0x50, 0xd9, 54 | 0x87, 0x4d, 0xe2, 0x32, 0x98, 0x86, 0xe4, 0x95, 0x62, 0x2a, 0x79, 0x59, 0x94, 0xcc, 0x92, 0xa8, 55 | 0xdb, 0x3f, 0x45, 0x03, 0x63, 0x80, 0x84, 0x93, 0xb0, 0x60, 0x37, 0x2d, 0xaa, 0x43, 0x8b, 0xa4, 56 | 0x48, 0x33, 0x50, 0xe4, 0x61, 0x4e, 0xc1, 0xf3, 0xb4, 0x00, 0xd7, 0x92, 0xb6, 0x1c, 0x43, 0xff, 57 | 0xd2, 0x5b, 0x11, 0xf6, 0x08, 0x02, 0xdf, 0x11, 0x73, 0x5d, 0x38, 0x10, 0x39, 0xdf, 0xb0, 0x88, 58 | 0xc7, 0xb4, 0x49, 0xc1, 0x1f, 0x74, 0x74, 0x88, 0x89, 0x8f, 0xdc, 0x6a, 0xb9, 0x1a, 0x6e, 0x61, 59 | 0x98, 0x50, 0x54, 0x09, 0xc1, 0xdc, 0x81, 0xc3, 0xd1, 0x7f, 0x6e, 0x5f, 0x2a, 0xfa, 0x8d, 0x68, 60 | 0xbe, 0x77, 0x51, 0x13, 0x58, 0x15, 0x6d, 0x95, 0x16, 0x10, 0x5c, 0x9e, 0xb6, 0xf7, 0xc7, 0xce, 61 | 0x2b, 0x90, 0xaa, 0xa2, 0x04, 0xd0, 0xc7, 0x34, 0x14, 0xe3, 0xa3, 0xf8, 0x50, 0x37, 0xb0, 0xc4, 62 | 0xaa, 0x4c, 0x81, 0xff, 0xb5, 0x9c, 0xec, 0x33, 0x58, 0x0a, 0xf3, 0x56, 0xc9, 0x3d, 0x9e, 0xb6, 63 | 0x6f, 0x3c, 0xca, 0x41, 0x09, 0xdd, 0x92, 0x43, 0xd6, 0xca, 0x41, 0x61, 0xe8, 0xe5, 0xe5, 0x17, 64 | 0x8f, 0xfb, 0x44, 0x2f, 0x2d, 0x0a, 0x70, 0x15, 0x7c, 0x9c, 0xd9, 0xde, 0x6b, 0x4e, 0x54, 0x91, 65 | 0x24, 0x11, 0xde, 0x0e, 0x11, 0x1d, 0xef, 0x69, 0xfc, 0x00, 0x7a, 0xa0, 0xae, 0x8d, 0x80, 0xe5, 66 | 0xdf, 0x63, 0x8d, 0xe9, 0xad, 0xf2, 0xd9, 0x8e, 0xa6, 0x38, 0xe4, 0x1b, 0x5a, 0xdf, 0xc3, 0xf4, 67 | 0x72, 0xf1, 0x7c, 0x6e, 0x70, 0xb0, 0x69, 0xa1, 0x48, 0x6b, 0x04, 0x1a, 0x9c, 0xb8, 0x0a, 0x7d, 68 | 0x94, 0x02, 0xe5, 0xea, 0x87, 0x79, 0x0c, 0x1c, 0x8d, 0xf7, 0x56, 0x1e, 0x33, 0x71, 0x6e, 0x53, 69 | 0x9a, 0x25, 0x91, 0x5d, 0xd3, 0x55, 0xdd, 0x11, 0x2f, 0x4c, 0xf0, 0x82, 0xe1, 0xc2, 0xcf, 0xb1, 70 | 0x39, 0xee, 0x6f, 0x64, 0x27, 0xf3, 0xb7, 0x76, 0x1e, 0x58, 0x28, 0x1a, 0xd6, 0x22, 0x1a, 0xbc, 71 | 0x98, 0x11, 0x95, 0x59, 0x16, 0x3f, 0x36, 0x20, 0x81, 0x4d, 0xb0, 0x26, 0xcc, 0x63, 0xd8, 0x56, 72 | 0xc7, 0xb5, 0x93, 0x2f, 0x0f, 0xd4, 0xae, 0x93, 0x29, 0xf3, 0x8e, 0x4d, 0x99, 0xa5, 0x89, 0xd3, 73 | 0xa4, 0x19, 0x28, 0x7b, 0x6f, 0x27, 0x4e, 0x50, 0x0d, 0x82, 0x9a, 0xcc, 0xc0, 0x89, 0x38, 0x93, 74 | 0x65, 0xc0, 0xff, 0x59, 0x31, 0x8f, 0x92, 0xd1, 0x1d, 0x2d, 0x12, 0xb7, 0x85, 0xff, 0xf7, 0xc7, 75 | 0x41, 0xa0, 0xa2, 0x79, 0xf0, 0x35, 0x13, 0xe1, 0x54, 0xdc, 0xce, 0xbb, 0x2b, 0x4e, 0xfd, 0xab, 76 | 0x34, 0xaf, 0xca, 0xba, 0x25, 0xe0, 0xbf, 0x05, 0xbf, 0x98, 0x9b, 0xca, 0xc9, 0xb3, 0xf7, 0x94, 77 | 0x26, 0xed, 0x5e, 0x6c, 0x90, 0x48, 0x8a, 0x91, 0xba, 0x4b, 0x89, 0x21, 0xbb, 0x63, 0x46, 0xdb, 78 | 0x16, 0xed, 0xc4, 0xde, 0x64, 0x06, 0x0e, 0x23, 0xe2, 0xa6, 0x09, 0x5b, 0x03, 0x90, 0xc1, 0x5a, 79 | 0xc1, 0x65, 0xa4, 0x39, 0x38, 0x93, 0xa6, 0xa2, 0x34, 0x89, 0xb0, 0xef, 0xf9, 0xa1, 0xa6, 0xf4, 80 | 0x17, 0xb0, 0x60, 0xf7, 0xdb, 0x3a, 0xcd, 0x4b, 0xf7, 0xed, 0xf7, 0x75, 0x09, 0xdc, 0x60, 0x2d, 81 | 0x6f, 0xdd, 0x3f, 0x52, 0xe0, 0x09, 0x73, 0xb8, 0xac, 0x93, 0x64, 0x2e, 0xda, 0xd2, 0x3b, 0x5a, 82 | 0x3a, 0x46, 0x33, 0xbd, 0xd6, 0xdb, 0xb6, 0x19, 0x45, 0x8d, 0x10, 0x03, 0xb0, 0x06, 0x0c, 0xc8, 83 | 0x01, 0x30, 0xb4, 0xc7, 0x9d, 0x4d, 0x5d, 0x3e, 0x39, 0x4f, 0x35, 0xa9, 0xa2, 0x6e, 0x8c, 0xd6, 84 | 0x6c, 0x83, 0xec, 0xe4, 0xcd, 0xf6, 0x05, 0x4f, 0xea, 0x2c, 0xc7, 0x0f, 0x5b, 0x19, 0x04, 0x0e, 85 | 0x03, 0x2a, 0xee, 0xec, 0x80, 0x57, 0x0f, 0xa1, 0xe8, 0x18, 0x1b, 0x71, 0xfa, 0x5d, 0x4e, 0x93, 86 | 0x94, 0x38, 0xb0, 0x75, 0xf1, 0x30, 0xed, 0x3d, 0xc3, 0xb0, 0x07, 0x61, 0x70, 0xd8, 0x1a, 0xc2, 87 | 0x44, 0x2e, 0xc3, 0x1b, 0xd7, 0xd2, 0x21, 0x02, 0xac, 0x9b, 0x23, 0x8f, 0xa1, 0x9c, 0x4e, 0x50, 88 | 0xaa, 0xf8, 0x4e, 0x93, 0xb2, 0xa2, 0x35, 0xf1, 0xca, 0x22, 0x7b, 0x71, 0x42, 0xaf, 0xf4, 0x60, 89 | 0x67, 0x10, 0x01, 0x5a, 0x07, 0xff, 0x04, 0x4a, 0x84, 0xc4, 0x3a, 0x9f, 0x81, 0xda, 0x89, 0xbe, 90 | 0x83, 0x84, 0x39, 0x78, 0xd3, 0xe1, 0x49, 0x7d, 0x0e, 0xd0, 0x8b, 0xf2, 0x3c, 0x47, 0x2f, 0x33, 91 | 0xf4, 0x8c, 0xdb, 0x17, 0xe8, 0x79, 0x89, 0x9e, 0xd7, 0xc3, 0xb3, 0xaf, 0x60, 0x52, 0x27, 0x54, 92 | 0x67, 0x09, 0x94, 0xb7, 0x99, 0xf2, 0x36, 0x57, 0xde, 0x16, 0xca, 0xdb, 0x52, 0x79, 0x5b, 0x29, 93 | 0x6f, 0x6b, 0xe5, 0xed, 0x0e, 0xbf, 0x05, 0xea, 0xcb, 0x0c, 0x3d, 0x0f, 0xcb, 0x0a, 0x14, 0xf2, 94 | 0x03, 0x85, 0xe0, 0x40, 0xc5, 0xa0, 0x10, 0x1c, 0x28, 0x04, 0xcf, 0xd4, 0x17, 0xfc, 0xbc, 0x40, 95 | 0xcf, 0x03, 0xd7, 0x94, 0xd1, 0x73, 0x04, 0xb4, 0xc0, 0xfc, 0x53, 0xd8, 0xb0, 0x50, 0xa0, 0x96, 96 | 0xe8, 0x79, 0x40, 0xab, 0x70, 0x6a, 0x85, 0x51, 0xad, 0xd4, 0x9e, 0x61, 0x88, 0xc2, 0x40, 0xc6, 97 | 0x3f, 0xdd, 0x8d, 0x88, 0x38, 0x20, 0x52, 0x74, 0x50, 0xb6, 0x69, 0x41, 0x18, 0xc4, 0x5d, 0xba, 98 | 0xb7, 0x51, 0xbc, 0x95, 0x12, 0x29, 0x7f, 0x29, 0x59, 0x48, 0xda, 0xf9, 0x2e, 0xe7, 0x73, 0x0c, 99 | 0x49, 0x4b, 0xf3, 0xe1, 0x1b, 0x41, 0xc8, 0xfd, 0x48, 0xfe, 0x70, 0xc2, 0xea, 0x7b, 0x14, 0xce, 100 | 0x71, 0x3e, 0x99, 0x2e, 0x97, 0xab, 0xaf, 0x4f, 0x56, 0x95, 0x0f, 0x06, 0xb8, 0xf5, 0x64, 0x06, 101 | 0xff, 0x61, 0xb8, 0xb5, 0x22, 0x3f, 0x09, 0x36, 0x0d, 0x26, 0x0b, 0x0c, 0xb4, 0x54, 0x64, 0xd6, 102 | 0x01, 0x2d, 0x27, 0x4b, 0x6d, 0xd2, 0x85, 0xec, 0x0a, 0xfc, 0xa1, 0x75, 0x31, 0x8c, 0x08, 0xfc, 103 | 0xc9, 0x5a, 0x9b, 0x7e, 0xae, 0x88, 0xad, 0x83, 0x43, 0x93, 0xaf, 0x50, 0xf3, 0x9d, 0xb1, 0xca, 104 | 0x99, 0x22, 0x42, 0x09, 0x37, 0x9b, 0x69, 0xab, 0xc4, 0x4a, 0x77, 0x87, 0xe0, 0x56, 0x78, 0x95, 105 | 0x41, 0x4f, 0xfd, 0x1c, 0x51, 0xaf, 0x5a, 0x36, 0x53, 0xcc, 0x0e, 0x68, 0xaa, 0xaf, 0x1e, 0x8b, 106 | 0x63, 0x61, 0x2c, 0x34, 0x50, 0x5d, 0x80, 0x04, 0x5c, 0xe0, 0xa9, 0x90, 0x00, 0x16, 0x86, 0x40, 107 | 0xe7, 0xba, 0x56, 0x77, 0x90, 0x6b, 0x63, 0xb1, 0xdd, 0x3a, 0x96, 0x18, 0xb9, 0x66, 0x46, 0xeb, 108 | 0x0e, 0x46, 0x95, 0xf4, 0x52, 0x73, 0x17, 0x1d, 0x94, 0x21, 0x6a, 0x24, 0x96, 0x95, 0x21, 0xd5, 109 | 0xb5, 0xe6, 0x09, 0x3a, 0x40, 0x3c, 0x13, 0x92, 0xc3, 0x4a, 0x97, 0xeb, 0xbc, 0x5f, 0xc2, 0x1a, 110 | 0x2d, 0x21, 0xd0, 0x44, 0xb1, 0xec, 0x60, 0x74, 0x71, 0xab, 0x0e, 0x6c, 0xd5, 0xaf, 0x75, 0xad, 111 | 0xc8, 0x5b, 0xf5, 0xd2, 0x01, 0x12, 0xca, 0x9d, 0x2e, 0xda, 0x00, 0x49, 0xe6, 0xce, 0x10, 0xad, 112 | 0x75, 0xcf, 0x51, 0x5d, 0xe3, 0xa2, 0x5f, 0x10, 0x4f, 0xe4, 0x71, 0xe0, 0x78, 0x3c, 0xe3, 0x18, 113 | 0xa2, 0xa7, 0x7d, 0xda, 0x52, 0xee, 0x70, 0x58, 0xc0, 0xc6, 0xb7, 0x76, 0xcd, 0xdd, 0xe4, 0x10, 114 | 0x48, 0x65, 0x54, 0x78, 0x1c, 0xd1, 0x12, 0x53, 0x96, 0x0b, 0x68, 0xa9, 0x41, 0x1f, 0x11, 0x1c, 115 | 0xc0, 0x85, 0x78, 0x49, 0x4d, 0x76, 0x22, 0xdf, 0x56, 0x9a, 0x45, 0x1a, 0x23, 0x3b, 0x58, 0xd4, 116 | 0x6f, 0x69, 0x6d, 0xcc, 0x46, 0xbd, 0x41, 0x59, 0x9e, 0x25, 0x7f, 0xe8, 0xc3, 0xc3, 0x68, 0x88, 117 | 0x0b, 0xd1, 0x08, 0x8f, 0x27, 0x73, 0xbf, 0x21, 0x8e, 0xbb, 0x14, 0x42, 0x28, 0xe8, 0xc7, 0xa3, 118 | 0x09, 0x29, 0x16, 0xec, 0x7b, 0x71, 0x0a, 0x29, 0x93, 0x50, 0x16, 0x8c, 0xf6, 0xf1, 0x30, 0x8b, 119 | 0x83, 0x19, 0x75, 0x32, 0xc9, 0x9f, 0xcf, 0xe7, 0xf2, 0xb1, 0xde, 0x6d, 0xc8, 0x7b, 0xdf, 0x65, 120 | 0xbf, 0xc9, 0xfa, 0x26, 0x32, 0xa2, 0xec, 0x77, 0x77, 0x77, 0x77, 0x5d, 0x2b, 0xaa, 0x5f, 0x44, 121 | 0x46, 0x71, 0xe3, 0xdd, 0xa7, 0x25, 0xfb, 0x89, 0xf5, 0x0f, 0x81, 0xbd, 0x10, 0x80, 0xcc, 0x23, 122 | 0x58, 0x92, 0x75, 0x68, 0x42, 0x88, 0xd4, 0x55, 0x7e, 0xf2, 0xb2, 0x96, 0x6b, 0x29, 0x7d, 0x29, 123 | 0x4d, 0xa2, 0xf8, 0xb5, 0x4d, 0x33, 0xe0, 0x7a, 0x48, 0xb2, 0x6a, 0x4f, 0xde, 0x97, 0x8c, 0x3d, 124 | 0xed, 0xcb, 0x37, 0x77, 0xfe, 0x0d, 0xa6, 0x28, 0xcd, 0xc9, 0x8e, 0xf6, 0x51, 0x2d, 0x53, 0x5a, 125 | 0x52, 0x03, 0x57, 0x61, 0x76, 0x20, 0xfd, 0x3d, 0x5a, 0x86, 0x8b, 0x97, 0xef, 0x2f, 0x6e, 0x1c, 126 | 0x70, 0xac, 0x4a, 0xdb, 0xf4, 0xc6, 0x82, 0xf8, 0xdf, 0x44, 0xa8, 0xae, 0x5d, 0xd6, 0xf6, 0x94, 127 | 0x95, 0x8a, 0x36, 0x91, 0x88, 0xed, 0x49, 0x02, 0x71, 0xb3, 0xef, 0xb0, 0x1f, 0x13, 0x8b, 0x82, 128 | 0x0c, 0x26, 0x48, 0x0b, 0xc8, 0x8e, 0x5c, 0xd6, 0xbb, 0xd4, 0x7a, 0x03, 0xd9, 0x19, 0x0d, 0x35, 129 | 0x41, 0x59, 0xe0, 0xf9, 0xf5, 0x4e, 0xa5, 0xa1, 0xcb, 0xbc, 0x5d, 0x6b, 0x6b, 0x68, 0x21, 0x71, 130 | 0xe8, 0xb4, 0xe8, 0x6f, 0xdf, 0x67, 0xca, 0x15, 0x25, 0xf9, 0x52, 0xa9, 0x84, 0x7e, 0xe8, 0x2c, 131 | 0xe6, 0xad, 0x56, 0x51, 0xcf, 0x41, 0xd4, 0xf2, 0x39, 0x9c, 0xcc, 0x3b, 0x27, 0x02, 0xa9, 0x2c, 132 | 0x38, 0x17, 0xc8, 0x30, 0xa8, 0xcc, 0x85, 0x05, 0xdb, 0x38, 0x1a, 0xe9, 0x60, 0x3c, 0xfa, 0x08, 133 | 0x02, 0x6a, 0x4c, 0x07, 0xe0, 0x89, 0x54, 0x4f, 0x2d, 0xdf, 0x29, 0x00, 0x15, 0x64, 0x5a, 0xa4, 134 | 0x7e, 0x51, 0x57, 0x29, 0xfc, 0x09, 0x30, 0x8d, 0x58, 0x61, 0x89, 0x15, 0xd8, 0x2c, 0x0a, 0x82, 135 | 0x3c, 0x56, 0x6b, 0xba, 0xea, 0x2c, 0x73, 0xbb, 0xdd, 0x5a, 0x7c, 0x8d, 0xa3, 0x18, 0xfe, 0x7f, 136 | 0x3c, 0xf2, 0xeb, 0x72, 0x68, 0xcd, 0x4e, 0x87, 0x77, 0x9e, 0xd2, 0x22, 0xa7, 0x30, 0x9d, 0x4e, 137 | 0xc7, 0xfa, 0x54, 0x0d, 0xbc, 0xb4, 0x96, 0x70, 0x9b, 0xd6, 0x4d, 0xeb, 0xc5, 0xfb, 0x34, 0xeb, 138 | 0x92, 0x70, 0x0f, 0xa8, 0xf4, 0x32, 0xba, 0x6d, 0x91, 0xbb, 0x88, 0x94, 0x12, 0xa9, 0xde, 0x7b, 139 | 0x71, 0x12, 0x88, 0x59, 0x2d, 0x73, 0x70, 0xca, 0xc7, 0x27, 0x19, 0xeb, 0xae, 0x65, 0xd9, 0xad, 140 | 0x57, 0x92, 0xa1, 0xa2, 0x22, 0xaa, 0x1d, 0x15, 0x44, 0xc8, 0x4c, 0x18, 0xf7, 0xae, 0xbd, 0x9f, 141 | 0xe6, 0x24, 0xcd, 0xc6, 0x3a, 0x0f, 0xf5, 0x68, 0x57, 0x42, 0x5a, 0x3a, 0xd6, 0x97, 0x83, 0xdf, 142 | 0xdf, 0x8f, 0x75, 0xc2, 0x0e, 0x44, 0xcf, 0x21, 0xbd, 0xa6, 0xdf, 0x83, 0x4d, 0x9f, 0x8c, 0x92, 143 | 0xf6, 0x44, 0xe9, 0xc3, 0xe8, 0xec, 0x74, 0x74, 0x18, 0xd7, 0xf9, 0xb1, 0x4e, 0x59, 0x31, 0x1b, 144 | 0xe9, 0x95, 0x25, 0xa5, 0xd1, 0x39, 0x9f, 0x5b, 0xa5, 0x4f, 0x16, 0x5e, 0x51, 0x4b, 0x5f, 0x83, 145 | 0x55, 0xf6, 0xc8, 0xc9, 0x12, 0x36, 0x49, 0x6b, 0xbc, 0x63, 0xee, 0x8c, 0x71, 0x1c, 0x63, 0x5f, 146 | 0xc3, 0x7d, 0xad, 0x74, 0xd2, 0x33, 0xf8, 0xf3, 0x2e, 0x49, 0x12, 0xcd, 0xa0, 0xe6, 0xa0, 0x45, 147 | 0xf6, 0xe8, 0xc8, 0x5e, 0x73, 0x3b, 0x57, 0xb1, 0xfb, 0x2f, 0xd3, 0x7d, 0x3a, 0x27, 0xc1, 0x81, 148 | 0x96, 0x80, 0xd1, 0xb2, 0xe8, 0x23, 0x15, 0x3b, 0xe9, 0xca, 0x9e, 0x31, 0x6a, 0x3d, 0x67, 0xa1, 149 | 0x84, 0x0d, 0x9d, 0x05, 0x61, 0x96, 0x74, 0x16, 0x80, 0xdb, 0xd3, 0x59, 0x08, 0x61, 0x55, 0x67, 150 | 0x41, 0xb8, 0xed, 0x5c, 0x9c, 0xe6, 0x7a, 0x28, 0x69, 0x67, 0x67, 0x61, 0xb9, 0xb5, 0x9d, 0xa7, 151 | 0x8a, 0x5e, 0x40, 0x21, 0xe4, 0x76, 0x16, 0xa4, 0xab, 0x58, 0x9f, 0x83, 0xe9, 0x0a, 0xbb, 0xe7, 152 | 0x69, 0x01, 0x5b, 0x34, 0x21, 0x64, 0x38, 0x6e, 0xb4, 0x77, 0x76, 0x29, 0x7a, 0x86, 0xb3, 0x4e, 153 | 0x2d, 0x8a, 0x99, 0x06, 0x77, 0x3f, 0x7c, 0xfa, 0x76, 0x44, 0x2b, 0x21, 0x70, 0xb8, 0xc0, 0xee, 154 | 0xfe, 0x0c, 0xe0, 0x2c, 0x94, 0x38, 0x14, 0xd0, 0x28, 0x91, 0x53, 0x3b, 0x6c, 0xe7, 0x64, 0x46, 155 | 0x83, 0x69, 0x10, 0x8f, 0x1d, 0x72, 0xd7, 0xe8, 0xe1, 0x08, 0xbb, 0x93, 0x2a, 0x6e, 0xb8, 0xbe, 156 | 0xb5, 0x48, 0x6c, 0xb7, 0x1b, 0x74, 0x6c, 0x72, 0xc1, 0x76, 0x2e, 0x42, 0x0a, 0xfb, 0xb9, 0x08, 157 | 0xc6, 0x6c, 0xe8, 0x22, 0x10, 0xb7, 0xa3, 0x8b, 0x50, 0xc2, 0x96, 0x2e, 0x82, 0x71, 0x4b, 0xb9, 158 | 0x6a, 0xca, 0xd7, 0x41, 0x4a, 0xbb, 0xba, 0x08, 0xcf, 0x6d, 0xeb, 0x32, 0x95, 0xf4, 0x0a, 0x54, 159 | 0xc2, 0xc6, 0x2e, 0x82, 0x49, 0x3b, 0xbb, 0x08, 0x27, 0x6d, 0xed, 0x0a, 0xda, 0xc0, 0xde, 0xec, 160 | 0x50, 0xc2, 0xe6, 0xec, 0x7d, 0x9d, 0xdd, 0x99, 0xc7, 0x70, 0x4a, 0x6c, 0x6d, 0x84, 0xaf, 0x94, 161 | 0xd0, 0x04, 0x3a, 0xe4, 0x5b, 0x4c, 0x92, 0x20, 0x99, 0x99, 0x66, 0x09, 0x88, 0x13, 0x96, 0xf5, 162 | 0xda, 0xc8, 0xb1, 0xf6, 0xf5, 0xe4, 0xf4, 0xbd, 0x96, 0xd0, 0x99, 0x52, 0xda, 0x4d, 0xbc, 0x5a, 163 | 0xad, 0x34, 0x0f, 0x01, 0xdb, 0x9c, 0x69, 0x4d, 0xdc, 0x94, 0x61, 0xcb, 0x7b, 0x84, 0xdd, 0x37, 164 | 0x19, 0x73, 0x47, 0xb6, 0x7e, 0xd5, 0x2d, 0x75, 0x10, 0xf2, 0x00, 0xf7, 0xdd, 0xe6, 0x6e, 0x4e, 165 | 0xe6, 0x6b, 0x8d, 0x00, 0x7a, 0x37, 0x0b, 0x82, 0xe4, 0xb2, 0x8b, 0xea, 0x90, 0xbd, 0xc6, 0x61, 166 | 0x5d, 0x35, 0x06, 0xbb, 0x2f, 0x75, 0x40, 0xe7, 0xcc, 0xc6, 0x49, 0x95, 0x07, 0xc6, 0xf2, 0x34, 167 | 0x2b, 0xe0, 0xa7, 0xf2, 0x63, 0xb1, 0x84, 0x21, 0x97, 0x21, 0x89, 0xc1, 0x72, 0xce, 0x0f, 0x59, 168 | 0x9b, 0x56, 0xec, 0xc2, 0x03, 0x3e, 0xe9, 0x44, 0x70, 0x19, 0xd9, 0xd0, 0x4c, 0xf5, 0x8e, 0x0e, 169 | 0x8b, 0x28, 0x30, 0x4c, 0x7f, 0x1e, 0xd8, 0xe7, 0x2c, 0xea, 0x99, 0x9f, 0x2f, 0x8e, 0xfb, 0xf4, 170 | 0xc2, 0x8d, 0xc0, 0x2f, 0xce, 0xfa, 0x14, 0x87, 0x1b, 0xa1, 0xa3, 0xbb, 0x01, 0x13, 0x77, 0xcc, 171 | 0x62, 0x86, 0xee, 0x5a, 0x06, 0x6b, 0xec, 0x94, 0x6d, 0x36, 0x9b, 0x45, 0xe6, 0xa5, 0x0d, 0xc9, 172 | 0x12, 0xba, 0x60, 0x3f, 0x34, 0x2f, 0x3b, 0x9e, 0x8a, 0x1f, 0x68, 0x32, 0x7a, 0xea, 0x6a, 0x02, 173 | 0x8d, 0x27, 0x12, 0x36, 0x38, 0x23, 0xa1, 0xb0, 0x01, 0x69, 0x89, 0x85, 0x0d, 0x44, 0x4f, 0x30, 174 | 0x6c, 0x30, 0x46, 0xa2, 0x61, 0x03, 0xd2, 0x13, 0x8a, 0xb1, 0xc9, 0x5e, 0x03, 0x67, 0x26, 0x20, 175 | 0x36, 0x68, 0x3d, 0x11, 0xb1, 0x52, 0x47, 0x2f, 0xa2, 0x31, 0x12, 0x13, 0x1b, 0x10, 0x37, 0xe1, 176 | 0x0b, 0x30, 0x66, 0x12, 0x63, 0x83, 0x32, 0x93, 0x19, 0x3b, 0xdd, 0x6a, 0x52, 0xd3, 0xc3, 0x70, 177 | 0xbb, 0xb1, 0x75, 0x18, 0x59, 0x4f, 0xdf, 0xd3, 0x67, 0x3f, 0xaa, 0x31, 0x74, 0xb6, 0xc7, 0xcf, 178 | 0xcd, 0xb1, 0xed, 0x88, 0x6c, 0x05, 0x06, 0x8a, 0xa6, 0x3d, 0xcd, 0x2a, 0x4f, 0xa4, 0x19, 0xae, 179 | 0x05, 0x48, 0xdc, 0x7a, 0xb1, 0x74, 0x98, 0x04, 0x75, 0x3d, 0x1d, 0x41, 0xb8, 0x2f, 0xa7, 0x4d, 180 | 0x43, 0x76, 0x54, 0x4e, 0x74, 0xd5, 0x4d, 0x2e, 0x91, 0x4e, 0x9d, 0xce, 0x4c, 0x70, 0x34, 0x6b, 181 | 0x22, 0xe3, 0xeb, 0x94, 0x97, 0xc0, 0x64, 0x45, 0x56, 0x73, 0x08, 0x6a, 0x9a, 0x73, 0x6e, 0xa4, 182 | 0xf4, 0x6c, 0xa8, 0x04, 0xce, 0x8b, 0x08, 0xd1, 0xf5, 0x2b, 0xea, 0xdd, 0xd4, 0x70, 0x99, 0xcb, 183 | 0x67, 0x05, 0x5d, 0x56, 0x16, 0xbc, 0x48, 0x44, 0xd3, 0xdf, 0x8d, 0x92, 0x4e, 0x15, 0x86, 0x4e, 184 | 0x55, 0xb7, 0x2a, 0xaf, 0x33, 0x30, 0xb9, 0x79, 0xdc, 0x95, 0xd3, 0xc4, 0xd8, 0x54, 0x2c, 0x30, 185 | 0x47, 0xad, 0xb0, 0x0b, 0xd4, 0xe9, 0x25, 0x67, 0x73, 0x16, 0xc1, 0x12, 0xcd, 0x91, 0xf7, 0x8e, 186 | 0xd4, 0xb7, 0x45, 0xd4, 0x62, 0x88, 0xae, 0x56, 0x4a, 0xe7, 0x88, 0x46, 0x77, 0xe4, 0x30, 0xbc, 187 | 0x03, 0xe7, 0x7c, 0xc7, 0x9b, 0xa2, 0x8a, 0x4e, 0x57, 0xee, 0x32, 0xef, 0xaf, 0xf1, 0x3b, 0x6a, 188 | 0xd3, 0x0b, 0x24, 0x99, 0x9b, 0xb0, 0x8d, 0x30, 0xb9, 0xf3, 0x7e, 0x01, 0x0e, 0x26, 0xf4, 0x39, 189 | 0x9c, 0x5d, 0x40, 0x39, 0xd4, 0xc5, 0x2e, 0x23, 0x46, 0x35, 0x34, 0x46, 0xb0, 0xb9, 0x34, 0x48, 190 | 0xe0, 0x1d, 0xf6, 0xc7, 0xef, 0xb7, 0x35, 0x75, 0x67, 0x3c, 0x3b, 0x3f, 0x2a, 0x9f, 0xbd, 0x86, 191 | 0x14, 0x5c, 0x75, 0x1b, 0xa5, 0xea, 0x4a, 0x6a, 0x5e, 0x41, 0x81, 0x36, 0xab, 0x17, 0x98, 0x72, 192 | 0x06, 0x36, 0x48, 0x86, 0x5c, 0x9c, 0x5e, 0x56, 0x5c, 0x3b, 0xbf, 0x38, 0xd3, 0xfd, 0xa2, 0x62, 193 | 0x38, 0x53, 0xf3, 0x04, 0xcd, 0x80, 0xb1, 0x9d, 0x35, 0x1a, 0x40, 0xf8, 0x58, 0x73, 0x14, 0x68, 194 | 0xea, 0x05, 0xc6, 0x11, 0xad, 0x05, 0x68, 0xd6, 0x1f, 0x39, 0x9f, 0x01, 0x32, 0x8f, 0xb5, 0x11, 195 | 0xd0, 0x88, 0xb3, 0xbf, 0xc6, 0x3b, 0x4b, 0xeb, 0xe3, 0x05, 0x5b, 0x25, 0x82, 0x5a, 0x2e, 0x97, 196 | 0x23, 0x8e, 0x6e, 0x38, 0xc1, 0x9a, 0xac, 0x57, 0x9a, 0x8b, 0x95, 0x53, 0x6a, 0x26, 0x8e, 0x50, 197 | 0x1a, 0x83, 0xe5, 0x9d, 0x20, 0x7e, 0xe0, 0xd6, 0xc4, 0x35, 0xa5, 0x85, 0xb8, 0x1b, 0xd4, 0x5f, 198 | 0xda, 0x72, 0xc2, 0xf9, 0x1a, 0x5c, 0xc3, 0xcd, 0x11, 0x2d, 0x5b, 0x1e, 0x55, 0x28, 0x37, 0x1c, 199 | 0x3b, 0x0d, 0x58, 0x19, 0x3e, 0xf7, 0x6c, 0x58, 0xf7, 0xff, 0xba, 0xf0, 0xff, 0x76, 0x5d, 0x18, 200 | 0xa7, 0x1c, 0x4a, 0xa4, 0x6f, 0xbd, 0xba, 0x87, 0x3c, 0x92, 0x29, 0x6e, 0xd4, 0x6b, 0x97, 0xb7, 201 | 0x01, 0xa0, 0x08, 0xdc, 0xe8, 0x45, 0x12, 0x37, 0xfa, 0xb0, 0xc8, 0x8d, 0x4e, 0x45, 0xe6, 0x46, 202 | 0x2f, 0x16, 0xaa, 0x15, 0xef, 0x55, 0x00, 0xaa, 0xd8, 0x0d, 0x30, 0x2c, 0x77, 0x93, 0x02, 0x3a, 203 | 0x3e, 0x50, 0x91, 0xbc, 0xd1, 0xab, 0x8a, 0xde, 0xe8, 0x56, 0x65, 0x6f, 0x99, 0xf7, 0xb9, 0x37, 204 | 0xe4, 0x4e, 0xd6, 0xfe, 0xeb, 0x22, 0x38, 0x8b, 0xa2, 0xa0, 0xa0, 0x8e, 0x79, 0xb9, 0x68, 0x2c, 205 | 0xbb, 0xfc, 0x0d, 0x51, 0xda, 0x6b, 0x5d, 0xf1, 0x15, 0xde, 0x59, 0x50, 0x85, 0xdc, 0x24, 0x4f, 206 | 0x96, 0x95, 0xf2, 0x3d, 0x64, 0xd0, 0x6b, 0xf0, 0x9c, 0xa7, 0xe1, 0x46, 0xb0, 0xb7, 0x4d, 0x9f, 207 | 0x21, 0xd6, 0xeb, 0xa3, 0x23, 0xfe, 0x1a, 0x71, 0x9f, 0xee, 0xf3, 0x08, 0xc9, 0x8f, 0xf4, 0xa8, 208 | 0x86, 0x0f, 0x4b, 0x5b, 0x9a, 0xa3, 0x8b, 0xc5, 0x5e, 0x96, 0x36, 0xed, 0xd1, 0x88, 0xb1, 0x4e, 209 | 0x3a, 0x04, 0xfb, 0x4b, 0x7e, 0xcf, 0xc0, 0xcf, 0x6f, 0x8d, 0x4c, 0xdf, 0xd7, 0x27, 0x41, 0x17, 210 | 0x36, 0x7a, 0x60, 0x59, 0x63, 0x40, 0xac, 0xe7, 0xe0, 0xec, 0x23, 0x13, 0x80, 0x54, 0xc9, 0x2a, 211 | 0x1e, 0x34, 0x06, 0x59, 0x2f, 0x31, 0x98, 0x57, 0x5d, 0x14, 0xc4, 0x65, 0x9d, 0x7e, 0x01, 0xbe, 212 | 0x92, 0x0c, 0x45, 0x01, 0x57, 0x8f, 0x71, 0x74, 0x26, 0xd8, 0xb6, 0xd3, 0xcb, 0x43, 0x2d, 0xab, 213 | 0xb3, 0x03, 0xea, 0xa2, 0xb1, 0x43, 0x35, 0xb4, 0x22, 0xc0, 0x81, 0xb2, 0x3e, 0x7b, 0x05, 0xe8, 214 | 0x5c, 0x0a, 0xd6, 0x4f, 0xa6, 0x4f, 0x7e, 0xb4, 0x39, 0x58, 0xde, 0xcb, 0x63, 0xb7, 0x5a, 0x3b, 215 | 0x8f, 0x1f, 0x42, 0x73, 0xb2, 0x69, 0xca, 0xec, 0xd0, 0x52, 0xa1, 0x7f, 0x9c, 0xc9, 0x42, 0x05, 216 | 0x4d, 0x35, 0xb1, 0x6b, 0xa5, 0x7d, 0xa9, 0xfd, 0xac, 0x48, 0xad, 0xf9, 0xd9, 0xb8, 0x10, 0xa6, 217 | 0xbc, 0x5f, 0x83, 0xf1, 0x88, 0x5b, 0x11, 0x1f, 0x2d, 0x38, 0x30, 0x63, 0x79, 0xe1, 0x54, 0x5c, 218 | 0x70, 0x11, 0xd7, 0x21, 0x3e, 0x9e, 0x5b, 0xaa, 0x8c, 0x9a, 0xf4, 0xb5, 0x2a, 0xe4, 0x93, 0xa6, 219 | 0x1f, 0xf7, 0x51, 0xd3, 0xe2, 0x90, 0x6c, 0xdb, 0xe1, 0xfa, 0x92, 0x8c, 0xba, 0x16, 0x3c, 0xea, 220 | 0x12, 0xb7, 0xa5, 0xdf, 0xfe, 0x1a, 0x2c, 0xbe, 0x5b, 0xbf, 0x45, 0x0e, 0x80, 0x7f, 0xea, 0x73, 221 | 0x85, 0x62, 0x5d, 0x9e, 0x55, 0x99, 0xe3, 0xd3, 0x5b, 0x8c, 0x13, 0x22, 0x2f, 0xf6, 0x19, 0x1a, 222 | 0xfb, 0x3e, 0xad, 0xfb, 0x40, 0xc5, 0x7b, 0x09, 0x45, 0x6b, 0xd4, 0xb7, 0x3c, 0x77, 0x1f, 0xf4, 223 | 0x58, 0x47, 0x8e, 0x5a, 0xc8, 0x19, 0xd3, 0x78, 0x1d, 0xa2, 0xcb, 0xa6, 0x66, 0x5f, 0x92, 0xe5, 224 | 0x0a, 0x1c, 0x5a, 0xa5, 0x58, 0x13, 0x5e, 0x25, 0x57, 0x2b, 0x76, 0x4f, 0xad, 0x6f, 0x43, 0xce, 225 | 0xae, 0xff, 0x3c, 0xa4, 0xef, 0xe3, 0x13, 0x31, 0x7d, 0x6e, 0xcb, 0x43, 0xbc, 0x57, 0xf3, 0x6b, 226 | 0xff, 0x15, 0x74, 0xa2, 0xaf, 0x2f, 0x78, 0xdb, 0x86, 0xd4, 0xb6, 0x2b, 0x2f, 0x57, 0xd8, 0x88, 227 | 0xd5, 0x45, 0xb8, 0x56, 0xbf, 0x61, 0xd6, 0x86, 0x59, 0xc5, 0xb8, 0xf3, 0xcd, 0x43, 0x02, 0x26, 228 | 0x4a, 0xae, 0xa7, 0x57, 0x78, 0x24, 0xe9, 0x64, 0xab, 0xfe, 0x73, 0xae, 0x29, 0xdf, 0x8e, 0xfb, 229 | 0x14, 0x7f, 0x32, 0xdc, 0x97, 0xfb, 0xad, 0x4b, 0xb2, 0x56, 0x8a, 0x51, 0xd1, 0x1a, 0xbb, 0x5c, 230 | 0xfd, 0xd3, 0xaa, 0x43, 0x55, 0xd1, 0x3a, 0x26, 0x4d, 0x7f, 0x40, 0xb1, 0x58, 0x2e, 0x92, 0xe5, 231 | 0xfc, 0xa4, 0xef, 0x3b, 0xc3, 0xf1, 0x85, 0xd5, 0x07, 0x9e, 0xab, 0xad, 0x73, 0x48, 0xed, 0xea, 232 | 0xd6, 0xd9, 0x3d, 0x6e, 0xa4, 0x34, 0xa3, 0xe0, 0x39, 0xf6, 0x77, 0xab, 0x16, 0xb6, 0x6e, 0x47, 233 | 0xb7, 0x7d, 0x71, 0x29, 0xef, 0xec, 0xa7, 0x8e, 0x67, 0x7d, 0x26, 0x43, 0xa2, 0x13, 0xaa, 0xd4, 234 | 0x53, 0x2e, 0x4e, 0xc5, 0x4e, 0x81, 0x14, 0x97, 0x21, 0xaf, 0x5a, 0x39, 0x67, 0x66, 0x19, 0x83, 235 | 0xe1, 0x9f, 0xcd, 0xb1, 0x6b, 0x5a, 0xe8, 0x63, 0x48, 0xf4, 0x8d, 0xed, 0x91, 0xe6, 0x55, 0xfb, 236 | 0xe2, 0xc5, 0x34, 0xcb, 0x9a, 0xb0, 0xd9, 0x97, 0x4f, 0xb6, 0x13, 0x91, 0x0d, 0xfb, 0xe1, 0x51, 237 | 0x8e, 0xfc, 0xdc, 0x15, 0x21, 0x95, 0x9f, 0xfa, 0xf1, 0xaf, 0x35, 0x9d, 0xf5, 0xe2, 0xeb, 0xdb, 238 | 0xa9, 0x43, 0xf4, 0x0f, 0x76, 0xfa, 0xca, 0x15, 0x3f, 0x8c, 0x30, 0xae, 0xcf, 0x2a, 0x33, 0xb4, 239 | 0x9d, 0xfc, 0xe5, 0xeb, 0xbe, 0x2b, 0xcc, 0x89, 0x4d, 0x52, 0xa7, 0xae, 0x2b, 0x88, 0x08, 0x8d, 240 | 0xee, 0xef, 0x16, 0xa2, 0x6d, 0x41, 0xff, 0x0a, 0x31, 0xd2, 0x3f, 0x2c, 0x1c, 0xab, 0xf3, 0x75, 241 | 0xf4, 0x58, 0x0a, 0x58, 0x1d, 0x6d, 0xb6, 0x3b, 0x5f, 0xfc, 0x46, 0x97, 0x24, 0x47, 0xc5, 0xc4, 242 | 0x94, 0xd9, 0x26, 0x74, 0x9f, 0xfd, 0xd0, 0x47, 0xab, 0x46, 0x20, 0xae, 0x7f, 0x7a, 0xca, 0x63, 243 | 0x76, 0x8d, 0xca, 0x6b, 0x14, 0x97, 0xc3, 0x7a, 0x65, 0x92, 0x68, 0x5c, 0x06, 0xaf, 0x5d, 0xa7, 244 | 0x15, 0xab, 0x2c, 0xd7, 0x21, 0x24, 0x5c, 0x62, 0x3d, 0xef, 0x83, 0xc2, 0x9b, 0xde, 0x58, 0x31, 245 | 0xbf, 0xdb, 0x06, 0xec, 0xa7, 0x60, 0x15, 0xab, 0x67, 0x38, 0x92, 0xe3, 0xe8, 0x81, 0x92, 0xa9, 246 | 0x51, 0x68, 0x1c, 0xfb, 0xe2, 0xfa, 0x23, 0x50, 0x30, 0x94, 0xbd, 0x3e, 0xea, 0xb8, 0x6c, 0x7c, 247 | 0xc5, 0xee, 0x50, 0x5b, 0x16, 0xee, 0xe9, 0xf5, 0x68, 0xd0, 0x94, 0xa9, 0x7e, 0x51, 0xee, 0x02, 248 | 0xa9, 0x18, 0xdd, 0x6b, 0x88, 0xfd, 0x17, 0x54, 0xa5, 0x44, 0xa9, 0x41, 0x40, 0x00, 0x00, 249 | }; 250 | -------------------------------------------------------------------------------- /PowerMeter/lib/WebServer/trianglify.min.js: -------------------------------------------------------------------------------- 1 | (function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;f="undefined"==typeof window?"undefined"==typeof global?"undefined"==typeof self?this:self:global:window,f.Trianglify=e()}})(function(){var e,f=Math.max,d=Math.sqrt,a=Math.abs,n=Math.floor;return function d(c,e,n){function f(i,o){if(!e[i]){if(!c[i]){var r="function"==typeof require&&require;if(!o&&r)return r(i,!0);if(t)return t(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var s=e[i]={exports:{}};c[i][0].call(s.exports,function(d){var e=c[i][1][d];return f(e?e:d)},s,s.exports,d,c,e,n)}return e[i].exports}for(var t="function"==typeof require&&require,a=0;ae.cell_size)throw new Error("Cell size must be greater than 2.");var h;if(e.color_function)h=function(f,d){return o(e.color_function(f,d))};else{var u=o.scale(e.x_colors).mode(e.color_space),g=o.scale(e.y_colors).mode(e.color_space);h=function(f,d){return o.interpolate(u(f),g(d),0.5,e.color_space)}}for(var p=e.width,m=e.height,y=n((p+4*e.cell_size)/e.cell_size),x=n((m+4*e.cell_size)/e.cell_size),_=(y*e.cell_size-p)/2,k=(x*e.cell_size-m)/2,w=e.cell_size*e.variance/2,j=function(e){return f(e,[-_,p+_],[0,1])},v=function(e){return f(e,[-k,m+k],[0,1])},z=e.points||r(p,m,_,k,e.cell_size,w,b),B=new a(z).triangles,S=[],G=function(e){return z[e]},P=0;P"):document;f.exports=function(f,n){function t(t){var o;if("object"==typeof d&&"object"==typeof d.versions&&"undefined"!=typeof d.versions.node)try{e("canvas")}catch(f){throw Error("The optional node-canvas dependency is needed for Trianglify to render using canvas in node.")}return t||(t=a.createElement("canvas")),t.setAttribute("width",n.width),t.setAttribute("height",n.height),o=t.getContext("2d"),o.canvas.width=n.width,o.canvas.height=n.height,f.forEach(function(e){o.fillStyle=o.strokeStyle=e[0],o.lineWidth=n.stroke_width,o.beginPath(),o.moveTo.apply(o,e[1][0]),o.lineTo.apply(o,e[1][1]),o.lineTo.apply(o,e[1][2]),o.fill(),o.stroke()}),t}return{polys:f,opts:n,svg:function(e){var d=a.createElementNS("http://www.w3.org/2000/svg","svg");return d.setAttribute("width",n.width),d.setAttribute("height",n.height),e&&e.includeNamespace&&d.setAttribute("xmlns","http://www.w3.org/2000/svg"),f.forEach(function(e){var f=a.createElementNS("http://www.w3.org/2000/svg","path");f.setAttribute("d","M"+e[1].join("L")+"Z"),f.setAttribute("fill",e[0]),f.setAttribute("stroke",e[0]),f.setAttribute("stroke-width",n.stroke_width),d.appendChild(f)}),d},canvas:t,png:function(){return t().toDataURL("image/png")}}}}).call(this,e("_process"))},{_process:"/home/fmauneko/code/trianglify/node_modules/process/browser.js",canvas:"/home/fmauneko/code/trianglify/node_modules/browser-resolve/empty.js",jsdom:"/home/fmauneko/code/trianglify/node_modules/browser-resolve/empty.js"}],"/home/fmauneko/code/trianglify/lib/points.js":[function(e,f){f.exports=function(e,f,d,a,t,o,r){for(var l=0.5*t,c=2*o,s=-o,b=[],h=-d;ha?(d+0.05)/(a+0.05):(a+0.05)/(d+0.05)},y.luminance=function(e){return y(e).luminance()},y._Color=t,t=function(){function e(){var e,f,d,a,n,t,o,i,r,l,c,s,b,h,u,g;for(n=this,d=[],(l=0,c=arguments.length);lf?i(e,l):i(l,n)},this._rgb=(n>f?i(new e("black"),this):i(this,new e("white"))).rgba(),this)},e.prototype.name=function(){var e,f;for(f in e=this.hex(),y.colors)if(e===y.colors[f])return f;return e},e.prototype.alpha=function(e){return arguments.length?(this._rgb[3]=e,this):this._rgb[3]},e.prototype.css=function(e){var f,d,a,n;return(null==e&&(e="rgb"),d=this,a=d._rgb,3===e.length&&1>a[3]&&(e+="a"),"rgb"===e)?e+"("+a.slice(0,3).map(Math.round).join(",")+")":"rgba"===e?e+"("+a.slice(0,3).map(Math.round).join(",")+","+a[3]+")":"hsl"===e||"hsla"===e?(f=d.hsl(),n=function(e){return $(100*e)/100},f[0]=n(f[0]),f[1]=n(100*f[1])+"%",f[2]=n(100*f[2])+"%",4===e.length&&(f[3]=a[3]),e+"("+f.join(",")+")"):void 0},e.prototype.interpolate=function(d,f,a){var n,t,o,i,r,l,c,s,b,h,u,g,p,m;if(s=this,null==a&&(a="rgb"),"string"===D(f)&&(f=new e(f)),"hsl"===a||"hsv"===a||"lch"===a||"hsi"===a)"hsl"===a?(p=s.hsl(),m=f.hsl()):"hsv"===a?(p=s.hsv(),m=f.hsv()):"hsi"===a?(p=s.hsi(),m=f.hsi()):"lch"===a&&(p=s.lch(),m=f.lch()),"h"===a.substr(0,1)?(o=p[0],u=p[1],l=p[2],i=m[0],g=m[1],c=m[2]):(l=p[0],u=p[1],o=p[2],c=m[0],g=m[1],i=m[2]),isNaN(o)||isNaN(i)?isNaN(o)?isNaN(i)?t=H:(t=i,(1===l||0===l)&&"hsv"!==a&&(h=g)):(t=o,(1===c||0===c)&&"hsv"!==a&&(h=u)):(n=i>o&&180f?(0>e[f]&&(e[f]=0),255e[f]&&(e[f]=0),1=t;d=++t)n[d]=+n[d];n[3]=1}else if(a=e.match(/rgba\(\s*(\-?\d+),\s*(\-?\d+)\s*,\s*(\-?\d+)\s*,\s*([01]|[01]?\.\d+)\)/))for(n=a.slice(1,5),d=o=0;3>=o;d=++o)n[d]=+n[d];else if(a=e.match(/rgb\(\s*(\-?\d+(?:\.\d+)?)%,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*\)/)){for(n=a.slice(1,4),d=i=0;2>=i;d=++i)n[d]=$(2.55*n[d]);n[3]=1}else if(a=e.match(/rgba\(\s*(\-?\d+(?:\.\d+)?)%,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)/)){for(n=a.slice(1,5),d=r=0;2>=r;d=++r)n[d]=$(2.55*n[d]);n[3]=+n[3]}else(a=e.match(/hsl\(\s*(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*\)/))?(f=a.slice(1,4),f[1]*=0.01,f[2]*=0.01,n=j(f),n[3]=1):(a=e.match(/hsla\(\s*(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)/))&&(f=a.slice(1,4),f[1]*=0.01,f[2]*=0.01,n=j(f),n[3]=+a[4]);return n},k=function(e){var f,d,a,n,t,o;if(e.match(/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/))return(4===e.length||7===e.length)&&(e=e.substr(1)),3===e.length&&(e=e.split(""),e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]),o=parseInt(e,16),n=o>>16,a=255&o>>8,d=255&o,[n,a,d,1];if(e.match(/^#?([A-Fa-f0-9]{8})$/))return 9===e.length&&(e=e.substr(1)),o=parseInt(e,16),n=255&o>>24,a=255&o>>16,d=255&o>>8,f=255&o,[n,a,d,f];if(t=_(e))return t;throw"unknown color: "+e},w=function(e,f,d){var a,n,t,o;return o=U(arguments),e=o[0],f=o[1],d=o[2],e/=360,e<1/3?(a=(1-f)/3,t=(1+f*x(c*e)/x(l-c*e))/3,n=1-(a+t)):e<2/3?(e-=1/3,t=(1-f)/3,n=(1+f*x(c*e)/x(l-c*e))/3,a=1-(t+n)):(e-=2/3,n=(1-f)/3,a=(1+f*x(c*e)/x(l-c*e))/3,t=1-(n+a)),t=R(3*(d*t)),n=R(3*(d*n)),a=R(3*(d*a)),[255*t,255*n,255*a]},j=function(){var e,f,d,a,n,t,o,i,r,l,c,s,b,h;if(b=U(arguments),a=b[0],i=b[1],t=b[2],0===i)o=d=e=255*t;else{for(c=[0,0,0],f=[0,0,0],l=0.5>t?t*(1+i):t+i-t*i,r=2*t-l,a/=360,c[0]=a+1/3,c[1]=a,c[2]=a-1/3,n=s=0;2>=s;n=++s)0>c[n]&&(c[n]+=1),16*c[n]?r+6*(l-r)*c[n]:1>2*c[n]?l:2>3*c[n]?r+6*((l-r)*(2/3-c[n])):r;h=[$(255*f[0]),$(255*f[1]),$(255*f[2])],o=h[0],d=h[1],e=h[2]}return[o,d,e]},v=function(){var e,d,f,a,o,i,l,c,r,s,t,b,h,u,g,p,m,y;return b=U(arguments),a=b[0],r=b[1],t=b[2],t*=255,0===r?c=f=e=t:(360===a&&(a=0),360a&&(a+=360),a/=60,o=n(a),d=a-o,i=t*(1-r),l=t*(1-r*d),s=t*(1-r*(1-d)),0===o?(h=[t,s,i],c=h[0],f=h[1],e=h[2]):1===o?(u=[l,t,i],c=u[0],f=u[1],e=u[2]):2===o?(g=[i,t,s],c=g[0],f=g[1],e=g[2]):3===o?(p=[i,l,t],c=p[0],f=p[1],e=p[2]):4===o?(m=[s,i,t],c=m[0],f=m[1],e=m[2]):5===o?(y=[t,i,l],c=y[0],f=y[1],e=y[2]):void 0),(c=$(c),f=$(f),e=$(e),[c,f,e])},r=18,s=0.95047,h=1,u=1.08883,z=function(){var e,f,a,n,t,o;return o=U(arguments),t=o[0],e=o[1],f=o[2],a=d(e*e+f*f),n=180*(Math.atan2(f,e)/Q),[t,a,n]},B=function(e,f,d){var a,n,t,o,i,r,l;return void 0!==e&&3===e.length&&(r=e,e=r[0],f=r[1],d=r[2]),void 0!==e&&3===e.length&&(l=e,e=l[0],f=l[1],d=l[2]),o=(e+16)/116,t=o+f/500,i=o-d/200,t=S(t)*s,o=S(o)*h,i=S(i)*u,n=M(3.2404542*t-1.5371385*o-0.4985314*i),a=M(-0.969266*t+1.8760108*o+0.041556*i),d=M(0.0556434*t-0.2040259*o+1.0572252*i),[R(n,0,255),R(a,0,255),R(d,0,255),1]},S=function(e){return 0.206893034=e?12.92*e:1.055*Z(e,1/2.4)-0.055))},G=function(){var e,f,d,a;return a=U(arguments),d=a[0],e=a[1],f=a[2],f=f*Q/180,[d,I(f)*e,Math.sin(f)*e]},P=function(e,f,d){var n,t,a,o,i,r,l;return r=G(e,f,d),n=r[0],t=r[1],a=r[2],l=B(n,t,a),i=l[0],o=l[1],a=l[2],[R(i,0,255),R(o,0,255),R(a,0,255)]},A=function(e,f,d){var a;return a=U(arguments),e=a[0],f=a[1],d=a[2],e=q(e),f=q(f),d=q(d),0.2126*e+0.7152*f+0.0722*d},q=function(e){return e/=255,0.03928>=e?e/12.92:Z((e+0.055)/1.055,2.4)},C=function(){var e,f,d,a,n,t;return t=U(arguments),d=t[0],f=t[1],e=t[2],n=d<<16|f<<8|e,a="000000"+n.toString(16),"#"+a.substr(a.length-6)},L=function(){var e,f,a,n,t,o,i,r,l;return l=U(arguments),i=l[0],a=l[1],f=l[2],e=2*Q,i/=255,a/=255,f/=255,o=F(i,a,f),t=(i+a+f)/3,r=1-o/t,0===r?n=0:(n=(i-a+(i-f))/2,n/=d((i-a)*(i-a)+(i-f)*(a-f)),n=Math.acos(n),f>a&&(n=e-n),n/=e),[360*n,r,t]},O=function(e,d,a){var n,t,o,i,r,l;return void 0!==e&&3<=e.length&&(l=e,e=l[0],d=l[1],a=l[2]),e/=255,d/=255,a/=255,i=F(e,d,a),o=f(e,d,a),t=(o+i)/2,o===i?(r=0,n=H):r=0.5>t?(o-i)/(o+i):(o-i)/(2-o-i),e===o?n=(d-a)/(o-i):d===o?n=2+(a-e)/(o-i):a===o&&(n=4+(e-d)/(o-i)),n*=60,0>n&&(n+=360),[n,r,t]},T=function(){var e,d,a,n,t,o,i,r,l,c;return c=U(arguments),i=c[0],a=c[1],e=c[2],o=F(i,a,e),t=f(i,a,e),d=t-o,l=t/255,0===t?(n=H,r=0):(r=d/t,i===t&&(n=(a-e)/d),a===t&&(n=2+(e-i)/d),e===t&&(n=4+(i-a)/d),n*=60,0>n&&(n+=360)),[n,r,l]},E=function(){var e,f,d,a,n,t,o;return o=U(arguments),d=o[0],f=o[1],e=o[2],d=N(d),f=N(f),e=N(e),a=K((0.4124564*d+0.3575761*f+0.1804375*e)/s),n=K((0.2126729*d+0.7151522*f+0.072175*e)/h),t=K((0.0193339*d+0.119192*f+0.9503041*e)/u),[116*n-16,500*(a-n),200*(n-t)]},N=function(e){return 0.04045>=(e/=255)?e/12.92:Z((e+0.055)/1.055,2.4)},K=function(e){return 0.008856=i;d=0<=i?++n:--n)a=e[d],"string"===D(a)&&(e[d]=y(a));if(null!=f)v=f;else for(v=[],d=t=0,r=e.length-1;0<=r?t<=r:t>=r;d=0<=r?++t:--t)v.push(d/(e.length-1))}return l(),u=e},s=function(e){return null==e&&(e=[]),m=e,_=e[0],x=e[e.length-1],l(),j=2===e.length?0:e.length-1},r=function(e){var f,d;if(null!=m){for(d=m.length-1,f=0;f=m[f];)f++;return f-1}return 0},b=function(e){return e},t=function(e){var f,d,a,t,n;return n=e,2=p;c=0<=p?++t:--t){if(s=v[c],g<=s){o=u[c];break}if(g>=s&&c===v.length-1){o=u[c];break}if(g>s&&gd,n=i(e,!0).lab()[0],o=f+(d-f)*e,t=n-o,c=0,s=1,r=20;0.01t?(c=e,e+=0.5*(s-e)):(s=e,e+=0.5*(c-e)),n=i(e,!0).lab()[0],t=n-o})();return e}:function(e){return e},o):g},o.colors=function(f){var d,a,n,t,i,r;if(null==f&&(f="hex"),e=[],a=[],2r;d=1<=r?++n:--n)a.push(0.5*(m[d-1]+m[d]));else a=m;for(t=0,i=a.length;tt.max&&(t.max=e),t.count+=1)},i=function(e,n){if(d(e,n))return null!=f&&"function"===D(f)?a(f(e)):null!=f&&"string"===D(f)||"number"===D(f)?a(e[f]):a(e)},"array"===D(e))for(r=0,l=e.length;r=K;b=1<=K?++q:--q)u.push(x+b/d*(g-x));u.push(g)}else if("l"===f.substr(0,1)){if(0>=x)throw"Logarithmic scales are only possible for values > 0";for(_=be*se(x),m=be*se(g),u.push(x),(b=C=1,$=d-1);1<=$?C<=$:C>=$;b=1<=$?++C:--C)u.push(Z(10,_+b/d*(m-_)));u.push(g)}else if("q"===f.substr(0,1)){for(u.push(x),b=L=1,J=d-1;1<=J?L<=J:L>=J;b=1<=J?++L:--L)z=A.length*b/d,p=n(z),p===z?u.push(A[p]):(B=z-p,u.push(A[p]*B+A[p+1]*(1-B)));u.push(g)}else if("k"===f.substr(0,1)){for(w=A.length,t=Array(w),c=Array(d),S=!0,j=0,r=null,r=[],r.push(x),(b=O=1,ee=d-1);1<=ee?O<=ee:O>=ee;b=1<=ee?++O:--O)r.push(x+b/d*(g-x));for(r.push(g);S;){for(i=T=0,fe=d-1;0<=fe?T<=fe:T>=fe;i=0<=fe?++T:--T)c[i]=0;for(b=E=0,de=w-1;0<=de?E<=de:E>=de;b=0<=de?++E:--E){for(R=A[b],k=V,(i=Y=0,ae=d-1);0<=ae?Y<=ae:Y>=ae;i=0<=ae?++Y:--Y)s=a(r[i]-R),s=ne;i=0<=ne?++N:--N)v[i]=null;for(b=X=0,te=w-1;0<=te?X<=te:X>=te;b=0<=te?++X:--X)l=t[b],null===v[l]?v[l]=A[b]:v[l]+=A[b];for(i=U=0,M=d-1;0<=M?U<=M:U>=M;i=0<=M?++U:--U)v[i]*=1/c[i];for(S=!1,i=oe=0,W=d-1;0<=W?oe<=W:oe>=W;i=0<=W?++oe:--oe)if(v[i]!==r[b]){S=!0;break}r=v,j++,200=F;i=0<=F?++ie:--ie)h[i]=[];for(b=re=0,I=w-1;0<=I?re<=I:re>=I;b=0<=I?++re:--re)l=t[b],h[l].push(A[b]);for(P=[],i=le=0,Q=d-1;0<=Q?le<=Q:le>=Q;i=0<=Q?++le:--le)P.push(h[i][0]),P.push(h[i][h[i].length-1]);for(P=P.sort(function(e,f){return e-f}),u.push(P[0]),(b=ce=1,H=P.length-1);ce<=H;b=ce+=2)isNaN(P[b])||u.push(P[b])}return u},y.brewer=g={OrRd:["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#b30000","#7f0000"],PuBu:["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#045a8d","#023858"],BuPu:["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#810f7c","#4d004b"],Oranges:["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#a63603","#7f2704"],BuGn:["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#006d2c","#00441b"],YlOrBr:["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#993404","#662506"],YlGn:["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"],Reds:["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15","#67000d"],RdPu:["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177","#49006a"],Greens:["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#006d2c","#00441b"],YlGnBu:["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"],Purples:["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#54278f","#3f007d"],GnBu:["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081"],Greys:["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525","#000000"],YlOrRd:["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"],PuRd:["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#980043","#67001f"],Blues:["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"],PuBuGn:["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016c59","#014636"],Spectral:["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"],RdYlGn:["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"],RdBu:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"],PiYG:["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"],PRGn:["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"],RdYlBu:["#a50026","#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"],BrBG:["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"],RdGy:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"],PuOr:["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"],Set2:["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494","#b3b3b3"],Accent:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17","#666666"],Set1:["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf","#999999"],Set3:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5","#ffed6f"],Dark2:["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"],Paired:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99","#b15928"],Pastel2:["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc","#cccccc"],Pastel1:["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"]},y.colors=m={indigo:"#4b0082",gold:"#ffd700",hotpink:"#ff69b4",firebrick:"#b22222",indianred:"#cd5c5c",yellow:"#ffff00",mistyrose:"#ffe4e1",darkolivegreen:"#556b2f",olive:"#808000",darkseagreen:"#8fbc8f",pink:"#ffc0cb",tomato:"#ff6347",lightcoral:"#f08080",orangered:"#ff4500",navajowhite:"#ffdead",lime:"#00ff00",palegreen:"#98fb98",darkslategrey:"#2f4f4f",greenyellow:"#adff2f",burlywood:"#deb887",seashell:"#fff5ee",mediumspringgreen:"#00fa9a",fuchsia:"#ff00ff",papayawhip:"#ffefd5",blanchedalmond:"#ffebcd",chartreuse:"#7fff00",dimgray:"#696969",black:"#000000",peachpuff:"#ffdab9",springgreen:"#00ff7f",aquamarine:"#7fffd4",white:"#ffffff",orange:"#ffa500",lightsalmon:"#ffa07a",darkslategray:"#2f4f4f",brown:"#a52a2a",ivory:"#fffff0",dodgerblue:"#1e90ff",peru:"#cd853f",lawngreen:"#7cfc00",chocolate:"#d2691e",crimson:"#dc143c",forestgreen:"#228b22",darkgrey:"#a9a9a9",lightseagreen:"#20b2aa",cyan:"#00ffff",mintcream:"#f5fffa",silver:"#c0c0c0",antiquewhite:"#faebd7",mediumorchid:"#ba55d3",skyblue:"#87ceeb",gray:"#808080",darkturquoise:"#00ced1",goldenrod:"#daa520",darkgreen:"#006400",floralwhite:"#fffaf0",darkviolet:"#9400d3",darkgray:"#a9a9a9",moccasin:"#ffe4b5",saddlebrown:"#8b4513",grey:"#808080",darkslateblue:"#483d8b",lightskyblue:"#87cefa",lightpink:"#ffb6c1",mediumvioletred:"#c71585",slategrey:"#708090",red:"#ff0000",deeppink:"#ff1493",limegreen:"#32cd32",darkmagenta:"#8b008b",palegoldenrod:"#eee8aa",plum:"#dda0dd",turquoise:"#40e0d0",lightgrey:"#d3d3d3",lightgoldenrodyellow:"#fafad2",darkgoldenrod:"#b8860b",lavender:"#e6e6fa",maroon:"#800000",yellowgreen:"#9acd32",sandybrown:"#f4a460",thistle:"#d8bfd8",violet:"#ee82ee",navy:"#000080",magenta:"#ff00ff",dimgrey:"#696969",tan:"#d2b48c",rosybrown:"#bc8f8f",olivedrab:"#6b8e23",blue:"#0000ff",lightblue:"#add8e6",ghostwhite:"#f8f8ff",honeydew:"#f0fff0",cornflowerblue:"#6495ed",slateblue:"#6a5acd",linen:"#faf0e6",darkblue:"#00008b",powderblue:"#b0e0e6",seagreen:"#2e8b57",darkkhaki:"#bdb76b",snow:"#fffafa",sienna:"#a0522d",mediumblue:"#0000cd",royalblue:"#4169e1",lightcyan:"#e0ffff",green:"#008000",mediumpurple:"#9370db",midnightblue:"#191970",cornsilk:"#fff8dc",paleturquoise:"#afeeee",bisque:"#ffe4c4",slategray:"#708090",darkcyan:"#008b8b",khaki:"#f0e68c",wheat:"#f5deb3",teal:"#008080",darkorchid:"#9932cc",deepskyblue:"#00bfff",salmon:"#fa8072",darkred:"#8b0000",steelblue:"#4682b4",palevioletred:"#db7093",lightslategray:"#778899",aliceblue:"#f0f8ff",lightslategrey:"#778899",lightgreen:"#90ee90",orchid:"#da70d6",gainsboro:"#dcdcdc",mediumseagreen:"#3cb371",lightgray:"#d3d3d3",mediumturquoise:"#48d1cc",lemonchiffon:"#fffacd",cadetblue:"#5f9ea0",lightyellow:"#ffffe0",lavenderblush:"#fff0f5",coral:"#ff7f50",purple:"#800080",aqua:"#00ffff",whitesmoke:"#f5f5f5",mediumslateblue:"#7b68ee",darkorange:"#ff8c00",mediumaquamarine:"#66cdaa",darksalmon:"#e9967a",beige:"#f5f5dc",blueviolet:"#8a2be2",azure:"#f0ffff",lightsteelblue:"#b0c4de",oldlace:"#fdf5e6"},D=function(){var e,f,d,a,n;for(e={},n=["Boolean","Number","String","Function","Array","Date","RegExp","Undefined","Null"],(d=0,a=n.length);dd&&(e=d),e},U=function(e){return 3<=e.length?e:e[0]},c=2*Q,l=Q/3,x=I,b=function(e){var f,d,a,n,o,r,l,c,t,i,s;return e=function(){var f,d,a;for(a=[],f=0,d=e.length;f=d;f=++d)a.push(o[f]+e*(r[f]-o[f]));return a}(),y.lab.apply(y,d)}):3===e.length?(i=function(){var f,d,a;for(a=[],f=0,d=e.length;f=d;f=++d)a.push((1-e)*(1-e)*o[f]+2*(1-e)*e*r[f]+e*e*l[f]);return a}(),y.lab.apply(y,d)}):4===e.length?(s=function(){var f,d,a;for(a=[],f=0,d=e.length;f=d;f=++d)a.push((1-e)*(1-e)*(1-e)*o[f]+3*(1-e)*(1-e)*e*r[f]+3*(1-e)*e*e*l[f]+e*e*e*c[f]);return a}(),y.lab.apply(y,d)}):5===e.length&&(d=b(e.slice(0,3)),a=b(e.slice(2,5)),f=function(e){return 0.5>e?d(2*e):a(2*(e-0.5))}),f},y.interpolate.bezier=b}).call(this)},{}],"/home/fmauneko/code/trianglify/node_modules/delaunator/index.js":[function(e,f){"use strict";function t(f,a,n){a||(a=m),n||(n=_);for(var u=Infinity,g=Infinity,w=-Infinity,j=-Infinity,v=this.coords=[],z=this.ids=new Uint32Array(f.length),B=0;Bw&&(w=p),x>j&&(j=x)}var y,S,G,P=(u+w)/2,R=(g+j)/2,A=Infinity;for(B=0;Bl(v[2*y],v[2*y+1],v[2*S],v[2*S+1],v[2*G],v[2*G+1])){var T=S;S=G,G=T}var E=v[2*y],Y=v[2*y+1],N=v[2*S],X=v[2*S+1],D=v[2*G],U=v[2*G+1],K=s(E,Y,N,X,D,U);for(this._cx=K.x,this._cy=K.y,h(z,v,0,z.length-1,K.x,K.y),this._hashSize=Math.ceil(d(f.length)),this._hash=[],B=0;Bl(p,x,t.x,t.y,t.next.x,t.next.y);)J=this._addTriangle(t.i,B,t.next.i,t.prev.t,-1,t.t),t.prev.t=this._legalize(J+2),this.hull=r(t),t=t.next;if($)for(t=M.prev;0>l(p,x,t.prev.x,t.prev.y,t.x,t.y);)J=this._addTriangle(t.prev.i,B,t.i,-1,t.t,t.prev.t),this._legalize(J+2),t.prev.t=J,this.hull=r(t),t=t.prev;this._hashEdge(M),this._hashEdge(M.prev)}this.triangles=W.subarray(0,this.trianglesLen),this.halfedges=V.subarray(0,this.trianglesLen)}function o(e,f,d,a){var n=e-d,t=f-a;return n*n+t*t}function l(e,f,d,a,n,t){return(a-f)*(n-d)-(d-e)*(t-a)}function i(e,f,d,a,n,t,o,i){e-=o,f-=i,d-=o,a-=i,n-=o,t-=i;var r=e*e+f*f,l=d*d+a*a,c=n*n+t*t;return 0>e*(a*c-l*t)-f*(d*c-l*n)+r*(d*t-a*n)}function c(e,f,a,n,t,o){a-=e,n-=f,t-=e,o-=f;var i=a*a+n*n,r=t*t+o*o;if(0==i||0==r)return Infinity;var l=a*o-n*t;if(0==l)return Infinity;var d=0.5*(o*i-n*r)/l,c=0.5*(a*r-t*i)/l;return d*d+c*c}function s(e,f,a,n,t,o){a-=e,n-=f,t-=e,o-=f;var i=a*a+n*n,r=t*t+o*o,l=a*o-n*t,d=0.5*(o*i-n*r)/l,c=0.5*(a*r-t*i)/l;return{x:e+d,y:f+c}}function b(e,f,d){var a={i:f,x:e[2*f],y:e[2*f+1],t:0,prev:null,next:null,removed:!1};return d?(a.next=d.next,a.prev=d,d.next.prev=a,d.next=a):(a.prev=a,a.next=a),a}function r(e){return e.prev.next=e.next,e.next.prev=e.prev,e.removed=!0,e.prev}function h(e,f,d,a,n,t){var o,i,r;if(20>=a-d)for(o=d+1;o<=a;o++){for(r=e[o],i=o-1;i>=d&&0>1,o),0u(f,e[o],r,n,t));do i--;while(0=i-d?(h(e,f,o,a,n,t),h(e,f,d,i-1,n,t)):(h(e,f,d,i-1,n,t),h(e,f,o,a,n,t))}}function u(e,f,d,a,n){var t=o(e[2*f],e[2*f+1],a,n),i=o(e[2*d],e[2*d+1],a,n);return t-i||e[2*f]-e[2*d]||e[2*f+1]-e[2*d+1]}function g(e,f,d){var a=e[f];e[f]=e[d],e[d]=a}function m(e){return e[0]}function _(e){return e[1]}f.exports=t,t.prototype={_hashEdge:function(f){this._hash[this._hashKey(f.x,f.y)]=f},_hashKey:function(e,f){var d=e-this._cx,t=f-this._cy,o=1-d/(a(d)+a(t));return n((2+(0>t?-o:o))/4*this._hashSize)},_legalize:function(e){var f=this.triangles,d=this.coords,a=this.halfedges,n=a[e],t=e-e%3,o=n-n%3,r=t+(e+2)%3,l=o+(n+2)%3,c=f[r],s=f[e],b=f[t+(e+1)%3],h=f[l],u=i(d[2*c],d[2*c+1],d[2*s],d[2*s+1],d[2*b],d[2*b+1],d[2*h],d[2*h+1]);return u?(f[e]=h,f[n]=c,this._link(e,a[l]),this._link(n,a[r]),this._link(r,l),this._legalize(e),this._legalize(o+(n+1)%3)):r},_link:function(e,f){this.halfedges[e]=f,-1!==f&&(this.halfedges[f]=e)},_addTriangle:function(e,f,d,n,a,o){var i=this.trianglesLen;return this.triangles[i]=e,this.triangles[i+1]=f,this.triangles[i+2]=d,this._link(i,n),this._link(i+1,a),this._link(i+2,o),this.trianglesLen+=3,i}}},{}],"/home/fmauneko/code/trianglify/node_modules/process/browser.js":[function(e,f){function d(){if(!o){o=!0;for(var e,f=t.length;f;){e=t,t=[];for(var d=-1;++df.s0&&(f.s0+=1),f.s1-=d(e),0>f.s1&&(f.s1+=1),f.s2-=d(e),0>f.s2&&(f.s2+=1),d=null}function n(e,f){return f.c=e.c,f.s0=e.s0,f.s1=e.s1,f.s2=e.s2,f}function t(e,f){var d=new a(e),t=f&&f.state,o=d.next;return o.int32=function(){return 0|4294967296*d.next()},o.double=function(){return o()+1.1102230246251565e-16*(0|2097152*o())},o.quick=o,t&&("object"==typeof t&&n(t,d),o.state=function(){return n(d,{})}),o}function o(){var e=4022871197;return function(f){f=f.toString();for(var d=0;d>>0,a-=e,a*=e,e=a>>>0,a-=e,e+=4294967296*a}return 2.3283064365386963e-10*(e>>>0)}}f&&f.exports?f.exports=t:d&&d.amd?d(function(){return t}):this.alea=t})(this,"object"==typeof d&&d,"function"==typeof e&&e)},{}],"/home/fmauneko/code/trianglify/node_modules/seedrandom/lib/tychei.js":[function(f,d){(function(e,f,d){function a(e){var f=this,d="";f.next=function(){var e=f.b,n=f.c,t=f.d,d=f.a;return e=e<<25^e>>>7^n,n=0|n-t,t=t<<24^t>>>8^d,d=0|d-e,f.b=e=e<<20^e>>>12^n,f.c=n=0|n-t,f.d=t<<16^n>>>16^d,f.a=0|d-e},f.a=0,f.b=0,f.c=-1640531527,f.d=1367130551,e===n(e)?(f.a=0|e/4294967296,f.b=0|e):d+=e;for(var a=0;a>>0)/4294967296};return o.double=function(){do var e=d.next()>>>11,f=(d.next()>>>0)/4294967296,a=(e+f)/2097152;while(0===a);return a},o.int32=d.next,o.quick=o,n&&("object"==typeof n&&t(n,d),o.state=function(){return t(d,{})}),o}f&&f.exports?f.exports=o:d&&d.amd?d(function(){return o}):this.tychei=o})(this,"object"==typeof d&&d,"function"==typeof e&&e)},{}],"/home/fmauneko/code/trianglify/node_modules/seedrandom/lib/xor128.js":[function(f,d){(function(e,f,d){function a(e){var f=this,d="";f.x=0,f.y=0,f.z=0,f.w=0,f.next=function(){var e=f.x^f.x<<11;return f.x=f.y,f.y=f.z,f.z=f.w,f.w^=f.w>>>19^e^e>>>8},e===(0|e)?f.x=e:d+=e;for(var a=0;a>>0)/4294967296};return o.double=function(){do var e=d.next()>>>11,f=(d.next()>>>0)/4294967296,a=(e+f)/2097152;while(0===a);return a},o.int32=d.next,o.quick=o,t&&("object"==typeof t&&n(t,d),o.state=function(){return n(d,{})}),o}f&&f.exports?f.exports=t:d&&d.amd?d(function(){return t}):this.xor128=t})(this,"object"==typeof d&&d,"function"==typeof e&&e)},{}],"/home/fmauneko/code/trianglify/node_modules/seedrandom/lib/xor4096.js":[function(d,a){(function(e,d,a){function n(e){var d=this;d.next=function(){var e,f,a=d.w,n=d.X,t=d.i;return d.w=a=0|a+1640531527,f=n[127&t+34],e=n[t=127&t+1],f^=f<<13,e^=e<<17,f^=f>>>15,e^=e>>>12,f=n[t]=f^e,d.i=t,0|f+(a^a>>>16)},function(e,d){var a,n,t,o,i,r=[],l=128;for(d===(0|d)?(n=d,d=null):(d+="\0",n=0,l=f(l,d.length)),t=0,o=-32;o>>15,n^=n<<4,n^=n>>>13,0<=o&&(i=0|i+1640531527,a=r[127&o]^=n+i,t=0==a?t+1:0);for(128<=t&&(r[127&(d&&d.length||0)]=-1),t=127,o=512;0>>15,a^=a>>>12,r[t]=n^a;e.w=i,e.X=r,e.i=t}(d,e)}function t(e,f){return f.i=e.i,f.w=e.w,f.X=e.X.slice(),f}function o(e,f){null==e&&(e=+new Date);var d=new n(e),a=f&&f.state,o=function(){return(d.next()>>>0)/4294967296};return o.double=function(){do var e=d.next()>>>11,f=(d.next()>>>0)/4294967296,a=(e+f)/2097152;while(0===a);return a},o.int32=d.next,o.quick=o,a&&(a.X&&t(a,d),o.state=function(){return t(d,{})}),o}d&&d.exports?d.exports=o:a&&a.amd?a(function(){return o}):this.xor4096=o})(this,"object"==typeof a&&a,"function"==typeof e&&e)},{}],"/home/fmauneko/code/trianglify/node_modules/seedrandom/lib/xorshift7.js":[function(f,d){(function(e,f,d){function a(e){var f=this;f.next=function(){var e,d,a=f.x,n=f.i;return e=a[n],e^=e>>>7,d=e^e<<24,e=a[7&n+1],d^=e^e>>>10,e=a[7&n+3],d^=e^e>>>3,e=a[7&n+4],d^=e^e<<7,e=a[7&n+7],e^=e<<13,d^=e^e<<9,a[n]=d,f.i=7&n+1,d},function(e,f){var d,a,n=[];if(f===(0|f))a=n[0]=f;else for(f=""+f,d=0;dn.length;)n.push(0);for(d=0;8>d&&0===n[d];++d);for(a=8==d?n[7]=-1:n[d],e.x=n,e.i=0,d=256;0>>0)/4294967296};return o.double=function(){do var e=d.next()>>>11,f=(d.next()>>>0)/4294967296,a=(e+f)/2097152;while(0===a);return a},o.int32=d.next,o.quick=o,t&&(t.x&&n(t,d),o.state=function(){return n(d,{})}),o}f&&f.exports?f.exports=t:d&&d.amd?d(function(){return t}):this.xorshift7=t})(this,"object"==typeof d&&d,"function"==typeof e&&e)},{}],"/home/fmauneko/code/trianglify/node_modules/seedrandom/lib/xorwow.js":[function(f,d){(function(e,f,d){function a(e){var f=this,d="";f.next=function(){var e=f.x^f.x>>>2;return f.x=f.y,f.y=f.z,f.z=f.w,f.w=f.v,0|(f.d=0|f.d+362437)+(f.v=f.v^f.v<<4^(e^e<<1))},f.x=0,f.y=0,f.z=0,f.w=0,f.v=0,e===(0|e)?f.x=e:d+=e;for(var a=0;a>>4),f.next()}function n(e,f){return f.x=e.x,f.y=e.y,f.z=e.z,f.w=e.w,f.v=e.v,f.d=e.d,f}function t(e,f){var d=new a(e),t=f&&f.state,o=function(){return(d.next()>>>0)/4294967296};return o.double=function(){do var e=d.next()>>>11,f=(d.next()>>>0)/4294967296,a=(e+f)/2097152;while(0===a);return a},o.int32=d.next,o.quick=o,t&&("object"==typeof t&&n(t,d),o.state=function(){return n(d,{})}),o}f&&f.exports?f.exports=t:d&&d.amd?d(function(){return t}):this.xorwow=t})(this,"object"==typeof d&&d,"function"==typeof e&&e)},{}],"/home/fmauneko/code/trianglify/node_modules/seedrandom/seedrandom.js":[function(f,d){(function(a,n){function t(e,f,d){var t=[];f=!0==f?{entropy:!0}:f||{};var b=l(r(f.entropy?[e,s(a)]:null==e?c():e,3),t),h=new o(t),x=function(){for(var e=h.g(g),f=m,d=0;e=_;)e/=2,f/=2,d>>>=1;return(e+d)/f};return x.int32=function(){return 0|h.g(4)},x.quick=function(){return h.g(4)/4294967296},x.double=x,l(s(h.S),a),(f.pass||d||function(e,f,d,a){return a&&(a.S&&i(a,h),e.state=function(){return i(h,{})}),d?(n[p]=e,f):e})(x,b,"global"in f?f.global:this==n,f.state)}function o(e){var f,d=e.length,a=this,n=0,t=a.i=a.j=0,o=a.S=[];for(d||(e=[d++]);n THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | Then in `src/main.c` you should use: 31 | 32 | #include 33 | #include 34 | 35 | // rest H/C/CPP code 36 | 37 | PlatformIO will find your libraries automatically, configure preprocessor's 38 | include paths and build them. 39 | 40 | More information about PlatformIO Library Dependency Finder 41 | - http://docs.platformio.org/page/librarymanager/ldf.html 42 | -------------------------------------------------------------------------------- /PowerMeter/logServer.sh: -------------------------------------------------------------------------------- 1 | socat -u udp-recv:5014 - 2 | -------------------------------------------------------------------------------- /PowerMeter/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; http://docs.platformio.org/page/projectconf.html 10 | 11 | [env:d1_mini] 12 | platform = espressif8266 13 | board = d1_mini_lite 14 | framework = arduino 15 | upload_speed = 921600 16 | monitor_speed = 115200 17 | 18 | lib_deps = ESPSoftwareSerial@5.0.3 19 | PZEM004T@1.1.1 20 | MQTT 21 | SimpleTimer 22 | ESPAsyncTCP 23 | ESP Async WebServer 24 | Time 25 | TimeZone 26 | 27 | ; For OTA firmware upload over the air you have to uncommend 28 | ; the following two lines 29 | upload_port = 192.168.1.250 30 | upload_protocol = espota 31 | ; upload_flags = --auth=OTAFUpdate 32 | -------------------------------------------------------------------------------- /PowerMeter/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // For MQTT support 4 | #include // For OTA firmware update support 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "secrets.h" 15 | 16 | #define FW_Version "1.0.1" 17 | 18 | // PZEM004T Configuration. 19 | // Change the pins if using something other than the Wemos D1 mini and D5/D6 for UART communication. 20 | #define PZEM_TIMEOUT 3500 21 | PZEM004T pzem( D6, D5); // RX,TX 22 | IPAddress ip( 192,168,1,1 ); 23 | 24 | unsigned long pzemDataOK = 0; 25 | unsigned long pzemDataNOK = 0; 26 | 27 | int mState = 0; // Current state for the state machine controlling the connection to the PZEM 28 | // 0 - Disconnected 29 | // 1 - Connecting 30 | // 2 - Retrieve data 31 | // 3 - Retrieving data 32 | 33 | unsigned long SLEEP_TIME = 60 * 1000; // Sleep time between reads of PZEM004T values 34 | int MONITOR_LED = 2; // Monitoring led to send some visual indication to the user. 35 | //unsigned long tick; // Used for blinking the MONITOR_LED: ON -> OTA , Blink FAST: Connecting, Blink SLOW: Working 36 | unsigned long ledBlink = 200; // Led blink interval: Fast - Connecting to PZEM, slow - Connected 37 | int monitor_led_state = LOW; 38 | 39 | WiFiClient WIFIClient; 40 | IPAddress thisDevice; 41 | String Wifi_ssid; 42 | char hostname[32]; 43 | 44 | SimpleTimer timer; 45 | 46 | MQTTClient MQTT_client(512); 47 | char MQTT_AttributesTopic[256]; 48 | char MQTT_TelemetryTopic[256]; 49 | char SensorAttributes[512]; 50 | char SensorTelemetry[512]; 51 | 52 | unsigned long previousMillis = 0; 53 | unsigned long pingMqtt = 5 * 60 * 1000; // Ping the MQTT broker every 5 minutes by sending the IOT Atributes message. 54 | unsigned long previousPing = 0; 55 | 56 | /* 57 | * Hostname: 58 | * 59 | * Sets the device hostname for OTA and MDNS. 60 | * 61 | * */ 62 | void setHostname() { 63 | 64 | // Set Hostname for OTA and network mDNS (add only 2 last bytes of last MAC Address) 65 | sprintf_P( hostname, PSTR("ESP-PWRMETER-%04X"), ESP.getChipId() & 0xFFFF); 66 | } 67 | 68 | /* 69 | * MQTT Support 70 | * 71 | * Static atributes like, IP, SSID, and so on are set to the MQTT atributes topic. 72 | * Telemetry data, data that changes through time, are sent to the MQTT telemetry topic. 73 | * 74 | */ 75 | 76 | //* Supporting functions: 77 | void calcAttributesTopic() { 78 | String s = "iot/device/" + String(MQTT_ClientID) + "/attributes"; 79 | s.toCharArray(MQTT_AttributesTopic,256,0); 80 | } 81 | 82 | void calcTelemetryTopic() { 83 | String s = "iot/device/" + String(MQTT_ClientID) + "/telemetry"; 84 | s.toCharArray(MQTT_TelemetryTopic,256,0); 85 | } 86 | 87 | /* 88 | * IOT Support: 89 | * 90 | * Functions that using MQTT send data to the IOT server by publishing data on specific topics. 91 | * 92 | */ 93 | void IOT_setAttributes() { 94 | String s = "[{\"type\":\"ESP8266\"}," \ 95 | "{\"ipaddr\":\"" + thisDevice.toString() + "\"}," \ 96 | "{\"ssid\":\""+ WiFi.SSID() + "\"}," \ 97 | "{\"rssi\":\""+ String(WiFi.RSSI()) + "\"}," \ 98 | "{\"web\":\"http://" + thisDevice.toString() + "\"}," \ 99 | "{\"dataok\":" + String(pzemDataOK) + "}," \ 100 | "{\"datanok\":" + String(pzemDataNOK) + "}" \ 101 | "]"; 102 | 103 | s.toCharArray( SensorAttributes, 512,0); 104 | Log.I("PowerMeter Attributes:"); 105 | Log.I(SensorAttributes); 106 | MQTT_client.publish( MQTT_AttributesTopic, SensorAttributes); 107 | } 108 | 109 | void IOT_setTelemetry(String SensorTelemetry) { 110 | //s.toCharArray(SensorAttributes, 512,0); 111 | MQTT_client.publish( MQTT_TelemetryTopic, SensorTelemetry); 112 | } 113 | 114 | // MQTT Calback function for receiving subscribed messages. 115 | void MQTT_callback(String &topic, String &payload) { 116 | /* Just a standard callback. */ 117 | Log.I("Message arrived in topic: "); 118 | Log.I(topic); 119 | 120 | Log.I("Message:"); 121 | Log.I( payload ); 122 | } 123 | 124 | //* Connects to the MQTT Broker 125 | void MQTT_Connect() { 126 | Log.I("Connecting to MQTT Broker..."); 127 | MQTT_client.begin( MQTT_Server, MQTT_Port , WIFIClient ); 128 | MQTT_client.onMessage( MQTT_callback ); 129 | MQTT_client.setOptions( 120, true, 120 ); 130 | 131 | while (! MQTT_client.connect( MQTT_ClientID, MQTT_UserID, MQTT_Password ) ) { 132 | Log.E("MQTT Connection failed."); 133 | delay(1000); 134 | } 135 | 136 | calcAttributesTopic(); 137 | calcTelemetryTopic(); 138 | 139 | Log.I("Connected to MQTT!"); 140 | } 141 | 142 | /* 143 | * OTA support 144 | * 145 | */ 146 | void OTA_Setup() { 147 | 148 | Log.I("Setting up OTA..."); 149 | ArduinoOTA.setHostname( hostname ); 150 | ArduinoOTA.begin(); 151 | 152 | // OTA callbacks 153 | ArduinoOTA.onStart([]() { 154 | Log.I(F("\r\nOTA Starting")); 155 | digitalWrite( MONITOR_LED, HIGH); 156 | }); 157 | 158 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 159 | uint8_t percent = progress/( total/100 ); 160 | 161 | if (percent % 10 == 0) { 162 | Log.I( String(percent)); 163 | digitalWrite( MONITOR_LED, !digitalRead( MONITOR_LED)); 164 | } 165 | }); 166 | 167 | ArduinoOTA.onEnd([]() { 168 | 169 | Log.I(F("OTA Done\nRebooting...")); 170 | digitalWrite( MONITOR_LED, LOW); 171 | }); 172 | 173 | ArduinoOTA.onError([](ota_error_t error) { 174 | Log.E("OTA Error: " + String(error)); 175 | 176 | if (error == OTA_AUTH_ERROR) { 177 | Log.E("OTA Auth Failed"); 178 | } else 179 | if (error == OTA_BEGIN_ERROR) { 180 | Log.E("OTA Begin Failed"); 181 | } else 182 | if (error == OTA_CONNECT_ERROR) { 183 | Log.E("OTA Connect Failed"); 184 | } else 185 | if (error == OTA_RECEIVE_ERROR) { 186 | Log.E("OTA Receive Failed"); 187 | } else 188 | if (error == OTA_END_ERROR) { 189 | Log.E("OTA End Failed"); 190 | } 191 | 192 | ESP.restart(); 193 | }); 194 | 195 | Log.I("OTA setup done!"); 196 | } 197 | 198 | void display_WIFIInfo() { 199 | Log.I("Connected to WIFI: " + WiFi.SSID() ); 200 | 201 | thisDevice = WiFi.localIP(); 202 | Log.I(" IP: " + thisDevice.toString() ); 203 | } 204 | 205 | /* 206 | * WIFI_Setup: Setup the WIFI connection. 207 | * 208 | * It cycles over the configured access points until a sucessufull connection is done 209 | * 210 | */ 211 | void WIFI_Setup() { 212 | bool connected = false; 213 | char *ssid; 214 | char *pwd; 215 | int cntAP = 0; 216 | int tries = 0; 217 | String out; 218 | 219 | Log.I("Connecting to WIFI..."); 220 | 221 | // Station mode. 222 | WiFi.disconnect(); 223 | WiFi.softAPdisconnect(true); 224 | WiFi.mode(WIFI_STA); 225 | 226 | while ( !connected ) { 227 | ssid = (char *)APs[cntAP][0]; 228 | pwd = (char *)APs[cntAP][1]; 229 | Log.I("Connecting to: "); 230 | Log.I(ssid); 231 | 232 | WiFi.begin(ssid, pwd ); 233 | 234 | if (WiFi.waitForConnectResult() != WL_CONNECTED) { 235 | Log.E("Connection Failed! Trying next AP..."); 236 | Log.E("Number of tries: " + String(tries)); 237 | 238 | cntAP++; 239 | tries++; 240 | // Circle the array one entry after another 241 | if (cntAP == NUMAPS ) 242 | cntAP = 0; 243 | 244 | // Set Monitor led on: Light up the led to show that something is working 245 | digitalWrite( MONITOR_LED, LOW); 246 | delay(1000); 247 | digitalWrite( MONITOR_LED, HIGH); 248 | 249 | yield(); 250 | } else 251 | connected = true; 252 | } 253 | 254 | /* WiFi.macAddress(MAC_address); 255 | for (unsigned int i = 0; i < sizeof(MAC_address); ++i){ 256 | sprintf(MAC_char,"%s%02x:",MAC_char,MAC_address[i]); 257 | } 258 | */ 259 | display_WIFIInfo(); 260 | } 261 | 262 | /* 263 | * check_Connectivity: Checks the connectivity. 264 | * 265 | * Checks the connectivity namely if we are connected to WIFI and to the MQTT broker. 266 | * If not, we try to reconnect. 267 | * 268 | */ 269 | 270 | void check_Connectivity() { 271 | 272 | /* Check WIFI connection first. */ 273 | if ( WiFi.status() != WL_CONNECTED ) { 274 | WIFI_Setup(); 275 | MQTT_Connect(); 276 | } else { 277 | /* Check MQTT connectivity: */ 278 | if ( !MQTT_client.connected() ) { 279 | MQTT_Connect(); 280 | // Send the IOT device attributes at MQTT connection 281 | IOT_setAttributes(); 282 | } 283 | } 284 | } 285 | 286 | /* 287 | * back_tasks: Executes the background tasks 288 | * 289 | * Calls the functions necessary to keep everything running smoothly while waiting or looping 290 | * Such tasks include mantaining the MQTT connection, checking OTA status and updating the timers. 291 | * 292 | */ 293 | 294 | void back_tasks() { 295 | MQTT_client.loop(); // Handle MQTT 296 | timer.run(); // Handle SimpleTimer 297 | } 298 | 299 | /* 300 | * PWRMeter_Connect: 301 | * 302 | * Tries to connect to the PowerMeter 303 | * 304 | */ 305 | 306 | void PWRMeter_Connect() { 307 | //uint8_t tries = 0; 308 | bool pzemOK = false; 309 | 310 | Log.I("Connecting to PZEM004T..."); 311 | appData.setPZEMState(PZEM_CONNECTING); 312 | pzem.setReadTimeout( PZEM_TIMEOUT); 313 | 314 | //while ( ((pzemOK=pzem.setAddress(ip)) == false) && ( tries < 10 ) ) { 315 | if ( (pzemOK=pzem.setAddress(ip)) == false) { 316 | Log.E("Failed to connect to PZEM004T..."); 317 | appData.setPZEMState(PZEM_CONNECTFAIL); 318 | //tries++; 319 | mState = 0; // Return to the NOT Connected State. 320 | } 321 | 322 | if (pzemOK) { 323 | Log.I("Connection to PZEM004T OK!"); 324 | appData.setPZEMState(PZEM_CONNECTED); 325 | mState = 2; // Move forward to the Connected State. 326 | } else { 327 | appData.setPZEMState(PZEM_DISCONNECTED); 328 | mState = 0; // Return to the NOT Connected State. 329 | } 330 | } 331 | 332 | /* 333 | * PWRMeter_getData: 334 | * 335 | * Gets data from the Power meter and sends it to the backend through MQTT 336 | * 337 | */ 338 | 339 | void PWRMeter_getData() { 340 | float v = 0; 341 | uint8_t tries = 0; 342 | 343 | // Set Monitor led on: 344 | digitalWrite( MONITOR_LED, LOW); 345 | 346 | Log.I("Getting PowerMeter data..."); 347 | 348 | // Get the PZEM004T Power Meter data 349 | do { 350 | v = pzem.voltage(ip); 351 | tries++; 352 | 353 | // Execute the back ground tasks otherwise we may loose conectivity to the MQTT broker. 354 | back_tasks(); 355 | delay(250); 356 | 357 | } while ( (v == -1) && ( tries < 10) ); 358 | 359 | if ( tries == 10 ) Log.E("Failed to get PowerMeter data after 10 tries!"); 360 | if ( v == -1 ) Log.E("No valid data obtained from the PowerMeter: V=-1"); 361 | else Log.I("PowerMeter Data OK!"); 362 | 363 | float i = pzem.current(ip); 364 | float p = pzem.power(ip); 365 | float e = pzem.energy(ip); 366 | 367 | // Turn led off: 368 | digitalWrite( MONITOR_LED, HIGH); 369 | 370 | // Build the MQTT message: 371 | String s = "{\"V\":" + String(v) + \ 372 | ",\"I\":" + String(i) + \ 373 | ",\"P\":" + String(p) + \ 374 | ",\"E\":" + String(e) + "}"; 375 | 376 | Log.I("-> Power Meter data: "); 377 | Log.I( s ); 378 | 379 | // Send the data through MQTT to the backend 380 | if ( v >= 0 ) { 381 | IOT_setTelemetry(s); 382 | 383 | // Set AppData for display 384 | appData.setVoltage( v ); 385 | appData.setCurrent( i ); 386 | appData.setPower( p ); 387 | appData.setEnergy( e ); 388 | appData.setSamplesOK(); 389 | 390 | pzemDataOK++; 391 | } else { 392 | Log.W("Data not sent due to invalid read."); 393 | appData.setSamplesNOK(); 394 | pzemDataNOK++; 395 | } 396 | 397 | mState = 2; // Move back to the Read data state to trigger another (future) read. 398 | } 399 | 400 | // Moves the state machine to the next state. 401 | void PWRMeter_ReadState() { 402 | mState = 2; 403 | } 404 | 405 | // Just blink the onboard led according to the defined period 406 | void Blink_MonitorLed() { 407 | digitalWrite( MONITOR_LED , monitor_led_state ); 408 | if ( monitor_led_state == LOW ) 409 | monitor_led_state = HIGH; 410 | else 411 | monitor_led_state = LOW; 412 | 413 | timer.setTimeout( ledBlink , Blink_MonitorLed ); // With this trick we can change the blink rate of the led 414 | } 415 | 416 | // Used to send the system atributes to the MQTT topic regarding the device attributes. 417 | void IOT_SendAttributes() { 418 | IOT_setAttributes(); 419 | } 420 | 421 | // Prints time. 422 | void printTime() { 423 | timeProvider.logTime(); 424 | } 425 | 426 | /* 427 | * MAIN CODE 428 | * 429 | */ 430 | 431 | void setup() { 432 | IPAddress udpServerAddress; 433 | udpServerAddress.fromString(UDPLOG_Server); 434 | 435 | appData.setFWVersion(FW_Version); 436 | appData.setLogServerIPInfo(udpServerAddress.toString()); 437 | 438 | Serial.begin(115200); 439 | delay (200); // Wait for the serial port to settle. 440 | setHostname(); 441 | Log.setSerial( true ); // Log to Serial 442 | Log.setServer( udpServerAddress, UDPLOG_Port ); 443 | Log.setTagName("PWM01"); // Define a tag for log lines output 444 | 445 | // Indicator onbord LED 446 | pinMode( MONITOR_LED, OUTPUT); 447 | digitalWrite( MONITOR_LED, LOW); 448 | 449 | // We set WIFI first... 450 | WIFI_Setup(); 451 | 452 | // Setup Logging system. 453 | Log.I("Enabling UDP Log Server..."); 454 | Log.setUdp( true ); // Log to UDP server when connected to WIFI. 455 | 456 | // Print a boot mark 457 | Log.W("------------------------------------------------> Power Meter REBOOT"); 458 | 459 | // Setup OTA 460 | OTA_Setup(); 461 | 462 | // Set time provider to know current date and time 463 | timeProvider.setup(); 464 | timeProvider.logTime(); 465 | 466 | //Connect to the MQTT Broker: 467 | MQTT_Connect(); 468 | 469 | // Send the IOT device attributes at MQTT connection 470 | IOT_setAttributes(); 471 | 472 | // Setup WebServer so that we can have a web page while connecting to the PZEM004T 473 | Log.I("Setting up the embedded web server..."); 474 | webServer.setup(); 475 | Log.I("Web server available at port 80."); 476 | 477 | delay(100); 478 | display_WIFIInfo(); // To display wifi info on the UDP socket. 479 | 480 | // Setup the monitor blinking led 481 | timer.setTimeout( ledBlink , Blink_MonitorLed ); 482 | 483 | // Periodically send to the MQTT server the IOT device state 484 | timer.setInterval( pingMqtt , IOT_SendAttributes ); 485 | 486 | // Periodically log the time 487 | timer.setInterval( 3 * 60 * 1000 , printTime ); 488 | 489 | // Setup MDNS 490 | MDNS.begin( hostname ); 491 | MDNS.addService("http", "tcp", 80); 492 | } 493 | 494 | void loop() { 495 | // Check if we are still connected. 496 | check_Connectivity(); 497 | 498 | // Power meter state machine 499 | switch (mState) 500 | { 501 | case 0: // We are'nt connected. Trigger a connection every 3s until we connect. 502 | timer.setTimeout( 3000 , PWRMeter_Connect ); 503 | ledBlink = 200; // Blink the LED Fast 504 | mState = 1; 505 | break; 506 | case 1: // Connecting. The PWRMeter_Connect function will move to next state 507 | // So we do nothing here. 508 | break; 509 | case 2: // We are connected. Trigger a Power meter read. 510 | PWRMeter_getData(); 511 | timer.setTimeout(SLEEP_TIME, PWRMeter_ReadState); 512 | ledBlink = 500; // Blink the LED Slow 513 | mState = 3; 514 | break; 515 | case 3: // We are waiting for reading the data. The PWRMeter_ReadState will move back to the previous state. 516 | break; 517 | 518 | default: 519 | break; 520 | } 521 | 522 | // Execute the background tasks. 523 | back_tasks(); 524 | 525 | ArduinoOTA.handle(); // Handle OTA. 526 | } 527 | -------------------------------------------------------------------------------- /PowerMeter/src/secrets.h: -------------------------------------------------------------------------------- 1 | #ifndef _SECRETS_H 2 | #define _SECRETS_H 3 | 4 | #define NUMAPS 4 5 | static char const *APs[NUMAPS][2] = { 6 | {"AAAAAA","asdfljkasdfqweui34u"}, 7 | {"BBBBB","açsdlkfj29338fnree2"}, 8 | {"CCCCCCC","dfewererr"}, 9 | {"ZZZZZZZ","herer3ra"} 10 | }; 11 | 12 | // For connecting to the MQTT Broker 13 | #define MQTT_Server "192.168.1.17" 14 | #define MQTT_Port 1883 15 | #define MQTT_ClientID "ESP8266_PowerMeter" 16 | #define MQTT_UserID "ESP8266_PowerMeter" 17 | #define MQTT_Password "password1" 18 | 19 | // For the UDP Log Server 20 | #define UDPLOG_Server "192.168.1.68" // My local PC, but should be a server" 21 | #define UDPLOG_Port 5014 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerMeter 2 | This repository holds a PZEM-004T and ESP8266 based power/energy meter. More information at: https://primalcortex.wordpress.com/2019/07/06/measuring-home-energy-consumption-with-the-pzem004t-and-esp8266/. Note that the master branch is for use with the PZEM-004T Version 2.0 that uses standard serial communications. The Version 3.0 uses modbus which does not work with this version. 3 | 4 | The repository structure is as follows: 5 | The *PowerMeter* repository/folder contains the firmware for an ESP8266 and PZEM-004T based power/energy meter. This is the firmware that should be used to have a full functional ESP8266 based powermeter. 6 | 7 | The PowerMeter is designed to use Wemos D1 ESP8266 boards. 8 | 9 | The *PZEM004T-Test* repository contains a simple program for testing the ESP8266 to PZEM004 communications and the retrieving of data from the PZEM004T. 10 | 11 | The *Node-Red* folder contains a flow that receives published telemetry data from the Power Meter, saves it on an InfluxDB database and creates a Node-Red Dashboard UI to show the latest collected data. 12 | 13 | The *Grafana* folder contains a very simple grafana dashboard that retrieves data from the InfluxDB database and displays it. It's a no frills dashboard... 14 | 15 | # Screenshots: 16 | 17 | Some screenshots of the PowerMeter working: 18 | 19 | *ESP8266 based Power Meter Web Interface:* 20 | ![](/images/web_page.png?raw=true) 21 | 22 | *The Node-Red UI:* 23 | ![](/images/node_red.png?raw=true) 24 | 25 | *The simple Grafana dashboard:* 26 | ![](/images/grafana.png?raw=true) 27 | -------------------------------------------------------------------------------- /images/grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcgdam/PowerMeter/7b96124b1ec915e708960cfab70335b451e19309/images/grafana.png -------------------------------------------------------------------------------- /images/node_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcgdam/PowerMeter/7b96124b1ec915e708960cfab70335b451e19309/images/node_red.png -------------------------------------------------------------------------------- /images/web_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fcgdam/PowerMeter/7b96124b1ec915e708960cfab70335b451e19309/images/web_page.png --------------------------------------------------------------------------------