Distinguish source of command (via binding or via openHAB)

Hi guys!

Is it possible - within rules - to distinguish the source of a command (via binding/channel or via openHAB)?
I’d like to know, if a command was issued let’s say for a switch item on KNX bus and openHAB is “notified” via the knx binding, or if the item got a command via openHAB (coming from a rule or a button click on UI).

I already tested some ideas (state changes, autoupdate, etc.) but haven’t found a solution yet…

Thanks in advance,
MW

A command is a command, source information is not included.

Most bindings do not produce commands in response to messages from outside; they produce instead Item state updates.
There are exceptions - KNX is one - where you can configure inbound messages to produce Item commands instead, but this is not usual.

Items are free at point of use. You can always link UI to one Item, rules to another, bindings to yet another; then you’ll know “who dun it”.

1 Like

Hi!
This means, I have to use different items for “internal” control as well as “external” control and link them together via rules. :neutral_face:

So I have to think about solutions how I can handle this without doubling code on a general base.

Thanks for the quick response…

That sounds like a XY Problem, if you’d tell us what you are trying to achive with your “internal” and “external” control we could posdibly come up with another way to it.
Achieving more control however comes with a price-tag, which migth be “doubled” code.

Hi!
Concrete use-case is as following:
I have a KNX actor I’d like to switch on/off via normal KNX bus (external, KNX wall switches).
Additionally I have rules to control this actor from openHAB side.

What I want to achieve is, that I’m able to know in the openHAB rule, if there has been a manual interaction on the KNX side (so someone in the house switched this actor on or off via a press on a wallswitch). If so, then the openHAB rule should NOT command the switch for a given time automatically.

While writing this lines I think I had the idea to solve my problem for the KNX items:
On the KNX Bus I have 2 different group addresses needed for communication. One for the command and the other one for the state feedback from the actor. Using an additional item on openHAB side reading the command (and not only the state feedback as at the moment) it should be possible to distinguish here as it was my original intention.

Maybe you also have a solution not only limited to the special KNX topology?!?

I do this, and it will get complicated. Be prepared to invest effort, there is no shortcut.

You might rephrase as “I want to detect when an external device changes state, but openHAB did not initiate the change.”
Actual methods to do that can be quite technology dependent.

A generic way -
Get rid of autoupdate on target Items (we want to see real status changes)
By rule, when OH commands by rule or UI set a “flag”. I do that with global variables, depending on your choice of rule language you might need to use an Item.
By rule, when a device state changes, examine the flag. If true, it was an OH initiated action. If false, a manual intervention. (now reset the flag)

You might end up with two or three Items representing each light, e.g. the Light itself, it’s manual/auto mode, etc. You can use expire feature to ease the later removal of Manual mode.
To expand beyond one light, you’ll find Groups and “associated Item names” schemes useful.

The really fiddly part comes with edge cases, as usual.
“What if a light is already ON at bootup?”
“What if a command evokes no change?”

Interestingly, none of that cares about source of commands. All commands are internal to openHAB.

1 Like

You should be able to write one rule that handles multiple sets of Items.

I actually have this sort of thing implemented in my system. In my case I have a few lamps that I have turn on/off based on the cloudiness. However, if someone manually changes these lights the cloudiness automation stops until the next day.

To solve this I use timing. The lights controller rule runs when the time of day changes or when the cloudiness changes. So I have another rule that triggers when the lights change state. That second rule looks to see if the time of day Item or cloudiness Item changed state recently (less than a second ago). If so, I ignore the light’s changing event. If not I assume it was manually triggered and set an override metadata value on the Item. The first rule looks at the metadata and ignores any that has the override flag set. Finally I have a rule that runs at the end of the time of day where the lights are controlled to reset the metadata flags back to off.

Honestly this approach only works OK. When I do it again, I’ll use the Proxy Item approach already discussed. But for the curious here are the three rules, written in JSScripting:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: vIsCloudy
    type: core.ItemStateChangeTrigger
  - id: "2"
    configuration:
      itemName: TimeOfDay
      state: DAY
    type: core.ItemStateChangeTrigger
  - id: "5"
    configuration:
      itemName: Presence
    type: core.ItemStateChangeTrigger
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: TimeOfDay
      state: DAY
      operator: =
    type: core.ItemStateCondition
  - inputs: {}
    id: "6"
    label: vIsCloudy isn't undefined
    configuration:
      type: application/javascript
      script: |
        var runtime = require("@runtime");

        items.getItem("vIsCloudy").rawState.class !== runtime.UnDefType
    type: script.ScriptCondition
