RH Alert - How To Cancel Timer

OH3 on RPi4:

I have a rule which gets triggered from high RH and I want it to send an email alert, but only if the RH stays above that level for 12 hrs.

I have a working script for sending the email and I have working timer scripts.

My question is what is the least complex way to handle canceling the timer if the RH goes back above the trigger point during the timer run?

It depends on how the scripts are written now.

OK so it can be done within the script? (good news)

triggers:
  - id: "1"
    configuration:
      cronExpression: 0 0 8,20 * * ? *
    type: timer.GenericCronTrigger
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: RH_Kitchen
      state: "64"
      operator: ">"
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >-
        var logger =
        Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Second
        Floor Humidity Alert");

        var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");

        var ZonedDateTime = Java.type("java.time.ZonedDateTime"); 

        var now = ZonedDateTime.now();


        var runme = function(){

        var Actions = Java.type("org.openhab.core.model.script.actions.Things");

        var mailActions = Actions.getActions("mail","mail:smtp:SMTP_Server");

        mailActions.sendHtmlMail("xxx@gmail.com", "Humidity Alert", "Second Floor - High Humidity for more than 24 hours.");
            this.timer = undefined; // reset the timer back to undefined
        }


        if(this.timer === undefined) {
            this.timer = ScriptExecution.createTimer(now.plusSeconds(43200), runme);
        }

        else {
                this.timer.cancel();
}
    type: script.ScriptAction

That looks mostly correct except that you need to reset this.timer to undefined when after you cancel it. It does not return to undefined on its own.

Really? Can you explain how it’s cancelling the timer if the RH drops below 64? Is it re-running the rule based on the cron trigger and then canceling the timer when it doesn’t meet the stated condition?

Does this look right?

triggers:
  - id: "1"
    configuration:
      cronExpression: 0 0 8,20 * * ? *
    type: timer.GenericCronTrigger
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: RH_Kitchen
      state: "64"
      operator: ">"
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >-
        var logger =
        Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Second
        Floor Humidity Alert");

        var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");

        var ZonedDateTime = Java.type("java.time.ZonedDateTime"); 

        var now = ZonedDateTime.now();


        var runme = function(){

        var Actions = Java.type("org.openhab.core.model.script.actions.Things");

        var mailActions = Actions.getActions("mail","mail:smtp:SMTP_Server");

        mailActions.sendHtmlMail("xxx@gmail.com", "Humidity Alert", "Second Floor - High Humidity for more than 24 hours.");
            this.timer = undefined; // reset the timer back to undefined
        }


        if(this.timer === undefined) {
            this.timer = ScriptExecution.createTimer(now.plusSeconds(43200), runme);
        }

        else {
                this.timer.cancel();
	        this.timer = undefined;
}
    type: script.ScriptAction

I didn’t notice the condition.

Of course, it won’t cancel anything if the state is below 64 because the rule won’t run.

You need to trigger the rule when ever RH_Kitchen changes. Remove the condition. Then add a line to set this.timer to undefined after you cancel it.

OH rules are event driven. An event occurs and some code runs. You can’t cancel a timer without running some code.

Totally confused. I get what you’re saying about the rule not running if the condition is not met. And I can trigger the rule whenever the RH changes but how does that result in me getting an alert when it’s over 64%?

Use another if() within the rule?

psuedocode
if (more than 64)
    if (timer not already running)   
         start a timer
else (less than 64)
   cancel any timer

@rossko57 thanks, I got this to work in testing using a switch but I’m getting an error when trying to use the RH levels. It doesn’t seem to like the > sign. I don’t get teh error when I replace > with =.
Error:

Temperature_Second_Floor" is not defined in <eval> at line number 14 

Script:

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Rule Name");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var ZonedDateTime = Java.type("java.time.ZonedDateTime"); 
var now = ZonedDateTime.now();

var runme = function(){
var Actions = Java.type("org.openhab.core.model.script.actions.Things");
var mailActions = Actions.getActions("mail","mail:smtp:SMTP_Server");
mailActions.sendHtmlMail("xxx@gmail.com", "Timer Cancel Test", "Test ");
    this.timer = undefined; // reset the timer back to undefined
}

if(this.timer === undefined)
if(Temperature_Second_Floor > 80) {
    this.timer = ScriptExecution.createTimer(now.plusSeconds(30), runme);
}

else(Temperature_Second_Floor < 80); {
    this.timer.cancel();
}

So, what is that? Some Item? You’re usually interested in Item states, not the whole Item with label,icon, etc.

Also beware states with units, depending on Item type. You have to compare with units too.

OK so I added teh state and found a missing ; but now I get an error about teh attic temp not being defined. Based on other scripts I see I can’t figure out why I’m getting this error. It should be like this shouldn’t it? Maybe this is because of the need to compare with units but I don’t know what that means.

else(Attic_Temp.state < 80);

Full script:

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Rule Name");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
var ZonedDateTime = Java.type("java.time.ZonedDateTime"); 
var now = ZonedDateTime.now();

var runme = function(){
var Actions = Java.type("org.openhab.core.model.script.actions.Things");
var mailActions = Actions.getActions("mail","mail:smtp:SMTP_Server");
mailActions.sendHtmlMail("xxx@gmail.com", "Timer Cancel Test", "Test");
    this.timer = undefined; // reset the timer back to undefined
}

if(this.timer === undefined)
if(Attic_Temp.state > 80) {
    this.timer = ScriptExecution.createTimer(now.plusSeconds(30), runme);
}
else(Attic_Temp.state < 80); {
    this.timer.cancel();
}

You’ve chosen to write your rule in javascript, but that is a DSL method. (Caution - most old forum examples are in DSL.)
You need to use a different syntax to get at Item states in javascript.
items["Attic_Temp"]

