Timer script Not Cancelling if state changes

You’ve a syntax error somewhere. Maybe a missing closing bracket or something. Without seeing the current code :man_shrugging:

That’s what I figured. Should be simple, but I don’t see it:

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

var ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
logger.info("Thing Offline 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", "Thing Offline", "A device has gone offline. Please contact BPS at monitors@bpsgreenhomes.com or 910-470-8203");

    this.timer = undefined; // reset the timer back to undefined
}
logger.info("this.timer = " + this.timer);

if(this.timer === undefined) {
    this.timer = ScriptExecution.createTimer(now.plusSeconds(30), runme);
}
logger.info("this.timer = " + this.timer);

else {
    this.timer.reschedule(now.plusSeconds(30));
}

You can’t put anything between the closing bracket of an if statement and the else.

I thought I was supposed to log the first line after the If & the first line after the Else.

OK these are the logs I get if I make the device offline and then put it back online before the timer has expired it still sends the email. That’s the problem:

2021-06-14 15:39:54.190 [INFO ] [org.openhab.model.script.Scratch    ] - Thing Offline Alert Rule Started
2021-06-14 15:39:54.199 [INFO ] [org.openhab.model.script.Scratch    ] - this.timer = undefined
2021-06-14 15:39:54.202 [INFO ] [org.openhab.model.script.Scratch    ] - this.timer = org.openhab.core.model.script.internal.actions.TimerImpl@5b9675
==> /var/log/openhab/events.log <==
2021-06-14 15:39:54.189 [INFO ] [ab.event.ThingStatusInfoChangedEvent] - Thing 'zwave:device:Z-Wave_Serial_Controller:node4' changed from ONLINE to OFFLINE (COMMUNICATION_ERROR): Node is not communicating with controller
2021-06-14 15:40:01.678 [INFO ] [ab.event.ThingStatusInfoChangedEvent] - Thing 'zwave:device:Z-Wave_Serial_Controller:node4' changed from OFFLINE (COMMUNICATION_ERROR): Node is not communicating with controller to ONLINE
==> /var/log/openhab/openhab.log <==
2021-06-14 15:40:24.198 [INFO ] [org.openhab.model.script.Scratch    ] - Email Sent

So the rule doesn’t run. What are the triggering conditions for the rule?

It’s all but impossible to figure out a problem like this if you don’t have a basic understanding of how your own code works. It would be well worth your time to spend it studying this code to understand it because otherwise we will spend many many more hours going back and forth with you trying something blindly without understanding why and making mistakes as a result and me needing to correct those mistakes just to answer one simple question: is the value of this.timer the same inside runme and it is outside of runme. And I don’t have many more hours to help with this.

Simplify. Make sure you understand what every line of code in this rule does. Make sure you understand the basic rules of syntax for the language. There are tons of freely available courses and tutorials out there to help with that. Slowly rebuild the rule little by little. Experiment to make sure you understand what the lines are really doing.

I’ll give you a little bit of help. As currently written in post 22 the code does the following:

  1. import the logger
  2. import ScriptExecution so you can create a Timer
  3. log to indicate the rule started
  4. import ZonedDateTime so you can create a Timer
  5. save now to a variable, now is the current date/time
  6. define a function named runme that gets called by the timer
    a. log to indicate when runme is called
    b. import Actions
    c. import Mail Actions
    d. send the email
    e. set this.timer to undefined so a new timer will be created the next time the rule runs
  7. log out the value of this.timer so you can see whether a new timer should be created or if the timer should be rescheduled
  8. if this.timer === undefined
    a. create a new timer for 30 seconds and set the handle to this.timer
  9. if this.timer !== undefined
    a. reschedule the existing timer for another 30 seconds.

Notice what might be missing here. Nowhere is the timer ever cancelled. An email will always be sent because you have no code to cancel the timer when the Thing goes back online. Is that the root problem? If so, the problem is that you didn’t implement it.

