Timer with dynamic names in rules

Hello everyone,

i have a rule, which notifys me, when a things goes offline. Its a simple, but heavily filled rule.
It looks like this:

rule "Thing: Offlinegeräte erkannt"
when
    Thing "Device1" changed or
    Thing "Device2" changed or
    [...]
    Thing "Device150" changed or
    Thing "Device151" changed
then
    if (newThingStatus == "OFFLINE") {
        //notify me that the device is offline
    }
    else if (newThingStatus == "ONLINE") {
        //notify me that the device is back online
    }
end

As you may see, everytime a thing goes offline i get a notification. Everytime it goes online, i get a second notification. But the problem is, that some devices turn offline for a minute and then turn online again. With this i get 2 notifications in a short amount of time. This rule is meant to notify me, when a device REALLY wents offline (e.g. no power). So i would like to use a timer, that notifys me, when a device stays offline for at least 20 Minutes.

If i add a timer like this:

var Timer timerOfflineDevice
rule
when
    // Device 1 turns offline
then
    if (newThingStatus == "OFFLINE") {
        timerOfflineDevice = createtimer(now.plusMinutes(20), [|
            //notify me, that Device 1 turned offline
            timerOfflineDevice = null
        ])
    else if (newThingStatus == "ONLINE" && timerOfflineDevice !== null) {
        timerOfflineDevice.cancel()
    }
    else if (timerOfflineDevice === null) {
        // notify me that the thing went online
    }
end

you can see i’ll only be notified, when a device turns offline for at least 20 min.
But what to do if multiple devices go offline in a short timespan? The rule triggers everytime and only notifies me for the LAST device that turns offline and online again. So i would like to have “dynamic timer names”

like this:

import org.openhab.core.model.script.ScriptServiceUtil
import org.openhab.core.thing.ThingUID
rule
when
    // Device X turns offline
then
    var device = ScriptServiceUtil.getInstance.thingRegistry.get(new ThingUID(triggeringThing)).getLabel
    var Timer timerOffline+device = null
    if (newThingStatus == "OFFLINE") {
        timerOffline+device = createtimer(now.plusMinutes(20), [|
            //notify me, that Device X turned offline
            timerOffline+device = null
        ])
    else if (newThingStatus == "ONLINE" && timerOffline+device !== null) {
        timerOffline+device.cancel()
    }
    else if (timerOffline+device === null) {
        // notify me that Device X went back online
    }
end

ScriptServiceUtil.getInstance.thingRegistry.get(new ThingUID(triggeringThing)).getLabel

gets the Things name. (e.g. “Device_1”)
This rule is meant to create a different timer, everytime a device changes its status to Offline. The timers name should look like this: timerOfflineDeviceDevice_1 or timerOfflineDeviceDevice_154.
But this does not work, since you cannot dynamically name a timer variable (Or am i wrong?)

As you can see in THIS POST Generating a dynamic variable name in a rule - #3 by Flole someone stated, that this is possible with the cache system. But i have absolutely no clue how it works and dont really understand the documentation of privateCache.

Maybe some intelligent pro can help me with this? The only solution i see at the moment is, that i create a different timer for every single Device. Since i have more than 100 devices, this is everthing else than effective.

Regards
Jimmi

You could use array, hashmap or other objects instead of a simple var to manage your time.

However you will still have the issue, that the event variables (newThingStatus, triggeringThing, etc) will also get overwritten.

Maybe have a look here, if your want to use this blockly script, where the issue is already solved:

I dont know how to use arrays. I have no idea what hashmaps are. Can you give me 2 example rules (one for each) to understand what theyre doing? Maybe with some comments?

Working directly with things may not be the best solution as there are limitations. There are several threads that may give you some hint.

Have a look in the form for example or better use blockly, then you do not need to know any syntax skills and can just drag & drop your code

What parts do you not understand. Perhaps we can clear that up for you.

Indeed, the privateCache would be the best way to handle this.

Were I to write this in Rules DSL it would look something like this:

import org.openhab.core.model.script.ScriptServiceUtil
import org.openhab.core.thing.ThingUID
rule
when
    // Device X turns offline
then
    val timerName = triggeringThing+"_Timer"

    // OFFLINE and no timer already scheduled
    if(newThingStatus != "ONLINE" && privateCache.get(timerName) == null) {
        privateCache.put(timerName, createTimer(now.plusMinutes(20), [ |
            // alert code goes here
            privateCache.put(timerName, null)
        ])
    }

    // Item returned to ONLINE status and timer exists
    else if(newThingStatus == "ONLINE" && privateCache.get(timerName) !== null) {
        privateCache.get(timerName).cancel
        privateCache.put(timerName, null)
    }

    // Item returned to ONLINE and no timer exists
    else if(newThingStatus == "ONLINE" && privateCache.get(timerName) === null) {
        // alerting code goes here
    }
end

But I wouldn’t use Rules DSL for this. Especially if you don’t know an array from a hole in the ground, I will second @Matze0211’s suggestion and consider using Blockly. You don’t have to know these things to such a degree because it’s all built into the blocks.

But I wouldn’t even do that. I’d use some of the rule templates to handle this.

First I’d create a Switch Item for each Thing I care about and put them into a Group. I’d name the Item something that can easily be mapped to the Thing itself (e.g. replace the ‘:’ with ‘_’) and use the Thing’s label as the Item’s label.

Then I’d use Thing Status Reporting [3.2.0;3.4.9] - #2 by Hydropower (OH 3) or Thing Status Reporting [4.0.0.0;4.9.9.9] (OH 4) to detect when the Things go OFFLINE or return ONLINE. The rule I’d write that get’s called by that rule would look something like (I’m on OH 4 so I’m using the latter link) in JS Scripting UI rule Action:

var statusItemName = thingId.replace(':', '_')+'_Status';
if(items.[statusItemName] === undefined) {
  console.error(''Status Item ' + statusItemName + " does not exist');
}
else if(newStatus == 'ONLINE'){
  items[statusItemName].postUpdate('ON');
}
else {
  items[statusItemName].postUpdate('OFF');
}

Then I’d use Open Reminder [3.3.0;3.4.9) - #5 by tdlr (OH 3) or Threshold Alert and Open Reminder [4.0.0.0;4.9.9.9] configured to call a rule when a member of the Group created above stays OFF for 20 minutes. The rule this template calls would have a script actions along the lines of this.

var item = items[alertItem];
if(!isAlerting) {
    console.warn(item.label + ' is back ONLINE');
}
else {
    console.info(item.label + ' has been OFFLINE for 20 minutes');
}

All this messing with Timers and cache and all the rest is handled for you. All you have to do is configure the rules and write some code to do the alerting or what ever.

Note, I wrote the code above in JS Scripting but they can be used with Rules DSL too. Even file based Rules DSL rules. However, I believe that you cannot have a Rule in a .rules file that doesn’t have triggers so you’d want to write it as a Rules DSL script.

1 Like

Thank you for that code, Rich.

Trying to execute it, i get this error in the log:
Script execution of rule with UID 'Thing-1' failed: The name 'privateCache' cannot be resolved to an item or type; line 137, column 39, length 12 in Thing
The documentation says (if i understand it correctly), that i have to import the Cache function. But none of the import texts work for me.

I am not a coding genius and learned all from this community and their documentations. Maybe i understand the “import”-region in the docs wrong.

If you are using JS Scripting as opposed to Nashorn, the docs you need are the add-on’s docs.

There is nothing to import. I think in Nashorn JS it’s context.privateCache. I don’t remember. I’d much rather write using a version of JS that’s ten years newer.

Thanks for your help. I am not that good in programming languages. I figured out, that i need the JS Scripting add-on. I installed i and read the docs about the cache. I need to use cache.private.get/put as he docs says. But i am getting this error:

Script execution of rule with UID ‘Thing-1’ failed: The name ‘cache’ cannot be resolved to an item or type;
JS Scripting is installed. Anything else i need to configure? Sorry for these dumb questions.
I write my rules in Rules DSL.

If you are not good with programming why mess with this stuff at all? You should use Blockly, as already recommended. The is a lot less you can mess up with Blockly.

It’s not enough to just install JS Scripting add-on. You have to actually write your rule using those standard for that library. The fact that the error is complaining about “Thing-1” implies you are trying to write JS in a .rules file. That is never going to work. You need to write JS Scripting in .js files under $OH_CONF/automation/js as documented.

Though if you are not good at programming, you probably should be creating managed rules, not file based rules anyway. Even if you don’t want to use Blockly, you will probably have an easier time creating rules through MainUI than through text files.

If you are not good at programming and insist on file based rules :person_shrugging: . I guess some people like to ride their bikes with flat tires.

Thank you for your honest opinion. I am writing my rules and dont use Blockly because i want to learn something. Some people riding their bikes with flat tires, but i desperately want to learn riding a bike. I did not know, that i have to use .js-files. Thank your for that info. i hope i did not waste your time. Youre on of the most helpful persons in this community.

Coding OH rules in text files instead of managed in the UI really isn’t going to help you learn anything useful from a programming perspective. The names and structure of the files and syntax for creating a rule is all very specific to OH. There is nothing you will learn from a generic programming perspective coding in text file that you would not learn by using managed rules in the UI. It’s not like learning to write JS Scripting rules will make it so you can write Node.js modules or web page JS libraries. That part is not transferable.

Also note that using Blockly is programming too. You are not missing out on learning programming by using Blockly. Teaching those things is what makes Blockly a really good platform to get started on.

But you don’t have to use Blockly in UI rules. You can use JS Scripting or even Rules DSL in Script Actions and Script Conditions. But you don’t have to worry about the overall structure of the rule and getting the syntax right for the basic structure of building the rule. All you have to do is worry about the code that runs when the rule triggers. As a result, you’ll never have to fight syntax errors because you forgot a closing quote on the trigger or forgot the end to close out the rule or the like. You can focus only on your code that does something.