Design Pattern required for locking Switches

Hi All,
I am running OpenHab 2.1.0
I am looking for a design pattern that would fit the following need:
Outcome: Would like to have a “lock” to prevent people from changing certain switches. Ideally, when a switch it triggered (sendCommand), the command action would be canceled if the lock is not unlocked.

I have the lock portion all figured out with Habpanel and a Switch called “KP_Lock_State” which is ON or OFF.

I have 4 switches (ozw_PowerSwitch3 through ozw_PowerSwitch6) which are all defined in a group called “gSecurelyManage”.

Firstly, is a rule with a proxy set of switches the best approach? Reading online, it seems to be the only way to intercept a command to my switches.

Secondly, I am having troubles using a group for the trigger in a rule. This is my setup:

rule "Keypad: Intercept Command"
when
        Item gSecurelyManage received command
then
  logInfo("Keypad", "Here...")
//    val trigger = triggeringItem
    val trigger = gSecurelyManage.members.sortBy[lastUpdate].last as SwitchItem
    logInfo("Keypad", "A managed item was changed to: " + trigger.state.toString)
    logInfo("Keypad", "  Name: " + trigger.name.toString)
    logInfo("Keypad", "  Old state: " + trigger.previousState(true, "mysql").state.toString)
//    logInfo("Keypad", "  Old state: " + previousState)
...
end

The persist setting is:

        gSecurelyManage : strategy = everyChange

The item config is:

Group:Switch gSecurelyManage
Switch ozw_PowerSwitch3   "Entertainment 3 [%s]" <switch> (ozw_id3,gSecurelyManage) { ... }
Switch _ozw_PowerSwitch3   "Entertainment 3 [%s]" <switch> { ... }
...

I have tried using the “triggeringItem” implicit variable, but it always returns “ozw_PowerSwitch6” as the name.
I have also tried the trick “gSecurelyManage.members.sortBy[lastUpdate].last” but it also returns “ozw_PowerSwitch6” each time.

I also can’t get the previous state, also tried the implicit variable “previousState”.

If I got the logInfo() lines working, i can figure out the rest.

Thanks for your help,
Paul

There currently is a feature request to add a locking mechanism to a dashboard or specific widget: https://github.com/openhab/org.openhab.ui.habpanel/issues/81

You can use visibility for this.

Define both, a Swich and a Text widget. Control with visibility (master switch on or off) which you want to be displayed.

Does habpanel support visibility?

If I use visibility, it won’t prevent changes via the api.

Sorry, i missed habpanel. And yes visibility does not protect from other changes than the ui. You need proxy items and a master switch to protect from all sides.

A few comments.

Proxy items are the “way to go” IF you need to intercept commands and conditionally execute them.
see Automation/Orchestration et seq

I’ve run my code system thru multiple re-architecture steps over the past few months for scalability, performance, and reliability reasons. One of the biggest headaches is figuring out which change in an item triggered a group change. I had huge amounts of “book-keeping code” in Xtend to keep this straight—worked correctly after multiple iterations BUT performance (even with suppressed logging) did not scale very well.

I decided to explore the JSR223 Jython variant. I was loathe to re-do all my rules into jython, even though I know Python pretty well. The critical overhead question was “which item in a group” triggered a change to the group. The jython-openhab interface solves this pretty well using the @item_group_triggered decorator.

The hardest part is getting the jython setup correct. I had no success with a global jython installation, but a standalone jar installation works.

The key meta-construct in an appropriately-pathed jython .py script is this:

@item_group_triggered("gSwitch_u_01")
def example(event):
    events.postUpdate("nlastSwitchname",event.itemName)

You have to define “nlastSwitchname” as a virtual item in an items file, viz…
x

String nlastSwitchname

AFAIK you cannot define a structure like a SwitchItem in jython and pass that structure as-a-whole to classic rules/Xtend.