OK getting closer I guess. I’m not getting errors anymore but I’m not getting the email either. I added some logging.

Script:

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Rule Name");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
logger.info("Temp Timer Alert Rule Started");

var ZonedDateTime = Java.type("java.time.ZonedDateTime"); 
var now = ZonedDateTime.now();

var runme = function(){
logger.info("Email Sent");
  
var Actions = Java.type("org.openhab.core.model.script.actions.Things");
var mailActions = Actions.getActions("mail","mail:smtp:SMTP_Server");
mailActions.sendHtmlMail("skye@bpsgreenhomes.com", "Timer Cancel Test", "Test");
    this.timer = undefined; // reset the timer back to undefined
}
logger.info("this.timer = " + this.timer);

if(this.timer === undefined)
if(items["Attic_Temp.state"] > 80) {
    this.timer = ScriptExecution.createTimer(now.plusSeconds(30), runme);
}
else {
if(items["Attic_Temp.state"] < 79)
    this.timer.cancel();
}
logger.info("this.timer = " + this.timer);

Logs:

2021-07-01 15:31:39.238 [INFO ] [openhab.model.script.Rules.Rule Name] - Temp Timer Alert Rule Started
2021-07-01 15:31:39.272 [INFO ] [openhab.model.script.Rules.Rule Name] - this.timer = undefined
2021-07-01 15:31:39.303 [INFO ] [openhab.model.script.Rules.Rule Name] - this.timer = undefined

Syntax still a mash of DSL and javascript. someItem.state is a DSL construction.

I suppose you would be interested in the actual state of the Item, maybe that could be logged out before you test it with if().

Oops.
Arggh, so frustrating. Still not working isn’t this right then?

if(items["Attic_Temp"] > 80)

Looks promising. I wonder what the state actually is.

Oops, I moved to another controller because that one was throwing unrelated database errors and I forgot to change the name of the item.

OK so it’s working, I get the email, but I’m getting an error from the timer cancel line:

2021-07-02 11:41:23.059 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'Timer_Alert_Temp' failed: TypeError: Cannot read property "cancel" from undefined in <eval> at line number 23

Script:

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Rule Name");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
logger.info("Temp Timer Alert Rule Started");

var ZonedDateTime = Java.type("java.time.ZonedDateTime"); 
var now = ZonedDateTime.now();

var runme = function(){
logger.info("Email Sent");
  
var Actions = Java.type("org.openhab.core.model.script.actions.Things");
var mailActions = Actions.getActions("mail","mail:smtp:SMTP_Server");
mailActions.sendHtmlMail("xxx@gmail.com", "Timer Cancel Test", "Test");
    this.timer = undefined; // reset the timer back to undefined
}
logger.info("this.timer = " + this.timer);

if(this.timer === undefined)
if(items["Temperature_Second_Floor"] > 80) {
    this.timer = ScriptExecution.createTimer(now.plusSeconds(900), runme);
}
else {
if(items["Temperature_Second_Floor"] < 79)
    this.timer.cancel();
}
logger.info("this.timer = " + this.timer)

This is why it is so important to get the curly brackets right. One way to ensure that is to use proper indentation so you can see where the different contexts start and end.

Here is your rule, as written, with proper indentation.

var logger = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.Rule Name");
var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
logger.info("Temp Timer Alert Rule Started");

var ZonedDateTime = Java.type("java.time.ZonedDateTime"); 
var now = ZonedDateTime.now();

var runme = function(){
    logger.info("Email Sent");
  
    var Actions = Java.type("org.openhab.core.model.script.actions.Things");
    var mailActions = Actions.getActions("mail","mail:smtp:SMTP_Server");
    mailActions.sendHtmlMail("xxx@gmail.com", "Timer Cancel Test", "Test");
    this.timer = undefined; // reset the timer back to undefined
}
logger.info("this.timer = " + this.timer);

if(this.timer === undefined)
    if(items["Temperature_Second_Floor"] > 80) {
        this.timer = ScriptExecution.createTimer(now.plusSeconds(900), runme);
    }
    else {
        if(items["Temperature_Second_Floor"] < 79)
        this.timer.cancel();
    }
logger.info("this.timer = " + this.timer)

Now perhaps you notice the error? this.timer.cancel() is only called in a block of code where this.timer can only ever be undefined. Nothing. but a log statement is executed when this.timer isn’t undefined and you can’t call cancel() on something that is undefined.

Do you mean that else clause to go with that first if statement?

if(this.timer === undefined) {
    if(items["Temperature_Second_Floor"] > 80) {
        this.timer = ScriptExecution.createTimer(now.plusSeconds(900), runme);
    }
}
else {
    if(items["Temperature_Second_Floor"] < 79) {
        this.timer.cancel();
    }
}

Notice how all the if statements now have { }. Before it wasn’t really clear which if statement you intended the else clause to go with. Adding the indentation above showed that it went with the second if statement. By adding { } I have forced the else to go with the first if statement. It also makes it more clear to humans reading the code which one it goes with.

And even if you make a mistake, the indentation shows your intent, it shows which if you intend the else to go with and that makes it easier for you or us to spot missing {} type errors.

Maybe the code makes more sense to human readers if we reorder the checks…,

if(items["Temperature_Second_Floor"] > 80) {
    if(this.timer === undefined) {
        this.timer = ScriptExecution.createTimer(now.plusSeconds(900), runme);
    }
}
else if(items["Temperature_Second_Floor"] < 79) {
    if(this.timer !== undefined){
        this.timer.cancel();
    }
}

In either case notice how proper indentation and use of curly brackets makes it much easier to tell what is going on. It’s not just to make the code pretty; it’s vital to make the code understandable.

1 Like