Action if an Item state has changed for at least an hour


I’d like to write a rule that switches our indoor humidifiers on/off depending on data from our humidity sensor. The Things and Items are setup, and I am now working on the rule to glue it together. My first simple rule reacts on changes to the Point that represents the humidity measurement. However I noticed now that even a short opening of our windows drops the humidity, which turns on the humidifiers, only for them to be turned off again a short time after the windows are closed.

So now I’d like to rewrite the rule to have it act only if the humidity drop/raise is stable for at least an hour.

My first thought was to have a rule that runs hourly, checks the humidity, if it is above or below the thresholds store this into a global variable and only on the next run switch the humidifier on/off if the humidity is still above the threshold.

Does this approach make sense or is there a better design pattern for solving this with openHAB?

What would be the best way to store such a global variable that can be accessed by the script on the next run? I am using ECMAScript (ECMAScript 262 Edition 11) for scripting.

You can use the private-rule-specific cache or the global rule cache for storing values:

cache.private.put(‘key’, ‘value’);
cache.shared.put(‘key’, ‘value’);

see cache - Documentation

1 Like

you could use the cache for runtime-variables or ask the persistence, if there was some changes somehow. works with ECMA-2021. Depending on your use case you can chose from a variety of history.objects:

1 Like

Thanks to both of you!

The cache confused me at first, I used the “Run” function in the UI to test my script, and there the private cache does not seem to work. But once the rule was triggered by cron, the private cache worked as well as it should.

So, it works with the cache, but being able to access the historic state data is even more elegant.

1 Like

In case anyone else reading this may need it, here is the script I ended up with:

Edit: updated/improved version further down below.

1 Like

One thing that @florian-h05 explained me that the run-command creates a new instance of the rule which is why the private cache will never work when you manually run a rule :wink:

@binderth I actually almost posted the idea with the persistence as well, e.g check how long the window has been open as an example

but then I saw that Robert actually wanted to check whether something is in a certain threshold which is a bit more complicated and not straight forward like my example but now I thought, I may still post it as an example here.

1 Like

I have to correct this a bit, there is a little but important difference between standard UI scripts and Blockly:

When you write a script in the UI and save it, the script gets a new context on every time running save. Therefore storing vars in the context using this (which is not recommended) or in the cache (which is the recommended way) works when you run a rule manually as long as you don’t press save.

However for Blockly it is not possible to persist data in the the cache (or the local context) between manual runs of the script, because the Blockly editor performs a save under the hood every single time you press run. I’ve checked this in the source code.

Some other options with persistence might give you better results. Right now you are getting the state from an hour ago, but what about all the values inbetween? Humidity could change a lot in an hour.

If you used minimumSince though you’ll know that all the readings between now and an hour ago have exceeded the threshold.

If you use aveageSince you’d know that the majority of the values between now an an hour ago have exceeded the threshold.

These are super minor changes which could lead to more appropriate behavior.

If you went down the averageSince route, you could make the rule more responsive by triggering it on every new humidity reading. Then as soon as the hour average goes above the threshold you’re rule would take action instead of needing to wait up to 59 minutes when your hourly rule runs next.

Yes, you are right, that is the better solution. I decided to go for the minimum, seems like the most stable option. I see also no problem with running the rule more often, now set it to every five minutes.

Not sure if using a change to the humidity is a good trigger. Say if my threshold is 60 % and it drops from 60 % to 59 % but then does not drop further, the rule would not really work, or would it? The initial change would not lead to an action, because the last hour minimum/maximum is still above/below the threshold, and then there would not be any further change to trigger the rule again.

Anyway, here is the updated script:

Checks humidity values and turns a humidifer on/off if needed

