ON/OFF Loop via a rule

I am trying to control a motor where when set to ON it will run for 3 seconds then stop for 3 seconds indefinately until set to OFF

I had it working once where it cycled through but didn’t loop, now it throws an exception and lately it says Im missing all sorts. I’m not sure what I’m missing.

Any advice helpful

var Timer timer = null

rule "siren"
when Item GMotor changed
then
    if (GMotor.state == ON) {
        timer = createTimer(now.plusSeconds(3), [ |

                val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
                mqttActions.publishMQTT("siren","1")

else            val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
                mqttActions.publishMQTT("siren","0")
                timer.reschedule(now.plusSeconds(3))
        ])

    } else if (GMotor.state == OFF) {
        timer = null
                val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
                mqttActions.publishMQTT("siren","0")

 }
end

That’s all a bit jumbled. Using { } with if/else is a good idea. You need to finish creating your timer block ] before continuing with an if/else from outside of the timer block.

Relevant discussion

Thanks. I have re-worked it based on the examples and with the rule “Siren OFF” removed it will loop sending 1 all of the time. It won’t send 1, 0, 1, 0

When I put in the rule “Siren OFF” it will send 1 then throw an exception. If i try rule “Siren OFF” in a different file it says it can’t find TimerH but when I look at the MQTT traffic it’s still sending 1 in the background.

var Timer TimerH = null

rule "Siren"
when
Item GHSiren changed to ON
then
if(TimerH != null) {
TimerH.cancel
TimerH = null
}

TimerH = createTimer(now.plusSeconds(1)) [|

         if(GHSiren.state==ON){

         val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
         mqttActions.publishMQTT("siren","1")
}
    else{

         val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
         mqttActions.publishMQTT("siren","0")

    }

    TimerH.reschedule(now.plusSeconds(1))

]

end

rule "Siren OFF"
when
Item GHSiren changed to OFF
then
GHSiren.sendCommand(OFF)
if(TimerH != null) {
TimerH.cancel
TimerH = null
}
end

Well, yes. When you define a variable at the head of a rule file, outside of any rule, it is only available to rules in that file.

Okay, so your GHSiren Item is the “master” control, right? All the time that this is ON, you want the siren to beep-beep.

In the LED examples, they look at the on/off state of the led to decide about turning the led off/on, in order to flash. That’s a completely different function to the master control.
You can’t use your GHSiren Item for this purpose as well.

You could use the state of your siren to find out if it is running or not, but you’ve chosen not to have a siren Item, just broadcast MQTT. So you cannot tell.

You’ll need something else to keep track of your siren, so that your rule knows when to turn it on/off.

var Timer TimerH = null
var boolean SirenRun = false

rule "Siren start or stop"
when
   Item GHSiren changed
then
   if(TimerH != null) {
          // tidy away any old timer
      TimerH.cancel
      TimerH = null
   }
   if (SirenRun) {
         // stop sound if running now
      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
      mqttActions.publishMQTT("siren","0")
      SirenRun = false
   }

   TimerH = createTimer(now.plusSeconds(10)) [ |
      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
      if (SirenRun) {
         mqttActions.publishMQTT("siren","0")
         SirenRun = false
      } else {
         mqttActions.publishMQTT("siren","1")
         SirenRun = true
      }
     TimerH.reschedule(now.plusSeconds(10))
   ]
end

That’s perfect, thank you

Testing just now and have activated it via a switch on a sitemap. I have it running but when changing to OFF the rule is still running, how do I stop it?

As i have an item GHSiren which activates the script, can it not stop it as well?

Oh, I missed the conditional for creating the timer

...
   if (SirenRun) {
         // stop sound if running now
      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
      mqttActions.publishMQTT("siren","0")
      SirenRun = false
   }
   
   if (GHSiren.state == ON) {
      TimerH = createTimer(now.plusSeconds(10)) [ |
         val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
         if (SirenRun) {
            mqttActions.publishMQTT("siren","0")
            SirenRun = false
         } else {
            mqttActions.publishMQTT("siren","1")
            SirenRun = true
         }
        TimerH.reschedule(now.plusSeconds(10))
      ]
   }
end

It says it’s missing ‘}’ at ‘end’

I’ve put it in where I thought it should be but it’s still running!

You’ve probably got an orphaned timer or three running in the background now, that happens when you edit rules files while a timer is waiting for its timeslot.

Restart openHAB ans see what is left.

I have restarted openhab but the rule file won’t do anything now. Whether the switch in the site map is ON or OFF there’s no MQTT being sent anymore.

I restarted openhab again and it looks to be working as expected now.

Thank you!

That’s working great. What would be involved in adding a run timer to it. So once the siren got triggered it cycled on then off for 5 mins then stops completely and the script exits?

1 Like

Why not put a 5 minute expire on your GHSiren master Item to set it back OFF

So I have tried to tidy it up a bit, defining better variables to suit the project but yet again I’ve ended up in a knot.

I have defined SirenControl which is looking for MQTT values coming from another program. This is working and I can see the variable changing in events.log but the script is not doing anything now. The 3 controls it’s looking for is GHSirenAP1, GHSirenAP2 and GHSirenStop. I’m not sure where to put the stop.

I have 3 scenarios I am trying to get in here. 1 (known as AP1) is to run the siren for 7 seconds then off for 4 in a cycle for 5 mins. I’ve not figured out how to do the 5 min thing yet). The second is to run the siren . The third is to stop either the first or second.

None of the options are working anymore.

var Timer TimerH = null
var boolean SirenRun = false

rule "Siren start or stop"
when
   Item SirenControl changed
