Design Pattern: Manual Trigger Detection

I’ve been having a hard time getting this to work. My main issue is the rule for

Device Change Indicates the device has changed state outside of OH (e.g. physically flipped the switch)

This fires for both the setting of the device via proxy AND when the device is turned on manually. Therefor you can’t tell which way the device is turned on.

I must be missing something.

Scott

It largely depends on what you are trying to accomplish.

In general, looking at the commands will tell you where the interaction came from. Lacking a command means the interaction came from outside of OH. So you need to pair the commands with updates. If the last command received is within 500 msec (for example) of the update, you know that the update is in response to the command.

So if you can’t rely on the commands alone, you’ll need to keep track of when the commands happen. Then on an update see how long ago the last command occured and if it’s soon enough we know that the update was in response to a known command.

Probably the simplest way to do that would be to save now in the cache or a global variable when a command is received. If an update triggers the rule, look to see how long go that timestamp is and if it’s too soon, ignore the update.

How do you determine if something is “Lacking a command”?

Perhaps something similar to the Deadman’s switch were a flag is set before the sendCommand and reset in the value changed rule. That way timing would not be needed.

If the flag is not set then you know it was done outside of OH.

I’ll try that and see how it works.

This is the solution I came up with. It seems to work. The only issue is if you have two items turned off and on in rapid succession it fails. (As to reset device to default state) I just have the second command wait a second to fix that. The rule “SwitchManuallyPressed” is run whenever a switch is manually pressed.

I have about 30 of these so I also dynamically create the rules. The device is the base name (no _Device)


  function dateTime() {
    const currentDate = new Date();

    const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Months are zero-indexed
    const day = String(currentDate.getDate()).padStart(2, '0');
    const hours = String(currentDate.getHours()).padStart(2, '0');
    const minutes = String(currentDate.getMinutes()).padStart(2, '0');

    const formattedDateTime = `${month}/${day} ${hours}:${minutes}`;
    return formattedDateTime;
  }

  const config = [{ id: 'DiningRoomChandelier',name: 'Dining Room Chandelier', description: 'Dining Room Chandelier Changed', itemName: 'dDiningRoomChandelier'},
                  { id: 'FrontHallway',name: 'Front Hallway', description: 'Front Hallway Changed', itemName: 'dFrontHallwayLight'}]

  function createDynamicRule(config) {
    return rules.JSRule({
    name: config.name + ' Changed',
    description: config.description,
    triggers: [triggers.ItemCommandTrigger(config.itemName+'_Proxy'),
               triggers.ItemCommandTrigger(config.itemName+'_Rule'),
               triggers.ItemStateChangeTrigger(config.itemName)],
    execute: (event) => {
      if (event.eventType == 'command') {
        console.log(event.itemName + ' to ' + event.receivedCommand);
           cache.shared.put(config.itemName, event.itemName);
           items.getItem(config.itemName).sendCommand(event.receivedCommand);
      } else {
        console.log(event.itemName + ' Changed from ' + event.oldState + ' to ' + event.newState + ' via ' + 
          (cache.shared.exists(event.itemName) ? cache.shared.get(event.itemName) : 'Wall Switch'));
        if (!cache.shared.exists(event.itemName)) {
          cache.shared.put('WallSwitch', dateTime() + " " + event.itemName + " " + event.newState);
          items.getItem('sManualSwitchPressed').sendCommand('ON')
        }  
        if (items.getItem(config.itemName+'_Proxy').state != event.newState)
           items.getItem(config.itemName+'_Proxy').postUpdate(event.newState);
        cache.shared.remove(event.itemName)       
      }
    },
    tags: ['JSScripting Automation Rule','Switch Detect'],
    id: config.id+'DetectSwitch'
  });
}

config.forEach(createDynamicRule);

function sendNotifications(TextToSend) {
  var hours = new Date().getHours();
  if (hours >= 6 && hours < 22) {
    console.log('NotificationActionSwitchPressed', 'A switch has been manually pressed outside openhab: ' + TextToSend);
    actions.NotificationAction.sendNotification('myopenhab@xyz.com', 'A switch has been manually pressed: ' + TextToSend);
  }
}

rules.JSRule({
  name: 'Switch Manually Pressed',
  description: 'NotificationAction on Switch Manually Pressed',
  triggers: [triggers.ItemCommandTrigger('sManualSwitchPressed', 'ON')],
  execute: (event) => {       
    sendNotifications(cache.shared.get('WallSwitch'));
    cache.shared.remove('WallSwitch');  
    items.getItem('sManualSwitchPressed').sendCommand('OFF');      
  },
  tags: ['Switch Detect','JSScripting Rule'],
  id: 'SwitchManuallyPressed'
});

That’s probably excessive. You could do this all with just one rule. It just takes checking the name of the triggering Item and generating the names of the other Items you need from that. See Design Pattern: Associated Items.

I don’t think there is anything necessarily wrong with generating a separate rule for each but it’s not necessary.

I agree. I went from 60 (one for Command and one for change) and combined each trigger and reduced it to 30. Will look into reducing that further. Thanks for the input…

I did change this to use group membership as the flag. The exact same code except that it triggers by belonging to that group.

It was kind of neat to see 30 rules generate in a second but this would far less messy…