So my workaround is any Xtend/classic rules which needs the result of the event-trigger has to pass the itemName through an intermediate String item like “nlastSwitchname” and use that to parse-out other info in classic rules/Xtend from the group info. An important part of any classic/Xtend rule structure in this case is to pull the value of the global item (e.g. nlastSwitchname) into a local rule-instance execution variable (say “plastSwitchname” at rule-firing time and use that value to de-referencieng other needed elements like state, etc. This helps control multiple group-rule-instance-firing SNAFUs.

I have now got something working…
Two issues remain:
A) if you click quickly on the switches, it glitches out and updates the wrong entry. There is some logic to minimize the impact. This will be fixed when the triggeringItem defined here works: https://docs.openhab.org/configuration/rules-dsl.html
B) I can’t for the life of me get rules to trigger on any item in a group changing. As a result, I have to put all the switches in the WHEN.

ITEMS:

Group:Switch:OR(ON, OFF) gSecurelyManage2 "All Managed switches" <switch>
Group:Switch:OR(ON, OFF) _gSecurelyManage2 "_All Managed switches" <switch>
Switch KPswitch1 "switch1" (gSecurelyManage2)
Switch KPswitch2 "switch2" (gSecurelyManage2)
Switch KPswitch3 "switch3" (gSecurelyManage2)
Switch _KPswitch1 "_switch1" (_gSecurelyManage2)
Switch _KPswitch2 "_switch2" (_gSecurelyManage2)
Switch _KPswitch3 "_switch3" (_gSecurelyManage2)

PERSISTENCE:

gSecurelyManage2* : strategy = everyChange, restoreOnStartup
_gSecurelyManage2* : strategy = everyChange, restoreOnStartup

RULES:

import java.util.concurrent.locks.ReentrantLock
var ReentrantLock KPLock2 = new ReentrantLock

rule "Keypad: Intercept Command 3"
when
        Item _KPswitch1 received command or
        Item _KPswitch2 received command or
        Item _KPswitch3 received command
then
  try{
    KPLock2.lock()
    val trigger = _gSecurelyManage2.members.sortBy[lastUpdate("mysql")].last as SwitchItem
    var sister = gSecurelyManage2.members.findFirst[name.equals(trigger.name.toString.replace('_', ''))] as SwitchItem
    sister.postUpdate(trigger.state)
  }catch(Throwable t){
    logInfo("Keypad2", "Try-Catch error3: " + t.toString)
  }finally{
    KPLock2.unlock()
  }
end

rule "Keypad: Intercept Command 2"
when
        Item KPswitch1 received command or
        Item KPswitch2 received command or
        Item KPswitch3 received command
then
  try{
    logInfo("Keypad2", "Here...")
    KPLock2.lock()
    Thread::sleep(500)
    logInfo("Keypad2", "In lock2...")
    logInfo("Keypad2", " 1: " + KPswitch1.lastUpdate("mysql"))
    logInfo("Keypad2", " 2: " + KPswitch2.lastUpdate("mysql"))
    logInfo("Keypad2", " 3: " + KPswitch3.lastUpdate("mysql"))
    val trigger = gSecurelyManage2.members.sortBy[lastUpdate("mysql")].last as SwitchItem
    if(trigger == NULL){
      logInfo("Keypad2", "Error with persistence, do nothing...")
    }else{
      var sister = _gSecurelyManage2.members.findFirst[name.equals("_" + trigger.name.toString)] as SwitchItem
      logInfo("Keypad2", "A managed item was changed to: " + trigger.state.toString)
      logInfo("Keypad2", "  Name: " + trigger.name.toString)
      logInfo("Keypad2", "  Old state: " + trigger.previousState(true, "mysql").state.toString)
      logInfo("Keypad2", "  Sister state: " + sister.state.toString)
      if(trigger.previousState(true, "mysql").state.toString == sister.state.toString){
        if(KP_Lock_State.state==ON){
          logInfo("Keypad", "Keypad2 is locked, return to old value...")
          trigger.postUpdate(sister.state)
        }else{
          logInfo("Keypad2", "Keypad is unlocked, send the command '" + trigger.state.toString + "' to '" + "_" + trigger.name.toString + "'")
          sister.sendCommand(trigger.state.toString)
        }
      }else{
        // Do nothing because we are comparing apples and oranges...
        logInfo("Keypad2", "Previous and sister states are not the same, clicking too fast, do nothing")
      }
    }
  }catch(Throwable t){
    logInfo("Keypad2", "Try-Catch error2: " + t.toString)
  }finally{
    KPLock2.unlock()
  }
end