You can do this using the REST API. A call like this…
curl -X GET --header "Accept: application/json" "http://localhost:8080/rest/persistence/items/DS_HallwayStairwell_Motion?starttime=2020-11-18T12%3A06%3A00"
… made using executeCommandLine will return all motion detections since the starttime. In your rule, you will need to calculate the time from 30 minutes ago. Example results…
You would need to do the same for all 5 motion sensors. SwitchItems and ContactItems are stored in persistence with two values, except for the latest one, in order to make a nice step in graphs. So, if you add up the datapoints value from all of the sensors and it is >=23, there have been 12 motion detections within the last 30 minutes. Double check the logic though, because something seems off. For 5 sensors, maybe >=28?
Thanks for the clues. Using your suggestion, here’s my code. Any suggestions for tweeks? The only downside is that several timers will be started, be running after one of them triggers.
rule "Look for awake status"
when
Item gMotion received update ON
then
if (awake_checking.state == OFF) return;
val motionTime = now.getMillis()
createTimer(nowplusMinutes(30), [|
if (awake_checking.state == ON) {
val awakeMotions = gMotion.sumSince(now.minusMinutes(30), "influxdb") as Number
if (awakeMotions >= 12) {
logInfo("awake", "People are awake and millis timestamp of first motion = " + motionTime.toString)
awake_checking.sendCommand(OFF)
}
}
]
end
Another thought … would this approach even work? It shuts down all launched awake timers within one minute of hitting 12 motions.
rule "Look for awake status"
when
Item gMotion received update ON
then
if (awake_checking.state == OFF) return;
var awake_min_left = 30
val motionTime = now.getMillis()
var awake_timer = createTimer(nowplusMinutes(1), [|
if (awake_checking.state == ON) {
val awakeMotions = gMotion.sumSince(now.minusMinutes(30), "influxdb") as Number
if (awakeMotions >= 12) {
< execute actions >
awake_checking.sendCommand(OFF)
}
else {
if (awake_min_left-- > 0)
awake_timer.reschedule(now.plusMinutes(1) )
}
}
]
end
I updated my code for some syntax errors. However, the code fails with an exception. Since the timer is called multiple times, I suspect the reference to the timer name is not unique, throwing the error. That means the timers can only be launched for the 30 minute duration and not extended. Is there a way to make this code work?
rule "Look for awake status"
when
Item gMotion received update ON
then
if (awake_checking.state == OFF) return;
var awake_min_left = 30
val motionTime = now.getMillis()
var Timer awake_timer = createTimer(now.plusMinutes(1), [|
if (awake_checking.state == ON) {
val awakeMotions = gMotion.sumSince(now.minusMinutes(30), "influxdb") as Number
if (awakeMotions >= 12) {
< execute actions >
awake_checking.sendCommand(OFF)
}
else {
if (awake_min_left-- > 0)
awake_timer.reschedule(now.plusMinutes(1) )
}
}
])
end
Exception seen (trimmed):
Job DEFAULT.Timer 23 2020-11-18T11:10:41.029-08:00:
Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
org.eclipse.xtext.xbase.impl.XIfExpressionImpl@6b440ac4
(conditionalExpression: false)} ] threw an unhandled Exception:
Caused by: org.eclipse.smarthome.model.script.engine.ScriptExecutionException:
The name 'awake_timer' cannot be resolved to an item or type; line 67, column 21, length 11
I don’t think you understood what I was referring to. Persistence wouldn’t play a part in this.
var motionCount = 0
var firstMotionTime = now.minusDays(1) // set it sometime way in the past just to have a value and type
var timers = newArrayList
rule "Look for awake status"
when
Item gMotion received update ON
then
if(awake_checking.state == OFF) return;
// first motion of the day, take a timestamp
if(motionCount == 0) {
firstMotionTime = now // why only save the millis? A DateTime is so much more useful
}
// We reached 12 motions in 30 minutes, do the awake stuff and reset everything
else if(motionCount >= 12){
logInfo("awake", "People are awake and timestamp of first motion is " + firstMotionTime);
awake_checking.sendCommand(OFF);
timers.forEach[timer | timer.cancel ]
timers.clear()
}
// Increment the counter and schedule a timer to decrement it in 30 minutes
motionCount = motionCount + 1;
timers.add(createTimer(now.plusMinutes(30), [ | moitionCount -1 ] );
end
Presumably you have a rule that runs after midnight to reset awake_checking back to ON.
Any variable you want to survive one run of the rule to the next needs to be defined as a global variable. In this case you need to keep the count and the reference to the Timers in global variables.
The above throws an exception: ‘cancel’ is not a member of ‘java.lang.Object’. Is there an import statement needed?
The code also does not capture the timestamp of the first motion in the time range once the timers start shifting the count. I want to know the timestamp of the first motion of 12 that occur within 30 minutes.
If I capture the timestamp for each motion in an array and use the timer to shift a pointer to that array, I can obtain the correct timestamp. However, I can’t figure out the proper code for this.
With the change, I am still getting this error when implementing the code.
var List<Timer> timers = newArrayList
I also get an execution error when it triggers on this line:
firstMotionTime = motionTimes.peek()
The error:
Rule 'Look for awake status': An error occurred during the script execution: Couldn't invoke 'assignVa
lueTo' for feature JvmVoid: (eProxyURI: misc.rules#|::0.2.3.2.0.1.1.0.2::0::/1)
Does using peek() return the first value in the queue?
Resolved all issues and code Rich provided works without problems. I integrated the code and the implementation error went away. Not sure why - probably typos.
Rich, thanks so much for your help. I hope this forum post helps others.
At long last, I am converting to OH3. I have resolved most conversion issues except one, this code. I am at a loss as to how to get this working in OH3. Appreciate any guidance from the community.
This code is a rolling 30-minute window looking for 12 motions to indicate someone is awake and returns the timestamp of the first motion of the 12.
Sorry, I should have included the log and specific code.
2021-12-01 10:04:53.587 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'dash.rules', using it anyway: │
The field Tmp_dashRules.motionTimes refers to the missing type Object
Here’s the code in question.
import java.util.List
import java.util.Queue
var motionCount = 0
var Queue<DateTime> motionTimes = newLinkedList
var List<Timer> timers = newArrayList
var awake_timer_first_motion = null
var sleep_timer_first_motion = null
var Timer sleep_looking_timer = null
rule "Check if awake by counting inside motions during rolling 30 min"
when
Item gMotion received update ON
then
if (vsleep_state.state == "awake" || traveling.state == ON) return;
if (awake_checking.state == OFF) {
if (peeps_home.state == ON &&
now.getHourOfDay >= 4 &&
now.getHourOfDay < 10) {
awake_checking.sendCommand(ON)
motionCount = 0
}
else return;
}
motionCount = motionCount + 1
if(motionCount >= 12) {
awake_timer_first_motion = motionTimes.peek().getMillis()
to_awake.sendCommand(ON) // set awake
// another rule acts on awake change and unwinds the timmers
}
else {
motionTimes.add(now)
timers.add(createTimer(now.plusMinutes(30), [ |
motionCount = motionCount - 1
motionTimes.remove()
]))
}
end
That’s an Info level log. It’s not even an error. But it is referring to one of the breaking changes that was announced and there are tons of postings about on the forum. There is no more DateTime. It has been replaced with ZonedDateTime. And the methods on ZonedDateTime have changed slightly. See DateTime Conversion (openHAB 3.x)
That log message is referring to motionTimes so look at the lines that reference that variable. You’ll notice that it’s defined as a Queue<DateTime>. There is no DateTime any more. Now it’s a ZonedDateTime.
Continuing on we see the there is an attempt to call getMillis() on the Object stored by the Queue. That method doesn’t exist on ZonedDateTime. And as far as what’s posted here it’s not even used anyway. Assuming it’s used in that other rule, why use millis? Keep it as a ZonedDateTime and you’ll have a lot more flexibility and the code will be easier to read and understand.