Debounce [3.2.0;3.4.9]

I am experiencing initialization issues with this rule - I suspect it is a form of race condition.

Now on openHAB startup, I end up having NULL debounced item while the “input items” (items having debounce metadata) are OK/ON.

I suspect the “input items” get their state update/state change (from NULL to ON) before the rule is ready to process the update.

This is of course just a theory. Is this expected behaviour?

It would be great if the rule has on-startup behaviour to initalize the result item if we do not receive any state changes. Or perhaps there is some other way to mitigate this?

I think this would be expected. It’s pretty typical that restoreOnStartup happens before the rule engine is ready so there is no event to trigger the rule in that case.

I would expect the proxy Item to be the one with restoreOnStartup rather than or in addition to Item linked to the Channel.

However, if the Channel linked Item is indeed getting an update before the rule engine runs that one is a little tricky. IIRC the additions to event that include more information about how the rule was triggered hasn’t been merged yet making it not quite as straight forward to tell when the rule is triggered during a system start. I’ll have to think about it.

This whole rule template will be rewritten for OH 4, I’ll have to take this into account.

I do not use restoreOnStartup so it might be that rules actually take some time to initialize… more than my things that start pushing state updates

This is my workaround in Device status online monitor

const cronOrStarted = typeof event === 'undefined' || (event.statusInfo === undefined && event.itemState === undefined)

There is also the type property in UI events: JavaScript Scripting - Automation | openHAB (I have not tried this, not sure which version includes this)

The Rules Engine starts at runlevel 50 but the Thing don’t initialize until runlevel 80. But it’s not deterministic. On a fast(ish) machine I could see that this rule is still initializing after the Things have initialized. But that wouldn’t be typical.

I know there is some talk about looking at this behavior in OH 4 because of other issues so maybe this will get fixed as a matter of course.------------------------------

Yes, but that will also match when the rule is run manually or based on a time trigger or when called from another rule. I’m not sure it makes sense to blindly run the rule like it’s system started in all of those circumstances.

I wasn’t aware that PR has been merged. That makes it a little easier, though I still need to decide what makes sense for some of those other trigger types.

Hmm weird thing. I have raspi4 fwiw.

I guess logs would reveal the sequence of events but have not looked into this so deeply.

One simple workaround I can think of is having a separate rule to initialize debounced item (if state is still null). But thought it would make sense to incorporate it into this one

If you would like to try it, here is a version of Debounce rewritten in JS Scripting using the helper library. It has two modes. When it’s triggered with an Item event it operates as expected.

When it’s triggered in any other way it does the following:

  • pulls the Items with “debounce” metadata
  • validates the metadata is usable, reporting what’s wrong with it
  • updates the proxy Item with the current state of the source Item
  • reports any Item which has “debounce” metadata but is not a member of the Debounce Group.

So it serves as a validity check on your Item’s configs as well as a way to initialize your proxy Items. You can add a System runlevel trigger or just run the rule manually to meet your initialization use case. I might make that an option that can be turned on and off through the rule template properties.

configuration: {}
triggers:
  - id: "2"
    configuration:
      groupName: Debounce
    type: core.GroupStateChangeTrigger
conditions:
  - inputs: {}
    id: "1"
    label: Limit rule execution
    description: Only runs rule if it's not an Item change to NULL/UNDEF or it's not
      an Item event
    configuration:
      type: application/javascript
      script: >
        console.loggerName = 'org.openhab.automation.rules_tools.Debounce';


        if(this.event !== undefined && this.event.itemName !== undefined){
          const item = items.getItem(this.event.itemName);
          if(item.isUninitialized) {
            console.debug('Debounce for Item', this.event.itemName, 'is blocked for state', this.event.itemState);
          }
          !item.isUninitialized;
        }

        else {
          console.trace('Debounce passed conditions');
          true;
        }
    type: script.ScriptCondition
