So after ignoring a pesky warning in the logs for quite some time about the eventual doom and deprecation of the ‘CreateTimerWithArgument’, I finally used the longer nights of our Southern Hemisphere winter to do something about it.
But I didn’t just want to replace that function, I also wanted to realign my rules away from a device-aligned structure, into a scenario aligned (playbook-like) structure.
The original device-aligned structure was not an intentional design choice I made, but as I was using UI based rules, I needed to keep all the manipulation of timers within the same UI script(s)…
So I did what any smart person would do… Look for the easy way out with some kind of new magic-wand function via the OpenHAB 4 wishlist. Funny thing is, with lots of helpful advice from @rlkoshak and @florian-h05, it turns out that what I was wishing for, already existed in a usable form within OpenHAB…
So I thought I might share what I did here, in case it helps someone else who is having similar ideas in relation to their rules. I have 2 disclaimers here:
#1 There is no right single answer for every problem, so adapt/ignore the following to suit your situation
#2 If you have spent dozen’s of hours writing 1000’s of lines of code getting your rules and timers working, don’t even look at @rlkoshak 's Openhab Rule tools… The heartache of all your code and time being replaced with simple, short rules, may be just too much to take
So lets start on what I did…
- Create rules aligned to Scenarios
Some examples below of what I mean by Scenario-aligned
So these examples are for when guests arrive/depart at the gate, or we arrive/depart the house.
- Break the actions down
As mentioned above, I used to have large monolithic rules aligned to devices, due to the constraints of the UI based timers. Using the timermgr from OpenHAB rule tools in conjunction with the Global Cache, I no longer have this restriction, and can manipulate the same timer from different rules/scripts.
Here is an example from one of the above rules:
I have also broken the executed script within a rule down into small function-aligned scripts - I have no idea if this is the most efficient way to run these, but it sure makes it easy as I drill-down into rules, then actions, to follow what is going on…
-
Move Simple item manipulation to Scenes
I previously used to have the (simple) manipulation of items in the Rule itself, but using a scene separates the behaviour somewhat, and it means you are not editing the rule to do an easy add/remove of an item (Which also reloads the rule upon save).
-
Use global cache for timers
So this enables the approach in items 1 & 2 above, and as an example, 2 different scripts from 2 different rules manipulating the same timer:
One version in the Gate guest arrive rule:
// This rule is used to turn on the Gate Button Lights and set a timer
// This will only turn on the button lights if night time (Astro managed item)
// This version runs upon the Guest arriving at the Gate
var {timerMgr} = require('openhab_rules_tools');
var timers = cache.shared.get('gate_tm', () => new timerMgr.TimerMgr());
itemName = "Gate_ButtonLights";
force_itemName = "Gate_Force_ButtonLights";
duration_itemName = "GateGuestArrive_GateButtonLightDuration";
duration = Number(items.getItem(duration_itemName).state);
var tmexpire = function(iname,forceiname) {
return () => {
items.getItem(iname).sendCommandIfDifferent("OFF");
};
}
if(items.getItem("Daylight").state === "OFF")
{
schedtime = time.ZonedDateTime.now().plusSeconds(duration);
items.getItem(itemName).sendCommandIfDifferent("ON");
timers.check(itemName+"_off_tm",schedtime.toString(),tmexpire(itemName,force_itemName),true,null,itemName+"_off_tm");
}
FYI - I already know there are redundant arguments used in the tmexpire function, but I have re-used this across many rules, some which do use this - so just keeping it consistent
And one in the Gate guest depart rule:
// This rule is used to turn on the Gate Button Lights and set a timer
// This will only turn on the button lights if night time (Astro managed item)
// This version runs upon the Guest departing at the Gate
var {timerMgr} = require('openhab_rules_tools');
var timers = cache.shared.get('gate_tm', () => new timerMgr.TimerMgr());
itemName = "Gate_ButtonLights";
force_itemName = "Gate_Force_ButtonLights";
duration_itemName = "GateGuestDepart_GateButtonLightDuration";
duration = Number(items.getItem(duration_itemName).state);
var tmexpire = function(iname,forceiname) {
return () => {
items.getItem(iname).sendCommandIfDifferent("OFF");
};
}
if(items.getItem("Daylight").state === "OFF")
{
schedtime = time.ZonedDateTime.now().plusSeconds(duration);
items.getItem(itemName).sendCommandIfDifferent("ON");
timers.check(itemName+"_off_tm",schedtime.toString(),tmexpire(itemName,force_itemName),true,null,itemName+"_off_tm");
}
On a naming convention whim (not based on science), I have chosen to create separate timermgr instances to group underlying timers:
- gate_tm - With off timers Gate close timer, gate button and garden light off timers
- heatpump_tm - With off/on timers for each heatpump within this instance
With the timers under these being the item name concatenated with “_off” or “_on”
- Move parameters into OpenHAB Items
I know this is pretty obvious, but when I originally wrote the first version of the code, I took some shortcuts, and just threw parameters into the code (e.g. gate close duration, or light turn off). In this refactor, I moved these into OpenHAB items, and read the values from there.
This means I can (eventually) create a UI ‘Administration’ Page for such aspects become ‘user’ configurable
duration_itemName = "GateGuestDepart_GateButtonLightDuration";
duration = Number(items.getItem(duration_itemName).state);
Just remember to throw a entry for every such item into the appropriate persistence file (in my case
/etc/openhab/persistence/influxdb.persist) so it saves the value, and brings it back on a restart.
GateGuestDepart_GateButtonLightDuration : strategy = everyChange, restoreOnStartup
Summary:
So with daylight savings only days away, any further major changes to my OpenHAB system will probably need to wait until next winter. But for now:
- I have tested the key functions that my original scripts used to perform, using these refactored versions, and they all seem to do the job very nicely
- My log is not filled with warnings about functions being deprecated
- I feel that the Rules/scripts etc are a lot easier to follow/maintain
- And yes - I have no doubt I will one day find some edge-case around this approach & the interaction of these rules/timers !!
Again, I am not suggesting this is the right way to do it, but it is just one way to do it.
I also know I can further tidy optimise this code, maybe even move some into NPM, so less duplication across rules etc.
Thanks again to those who gave me the advice and support, and I hope that someone finds the above useful.