[Solved] How to find the First Occurrence in Time Range use Influxdb

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…

{
  "name": "DS_HallwayStairwell_Motion",
  "datapoints": "3",
  "data": [
    {
      "time": 1605719188000,
      "state": "OFF"
    },
    {
      "time": 1605719231000,
      "state": "OFF"
    },
    {
      "time": 1605719231000,
      "state": "ON"
    }
  ]
}

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

That’s not right either… add 1, if the datapoints are >0. So, >=23 + (number of sensors where datapoints were > 0).

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.

Thanks for providing more clarity on your suggestion.

Please help me understand what the timers do here. As written, it looks like it triggers after the 12 motions occur, no matter the timing.

Stupid me … I get it. The timer reduces the count at 30 minutes.

I ran into 2 issues with the suggested code.

timers.forEach[timer | timer.cancel ]

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.

OK, it wasn’t able to figure out it’s a list of Timers and is treating them as Objects.

You can cast them back to Timer

timers.forEach[timer | (timer as Timer).cancel ]

Or define the list so it knows it’s holding Timers

import java.util.List

...
var List<Timer> timers = newArrayList

You’ll want to use a Queue.

import java.util.List
import java.util.Queue

var motionCount = 0
var Queue<DateTime> motionTimes = newLinkedList
var List<DateTime> timers = newArrayList

rule "Look for awake status"
when
    Item gMotion received update ON
then
    if(awake_checking.state == OFF) return;

    if(motionCount >= 12) {
        firstMotionTime = motionTimes.peek()
        logInfo("awake", "People are awake and timestamp of first motion is " + firstMotionTime)
        awake_checking.sendCommand(OFF)
        timers.forEach[timer | timer.cancel ]
        timers.clear()    
        motionTimes.clear()
    }
    else {
        motionCount = motionCount + 1;
        motionTimes.add(now)
        timers.add(createTimer(now.plusMinutes(30), [ |
            motionCount - 1
            motionTimes.remove()
        ])
    }

end

Rich, Many thanks for your guidance. Much appreciated!

Love this solution. In testing it, I came across this error when installing the code:

Validation issues found in configuration model 'misc.rules', using it anyway:
Cannot cast from DateTime to Timer

Any ideas on the issue?

The definition of timers is wrong. It should be

var List<Timer> timers = newArrayList

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?

I did some more work on this and found the cause of the error. Fixed the error “assignValueTo” by placing a “val” in front of the line.

val firstMotionTime = motionTimes.peek()

I am still getting the message below when installing the code. The code appears to work though.

Cannot cast from DateTime to Timer

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.

There is nothing about any of the rules posted above that involves breaking changes as far as I can tell. It should work as written.

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.

Thanks for the guidance.