Design Pattern - Timer Management

The python version is OH 2.5 only. The JavaScript version is OH 3 only.

I’ve not the time right now to update the Python to 3, though prs are always welcome. The challenging part is too implement it in such a way as to not lose 2.5 support as well which makes it more than just replacing joda.

I tried to replace the relevant code by the new snippets. But it seems not as easy :slight_smile:
I’m struggling at the moment with python on my openhab3 system. Maybe it was the wrong decision to got with python, because at the moment it’s not fully supported and the acceptance in the community seems low.
Anyone has a best practice with oh3 python without timer management class?

That’s why I haven’t done it yet myself. :wink:

It’s important to remember that almost everything to do with a Timer is working with Java classes. Therefore, with some minor adjustments to account for different syntax in the various languages, creating and interacting with a Timer is going to be the same. They are all working with the same Java Timer and ZonedDateTime class.

Therefore, you should be able to use a JavaScript example for creating a Timer or a Rules DSL example for creating a Timer and, with some minor changes to account for differences in the language, it will work the same.

In Python’s case you need to import ScriptExecution and java.time.ZonedDateTime instead of Joda DateTime. Then call ScriptExecution.createTimer passing a ZonedDateTime Object as the time to run the Timer instead of a Joda DateTime.

To get by you could just simply remove the Joda DateTime stuff from time_utils.py which will break OH 2.5 support but you don’t need 2.5 support. See DateTime Conversion (openHAB 3.x). Even though that’s all Rules DSL, because it’s talking about the same Java Objects and Classes you should be able to translate that to Python relatively easily. Some of the examples there will work line for line.

I’ve been working on implementing the Timer Management. So far, I had both successes and failures. What I’m working currently on is switching on 2 lights when, at night time, the frontdoor or backdoor is opened. However, when either or both these two lights are already on, the timer should not be started for that specific light that is already on (winter case where one uses more lights). Else we’ll end up in darkness.

I first tried

var logger =
        Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Lights");

        scriptExtension.importPreset("default");

        this.ZonedDateTime = (this.ZonedDateTime === undefined) ? Java.type("java.time.ZonedDateTime") : this.ZonedDateTime;

        // Load TimerMgr

        this.OPENHAB_CONF = (this.OPENHAB_CONF === undefined) ? java.lang.System.getenv("OPENHAB_CONF") : this.OPENHAB_CONF;

        load(OPENHAB_CONF+'/automation/lib/javascript/community/timerMgr.js');

        load(OPENHAB_CONF+'/automation/lib/javascript/community/timeUtils.js');

        // Create timer only when there isn't one, create function to xcan timer

        this.tm = (this.tm === undefined) ? new TimerMgr() : this.tm;

        // Get light states

        var wandState = items["koofSpots_gFloor_switch"]; var pilaarState = items["pilaarverlichting_gFloor_switch"];

        if(wandState == "OFF") {
          events.sendCommand("koofSpots_gFloor_switch", "ON");
          this.tm.check("wand", "30s", function() { events.sendCommand("koofSpots_gFloor_switch", "OFF");
            }
          );
        }

The 30s is just a test, or I’ll be waiting too long during the test phase. What I only accomplished in the rule above is: when the light is off (one light for simplicity), it goes on when the rule is triggered and switches off as the timer expires. So this does work.

Now for resetting or rescheduling the timer I tried

...
events.sendCommand("koofSpots_gFloor_switch", "ON");
          this.tm.check("wand", "30s", function() { events.sendCommand("koofSpots_gFloor_switch", "OFF");
            }
          );
...

And this does work as well. Each time an ON command is sent and the timer is rescheduled/reset for another, in this case, 30 seconds.
So I thought I’ll make it into this:

...
if(wandState == "OFF" || tm.hasTimer("wand")) {
          events.sendCommand("koofSpots_gFloor_switch", "ON");
          this.tm.check("wand", "30s", function() { events.sendCommand("koofSpots_gFloor_switch", "OFF");
            }
          );
        }
...

The idea is that the timer is rescheduled/reset to start again if the door is opened because that same code runs when 1) the light has already gone out or 2) when the timer is active. It also prevents from switching off lights that were on in the first place as the timer would never start. However, somehow this doesn’t work. It looks like the timer is cancelled when the door is opened for a second time, so when the timer has started counting down. The lights will then stay on indefinitely. If I do not open the door during the running timer, it works as advertised and the light goes off in 30s. This tells me that the timer is created on the first run.

Anyone who has a solution for this issue? Or maybe even better. Cleaner code/more elegant or generic way of coding?

There are a number of ways to deal with this.

1. Disable rules

Create a rule that triggers at sundown or an appropriate time.

Add a condition to only run this rule when the light is not already ON.

The Script Action is to enable your current rule.

Sometime at night trigger a third rule that disables your current rule again so it won’t run until the next day.

Neither of the two new rules require any code and can be managed through the UI alone.

This lets you simplify the code you have to write because you already know that if the code is running at all it’s late enough in the day and the light wasn’t already ON when the it became late enough. However it will violate DRY because you’ll have two of the same set of three rules for each light. but the simplicity of the code might make that worth the trade.

2. Condition Script

Add a trigger to the rule to run at the start of the time when the timer should be considered.

Add a Condition to prevent the rule from running outside of the allowed times of day.

Add a Script Condition that checks to see if the rule was triggered by an Item or not (event is undefined when it was triggered based on Time). If it wasn’t triggered based on an Item event, save the current states of the lights and return false. If it was triggered based on an Item event, check if the light in question was ON when the rule was triggered that first time and only return true if it was ON.

Again, your Script Action just has to set and manage the timer because we’ve already verified it needs to run.

The Script Condition would look something like

this.startStates = (this.startStates === undefined) ? { "koofSpots_gFloor_switch":"OFF",
                                                        "pilaarverlichting_gFloor_switch": "OFF" } : this.startStates;

// It's a time based trigger
if(this.event === undefined){
    // Record the current states of the lights
    this.startStates = { "koofSpots_gFloor_switch":items["koofSpots_gFloor_switch"],
                         "pilaarverlichting_gFloor_switch":items["pilaarverlichting_gFloor_switch"] };
    // Don't run the rule
    false;
}

// It's an Item based trigger
else {
    // Only run the rule if the light was OFF when the rule was triggered based on Time
    var lightSwitch = <some code to build the name of the light switch based on the door switch Item's name>;
    this.startStates(lightSwitch) == OFF;
}

The conditions will prevent the Script Action from running except in cases where a timer is required. It prevents it from running in all other cases.

3. Add conditions to the script action

This is basically the same as the above except instead of using a Script Condition put the variables and checks inside the Script Action.

Hi Rich,

Thanks once more for your elaborate answer. I am using your Time of Day pattern so that is an easy condition to use → Only run when it is EVENING or NIGHT. The starterState is a nice way of determining the rest of the condition required to run or not to run the timer. I’ll try and implement that in the next few days.