actions:
  - inputs: {}
    id: "3"
    label: Debounces the state of an Item
    description: Waits the configured amount of time after the most recent change
      before updating or commanding a proxy Item.
    configuration:
      type: application/javascript
      script: >-
        var {timerMgr} = require('openhab_rules_tools');

        console.loggerName = 'org.openhab.automation.rules_tools.Debounce';


        var USAGE = "Debounce metadata should follow this format:\n" 
                    + ".items File: debounce=ProxyItem[command=true, timeout='PT2S', state='ON,OFF']\n"
                    + "UI YAML: use 'debounce' for the namespace and metadata format\n"
                    + "value: ProxyItem\n"
                    + "config:\n"
                    + "  command: true\n"
                    + "  timeout: PT3S\n"
                    + "  state: ON,OFF\n"
                    + "};\n"
                    + "timeout must be in a format supported by time.toZDT() in the add-on's helper library."

        /**
         * Get and check the Item metadata.
         * @return {dict} the metadata parsed and validated
         */
        var getConfig = (itemName) => {
          
          // ~ TODO ~ TODO ~ TODO ~ TODO ~ TODO ~ TODO ~ TODO ~ TODO
          // Replace with item.getMetadata() when available
          // Get Metadata query stuff
          const MetadataRegistry = osgi.getService('org.openhab.core.items.MetadataRegistry');
          const Metadata = Java.type('org.openhab.core.items.Metadata');
          const MetadataKey = Java.type('org.openhab.core.items.MetadataKey');

          const md = MetadataRegistry.get(new MetadataKey('debounce', itemName));
          if(!md) {
            throw itemName + ' does not have debounce metadata!\n' + USAGE;
          }
          
          if(!md.value) {
            throw itemName + ' has malformed debounce metadata, no value found!\n' + USAGE;
          }
          
          if(!items.getItem(md.value)) {
            throw itemName + ' has invalid debounce metadata, proxy Item ' + md.value + ' does not exist!\n' + USAGE;
          }
          
          if(!md.configuration['timeout']) {
            throw itemName + 'has malformed debounce metadata, timeout configuration parameter does not exist!\n' + USAGE;
          }
          
          if(!time.toZDT(md.configuration['timeout'])) {
            throw itemName + ' has invalid debounce metadata, timeout ' + md.configuration['timeout'] + ' cannot be parsed to a valid duration!\n' + USAGE;
          }
          
          var cfg = {'proxy':   md.value,
                     'timeout': md.configuration['timeout'],
                     'command': 'command' in md.configuration && md.configuration['command'].toString().toLowerCase() == 'true',
                     'states':  [],
                    };
          const stateStr = md.configuration['states'];
          if(stateStr) {
            stateStr.split(',').forEach((st) => {
              cfg.states.push(st.trim());
            });
          }
          
          return cfg;
        };


        /**
         * Called at the end of a debounce to update or command the proxy Item
         * @param {string} name the originating Item's name
         * @param {string} state the originating Item's new state
         * @param {string} proxy the name of the proxy Item
         * @param {boolean} isCommand whether to send a command or update to the proxy
         * @return {function} an argumentless function to call to update/command the proxy Item at the end of the debounce
         */
        var endDebounceGenerator = (name, state, proxy, isCommand) => {
          return function(){
            console.debug('End debounce for', name, "state", state, 'with proxy', proxy, 'and isCommand', isCommand);
            const isCurrState = (items.getItem(name).state == state)
            if(isCommand && !isCurrState) {
              console.trace('Commanding');
              items.getItem(proxy).sendCommand(state);
            }
            else if(!isCommand && !isCurrState) {
              console.trace('Updating');
              items.getItem(proxy).postUpdate(state);
            }
          };
        };


        var debounce = () => {
          console.debug('Debounce:', event.type, 'item:', event.itemName);

          // Initialize the timers, congif and end debounce function
          const timers = cache.private.get('timerMgr', () => new timerMgr.TimerMgr());
          const cfg = getConfig(event.itemName);
          const endDebounce = endDebounceGenerator(event.itemName, event.itemState, cfg.proxy, cfg.command);

          // If there are no states in the debounce metadata or if the new state is in the list of debounce states
          // set a timer based on the timeout parameter
          if(cfg.states.length == 0
             || cfg.states.includes(event.itemState.toString())) {
            console.debug('Debouncing ', event.itemName, "'s state", event.itemState, 
                         'using proxy', cfg.proxy, 'timeout', cfg.timeout, 
                         'command ', cfg.command, 'and states ', cfg.states);
            timers.check(event.itemName, cfg.timeout, endDebounce);
          }
          // If this is not a debounced state, immediately forward it to the proxy Item
          else {
            console.debug(event.itemName, 'changed to', event.itemState, 'which is not among the debounce states:', cfg.states);
            timers.cancel(event.itemName);
            endDebounce();
          }  
        };



        var init = () => {
          console.info("Validating Item metadata, group membership, and initializing the proxies");
          let isGood = true;
          let badItems = [];
          
          // Get all the Items with debounce metadata and check them
          const is = items.getItems();
          console.debug('There are', is.length, 'Items');
          const filtered = is.filter( item => item.getMetadataValue('debounce'));
          console.debug('There are ', filtered.length, 'Items with debounce metadata');
          filtered.forEach(item => {
            console.debug('Item', item.name, 'has debounce metadata');
            try {
              const cfg = getConfig(item.name);
              const proxy = items.getItem(cfg.proxy);
              if(proxy.state != item.state) {
                console.info('Updating', cfgProxy, 'to', item.state);
                proxy.postUpdate(item.state);
              }
              else {
                console.debug(cfg.proxy, 'is already in the state of', item.state);
              }
              if(!item.groupNames.includes('Debounce')) {
                console.warn(item.name, 'has debounce metadata but is not a member of Debounce!')
                isGood = false;
                badItems.push(item.name);
              }
            }
            catch(e) {
              console.warn('Item', item.name, 'has invalid debounce metadata:\n', e, '\n', USAGE);
              isGood = false;
              badItems.push(item.name);
            }
            
          });
          
          // Report those Items that are members of Debounce but don't have debounce metadata
          items.getItem('Debounce').members.filter(item => {!item.getMetadataValue('debounce')}).forEach(item => {
            console.warn(item.name, 'is a member of Debounce but lacks debounce metadata');
            isGood = false;
            badItems.push(item.name);
          });
          
          if(isGood) {
            console.info('All debounce Items are configured correctly');
          }
          else{
            console.log('The following Items have an invalid configuration. See above for details:', badItems);
          }
        }


        // ~ TODO ~ TODO ~ TODO ~ TODO ~ TODO ~ TODO ~ TODO ~ TODO

        // change when there is always an event object

        if(this.event === undefined) {
          init();
        }

        else {
          switch(event.type) {
            case 'ItemStateEvent':
            case 'ItemStateChangedEvent':
            case 'ItemCommandEvent':
              debounce();
              break;
            default:
              init();
          }
        }
    type: script.ScriptAction

