var Number runtime
val ZonedDateTime Start = now().withHour(0).withMinute(0).withSecond(0)
runtime = (Debug.averageSince(Start) * Duration.between(Start, now).getSeconds()).doubleValue
Debug_Runtime.postUpdate(runtime)
What do you mean by “won’t work?” What result are you getting and what are you expecting?
Why this matters is rrd4j only saves one record or second, even if the item changes faster than that.
I doubt averageSince is the right function call to get the total duration. Let’s say the switch is saved as 1 when ON and it’s been on for an hour. The average value for the switch over the last hour will be 1.
rule "Runtime - Test"
when
Time cron "0 40 7 1/1 * ? *"
then
var Number runtime
val ZonedDateTime Start = now
Thread::sleep(60000)
Debug.sendCommand(ON)
Thread::sleep(10000)
runtime = (Debug.averageSince(Start) * Duration.between(Start, now).getSeconds()).doubleValue
Debug_Runtime.postUpdate(runtime)
// expected: 10s
Thread::sleep(10000)
runtime = (Debug.averageSince(Start) * Duration.between(Start, now).getSeconds()).doubleValue
Debug_Runtime.postUpdate(runtime)
// expected: 20s
Thread::sleep(10000)
runtime = (Debug.averageSince(Start) * Duration.between(Start, now).getSeconds()).doubleValue
Debug_Runtime.postUpdate(runtime)
// expected: 30s
end
2023-05-30 07:41:00.053 [INFO ] [...] - Item 'Debug' received command ON
2023-05-30 07:41:00.053 [INFO ] [...] - Item 'Debug' changed from OFF to ON
2023-05-30 07:41:10.055 [INFO ] [...] - Item 'Debug_Runtime' changed from 2300 s to 42.003782042302845 s
2023-05-30 07:41:20.056 [INFO ] [...] - Item 'Debug_Runtime' changed from 42.003782042302845 s to 53.33978057158102 s
2023-05-30 07:41:30.057 [INFO ] [...] - Item 'Debug_Runtime' changed from 53.33978057158102 s to 64.29389067524116 s
I would expect to have a “ON” - time of 10seconds, 20seconds, … and not 40+ seconds.
MDAR
(Stuart Hanlon, UK importer of Velbus hardware)
4
I’d like do something similar with my boiler call relay, to know how long it’s been active for.
I would approach this with a
when boilerCall changed
I’d save the Epoch time of the change to a variable and compare that to the next change.
The problem I see with using a Cron trigger is that you could miss many changes between triggers.
I think to make this work using this approach, you’ll have to use a periodic strategy (e.g. every second) in your persistence so that when the time period is small there’s more than one entry for averageSince to work with.
A more direct approach would be something like @MDAR describes.
rule "Calculate On Time"
when
Item MySwitch changed from ON to OFF
then
val lastUpdate = MySwitch.lastUpdate
val delta = Duration.between(lastUpdate, now)
var oldOnTime = if (MySwitchOnTime.state == NULL || MySwitchOnTime.state === UNDEF) MySwitchOnTime.state as QuantityType else 0 | s
MySwitchOnTime.update(oldOnTime.plus(delta.toSeconds()))
end
I’m not 100% positive on the cast to QuantityType. Working with units in Rules DSL is a pain.
In JS Scripting it would look something like:
var lastUpdate = items.MySwitch.history.lastUpdate();
var delta = time.Duration.between(lastUpdate, time.toZDT());
var mySwitchRuntime = items.MySwitch
var oldOnTime = (!mySwitchRuntime.isUninitialized) ? mySwitchRuntime.quantityState : Quantity('0 s');
mySwitchRuntime.postUpdate(oldOnTime.plus(Quantity(delta.seconds(), 's'));
In both cases, the way the code works is when the Switch changes from ON to OFF we pull when it turned ON from the database and calculate the number of seconds between now and then and add it to the running total.
If you are looking for the amount time it was ON for the full day, you can create a rule to reset at midnight.
Regarding @MDAR approach: I would like to have the runtime available while the item is in ON state. This would only be available if the item is turned OFF.
I think to make this work using this approach, you’ll have to use a periodic strategy (e.g. every second) in your persistence so that when the time period is small there’s more than one entry for averageSince to work with.
I only had a every5Minutes Persistence strategy, therefore it couldn’t work. I’ve changed over to a presistence strategy with everySecond. With this I get the following results:
2023-06-01 22:11:00.027 [INFO ] [openhab.event.ItemCommandEvent ] - Item 'Debug' received command ON
2023-06-01 22:11:00.027 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Debug' changed from OFF to ON
2023-06-01 22:11:10.029 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Debug_Runtime' changed from 0 s to 34.76296497809341 s
2023-06-01 22:11:20.031 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Debug_Runtime' changed from 34.76296497809341 s to 39.76303792395171 s
2023-06-01 22:11:30.032 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Debug_Runtime' changed from 39.76303792395171 s to 44.763091991763595 s
If I make the times between the calculations bigger, I get the following results:
2023-06-01 22:25:00.864 [INFO ] [openhab.event.ItemCommandEvent ] - Item 'Debug' received command ON
2023-06-01 22:25:00.916 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Debug' changed from OFF to ON
2023-06-01 22:26:00.870 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Debug_Runtime' changed from 0 s to 61.94773108544652 s
2023-06-01 22:27:00.875 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Debug_Runtime' changed from 61.94773108544652 s to 121.8762837580829 s
2023-06-01 22:28:00.880 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Debug_Runtime' changed from 121.8762837580829 s to 181.8226757794214 s
Looks like that these formula won’t work for very short durations. For longer durations everythings works as expected.
It gets more complicated but you could use a Design Pattern: Looping Timers to calculate the time ON instead of persistence.
rule "MySwitch turned ON"
when
Item MySwitch changed to ON
then
// Do nothing if this happens. It means the switch was turned OFF and then ON again
// in less than a second.
if(sharedCache.get('MySwitchTimer') !== null) {
logDebug('MySwitch', 'Timer already exists! Ignoring as the timer is already running.')
return;
}
privateCache.put('MySwitch_timer', createTimer(now.plusSeconds(1), [ |
val state = MySwitch_OnTime.state as QuantityType
// Initialize the count Item if it's NULL or UNDEF
if(state == NULL || state == UNDEF) {
MySwitch_OnTime.postUpdate('1 s')
}
// Add one second to the total time
else {
MySwitch_OnTime.postUpdate(state.plus('1 s'))
}
// Reschedule if it's still ON, exit the timer if not
if(state == ON) {
privateCache.get('MySwitch_timer').reschedule(now.plusSeconds(1)
}
else {
privateCache.put('MySwitch_timer', null)
}
]))
end
This will be accurate to within a second. Additional book keeping and an another rule would be required to track to the millisecond or nanosecond. You’ll need to use the sharedCache for the second rule to see the Timer though.
Instead of using a global variable, this uses the privateCache which will cancel the timer for us when the rule is unloaded instead of leaving it orphaned to create an exception in the logs later. We create a timer for one second from now which adds one to the Time Item and if the Switch is still ON it reschedules the timer for another second. Once the Switch is no longer ON the loop will exit.