The status of a scene and "inactive" when a "member" changes

Hello, up until now I’ve been creating scenes using groups, but I think it’s a great idea to simply add items to a scene and set target states.

However, I’d like to be able to retrieve a scene’s status without too much effort.

For example: if I lower all the blinds to 80% in the evening and then move one blind, I press the scene button again and that one blind returns to its previous state.

Up until now, I’ve had oh-buttons on a page:

                - component: oh-button
                  config:
                    action: command
                    actionCommand: "=(items.szeneGruppeRolloSchlafen.state > '79' &&
                      items.szeneGruppeRolloSchlafen.state < '81') ? '0' : '80'"
                    actionItem: szeneGruppeRolloSchlafen
                    active: "=(items.szeneGruppeRolloSchlafen.state > '79' &&
                      items.szeneGruppeRolloSchlafen.state < '81') ? true :
                      false"
                    outline: true
                    style:
                      width: 92px
                    text: Schlafen

That allowed me to read the status, but if I now need two groups (100% and 80%), I can evaluate both groups under ‘active’, but I can’t send two ‘actionCommands’ to both groups at the same time. At least not in a way that’s easy to maintain.

To reliably generate the status of a scene, complex approaches are always required, which no longer justify the simplicity and clarity of the scene.

It is easier, then, to link all items into groups by scene/state.

Is there another way to achieve this more simply?

The best option I can think of:

Create sub-groups for all types and states of a scene

  • bedtime_rollershutter_100%
  • bedtime_rollershutter_80%
  • bedtime_lights_on
  • bedtime_lights_10%
  • bedtime_revert_rollershutter_100%
  • bedtime_revert_rollershutter_80%
  • bedtime_revert_lights_on
  • bedtime_revert_lights_10%

Then create two rules (normal + revert) that trigger all groups accordingly.

and then trigger the rule using an OH button and set ‘active:’ for each group.

But here too, I have to make a lot of changes in many places to maintain it.

I hope it’s clear what I’m trying to achieve. I’m actually an advanced user, but I’m stuck here because I don’t really like any of the approaches and they all seem too much hassle.

It would be great if the groups had a status of active:true when all items are in their target state.

Or would it be easier to create OH buttons for each scene and then maintain a number item as an enum in parallel? At the same time, create a group containing all the items used in the scenes. The enum is then set to “undefined” as soon as an item in that group changes.

There had been long discussions whether a scene in openHAB should have a state or not.
The consens was a stateless approach.
Think of this:
An Item is member of several scenes.
Scene A is switching the Items state to on, scene B sets it to off but does not touch other members of scene A.
What would be the state of scene A if scene B is activated ?
As you did not deactivate scene A, you would expect it to be ON. But as the one Item was switched OFF by scene B, scene A is not ON.
I hope, this explains why scenes are stateless.

Thank you very much for your response.

Sorry, I don’t get that. Of course, scene A is OFF in this example.

I also don’t understand why we would skip it just for the sake of simplicity as I read elsewhere. We don’t have to use the status if we don’t need it.

I’m thinking of two scenes:

- Close all blinds

- Close all blinds in the bedroom

If I select “Close all blinds,” then both scenes are active.

But that’s exactly what MaunUI → Settings → Scenes does. It’s just a making of Items to a desired state.

The Action in your widget would be too run a rule with the screen ID desired.

This seems to perfectly match this part of your post.

What does this even mean? If one Item is different from the scene’s state, does that mean the scene is ON or OFF? If the scene was activated and later an Item changed, is it still ON? If all the Items match the scene but the scene was never activated, is the scene ON or OFF?

Perhaps because the status of a scene isn’t a simple thing. Yes or no are all valid responses to all of the questions I asked above. There’s are at heart six variations possible to determine where a scene is active or not.

I think the decision to not tackle that in OH core and force only one possibility was the correct decision.

Probably. What you described seems needlessly complex. But it’s kind of an XY Problem as you don’t actually provide the specifics about what a screen is ON or OFF.

If it were me, a scene would be considered active if all the Items in a scene matched the designated states. I’m that case I’d have a role that watches the Items and compares there current states to the states if the scene (which I would pull from the API to get the mappings). Then it’s a simple loop though the Items and setting a Scene Item to ON or OFF. Then the widgets just have to check one Item and run one rule.