This version requires Announcing the initial release of openhab_rules_tools which can be installed through openHABian or manually from the command line. New versions of openHABian will install it by default.

It’s not been made into a rule template yet. For now it should work in OH 3.4. But soon I’ll adopt some of the new goodness coming down the line (event.type to tell all the ways a rule is triggered, better ways to get the Item metadata) and it will only support OH 4.0 and later. At that point I’ll probably create a new post to the Marketplace that only supports OH 4.0+ and leave this one for legacy support.

Note, the timeout property has changed to only support ISO8601 formatted durations but they have also been expanded to support other stuff (e.g. number of milliseconds). That will be the first thing you see when you run it manually.

Installation:

  • install the JS Scripting add-on
  • create a new rule, fill out the metadata (UID, name, description, etc.)
  • copy the code above and paste it into the code tab of the rule
  • since there’s no properties you might need to do a find and replace for the Group name and debounce metadata namespace you used if it’s not “Debounce” and “debounce” respectively.

Even if you don’t try it out, let me know based on the description if I’m heading in the right direction.

One of the things I want to do with all of my rule templates is improve that initial error checking of the metadata (where applicable) in a similar way.

Thanks, very much would like to try this out! Does this work in 3.3? I haven’t updated yet

Tried it but getting some errors

  • mimetype refers to old-js, changed the it to refer to new ecmascript
  • some usage message refer to “state” not “states”. Probably a typo

But still getting this:

023-01-07 13:42:05.903 [WARN ] [nhab.automation.rules_tools.Deb ounce] [org.openhab.automation.jsscripting] - Item SiemensS7Modb usHoldingOKRaw has invalid debounce metadata: {} Debounce metadata should follow this format: .items File: debounce=ProxyItem[command=true, timeout='PT2S', st ate='ON,OFF'] UI YAML: use 'debounce' for the namespace and metadata format value: ProxyItem config: command: true timeout: PT3S state: ON,OFF }; timeout must be in a format supported by time.toZDT() in the add -on's helper library.

Metadata entry

value: SiemensS7ModbusHoldingOK
config:
  timeout: PT5M
  states: OFF

That’s right. They swapped the mime types in 4 already. Now JS Scripting gets the standard and Nashorn has the non-standard one.

Not as written. It uses the new privateCache which wasn’t added until 3.4. You’ll have to change the line that instantiates the timerMgr to use just cache, not cache.private.

That error doesn’t make sense to me. I can’t remember when certain things were added to the helper library. You might need to install the openHABian node module to get the gateway version of that, though I fear that has dependencies on changes in core.

You’ll probably need to upgrade to 3 4 fire this to work.

Ok; thanks for the help anyways!

I am unable to test this now since I avoid updates during winter time :smile:

I used npm from debian repo to install the helper, perhaps that is the issue. Unfortunately I do not use the openhabian, I am not sure what you refer with that.

