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
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…
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?
Why long action inside timer is better than thread sleep? Is’t thread bussy when timer is running?
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).
// 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…