Not getting error statements when loading/parsing rules file

In every rules language except Rules DSL (even Blockly) you can access Item metadata and have rules that call other rules.

In managed rules and I think even some of the other files based rules languages there is a generic event trigger you can use to catch all events matching a certain pattern (see Thing Status Reporting [4.0.0.0;4.9.9.9] for and example to catch all Thing status changes).

Your tool box is small with Rules DSL. That’s the real problem here and why I say you’ve probably out grown it.

There are certain operations where Rules DSL fails to determine the type correctly. But you’ll encounter fewer problems if you cast when you hit those than to force the type of the variables.

Meh, that doesn’t bother me. It shouldn’t matter either way. But forcing type can add minutes to how long it takes to load your rules, even on a fast machine, in some circumstances.

And it’s not even reliable to add the type to a variable. Just because it’s var String myString doesn’t mean you can’t assign a BigDecimal to it later and there won’t be a single complaint.

This could probably be captured using Item tags. You could then use the Item registry to pull all Items with a given tag. You could also use Groups but that’s a lot of Groups I think. You can get at the ItemRegistry in Rules DSL.

This is where I would use Item metadata. You can create a namespace, let’s call it “lights”. The value would be set to one of the keys you are using in your hashmaps and you can add a config for the light time and single press/double press. Or maybe put the light Item with the sensor Items. It’s not clear to me yet how the numbers are being use or why the *2.

Much better to use

