[SOLVED] Rule for gate status monitoring

  • Platform information:
    • Hardware: x86_64/2GB/80GB
    • OS: Linux (Ubuntu 18.04)
    • Java Runtime Environment: openjdk-8-jre-headless, openjdk version “1.8.0_191”
    • openHAB version: 2.4.0 (in docker)
  • Issue of the topic: As decribed below
  • Please post configurations (if applicable):
    • Items configuration related to the issue: Defined in PaperUI, basing on 1-W search
    • Sitemap configuration related to the issue: No sitemap so far
    • Rules code related to the issue: No rules so far
    • Services configuration related to the issue: Only opencloud integration and other non related
  • If logs where generated please post these here using code fences: …

I’m not so strong in C++ so maybe someone could point me some solution. I have written rule that should monitor gate status and remaid me every 10m via Chromecast (here only log) that it’s not closed, but I cannot round my measurements to minutes.

rule “Timer-reminder for opened gate”
when
// Tamper sensor (OFF=open)
Item GateState changed to OFF
then
var int GateNow = 0
var int GateFOpen = 0
var int GateOpen = 0
var int i = 1
GateFOpen = now.millis
GateOpen = now.millis
logInfo(“rules.IB”,“Monitoring gate status…”)
while (i < 2) {
Thread::sleep(1000)
GateNow = now.millis
// To be changeg for 600000 (10m) later
if(GateNow >= (GateOpen + 10000)) {
if(GateState.state == ON) {
logInfo(“rules.IB”,“Gate closed.”)
i = 2
} else {
var int HowLong = ((GateNow - GateFOpen) / 60000)
logInfo(“rules.IB”,“Gate opened for " + Math::round(HowLong) + " minutes…”)
GateOpen = now.millis
}
}
}
end

Execution fails with:
2019-03-23 11:18:32.937 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘Timer-reminder for opened gate’: An error occurred during the script execution: Could not invoke method: java.lang.Math.round(float) on instance: null

1 Like

Don’t bother figuring out what’s wrong with the current rule: you should not make use of Thread::sleep for more than a second or so, as explained here.

There are two alternatives: Timers (also briefly explained by Rich in the mentioned thread) and Expiring items.

Something like this will probably work. Two rules and a repeating timer:

var Timer tmGateOpen = null
var DateTime dtGateOpened = null

rule "Timer-reminder for opened gate"
when
    // Tamper sensor (OFF=open)
    Item GateState changed to OFF
then
    var String tag = "rules.IB"
    logInfo(tag, "Gate just opened, remember start time")

    tmGateOpen?.cancel  // Cancel any running timer, just to be sure
    dtGateOpened = now    // Gate just opened, remember

    tmGateOpen = createTimer(now.plusMinutes(10), [ |
        // Report gate still open
        logWarn(tag, "Gate opened for " + now.millis - dtGateOpened.millis + " minutes...")
        //:TODO: Send notification or whatever...
        tmGateOpen.reschedule(now.plusMinutes(10))  // Check back in 10 minutes
    ])
end

rule "Gate closed reminder"
when
    Item GateState changed to ON
then
    logInfo("rules.IB","Gate closed")
    tmGateOpen?.cancel  // Cancel the timer
    GateOpened = null   // Forget opened time
end

Thanks! I’ll have to rewrite some of my rules in that case…

  1. What if i have to run few actions in rule in propper sequence (one after another)? I should place timer between them or run the long one action in timer?
  2. Why long action inside timer is better than thread sleep? Is’t thread bussy when timer is running?

What do you mean by long? Can you give an example of what could take a long time to run?

For examlpe, when door bell is pressed in front of my house, I have to:

  1. set cctv camera to propper position (i takes 1-3 secs),
  2. turn on light outside (if it is night, before taking pictures),
  3. take picture and send it via telegram,
  4. volume up chomecas Google Mini speakers (it takes 1-2 sec),
  5. run door bell ring on them (after vol up),
  6. turn off outside light (afterr all above operations).
    So as you see, runing in propper sequence is cruitial for me.

Another one (main gate opening):

  1. when opened and its dark outside turn on light,
  2. set cctv camera on position 1,
  3. take 1 photo,
  4. send 1 photo with notofication,
  5. set cctv camera on position 2,
  6. take 2 photo,
  7. send 2 photo,
  8. play TTS notification on Google Mini via chromecast.

It’s easy to do it with a little state machine, like this one:

This is a step-by-step with option to wait from step to step. The big advantage is, there is only one timer to do the job, only one thread, every step is only a few millis runtime.
Please be aware that you have to build the state machine slightly different, as you have to ensure the timer isn’t reset while scheduled, so use a if(myTimer === null) and don’t forget to set myTimer = null as the very last step. This will ensure the code isn’t started more than once (so no reentrant lock needed).

1 Like

Thanks… I’m working on my rules, try to rewrite them, but why this simple switch/case does’t work?

var Timer tRBW = null
var Number nRBW = 0

nRBW have “1” value but never get classified to run lines from “case 1:”.

PS Where I should check “if(myTimer === null)” and where I should set “myTimer = null”, and why?

As text:
tRBW?.cancel
nRBW = 0
tRBW = createTimer(now, [|
nRBW += 1
logInfo(“rules.IB”,"nRBW = " + nRBW) // This logs “1” value
switch (nRBW) {
case 1 : {
logInfo(“rules.IB”,“K1”)
tRBW.reschedule(now.plusSeconds(2))
}
case 2 : {
logInfo(“rules.IB”,“K2 2222”)
tRBW.reschedule(now.plusSeconds(2))
}
case 3 : {
logInfo(“rules.IB”,“K3 3333”)
tRBW.reschedule(now.plusSeconds(2))
}
case 4 : {
logInfo(“rules.IB”,“K4 4444”)
tRBW.reschedule(now.plusSeconds(2))
}
case 5 : {
logInfo(“rules.IB”,“K5 5555”)
tRBW.reschedule(now.plusSeconds(2))
}
case 6 : {
logInfo(“rules.IB”,“K6 6666”)
tRBW?.cancel
}
default: {
logInfo(“rules.IB”,“DEF”)
logInfo(“rules.IB”,"nRBW = " + nRBW) // This too
tRBW?.cancel
//myTimer = null
}
}
])

Where do you define the var tRBW and nRBW?

// global var is defined at the begin of the rules file!
var Timer  tRBW = null
var Number nRBW = 0

rule "create timer"
when
    Item someItem received command   // some trigger to start...
then
    if(tRBW === null) {              // timer isn't started yet, so 
        nRBW = 0                     // initialize counter
        tRBW = createTimer(now, [ |  // and create the timer
            nRBW += 1                // count up
            logInfo("rules.IB","nRBW = {}", nRBW)
            switch (nRBW) {          // test counter
                case 1 : {
                    logInfo("rules.IB","K1")
                    tRBW.reschedule(now.plusSeconds(2))
                }
                case 2 : {
                    logInfo("rules.IB","K2 2222")
                    tRBW.reschedule(now.plusSeconds(2))
                }
                case 3 : {
                    logInfo("rules.IB","K3 3333")
                    tRBW.reschedule(now.plusSeconds(2))
                }
                case 4 : {
                    logInfo("rules.IB","K4 4444")
                    tRBW.reschedule(now.plusSeconds(2))
                }
                case 5 : {
                    logInfo("rules.IB","K5 5555")
                    tRBW.reschedule(now.plusSeconds(2))
                }
                default : {                                    // last step, so
                    logInfo("rules.IB","last step {}", nRBW)
                    tRBW = null                                // delete pointer
                }
            }
        ])
    }
end

You don’t need to cancel the timer, the timer is already expired, as the code is actually executed. instead, do not reschedule the timer (but don’t forget to set the timer to null as the last step)

Thanks for code, again. Var definition is at the very beggining of that rules file:
“1: ////// Automaty Rules File
2: var Timer tRBW = null
3: var Number nRBW = 0
…”

I’m confused, because that rule still never gets into “case 1”, “2”, etc. It’s from beginning goes straight to “default”. I can’t understand why…

Bellow fragment of log file:
“2019-03-24 20:40:35.899 [INFO ] [ipse.smarthome.model.script.rules.IB] - START
2019-03-24 20:40:35.901 [INFO ] [ipse.smarthome.model.script.rules.IB] - nRBW = 1
2019-03-24 20:40:35.901 [INFO ] [ipse.smarthome.model.script.rules.IB] - DEF
2019-03-24 20:40:35.902 [INFO ] [ipse.smarthome.model.script.rules.IB] - nRBW = 1”.

It’s going to be one of those odd effects of different number types.

Try
switch (nRBW.intValue)

Great! It works! Many thanks. :wink:

Even though it appeared to be an integer, and not 1.0, to begin with. Funny things, Numbers. :crazy_face: