Sensor configuration, unstable status

Good evening everyone !

I’m Fred, from France, a happy new user of openhab. I have achieved almost all my goals with openhab, even if I started with very small needs and ended with a lot more. It’s so fun and very adictive. Only my imagination seems to be a limitation.

I have a small issue, not really important, but I’m sure there’s a way to fix it.

I have a open/close door Z-Wave sensor fitted on a door that close very slowly. At the end, when the door is about to be fully closed, my sensor ‘hesitate’ before sending the ‘close’ signal. To be clear, the sensor send ‘closed-open-closed’ informations within a second, du to the slow motion of that door.
I have edited a rule to send notifications to my phone (via the openHAB android app). By consequence, I received 3 notifications : closed then open then closed.
Is there a way to have the item (related to the sensor thing) to change its status only when this status is stable ? Maybe something like the status change only once it has been stable for 2 seconds ? Or the notification boroadcasted only 2 seconds after the stable status ?
I wish I’m clear in my explanation… English is not my native tongue.

Here is the code for my rule ‘Notification for door closed’:

configuration: {}
triggers:

  • id: “1”
    configuration:
    itemName: Porte_Cote_Garage_Door_Sensor
    state: CLOSED
    previousState: OPEN
    type: core.ItemStateChangeTrigger
    conditions: []
    actions:
  • inputs: {}
    id: “2”
    configuration:
    message: Porte Coté Garage Fermée
    type: notification.SendBroadcastNotification

Install the Debounce rule template from the Marketplace and follow the instructions (more on rule templates here) to configure your Items and instantiate the rule. That will be the easiest approach.

If you want to code up the rule yourself, see [Deprecated] Design Pattern: Motion Sensor Timer. The overall approach is set a Timer on the first event for some short time (two seconds to meet your requirement). When the rule triggers again, if the Timer already exists reschedule it. Two seconds after the Item stops changing the Timer will go off and send your alert in the Timer’s body.

1 Like

Debounce is exactly what I need !
But… I must be a complete nut, as I can’t find it anywhere in the Marketplace…
Probably I’m not looking at the right place ?

You need to enable the marketplace in Settings → Community Marketplace → Enable Community Marketplace.

They you will find all the published rule templates under Settings → Automation

1 Like

These options are enabled… But I only have 24 ‘rules templates shared by the community’ and Debounce is not among them. I can see ‘Persistence Presence Simulation’ from you, but not Debounce…

Ok, found. I had to click ‘Include not compatible add ons’. (As I run openHAB 3.4.1)

Well… Thank you Rich.
I have created a Debounce group, add my sensor in the group with special metadata and create the Debounce rule. Now, I have to check if it works (as my problem doesn’t occurs every time I close my door, sometimes the sensor works flawlessly, sometimes not).

Hmmm, it should have still shown up. I may need to adjust the versioning.

Obviously, I must have made something wrong. This morning it doesn’t work.

Here the log :


Well maybe you need more informations to help me.

So here what else I have done :

I hope with all these informations, you might be able to help me…
Best regards

The Item garagesensordeb doesn’t exist. The rule doesn’t create that Item for you. You have to create it yourself.

And when posting your configs, if there is a Code tab, please copy the YAML you find there and paste it into your reply using code fences.

```
code goes here
```

Screen shots are all but impossible to read, cannot be searched, and leave out a ton of necessary context. Your screen shots of the Debounce rule pretty much tells me nothing.

do the same for logs.

Thank you for the tips. I’ll do that now.

Ok, so I have created a proxy item with the same name as what I’ve written in the raw sensor metadata, put that new item inside the debounce group. But this proxy item doesn’t reflect the open or closed state… So I can’t use it to create a notification rule…
I’m totally lost. I don’t know which code to post here to show you where I’m wrong… A bit upset.

Here is the metadata in the raw sensor

value: Garage_Door_Sensor
config:
  timeout: 1s

Here is my debounce rule

configuration:
  debounceGroup: Debounce
triggers:
  - id: "1"
    configuration:
      groupName: Debounce
    type: core.GroupStateChangeTrigger
conditions:
  - inputs: {}
    id: "2"
    label: Only debounce non NULL and UNDEF states
    configuration:
      type: application/javascript
      script: >-
        if(typeof(require) === "function") Object.assign(this,
        require('@runtime'));

        var type = (typeof(require) === "function") ? UnDefType : UnDefType.class;

        event.itemState.class != type;
    type: script.ScriptCondition
