[SOLVED] Trying to check historicState between two dates and getting the average

I’m using OH 3.3 in a RPi 4 with SSD, persistence in MySQL.

Everything is working flawlessly and I have multiple other rules just working fine. But now I want to create a more dynamic rule in order to have a better irrigation system, like so:

var Timer offTimer = null

rule "Irrigation system"
when
    Time cron "0 0 0 ? * *"
then
    // Set start and end times for humidity measurements
    val ZonedDateTime istart = now.minusDays(1).withHour(8).withMinute(0).withSecond(0).withNano(0)
    val ZonedDateTime iend = now.minusDays(1).withHour(20).withMinute(0).withSecond(0).withNano(0)

    // Get all humidity readings between start and end times
    val humidityReadings = OutsideHumidity.historicState(istart, iend).states

    // Calculate average humidity level
    val averageHumidity = humidityReadings.map[state | state as Number].reduce[sum, state | sum + state] / humidityReadings.size // Use reduce() to calculate the average

    // Check if average humidity is above 90%
    if (averageHumidity >= 90) {
        logInfo("Irrigation system", "Average humidity is above 90%. Irrigation system will not activate.")
    } else {
        logInfo("Irrigation system", "Average humidity is below 90%. Irrigation system will activate.")

        // Activate irrigation system
        IrrigationOutput1.sendCommand(ON)
        offTimer = createTimer(now.plusMinutes(5), [|
            IrrigationOutput1.sendCommand(OFF)
            IrrigationOutput2.sendCommand(ON)
            offTimer = createTimer(now.plusMinutes(5), [|
                IrrigationOutput2.sendCommand(OFF)
                IrrigationOutput3.sendCommand(ON)
                offTimer = createTimer(now.plusMinutes(5), [|
                    IrrigationOutput3.sendCommand(OFF)
                    IrrigationOutput4.sendCommand(ON)
                    offTimer = createTimer(now.plusMinutes(10), [|
                        IrrigationOutput4.sendCommand(OFF)
                        IrrigationOutput5.sendCommand(ON)
                        offTimer = createTimer(now.plusMinutes(10), [|
                            IrrigationOutput5.sendCommand(OFF)
                        ])
                    ])
                ])
            ])
        ])
    }
end

Problem is, I am getting this error message in the log:

[ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'exterior-2' failed: An error occurred during the script execution: Could not invoke method: org.openhab.core.persistence.extensions.PersistenceExtensions.historicState(org.openhab.core.items.Item,java.time.ZonedDateTime,java.lang.String) on instance: null in exterior

What am I doing wrong here and how can I achieve this?

Thanks in advance. :slight_smile:

I think historic state needs just one date cause it gives one state from this date.

<item>.historicState(ZonedDateTime)

Whats about

<item>.averageBetween(ZonedDateTime, ZonedDateTime)

This should do what you want…
Greets

1 Like

I’m not aware of such a method as .states for .historicState.
Please be aware that .historicState() will give you one value.

But you don’t need to calculate average yourself, there is a method…

val averageHumidity = OutsideHumidity.averageBetween(istart,iend)

Another thing is the timer for cycling through the vents. Maybe a somewhat cleaner solution:

var Timer tIrrigation = null
var Integer iIrrigation = 0

rule "Irrigation system"
when
    Time cron "0 0 0 ? * *"
then
    // Set start and end times for humidity measurements
    val ZonedDateTime istart = now.minusDays(1).withHour(8).withMinute(0).withSecond(0).withNano(0)
    val ZonedDateTime iend = now.minusDays(1).withHour(20).withMinute(0).withSecond(0).withNano(0)

    // Get average humidity level
    val averageHumidity = OutsideHumidity.averageBetween(istart, iend)

    // Check if average humidity is above 90%
    if (averageHumidity >= 90) {
        logInfo("irrigation", "Average humidity is above 90%. Irrigation system will not activate.")
        // stop the rule
        return;
    } 
    
    logInfo("irrigation", "Average humidity is below 90%. Irrigation system will activate.")

    // initialize the counter
    iIrrigation = 0
    // create the timer to start in 10 Milliseconds
    tIrrigation = createTimer(now.plusNanos(10000000), [|
        // not first run?
        if(iIrrigation > 0)
            // then switch off the vent
            sendCommand("IrrigationOutput"+iIrrigation.toString,"OFF")
        
        // count up
        iIrrigation += 1
        // is there another vent to switch on?
        if(iIrrigation > 5)
            // if not, stop the timer code
            return;
        
        // switch the vent on
        sendCommand("IrrigationOutput"+iIrrigation.toString,"ON")
        // reschedule the timer (the last two vents need more time)
        tIrrigation.reschedule(now.plusMinutes(if(iIrrigation < 4) 5 else 10))
    ])
end

Using a Group Item for the vents would be better (let’s call it gVents, just a Group)

var Timer tIrrigation = null

rule "Irrigation system"
when
    Time cron "0 0 0 ? * *"
then
    val ZonedDateTime zdtStart = now.minusDays(1).withHour( 8).withMinute(0).withSecond(0).withNano(0)  // set start time
    val ZonedDateTime zdtEnd   = now.minusDays(1).withHour(20).withMinute(0).withSecond(0).withNano(0)  // set  end  time
    val averageHumidity = OutsideHumidity.averageBetween(zdtStart, zdtEnd)                              // get average 

    if (averageHumidity >= 90) {                                                                        // Check if above 90%
        logInfo("irrigation", "Average humidity is above 90%. Irrigation system will not activate.")
        return;                                                                                         // stop the rule
    } 
    logInfo("irrigation", "Average humidity is below 90%. Irrigation system will activate.")

    tIrrigation = createTimer(now.plusNanos(10000000), [|                                               // create the timer to start in 10 Milliseconds
        var Integer iMarker = 0                                                                         // create local var
        val active = gVents.members.sortBy[ name ].filter[ i | i.state == ON ].head                     // get active vent
        if(active !== null)                                                                             // check if there is any
            iMarker = Integer.parseInt(active.name.substring(active.name.length()-1))                   // get the digit
        
        gVents.members.filter[i|i.state != OFF].forEach[v|v.sendCommand(OFF)]                           // switch off any vent which isn't
        iMarker += 1                                                                                    // count up
        gVents.members.filter[i|i.name.endsWith(iMarker.toString)].head.sendCommand(ON)                 // open the next vent, if there is any
        if(iMarker <= gVents.members.size)                                                              // out of bound?
            tIrrigation.reschedule(now.plusMinutes(if(iMarker < 4) 5 else 10))                          // reschedule timer
    ])
end
2 Likes

Yes, you are correct, thank you for that! :slight_smile:

Thank you SO much for your kind help!

Your code works flawlessly. Incredible.

Wish you a great day. :smiley:

You’re welcome :slight_smile:

1 Like