Node-RED hurts my head, man

To start with, I’m a building automation specialist by trade; I’ve worked in systems with both written automation languages and graphical UIs for automation tasks. I also have experience with Ruby, Javascript, and Java; I’m not new to software or automation.

When I first started with HA a few months back, I felt pretty hamstrung by the limited automation options in the basic configuration, but I always knew Node was an option, and from screenshots I’d seen, I really thought it would be similar to the graphical automation interfaces I’m used to.

How wrong I am :laughing:

Here’s a pretty simple automation task that I can’t for the life of me figure out how to do in Node: my home is a two-story geodesic dome, with no walls or doors anywhere except the bathroom. I have no central heat, just an oil-filled space heater downstairs that is usually enough to keep the house warm (with a fan blowing by it to get the air flowing). I have a Zigbee temperature sensor on the wall upstairs where we spend a lot of our time and where it tends to be warmer (heat rising and all that).

I bought a smart plug to automate the space heater; when it’s cold, turn it on, when it’s warm, turn it off. Change the setpoint based on occupancy. Ya know, basic thermostat stuff.

But, nodes can only have one input? Or, if they have multiple inputs, you can’t…really access the other inputs? How do you do a basic AND? This is how I would do this in a normal automation GUI:

So many of those blocks have multiple inputs and do not seem to have anything corresponding in Node.

I tried doing something like what is in this other thread but it doesn’t make sense to me and doesn’t work. From what little debugging I’m able to get this thing to output, the join isn’t joining in my example the same way it was in his.

So…I guess, can someone explain to my building-automation brain how this danged thing works, and perhaps provide an example of how you would do what I did above in Node?

Welcome!

Sorry you are struggling with Node-Red. I too have a background in industrial automation and lots of programming languages (I’m an old guy still learning new tricks).

Node-Red was completely new to me too. I read a lot and watched various videos on getting started which helped me.

The example you referenced was to combine multiple messages into a single payload - they were not trying to do AND logic but building a new message.

There are ways to do basic AND logic. Using the trigger : state node you can “watch” for multiple events where all events need to be “true” There is a similar example here:

1 Like

Basic And logic

[{"id":"8873a4ec73fd74e6","type":"inject","z":"120358abd7c22d30","name":"start","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":222,"y":4656,"wires":[["b22adb4aa49dbfaa"]]},{"id":"b22adb4aa49dbfaa","type":"switch","z":"120358abd7c22d30","name":"condition A","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":390,"y":4656,"wires":[["9d2dfd73b55f24bf"],["0b35603e9c945d3d"]]},{"id":"9d2dfd73b55f24bf","type":"switch","z":"120358abd7c22d30","name":"condition B","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":566,"y":4656,"wires":[["cdda14473a7604bf"],["0b35603e9c945d3d"]]},{"id":"cdda14473a7604bf","type":"debug","z":"120358abd7c22d30","name":"both condition were valid","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":806,"y":4656,"wires":[]},{"id":"0b35603e9c945d3d","type":"debug","z":"120358abd7c22d30","name":"one or both condition were invalid","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":836,"y":4704,"wires":[]}]

I don’t fully follow your chart but here’s an example I came up with. I am assuming your temperature and occupancy sensors are in Home Assistant.

