As a general rule, if you are asking a device to do something you should use sendCommand.
If you are updating the state of something based on something (e.g. a sensor is telling you a light is on so update the light switch’s state to ON) use postUpdate.
No, there isn’t. This is a complicated one that I ended up solving by creating the Dead Man’s Switch Design Pattern.
The tl;dr is create a state that defaults to “MANUAL”. Whenever the device is controlled by a Rule this state gets set to “RULE” (you can use multiple states if you need to distinguish between startup, timers, etc) while the rule is running and then goes back to MANUAL. Have a rule that triggers any time the device receives an update. If the state is “MANUAL” you know this is a command that was triggered from the device or from the sitemap. If not you know it came from a Rule. With this information you can check in the second rule where the command came from and behave accordingly.
I haven’t moved this design pattern into its own thread yet because it is complicate, a little brittle (because the timing needs to be managed) and this use case doesn’t come up very often.
I use it to set an override flag on my lights. During the day if the weather says its cloudy the lights turn on. If one of us manually turns the light on or off we want it to stay that way, overriding the weather behavior.
It turns out to be less useful than it could be in my case because the physical switches don’t report their current state and the refresh is too long.
To expand your minimal example:
var String whoCalled = "MANUAL"
rule "rule 1"
when
Item Door_Sensor changed from CLOSED to OPEN
then
whoCalled = "RULE"
//... logic...
Ceiling_Light.sendCommand(ON)
//...
timer = createTimer(deadline) [|
//... the normal timer logic
Ceiling_Light.sendCommand(OFF)
]
Thread::sleep(100) // give the commands a chance to hit the bus and the rule below to trigger
whoCalled = "MANUAL"
end
rule "rule 2"
when
Item Ceiling_Light received update
then
if(whoCalled != "RULE") {
if (timer != null) {
timer.cancel
}
}
end