Also notice how the indentation is used in the list above. It makes it way easier to see what’s going on. Do the same in your code (the lines under runme between the { } should be indented).

The rule runs but it sends the email, even if the Thing goes back online before the timer expires.

I just realized I could post ALL the code:

triggers:
  - id: "1"
    configuration:
      thingUID: zwave:device:Z-Wave_Serial_Controller:node4
      status: OFFLINE
    type: core.ThingStatusChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >+
        var logger =
        Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Scratch");


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

        logger.info("Thing Offline 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", "Thing Offline", "A device has gone offline. Please contact BPS at monitors@bpsgreenhomes.com or 910-470-8203");

            this.timer = undefined; // reset the timer back to undefined
        }

        logger.info("this.timer = " + this.timer);


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

        else {
            this.timer.reschedule(now.plusSeconds(30));
        }

        logger.info("this.timer = " + this.timer);


    type: script.ScriptAction

Yes. Didn’t you want it to run a second time, to reschedule the timer?

So here you’ve asked it to run only when the Thing changes to OFFLINE.
It will not run when the Thing changes to ONLINE.

I don’t think that’s all you’ve got to look at though; if it only reschedules the Timer then it just puts off the email, it doesn’t go away.
You’re going to need to take different actions depending on what the Thing status changed to, ONLINE or OFFLINE.

Yes I realized it was just rescheduling and replaced that line with:

this.timer.cancel();

Which seems to work.

And I do realize now that it doesn’t trigger again to cancel the email when the Thing comes back online. My history with these types of rules is in Vera using Reactor which includes an action when the condition is no longer true & I thought this script might be doing that somehow.

I DO like the alternate structure you propose but fear it’s another rabbit hole for me because I find this code so difficult to understand (it IS starting to get better though).

So here are my questions regarding that:

Triggering from any Thing: I would really like to trigger off of any Thing status change. When making the rule in the UI though, you can only pick one Thing for the trigger. I looked at the docs where it talks about Thing triggers but there is nothing there about triggering from any Thing status. Is this a peice of code or is there some other way of doing this?

Cancelling the timer and examining the new status: Will my line above work to cancel the timer? I assume checking the status of all things is another line of code I need.

Regarding Things popping in & out and the reliability of ONLINE/OFFLINE status, it seems that it will work good enough for our use-case for most of our devices. For our battery-powered devices, I can look for missing battery reports as you suggested previously.

No, you’d have to list them all individually.

There are example rules on the forum that run periodically and report all the Things that are offline though. A search should find several examples.

I should have phrased as “trigger from your one Thing but for any change of status”, we were looking at just one Thing at the time.

After all this, it’s polite to show the complete rule that works for the benefit of later readers.

@rlkoshak Yes looking at other examples is how I got here. There are mostly examples of how it can’t, could, should, or would be done if we had some thing we need to make it happen. It seems crazy to have to make 20 or whatever triggers to try and do this. The best solution to me would seem to be using Log Reader as discussed here:

Though I can’t seem to get that to work for me for some reason.

@rossko57 Ahh ANY SINGLE thing - got it!

Anyway I’ll work more on that & certainly post whatever I’m able to come up with as my best solution. Thanks to both of you for helping with this code language & the logging & such.

OK the ultimate answer to my original question was a simple change in the script. Well, a number of changes with different improvements, but one to solve the timer canceling issue. The entire rule is below I am triggering it with both offline & online triggers. Thing goes offline, rule triggers, thing goes back online before the timer expires and the rule cancels the email. This would work well if the trigger is for one individual Thing. If many Things are used as triggers, any of the Things going offline triggers it. It will still work well in the case of the individual Thing going offline & back online which DOES happen fairly frequently. If multiple Things are going offline & online the results could be messy, but that’s a limitation of this whole problem whether a bunch of Thing status’s are used as triggers or Log Reader is used.

