Node-Red in Home Assistant

Newbie here, I’m tearing my hair out a little trying to figure out how to solve this node-red flow in HA…

Current setup: Victron 5kva multi-plus inverter + 12 x 355w panels + 7.2Kw Li-on battery pack

Logic is as follows:-

I poll my battery and when SOC reaches 80% 2 x Sonoff TH16 switches go on for EV charging
Condition 1 - if battery SOC dips below 70% the switches will turn off
Condition 2 - it must happen between 10h00 - 15h30 when solar production is usually at it’s peak
Condition 3 - if my consumption exceeds 5000w for over 3 minutes it turns off both switches, to avoid turning off switches if the demand is temporary like boiling a ketlle or microwave warming up food

My predicament is that if the load exceeds 5000w for over 3 minutes I need to have the switches remain in the off position say for another 30-45 minutes to minimize dipping into the Eskom grid, as most likely oven/s and/or stove will be in use to prepare meals.

I suppose another solution would be to upgrade my current setup and get a bigger inverter 8/10kva (no rescrictions as yet in Joburg on PV sizing) but that comes at a cost, definitely on the cards for the future.

@Paul … speak to him … if ever I saw a NodeRed system, he did it.

Alos relativley new to Node red but I would have done it using the a global variable for MeanLoad and the below flows.

The function “check status” as
var newMsg = msg;
var now = new Date();
var hour = now.getHours();
var minutes = now.getMinutes();
var time = hour*100+minutes;
var MeanLoad = global.get(‘MeanLoad’) || 0;
var SOC = Msg.payload;

if ((time > 1000) && (time < 1530) && (MeanLoad < 5000) && (SOC >70) && (SOC<80))
{
newMsg.payload = “on”;
} else
{
newMsg.payload =“off”;
}
return newMsg;

Anther way to look at it is to have a Timeout running.So if the payload is > than 5000w the payload will not go through and if the timer runs out condition x will be triggered.
Some thing like this could also work.

