[Solved] Working with groupmembers in jsscripting (ECMA2021)

Hi guys,

I use openHAB 3.3.0.M2

I’m trying to figure out how to write a rule which does some things with the triggeringItem of a group.

My problem is not the logic itself but the syntax…

lets say we have 2 items which belong to the same equipment and names are prefixed in the same way:

  • SqueezeBedroomPlaypause
  • SqueezeBedroomPower

SqueezeBedroomPlaypause is member of gSqueezePlaypause which acts as the rule trigger

What I want to achieve is

when gSqueezePlaypause gets updated

  • get the triggering Itemname(s)
  • use the prefix e.g. (SqueezeBedroom)
  • change suffix from (Playpause) to (Power)
  • send OFF command to e.g. (SqueezeBedroomPower)

what I got is nearly nothing so far…

const {log, rules, triggers} = require("openhab");

const playeritems = items.getItem("gSqueezePlaypause");

rules.JSRule({
  name: "ECMA2021Script - Squeezebox Watchdog",
  description: "switch squeezebox off on pause",
  triggers: [
    triggers.GroupStateUpdateTrigger('gSqueezePlaypause')
  ],
  execute: data => {
    array.forEach(element => {
      if(playeritems.members.state === "OFF"){
          const str = playeritems.members;
          const res = str.replace("Playpause", "Power");
          res.sendCommand("OFF");
      }
    });
  },
  tags: []
});

any help appreciated :wink: and any hints where I can find more information on groups and members in jsscripting and some examples would be really awesome :yum::+1:

Given how you’ve defined the rule:

data.itemName

That’s the Item that triggers the rule. I think a GroupStateUpdateTrigger corresponds with Rules DSL "Member oftrigger. Sodata.itemName` will be the name of the Item that was updated that caused the rule to trigger.

This will be pretty much the same as Rules DSL. Assuming your Items that trigger this rule always end with “Playpause”

let itemRoot = data.itemName.replace('Playpause', '');

If not, it might be worth while renaming the Items to insert a marker and use split(). For example if the Item names follow SqueezeBedroom_Playpause

let itemRoot = data.itemName.split('_')[0];

Assuming that’s all you need the root of the Item name for you don’t need the above and just need a replace.

let itemName = data.itemName.replace('Playpause', 'Power');

Using openhab-js you’ll want to pull the Item. Combining everything

items.getIten(data.itemName.replace('Playpause', 'Power').sendCommand('OFF');

So, based on what you describe you want to have happen it’s really just a one liner:

const {log, rules, triggers} = require("openhab");

rules.JSRule({
  name: "ECMA2021Script - Squeezebox Watchdog",
  description: "switch squeezebox off on pause",
  triggers: [
    triggers.GroupStateUpdateTrigger('gSqueezePlaypause')
  ],
  execute: data => {
    Items.getItem(data.itemName.replace('Playpause', 'Power').sendCommand('OFF');
    });
  },
  tags: []
});

When a member of gSqueezePlaypause is updated, it will send the OFF command to the corresponding Power Item.

That’s probably not what you want. You probably only want some updates or commands to the Playpause Item to cause the OFF command to be sent to the Power Item but you’ve not provided enough info to understand what.

Based on what you are asking for I don’t quite understand the code. Some problems and potential problems I see:

  • What’s the forEach there for? Do you really mean to turn off all the Items when the rule is triggered?That different from what you said but could be handled easily enough. Put all your Power Items into a Group and sendCommand to that Group. Again it’s a one liner only this time it’s items.getItem('gSqueezePower').sendCommand('OFF');

  • You should pull gSqueezePlaypause inside the rule. I don’t think that handle you get on the Item will remain good forever so you may need to refresh it when the rule runs.

  • playeritems.members.state doesn’t make any sense at all. playeritems is a Group Item. A Group Item has members and, if it has an aggregation function also has a state. members doesn’t have a state. members is an Array of Items.

  • Again, playeritems.members is an Array of Item Objects. It’s not a String so there is no replace on it.

See JavaScript Scripting - Automation | openHAB for the complete reference for Items and Item (which are the only two things you are using here). I don’t think the main problem here is syntax but a misunderstanding of what members is.

For how to manipulate the members of the Group, see generic JavaScript references and tutorials for how to process an Array (e.g. findFirst, sort, map, reduce, forEach, etc.). members is just a plain old JavaScript Array. It holds Item Objects (see JavaScript Scripting - Automation | openHAB).

1 Like

hi Rich,

