[{"id":"0e9dd00a8d8d887e","type":"api-call-service","z":"69b1916de598283b","name":"HWC Turn ON","server":"e57dff95.0a006","version":7,"debugenabled":true,"action":"switch.turn_on","floorId":[],"areaId":["hallway"],"deviceId":["4c438143908382ce2db0f6f0a2fecdb2"],"entityId":["switch.hwc_esp32_hot_water_cylinder_switch"],"labelId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"2","valueType":"num"},{"property":"in_charge_cycle_now","propertyType":"flow","value":"true","valueType":"bool"},{"property":"transition","propertyType":"flow","value":"2","valueType":"num"}],"queue":"none","blockInputOverrides":false,"domain":"switch","service":"turn_on","x":780,"y":180,"wires":[["aa1c04e5c4a1eeef"]]},{"id":"6e3d3be2e8bf77f5","type":"inject","z":"69b1916de598283b","name":"End","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"30 17 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"3","payloadType":"num","x":90,"y":360,"wires":[["0ff590f42cfe4163"]]},{"id":"01fd5e8a7040f5ae","type":"api-call-service","z":"69b1916de598283b","name":"HWC Turn OFF","server":"e57dff95.0a006","version":7,"debugenabled":false,"action":"switch.turn_off","floorId":[],"areaId":["hallway"],"deviceId":["4c438143908382ce2db0f6f0a2fecdb2"],"entityId":["switch.hwc_esp32_hot_water_cylinder_switch"],"labelId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"5","valueType":"num"},{"property":"in_charge_cycle_now","propertyType":"flow","value":"false","valueType":"bool"},{"property":"transition","propertyType":"flow","value":"5","valueType":"num"}],"queue":"none","blockInputOverrides":false,"domain":"switch","service":"turn_off","x":540,"y":360,"wires":[["aa1c04e5c4a1eeef"]]},{"id":"89ab4b0a8e433244","type":"change","z":"69b1916de598283b","name":"Set Var","rules":[{"t":"set","p":"onCounter","pt":"flow","to":"0","tot":"num"},{"t":"set","p":"surplus_continue","pt":"flow","to":"3200","tot":"num"},{"t":"set","p":"surplus_startup","pt":"flow","to":"3000","tot":"num"},{"t":"set","p":"surplus_cutoff","pt":"flow","to":"2000","tot":"num"},{"t":"set","p":"surplus","pt":"flow","to":"0","tot":"num"},{"t":"set","p":"began_charging_today","pt":"flow","to":"false","tot":"bool"},{"t":"set","p":"in_charge_cycle_now","pt":"flow","to":"false","tot":"bool"},{"t":"set","p":"Hours_on_today","pt":"global","to":"0","tot":"num"},{"t":"set","p":"Minutes_on_today","pt":"global","to":"0","tot":"num"},{"t":"set","p":"Seconds_on_today","pt":"global","to":"0","tot":"num"},{"t":"set","p":"HWC_daily_duration","pt":"flow","to":"0.0","tot":"num"},{"t":"set","p":"payload","pt":"msg","to":"1","tot":"num"},{"t":"set","p":"transition","pt":"flow","to":"1","tot":"num"},{"t":"set","p":"HWC_on_time","pt":"flow","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":240,"y":40,"wires":[["77f569b753cc29b5","aa1c04e5c4a1eeef"]]},{"id":"77f569b753cc29b5","type":"time-range-switch","z":"69b1916de598283b","name":"","lat":"","lon":"","startTime":"8:59","endTime":"16:59","startOffset":0,"endOffset":0,"x":430,"y":40,"wires":[["662063441610883a"],["25091c5f11988aca"]]},{"id":"24f6996466429cf1","type":"delay","z":"69b1916de598283b","name":"","pauseType":"delay","timeout":"2","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":800,"y":60,"wires":[["147211fd68b918fe"]]},{"id":"022fe8ce3ede0891","type":"switch","z":"69b1916de598283b","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"surplus_startup","vt":"flow"},{"t":"lte","v":"surplus_startup","vt":"flow"}],"checkall":"true","repair":false,"outputs":2,"x":270,"y":140,"wires":[["da38370a19569cef"],["77f569b753cc29b5"]]},{"id":"da38370a19569cef","type":"change","z":"69b1916de598283b","name":"begin charge true","rules":[{"t":"set","p":"began_charging_today","pt":"flow","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":180,"wires":[["0e9dd00a8d8d887e"]]},{"id":"0ff590f42cfe4163","type":"change","z":"69b1916de598283b","name":"begin charge false","rules":[{"t":"set","p":"began_charging_today","pt":"flow","to":"false","tot":"bool"},{"t":"set","p":"in_charge_cycle_now","pt":"flow","to":"false","tot":"bool"},{"t":"set","p":"payload","pt":"msg","to":"3","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":290,"y":360,"wires":[["aa1c04e5c4a1eeef","01fd5e8a7040f5ae"]]},{"id":"aa1c04e5c4a1eeef","type":"state-machine","z":"69b1916de598283b","name":"State Generator","triggerProperty":"payload","triggerPropertyType":"msg","stateProperty":"payload","statePropertyType":"msg","initialDelay":"0","persistOnReload":true,"outputStateChangeOnly":false,"throwException":false,"states":["evening","morning","HWC_ON","HWC_OFF"],"transitions":[{"name":"1","from":"evening","to":"morning"},{"name":"2","from":"morning","to":"HWC_ON"},{"name":"3","from":"HWC_ON","to":"HWC_OFF"},{"name":"4","from":"HWC_OFF","to":"HWC_ON"},{"name":"5","from":"HWC_OFF","to":"evening"},{"name":"6","from":"morning","to":"evening"},{"name":"7","from":"morning","to":"morning"},{"name":"8","from":"HWC_OFF","to":"HWC_OFF"}],"x":420,"y":520,"wires":[["7d635f678f04f984"]]},{"id":"7d635f678f04f984","type":"function","z":"69b1916de598283b","name":"timing","func":"var system_state = msg.payload; // Example variable assignment\n\nswitch (system_state) {\n    case \"evening\":\n        msg.topic = \"Transition to evening\"\n        var oncycles = flow.get(\"onCounter\");\n        var totalSecs = flow.get(\"HWC_daily_duration\");\n        var hours = Math.floor(totalSecs/3600.0);\n        var minutes = Math.floor((totalSecs % 3600) / 60);\n        var seconds = Math.floor(totalSecs % 60);\n        global.set(\"Hours_on_today\", hours);\n        global.set(\"Minutes_on_today\", minutes);\n        global.set(\"Seconds_on_today\", seconds);\n        msg = { payload: {\n            \"HWC On Time Today Hours\": hours ,\n            \"HWC On Time Today Minutes\": minutes ,\n            \"HWC On Time Today Seconds\": seconds,\n            \"HWC Relay cycles Today\": oncycles, \n        }, topic: msg.topic };\n        //could test for dark days here\n        //if begin_charge_today == false then do something\n        break;\n    case \"morning\":\n        msg.topic = \"Transitioning to morning\"\n        //Do nothing counters already set\n        break;\n    case \"HWC_ON\":\n        msg.topic = \"HWC has been turned on\"\n        //initialise the timepoint when HWC switched on\n        var d=new Date();\n        flow.set(\"HWC_on_time\", d.getTime()/1000.0 );\n        //increment the HWC relay counter\n        flow.set(\"onCounter\", flow.get(\"onCounter\")+1);\n        break;\n    case \"HWC_OFF\":\n        if ( flow.get(\"transition\") == 4 ) {\n            msg.topic = \"HWC has been turned off\"\n            //measure seconds between on and off time\n            var now=new Date();\n            var cycle_duration = (now.getTime() / 1000.0) - flow.get(\"HWC_on_time\");\n            var total_ontime = flow.get(\"HWC_daily_duration\") + cycle_duration; \n            flow.set(\"HWC_daily_duration\", total_ontime);\n        }\n        break;\n    default:\n    break;\n        //System.out.println(\"Invalid switch state\");\n}\n\n\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":570,"y":520,"wires":[["55da49120d775633"]]},{"id":"58cd72b54e9659ea","type":"api-call-service","z":"69b1916de598283b","name":"HWC Turn OFF","server":"e57dff95.0a006","version":7,"debugenabled":false,"action":"switch.turn_off","floorId":[],"areaId":["hallway"],"deviceId":["4c438143908382ce2db0f6f0a2fecdb2"],"entityId":["switch.hwc_esp32_hot_water_cylinder_switch"],"labelId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"3","valueType":"num"},{"property":"in_charge_cycle_now","propertyType":"flow","value":"false","valueType":"bool"},{"property":"transition","propertyType":"flow","value":"3","valueType":"num"}],"queue":"none","blockInputOverrides":false,"domain":"switch","service":"turn_off","x":380,"y":760,"wires":[["3953415490cd9d48","aa1c04e5c4a1eeef"]]},{"id":"3953415490cd9d48","type":"delay","z":"69b1916de598283b","name":"Stand-Down-Delay","pauseType":"delay","timeout":"5","timeoutUnits":"minutes","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":650,"y":760,"wires":[["abb524397aeca2b1"]]},{"id":"ea6d855de2d7da97","type":"api-call-service","z":"69b1916de598283b","name":"Turn HWC back ON","server":"e57dff95.0a006","version":7,"debugenabled":false,"action":"switch.turn_on","floorId":[],"areaId":["hallway"],"deviceId":["4c438143908382ce2db0f6f0a2fecdb2"],"entityId":["switch.hwc_esp32_hot_water_cylinder_switch"],"labelId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"4","valueType":"num"},{"property":"in_charge_cycle_now","propertyType":"flow","value":"true","valueType":"bool"},{"property":"transition","propertyType":"flow","value":"4","valueType":"num"}],"queue":"none","blockInputOverrides":false,"domain":"switch","service":"turn_on","x":720,"y":840,"wires":[["aa1c04e5c4a1eeef"]]},{"id":"e1c7339b66bd60d7","type":"delay","z":"69b1916de598283b","name":"","pauseType":"delay","timeout":"30","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":780,"y":900,"wires":[["abb524397aeca2b1"]]},{"id":"7ffa9fe35823cf4d","type":"switch","z":"69b1916de598283b","name":"sufficient?","property":"payload","propertyType":"msg","rules":[{"t":"gte","v":"surplus_continue","vt":"flow"},{"t":"lt","v":"surplus_continue","vt":"flow"}],"checkall":"true","repair":false,"outputs":2,"x":460,"y":900,"wires":[["ea6d855de2d7da97"],["4d7383d48b6b014a"]]},{"id":"9c83541f2fec5a9b","type":"switch","z":"69b1916de598283b","name":"in_charge_cycle true?","property":"in_charge_cycle_now","propertyType":"flow","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":240,"y":680,"wires":[["58cd72b54e9659ea"]]},{"id":"25091c5f11988aca","type":"switch","z":"69b1916de598283b","name":"dark day?","property":"began_charging_today","propertyType":"flow","rules":[{"t":"false"}],"checkall":"true","repair":false,"outputs":1,"x":100,"y":520,"wires":[["60637d56d61b7558"]]},{"id":"60637d56d61b7558","type":"change","z":"69b1916de598283b","name":"6","rules":[{"t":"set","p":"payload","pt":"msg","to":"6","tot":"num"},{"t":"set","p":"transition","pt":"flow","to":"6","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":230,"y":520,"wires":[["aa1c04e5c4a1eeef"]]},{"id":"55da49120d775633","type":"function","z":"69b1916de598283b","name":"Set Hysteresis","func":"function getState(entity_id) {\n    const ha = global.get('homeassistant');\n    return parseFloat(ha.homeAssistant.states[entity_id]?.state || 0);\n}\n\nconst soc = getState('sensor.my_home_battery');\n\nif ( soc >= 90 ) { //battery State Of Charge\nflow.set(\"surplus_startup\", 1100);  //Required to kick in \nflow.set(\"surplus_continue\", 1900);  //as soon as surplus reaches this battery will charge\nflow.set(\"surplus_cutoff\", 3200);  //45 sec @ this power triggers HWC off\n} \nelse if ( soc >= 60 && soc < 90 ) { //battery State Of Charge\nflow.set(\"surplus_startup\", 2100);  //Required to kick in \nflow.set(\"surplus_continue\", 2400);  //as soon as surplus reaches this battery will charge\nflow.set(\"surplus_cutoff\", 1900);  //45 sec @ this power triggers HWC off\n\n} else if ( soc >=40 && soc < 60 ) {\nflow.set(\"surplus_startup\", 2700);\nflow.set(\"surplus_continue\", 3000);\nflow.set(\"surplus_cutoff\", 1800);\n\n} else {  //battery Empty. High on threshold \nflow.set(\"surplus_startup\", 3500);\nflow.set(\"surplus_continue\", 3600);\nflow.set(\"surplus_cutoff\", 1500);\n}\n\nvar newMsg = { payload: soc, topic: msg.topic };\nreturn newMsg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":520,"wires":[[]]},{"id":"662063441610883a","type":"change","z":"69b1916de598283b","name":"7","rules":[{"t":"set","p":"payload","pt":"msg","to":"7","tot":"num"},{"t":"set","p":"transition","pt":"flow","to":"7","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":40,"wires":[["24f6996466429cf1","aa1c04e5c4a1eeef"]]},{"id":"4d7383d48b6b014a","type":"change","z":"69b1916de598283b","name":"8","rules":[{"t":"set","p":"payload","pt":"msg","to":"8","tot":"num"},{"t":"set","p":"transition","pt":"flow","to":"8","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":900,"wires":[["e1c7339b66bd60d7","aa1c04e5c4a1eeef"]]},{"id":"05a936471d64f2b2","type":"inject","z":"69b1916de598283b","name":"9 am","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"00 09 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":70,"y":40,"wires":[["89ab4b0a8e433244"]]},{"id":"7b6338120c4765d3","type":"comment","z":"69b1916de598283b","name":"Overview","info":"# Overview\nThe HWC will turn ON if there is surplus power and turn OFF if the battery discharge power is too high for too long.\nThe agressiveness of HWC charging is also determined by the telsa batteries state of charge.\n\n# Nodes to adjust\nThe \"Set Hysteresis\" node contains all the adjustable parameters\n\n\n","x":80,"y":1020,"wires":[]},{"id":"147211fd68b918fe","type":"function","z":"69b1916de598283b","name":"get solar states","func":"function getState(entity_id) {\n    const ha = global.get('homeassistant');\n    if (!ha || !ha.homeAssistant || !ha.homeAssistant.states) {\n        node.warn(\"Home Assistant not available in global context\");\n        return 0;\n    }\n    return parseFloat(ha.homeAssistant.states[entity_id]?.state || 0);\n}\n\nconst production = getState('sensor.my_home_solar_power_2');\nconst consumption = getState('sensor.my_home_load_power_2');\nconst surplus = production - consumption;\n\nflow.set(\"surplus\", surplus);\n\n// Show the node status\nconst ts = new Date().toLocaleString();\n\n//node.status({ fill: \"green\", shape: \"dot\", text: `Surplus: ${surplus.toFixed(1)} W` });\nnode.status({\n    fill: \"green\",\n    shape: \"dot\",\n    text: `Surplus: ${surplus.toFixed(1)} W (${ts})`\n});\n\nconst newMsg = { payload: surplus, topic: msg.topic };\nreturn newMsg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":100,"y":140,"wires":[["022fe8ce3ede0891"]]},{"id":"abb524397aeca2b1","type":"function","z":"69b1916de598283b","name":"get solar states","func":"function getState(entity_id) {\n    const ha = global.get('homeassistant');\n    if (!ha || !ha.homeAssistant || !ha.homeAssistant.states) {\n        node.warn(\"Home Assistant not available in global context\");\n        return 0;\n    }\n    return parseFloat(ha.homeAssistant.states[entity_id]?.state || 0);\n}\n\nconst production = getState('sensor.my_home_solar_power_2');\nconst consumption = getState('sensor.my_home_load_power_2');\nconst surplus = production - consumption;\n\nflow.set(\"surplus\", surplus);\n\n\n// Show the node status\nconst ts = new Date().toLocaleString();\n\n//node.status({ fill: \"green\", shape: \"dot\", text: `Surplus: ${surplus.toFixed(1)} W` });\nnode.status({\n    fill: \"green\",\n    shape: \"dot\",\n    text: `Surplus: ${surplus.toFixed(1)} W (${ts})`\n});\n\nconst newMsg = { payload: surplus, topic: msg.topic };\nreturn newMsg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":160,"y":900,"wires":[["7ffa9fe35823cf4d"]]},{"id":"91955e402eb373f8","type":"server-state-changed","z":"69b1916de598283b","name":"Battery Power Stress","server":"e57dff95.0a006","version":6,"outputs":2,"exposeAsEntityConfig":"","entities":{"entity":["sensor.my_home_battery_power_2"],"substring":[],"regex":[]},"outputInitially":false,"stateType":"str","ifState":"surplus_cutoff","ifStateType":"flow","ifStateOperator":"gte","outputOnlyOnStateChange":true,"for":"150","forType":"num","forUnits":"seconds","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"true","valueType":"bool"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":760,"wires":[["9c83541f2fec5a9b"],[]]},{"id":"e57dff95.0a006","type":"server","name":"Home Assistant","addon":true},{"id":"8419dcae88abde00","type":"global-config","env":[],"modules":{"node-red-contrib-home-assistant-websocket":"0.80.3","node-red-contrib-time-range-switch":"1.2.0","node-red-contrib-persistent-fsm":"1.2.1"}}]