I have a weather station that measures rain precipitation in the form of a motonic counter with an upper limit. When that limit is reached, it goes back to 0 before starting to increase again:
This works just fine by giving me the difference in value between the past date and the current date but it fails when the period covers the “roll over” event.
In that case, it gives me a very negative value which is not representative at all of the reality.
Just to make sure I understand, the sensor counts up over days until the max is exceeded and then it starts over. It doesn’t reset under any other circumstances (time, power loss, etc.), correct?
I once wrote a rule template to handle something similar to this but unfortunately it’s not one that I rewroite for OH 4. It’s been on my list but no one really used it so it’s never been a priority, and there is another rule template that does mostly the same thing only better.
Both are focused on energy reading but the same approach applies here I think. You’ll need a proxy Item for tracking the daily rainfall.
At a high level trigger the rule on changes to the sensor Item.
If the current state is > the previous state
a. Add the difference between the previous state and the current state tracking Item
else
b. Add the new value to the current state tracking Item
At midnight trigger a rule to reset the tracking Item to 0, or just let it continue to increase forever. Unless your name is Noah you are unlikely to see enough rain over a lifetime to exceed the maximum value allowed. I’m not sure which will work best for your evolution calculation. I think resetting would still work except between midnight and 1 am perhaps so maybe that needs a work around if you reset it.
This will essentially eliminate that rollover (the following assumes a managed rule and JS Scripting and UoM).
var currReading = Quantity(event.itemState);
var prevReading = Quantity(event.oldItemState);
var total = items.ProxyItem.quantityState;
if(currReading.lessThan(prevReading)) {
total = total.add(currReading);
} else {
const delta = currReading.minus(prevReading);
total = total.add(delta);
}
items.ProxyItem.postUpdate(total);
You will use ProxyItem in your current rule.
Other notes, time.toZDT() can make your time code a lot simpler:
const minus_1h = time.toZDT('PT-1H'); // ISO8601 duration string
const start_of_day = time.toZDT('00:00'); // supports AM/PM as well as 24 hour time strings
If you keep the now.withHour(.... approach make sure to add withMillis(0) or else you could be off of exactly midnight by almost a second.
(function(){
const cumulativeItem = items.Cotech367959_zigbee2mqtt_Rain_amount_cumulative;
const monotonicItem = items.Cotech367959_zigbee2mqtt_Rain_amount_monotonic;
const isFromStateChange = (event.itemState && event.oldItemState);
// use "dummy" values when the rule has not been triggered from a real change
const currReading = (isFromStateChange) ? Quantity(event.itemState) : Quantity(cumulativeItem.quantityState);
const prevReading = (isFromStateChange) ? Quantity(event.oldItemState) : currReading.subtract("4mm");
// make sure to use an initial value when the item has just been created
let total = Quantity(monotonicItem.quantityState ?? "0mm");
if(currReading.lessThan(prevReading)) {
total = total.add(currReading);
} else {
const delta = currReading.subtract(prevReading);
total = total.add(delta);
}
if (isFromStateChange)
monotonicItem.postUpdate(total);
else
console.log(`Not a real event, but would have changed from ${monotonicItem.quantityState} to ${total}`);
})
();
I’m using the IIFE (Immediately Invoked Function Expression) syntax to be able to use let/const at the top level, avoiding pollution of the global scope.
That uses the privateCache to store the previous reading and it uses the ability to pass arguments to transformations so they can be made more generic. See JavaScript Scripting - Automation | openHAB for details). In this case we pass the name of the Item as an argument.
Note that the global scope for a transformation and a rule does not extend beyond the rule itself. It’s good to get in the habbit of using let and const and being concerned about global scope and all that but when it comes to OH rules, you only really need to worry about the global scope when you are importing third party libraries.
It’s a good habit but technically unncessessary in this context. It’s also unnecessary in the transform above.
Well, thing is, if I’m not doing this, I can’t manually run the script to observe its behavior more than once. On the second run, I get errors saying that the let variables are already declared.
Well, maybe it’s only in the script part, but all in all, it’s an habit that I have taken up on doing.
That’s an interesting suggestion indeed. One less item, but can’t access the original “looping” value anymore to compare the two. I’ll have to figure out what’s best.
Indeed, you can’t use const and let in the global context of a script action. This is only a limitation for maanged rules.
What I was referring to though was polluting the global context. If you are not importing a library, what you see in front of you is the entire global context. There’s nothing to pollute. But you do have to use var in that case.
And I’m not saying it’s wrong. I just dont’ want someone to come along and thing that it’s required. That’s alll.
That’s what the cache is for, though now that I look at it I’ve a bug in my implementation above. I’ll fix it. I never actually store currReading into the cache after that initial get.
But the previous reading is supposed to be stored in the cache so it can be pulled and compared in place of the separate Item.