Node Red state trigger below a value - fire once only?

I am trying to get node red automation going where i trigger a lamp to come on when a lux sensor drops below 50 lux. I’m using a state node which is set to fire when the value is <50 and it works fine.

The problem is the sensor updates every 5 minutes, and it fires again every time as it is below 50. It only stops when lux = 0 as then ‘state = previous state’ or when it goes back above 50. How can i get a ‘fire once when below 50’ type of trigger?

This is a question of ‘state memory’, since your flow does not remember that it has already turned the lamp on.

There are several approaches that you could try.

Since, I assume, you are using the Event: state node, at every state change / update this node receives both the current state and the previous state. The node configuration condition will normally only consider the current state, however you can use JSONata and the $prevEntity().state expression so as to compare both.

In the ‘If State’ option we can test for a state condition, but if you select the JSONata option (bottom of the list) then the condition test is a JSONata expression that is evaluated, and must return true for the flow to continue from the top node exit.

$prevEntity().state>="50" and $entity().state<"50"

This expression is only true when the falling lux state value transitions from over 50 to under 50 in the sensor update.

Here I have retained the node ‘State value’ as string rather than number, although it is easy in this case to use ‘State value’ number and remove the quotes from the expression.

This should work and fire once and once only where there is a regular declining lux state value that goes between over 50 to under 50 in one update. Problems will occur if the state value goes ‘unavailable’ at the critical point, as we are relying on the previous value being the ‘memory’. Going from 60 to ‘unavailable’ to 40 would fail to trigger correctly.

A more robust approach is to hold ‘switched memory’ somewhere, either as a new binary switch entity in Home Assistant, or as a context value in Node-RED.

If, for example, on switching on the lamp your flow set flow context variable “lamp” to “on” (very easy to do in a Change node) the conditional test could be modified to check that the lamp was “off” and lux below 50, thus only firing as required.

$entity().state<50 and $flow.context("lamp") = "off"

To access any HA entity, for example a binary sensor holding the state value of the lamp (“on” or “off”) you can use

$entity().state<50 and $entities('binary_sensor.my_lamp').state = "off"

Other nodes are available. The Trigger state node does much the same as Events: state, but enables multiple conditions to be included by configuration. Since these conditions are all ‘and’, you could test

  • this entity
  • current state
  • <50

and then add a second condition

  • entity_id : binary_sensor.my_lamp
  • current state
  • off

The Trigger: state node makes it easier as no JSONata is required, but it does not have the facility to code for ‘unavailable’, so extra conditions would have to be added to test for current state is not previous state, and current state is not ‘unavailable’ if these are required.

And a third approach would be to accept that the Event: state node is going to trigger every five minutes during the night, but make the flow more resilient to this. Using the Delay node set to Rate Limit messages to, say, one per hour or even 2 per day would cut down on the noise.

Just to point out sending the same command over and over has no effect on an entity. Turning a light on that is already on is basically ignored by HA.

There is also the filter node. This node requires the payload to change before it can pass again. Using turn_{{payload}} in the call service and sending on/off from it’s respective trigger.

image

[{"id":"ddccd19468a58f8c","type":"server-state-changed","z":"0f8b525850b0c871","name":"turn on, send on","server":"","version":5,"outputs":2,"exposeAsEntityConfig":"","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"50","ifStateType":"num","ifStateOperator":"lt","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"on","valueType":"str"}],"x":260,"y":1980,"wires":[["d38c1a5d8936f2b7"],[]]},{"id":"bd7ddb8a42eb8ee8","type":"server-state-changed","z":"0f8b525850b0c871","name":"turn off, send off","server":"","version":5,"outputs":2,"exposeAsEntityConfig":"","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"50","ifStateType":"num","ifStateOperator":"gte","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"off","valueType":"str"}],"x":260,"y":2080,"wires":[["d38c1a5d8936f2b7"],[]]},{"id":"d38c1a5d8936f2b7","type":"rbe","z":"0f8b525850b0c871","name":"","func":"rbe","gap":"","start":"","inout":"out","septopics":false,"property":"payload","topi":"topic","x":490,"y":2020,"wires":[["8a0675a6337d58ce"]]},{"id":"8a0675a6337d58ce","type":"api-call-service","z":"0f8b525850b0c871","name":"turn_{{payload}}","server":"","version":5,"debugenabled":false,"domain":"light","service":"turn_{{payload}}","areaId":[],"deviceId":[],"entityId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":700,"y":2020,"wires":[[]]}]

Many thanks @Biscuit - i understand the implications and the code there. I think then, to cover both robustness and simplicity, a binary helper in HA is the best bet that turns on if <50. I can then reference that from NR as the trigger. Never thought of that so thanks for the pointer!

Hi @Mikefila - agreed, i watched for a while to see if there was any negative impact. The issue is it triggers the first step in a dimming schedule; if it triggers again the schedule goes back to the start. I think @Biscuit has pointed me towards the simplest solution…

Edit - i’ve just read your reply (again) and digested it properly. The filter node looks like it will also do the job nicely, thanks for the tip!