sensorName: name of the Point (type Number) representing the humidity measurement
humidifierName: name of the Point (type Switch) controlling the humidifer
floorShort: short code for the floor/room, for logging and email notification
floorLong: long name of the floor/room, for logging and email notification
lowerThresholdName: name of the Point (type Number) representing the lower threshold, under which the humidfier will be turned on.
lowerThresholdName: name of the Point (type Number) representing the upper threshold, under which the humidfier will be turned off.
thresholdPeriodName: name of the Point (type Number) representing the amount of minutes we check back for the last reading. Only if both current and last reading are above or below the threshold an action will be taken, to avoid unncessary switching if the humidity changes for only s hort period of time, e.g. when the windows are opened.
function controlHumidifier( sensorName, humidifierName, floorShort, floorLong, lowerThresholdName, upperThresholdName, thresholdPeriodName )
  // Retrieve all the items we need
  var sensor = items.getItem( sensorName );
  var humidifier = items.getItem( humidifierName );
  var lowerThreshold = parseFloat( items.getItem( lowerThresholdName ).state );
  var upperThreshold = parseFloat( items.getItem( upperThresholdName ).state );
  var lastReadingMinutes = parseFloat( items.getItem( thresholdPeriodName ).state );
  // Get humidity values, current and last reading as per the configured minutes interval
  var humidity = parseFloat( sensor.state );
  var intervalStartDate= new Date();
  intervalStartDate.setMinutes( intervalStartDate.getMinutes() - lastReadingMinutes );
  var humidityMinimum = parseFloat( sensor.history.minimumSince( intervalStartDate) );
  var humidityMaximum = parseFloat( sensor.history.maximumSince( intervalStartDate) );
  var consoleMsg = "Humidity " + floorShort + ": " + lastReadingMinutes + "min min=" + humidityMinimum + "%, " + lastReadingMinutes + "min max=" + humidityMaximum + "%, ";
  var emailSubj = null;
  var emailText = null;

  // If current and last reading are above the upper limit and the humidifier is ON, we switch it OFF
  if( humidityMinimum > upperThreshold && humidifier.state == "ON" )
      humidifier.sendCommand( "OFF" );
      emailSubj = "Luftfeuchtigkeit " + floorShort + " > " + upperThreshold + " %";
      emailText = "Luftfeuchtigkeit im " + floorLong + " ist " + humidity + " %. Luftbefeuchter wurde abgeschalten.";
      consoleMsg += "upper threshold=" + upperThreshold + "% => switching OFF humidifier"; consoleMsg );
  // If current and last reading are below the lower limit and the humidifier is OFF, we switch it ON
  else if( humidityMaximum < lowerThreshold && humidifier.state == "OFF" )
      humidifier.sendCommand( "ON" );
      emailSubj = "Luftfeuchtigkeit " + floorShort + " < " + lowerThreshold + " %";
      emailText = "Luftfeuchtigkeit im " + floorLong + " ist " + humidity + " %. Luftbefeuchter wurde angeschalten.";
      consoleMsg += "lower threshold=" + lowerThreshold + "% => switching ON humidifier"; consoleMsg );
      consoleMsg += "lower threshold=" + lowerThreshold + "%, upper threshold=" + upperThreshold + "%, humidifer=" + humidifier.state + " => no action required."; consoleMsg );

  // Send out email
  if( emailSubj )
        .get( "mail", "<your-smtp-thing>" )
        .sendMail( "<your-email-address>", emailSubj, emailText );

// Check humidity on ground floor
    "HT_EG_WZ_Sensor_Humidity", "Humidifier_EG_WZ_Power",
    "EG", "Erdgeschoss",
    "Config_Humidity_EG_Lower_Threshold", "Config_Humidity_EG_Upper_Threshold", "Config_Humidity_EG_Last_Reading_Minutes"

// Check humidity on first floor
    "HT_OG_SZ_Sensor_Humidity", "Humidifier_OG_GL_Power",
    "OG", "Obergeschoss",
    "Config_Humidity_OG_Lower_Threshold", "Config_Humidity_OG_Upper_Threshold", "Config_Humidity_OG_Last_Reading_Minutes"

It depends on how often the Item changes/updates. Sensors tend to be gittery and humidity tends to move a lot too on its own.

If the Item changes to 59 % and then get’s updated to 59 % every five minutes for the next hour, you’d use a received update trigger and the rule will run every time the Item receives and update, even if it doesn’t change.

Yes, understand, if going for the updates instead of changes it would work.

You will probably want to also take into account how long since the last update and last change, and likely want to run the script on a schedule as well.

I.e, if the dehumidifier is turned on, and then the sensor dies, you would not want to leave the dehumidifier permanently on.

Likewise, if the sensor gets wedged, and is sending updates, but sends the exact same value over and over, you probably don’t want to keep running the dehumidifier

And yes, I have had both of the above happen to me when controlling a furnace. Both times of course when I was not home, and both times resulted in a very warm house.


Looking at the maximum/minimum value of the previous hour should cover this I think.

I have not really covered the case that the sensor dies, but I am also not too concerned about it. It is about humidifiers, not de-humidifiers, and their effect is not that immediate. There’d be no harm if they run for a day or two while the humidity is higher than the threshold and the sensor is not working.