actions:
  - inputs: {}
    id: "4"
    configuration:
      type: application/javascript
      script: >-
        var logger = log("Weather Lights");


        //var OPENHAB_CONF = java.lang.System.getenv("OPENHAB_CONF");

        //load(OPENHAB_CONF+'/automation/lib/javascript/personal/metadata.js');


        if(items.getItem("Presence").state == "OFF") {
          items.getItem("TOD_Lights_ON_WEATHER").sendCommand("OFF");
        }


        else {
          // TODO find alternative to sleep
        //  if(event !== undefined && event.itemName == "TimeOfDay") {

        //    java.lang.Thread.sleep(500);

        //  }
          
          logger.info("Cloudiness changed to " + items.getItem("vIsCloudy").state + ", adjusting the lights");
          items.getItem("TOD_Lights_ON_WEATHER")
            .members
            .filter(light => light.state != items.getItem("vIsCloudy").state)
            .filter(light => light.getMetadataValue("LightsOverride") == "false" || light.getMetadataValue("LightsOverride") === null)
        //    .filter(light => getMetadataValue(light.name, "LightsOverride") == "false" || getMetadataValue(light.name, "LightsOverride") === null)
            .forEach(light => light.sendCommand(items.getItem("vIsCloudy").state));
        }
    type: script.ScriptAction
configuration: {}
triggers:
  - id: "1"
    configuration:
      groupName: TOD_Lights_ON_WEATHER
    type: core.GroupStateChangeTrigger
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: TimeOfDay
      state: DAY
      operator: =
    type: core.ItemStateCondition
  - inputs: {}
    id: "4"
    label: Manual trigger?
    description: TimeOfDay changed more than 10 seconds ago and vIsCloudy more than
      5 seconds ago
    configuration:
      type: application/javascript
      script: >-
        var runtime = require('@runtime');

        var logger = log("Weather Lights Override");


        var PersistenceExtensions = Java.type("org.openhab.core.persistence.extensions.PersistenceExtensions");

        var ZonedDateTime = Java.type("java.time.ZonedDateTime");


        // Alternative to persistence

        //java.lang.Thread.sleep(500);


        logger.debug("Checking to see if enough time has past since a transition");


        // Todo, make it work with helper and OHItem JS Obejct

        var tod = PersistenceExtensions.lastUpdate(runtime.ir.getItem("TimeOfDay"), "mapdb")
                                       .isBefore(ZonedDateTime.now().minusSeconds(10));
        var cloudy = PersistenceExtensions.lastUpdate(runtime.ir.getItem("vIsCloudy"), "mapdb")
                                          .isBefore(ZonedDateTime.now().minusSeconds(5));

        logger.info("Time of day: " + tod + " cloudy: " + cloudy);


        tod && cloudy
    type: script.ScriptCondition
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >-
        var logger =
        Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab.model.script.Weather
        Lights Override");


        //var OPENHAB_CONF = java.lang.System.getenv("OPENHAB_CONF");

        //load(OPENHAB_CONF+'/automation/lib/javascript/personal/metadata.js');


        logger.info("Manual light trigger detected, overriding the light for" + event.itemName);


        items.getItem(event.itemName).upsertMetadataValue("LightsOverride", "true");

        //setMetadataValue(event.itemName, "LightsOverride", "true");
    type: script.ScriptAction
configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: TimeOfDay
      previousState: DAY
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: "var logger = log(\"Reset Lights Override\");


        logger.info(\"Resetting the LightsOverride metadata value to
        false\");


        logger.debug(\"Looping through the lights\");

        for(let light of
        items.getItem(\"TOD_Lights_ON_WEATHER\").members) {

        \  logger.debug(\"Current override is {}\",
        light.getMetadataValue(\"LightsOverride\"));

        \  logger.debug(\"Success = {}\",
        light.upsertMetadataValue(\"LightsOverride\", \"false\"));

        }

        \    "
    type: script.ScriptAction