How to calculate average dates of when an item has been switched on?

Hi all,
I would like to try and save on my electricity bill by controlling my upstairs electric heaters. I have seen some very good topics on how to do this with a predefined schedule.
On my research for documentations, I have seen the following paper from Microsoft where researchers have estimated the probability of room occupancy in the future based of comparison PIR response from the previous days. I find it really interesting as it does not involve machine learning but just comparison and averaging.
Anyway, I would like to implement something similar at home. I am triggering a switch every night before going to bed. This switch is used to switch off the house lights. It is not yet persisted.
My goal is to have the bedroom temperature at a predefined temperature at the moment I am triggering the switch. To do so, I need to have a prediction of the time when this switch will be triggered so I can preheat the bedroom.
I would like to do something like the following :

  • on Monday evening: collect the time of the last five Monday when the bed time switch is changed to ON.
  • Calculate the average for the collected times
  • use the resulting averaged time minus a certain duration (to be determined from experiments) to trigger the bedroom preheating.
  • repeat for each day of the week

How would you do the first task?

Thanks in advance.

Ludovic

I am interested too. Would like to know how to calculate how long the lights is on today and yesterday, last month etc

You might be able to this.

  1. Call val oneMon = Betime.historicState(now.minusDays(1)).getTimestamp
  2. Repeat for the remaining Mondays as far back as you want.
  3. getTimestamp returns a Date and you will probably want to convert it to a Joda DateTime val oneMonJDT = new DateTime(oneMon.getTime)
  4. Now use the hourOfDay and minuteOfHour or minuteOfDay to get to the time the switch was flipped prior to now.minusDays(1)
  5. Average them together to come up with the average time.

Note, the time in step 1 needs to be late enough that it is all but guaranteed to be after Bedtime is switched ON but before it is switched back OFF.

I’ve no idea if this will work and I have a vague memory that historicState might be broken right now. But it is how I would first attempt to approach the problem.

It’s a pretty clever yet simple approach. The community would benefit if you were to write up your progress.

This is a very different and actually harder question to answer. You have two approaches. Store the state of the Switch that represents the light every minute to a DB that stores the state of Switch Items as 1 for ON and 0 for OFF (InfluxDB does this, not sure of others). Then you can use sumSince to get the total number minutes that the light was ON from now to that point in time.

You will have to keep track of a running sum and the running sum for yesterday separately because sumSince always calculates from now backwards. You can’t pick two individual times.

Alternatively, you can write Rules to create a running total based on when the light turns ON and OFF. When the light turns ON set a timestamp. When the light turns OFF add the number of seconds that have passed to a running total. Again, you will need to keep the running totals for yesterday and beyond separately.

Hi,
Many thanks for your help. I will try to follow your guidelines and let you know.

However, I don’t really understand step 1. Why do I need to get the time stamp of the day before? If it is Thursday, then I will get the time stamp of Wednesday. Should the step 1 rather be to get the time stamp of seven days before?

Thanks again.

Ludovic

You need to use an Integer number in the function. @rlkoshak used that as an example.
If you keep 1 then run your calculation rule on a Tuesday with a cron statement.
And you will have:

val oneMon = Betime.historicState(now.minusDays(1)).getTimestamp // Yesterday Monday
val oneMon = Betime.historicState(now.minusDays(8)).getTimestamp // Last week Monday
val oneMon = Betime.historicState(now.minusDays(15)).getTimestamp // 2 weeks ago Monday
val oneMon = Betime.historicState(now.minusDays(22)).getTimestamp // 3 weeks ago Monday

Ok,
I think I understand now.

Ludovic

Hi there,
@rlkoshak, I tried your suggestion

As far as I understood from documentation, historicState will return the state of the item at the specified point of time, which is now.minusDays(1) in your suggestion.
Adding .getTimestamp in this case will result in just assigning to onMon the time stamp of now.minusDays(1).

In my case, I need to have the point if time from now.minusDays(x) when the item Bedtime switched from OFF to ON, or 0 to 1.

My item Bedtime switches from OFF to ON only once a day. I found how to get the point of time when Bedtime switched from OFF to ON on the day before by using Bedtime.previousState(true, "influxdb").getTimestamp. This will obviously not work for the previous days.

I cannot figure out how to get Bedtime.previousState(true, "influxdb").getTimestamp from now.minusDays(x)

Any ideas?

Ludovic

Hi there,
Just a quick update to say that I have found a way to get the time stamp of an event from a specified date:

val minus_days = 1 //start to look from the day before
val start_hour = 21 //start to look from 21hxx
val start_min = 0 //start to look from xxh00
val incr_min = 5 //increment of 5 min when looking (21h00, 21h05, ...)

//Time stamp to increment. This time stamp will be the one to be returned
val minuit_JDT = new DateTime(now.minusDays(minus_days).withTimeAtStartOfDay().plusHours(start_hour).plusMinutes(start_min))
//Time stamp specifying when to stop looking
val fin_boucle_JDT = new DateTime(now.minusDays(minus_days-1).withTimeAtStartOfDay())

//Loop to find the time stamp
while(minuit_JDT <= fin_boucle_JDT && Bedtime.historicState(minuit_JDT,"influxdb").getState <= 0)
{
    minuit_JDT = minuit_JDT.plusMinutes(incr_min)
}

Sorry, I don’t have time to explain more how it works. I will try to edit the current post and give more explainations when I can.

It needs to become more generic I think (should be able to handle boolean, string ,…, from .getState, …) and to handle exceptions (what to do when there is no data to get state from? …).

As I will have to use this code several times in a rule, does anyone know how to convert it into a lambda (or a script)? By the way, which one is more adapted?

Also, out of curiosity, if I call twice the same lambda/script in a rule, will two instances of the lambda/script be called and will they run in parallel?

Technically, it returns a HistoricItem that has a number of methods, one of which is to get the state of the Item at that time. But we are using the getTimestamp method here to get the timestamp that is stored with the Item in the database. So if you are using a changed strategy in persistence, it will be the time that the Item changed to that state prior to now.minusDays(1).

See Archived Projects | The Eclipse Foundation

It makes no sense for getTimestamp to return the same date/time you passed to it to create it in the first place. If that is indeed what is happening then I’d argue it is a bug and an issue should be filed.

boolean is not a valid Item state. See Items | openHAB for a list of all the Items and the types the Items store.

historicState will return null if there is no data at that time.

You can’t pass arguments nor get a return value from a Rules DSL Script.

Lambdas are Objects. If you create the lambda as a global and you call twice or more from Rules running at the same time, they will be sharing the same lambda and depending on timing and shared resources they will stomp on each other.

This is one of the reasons I frequently recommend against the use of lambdas.

However, this is a case where a lambda can be quite useful. So what I would recommend, since you only need to call it from the same Rule, is to create the lambda within the Rule. This will give each instance of the Rule running its own copy of the lambda Object to work with.

Many thanks for your answer

I think I know why it behaves like this, when I tried it to use historicState, I used it with an item using an “every minute” persistence strategy.

I am not sure to understand. Even if the persistence strategy if every change, wouldn’t historicState return null the item was not persisted at now.minusDays(1)?

I will try and make a lambda out of the code and post an update.

Ludovic

I’m not sure how far back it goes but if you don’t have a value at that specific instant, it looks backwards until it finds the first entry in the DB and that is what gets returned. I’m sure there is a limit, maybe one day, maybe longer, maybe shorter.

Ok, thanks

Hi there,

I have made some progress. I now have a lambda and a rule to calculate the average of the dates. I have modified a bit how I want to get the average of the dates:

  • If today is a week day, calculate the average of the occurence of the item from the past 5 week days
  • If today is during the weekend, calculate the average of the occurence of the item from the
    last weekend

The lambda is shown below with some notes:

    // Lambda function to get the first time a given item was triggered starting FROM a specific date

    var historic = [
            GenericItem item_to_look, //self explained
            GenericItem minus_days, //number of days since today from when to look
            GenericItem days, //stopping day
            GenericItem start_hour | //start to look from this specified hour

            val start_min = 0 //start to look from this specified minutes
            val incr_min = 5 // increment

            //The current lambda will do the following
            //- start to look from minus_day at the specified start_hour and specified start_min (for instance from 5 days ago at 18h00). This will be the first value assigned to date_to_look_JDT
           //- Return the start date (first value of  date_to_look_JDT) if the state of item_to_look is null (in case item_to_look is not yet persisted, for instance)
           //- compare the state of item_to_look at the speficied date (date_to_look_JDT) to the desired state (0 in this case)
            //- if the state of item_to_look is not the desired one, increment date_to_look_JDT with the value incr_min
            //- if the state of item_to_look is not the desired one and date_to_look_JDT is now the stopping date (fin_boucle_JDT), date_to_look_JDT is set to fin_boucle_JDT and retu$
            //- if the state of item_to_look is the desired one, return date_to_look_JDT

            val date_to_look_JDT = new DateTime(now.minusDays(minus_days).withTimeAtStartOfDay().plusHours(start_hour).plusMinutes(start_min)) //Initialization
            val fin_boucle_JDT = new DateTime(now.minusDays(days).withTimeAtStartOfDay())

            while(date_to_look_JDT <= fin_boucle_JDT && item_to_look.historicState(date_to_look_JDT,"influxdb").getState <= 0)
            {
                    date_to_look_JDT = date_to_look_JDT.plusMinutes(incr_min)
                    start_min = start_min + incr_min
            }
            logInfo("erru","date_to_look_JDT:  " + date_to_look_JDT)
            date_to_look_JDT
    ]

