Erratic JSscripting Rule Behavior

The rule below is supposed to send a notification and announce, via Echo devices, when the bathroom window is open. The rule should only run if the outside temperature is below 10ºC and only run after 10 minutes, and every 10 minutes after that. In the majority of cases the rule runs regardless of the outside temperature and runs after a random amount of time - always less than 10 minutes - as soon as 2 minutes and every 1-2 minutes afterwards. There are no errors/warnings in the logs. It just seems that openhab forgot how to tell time and is ignoring the temperature. Disabling/enabling the rule clears this problem for a while, but it always comes back. I tried setting the log level of the Javascript Scripting binding to debug, but nothing unusual appeared in the logs. This has been going on for a few months now. I’m out of ideas.

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: Bathroom_Windows
      state: OPEN
    type: core.ItemStateChangeTrigger
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: PresenceSwitch
      state: ON
      operator: =
    type: core.ItemStateCondition
  - id: "4"
    configuration:
      itemName: BackYard_TemperatureSensor
      operator: <
      state: 10ºC
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: >
        items.getItem("WON").sendCommand("ON")

        var NoticeInterval = setInterval(()=> {
          if(items.getItem("WON").state == "ON") {
            if(items.getItem("ChetPresence").state == "ON") {
              items.getItem("Echo_Living_Room_TTS").sendCommand("Entschuldigung Sie bitte. Das Badfenster ist noch auf, kann jemand es zumachen, bitte?")
              items.getItem("Echo_Office_TTS").sendCommand("The Bathroom Window is still open. Can someone close it please.")
              actions.NotificationAction.sendNotification("kchest@gmail.com", "Hey Chet, the Bathroom Window is still open. Can you close it please.")
            }
            if(items.getItem("KatPresence").state == "ON") {
              actions.NotificationAction.sendNotification("kathrin@skorpil.org", "Hey Kathrin, the Bathroom Window is still open. Can you close it please.")
            }
          }
          else clearInterval(NoticeInterval);}, 1000*60*10)
    type: script.ScriptAction

  • Platform information:
    • Hardware: Gigabyte Brix MiniPC model GB-BXI7-5775R/MBHM87P-00 BIOS F2. 8GB RAM, 228GB HDD.
    • OS: Ubuntu 22.04.01LTS
    • Java Runtime Environment: OpenJDK 64-Bit Server VM Zulu17.50+19-CA (build 17.0.11+9-LTS, mixed mode, sharing)
    • openHAB version: 4.1.2

This looks wrong

10ºC

It should be

10 °C

Notice the space between the number and the start of the unit and the different character being used for degrees. I suspect your condition is not working because it’s not recognizing that value as a quantity.

You need to add logging to your script to log out the states of important Items to see what the triggering Item is and the condition Item is when the rule runs.

Also, if your rule gets triggered while an interval is already running it will just blindly create a new interval and you’ll end up with two intervals running at the same time. That can explain the weird timing that creeps in over time.

First, let me say this is one of the use cases that caused me to write Threshold Alert and Open Reminder [4.0.0.0;5.9.9.9]. You can configure that to handle all the timing stuff and your code would only need to focus on the alerts.

If you decide to implement this yourself, what you need to do is check to see if an interval is already running and only if it’s not create the interval. To do that, you’ll need to store NoticeInterval in the cache.

// Why not base the alert on the state of the window? what benefit does WON provide? How is it ever turned OFF?
items.WON.postUpdate('ON'); // commands should be reserved for cases where you are asking something to do something, this is a status flag
cache.private.get('NoticeInterval', () => setInterval( () =>{
  if(items.WON.state == 'ON') {
    if(items.ChetPresence.state == 'ON') {
      items.Echo_Living_Room_TTS.sendCommand("Entschuldigung Sie bitte. Das Badfenster ist noch auf, kann jemand es zumachen, bitte?");
      items.Echo_Office_TTS.sendCommand("The Bathroom Window is still open. Can someone close it please.")
      actions.NotificationAction.sendNotification(...);
    }
    else if(items.KatPresence.state == 'ON') {
      actions.NotificationAction.sendNotification(...);
    }
  }
  else {
    clearInterval(cache.private.get('NoticeInterval'));
    cache.private.put('NoticeInterval', null);
  }
}), 1000*60*10);

The interval will only be created if one isn’t already running. If one is already running the rule doesn’t do anything.

If I were to implement this I’d use Looping Timer from openHAB Rules Tools Announcements. It provides a little more flexibility (e.g. you can change the amount of time between iterations of the loop). But it’s still going to require saving the Timer to the cache and only creating one when one doesn’t already exist.

Thanks for all the suggestions, Rich. The temperature syntax error was obvious when you pointed it out…duh.

We have one bathroom with a window, so this rule will only have one interval timer running. It has worked fine in the past, only within the past 2 months has it become erratic. Still, I’ll have a look at the Rules Tools, it won’t hurt to add more tools in the toolbox.

The purpose of the “WON”.
There may be situations where we are unable to go to the bathroom to close the window. After being nagged by the system for 30 minutes it should give up and stop making the announcements. Using the WON (with a 30 minute timeout) was the way I enabled the rule to stop after a set period.