Hi, this is exactly what I want to achieve. A Item representing the active true/false would be great.

A scene should be active when all items are at their designated states, no matter what caused them to do so.

Could you please give me a hint or example how to implement this in a efficient wasy?

thanks so much in advance

Creating the Item should be straight forward. You just create one Switch Item per scene. Be sure to use a name that matches the Label of the scene with the spaces replaced by “_”. That will make the rule easier as we won’t need to keep a separate mapping anywhere.

Next you have a choice. How timely do you want this Item to be updated? Immediately? Is a 30 second delay OK? A five minute delay? That will determine how to proceed.

If it must be immediate, then you’ll need to create at least one Group Item. This Item contains as members all Items that are involved in a scene. You don’t necessarily need a separate Group per scene, though you could certainly do it that way. We are just going to use this Group to trigger the rule that determines which Scenes are ON or OFF.

If a delay is OK, just use a cron trigger and you can forego the Group entirely.

I think the most expedient way to get the list of Items and the desired state mapping is through the REST API. So first we will get a list of all the Rules with the “Scene” tag and then use the REST API to pull those rules as JSON. Then process each one to test which Scenes are ON or OFF.

Here is some code which should do the above. As you can see, it’s not too long nor complex and it handles this for all scenes that are defined in MainUI in this one rule.

// Get the JSON for all the Scenes
const API_TOKEN = 'oh.status.Generate your own'
const headers = new Map();
headers.set('accept', 'application/json');
headers.set('X-OPENHAB-TOKEN', API_TOKEN);
const scenes = JSON.parse(actions.HTTP.sendHttpGetRequest('https://openhab.koshak.us/rest/rules?tags=Scene&staticDataOnly=false', headers, 3000));

// Loop through all the scenes to determine if they are active or not
scenes.forEach(scene => {
  const sceneItem = scene.name.replace(' ', '_'); // assumes the label and Item name are the same with spaces replaced with "_"
  // Reduce the list of actions to a single boolean value. If any Item's current state doesn't match
  // the command in the scene returns false. If all Items's states match the scene command returns true
  const sceneActive = scene.actions.reduce( (accumulator, curr) => {
    const itemActive = items[curr.configuration.itemName].state == curr.configuration.command;
    return (!accumulator) ? accumulator : itemActive; // if accumulator is false always false else return if Item state matches scene
  });
  // Update the scene Item
  const newSceneItemState = (sceneActive)? "ON" : "OFF";
  items[sceneItem].postUpdate(newSceneItemState);
});

If you trigger this rule by changes to members of a Group, it will recalculate the status of the scenes immediately. If you trigger it with a cron trigger, it will recalculate the status Items periodically on a set schedule. But the code remains the same.

Thank you very much @rlkoshak !

I had to slightly modify the code, but it is working great. I’ll post it here to help others with OH 5.1.4

I did a Test with my garage Lights:

my Test Scene:

my ON/OFF representing Item

grafik

my ECMAScript (application/javascript)

grafik

// Get the JSON for all the Scenes
const API_TOKEN = 'oh.status.Generate your own';
const headers = new java.util.HashMap();
headers.put('accept', 'application/json');
headers.put('X-OPENHAB-TOKEN', API_TOKEN);

const HTTP = Java.type("org.openhab.core.model.script.actions.HTTP");
const scenes = JSON.parse(
  HTTP.sendHttpGetRequest(
    'http://192.168.178.42:8080/rest/rules?tags=Scene&staticDataOnly=false',
    headers,
    3000
  )
);

// Loop through all the scenes to determine if they are active or not
scenes.forEach(scene => {
  const sceneItemName = scene.name.replaceAll(' ', '_');
  // assumes the label and Item name are the same with spaces replaced with "_"
  // Reduce the list of actions to a single boolean value. If any Item's current state doesn't match
  // the command in the scene returns false. If all Items's states match the scene command returns true
  
  const sceneItem = items[sceneItemName];
  if (!sceneItem) {
    console.log("Kein Item gefunden für Szene: " + sceneItemName);
    return;
  }

  const sceneActive = scene.actions.reduce((accumulator, curr) => {
    if (!accumulator) return false;
    const itemActive  = items[curr.configuration.itemName];
    if (!itemActive ) return false;
    return itemActive .state == curr.configuration.command;
  }, true);
  // Update the scene Item
  sceneItem.postUpdate(sceneActive ? "ON" : "OFF");
  console.log("Szene " + scene.name + " aktiv: " + sceneActive);
});
    const targetItem = items[curr.configuration.itemName];
    if (!targetItem) return false;
    return targetItem.state == curr.configuration.command;
  }, true); // Startwert true wichtig!

  sceneItem.postUpdate(sceneActive ? "ON" : "OFF");
  //logger.info("Szene " + scene.name + " aktiv: " + sceneActive);
});