actions:
  - inputs: {}
    id: "3"
    label: Debounce state based on debounce metadata
    description: "Expected Metadata: debounce=ProxyItem[timeout='2s',
      state='OFF',  command=true]"
    configuration:
      type: application/javascript
      script: >
        if(typeof(require) === "function") Object.assign(this,
        require('@runtime'));


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


        // Get Metadata query stuff

        this.FrameworkUtil = (this.FrameworkUtil === undefined) ? Java.type("org.osgi.framework.FrameworkUtil") : this.FrameworkUtil;

        this.ScriptHandler = Java.type("org.openhab.core.automation.module.script.rulesupport.shared.ScriptedHandler");

        this._bundle = (this._bundle === undefined) ? FrameworkUtil.getBundle(ScriptHandler.class) : this._bundle;

        this.bundle_context = (this.bundle_context === undefined) ? this._bundle.getBundleContext() : this.bundle_context;

        this.MetadataRegistry_Ref = (this.MetadataRegistry_Ref === undefined) ? bundle_context.getServiceReference("org.openhab.core.items.MetadataRegistry") : this.MetadataRegistry_Ref;

        this.MetadataRegistry = (this.MetadataRegistry === undefined) ? bundle_context.getService(MetadataRegistry_Ref) : this.MetadataRegistry;

        this.Metadata = (this.Metadata === undefined) ? Java.type("org.openhab.core.items.Metadata") : this.Metadata;

        this.MetadataKey = (this.MetadataKey === undefined) ? Java.type("org.openhab.core.items.MetadataKey") : this.MetadataKey;


        // Load TimerMgr

        // TODO: Replace with an installed library when the marketplace supports it

        //this.OPENHAB_CONF = (this.OPENHAB_CONF === undefined) ? java.lang.System.getenv("OPENHAB_CONF") : this.OPENHAB_CONF;

        //load(this.OPENHAB_CONF+'/automation/lib/javascript/community/timerMgr.js');


        var TimerMgr = function() {
          var OPENHAB_CONF = java.lang.System.getenv("OPENHAB_CONF");
          this.log = Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Rules.rules_tools.TimerMgr");
          this.log.debug("Building timerMgr instance.");
          this.timers = {};
        //  this.log.debug("Loading timeUtils");

        //  load(OPENHAB_CONF+'/automation/lib/javascript/community/timeUtils.js');
          this.ScriptExecution = Java.type("org.openhab.core.model.script.actions.ScriptExecution");
          this.log.debug("Timer Mgr is ready to operate");
        }


        // reproduced from timeUtils, truncated to only parse duration strings

        TimerMgr.prototype.toDateTime = function(when) {
          var Duration = Java.type("java.time.Duration");
          var ZonedDateTime = Java.type("java.time.ZonedDateTime");
          var dur = null;
          var dt = null;

          var regex = new RegExp(/[\d]+[d|h|m|s|z]/gi);
          var numMatches = 0;
          var part = null;

          var params = { "d": 0, "h": 0, "m":0, "s":0, "z":0 };
          while(null != (part=regex.exec(when))) {
            this.log.debug("Match = " + part[0]);
            numMatches++;

            var scale = part[0].slice(-1).toLowerCase();
            var value = Number(part[0].slice(0, part[0].length-1));
            params[scale] = value;
          }

          if(numMatches === 0){
            this.log.warn("Could not parse any time information from '" + timeStr +"'. Examples of valid string: '8h', '2d8h5s200z', '3d 7m'.");
          }
          else {
            this.log.debug("Days = " + params["d"] + " hours = " + params["h"] + " minutes = " + params["m"] + " seconds = " + params["s"] + " msec = " + params["z"]);
            dur = Duration.ofDays(params["d"]).plusHours(params["h"]).plusMinutes(params["m"]).plusSeconds(params["s"]).plusMillis(params["z"]);
          }
          
          if(dur !== null) {
            dt = ZonedDateTime.now().plus(dur);
          }
          return dt;
        },


        TimerMgr.prototype._notFlapping = function(key) {
          this.log.debug("Timer expired for " + key);
          if (key in this.timers && "notFlapping" in this.timers[key]) {
            this.log.debug("Calling expired function " + this.timers[key]["notFlapping"]);
            this.timers[key]["notFlapping"]();
          }
          if (key in this.timers){
            this.log.debug("Deleting the expired timer");
            delete this.timers[key];
          }
        },


        TimerMgr.prototype._noop = function() { },


        TimerMgr.prototype.check = function(key, when, func, reschedule, flappingFunc) {
          this.log.debug("Timer manager check called");
          if (reschedule === undefined) reschedule = false;

          var timeout = this.toDateTime(when);
          this.log.debug("Timer to be set for " + timeout.toString());

          // Timer exists
          if (key in this.timers){
            if (reschedule){
              this.log.debug("Rescheduling timer " + key + " for  " + timeout.toString());
              this.timers[key]["timer"].reschedule(timeout);
            }
            else {
              this.log.debug("Cancelling timer " + key);
              this.cancel(key);
            }
            if (flappingFunc !== undefined){
              this.log.debug("Running flapping function for " + key);
              flappingFunc();
            }
          }
          
          // Timer doesn't already exist, create one
          else {
            this.log.debug("Creating timer for " + key);
            var timer = this.ScriptExecution.createTimerWithArgument(timeout, this, function(context) { context._notFlapping(key); });
            this.timers[key] = { "timer": timer,
                                 "flapping": flappingFunc,
                                 "notFlapping": (func !== undefined) ? func : this._noop }
            this.log.debug("Timer created for " + key);
          }
        },


        TimerMgr.prototype.hasTimer = function(key) {
          return key in this.timers;
        },


        TimerMgr.prototype.cancel = function(key) {
          if (key in this.timers) {
            this.timers[key]["timer"].cancel();
            delete this.timers[key];
          }
        },


        TimerMgr.prototype.cancelAll = function() {
          for (var key in this.timers) {
            if (!this.timers[key]["timer"].hasTerminated()) {
              this.log.debug("Timer has not terminated, cancelling timer " + key);
              this.cancel(key);
            }
            delete this.timers[key];
            this.log.debug("Timer entry has been deleted for " + key);
          }
        }

        // TODO: End


        /**
         * Get and check the item metadata.
         * @return {dict} The metadata parsed and validated
         */
        var checkMetadata = function(itemName, timers) {
          var USAGE = "Debounce metadata should follow debounce=ProxyItem[command=true, timeout='2s', state='ON,OFF']."
          var cfg = MetadataRegistry.get(new MetadataKey("debounce", itemName));
          if(cfg === null) {
            throw itemName + " does not have debounce metadata! " + USAGE;
          }
          
          if(cfg.value === undefined || cfg.value === null) {
            throw itemName + " does not have a proxy Item defined! " + USAGE;
          }
          if(items[cfg.value === undefined]) {
            throw "Proxy Item " + cfg.value + " does not exist! " + USAGE;
          }
          if(cfg.configuration["timeout"] == undefined || cfg.configuration["timeout"] === null) {
            throw itemName + " does not have a timeout parameter defined! " + USAGE;
          }
          if(timers.toDateTime(cfg.configuration["timeout"]) === null) {
            throw itemName + "'s timeout " + cfg.configuration["timeout"] + " is not valid! " + USAGE;
          }
          var dict = {"proxy": cfg.value,
                      "timeout": cfg.configuration["timeout"],
                      "command": "command" in cfg.configuration && cfg.configuration["command"].toString().toLowerCase() == "true",
                      };
                      
          dict["states"] = [];
          var stateStr = cfg.configuration["states"];
          if(stateStr !== undefined && stateStr !== null) {
            var split = stateStr.split(",");
            for(var st in split) {
              dict["states"].push(split[st]);
            }
          }
          return dict;
        }


        /**
         * Called when the debounce timer expires, transfers the current state to the 
         * proxy Item.
         * @param {string} state the state to transfer to the proxy Item
         * @param {string} name of the proxy Item
         * @param {Boolean} when true, the state is sent as a command
         */
        var end_debounce_generator = function(state, proxy, isCommand) {
            return function() {
                logger.debug("End debounce for " + proxy + ", new state = " + state + ", curr state = " + items[proxy] + ", command = " + isCommand);
                if(isCommand && items[proxy] != state) {
                  logger.debug("Sending command " + state + " to " + proxy);
                  events.sendCommand(proxy, state.toString());
                }
                else if (items[proxy] != state) {
                  logger.debug("Posting update " + state + " to " + proxy);
                  events.postUpdate(proxy, state.toString());
                }
              }
        }


        this.timers = (this.timers === undefined) ? new TimerMgr() : this.timers;

        var cfg = checkMetadata(event.itemName, this.timers);


        if(cfg["states"].length == 0 || 
          (cfg["states"].length > 0 && cfg["states"].indexOf(event.itemState.toString()) >= 0)) {
          logger.debug("Debouncing " + event.itemName + " with proxy = " + cfg["proxy"] 
                       + " timeout = " + cfg["timeout"] + " and states = " + cfg["states"]);
          this.timers.check(event.itemName, cfg["timeout"], 
                            end_debounce_generator(event.itemState, cfg["proxy"], cfg["command"]));    
        }

        else {
          logger.debug(event.itemName + " changed to " + event.itemState + " which is not debouncing");
          this.timers.cancel(event.itemName); // Cancel the timer if it exists
          end_debounce_generator(event.itemState, cfg["proxy"], cfg["command"])();
        }
    type: script.ScriptAction

Now, the proxy sensor is supposed to show open or closed, I presume. It’s just NULL.

It’s OK ! I just had to activate the door to see NULL going to CLOSED. Now, it seems to work ! I have update my Notification Rules with the proxy sensor instead of the raw one, and it seems to work.

You, guys, are genius (I’m not, but soon).

Thank you very, very much, Rich, for your patience.

1 Like

No, this Item shouldn’t be inside the Debounce Group. Only the “raw” Items go in the Group.

You should get errors in the logs when this Item changes because it doesn’t have debounce metadata.

1 Like

Once again, you’re right. Debounce worked but I got a error message. Now, I removed it for the debounce group, and it works without error message.

Thanks again.