[{"id":"c187bca3.3e784","type":"mqtt in","z":"b432bbb7.4bcd48","name":"emon/emontx/power2","topic":"emon/emontx/power2","broker":"44985a1b.bb67a4","x":113,"y":505,"wires":[["3a36333f.c5c9cc"]]},{"id":"e8e9fa27.171608","type":"inject","z":"b432bbb7.4bcd48","name":"Send Manually","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"x":126,"y":107,"wires":[["71cd2d97.8e32d4"]]},{"id":"d68b8d43.29747","type":"http request","z":"b432bbb7.4bcd48","name":"Post","method":"POST","ret":"txt","url":"","x":707.25,"y":104,"wires":[["c072a7cd.3f8d58","d7346137.28cba"]]},{"id":"71cd2d97.8e32d4","type":"function","z":"b432bbb7.4bcd48","name":"Set API key here 1","func":"msg.action = msg.payload;\nmsg.headers = { \n 'X-Pvoutput-Apikey': 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn',\n 'X-Pvoutput-SystemId': 'nnnn',\n 'Content-Type': 'application/x-www-form-urlencoded'\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":366.75,"y":105.5,"wires":[["7cc49da7.833b64"]]},{"id":"c072a7cd.3f8d58","type":"debug","z":"b432bbb7.4bcd48","name":"headercheck","active":true,"console":"false","complete":"true","x":898,"y":104,"wires":[]},{"id":"c644eabb.39bb18","type":"inject","z":"b432bbb7.4bcd48","name":"Every 5 mins","topic":"","payload":"","payloadType":"str","repeat":"300","crontab":"","once":false,"x":128,"y":155,"wires":[["71cd2d97.8e32d4"]]},{"id":"9f17ab8.f60e858","type":"comment","z":"b432bbb7.4bcd48","name":"Send data to PVOutput.org","info":"This flow allows you to send data to PVOutput.org\n\nYou'll need to sign up an account (it's free) and get\nAPI key.\n\nYou need to put this API key and the System ID\ninto both the \"Set API Key here\" boxes.\n\nMake sure you have set the correct Time Zone in the console - \n sudo dpkg-reconfigure tzdata\n \nAnd also in the EmonCMS webpage account settings.\n\nOtherwise you'll get offset graphs and data.","x":131,"y":42,"wires":[]},{"id":"761f78ba.89e088","type":"debug","z":"b432bbb7.4bcd48","name":"Debug1","active":true,"console":"true","complete":"payload","x":709,"y":151.5,"wires":[]},{"id":"7cc49da7.833b64","type":"function","z":"b432bbb7.4bcd48","name":"Set up data","func":"msg.action = msg.payload;\n\nmsg.url = \"http://pvoutput.org/service/r2/addstatus.jsp\";\n\n// set up the data/time\nvar now = new Date(); \nvar year = now.getFullYear(); \nvar month = now.getMonth()+1; // Months start at 0 not 1 - FTFY\nvar day = now.getDate();\nvar hour = now.getHours(); \n\n//var hour = now.getHours()+1; // GMT to BST\n//if (hour === 24) { // BST overflow - This clearly needs some work!\n// hour = 0;\n// day = day +1;\n//} \n\nvar minute = now.getMinutes();\nvar second = now.getSeconds(); \n\n// fix any short date/times so all values are two digits\nif(month.toString().length == 1) { var month = '0'+month; }\nif(day.toString().length == 1) { var day = '0'+day;}\nif(hour.toString().length == 1) { var hour = '0'+hour; }\nif(minute.toString().length == 1) { var minute = '0'+minute; }\nif(second.toString().length == 1) { var second = '0'+second; }\n\nuploaddate = year.toString()+month.toString()+day.toString(); // string the dates or we get errors!\n\n//pull back stored data\nvar power1 = flow.get('power1')||0; // Power1 - Grid power import/export\nvar power2 = flow.get('power2')||0; // Power2 - Solar power import\nvar power3 = flow.get('power3')||0; // Power3 - My EV charger (you may use this for something else - or not)\n//var vrms = flow.get('vrms')||0; // Grid voltage\nvar contot = flow.get('contot')||0; // Consumption total for the day (watts)\nvar gentot = flow.get('gentot')||0; // Generation total for the day (watts)\nvar power1max = flow.get('power1max')||0; // Power1max - maximum Power1 of the last 5mins\ncontot = (Math.round(contot*100))/100; // trim to 2dp\nvar powerc = parseInt(power1, 10) + parseInt(power2, 10); // Consumption power now (need to convert back from string to int in decimal). Use Power1 or Power1max as described in function StorePower1\n// v1=gentotal v2=solar v3=contotal v4=con_max v5=t v6=EV v7=t_hall v8=t_landing v9=t_attic v10=t_outside v11=t_top v12=t_bottom\n//msg.payload = {\"d\":year+month+day,\"t\":hour+':'+minute,\"v1\":gentot,\"v2\":power2,\"v3\":contot,\"v4\":power1max,\"v6\":vrms,\"v8\":\"28.1\"};\nmsg.payload = {\"d\":uploaddate,\"t\":hour+':'+minute,\"v1\":gentot,\"v2\":power2,\"v3\":contot,\"v4\":power1max};\n\nflow.set('power1max',0); // reset maximum Power1 for the next five min period\n\nreturn msg;","outputs":1,"noerr":0,"x":535,"y":105,"wires":[["d68b8d43.29747","761f78ba.89e088","ac542fdb.fa626"]]},{"id":"10807c59.ef7f84","type":"mqtt in","z":"b432bbb7.4bcd48","name":"emon/#","topic":"emon/#","broker":"44985a1b.bb67a4","x":846,"y":772,"wires":[["29df4355.d620bc"]]},{"id":"29df4355.d620bc","type":"debug","z":"b432bbb7.4bcd48","name":"View all 'emon/#' MQTT traffic","active":false,"console":"false","complete":"payload","x":1061,"y":771,"wires":[]},{"id":"192a7e87.e6d581","type":"function","z":"b432bbb7.4bcd48","name":"Store Power1","func":"var power1 = msg.payload;\nflow.set('power1',power1);\nmsg.payload = {\"Power1\": power1};\nreturn msg;","outputs":1,"noerr":0,"x":301,"y":446,"wires":[["92244084.6ddbc"]]},{"id":"6d440b84.92bbf4","type":"mqtt in","z":"b432bbb7.4bcd48","name":"emon/emontx/power1","topic":"emon/emontx/power1","broker":"44985a1b.bb67a4","x":109,"y":445,"wires":[["192a7e87.e6d581"]]},{"id":"92244084.6ddbc","type":"debug","z":"b432bbb7.4bcd48","name":"","active":false,"console":"false","complete":"false","x":506,"y":445,"wires":[]},{"id":"3a36333f.c5c9cc","type":"function","z":"b432bbb7.4bcd48","name":"Store Power2","func":"// Just for reference all the calcs are done here due to timing issues with data turning up on two difference MQTT queues\n// the power1/power2 values get out of step and produce odd results.\n\n// initialise power2 to 0 if it doesn't exist already\nvar power2 = msg.payload;\nvar gentot = flow.get('gentot')||0; // generation total (for the day so far)\nvar gen_ts = flow.get('gen_ts')||0; // generation timestamp of last reading\nvar contot = flow.get('contot')||0; // consumption total (for the day so far) or 0 if not set\nvar con_ts = flow.get('con_ts')||0; // consumption timestamp of last read or 0 if not set\nvar now_ts = Math.round(+new Date()); // now timestamp\n\nvar power1 = flow.get('power1')||0; // get power1\nif (power1 === 0) {power1 = 1; } // match the old system\n\nif (gen_ts === 0) { // no previous gen timestamp\n flow.set('gentot',0);\n flow.set('gen_ts',now_ts);\n flow.set('power2',power2);\n} else {\n var diff = (now_ts - gen_ts) / 1000; // time since last reading in seconds\n gentot = gentot + (power2 / 3600 * diff); // increase the consumption total by the watts * seconds\n gentot = (Math.round(gentot*100))/100; // trim to 2dp\n flow.set('gentot',gentot);\n flow.set('gen_ts',now_ts);\n flow.set('power2',power2);\n}\n\nif (con_ts === 0) { // no previous consumption timestamp\n var diff = 0;\n flow.set('contot',0);\n flow.set('con_ts',now_ts);\n flow.set('power1',power1);\n flow.set('power1max',power1);\n flow.set('powerc',0);\n} else {\n var powerc = parseInt(power1, 10) + parseInt(power2, 10); // Consumption power now (need to convert back from string to int in decimal)\n flow.set('powerc',powerc); // store for later\n var diff = (now_ts - con_ts) / 1000; // time since last reading in seconds\n var powinc = (powerc / 3600 * diff);\n contot = contot + (powerc / 3600 * diff); // increase the consumption total by the watts * seconds\n flow.set('contot',contot);\n flow.set('con_ts',now_ts); \n flow.set('power1',power1);\n}\n\n// this section stores the max power usage - short spikes like kettles/toasters/microwave are missed from PVO graphing\n// if you don't like this remove this section. power1max is reset in the SetUpData function after POSTing\nvar power1max = flow.get('power1max')||0;\nif (powerc > power1max) {power1max = powerc; flow.set('power1max',power1max); }\n\nvar timestamp = new Date();\n\nvar now = new Date(); \nvar hour = now.getHours(); \nvar minute = now.getMinutes();\nvar second = now.getSeconds(); \n\n//msg.payload = {\"ConTot\": contot, \"Power1\": power1, \"Power2\": power2, \" DiffTS\": diff, \"Power1max\": power1max, \"PowerC\": powerc, \"PowInc\": powinc, \"GenTot\": gentot, \"PowerA\": power1, \"PowerB\": power2};\n\nvar msg1 = { payload: {\"PowA\":power1, \"PowB\": power2, \"Power1max\": power1max, \"H\": hour, \"M\": minute, \"S\":second} };\nvar msg2 = { payload: powerc };\nreturn [ msg1, msg2 ];\n\n\n","outputs":"2","noerr":0,"x":303,"y":505,"wires":[["fc72df40.038d2"],["e1d96b5f.bc14b8"]]},{"id":"fc72df40.038d2","type":"debug","z":"b432bbb7.4bcd48","name":"","active":false,"console":"false","complete":"payload","x":508,"y":499,"wires":[]},{"id":"f4e34e07.0b1cb","type":"inject","z":"b432bbb7.4bcd48","name":"Reset cumulative counters","topic":"ResetPVO","payload":"","payloadType":"date","repeat":"","crontab":"59 23 * * *","once":false,"x":903,"y":518,"wires":[["997a570a.f864b8"]]},{"id":"fa807916.f9ec68","type":"mqtt in","z":"b432bbb7.4bcd48","name":"emon/emontx/power3","topic":"emon/emontx/power3","broker":"44985a1b.bb67a4","x":118,"y":645,"wires":[["ebd486c3.be5aa8"]]},{"id":"ebd486c3.be5aa8","type":"function","z":"b432bbb7.4bcd48","name":"Store Power3","func":"// Even though we have Power3 PVO only understands generation and consumption\n// So no need to do all this stuff here\n\nvar power3 = msg.payload;\nflow.set('power3',power3);\n\nmsg.payload = \"Power3:\" + power3;\nreturn msg;","outputs":1,"noerr":0,"x":325,"y":644,"wires":[["75f4c90f.517668"]]},{"id":"75f4c90f.517668","type":"debug","z":"b432bbb7.4bcd48","name":"","active":false,"console":"false","complete":"false","x":519,"y":644,"wires":[]},{"id":"23e2531a.18f98c","type":"mqtt in","z":"b432bbb7.4bcd48","name":"emon/emontx/vmrs","topic":"emon/emontx/vrms","broker":"44985a1b.bb67a4","x":119,"y":704,"wires":[["74b3e05c.4860a"]]},{"id":"74b3e05c.4860a","type":"function","z":"b432bbb7.4bcd48","name":"Store Vrms","func":"// store the value\nvar vrms = msg.payload;\n\nvrms = vrms / 100; // temp until all the RFM69Pi is sorted\n\nflow.set('vrms',vrms);\n\nmsg.payload = \"VRMS:\" + vrms;\nreturn msg;\n","outputs":1,"noerr":0,"x":327,"y":702,"wires":[["baa47480.68b878"]]},{"id":"baa47480.68b878","type":"debug","z":"b432bbb7.4bcd48","name":"","active":false,"console":"false","complete":"false","x":513,"y":701,"wires":[]},{"id":"997a570a.f864b8","type":"function","z":"b432bbb7.4bcd48","name":"Reset totals","func":"// its midnight - reset the accumulated totals\ngentot = 0; // generation total for the day\nflow.set('gentot',gentot);\ncontot = 0; // comsumption total for the day\nflow.set('contot',contot);","outputs":1,"noerr":0,"x":1114,"y":518,"wires":[[]]},{"id":"d3db3be.f2c24c8","type":"mqtt in","z":"b432bbb7.4bcd48","name":"emon/study/temperature","topic":"emon/study/temperature","broker":"44985a1b.bb67a4","x":128,"y":774,"wires":[["94fa274f.6b05d8"]]},{"id":"94fa274f.6b05d8","type":"function","z":"b432bbb7.4bcd48","name":"Store T_study","func":"// store the value\nvar t_study = msg.payload;\nt_study = t_study / 100; // temp until all the RFM69Pi is sorted\nflow.set('t_study',t_study);\nmsg.payload = \"Study:\" + t_study;\nreturn msg;","outputs":1,"noerr":0,"x":355,"y":773,"wires":[["6310f4cc.9cef0c"]]},{"id":"6310f4cc.9cef0c","type":"debug","z":"b432bbb7.4bcd48","name":"","active":false,"console":"false","complete":"false","x":542,"y":774,"wires":[]},{"id":"19dc3dd8.e623c2","type":"mqtt in","z":"b432bbb7.4bcd48","name":"emon/bedroom/temperature","topic":"emon/bedroom/temperature","broker":"44985a1b.bb67a4","x":135,"y":827,"wires":[["5fc967af.a03698"]]},{"id":"5fc967af.a03698","type":"function","z":"b432bbb7.4bcd48","name":"Store T_bedroom","func":"// store the value\nvar t_bedroom = msg.payload;\nt_bedroom = t_bedroom / 100; // temp until all the RFM69Pi is sorted\nflow.set('t_bedroom',t_bedroom);\nmsg.payload = \"Bedroom:\" + t_bedroom;\nreturn msg;","outputs":1,"noerr":0,"x":363,"y":829,"wires":[["257b19aa.da84e6"]]},{"id":"257b19aa.da84e6","type":"debug","z":"b432bbb7.4bcd48","name":"","active":false,"console":"false","complete":"false","x":546,"y":829,"wires":[]},{"id":"d7346137.28cba","type":"function","z":"b432bbb7.4bcd48","name":"Check Status 1","func":"if (msg.statusCode == 200) { \n var stat = \"\";\n flow.set('pvostat','200'); // this clears the outstanding PVO data, so it won't be uploaded again\n var msg1 = null;\n var msg2 = null;\n} else {\n var time = new Date().toString();\n flow.set('pvostat', msg.statusCode);\n var stat = \"FAILED: Time:\" + time + \" StatusCode:\" + msg.statusCode + \" StatusMsg:\" + msg.payload;\n var msg1 = null;\n var msg2 = null;\n// msg1.payload = stat;\n// msg2.payload = stat;\n var msg1 = { payload: stat };\n var msg2 = { payload: stat };\n}\nreturn (msg1, msg2);","outputs":"2","noerr":0,"x":897,"y":153,"wires":[["baa7de09.45582","8d14e679.b29678"],["fe3a3389.3b9b2"]]},{"id":"baa7de09.45582","type":"debug","z":"b432bbb7.4bcd48","name":"Post dump1","active":true,"console":"false","complete":"payload","x":1094,"y":127,"wires":[]},{"id":"d27c9616.2d8368","type":"inject","z":"b432bbb7.4bcd48","name":"Every Midnight","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"59 23 * * *","once":false,"x":139,"y":199,"wires":[["71cd2d97.8e32d4"]]},{"id":"fe3a3389.3b9b2","type":"file","z":"b432bbb7.4bcd48","name":"PVO error log1","filename":"/media/usb/pvo_errors.log","appendNewline":true,"createDir":true,"overwriteFile":"false","x":1110,"y":215,"wires":[]},{"id":"e1d96b5f.bc14b8","type":"mqtt out","z":"b432bbb7.4bcd48","name":"emon/emontx/power-usage","topic":"emon/emontx/power-usage","qos":"0","retain":"false","broker":"44985a1b.bb67a4","x":539,"y":542,"wires":[]},{"id":"ac542fdb.fa626","type":"function","z":"b432bbb7.4bcd48","name":"Store PVO data","func":"//store the PVO data in case it doesn't upload correctly\nflow.set ('pvodata',msg.payload)\nreturn msg;","outputs":1,"noerr":0,"x":551,"y":192,"wires":[["524dc971.ccd6f8"]]},{"id":"ba0c10e4.0b273","type":"http request","z":"b432bbb7.4bcd48","name":"Post2","method":"POST","ret":"txt","url":"","tls":"","x":720.25,"y":314,"wires":[["7d37535e.3335ec","82678181.eee75"]]},{"id":"9f08c692.4c46a8","type":"function","z":"b432bbb7.4bcd48","name":"Set API key here 2","func":"msg.action = msg.payload;\nmsg.headers = { \n 'X-Pvoutput-Apikey': 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn',\n 'X-Pvoutput-SystemId': 'nnnn',\n 'Content-Type': 'application/x-www-form-urlencoded'\n};\n\nmsg.url = \"http://pvoutput.org/service/r2/addstatus.jsp\";\n\nreturn msg;","outputs":1,"noerr":0,"x":516.75,"y":312.5,"wires":[["ba0c10e4.0b273","a44906df.076a08"]]},{"id":"7d37535e.3335ec","type":"debug","z":"b432bbb7.4bcd48","name":"headercheck2","active":false,"console":"false","complete":"true","x":906,"y":314,"wires":[]},{"id":"b22c7668.760e98","type":"inject","z":"b432bbb7.4bcd48","name":"Check every 1 min","topic":"","payload":"","payloadType":"str","repeat":"60","crontab":"","once":false,"x":129,"y":317,"wires":[["b638e83a.a1c888"]]},{"id":"a44906df.076a08","type":"debug","z":"b432bbb7.4bcd48","name":"Debug2","active":true,"console":"true","complete":"payload","x":696,"y":357.5,"wires":[]},{"id":"82678181.eee75","type":"function","z":"b432bbb7.4bcd48","name":"Check Status 2 ","func":"if (msg.statusCode == 200) { \n var time = new Date().toString();\n var stat =\"\";\n flow.set('pvostat', 200); // this clears the outstanding PVO status, so it won't be uploaded again\n var stat = \"SENTOK: Time:\" + time + \" StatusCode:\" + msg.statusCode + \" StatusMsg:\" + msg.payload;\n// var msg1 = null;\n// var msg2 = null;\n var msg1 = { payload: stat };\n var msg2 = { payload: stat };\n} else {\n var time = new Date().toString();\n flow.set('pvostat', msg.statusCode);\n var stat = \"ReFAIL: Time:\" + time + \" StatusCode:\" + msg.statusCode + \" StatusMsg:\" + msg.payload;\n// var msg1 = null;\n// var msg2 = null;\n var msg1 = { payload: stat };\n var msg2 = { payload: stat };\n}\nreturn (msg1, msg2);","outputs":"2","noerr":0,"x":899,"y":354,"wires":[["41cf7f4a.279e2"],["30cf215d.ab8d0e"]]},{"id":"41cf7f4a.279e2","type":"debug","z":"b432bbb7.4bcd48","name":"Post dump2","active":true,"console":"false","complete":"payload","x":1085,"y":348,"wires":[]},{"id":"30cf215d.ab8d0e","type":"file","z":"b432bbb7.4bcd48","name":"PVO Error log2","filename":"/media/usb/pvo_errors.log","appendNewline":true,"createDir":true,"overwriteFile":"false","x":1100,"y":395,"wires":[]},{"id":"b638e83a.a1c888","type":"function","z":"b432bbb7.4bcd48","name":"Fetch PVO data","func":"//store the PVO data in case it doesn't upload correctly\nvar pvostat = flow.get('pvostat')||200;\nif (pvostat == 200) {\n msg = null;\n} else {\n var pvodata = flow.get('pvodata')||\"\";\n msg.payload = pvodata;\n}\nreturn msg;","outputs":1,"noerr":0,"x":327,"y":315,"wires":[["9f08c692.4c46a8"]]},{"id":"8d14e679.b29678","type":"file","z":"b432bbb7.4bcd48","name":"PVO Data","filename":"PVO_data.log","appendNewline":true,"createDir":true,"overwriteFile":"false","x":1094,"y":164,"wires":[]},{"id":"524dc971.ccd6f8","type":"debug","z":"b432bbb7.4bcd48","name":"Data stored","active":true,"console":"false","complete":"payload","x":724,"y":194,"wires":[]},{"id":"31959358.f4405c","type":"inject","z":"b432bbb7.4bcd48","name":"Clear error","topic":"","payload":"Clear","payloadType":"str","repeat":"","crontab":"","once":false,"x":866,"y":718,"wires":[["7da0c276.30da0c"]]},{"id":"7da0c276.30da0c","type":"function","z":"b432bbb7.4bcd48","name":"cleared","func":"flow.set('pvo', 200); // this clears the outstanding PVO upload error\nreturn msg;","outputs":1,"noerr":0,"x":1039,"y":717,"wires":[[]]},{"id":"2696b14a.d9694e","type":"comment","z":"b432bbb7.4bcd48","name":"Check data sent OK","info":"The PVO website is busy and sometimes can't respond quickly enough and we get timeouts.\nThe next bit of code catches any errors and resubm its the code until it's sent OK.\n\nThat said, if it can't resend the data before the next data packet has to be sent (5 mins) then\nthe current packet will be lost. This is pretty rare though and won't be noticable on the graphs.\n","x":106,"y":271,"wires":[]},{"id":"c7fda7ab.380258","type":"comment","z":"b432bbb7.4bcd48","name":"The Main Event","info":"This next big bit (esp. Store Power2) does the heavy lifting","x":101,"y":393,"wires":[]},{"id":"27535925.d8aca6","type":"comment","z":"b432bbb7.4bcd48","name":"Other data to be sent","info":"The boxes below pull in other data to be sent to PVO.\nAdjust to your needs, and amend the Set Up Data function","x":114,"y":593,"wires":[]},{"id":"18bb8ab1.e74475","type":"comment","z":"b432bbb7.4bcd48","name":"Debugging nodes","info":"","x":857,"y":662,"wires":[]},{"id":"e212dc69.1ded2","type":"comment","z":"b432bbb7.4bcd48","name":"Reset the counters at midnight","info":"This bit redaily sets the cumulative counters","x":887,"y":474,"wires":[]},{"id":"44985a1b.bb67a4","type":"mqtt-broker","z":"b432bbb7.4bcd48","broker":"localhost","port":"1883","clientid":"","usetls":false,"verifyservercert":true,"compatmode":true,"keepalive":"15","cleansession":true,"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":""}]