Sync input_number helper with climate temperature attribute in Node-RED optimizations

Dear all,

I need a slider for setting my temperature in climate entities to better control them in a room card. I created a helper for this input_number.keller_buro_heizung_zieltemperatur. I can set the temperature with the top node-red-flow (see attached picture).

The challenge is, that the input_number shall also be set to the correct value, when the temperature of the climate entity is set somewhwer else. For this I created the lower node-red-flow. This also works.

My question is now, is there maybe a better way to do this? Because it seems, that in my flow there is a kind of (bad?) interaction between the top and the lower flow. If I change the temperature via input_number (top flow) of course the temperature in the climate entity is changed => The bottom flow is also triggered and vice versa.

Node-RED export
[{"id":"5f791ee6aba5b9a3","type":"server-state-changed","z":"916ac106527864fc","name":"Heating target temperature input_number changed","server":"7211bec6.fcfb3","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"input_number.keller_buro_heizung_zieltemperatur","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":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":210,"y":1660,"wires":[["6d9ca21716e0cf71"]]},{"id":"816c7f63e9c25b28","type":"api-call-service","z":"916ac106527864fc","name":"Set climate target temperature","server":"7211bec6.fcfb3","version":5,"debugenabled":false,"domain":"climate","service":"set_temperature","areaId":[],"deviceId":["109b8603c323a2cb933059ca0f2d0ccd"],"entityId":[],"data":"{\"temperature\": msg.payload}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1190,"y":1660,"wires":[[]]},{"id":"62dd2eab9904196d","type":"change","z":"916ac106527864fc","name":"Transform String to Number","rules":[{"t":"set","p":"payload","pt":"msg","to":"$number(msg.payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":900,"y":1660,"wires":[["816c7f63e9c25b28"]]},{"id":"c5e2e97389e1936c","type":"server-state-changed","z":"916ac106527864fc","name":"Heating target temperature climate changed","server":"7211bec6.fcfb3","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"climate.keller_buro_heizung_thermostat","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":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"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":190,"y":1780,"wires":[["d20ffcfb4906b4be"]]},{"id":"2f7d8f5406f06635","type":"api-call-service","z":"916ac106527864fc","name":"Set input_number target temperature","server":"7211bec6.fcfb3","version":5,"debugenabled":false,"domain":"input_number","service":"set_value","areaId":[],"deviceId":[],"entityId":["input_number.keller_buro_heizung_zieltemperatur"],"data":"{\"value\": msg.payload}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1210,"y":1780,"wires":[[]]},{"id":"d4fc5748b685042e","type":"change","z":"916ac106527864fc","name":"Transform Number to String","rules":[{"t":"set","p":"payload","pt":"msg","to":"$string(data.new_state.attributes.temperature)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":900,"y":1780,"wires":[["2f7d8f5406f06635"]]},{"id":"d20ffcfb4906b4be","type":"switch","z":"916ac106527864fc","name":"Target temperature really changed?","property":"data.old_state.attributes.temperature","propertyType":"msg","rules":[{"t":"neq","v":"data.new_state.attributes.temperature","vt":"msg"},{"t":"eq","v":"data.new_state.attributes.temperature","vt":"msg"}],"checkall":"true","repair":false,"outputs":2,"x":550,"y":1780,"wires":[["d4fc5748b685042e"],[]]},{"id":"6d9ca21716e0cf71","type":"switch","z":"916ac106527864fc","name":"Target temperature really changed?","property":"data.old_state.state","propertyType":"msg","rules":[{"t":"neq","v":"data.new_state.state","vt":"msg"},{"t":"eq","v":"data.new_state.state","vt":"msg"}],"checkall":"true","repair":false,"outputs":2,"x":590,"y":1660,"wires":[["62dd2eab9904196d"],[]]},{"id":"7211bec6.fcfb3","type":"server","name":"Home Assistant","version":4,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":false,"cacheJson":false,"heartbeat":false,"heartbeatInterval":"","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m"}]

THX

Unfortunately this is a “quote,” not an export of the JSON.
This means that two [ being next to each other end up as and can’t be read. This corrupts the JSON.

You need to use the </> option and paste in the JSON.

[{“id”:“5f791ee6aba5b9a3”,“t:...THIS IS JUST AN EXAMPLE NOT THE CORRECT JSON

Thanks for the hint. I changed it in the initial post.

To try and answer your original question…

In summary I believe you have set up the following.

Event: state change (input_number) → Call service (climate set temp)
Event: state change (climate temp) → Call service (input_number set value)

As a first observation, this is not necessary as the climate card has the target temperature (large circle) which can be adjusted from the - / +.
However, I can see that perhaps adding an additional input control might be helpful, so yes an input_number with a slider is useful.

There is indeed potential for a ‘race’ situation. Manually change input slider, updates climate temp, climate temp state changes, updates input, input state changes, updates climate temp
and so on, causing a loop in Node-RED.

To stop a possible loop, as you have already dealt done, it is necessary to break the loop when the forced service call against either setting tries an update with the same value. Preventing the state-change from firing when the state value has not actually changed seems to be the key to preventing a loop.

So yes it works, and yes I have tested it.
No, I don’t think you can improve on this (other than by not doing it in the first place) but others may have ideas. The ‘feedback loop’ here is perhaps too invisible to be helpful for debugging and maintenance, but I can’t see an easy way to connect both parts together without making it less efficient.

The most critical issues are likely to be seen at start up, when the state values are unknown, undefined, or wrong. I wonder what happens on rebooting Node-RED.

As a small improvement, the change nodes you are using could be eliminated. The Event: state node allows for changing string states to numbers (State type) and also allow for ignoring current state values being undefined or unavailable or equal to the previous state. Personally I would use the settings in the node to do the hard work for me.

As an aside, for myself testing this against a air-conditioning unit where the climate entity has state value “on” or “off”, the temperature setting is down in the attributes, and so I had to get this (number) and do the check to see if it had really changed myself. I did this with a bit of JSONata in the output property, for msg.payload using J: expression

(
    $new:=$entity().attributes.temperature;
    $old:= $prevEntity().attributes.temperature;
    $new != $old and $new>0 ? $new
)

I guess the best option is to disconnect the feedback from the climate temperature back to the input_number slider and avoid the loop entirely!

Thats correct. I updated the screenshot in the first post, because I saw, that there was a mistake.

I will check, what will happen here in detail.

For Heating target temperature input_number changed I can set it, thank you for the feedback. For Heating target temperature climate changed this is I think not possible, because then it will not fire, when the attribute temperature is changed, if I understood it correctly. Furthermore as the temperature is an attribute I think I still need the switch node Target temperature really changed? and Transform Number to String.

That is not an opiton, because I need that the Heating target temperature input_number changed is always in sync with the current attribute temperature of the respective climate entity.

Enclose my updated flow:

[{"id":"cbf9ce0a79b00ab4","type":"server-state-changed","z":"916ac106527864fc","name":"Heating target temperature input_number changed","server":"7211bec6.fcfb3","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"input_number.keller_buro_heizung_zieltemperatur","entityIdType":"exact","outputInitially":false,"stateType":"num","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":290,"y":1620,"wires":[["23aaa2344581ac4b"]]},{"id":"23aaa2344581ac4b","type":"api-call-service","z":"916ac106527864fc","name":"Set climate target temperature","server":"7211bec6.fcfb3","version":5,"debugenabled":false,"domain":"climate","service":"set_temperature","areaId":[],"deviceId":["109b8603c323a2cb933059ca0f2d0ccd"],"entityId":[],"data":"{\"temperature\": msg.payload}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1270,"y":1620,"wires":[[]]},{"id":"c735d5e1140f130e","type":"server-state-changed","z":"916ac106527864fc","name":"Heating target temperature climate changed","server":"7211bec6.fcfb3","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"climate.keller_buro_heizung_thermostat","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":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"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":270,"y":1740,"wires":[["4658ea0edc818c45"]]},{"id":"435e81d794331277","type":"api-call-service","z":"916ac106527864fc","name":"Set input_number target temperature","server":"7211bec6.fcfb3","version":5,"debugenabled":false,"domain":"input_number","service":"set_value","areaId":[],"deviceId":[],"entityId":["input_number.keller_buro_heizung_zieltemperatur"],"data":"{\"value\": msg.payload}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1290,"y":1740,"wires":[[]]},{"id":"de4a331ca6e43ec2","type":"change","z":"916ac106527864fc","name":"Transform Number to String","rules":[{"t":"set","p":"payload","pt":"msg","to":"$string(data.new_state.attributes.temperature)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":960,"y":1740,"wires":[["435e81d794331277"]]},{"id":"4658ea0edc818c45","type":"switch","z":"916ac106527864fc","name":"Target temperature really changed?","property":"data.old_state.attributes.temperature","propertyType":"msg","rules":[{"t":"neq","v":"data.new_state.attributes.temperature","vt":"msg"},{"t":"eq","v":"data.new_state.attributes.temperature","vt":"msg"}],"checkall":"true","repair":false,"outputs":2,"x":630,"y":1740,"wires":[["de4a331ca6e43ec2"],[]]},{"id":"7211bec6.fcfb3","type":"server","name":"Home Assistant","version":4,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":false,"cacheJson":false,"heartbeat":false,"heartbeatInterval":"","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m"}]

Thanks a lot again for your feedback!

1 Like

My ‘heating device’ that I tested this with is an air-conditioning unit. The ‘target’ setting temperature is certainly in the attributes as the entity state is just ‘on’ or ‘off’.

My experience is that Home Assistant generates an event if either the state changes, or any one of the attributes changes. Hence there should be a trigger event on heating target temperature climate when the attribute temperature changes. And yes, I had to put in some extra JSONata to pick out the attribute temperature for both new and old states to do the compare by hand. It did work for me!

Anyway, to answer your original post - it does seem to work and I don’t think that there is not much to improve on, although someone else may know more than we do!

All the best with your project!

I quickly tested it again and for me it is only triggering if I unset the checkbox Current state equals previous state. I saw this hint also in other posts Monitor entitiy attributes change in Node Red and Detect State Change in Entity Attribute via Node Red ! . Mmh, it is interesting, that it is working for you.

Thanls again for your very good feedback, so I could optimze my flow and got a second opinion. Have a happy new year!

You are correct. To capture changes that only occur in the attributes, the event: state node has to pass all changes, so the checkbox has to be unticked. I was doing the compare in code, which makes for extra work but can be done.

It looks like there still where some wierd feedback loops, where I can’t found the reason. If I changed the target temperature via the slider, which goes to different values (e.g. from 10°C to 25°C in some steps), there was some kind of temperature hopping (see next screenshot from 10:15). Not always, but sometimes. This was only stopable, after I cut off the feedback loop in Node-RED and update.

grafik

I now try to cut of the feedback loop via a gate node. I will look if this helps.

[{"id":"cbf9ce0a79b00ab4","type":"server-state-changed","z":"916ac106527864fc","name":"Heating target temperature input_number changed","server":"7211bec6.fcfb3","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"input_number.keller_buro_heizung_zieltemperatur","entityIdType":"exact","outputInitially":false,"stateType":"num","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":290,"y":1700,"wires":[["7ed04b8ff196d0ab"]]},{"id":"23aaa2344581ac4b","type":"api-call-service","z":"916ac106527864fc","name":"Set climate target temperature","server":"7211bec6.fcfb3","version":5,"debugenabled":false,"domain":"climate","service":"set_temperature","areaId":[],"deviceId":["109b8603c323a2cb933059ca0f2d0ccd"],"entityId":[],"data":"{\"temperature\": msg.payload}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1310,"y":1700,"wires":[[]]},{"id":"c735d5e1140f130e","type":"server-state-changed","z":"916ac106527864fc","name":"Heating target temperature climate changed","server":"7211bec6.fcfb3","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"climate.keller_buro_heizung_thermostat","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":true,"ignoreCurrentStateUnavailable":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":270,"y":1800,"wires":[["4658ea0edc818c45"]]},{"id":"435e81d794331277","type":"api-call-service","z":"916ac106527864fc","name":"Set input_number target temperature","server":"7211bec6.fcfb3","version":5,"debugenabled":false,"domain":"input_number","service":"set_value","areaId":[],"deviceId":[],"entityId":["input_number.keller_buro_heizung_zieltemperatur"],"data":"{\"value\": msg.payload}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1330,"y":1800,"wires":[["8fd6a2727ea5cc83"]]},{"id":"de4a331ca6e43ec2","type":"change","z":"916ac106527864fc","name":"Transform Number to String","rules":[{"t":"set","p":"payload","pt":"msg","to":"$string(data.new_state.attributes.temperature)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":960,"y":1800,"wires":[["435e81d794331277","c2477d23d255be41"]]},{"id":"4658ea0edc818c45","type":"switch","z":"916ac106527864fc","name":"Target temperature really changed?","property":"data.old_state.attributes.temperature","propertyType":"msg","rules":[{"t":"neq","v":"data.new_state.attributes.temperature","vt":"msg"},{"t":"eq","v":"data.new_state.attributes.temperature","vt":"msg"}],"checkall":"true","repair":false,"outputs":2,"x":630,"y":1800,"wires":[["de4a331ca6e43ec2"],[]]},{"id":"c2477d23d255be41","type":"change","z":"916ac106527864fc","name":"Close Gate","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"close","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1010,"y":1760,"wires":[["7ed04b8ff196d0ab"]]},{"id":"5ef8afc62784b014","type":"change","z":"916ac106527864fc","name":"Open Gate","rules":[{"t":"set","p":"topic","pt":"msg","to":"control","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"open","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1470,"y":1860,"wires":[["7ed04b8ff196d0ab"]]},{"id":"7ed04b8ff196d0ab","type":"gate","z":"916ac106527864fc","name":"","controlTopic":"control","defaultState":"open","openCmd":"open","closeCmd":"close","toggleCmd":"toggle","defaultCmd":"default","statusCmd":"status","persist":false,"storeName":"memoryOnly","x":670,"y":1700,"wires":[["23aaa2344581ac4b"]]},{"id":"8fd6a2727ea5cc83","type":"delay","z":"916ac106527864fc","name":"","pauseType":"delay","timeout":"200","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1280,"y":1860,"wires":[["5ef8afc62784b014"]]},{"id":"7211bec6.fcfb3","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":false,"cacheJson":false,"heartbeat":false,"heartbeatInterval":"","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]