wow :wink:

seems the syntax isn’t the only problem… maybe my bad english also…

My goal was If one or more members of gSqueezePlaypause change to OFF, each of the corresponding Power items should be switched to OFF.

What is not implemented so far is a 1 minute timer which queries the last item change and only switches the Power item OFF if the item hasn’t changed in the last 60 seconds… maybe use time comparison for that…

no I wasn’t aware of the fact that playeritems.members is already an array itself… I wanted to walk through the members which state is OFF and replace the suffix for each to send the command…

good point :wink:

that was more kind of a placeholder while struggeling with the syntax…

ok that makes sense… the code I provided was a bit misleading maybe…

yes you’re right, I got the idea with members out of a RulesDSL example which looked similar to what I was thinking of in theory… thanks for pointing me to javascript arrays - I will have a look at it - and hopefully everything is easier with that at hand.

OK same one liner but change the trigger to a `GroupStateChangedTrigger(‘gSqueezePlaypause’, ‘OFF’) (I think that’s how you do it, look at the reference docs to be sure). Use the same

items.getIten(data.itemName.replace('Playpause', 'Power').sendCommand('OFF'); 

That way when any member of gSqueezePlaypause changes to OFF, an OFF command will be sent to the corresponding “Power” Item.

That’s all there is to it.

See JavaScript Scripting - Automation | openHAB for timers.

When the rule triggers, put that one liner into a timer’s body. Then check to see if the state of data.itemName is still OFF before sending OFF to the Power Item.

1 Like

seems to be working…

this is my rule so far

const {log, rules, triggers} = require("openhab");

rules.JSRule({
  name: "ECMA2021Script - Squeezebox Watchdog",
  description: "switch squeezebox off on pause after 1 minute",
  triggers: [
    triggers.GroupStateChangeTrigger('gSqueezePlaypause','OFF')
  ],
  execute: data => {
    const a = setTimeout(timer1, 60000);  
  
    function timer1() {  
      console.log("Power Off",data.itemName.state);
      if(data.itemName.state !== "ON") {
        items.getItem(data.itemName.replace('Playpause', 'Power')).sendCommand('OFF');
      }
    }
  },
  tags: []
});

two things that makes me pull my hair out is the logging which player is turned off and the if clause condition…

  • with data.itemName it returns null
  • with data.itemName.state it returns undefined
  • with data.itemName.toString it returns function toString() { [native code] }

but how do I get the state of the actual data.itemName??

Ok update… it’s working but still no itemName in the logs…

but this reveals the actual state:

items.getItem(data.itemName).state

and this is the rule

const {log, rules, triggers} = require("openhab");

rules.JSRule({
  name: "ECMA2021Script - Squeezebox Watchdog",
  description: "switch squeezebox off on pause after 1 minute",
  triggers: [
    triggers.GroupStateChangeTrigger('gSqueezePlaypause','OFF')
  ],
  execute: data => {
    const a = setTimeout(timer1, 60000);  
  
    function timer1() {  
      //console.log("Power Off",items.getItem(data.itemName)); // not working
      if(items.getItem(data.itemName).state == "OFF") {
        items.getItem(data.itemName.replace('Playpause', 'Power')).sendCommand('OFF');
      }
    }
  },
  tags: []
});

Are you sure that console.log(data.itemName) returns null if the rule is run by the trigger (not manually)?

Could you try data.newState?

1 Like

sorry you’re right :see_no_evil: forgot that the item is empty if the rule is triggered manually - it was late yesterday…

so actually console.log(data.itemName) is working!

here’s the complete working rule:

const {log, rules, triggers} = require("openhab");

rules.JSRule({
  name: "ECMA2021Script - Squeezebox Watchdog",
  description: "switch squeezebox off on pause after 1 minute",
  triggers: [
    triggers.GroupStateChangeTrigger('gSqueezePlaypause','OFF')
  ],
  execute: data => {
    const a = setTimeout(timer1, 60000);  
  
    function timer1() {
      if(items.getItem(data.itemName).state == "OFF") { // if Playpause is OFF
        if(items.getItem(data.itemName.replace('Playpause', 'Power')).state !== "OFF") { // and Power is not OFF
          console.log("Power Off",data.itemName.replace('Playpause', '')); // log Playername
          items.getItem(data.itemName.replace('Playpause', 'Power')).sendCommand('OFF'); // send OFF command to actual Player
        }
      }
    }
  },
  tags: []
});

thanks for your explanations and your help guys!

1 Like