[{"id":"8cfcb1ff.b957e","type":"tab","label":"EV Charged Notification","disabled":false,"info":"# Appliance Notification\n\nThis is a vast derivation of the code originally found at the below. \n - [Complete instructions for **Google Home, Android & Windows**](https://notenoughtech.com/home-automation/washing-machine-notifications/)\n - [Complete instructions for **Alexa**](https://notenoughtech.com/home-automation/alexa-is-my-washing-ready/): \n\n# Settings\nSee Settings node in the setup menu - each setting had been explained in the comment.\n\n# Appliance data\nDepending on the MQTT message structure, you will need to adjust the power input to your needs. \n\n# Features\n\n**New & changed Stuff**:\n\n- Added debug code and nodes to help with testing\n- notifications also tell you power usage \n- added the ability to push to a csv file (unformatted and formatted times/costs/power)\n- made more generic for ANY appliance\n- notification tells how long appliance was going for as well as cost\n- removed any reference to the nagging until switched off. All my appliances stay on standby\n- uses power value from mqtt\n- most config is now in the settings function node, even for notifications\n- some efficencies, more still can be done\n- added Pushover notifications (push notification app for android and iphone)\n- lots more code comments\n- removed flow variables where context ones will do.\n\n**Original Features**:\n\n- Google Home random voice notifications (Change IP address for device) \n- DELETED: Android, Alexa, and Windows 10 Notifications \n- No timers, nothing to set up \n- DELETED: Nagging mode for Google Home \n- Cost of washing (supports multi tarifs) \n\n**Still to do**:\n\n- System currently relies on setting the right teleperiod or times that data gets pushed to match the code (eg 60 mins)\n- more efficient code in some places would be nice\n- Should really switch the appliance off when finished to save standby power (need an on switch somewhere)\n"},{"id":"3260cfab.0f6d8","type":"group","z":"8cfcb1ff.b957e","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["91609baf.a3dae8","a369b5db.c72258","273b0c2b.3d3bd4","244dd341.1a034c","2316932d.1d489c"],"x":874,"y":79,"w":452,"h":202},{"id":"85bfd9df.f70e58","type":"group","z":"8cfcb1ff.b957e","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["72f9c1dd.60ad","2fff0056.25422","18a3cba.181d334","4ef56ac1.b18cb4","dd25bb24.d87028","99466a15.949de8","86042560.10bc58"],"x":874,"y":611,"w":472,"h":322},{"id":"4219661b.098d48","type":"group","z":"8cfcb1ff.b957e","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["e007722f.02bb","be6a8a99.0b0958","af82b5e4.2f41d8","ddd5d398.9df2a","4baf4cc1.87d384","5e5a02a3.cc51dc","b468246f.61ebf8"],"x":874,"y":299,"w":452,"h":302},{"id":"7e5a8959.71df28","type":"group","z":"8cfcb1ff.b957e","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["bc300e6c.85029","7b940ad4.7685e4","6b546f5a.74bbe","686cd8f1.f0fd28","d1ec5ba5.d7eeb8","3f6a0b44.15f7c4","369309f5.85c146","48013a77.fa2f14"],"x":36,"y":170,"w":452,"h":282},{"id":"396b9814.6875d8","type":"group","z":"8cfcb1ff.b957e","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["aa2c31f8.51871","7b713146.b36f5","1348c522.0f2c8b","18fe6e4f.4a1072","bfb51512.055008","59009dab.41fda4","3ad3c9fb.548a86","965e8b4.c68b778","4770b186.3f53c"],"x":34,"y":459,"w":712,"h":462},{"id":"e932d4ab.b99c08","type":"function","z":"8cfcb1ff.b957e","name":"Main Loop","func":"// -----------------------------------------\n// Notify and store power use and cost for an appliance\n// MAIN LOOP\n// Full loop to calculate power average of appliance and \n// work out when it is operating. When operational\n// fill arrays with power used, and cost of power calculated.\n// -----------------------------------------\n// 2021-04-12 V1.3 - zorruno\n// - Fixed loop not detecting Finished-> Standby \n// - Moved some flow variables to context\n// - Changed csv filename to reflect appliance\n// -----------------------------------------\n\n// The input message must be the current power in Watts\nvar power = msg.payload;\n\n// Get flow user settings variables into local variables\nvar res = flow.get(\"resolution\");\nvar tariff = flow.get(\"tariff\");\nvar metricsf = flow.get(\"metricFrequency\");\nvar standby = flow.get(\"standbyPower\");\nvar opPowerMin = flow.get(\"operatingPowerMinium\");\nvar currentCycle = flow.get(\"currentApplianceCycle\");\n\n// Get flow variables into local variables\nvar operation = context.get(\"operation\") || \"Off\";\nvar recentPowerArray = flow.get(\"recentPowerArray\") || [0];\nvar cycleCostArray = flow.get(\"cycleCostArray\") || [0];\nvar cyclePowerArray = flow.get(\"cyclePowerArray\") || [0];\n\n// Get date, seconds and hours\nvar date = new Date();\nvar dateS = date.getTime()/1000;\nvar hour = date.getHours();\n\n// -----------------------------------------\n// Fill power array, and calculate average \n// power. Do this on every loop.\n// -----------------------------------------\n\n// Function add for reduce array\nfunction add(accumulator, a) {\n return accumulator + a;\n}\n// Push power into TotalPower array for average\nrecentPowerArray.unshift(power);\n// Remove X element to get total resolution for average calc\nif(recentPowerArray[res] === undefined) {\n flow.set(\"recentPowerArray\", recentPowerArray);\n}\nelse {\n recentPowerArray.splice(res, 1);\n flow.set(\"recentPowerArray\", recentPowerArray);\n}\n// Calculate average power from array\nvar sum = recentPowerArray;\nvar average = (sum.reduce(add)/recentPowerArray.length); // Average the array\n\n\n// -----------------------------------------\n// Fill cycle power array, and calculate average power.\n// Only do this when cycle is occurring.\n// Note this method doesn't capture EVERY data point - \n// we'll miss the first few as we are calculating an \n// average. We could capture more, but that is less efficient.\n// -----------------------------------------\nif(operation === \"Operating\"){\n // Push watthours into cyclePowerArray for cycle\n var wattHoursNow = power / ( 60 * ( 60 / metricsf ));\n cyclePowerArray.push(wattHoursNow);\n flow.set(\"cyclePowerArray\", cyclePowerArray);\n\n // Calculate the cost of power\n var price;\n if( hour >= tariff.start && hour < tariff.end ){\n price = tariff.costDay; // Apply day tariff\n }\n if( hour < tariff.start || hour >= tariff.end ){\n price = tariff.costNight; // Apply night tariff\n }\n\n // Fill cycleCostArray\n var costPerMinute = power/1000 * price / (60* (60/metricsf));\n cycleCostArray.push(costPerMinute);\n flow.set(\"cycleCostArray\", cycleCostArray); // Add to cost array\n}\n\n// -----------------------------------------\n// Appliance is off\n// -----------------------------------------\n//if(average === 0){\nif(average < standby){\n context.set(\"operation\", \"Off\");\n}\n\n// -----------------------------------------\n// Appliance has gone into Standby from Off\n// -----------------------------------------\nif(average >= standby && operation === \"Off\"){\n context.set(\"operation\", \"Standby\");\n}\n\n// -----------------------------------------\n// Appliance has gone into Standby from Finished\n// -----------------------------------------\nif(average >= standby && operation === \"Finished\"){\n context.set(\"operation\", \"Standby\");\n}\n\n// -----------------------------------------\n// Appliance has started its Operating cycle \n// from Standby or Off\n// -----------------------------------------\nif((average > opPowerMin && operation === \"Standby\") || (average > opPowerMin && operation === \"Off\")){\n context.set(\"operation\", \"Operating\");\n currentCycle.cycleTimeStart = dateS;\n cycleCostArray = [0]; // Clear array to start cycle\n flow.set(\"cycleCostArray\",cycleCostArray);\n cyclePowerArray = [0]; // Clear array to start cycle\n flow.set(\"cyclePowerArray\",cyclePowerArray);\n}\n\n// -----------------------------------------\n// Appliance was in Operating cycle, \n// but now has Finished\n// -----------------------------------------\nif(average < opPowerMin && operation === \"Operating\"){\n context.set(\"operation\", \"Finished\");\n currentCycle.cycleTimeStop = dateS;\n\n // Calculate & format total cost of the entire cycle\n var sumCost = flow.get(\"cycleCostArray\");\n var costOfPower = sumCost.reduce(add);\n currentCycle.totalCycleCostDollars = costOfPower;\n \n // Format as $ or cents\n if ( costOfPower < 0.01 ){\n costOfPower = '<1c';\n } else if ( costOfPower < 1){\n costOfPower = costOfPower * 100;\n costOfPower = Math.round(costOfPower);\n costOfPower = costOfPower.toString() + 'c';\n } else {\n costOfPower = costOfPower.toFixed(2);\n costOfPower = '$' + costOfPower.toString();\n }\n currentCycle.totalCycleCostFormatted = costOfPower ;\n\n // Calculate & format total power use of the entire cycle\n var sumPower = flow.get(\"cyclePowerArray\");\n var sumOfPower = sumPower.reduce(add);\n currentCycle.totalCyclePowerWattHours = sumOfPower;\n\n // Format as wH or kWh\n if ( sumOfPower >= 1000 ){\n sumOfPower = sumOfPower / 1000;\n sumOfPower = sumOfPower.toFixed(1); // 1 decimal place\n sumOfPower = sumOfPower.toString() + 'kWh';\n } else if ( costOfPower < 1){\n costOfPower = '<1Wh';\n } else {\n sumOfPower = sumOfPower.toFixed(1); // 1 decimal place\n sumOfPower = sumOfPower.toString() + 'Wh';\n }\n\n currentCycle.totalCyclePowerFormatted = sumOfPower ;\n\n msg.payload = context.get(\"operation\") ;\n return [msg,msg] \n}\n\n\n// -----------------------------------------\n// Output debug stuff on each loop\n// -----------------------------------------\nif(flow.get(\"debugFlow\") === true){\n\n flow.set(\"debugAverage\",average) ;\n\n // Calculate total cost of the entire cycle so far\n var costSoFar = flow.get(\"cycleCostArray\").reduce(add);\n flow.set(\"debugCostSoFar\",costSoFar) ;\n\n // Calculate total power use of the entire cycle so far\n var powerSoFar = flow.get(\"cyclePowerArray\").reduce(add);\n flow.set(\"debugPowerSoFar\",powerSoFar) ;\n\n flow.set(\"debugCyclePowerArray\",cyclePowerArray) ;\n flow.set(\"debugCycleCostArray\",cycleCostArray) ;\n flow.set(\"debugRecentPowerArray\",recentPowerArray) ;\n\n msg.payload = context.get(\"operation\") ;\n return [null,msg]\n}\n\n\n \n","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":670,"y":300,"wires":[["233b1321.0d520c"],["59009dab.41fda4"]]},{"id":"eab8b087.e8911","type":"comment","z":"8cfcb1ff.b957e","name":"Settings","info":"","x":120,"y":80,"wires":[]},{"id":"57b444a9.248eac","type":"function","z":"8cfcb1ff.b957e","name":"Settings","func":"//see Setup tab\n","outputs":1,"noerr":0,"initialize":"// -----------------------------------------\n// Notify and store power use and cost for an appliance\n// FLOW VARIABLES\n// Full loop to calculate power average of appliance and \n// work out when it is operating. When operational\n// fill arrays with power used, and cost of power calculated.\n// -----------------------------------------\n// 2021-04-12 V1.3 - zorruno\n// - Fixed loop not detecting Finished-> Standby \n// - Moved some flow variables to context\n// - Changed csv filename to reflect appliance\n// -----------------------------------------\n\n// Debugging nodes\nflow.set(\"debugFlow\", false);\n\n// Appliance Names (for notifications)\n// ie. \"your ApplianceName is ApplianceAction\"\nflow.set(\"applianceName\", \"EV Charger\"); \nflow.set(\"applianceAction\", \"charging\"); \n\n// Announcement text (for voice notifications if used)\n// these are randomised later.\nflow.set(\"voiceAnnouncements\", [\n \"Hey, the e v charging is now complete\",\n \"Hey, your charging is complete\",\n \"The car charging is done\",\n \"The e v charging has finished\",\n \"E V Charging has now finished\",\n ]);\n\n\n// Electrical tariff info \n// Updated with Auckland Contact Energy Costs, Feb 2021\n// cost is in cents per kWh and start and end are the times\n// to change tariffs.\nvar tariff = {\"costDay\": 0.2023, \n \"costNight\": 0.0,\n \"start\": 0,\n \"end\": 21};\n\n//---------------------------------------\n// Appliance power settings\n//---------------------------------------\n\n// Average power in standby mode (it will show off if average\n// power is above this and below the operatingPowerMinimum)\n// Currently this just affects showing 'standby' vs 'off'\n// so actual value is not critical.\nflow.set(\"standbyPower\", 2); \n// Minimum power when actually operating.\n// This is averaged over 'resolution' values, so no problem\n// if it drops below this value at times during operation.\nflow.set(\"operatingPowerMinium\", 100); \n// How many times to do a power reading for rolling average.\nflow.set(\"resolution\", 6); \n// How often does the appliance report back (seconds)\nflow.set(\"metricFrequency\", 60); \n\n//---------------------------------------\n// No need to change these \n//---------------------------------------\nflow.set(\"tariff\", tariff);\n\n//flow.set(\"operation\", \"Off\");\nflow.set(\"recentPowerArray\", [0]);\nflow.set(\"cycleCostArray\", [0]);\nflow.set(\"cyclePowerArray\", [0]);\n\nvar cycle = { \"cycleTimeStart\": null,\n \"cycleTimeStop\": null,\n \"totalCycleCostFormatted\": 0,\n \"totalCycleCostDollars\": 0,\n \"totalCyclePowerFormatted\": 0,\n \"totalCyclePowerWattHours\": 0,\n }\n \nflow.set(\"currentApplianceCycle\",cycle);\n","finalize":"","libs":[],"x":120,"y":120,"wires":[[]]},{"id":"e49748b3.532cc8","type":"mqtt in","z":"8cfcb1ff.b957e","name":"Appliance Data","topic":"stat/tasmo-wemosd1-7280-powermon-1/EnergyMeterCount","qos":"0","datatype":"json","broker":"e28b763a.77bd98","inputs":0,"x":300,"y":120,"wires":[["279aedc1.461022","8978c85f.c239c8"]]},{"id":"233b1321.0d520c","type":"link out","z":"8cfcb1ff.b957e","name":"","links":["244dd341.1a034c","559a8885.34c1e8","4baf4cc1.87d384","943923e2ca4ccb1e"],"x":795,"y":260,"wires":[]},{"id":"279aedc1.461022","type":"debug","z":"8cfcb1ff.b957e","name":"","active":false,"tosidebar":true,"console":true,"tostatus":true,"complete":"payload.EVChargerWhCount","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":480,"y":71,"wires":[]},{"id":"aa2c31f8.51871","type":"debug","z":"8cfcb1ff.b957e","g":"396b9814.6875d8","name":"Cycle: Cost Array","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"$flowContext('debugCycleCostArray')\t","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":550,"y":820,"wires":[]},{"id":"7b713146.b36f5","type":"debug","z":"8cfcb1ff.b957e","g":"396b9814.6875d8","name":"Recent Power Readings","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"$flowContext('debugRecentPowerArray')\t","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":580,"y":880,"wires":[]},{"id":"4853b668.0ee738","type":"comment","z":"8cfcb1ff.b957e","name":"Operation","info":"","x":660,"y":260,"wires":[]},{"id":"eb3c3f47.e1d9b","type":"comment","z":"8cfcb1ff.b957e","name":"Notifications","info":"","x":946,"y":55,"wires":[]},{"id":"72f9c1dd.60ad","type":"cast-to-client","z":"8cfcb1ff.b957e","g":"85bfd9df.f70e58","name":"Notify A Google Home","url":"","contentType":"","message":"","language":"","ip":"","port":"","volume":"50","x":1220,"y":892,"wires":[[]]},{"id":"2fff0056.25422","type":"play audio","z":"8cfcb1ff.b957e","g":"85bfd9df.f70e58","name":"","voice":"2","x":1060,"y":752,"wires":[]},{"id":"18a3cba.181d334","type":"function","z":"8cfcb1ff.b957e","g":"85bfd9df.f70e58","name":"Random Notification Selection","func":"// Get a set of statements and push one at random back to\n// the payload. An example for a washing machine is below\n//\n// var announce = [\n// \"The washing machine is now complete\",\n// \"Your washing is complete\",\n// \"The washing is done\",\n// \"The washing machine has finished\",\n// \"Washing has now finished\",\n// ];\n \nvar announce = flow.get(\"voiceAnnouncements\");\n \nvar random = announce[Math.floor(Math.random() * announce.length)];\nmsg.payload = random;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1190,"y":712,"wires":[["2fff0056.25422","4ef56ac1.b18cb4"]]},{"id":"4ef56ac1.b18cb4","type":"change","z":"8cfcb1ff.b957e","g":"85bfd9df.f70e58","name":"Google Home Notification Details","rules":[{"t":"set","p":"ip","pt":"msg","to":"192.168.2.97","tot":"str"},{"t":"set","p":"language","pt":"msg","to":"En-gb","tot":"str"},{"t":"set","p":"volume","pt":"msg","to":"80","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1180,"y":832,"wires":[["72f9c1dd.60ad"]]},{"id":"dd25bb24.d87028","type":"link in","z":"8cfcb1ff.b957e","g":"85bfd9df.f70e58","name":"Appliance Audio Announcements","links":["82804458c4c01c96"],"x":915,"y":712,"wires":[["18a3cba.181d334"]]},{"id":"99466a15.949de8","type":"inject","z":"8cfcb1ff.b957e","g":"85bfd9df.f70e58","name":"Test Notification","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1240,"y":652,"wires":[["18a3cba.181d334"]]},{"id":"91609baf.a3dae8","type":"pushover","z":"8cfcb1ff.b957e","g":"3260cfab.0f6d8","name":"Pushover Phone Message","device":"MiZor","title":"","priority":0,"sound":"classical","url":"","url_title":"","html":false,"x":1180,"y":240,"wires":[]},{"id":"a369b5db.c72258","type":"function","z":"8cfcb1ff.b957e","g":"3260cfab.0f6d8","name":"Notification Text","func":"// Get Current Cycle Symmary Array\nvar currentCycle = flow.get(\"currentApplianceCycle\");\n\nvar date = new Date();\nmsg.payload = {};\n\n// Function to convert unix epoch to X hours and Y mins\n// and format as 'Xh Xmin'. Drop the hours if less than 1.\nfunction secondsToHms(d) {\n d = Number(d);\n var h = Math.floor(d / 3600);\n var m = Math.floor(d % 3600 / 60);\n if (h < 1 ) {\n return ('0' + m).slice(-2)+\"min\";\n }\n else {\n return ('0' + h).slice(-2) + \"h \" + ('0' + m).slice(-2)+\"min\";\n }\n}\n\n// Function to convert unix epoch to 00:00\nfunction epochToFormattedTime(d) {\n d = Date(d * 1000);\n var h = d.gethours();\n var m = \"0\" + d.getminutes();\n return h + ':' + m.substr(-2);\n}\n\n\n// Extract start/stop time based on the unix timestamp\n// First multiply by 1000 so that the argument is in milliseconds, not seconds.\nvar startDateTime = new Date(currentCycle.cycleTimeStart * 1000);\nvar stopDateTime = new Date(currentCycle.cycleTimeStop * 1000);\nvar startDateHours = \"0\" + startDateTime.getHours();\nvar stopDateHours = \"0\" + stopDateTime.getHours();\nvar startDateMinutes = \"0\" + startDateTime.getMinutes();\nvar stopDateMinutes = \"0\" + stopDateTime.getMinutes();\n\n// Format times\n// Will display time in 10:30 format (no seconds)\nvar formattedStartTime = startDateHours.substr(-2) + ':' + startDateMinutes.substr(-2)\nvar formattedStopTime = stopDateHours.substr(-2) + ':' + stopDateMinutes.substr(-2)\n\n// Put together a notification ready for sending to notifying tools\nmsg.topic = flow.get(\"applianceName\") + \" Notification\";\nmsg.payload = \"Your \" + flow.get(\"applianceAction\") + \n \" is complete.\\nIt started at \" + formattedStartTime + \n \", finished at \" + formattedStopTime + \n \", and used \" + currentCycle.totalCyclePowerFormatted + \n \", taking \" + secondsToHms(currentCycle.cycleTimeStop - currentCycle.cycleTimeStart) + \n \" at a cost of \" + currentCycle.totalCycleCostFormatted;\n\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1220,"y":180,"wires":[["91609baf.a3dae8"]]},{"id":"273b0c2b.3d3bd4","type":"inject","z":"8cfcb1ff.b957e","g":"3260cfab.0f6d8","name":"Test Notification","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1220,"y":120,"wires":[["a369b5db.c72258"]]},{"id":"244dd341.1a034c","type":"link in","z":"8cfcb1ff.b957e","g":"3260cfab.0f6d8","name":"Appliance Pushover Notification","links":["233b1321.0d520c"],"x":915,"y":180,"wires":[["a369b5db.c72258"]]},{"id":"bc300e6c.85029","type":"inject","z":"8cfcb1ff.b957e","g":"7e5a8959.71df28","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1800","payloadType":"num","x":132,"y":291,"wires":[["369309f5.85c146"]]},{"id":"7b940ad4.7685e4","type":"debug","z":"8cfcb1ff.b957e","g":"7e5a8959.71df28","name":"","active":false,"tosidebar":true,"console":true,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":362,"y":351,"wires":[]},{"id":"6b546f5a.74bbe","type":"inject","z":"8cfcb1ff.b957e","g":"7e5a8959.71df28","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"300","payloadType":"num","x":132,"y":331,"wires":[["369309f5.85c146"]]},{"id":"686cd8f1.f0fd28","type":"inject","z":"8cfcb1ff.b957e","g":"7e5a8959.71df28","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"30","payloadType":"num","x":132,"y":371,"wires":[["369309f5.85c146"]]},{"id":"d1ec5ba5.d7eeb8","type":"comment","z":"8cfcb1ff.b957e","g":"7e5a8959.71df28","name":"Inject Values For Testing Only","info":"Can be used to generate test power values into the input","x":182,"y":211,"wires":[]},{"id":"8978c85f.c239c8","type":"function","z":"8cfcb1ff.b957e","name":"Convert to Watts","func":"// We want the msg.payload to be the power now in Watts.\n// in this case, the payload is a count from a check meter\n// This check meter gives 2Wh per pulse so to convert to Watts\n// we multiply the count by two and multiply by 60 (minutes)\n\nmsg.payload = msg.payload.EVChargerWhCount;\nmsg.payload = msg.payload * 2 * 60;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":500,"y":120,"wires":[["e932d4ab.b99c08","5b6b9f9e.c16d9"]]},{"id":"5b6b9f9e.c16d9","type":"debug","z":"8cfcb1ff.b957e","name":"","active":false,"tosidebar":true,"console":true,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":680,"y":71,"wires":[]},{"id":"1348c522.0f2c8b","type":"debug","z":"8cfcb1ff.b957e","g":"396b9814.6875d8","name":"Cycle: Power Array","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"$flowContext('debugCyclePowerArray')\t","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":560,"y":760,"wires":[]},{"id":"5bfac55b.863d9c","type":"comment","z":"8cfcb1ff.b957e","name":"Power Input","info":"","x":290,"y":80,"wires":[]},{"id":"18fe6e4f.4a1072","type":"comment","z":"8cfcb1ff.b957e","g":"396b9814.6875d8","name":"If Debugging On","info":"","x":140,"y":500,"wires":[]},{"id":"e007722f.02bb","type":"file","z":"8cfcb1ff.b957e","g":"4219661b.098d48","name":"Push to CSV File","filename":"","appendNewline":true,"createDir":true,"overwriteFile":"false","encoding":"none","x":1210,"y":500,"wires":[["ddd5d398.9df2a"]]},{"id":"be6a8a99.0b0958","type":"function","z":"8cfcb1ff.b957e","g":"4219661b.098d48","name":"Data Manipulation for CSV File","func":"// Get Current Cycle Symmary Array\nvar currentCycle = flow.get(\"currentApplianceCycle\");\n\nvar date = new Date();\nmsg.payload = {};\n\n// Function to convert unix epoch to X hours and Y mins\n// and format as 'Xh Xmin'. Drop the hours if less than 1.\nfunction secondsToHms(d) {\n d = Number(d);\n var h = Math.floor(d / 3600);\n var m = Math.floor(d % 3600 / 60);\n if (h < 1 ) { \n return ('0' + m).slice(-2)+\"min\"; \n }\n else {\n return ('0' + h).slice(-2) + \"h \" + ('0' + m).slice(-2)+\"min\"; \n }\n}\n\n// Function to convert unix epoch to 00:00\nfunction epochToFormattedTime(d) {\n d = Date(d * 1000);\n var h = d.gethours();\n var m = \"0\" + d.getminutes();\n return h + ':' + m.substr(-2);\n}\n\n\n// Extract start/stop time based on the unix timestamp\n// First multiply by 1000 so that the argument is in milliseconds, not seconds.\nvar startDateTime = new Date(currentCycle.cycleTimeStart * 1000);\nvar stopDateTime = new Date(currentCycle.cycleTimeStop * 1000);\nvar startDateHours = startDateTime.getHours();\nvar stopDateHours = stopDateTime.getHours();\nvar startDateMinutes = \"0\" + startDateTime.getMinutes();\nvar stopDateMinutes = \"0\" + stopDateTime.getMinutes();\n\n// Format times\n// Will display time in 10:30 format (no seconds)\nvar formattedStartTime = startDateHours + ':' + startDateMinutes.substr(-2)\nvar formattedStopTime = stopDateHours + ':' + stopDateMinutes.substr(-2)\n\ncsvPayload={\n \"date/time\": date,\n \"Appliance\": flow.get(\"applianceName\"), \n \"Start Time\": formattedStartTime, \n \"Finish Time\": formattedStopTime,\n \"Cycle Time Formatted\": secondsToHms(currentCycle.cycleTimeStop - currentCycle.cycleTimeStart), \n \"Cycle Time (s)\": currentCycle.cycleTimeStop - currentCycle.cycleTimeStart, \n \"Cycle Power Use Formatted\": currentCycle.totalCyclePowerFormatted, \n \"Cycle Power Use (Wh)\": currentCycle.totalCyclePowerWattHours, \n \"Cycle Cost Formatted\": currentCycle.totalCycleCostFormatted,\n \"Cycle Cost ($)\": currentCycle.totalCycleCostDollars,\n}\n\nmsg.payload=csvPayload;\nvar filename = flow.get(\"applianceName\"); \nmsg.filename = \"/data/csvlogs/\" + filename.replace(/\\s+/g, '') + \"_poweruse.csv\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1170,"y":400,"wires":[["af82b5e4.2f41d8"]]},{"id":"af82b5e4.2f41d8","type":"json","z":"8cfcb1ff.b957e","g":"4219661b.098d48","name":"Convert to JSON","property":"payload","action":"","pretty":false,"x":1210,"y":450,"wires":[["e007722f.02bb"]]},{"id":"ddd5d398.9df2a","type":"debug","z":"8cfcb1ff.b957e","g":"4219661b.098d48","name":"debug","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":1180,"y":560,"wires":[]},{"id":"4baf4cc1.87d384","type":"link in","z":"8cfcb1ff.b957e","g":"4219661b.098d48","name":"Appliance History CSV File Creation","links":["233b1321.0d520c"],"x":915,"y":400,"wires":[["be6a8a99.0b0958"]]},{"id":"5e5a02a3.cc51dc","type":"inject","z":"8cfcb1ff.b957e","g":"4219661b.098d48","name":"Test Notification","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1220,"y":340,"wires":[["be6a8a99.0b0958"]]},{"id":"3f6a0b44.15f7c4","type":"inject","z":"8cfcb1ff.b957e","g":"7e5a8959.71df28","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"3265","payloadType":"num","x":132,"y":251,"wires":[["369309f5.85c146"]]},{"id":"369309f5.85c146","type":"change","z":"8cfcb1ff.b957e","g":"7e5a8959.71df28","name":"Passthrough","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":352,"y":291,"wires":[["7b940ad4.7685e4","e932d4ab.b99c08"]]},{"id":"48013a77.fa2f14","type":"inject","z":"8cfcb1ff.b957e","g":"7e5a8959.71df28","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":132,"y":411,"wires":[["369309f5.85c146"]]},{"id":"2316932d.1d489c","type":"comment","z":"8cfcb1ff.b957e","g":"3260cfab.0f6d8","name":"Pushover App","info":"","x":970,"y":120,"wires":[]},{"id":"86042560.10bc58","type":"comment","z":"8cfcb1ff.b957e","g":"85bfd9df.f70e58","name":"Google Home Announce","info":"","x":1010,"y":652,"wires":[]},{"id":"b468246f.61ebf8","type":"comment","z":"8cfcb1ff.b957e","g":"4219661b.098d48","name":"Data to CSV File","info":"","x":980,"y":340,"wires":[]},{"id":"bfb51512.055008","type":"debug","z":"8cfcb1ff.b957e","g":"396b9814.6875d8","name":"Debug: Cost So Far","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"$flowContext('debugCostSoFar')","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":560,"y":640,"wires":[]},{"id":"59009dab.41fda4","type":"change","z":"8cfcb1ff.b957e","g":"396b9814.6875d8","name":"Passthrough","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":190,"y":720,"wires":[["bfb51512.055008","3ad3c9fb.548a86","965e8b4.c68b778","1348c522.0f2c8b","aa2c31f8.51871","7b713146.b36f5","4770b186.3f53c"]]},{"id":"3ad3c9fb.548a86","type":"debug","z":"8cfcb1ff.b957e","g":"396b9814.6875d8","name":"Debug: Power So Far","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"$flowContext('debugPowerSoFar')","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":570,"y":700,"wires":[]},{"id":"965e8b4.c68b778","type":"debug","z":"8cfcb1ff.b957e","g":"396b9814.6875d8","name":"Debug: Average Now","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"$flowContext('debugAverage')","targetType":"jsonata","statusVal":"payload","statusType":"auto","x":570,"y":580,"wires":[]},{"id":"4770b186.3f53c","type":"debug","z":"8cfcb1ff.b957e","g":"396b9814.6875d8","name":"Operation","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":530,"y":520,"wires":[]},{"id":"e28b763a.77bd98","type":"mqtt-broker","name":"Panda","broker":"192.168.3.200","port":"1883","clientid":"","autoConnect":true,"usetls":false,"compatmode":false,"protocolVersion":4,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]