[
{
“id”: “13e1e66a.5e0bba”,
“type”: “subflow”,
“name”: “Last Active v2”,
“info”: “This node will display the time since it received a payload the payload will be passed through but will be reset the timer on each payload.\nIf switched to manual. The timer can be started with \n_topic : cmnd_\n_payload_ : reset - Reset the timer\n_payload_ : OFF - Switch off timer\n_payload_ : ON - Switch on timer\n\nSecond output report the seconds so that it could be used as a trigger \n”,
“category”: “”,
“in”: [
{
“x”: 160,
“y”: 120,
“wires”: [
{
“id”: “134456bc.08c889”
}
]
}
],
“out”: [
{
“x”: 360,
“y”: 250,
“wires”: [
{
“id”: “13e1e66a.5e0bba”,
“port”: 0
}
]
},
{
“x”: 740,
“y”: 230,
“wires”: [
{
“id”: “b912d0b8.e11cc”,
“port”: 0
},
{
“id”: “5638065c.89f038”,
“port”: 0
}
]
},
{
“x”: 490,
“y”: 580,
“wires”: [
{
“id”: “6454199e.1d0c88”,
“port”: 0
}
]
}
],
“env”: [
{
“name”: “defActive”,
“type”: “bool”,
“value”: “true”,
“ui”: {
“icon”: “font-awesome/fa-bullseye”,
“label”: {
“en-US”: “Default On”
},
“type”: “checkbox”,
“opts”: {}
}
},
{
“name”: “useTimeOut”,
“type”: “str”,
“value”: “NO”,
“ui”: {
“label”: {
“en-US”: “Use Alarm”
},
“type”: “select”,
“opts”: {
“opts”: [
{
“l”: {
“en-US”: “YES”
},
“v”: “YES”
},
{
“l”: {
“en-US”: “NO”
},
“v”: “NO”
}
]
}
}
},
{
“name”: “time”,
“type”: “num”,
“value”: “”,
“ui”: {
“label”: {
“en-US”: “Alarm TImeout”
},
“type”: “input”,
“opts”: {
“types”: [
“num”
]
}
}
},
{
“name”: “safe”,
“type”: “str”,
“value”: “”,
“ui”: {
“label”: {
“en-US”: “Safe Payload”
},
“type”: “input”,
“opts”: {
“types”: [
“str”
]
}
}
},
{
“name”: “unsafe”,
“type”: “str”,
“value”: “”,
“ui”: {
“label”: {
“en-US”: “Unsafe Payload”
},
“type”: “input”,
“opts”: {
“types”: [
“str”
]
}
}
}
],
“color”: “#C0DEED”,
“outputLabels”: [
“msg.payload”,
“Time in sec”,
“Alarm Output”
],
“icon”: “node-red/timer.svg”,
“status”: {
“x”: 1120,
“y”: 200,
“wires”: [
{
“id”: “48f5a961.610288”,
“port”: 0
},
{
“id”: “b2c3a89.3f31458”,
“port”: 1
},
{
“id”: “a1b8c627.6b3148”,
“port”: 0
}
]
}
},
{
“id”: “49929687.926888”,
“type”: “inject”,
“z”: “13e1e66a.5e0bba”,
“name”: “”,
“repeat”: “1”,
“crontab”: “”,
“once”: false,
“onceDelay”: 0.1,
“topic”: “”,
“payload”: “1”,
“payloadType”: “str”,
“x”: 220,
“y”: 50,
“wires”: [
[
“63fdedb.e938414”
]
]
},
{
“id”: “5638065c.89f038”,
“type”: “function”,
“z”: “13e1e66a.5e0bba”,
“name”: “Auto Run”,
“func”: “\n\n\nif (msg.topic == "cmnd"){\n if(msg.payload == "reset"){\n flow.set("count",0) \n }else if(msg.payload == "ON"){\n flow.set("cmnd","ON") \n }else if(msg.payload == "OFF"){\n flow.set("cmnd","OFF") \n flow.set("count",0) \n count = flow.get("count")||0\n }\n}else{\ncount = flow.get("count")||0\ncmnd = "ON"\nif (cmnd == "ON"){color = "green"}else{color="red"}\nif (cmnd == "ON"){\n count++\n flow.set("count",count)\n msg.payload = count ;\n return msg;\n}\n}\n”,
“outputs”: 1,
“noerr”: 0,
“x”: 770,
“y”: 50,
“wires”: [
[
“48f5a961.610288”,
“6454199e.1d0c88”
]
]
},
{
“id”: “91bf311c.999cd”,
“type”: “inject”,
“z”: “13e1e66a.5e0bba”,
“name”: “”,
“repeat”: “”,
“crontab”: “”,
“once”: false,
“onceDelay”: 0.1,
“topic”: “cmnd”,
“payload”: “ON”,
“payloadType”: “str”,
“x”: 120,
“y”: 240,
“wires”: [

]
},
{
“id”: “95d6a0a5.0e116”,
“type”: “inject”,
“z”: “13e1e66a.5e0bba”,
“name”: “”,
“repeat”: “”,
“crontab”: “”,
“once”: false,
“onceDelay”: 0.1,
“topic”: “cmnd”,
“payload”: “OFF”,
“payloadType”: “str”,
“x”: 120,
“y”: 280,
“wires”: [

]
},
{
“id”: “fe9ef7f5.f029b8”,
“type”: “inject”,
“z”: “13e1e66a.5e0bba”,
“name”: “”,
“repeat”: “”,
“crontab”: “”,
“once”: false,
“onceDelay”: 0.1,
“topic”: “cmnd”,
“payload”: “RESET”,
“payloadType”: “str”,
“x”: 130,
“y”: 320,
“wires”: [

]
},
{
“id”: “63fdedb.e938414”,
“type”: “function”,
“z”: “13e1e66a.5e0bba”,
“name”: "Test if defalt Active is ON or OFF ",
“func”: “info = env.get("defActive")\n\nif (info === true){\n return [msg,null]; \n}else{\n return [null,msg] \n}\n\n”,
“outputs”: 2,
“noerr”: 0,
“x”: 510,
“y”: 60,
“wires”: [
[
“5638065c.89f038”
],
[
“b912d0b8.e11cc”
]
],
“outputLabels”: [
“true”,
“false”
]
},
{
“id”: “48f5a961.610288”,
“type”: “function”,
“z”: “13e1e66a.5e0bba”,
“name”: “”,
“func”: “sec = msg.payload\n\n var convertSeconds = function(sec) {\n var hrs = Math.floor(sec / 3600);\n var min = Math.floor((sec - (hrs * 3600)) / 60);\n var seconds = sec - (hrs * 3600) - (min * 60);\n seconds = Math.round(seconds * 100) / 100\n \n var result = (hrs < 10 ? "0" + hrs : hrs);\n result += "h" + (min < 10 ? "0" + min : min);\n result += ":" + (seconds < 10 ? "0" + seconds : seconds);\n return result;\n }\nif (sec >= 60){\ninfo = convertSeconds(sec) ;\n}else{\ninfo = sec ; \n}\nstat = env.get("useTimeOut")\nif (stat == "YES"){\n msg.payload ="Armed-Time:"+info+"s"\nreturn msg; \n}else{\n msg.payload ="Time:"+info+"s"\nreturn msg; \n}\n”,
“outputs”: 1,
“noerr”: 0,
“x”: 920,
“y”: 130,
“wires”: [

]
},
{
“id”: “134456bc.08c889”,
“type”: “switch”,
“z”: “13e1e66a.5e0bba”,
“name”: “”,
“property”: “topic”,
“propertyType”: “msg”,
“rules”: [
{
“t”: “eq”,
“v”: “cmnd”,
“vt”: “str”
},
{
“t”: “else”
}
],
“checkall”: “true”,
“repair”: false,
“outputs”: 2,
“x”: 280,
“y”: 120,
“wires”: [
[
“63fdedb.e938414”
],
[
“f61be35b.5e46d”
]
]
},
{
“id”: “f61be35b.5e46d”,
“type”: “change”,
“z”: “13e1e66a.5e0bba”,
“name”: “”,
“rules”: [
{
“t”: “set”,
“p”: “topic”,
“pt”: “msg”,
“to”: “cmnd”,
“tot”: “str”
},
{
“t”: “set”,
“p”: “payload”,
“pt”: “msg”,
“to”: “reset”,
“tot”: “str”
}
],
“action”: “”,
“property”: “”,
“from”: “”,
“to”: “”,
“reg”: false,
“x”: 460,
“y”: 160,
“wires”: [
[
“5638065c.89f038”
]
]
},
{
“id”: “b912d0b8.e11cc”,
“type”: “function”,
“z”: “13e1e66a.5e0bba”,
“name”: “”,
“func”: “\n\n\nif (msg.topic == "cmnd"){\n if(msg.payload == "RESET"){\n flow.set("count",0) \n }else if(msg.payload == "ON"){\n flow.set("cmnd","ON") \n }else if(msg.payload == "OFF"){\n flow.set("cmnd","OFF") \n flow.set("count",0) \n count = flow.get("count")||0\n }\n}else{\ncount = flow.get("count")||0\ncmnd = "ON"\nif (cmnd == "ON"){color = "green"}else{color="red"}\nif (cmnd == "ON"){\n count++\n flow.set("count",count)\n msg.payload = count ;\n // node.status({fill:color,shape:"ring",text:"Last Active:"+count+" sec"})\n return msg;\n}\n\n}\n”,
“outputs”: 1,
“noerr”: 0,
“x”: 570,
“y”: 360,
“wires”: [
[
“b2c3a89.3f31458”,
“6454199e.1d0c88”
]
]
},
{
“id”: “b2c3a89.3f31458”,
“type”: “function”,
“z”: “13e1e66a.5e0bba”,
“name”: “Auto Run”,
“func”: “\n\n\nif (msg.topic == "cmnd"){\n if(msg.payload == "RESET"){\n flow.set("count",0) \n }else if(msg.payload == "ON"){\n flow.set("cmnd","ON") \n }else if(msg.payload == "OFF"){\n flow.set("cmnd","OFF") \n flow.set("count",0) \n count = flow.get("count")||0\n }\n}else{\ncount = flow.get("count")||0\ncmnd = flow.get("cmnd")||"OFF"\nif (cmnd == "ON"){color = "green"}else{color="red"}\nif (cmnd == "ON"){\n count++\n flow.set("count",count)\n msg.payload = count ;\n //node.status({fill:color,shape:"ring",text:"Last Active:"+count+" sec"})\n return [msg,null];\n}else{\nmsg.payload = "Counter Off"\nreturn [null,msg]\n //node.status({fill:color,shape:"ring",text:"Last Active:"+count+" sec"})\n}\n\n}\n”,
“outputs”: 2,
“noerr”: 0,
“x”: 730,
“y”: 360,
“wires”: [
[
“a1b8c627.6b3148”
],

]
},
{
“id”: “a1b8c627.6b3148”,
“type”: “function”,
“z”: “13e1e66a.5e0bba”,
“name”: “”,
“func”: “sec = msg.payload\n\n var convertSeconds = function(sec) {\n var hrs = Math.floor(sec / 3600);\n var min = Math.floor((sec - (hrs * 3600)) / 60);\n var seconds = sec - (hrs * 3600) - (min * 60);\n seconds = Math.round(seconds * 100) / 100\n \n var result = (hrs < 10 ? "0" + hrs : hrs);\n result += "h" + (min < 10 ? "0" + min : min);\n result += ":" + (seconds < 10 ? "0" + seconds : seconds);\n return result;\n }\nif (sec >= 60){\ninfo = convertSeconds(sec) ;\n}else{\ninfo = sec ; \n}\ninfo = env.get("useTimeOut")\n\n\nmsg.payload ="Auto:OFF Since:"+info\nreturn msg;”,
“outputs”: 1,
“noerr”: 0,
“x”: 930,
“y”: 200,
“wires”: [

]
},
{
“id”: “6454199e.1d0c88”,
“type”: “function”,
“z”: “13e1e66a.5e0bba”,
“name”: “Alarm MSG”,
“func”: “info = env.get("useTimeOut")\n \ncurrTime = msg.payload\n\nif (info == "YES"){\n sendMsg = flow.get("sendMsg")||0 \n time = env.get("time")\n if (currTime > time && sendMsg === 0){\n unsafe = env.get("unsafe") \n msg.payload = unsafe\n flow.set("sendMsg",1)\n return msg; \n }else if (currTime < time && sendMsg == 1){\n safe = env.get("safe") \n msg.payload = safe\n flow.set("sendMsg",0)\n return msg; \n }\n \n}\n\n”,
“outputs”: 1,
“noerr”: 0,
“x”: 270,
“y”: 580,
“wires”: [

]
},
{
“id”: “e34aee4d.695b4”,
“type”: “comment”,
“z”: “13e1e66a.5e0bba”,
“name”: “Alarm Trigger ___________________________________________________________________________”,
“info”: “”,
“x”: 470,
“y”: 510,
“wires”:
},
{
“id”: “e2d78f54.daec9”,
“type”: “subflow:13e1e66a.5e0bba”,
“z”: “74b8f4d1.72632c”,
“name”: “Time Out”,
“env”: [
{
“name”: “useTimeOut”,
“value”: “YES”,
“type”: “str”
},
{
“name”: “time”,
“value”: “15”,
“type”: “num”
},
{
“name”: “safe”,
“value”: “No Problem”,
“type”: “str”
},
{
“name”: “unsafe”,
“value”: “Alarm Trigger”,
“type”: “str”
}
],
“x”: 700,
“y”: 4350,
“wires”: [
,
,
[
“dd89b3ff.fa06c”
]
]
},
{
“id”: “14f3be99.905061”,
“type”: “switch”,
“z”: “74b8f4d1.72632c”,
“name”: “”,
“property”: “payload”,
“propertyType”: “msg”,
“rules”: [
{
“t”: “lt”,
“v”: “5000”,
“vt”: “str”
}
],
“checkall”: “true”,
“repair”: false,
“outputs”: 1,
“x”: 530,
“y”: 4350,
“wires”: [
[
“e2d78f54.daec9”
]
]
},
{
“id”: “ff9269a0.49c118”,
“type”: “inject”,
“z”: “74b8f4d1.72632c”,
“name”: “”,
“props”: [
{
“p”: “payload”
},
{
“p”: “topic”,
“vt”: “str”
}
],
“repeat”: “”,
“crontab”: “”,
“once”: false,
“onceDelay”: 0.1,
“topic”: “”,
“payload”: “5500”,
“payloadType”: “num”,
“x”: 290,
“y”: 4280,
“wires”: [
[
“14f3be99.905061”
]
]
},
{
“id”: “8af40e9c.bbe47”,
“type”: “inject”,
“z”: “74b8f4d1.72632c”,
“name”: “”,
“props”: [
{
“p”: “payload”
},
{
“p”: “topic”,
“vt”: “str”
}
],
“repeat”: “”,
“crontab”: “”,
“once”: false,
“onceDelay”: 0.1,
“topic”: “”,
“payload”: “1000”,
“payloadType”: “num”,
“x”: 300,
“y”: 4390,
“wires”: [
[
“14f3be99.905061”
]
]
},
{
“id”: “dd89b3ff.fa06c”,
“type”: “debug”,
“z”: “74b8f4d1.72632c”,
“name”: “”,
“active”: true,
“tosidebar”: true,
“console”: false,
“tostatus”: false,
“complete”: “false”,
“statusVal”: “”,
“statusType”: “auto”,
“x”: 930,
“y”: 4360,
“wires”:
}
]

It is a fairly steep learning curve but well worth the time once you get the hang of it.

1 Like

Thanks @TheTerribleTriplet @Ryeman @Paul

Will definitley do another deep dive with your advise when I get a chance hopefully over the weekend.

I must say HA + automations can be a rabbit hole but node-red gives a nice visual display especially for those without a software development/coding background.

1 Like