[{"id":"e3cd031708f597a1","type":"server-state-changed","z":"120358abd7c22d30","name":"Temp / Occupancy changed","server":"","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":["sensor.temperature","person.person1"],"entityIdType":"list","outputInitially":false,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[],"x":264,"y":4848,"wires":[["af87ee9d39f0c788"]]},{"id":"af87ee9d39f0c788","type":"api-current-state","z":"120358abd7c22d30","name":"current temperature","server":"","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"sensor.temperature","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":506,"y":4848,"wires":[["f0a0e0703b73d1dc"]]},{"id":"f0a0e0703b73d1dc","type":"api-current-state","z":"120358abd7c22d30","name":"home?","server":"","version":3,"outputs":2,"halt_if":"home","halt_if_type":"str","halt_if_compare":"is","entity_id":"person.person1","state_type":"str","blockInputOverrides":false,"outputProperties":[],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":674,"y":4848,"wires":[["e664c28638568cd4"],["484ca66eeb05d705"]]},{"id":"e664c28638568cd4","type":"switch","z":"120358abd7c22d30","name":"home set point","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"68","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":848,"y":4848,"wires":[["b217a53e271806da"],["aeb090080ebb6cdc"]]},{"id":"b217a53e271806da","type":"api-call-service","z":"120358abd7c22d30","name":"turn on","server":"","version":5,"debugenabled":false,"domain":"switch","service":"turn_on","areaId":[],"deviceId":[],"entityId":["switch.heater"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1040,"y":4848,"wires":[[]]},{"id":"484ca66eeb05d705","type":"switch","z":"120358abd7c22d30","name":"away set point","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"62","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":848,"y":4896,"wires":[["b217a53e271806da"],["aeb090080ebb6cdc"]]},{"id":"aeb090080ebb6cdc","type":"api-call-service","z":"120358abd7c22d30","name":"turn off","server":"","version":5,"debugenabled":false,"domain":"switch","service":"turn_off","areaId":[],"deviceId":[],"entityId":["switch.heater"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1033,"y":4897,"wires":[[]]}]

Personally what I would do is create a generic thermostat in Home Assistant. Do it this way will allow the thermostat component to handle the basic operations, Then use Node-RED to set the target temperature.

image

[{"id":"193b9d79ca600319","type":"server-state-changed","z":"120358abd7c22d30","name":"Occupancy changed","server":"","version":5,"outputs":2,"exposeAsEntityConfig":"","entityId":"person.person1","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"home","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[],"x":394,"y":4368,"wires":[["2cd54accdf85f1fe"],["06080031e75f41ca"]]},{"id":"06080031e75f41ca","type":"api-call-service","z":"120358abd7c22d30","name":"set away temp","server":"","version":5,"debugenabled":false,"domain":"climate","service":"set_temperature","areaId":[],"deviceId":[],"entityId":["climate.heater"],"data":"{temperature: 62}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":611,"y":4417,"wires":[[]]},{"id":"2cd54accdf85f1fe","type":"api-call-service","z":"120358abd7c22d30","name":"set home temp","server":"","version":5,"debugenabled":false,"domain":"climate","service":"set_temperature","areaId":[],"deviceId":[],"entityId":["climate.heater"],"data":"{temperature: 68}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":608,"y":4368,"wires":[[]]},{"id":"dc734795ada92586","type":"comment","z":"120358abd7c22d30","name":"example 1","info":"","x":372,"y":4320,"wires":[]},{"id":"0d259c86022e23ad","type":"comment","z":"120358abd7c22d30","name":"example 2 - using JSONata and input overrides","info":"","x":492,"y":4464,"wires":[]},{"id":"7a4ce338e7f90833","type":"server-state-changed","z":"120358abd7c22d30","name":"Occupancy changed","server":"","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"person.person1","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"{\"data\": {\"temperature\": $entity().state = \"home\" ? 68 : 62}}","valueType":"jsonata"}],"x":394,"y":4512,"wires":[["18427503b16928fa"]]},{"id":"18427503b16928fa","type":"api-call-service","z":"120358abd7c22d30","name":"set climate temp","server":"","version":5,"debugenabled":false,"domain":"climate","service":"set_temperature","areaId":[],"deviceId":[],"entityId":["climate.heater"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":608,"y":4512,"wires":[[]]}]

edit: fixed some issues and realized some of the import might not import correctly due to me running a beta version

4 Likes

And this is how I keep learning from one of the best. Thank you.

2 Likes

Kermit,

Thanks for your help with this. I think I am (barely) beginning to understand what’s going on here.

I also have a home mode status that is an input select (options: [Heating, Free Cooling, Air Conditioning]) and I think I finally got adding that figured out, too, as well as some verification that the windows are closed before I turn on the heat.

I guess I can’t really have a events: state object output true or false directly on the value of an input_select but I tossed the input_select into a switch and it did the trick.

[{"id":"193b9d79ca600319","type":"server-state-changed","z":"ada649a6d979dd94","name":"occupied?","server":"8a71b397.2b4b8","version":5,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"zone.home","entityidfiltertype":"exact","outputinitially":false,"state_type":"habool","haltifstate":"0","halt_if_type":"num","halt_if_compare":"lt","outputs":2,"output_only_on_state_change":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[],"x":220,"y":180,"wires":[["2cd54accdf85f1fe"],["06080031e75f41ca"]]},{"id":"06080031e75f41ca","type":"api-call-service","z":"ada649a6d979dd94","name":"set away temp","server":"8a71b397.2b4b8","version":5,"debugenabled":false,"domain":"climate","service":"set_temperature","areaId":[],"deviceId":[],"entityId":["climate.heater"],"data":"{temperature: 62}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":840,"y":260,"wires":[[]]},{"id":"2cd54accdf85f1fe","type":"api-call-service","z":"ada649a6d979dd94","name":"set home temp","server":"8a71b397.2b4b8","version":5,"debugenabled":false,"domain":"climate","service":"set_temperature","areaId":[],"deviceId":[],"entityId":["climate.heater"],"data":"{temperature: 68}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":840,"y":160,"wires":[[]]},{"id":"dc734795ada92586","type":"comment","z":"ada649a6d979dd94","name":"example 1","info":"","x":228,"y":132,"wires":[]},{"id":"278fad7d02c7ce0a","type":"server-state-changed","z":"ada649a6d979dd94","name":"mode_status","server":"8a71b397.2b4b8","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_select.home_mode","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"x":230,"y":340,"wires":[["b02766ce4e9808f3"]]},{"id":"c83c2bc40af4ad45","type":"api-call-service","z":"ada649a6d979dd94","name":"turn off heater","server":"8a71b397.2b4b8","version":5,"debugenabled":false,"domain":"climate","service":"turn_off","areaId":[],"deviceId":[],"entityId":["climate.heater"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":840,"y":360,"wires":[[]]},{"id":"b02766ce4e9808f3","type":"switch","z":"ada649a6d979dd94","name":"in_heat_mode?","property":"payload","propertyType":"msg","rules":[{"t":"neq","v":"Heating","vt":"str"},{"t":"eq","v":"Heating","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":400,"y":340,"wires":[["c83c2bc40af4ad45"],["43127e109a82ed5e"]]},{"id":"da5c425c2961d612","type":"api-call-service","z":"ada649a6d979dd94","name":"turn on heater","server":"8a71b397.2b4b8","version":5,"debugenabled":false,"domain":"climate","service":"turn_on","areaId":[],"deviceId":[],"entityId":["climate.heater"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":840,"y":420,"wires":[[]]},{"id":"43127e109a82ed5e","type":"api-current-state","z":"ada649a6d979dd94","name":"window_closed?","server":"8a71b397.2b4b8","version":3,"outputs":2,"halt_if":"false","halt_if_type":"bool","halt_if_compare":"is","entity_id":"switch.bedroom_window_open","state_type":"habool","blockInputOverrides":false,"outputProperties":[],"for":"20","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":580,"y":420,"wires":[["da5c425c2961d612"],[]]},{"id":"29423e894b3397ee","type":"server-state-changed","z":"ada649a6d979dd94","name":"window_open?","server":"8a71b397.2b4b8","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":["switch.bedroom_window_open","switch.fan_window_open"],"entityidfiltertype":"list","outputinitially":false,"state_type":"habool","haltifstate":"true","halt_if_type":"bool","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[],"x":240,"y":420,"wires":[["c83c2bc40af4ad45"],[]]},{"id":"eb712a1a565ea7ff","type":"server-state-changed","z":"ada649a6d979dd94","name":"night?","server":"8a71b397.2b4b8","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_boolean.night","entityidfiltertype":"exact","outputinitially":false,"state_type":"habool","haltifstate":"true","halt_if_type":"bool","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[],"x":210,"y":280,"wires":[["06080031e75f41ca"],["64597a5f2fc68719"]]},{"id":"64597a5f2fc68719","type":"api-current-state","z":"ada649a6d979dd94","name":"occupied?","server":"8a71b397.2b4b8","version":3,"outputs":2,"halt_if":"0","halt_if_type":"num","halt_if_compare":"gt","entity_id":"zone.home","state_type":"habool","blockInputOverrides":false,"outputProperties":[],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":370,"y":240,"wires":[["2cd54accdf85f1fe"],[]]},{"id":"8a71b397.2b4b8","type":"server","name":"Home Assistant","addon":true}]

Also looks like your outputs to the thermostat didn’t quite work; I changed it from jsonata {temperature: 62} to JSON {"temperature": 62} and it works fine that way; I was getting API errors otherwise.

In the output properties, you can use JSONata

$entities("input_select.something").state = "text" ? true : false

https://zachowj.github.io/node-red-contrib-home-assistant-websocket/guide/jsonata.html

But you can add data to msg.payload which is almost the same as having multiple inputs.
And then process them with java in a function.

f.e. set
msg.payload.value1 = true
msg.payload.value2 =false
And then based on the AND value, return output 1 or output 2:

if (msg.payload.value1 && msg.payload.value2)
{
    return [null,msg];
}
else
{
   # maybe change some data f.e.
    msg.payload.attributes.temperature = 22;
    return [msg,null];
}
return msg;

This way enabled me to do some basic java (as I used to be in the same position 3 years ago)

I can see what you’re doing here and it makes sense, but, man, it’s verbose. Also it looks like there’s some fun race conditions where if the messages don’t arrive in time then you don’t get one of the values and it doesn’t work the way you’d expect.

It feels like someone who had absolutely no idea how GUI-based automation systems work, tried to build a GUI-based automation system. Or, probably more accurately, someone took a GUI-based programming system and said “this can work for automation” and while they’re not wrong…it’s just all kinds of backwards from the perspective of someone who actually does this for a living.

But this doesn’t provide a discrete true and false output from the events: state block, instead providing a single output of true or false which I then have to discriminate elsewhere (rather than discriminating based on where I link the outputs).

I’ve also had a steep learning curve, but I think I’ve come a long way over the last 3 years :stuck_out_tongue:

Can’t disagree with you there, been a plc / systems integrator for more than 30 years and had to “unlearn” a few things when I started using NR. I have since migrated back to the HA built in automations as they have gotten better. But I am still forced to view things a bit different to get things where I want.

A fundamental idea in NR is that you discriminate inputs on their message topics or some element of the payload. The Switch node can do this, for example.

The Join node might help you if you want to wait until several messages have arrived or a timeout occurs before emitting an output message.

You might be familiar with the concept of a state machine in automation. There is a excellent contributed node that implements this.

You’re right, it does bend the brain but it’s so worthwhile when it eventually clicks.

1 Like