then
   if(TimerH != null) {
          // tidy away any old timer
      TimerH.cancel
      TimerH = null
   }
   if (SirenRun) {
         // stop sound if running now
      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
      mqttActions.publishMQTT("siren","0")
      SirenRun = false
   }

      if (SirenControl == "GHSirenAP1") {
   TimerH = createTimer(now.plusSeconds(7)) [ |
      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
      if (SirenRun) {
         mqttActions.publishMQTT("siren","0")
         SirenRun = false
      } else {
         mqttActions.publishMQTT("siren","1")
         SirenRun = true
      }
     TimerH.reschedule(now.plusSeconds(4))
   ]

if (SirenControl == "GHSirenAP2") {
      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
      mqttActions.publishMQTT("siren","1")
      SirenRun = true

}
}
end

Okay, let’s analyse the rule as it stands.

Rule runs whenever SirenControl changes.

First thing it does is cancel any running timer and silence the siren, regardless of what value SirenControl has.
Hint - that means any value will work as a “stop”.

Then you do a comparison with SirenControl. That’s an error - SirenControl is an Item object (I guess?) with type, name. label, blah. What you are interested in the Item’s state.

The new final section will (if you fix the problem about state comparison) start the siren forever.

If you want unequal periods for the timers rescheduling, then you will have to do different rescheduling within the timer body. The createTimer time is, naturally enough, only used when you create the timer to begin the on-off timing loop.

I think I have it working.

Does the below look like it should be stable enough?

Final edit - The below seems to be fully working now. Hopefully no infinite loops. I’m still not sure how to do the cancel after 30 minutes. Any advice on this final hurdle? I need AP1 and AP2 to run for 15mins max

var Timer TimerH = null
var boolean SirenRun = false

rule "Siren start or stop"
when
   Item SirenControl changed
then
   if(TimerH != null) {
          // tidy away any old timer
      TimerH.cancel
      TimerH = null
   }
   if (SirenRun) {
         // stop sound if running now
      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
      mqttActions.publishMQTT("siren","0")
      SirenRun = false
   }

      if (SirenControl.state == "GHSirenAP1") {
   TimerH = createTimer(now.plusSeconds(1)) [ |
      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
      if (SirenRun) {
         mqttActions.publishMQTT("siren","0")
         SirenRun = false
      } else {
         mqttActions.publishMQTT("siren","1")
         SirenRun = true
      }
     TimerH.reschedule(now.plusSeconds(5))
   ]
}
if (SirenControl.state == "GHSirenAP2") {
val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
      mqttActions.publishMQTT("siren","1")
      SirenRun = true
}

if (SirenControl.state == "GHSirenTest") {

      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
         mqttActions.publishMQTT("siren","1")
         SirenRun = true

 TimerH = createTimer(now.plusSeconds(5)) [ |
         mqttActions.publishMQTT("siren","0")
         SirenRun = false
         SirenControl.postUpdate("GHSirenStop")
   ]
}

if (SirenControl.state == "GHSirenStop") {

      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
         mqttActions.publishMQTT("siren","0")
         SirenRun = false
      SirenControl.postUpdate("GHSirenStop")
}
end

Advice same as before, use expire to set your master control back to off/stop after whatever period you settle on.
I’m not going to deliver a tutorial on how to install and use the expire binding, there are lots of examples.

That’s not the only way, you could use a timer instead, your rule already contains examples of timer use.

Your beep-beep loop does not perform the asymmetric times that you previously wanted.

Your new ‘stop’ - EDIT - ‘test’ section need only do the postUpdate in its timer block; that will cause a change causing the rule to trigger and perform the actual stop actions.

Your new “stop” section is unnecessary, as the rule already performs the stop actions every time the rule is run, before doing anything new.

Thank you, I’ll look at expire, I use that elsewhere.

The beep beep loop works as expected. I had to drop the first timer to 1 as this seems to be a start delay. If i had it at 10 nothing would happen for 10 seconds then after that it loops based on the bottom timer. So with the example above there is a 1 second pause then 5 seconds on, 5 seconds off.

With the stop section, I need to update the item as I am calling all of these from a php script, in this case I can put the postUpdate into (sirenRun) to achieve the same result?

I don’t know what that means. Not sure why you need to update the Item to Stop when you’ve first tested to see if it is already Stop.

I’ll try your logic and see if it works.

With regards to the expire, is this something that gets added to the timer.reschedule line?

The on/off loop is working in a way that I don’t fully understand which is what’s confusing me a bit on where to put the expire line

Nope, expire is not a rule thing at all. It’s a binding, and you link it to an Item, configuring an expiry time and an action to perform on that Item. Its entirely independent of rules.

   // first we create a timer, scheduled for the future
   // at the future time, it will run the code between the square brackets [ ] 
TimerH = createTimer(now.plusSeconds(1)) [ |
      val mqttActions=getActions("mqtt","mqtt:broker:MQTTBroker")
           // if the siren is running now, turn it off
      if (SirenRun) {
         mqttActions.publishMQTT("siren","0")
         SirenRun = false
           // if the siren is not running now, turn it on
      } else {
         mqttActions.publishMQTT("siren","1")
         SirenRun = true
      }
          // timer will only run one time, unless we do this  -
     TimerH.reschedule(now.plusSeconds(5))
          // we keep doing that, so it will run forever 
          // until something external cancels it
   ]

Ah, it’s starting to sink in now

So with the expire binding, if I link it to SirenControl which I have defined as an item, this would cancel anything running for say 15 minutes but it means that I couldnt have P1 cancel after 15mins and P2 after 30 without defining another item?