openHABian comes with a config tool to install/remove lots of third party stuff like Mosquito, Frontal, etc. openhab-rules-tools is included among those.

I believe new installs of openHABian will install it by default.

Hi,
I am new in rule templates at all and I am feeling to be an idiot, since I cannot find any rule or blockly template below “Settings>Automation>Languages & Technologies” as described in the docs.


My question here: What is overseen by myself to install rule templates?
I try to find help since hours on the web… but it seems, I am the only one who’s affected…

I use openhabian 3.4.0 (migrated over the time from 2.something) on a RasPi 4.
Most of the config comes from files, but step by step moving to UI.

May somebody is able to point me to the right direction.
Thanks!

Debounce is just a template for a rule. It doesn’t create any rule by itself.

You have to create a new rule an select the debounce template for your new rule.

For example:

Resulting in the actual rule:

Hi Christian,
thanks for your short time reply.
But what should I say: If I click on the plus sign inside the rule list to add a new rule, the upcoming window does not showcase ‘Create from Template’ as you see in the following picture:

If you scroll down within “automation” you should see the rules templates. As the rules templates are online you need a working internet connection so that they show up

Hi Locke,
thanks, since it looks like to be a more general problem in my installation environment, I opened an extra thread, outside of ‘debounce’.
/Mav

You have to enable the marketplace under settings - community marketplace (or something like that).

1 Like

Hi Rich,
thanks for the hint. The setting were enabled already, but disabling and re-enabling solved the problem… the missing sections now appeared.
BR
/Mav

For a long time I was looking for a feature to lower the knx traffic while updating temperature values from one “Resol”-binding to the “knx”-binding without typing tons of rules. So at this point thanks for that!

Formerly I had an item definition to “follow” the Resol data and update the values to knx:


Number:Temperature ResolEMS1_buffer_top "temp top [%.1f %unit%]"  (gResol)  { channel="resol:device:VBUS:DeltaSol_MX-Modules:temperature_module_1_sensor_1", channel="knx:device:bridge:IPRouter:proxy_resol_temp_buffer_top" [profile="follow"] }

This “follow”-profile works, but the values are being updated every 6 to 10 seconds. Far too much traffic within the knx line with a lot of unused data.

I tried the following item definition to ‘debounce’ the Resol binding temperature values by 3 minutes:


Number:Temperature    raw_resol_temp_buffer_top        "raw: Resol temp buffer top [%.1f] °C"        (gProxy)    {channel="resol:device:VBUS:DeltaSol_MX-Modules:temperature_module_1_sensor_1"}

Number:Temperature    debounced_resol_temp_buffer_top "debounced: Resol temp buffer top [%.1f] °C"        (gProxy)    {debounce="raw_resol_temp_buffer_top"[timeout="3m"], channel="knx:device:bridge:IPRouter:proxy_resol_temp_buffer_top" [profile="follow"]}

EDIT

I missed the group. The Item now is member of the group ‘Debounce’.

I misunderstod the concept. Should be “RAW-Item with ‘debounce’-profile to update proxy”. Therefor I tried this:


Group                 Debounce                  "Debounce"

Number:Temperature    raw_resol_test          "raw: Resol test - temp buffer top [%.1f] °C"          (Debounce)    { debounce="debounced_resol_test"[timeout="1m"], channel="resol:device:VBUS:DeltaSol_MX-Modules:temperature_module_1_sensor_1"}

Number:Temperature    debounced_resol_test    "debounced: Resol test - temp buffer top [%.1f] °C"    (gProxy)    { channel="knx:device:bridge:IPRouter: proxy_resol_temp_buffer_top" [profile="follow"]}

The item ‘debounced_resol_test’ is not updated.

OH 3.4.3 with “openhab_rules_tools” and “JS Scripting” installed.

Did I miss something? I tried several things, but found no solution. No hints within the logs as far as I can see.

Thanks,
Dirk

The “raw” Item should be linked to the Channel.

The “raw” Item should be a member of the Debounce Group.

The "debounced " Item should not be linked to anything. No channels, no follow profile, nothing. The only source of it’s state will come from the Debounce rule. If you need to cause the debounced value to be sent back out to another Channel, you can link the Item to a Channel but set the “command” option to “true”.

This version of the rule template was written in Nashorn JS so nothing else needs to be installed. If you are running this on OH 4, please use Debounce [4.0.0.0;4.1.0.0) instead. Note that if you remove the template and readd it after moving to OH 4 it will install this version. OH 4 requires the JS Scripting add-on to be installed but it also has a lot of error checking and if there is something wrong it will report it.