val ProcessButtonPress = [ String lightName ...

All the types are captured automatically and it’s so much easier to read.

If you use jRuby, they have a lot of timer management stuff built in . If JS Scripting I’ve a library with a TimerMgr which does timer book keeping in cases where you have one timer per Item. One function call handles creation and rescheduleing and all the book keeping and cleanup.

Why not use one rule and the implicit variables to map the event to a “Sensor”?

As an alternative, there is a progfile (it might be on the SmarthomeJ marketplace) that allows you to link an event Channel to an Item. That saves a lot of mapping and it might be an option to apply here.

I’m just typing this in so what I have here is likely going to be error ridden. But what about something like this, as a managed rule since that’s what I use:

uid: lights
label: Lights timers 
description: Turn on and off lights according to a timer
triggers:
  - id: "3"
    label: A Thing Changes Status
    description: Triggers when any Thing changes status
    configuration:
      types: ChannelTriggeredEvent
      payload: ""
      topic: openhab/channels/*/triggered
      source: "shelly"
    type: core.GenericEventTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: |
        var {TimerMgr} = require('openhab_rules_tools');


        var parsed = JSON.parse(event.payload); // the event comes as JSON, log it out to make sure you get the right values and put them into "triggerEvent" below

        var triggerEvent = {};

        triggerEvent['thingID'] = event.topic.split('/')[2];

        triggerEvent['channel'] = parsed[0].channel

        triggerEvent['event'] = parsed[0].event

        var timers = cache.private.get('timers', () => TimerMgr()); // pull the TimerMgr from the cache or create one if there isn't one

        // use a tag here so we can pull the switch by tag instead of name
        var swOutput = items.getItemsByTag(triggerEvent['channel'])[0];

        // get all the Items with the this channel in it's lights metadata, could use tags but we need metadata anyway
        // Could have yet another mapping instead of using the Channel name but this makes the code simpler
        var lightsItems = items.getAllItems().filter( item =>  item.getMetadata('lights') !== undefined);

        var lightsToControl = lightsItems.filter( item => item.getMetadata('lights).value == triggerEvent['channel']);

        lightsToControl.forEach( light => {
            console.debug('Controlling light ' + light + '  based on event ' + triggeringEvent['event']);
            const md = light.getMetadata('lights').configuration['time'];

            // break early if this light doesn't respond to the event
            if(md.configuration['SINGLE'] === undefined && md.configuration['DOUBLE'] === undefined && md.configuration['TRIPLE') === undefined) {
              break;
            } 
            else if(triggeringEvent.event == 'LONG_PRESS' 
                       && (md.configuration['SINGLE'] === undefined || md.configuration['SINGLE'] != 'true')) {
              break;
            }
            else if((triggeringEvent.event == 'SHORT_LONG_PRESS' || triggeringEvent.event == 'LONG_SHORT_PRESSED')
                       && (md.configuration['DOUBLE'] === undefined || md.configuration['DOUBLE'] != 'true')) {
              break;
            }
           else if(triggeringEvent.event == 'TRIPLE_PRESS' 
                        && (md.configuration['TRIPLE'] === undefined || md.configuration['TRIPLE'] != 'true')) {
              break;
           }
      

            // always cancel the timer if the light is on, they will be recreated if the light turns on
            // single press
            if(swOutput.state == 'ON') {
                timers.cancel('light');
            }

            switch(triggeringEvent['event']) {
              case 'SHORT_PRESSED':
                if(swOutput.state == 'ON') {
                  light.sendCommand('ON');
                  timers.check(light.name, md.configuration['time'], () => {
                    light.sendCommand('OFF');
                  }
               }
               else {
                 light.sendCommand('OFF');
                 timers.cancel(light.name);
               }
               break;
              case  'DOUBLE_PRESSED': 
              case 'TRIPLE_PRESSED':
                  light.sendCommand('ON');
                  timers.check(light.name, light.getMetadata('lights').configuration['time'], ()=> light.sendCommand('OFF'));
                break;
              case 'LONG_PRESSED':
              case 'SHORT_LONG_PRESSED':
              case 'LONG_SHORT_PRESSED':
                light.sendCommand('ON');
                break;
                
        }
    type: script.ScriptAction

I believe the generic trigger above will catch and trigger the rule on all ChannelTrigger events that include “shelly” somewhere in the event. The event from this trigger comes over as JSON which can be parsed and the data extracted. You can use the name of the Channel as the mapping between Items and those Item that respond to the switch.

I use my TimerMgr library to handle all the timer stuff. All I have to do is call check to create the timer and cancel to cancel it. We use the cache to save the timers from one run to the next.

Instead of using the Item name to get the swOutput Item, we use a tag that matches the channel that triggered the rule.

Next we get all the Item with “light” metadata defined. We further reduce the list to just those whose “light” metadata values match the Channel ID. That leaves us with the list of Items that respond to only this Channel so we loop through them. We could continue using filters but I’ll switch so you can see another approach.

Now we loop through the Items and skip over any Item that doesn’t have metadata indicating that light responds to this specific event from the Channel.

I notice that in all but SINGLE_PRESS you cancel the timers when swOutput is ON, and we recreate the timers in SINGLE_PRESS anyway in that case so we will just always cancel the timers. That saves a lot of repeated code.

Now we have the switch statement. But because we’ve already narrowed down to the case where we know we need to command the Item, and we’ve already canceled the timers that need to be canceled, all we need to do command the Item and set the timer based on the event.

The Item metadata would look something like this:

.items files
lights="shelly:shellyplus2pm-relay:80646fdb89e0:relay2#button"["DOUBLE"='true', "TRIPLE"='true']

ui
value: "shelly:shellyplus2pm-relay:80646fdb89e0:relay2#button"
config:
  DOUBLE: 'true'
  TRIPLE: 'true'

That’s the metadata you’d use for a given light that responds to DOUBLE or TRIPLE clicks on that shell button.

I tried to show a few different things above that you can leverage that do not exist in Rules DSL.

If I were to use Scenes, you can create an ON and OFF scene for each “room” and button press event (which could be a number of Scenes). Then you’d need a mapping between the button Channel ID and the Scene number (maybe still hard coded as a map perhaps). But once you have that mapping the code just becomes:

// define mappings and timings

var sceneId = sceneName + '-' + triggeringEvent.event;
switch(triggeringEvent['event']) {
  case 'SHORT_PRESSED':
    if(swOutput.state == 'ON') {
      rules.runRule(sceneId+'-ON');
      timers.check(sceneId, timerTime, () => {
        rules.runRule(sceneId+'-OFF');
      }
      else {
        rules.runRule(sceneId+'-OFF');
        timers.cancel(sceneId);
      }
      break;
...                
}

Which lights to turn ON and OFF get captured in the Scenes and you just need to figure out which scene to call and how long to set the timer for.

These are just ideas. There is almost certainly a bug or two in the above. The intent is to show how you can use some of the new tools and features you don’t have access to in Rules DSL to solve these sorts of problems.