Issue of the topic: Configure a loop to turn on → wait 20 minutes ->turn off → wait 20 minutes → loop (turn on again and so) until a proxy item switch is turned off.
Can you help me make a rule that controls a sonoff simple switch to turn on and off a heating fan. The idea is to switch on a proxy switch (Calefaccion) that turns on the sonoff (Calefactor) → wait 20 minutes ->turn off → wait 20 minutes → loop (turn on again and so) until the proxy item (Calefaccion) switch is turned off, breaking the loop and turning the sonoff OFF.
Here is what I have since I could not make the timers work as intended, creating an uncontrolable loop that kills my server by sending constant ON/OFF commands like crazy.
rule "Calefactor ON"
when
Item Calefaccion changed from OFF to ON
then
if (Calefaccion.state == ON) {
while (Calefaccion.state == ON) {
Calefactor.sendCommand(ON)
Thread::sleep(1200000)
Calefactor.sendCommand(OFF)
Thread::sleep(1200000)
}} else { Calefactor.sendCommand(OFF)}
end
The only proper way to implement this will be to use a Timer. While this rule is running it could be triggered again but those subsequent triggers will just queue up and cause the rule to run again once the rule finally ends (hours later perhaps). With Timers, the rule only spends a few milliseconds running meaning that triggers of the rule don’t get queued up.
Furthermore, with the sleeps, if you change Calefaccion’s state to OFF, it could be up to 40 minutes before that rule will exit. With a timer, you can immediately cancel the timer.
I might recommend looking into Blockly going forward. There’s a looping timer block available on the marketplace that implements this pretty straight forwardly with minimal work. Trigger the rule on any change to “Calefaccion”.
If Calefaccion changed to ON we immediately command the Calefactor Item to ON (I use BoundSwitch here as that’s the name of a test switch In happened to have handy). Then create a looping timer which runs every 20 minutes that toggles the Calefactor Item. The loop will continue until the ProxySwitch (Calefaccion Item in this case) is no longer ON.
That last part is there because the block requires it. But in fact it’s not strictly needed because if Caledaccion changed to anything other than ON, we cancel the looping timer so it’s no longer running and we immediately command the Calefactor Item to OFF.
Doing this in Rules DSL is largely the same logic.
rule "Calefactor Changed"
when
Item Calefaccion changed
then
if(newState == ON) {
privateCache.put('MyTimer', createTimer(0, [ |
Calefactor.sendCommand(if(Calefactor.state == ON) OFF else ON)
privateCache.get('MyTimer').reschedule(now.plusMinutes(20))
])
}
else {
privateCache.get('MyTimer')?.cancel
Calefactor.sendCommand(OFF)
}
end
I strongly encourage the use of the cache over global variables. The logic above is pretty much the same as the Blockly. If Calefaccion changed to ON we create a Timer that runs immediately which toggles Calefactor and then. reschedules itself to run in 20 minutes when it toggles Calefactor again. If Calefaccion changed to OFF, we cancel the Timer and command Calefactor to OFF.
Is this rule working with OH3? I cannot make it work, double checked my things/items but no luck. There are things that I don’t know how to work with like privateCache.get and new things to me.
Can Blockly be backed up? I only use DSL rules because its easy to backup and restore the whole OH configuration.
No, the cache is a map. The first argument is the key i.e. 'MyTimer' and the second argument is the Timer itself (createTimer...).
It should. The overall concept is the same as would be used from OH 1 forward.
However, the privateCache and sharedCache I’m not sure existed in OH 3, or if it does which version of OH 3. But the docs are at Textual Rules | openHAB. Look at the version of the docs for 3.4 to verify.
Of course. All managed configs are included when you use openhab-cli backup and if you do your own backups they are located in /var/lib/openhab/jsondb.
Without using the cache the rule would look something like:
var Timer myTimer = null;
rule "Calefactor Changed"
when
Item Calefaccion changed
then
if(newState == ON) {
myTimer = createTimer(0, [ |
Calefactor.sendCommand(if(Calefactor.state == ON) OFF else ON)
myTimer.reschedule(now.plusMinutes(20))
])
}
else {
myTimer?.cancel
Calefactor.sendCommand(OFF)
}
end
You could write that rule in JRuby (which supports openhab 3.4) like this:
rule "Cycle the heater" do
received_command Calefaccion
run do |event|
if event.on?
Calefactor.on
after(20.minutes, id: Calefactor) do |timer|
Calefactor.toggle
timer.reschedule unless timer.cancelled?
end
else
timers.cancel(Calefactor)
Calefactor.ensure.off
end
end
end
Your trigger is item changed, not received command.
Although I prefer received command (and therefore you need to send a command to trigger it), you can make it trigger the same way as your original rule:
rule "Cycle the heater" do
changed Calefaccion
run do |event|
if event.on?
Calefactor.on
after(20.minutes, id: Calefactor) do |timer|
Calefactor.toggle
timer.reschedule unless timer.cancelled?
end
else
timers.cancel(Calefactor)
Calefactor.ensure.off
end
end
end
Still not working, I don’t know what is wrong, the item can be directly controlled and if I test the proxy switch with the ‘real’ heater item (making it point to a simple command ON and toggling from the sitemap) it works but obviously, no rule is taking place and its not the desired behaviour.
I tested the rule above, it works with event.on? to check the changed state.
Can you try this with added logging:
rule "Cycle the heater" do
changed Calefaccion
run do |event|
if event.on?
logger.info "Starting"
Calefactor.on
after(2.seconds, id: Calefactor) do |timer|
logger.info "Toggling"
Calefactor.toggle
timer.reschedule unless timer.cancelled?
end
else
logger.info "Stopping"
timers.cancel(Calefactor)
Calefactor.ensure.off
end
end
end
2024-05-23 23:42:33.841 [INFO ] [ort.loader.AbstractScriptFileWatcher] - Loading script '/C:/openhab/conf/automation/ruby/Calefactor.rb'
2024-05-23 23:42:36.515 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/C:/openhab/conf/automation/ruby/Calefactor.rb': Error during evaluation of Ruby in /C:/openhab/conf/automation/ruby/Calefactor.rb at line 1: (NoMethodError) undefined method `rule' for main:Object
I really, really thank you for your time and support but this is going to be a headache for contingency reasons (if I have to reinstall OH for whatever reason in the future) only for one rule to work. All my rules are DSL ‘vanilla’ as much as possible for this reason, I even use DSL for easy backup too; so I prefer to stick to DSL scripts, until maybe someday I migrate to blockly.
// Definition of global vars at start of file!
var Timer tCalefactor = null
rule "Calefactor timed ON/OFF or OFF"
when
Item Calefaccion changed
then
tCalefactor?.cancel // dismiss timer, if present
if(newState == OFF) {
if(Calefactor.state != OFF) Calefactor.sendCommand(OFF) // switch Item OFF if not OFF
} else
tCalefactor = createTimer(now, [| // create Timer and start immediately
Calefaccion.sendCommand(if(Calefaccion.state != ON) ON else OFF) // toggle Item
tCalefactor.reschedule(now.plusMinutes(20)) // reschedule Timer
])
end
Thanks Udo, that rule was almost perfect for my needs; I just had to change the items on: Calefaccion.sendCommand(if(Calefaccion.state != ON) ON else OFF)
for: Calefactor.sendCommand(if(Calefactor.state != ON) ON else OFF)
and everything works as expected. I tested it by changing the (now.plusMinutes(20)) to (now.plusSeconds(3)) and worked like I needed. Now I will put back the (now.plusMinutes(20)) and its done.