triggers:
  - id: "1"
    configuration:
      thingUID: zwave:device:Z-Wave_Serial_Controller:node4
      status: OFFLINE
    type: core.ThingStatusChangeTrigger
  - id: "3"
    configuration:
      thingUID: zwave:device:Z-Wave_Serial_Controller:node4
      status: ONLINE
    type: core.ThingStatusChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >+
        var logger =
        Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Scratch");


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

        logger.info("Thing Offline 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", "Thing Offline", "A device has gone offline. Please contact BPS at monitors@bpsgreenhomes.com or 910-470-8203");

            this.timer = undefined; // reset the timer back to undefined
        }

        logger.info("this.timer = " + this.timer);


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

        else {
            this.timer.cancel();
        }

        logger.info("this.timer = " + this.timer);


    type: script.ScriptAction

I don’t think that’s complete (had suspicions).

So your rule triggers from either transition to ONLINE or transition to OFFLINE.
Either way, it’ll later send you a message claiming it was “offline” - in reality it could have been either.

Unless, within the time window, the rule runs again. Whether that is for ONLINE or OFFLINE, it will cancel the timer. The Thing might be in either state.

You really need to look at your Thing status before deciding what to do about it.

This is all a bit clumsy and not easily expanded to many Things. But as said before, you’re not really interested in Thing status, you want to know if the end device has stopped talking and there are different approaches for that (which do scale).

I realize that this rule could get in a reversed position. Here’s how I envision it: The rule starts with the Thing or Things online. If it’s single Thing it should work fairly well. If the Thing goes offline the rule starts, with the timer at say, 3 hrs. For our use that’s good enough, and I realize that the Thing might have already been off for a while. Based on what I’ seeing I get a Thing Offline status for our Things within a reasonable number of hours of them dropping. If the Thing comes back online in that time the email is never sent & I’m happy. If it times out the email is sent I can see what’s going on. If the Thing comes back Online and starts tripping the rule in reverse, I just manually trp the rule to correct it.

I realize this is not good for battery powered Things like our leak sensors. For that I could watch for battery level updates as you have suggested (haven’t got into that yet).

Now I’m not saying I LIKE this. It seems rediculous to have to go through this much to just be able to get notified if devices in this kind of system have dropped off of the network. But as I have seen you write elsewhere, “It seems like it should be simple but it’s not.”

So if I have no rule for this I have no way of knowing if things are offline. So I made a rule to email me when they’re offline and that worked. But usually they connect again by themselves so by the time I log in to look there is no problem. Enter the timer with the cancel function.

I understand what you’ve said about how an online status is not really truly indicating that the device is actually online, but it mostly works for our devices from what I’ve seen.

So your suggestion is to monitor for reports from whatever devices I have and alert based on missing reports. I like the sound of that but also worry that it’s another rabbit hole to go down. It sounds like it involves learning when/what each device reports and customizing a line of code for that individual device. Code that I don’t understand and will struggle to make work. Meanwhile I have actual offline alerts coming in which I feel I should be able to use for my purpose so that’s the direction I went.

Can you point me to an example of how to do this with typical zwave devices? In our case it’s multisensors (used for motion, temp, & RH) MIMO’s (used for controlling dehumidifiers mostly), regular plug-in appliance modules, wall switches, and leak sensors. All zwave.

By the time you typed that, you could have the rule checking actual status instead of guessing/hoping

“SomeItem” linked to a zwave device channel that updates periodically, e.g. temperature every twenty minutes. You only need one channel per device.
Add an Item expire property, period 45mins (two missed reports), have expire action set state to UNDEF. Put similar Items in a Group “MyImportantDevices”.
No rules so far.

DSL rule

rule "alert broken device"
when
    Member of MyImportantDevices changed to UNDEF