The rule using the above lambda is shown below. I am using an array to store the values of
date_to_look_JDT converted to minutes of day. This allows one to compute the average for an arbitrary amount of dates.

    rule "presence"
    when
            Item Dummy received command
    then
            val start_hour = 20
            val minus_days = 0

            val minus = 7
            val i = 1
            val inrc_ctrl = 0

            val infoarray = newArrayList() //Array where will be the storage of the numbers of minutes of day from the lambda return for each call of the said lambda 

            var Number day_of_week = now.getDayOfWeek

            if(day_of_week < 6) //if now is during week days
            {
                    while(minus_days < 7) //go through the last 7 days
                    {
                            if(day_of_week-i < 1) minus_days = i + 2 //allows to skip days of weekend
                            else minus_days = i

                            i = i + 1
                            infoarray.add(historic.apply(avg_MqttMuwaveSensorLampeSalon,minus_days,minus_days-1,start_hour).getMinuteOfDay())
                            //Convert date_to_look_JDT from the lambda to the equivalent number of minutes from that date
                            //populate infoarray with the converted number minutes of day from date_to_look_JDT
                    }
            }
            else //if now is during the weekend. Go through Saturday and Sunday from the week before
            {
                    if(day_of_week == 6) inrc_ctrl = 0
                    else inrc_ctrl = 1

                    while(minus_days < (7 + inrc_ctrl))
                    {
                            minus_days = i + 5 + inrc_ctrl
                            i = i + 1
                            infoarray.add(historic.apply(avg_MqttMuwaveSensorLampeSalon,minus_days,minus_days-1,start_hour).getMinuteOfDay())
                    }
            }

            //The part below will calculate the average from minutes of day gathered from the above part of the rule
            val buff = -1
            val avg = 0
            val it = infoarray.listIterator()

            while(it.hasNext())
            {
                    buff = it.next()
                    avg = avg + buff
            }
            avg = avg/infoarray.size()
    //      logInfo("erru","Average " + avg)

            //Add the determined average to today
            val estimated_JDT = new DateTime(now.withTimeAtStartOfDay().plusMinutes(avg))
            logInfo("erru","Estimated time of event " + estimated_JDT)

    end

The log showing the results is given below:

    2018-10-31 20:38:44.319 [INFO ] [.eclipse.smarthome.model.script.erru] - date_to_look_JDT:  2018-10-30T21:10:00.000+01:00

    2018-10-31 20:38:44.368 [INFO ] [.eclipse.smarthome.model.script.erru] - date_to_look_JDT:  2018-10-29T20:05:00.000+01:00

    2018-10-31 20:38:44.506 [INFO ] [.eclipse.smarthome.model.script.erru] - date_to_look_JDT:  2018-10-26T20:40:00.000+02:00

    2018-10-31 20:38:44.532 [INFO ] [.eclipse.smarthome.model.script.erru] - date_to_look_JDT:  2018-10-25T20:00:00.000+02:00

    2018-10-31 20:38:44.586 [INFO ] [.eclipse.smarthome.model.script.erru] - date_to_look_JDT:  2018-10-24T20:10:00.000+02:00

    2018-10-31 20:38:44.601 [INFO ] [.eclipse.smarthome.model.script.erru] - Estimated time of event 2018-10-31T20:25:00.000+01:00

It works! But it is still missing some generalization.

Ludovic

EDIT: modfied the lambda to take into consideration the case when the item is not yet persisted

1 Like