Full disclosure: I wrote that rule a very long time ago, when I was just starting to learn about javascript. It worked fine so I didn’t bother it. Now that you I have looked at it with your comments in mind, I thought of another way to implement it. Hopefully this works better:


function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
for (let i = 0; i < 6; i++) {
  sleep(1000*60*10)
    .then(() if(items.getItem("Bathroom_Windows").state == "Closed") { break; }
    .then(() => if(items.getItem("ChetPresence").state == "ON") {
        items.getItem("Echo_Living_Room_TTS").sendCommand("Entschuldigung Sie bitte. Das Badfenster ist noch auf, kann jemand es zumachen, bitte?")
        items.getItem("Echo_Office_TTS").sendCommand("The Bathroom Window is still open. Can someone close it please.")
        actions.NotificationAction.sendNotification("kchest@gmail.com", "Hey Chet, the Bathroom Window is still open. Can you close it please.")
      }
      if(items.getItem("KatPresence").state == "ON") {
        actions.NotificationAction.sendNotification("kathrin@skorpil.org", "Hey Kathrin, the Bathroom Window is still open. Can you close it please.")
      }
}

I’m hoping this will repeat 6 times, checking each time if the bathroom window is open, if it is closed the for loop will break out, ending the cycle. Else it will make the announcements 6 times, waiting 10 times between announcements. Limited time, pausing between iterations, no timer conflicts.

I’ve used promises successfully in other rules.

Unless the rule is run again while an interval is already running.

If the window Item closes and then opens faster than ten minutes, you’ll have two intervals running.

You could just check for that in the loop. After three iterations simply exit the loop. Note, as currently implemented, when WON is OFF the interval continues to run in the background. It just doesn’t send alerts which it’s off. It doesn’t actually stop the interval.

Do promises work now? They didn’t use to. Unlike a “real” node.js environment, there is no event loop and that’s what promise works off of. It won’t throw errors but it also won’e actually sleep like you want. Use

var Thread = Java.type('java.lang.Thread');
Thread.sleep(1000);

This is going to tie up the rule for up to 60 minutes. If you open and close the window twice in that 60 minutes, the second OPEN is going to trigger the rule again but because the first one is still waiting, it will queue up and wait for the first 60 minutes to pass and then trigger the rule again and run it again for up to another 60 minutes. Open the close the window 24 times over the course of the day and this rule could run all day long. You may as well just change the rule trigger to a cron trigger to check every ten minutes if the window has been open for at least ten minutes (lastUpdate) and if it has send the notifications. Then you don’t need any sleeps or loops.

If you want it to be more precise than that, you really should use a timer. The only thing to watch out for is to keep a reference to the timer so you can cancel it and/or reschedule it.

Using LoopingTimer it would look like this (trigger the rule on any change to the window Item):

// Import the looping timer
var {LoopingTimer} = require('openhab_rules_tools');

// Pull the existing looping timer if there is one, otherwise create one
var timer = cache.private.get('timer', () => LoopingTimer());

// If the window opened, cancel the alerting timer
if(event.itemState.toString() != 'OPEN') {
  timer.timer.cancel();
  cache.private.put('timer', null);
  items.WON.postUpdate('ON'); // reset WON to ON
}

// Sleep for ten minutes, send alerts if people are home and repeat until 
// - timer is cancelled because the window opened
// - WON changes to OFF
else {
  timer.loop(() => {

    // Fail fast, if WON is OFF exit the loop by returning null
    if(items.WON.state == 'OFF') return  null;

    // Send the alerts if presence indicates they should be sent
    if(items.getItem("ChetPresence").state == "ON") {
      items.getItem("Echo_Living_Room_TTS").sendCommand("Entschuldigung Sie bitte. Das Badfenster ist noch auf, kann jemand es zumachen, bitte?")
      items.getItem("Echo_Office_TTS").sendCommand("The Bathroom Window is still open. Can someone close it please.")
      actions.NotificationAction.sendNotification("email", "Hey Chet, the Bathroom Window is still open. Can you close it please.")
    }
    if(items.getItem("KatPresence").state == "ON") {
      actions.NotificationAction.sendNotification("email", "Hey Kathrin, the Bathroom Window is still open. Can you close it please.")
    }

    // Exit the loop if we've exceeded 6 loops
    var alertCount = cache.private.get('alertCount', () => -1) + 1;
    if(alertCount >= 6) {
      cache.private.put('alertCount', 0);
      return null;
    }

    // Schedule the loop to run again in ten minutes by return PT10M
    cache.private.put('alertCount', alertCount);
    return 'PT10M';

  }, 'PT10M');

I kept WON involved so you have a way to manually turn the alerts off and added a max count of 6 loops to the logic.

The above could be implemented with a standard OH timer or an setInterval but in both cases you must keep a reference to the timer in the cache so you can cancel it or at least not recreate it when the rule triggers and the timer already exists.

Thanks so much, Rich for all your help, insights, and instruction.