then
    logInfo("test", "Something has broken!")
    logInfo ("test", "Something that " + triggeringItem.label + " belongs to!"
end

There are a hundred odd different communication technologies and methodologies to deal with. How-to-do requires tailoring for each.
There are probably a half-dozen generic approaches that would cover most cases, albeit require tailoring still for individual devices in some cases.

Most people are concerned about just a few key functions. Example “did the heating fail to come on when wanted?” Monitoring device status won’t help if the failure is due to fuel supply, different approach needed. Etc.

But I think what rossko57 and I are trying to push you towards is if you actually go down that path now, it will work for all of your devices, battery powered or not. You are stuck trying to solve this one way when there are other more appropriate ways to accomplish the exact same thing in a more scalable manner.

You seem bound and determined to go down this path of using the Thing status and we are happy to help you with that. But, at the risk of speaking for rossko57, we both think that’s the wrong approach. The Thing’s ONLINE status is a poor proxy for the device’s actual status. Working with Thing status is awkward at best and inefficient (from the user’s perspective).

I wrote up a pretty comprehensive tutorial with examples in Python and Rules DSL. Design Patterns: Generic Is Alive

And don’t fall into the sunk costs fallacy. It doesn’t matter how much time you’ve spent on the current approach. If it’s the wrong approach investing more time into it isn’t going to make it the right approach.

Here’s the thing about openHAB. Any reasonable approach is going to be technology independent. It is going to depend on Items and it doesn’t matter what those Items are linked to. Don’t restrict your searching and example to only those that deal with Zwave. You likely won’t actually find anything that actually talks about Zwave. Almost all the examples and tutorials you will find will talk about Items, not technologies. And they will work for all technologies.

In the code linked to above (the first approach is basically what rossko57 suggested except with a whole lot more explanation and if you don’t like that there are two additional approaches presented), I can use the exact same rule to detect when my Zwave Power meter goes offline, my connection the OpenWeatherMap’s API goes offline (or is blocked for some reason), MQTT devices (though the LWT is a better approach for that), or anything else that reports periodically.

For the Expire binding code all you have is a Group, Items configured with Expire, and a two-three line rule to generate an alert when the device changes to UNDEF.

The second approach uses separate Switch Items which get updated when any Item linked to a Channel on a given Thing updates. This is a good approach for when you have lots of Items representing the same device as it lets you use updates on all of those Items to reset the timer before it gets marked offline. Again the code is presented in Rules DSL and Python.

Don’t worry about the complex example as it has a bunch of extra stuff in it to limit how often a device can be reported as offline and such. I don’t even use that rule any more and have greatly simplified it.

I’m not determined to do it this way at all. It’s just that every time I try and look at any other approaches it leads to the next 100 things I don’t know - LOL.

I’ll look at this tomorrow and see where I can get.

Thx-

OK so for teh first example I created a (location) group called “Item Status”.

I made a couple of Items members of that group.

There is no expire binding in OH3 right? So I set the expiration Metadata to some appropriate time for each device.

I created a rule to send an email when a member of the group changed state to UNDEF.

It’s working - YAY! And so simple… BUT, we have switches for dehumidifiers which might not turn on all winter so using an expire time of x hours won’t really work. I suppose I could toggle it every 99 hours or something.

Same with our leak sensors. They send battery updates, but I don’t know how often.

Other communications like from polling or something are related to Things which can’t be part of the group right?

Correct, it’s part of core now.

There might be other Channels on that device that report more frequently and you can use that or a combination of all the Channels. If not indeed some other approach might be needed. This might be a case where you have to rely on the Thing status or the like. However, for devices like that, especially battery powered devices, it might be months before the controller decides it’s dead.

For mains powered devices though you should be able to send the special REFRESH command to the Item linked to the device. That should cause the controller to poll for the current states of the device and update the Item. That ought to give you a way to set up another rule to poll the device. However, many devices and/or the Zwave controller have polling period properties too and the Item I think gets updated when it responds to a poll.

Overall, you’ll have to experiment for those outlier devices to come up with how often they are updated and set the time accordingly.

Create a rule to log out when those Items receive an update. After a week or so look through the logs and you should be able to find how often. Unfortunately updates are not logged into events.log so you can’t rely on the existing logging.

I used to have some zwave smoke alarms and they had a lifeline Channel that updated a couple times a day. The battery was reported weekly though.