and the rule

configuration: {}
triggers:
  - id: "1"
    configuration:
      groupName: alleLichterGarage
    type: core.GroupStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      considerConditions: true
      ruleUIDs:
        - 50a916fd9b
    type: core.RunRuleAction

Thanks, now I can use my use case and easily configure scenes at the same time. All I have to do is create the “State” item for the scene and link all the scene items to a group.

Unfortunately, I noticed something else and would like to get your opinion on it.

When all my blinds move at the same time, the script is triggered 24 times for 30–60 seconds every 2 seconds because each blind changes its status. So the script is triggered about 500 times.

Should I just let it run like this, or is there a better way to handle this? Am I missing something?

I’d actually like to implement this on an event-based basis.

Yes, I left the error checking out as an exercise for the student. :stuck_out_tongue: Though I do see where I left out the initial value on the reduce. That’s a good catch.

One warning though. In some cases you might have a command defined in the scene that is not the same as the state of the Item. For example you can send INCREASE as a command to a Dimmer but a Dimmer will never have the state INCREASE.

There are some things that can be done around that, but it’s always going to be a limitation so watch out for that.

Also, the rule could be even smarter and create the status Item for you. :wink:

That’s what I would expect. It will process those in sequence. The rule should take far less than a second to run so unless you are seeing problems you don’t necessarily need to do anything about it.

If you did want to address that though you’d have to add a debounce to the rule somehow. The traditional approach is a timer that keeps getting rescheduled every time the rule is triggered until the triggers stop coming and the timer goes off and calculates the new states. But you could also do it without the rescheduling and let the rule run periodically inbetween the first and last trigger.

You could trigger the rule for only when the members receive a command instead of changes. But that will really only work if these devices are never toggled or changed outside of OH as those will only come in as updates instead of commands. You’d probably want to set a short timer here too (100 msec) because the command comes before the Items change state. So when the rule runs the Items will not have changed state yet.

It might be enough to do just a simple timestamp lock. Set a timestamp and ignore any subsequent triggers for the next second or half second. That could filter out a bunch of those runs of the rule more simply but it runs the risk that you miss that last event and the Items were not all in their final state when the rule last run so the status Item won’t match the state of the scene because you missed that last change.

You could use a count. Look at your smallest scene and set the count to 1 minus the number of Items in that scene. Count the number of times the rule is triggered and only process that 1 minus numbered trigger, ignoring the rest.

Note, all of these approaches mainly just keep the rule’s action from running. The rule will still trigger all those times, it just only does the work of calculating the scene status Item’s state less often.

The timer approach is the only one that’s foolproof but it adds latency from when the scene was activated and the status Item is updated.

Some of these approaches can be implemented in the rule’s condition instead of in the action. But the timer approach probably belongs in the script action. Something like this:

if(cache.private.exists('debounce') return; // reschedule and return if you want the rule to wait until everything settles before running the calculation

// This sets the timer to half a second. Adjust as desired
cache.private.put('debounce', actions.ScriptExecution.createTimer(time.toZDT(500), () => {
    cache.private.remove('debounce');
    // the rest of the code as it's written now
});

Oh, right, thanks for the tip about the commands (INCREASE). I’ll have to think about that some more, but I don’t think it’ll be a problem for me.

Debouncing was my first thought too. thanks a lot for the tip about the “context variable.”

Maybe I can just let it run as is. I have pretty high-performance server hardware and OpenHAB in a Proxmox VM under Docker.

Unfortunately, I won’t be able to continue for a few days, but I’ll be in touch. Thanks!

For now, I’ve set up a cron job to update the states every 30 seconds, and I’m happy with that, so I’ll leave it as is.

My main use case is that if, for example, I move a roller shade out of the active scene, I can use the scene to move it back in and then see that the scene is active again.

Thanks again.