Building my first JSRule. Issue with GroupMembers

I want to learn this with a easy function.
I’m searching how to walk to all members of a group like I did with DSL but can’t find a solution yet.

rules.JSRule({
    name: "Tougle Koelkastlampen",
    description: "test ",
    triggers: [triggers.GroupStateChangeTrigger('Koelkastlampen')], //triggers.GroupStateChangeTrigger('my_group', 'OFF', 'ON');
    execute: (event) => {
        const triggerItem = event.itemName;
        const triggerGroup = event.groupName;
        const newValue = event.newState
        triggerGroup.members.forEach[ i | i.sendCommand (newValue)] 
       console.log("Rule has fired because of " + triggerItem + " from group ", triggerGroup + " with new value ", newValue);    
     }
});   

The error I get

2024-02-26 09:49:23.535 [ERROR] [automation.script.file.javartrial.js] - Failed to execute rule Tougle-Koelkastlampen-8d3140ff-1f97-4ae7-8bca-cbf27a7e5dfd: TypeError: Cannot read property "forEach" from undefined: TypeError: Cannot read property "forEach" from undefined
	at execute (javartrial.js:37)
	at execute (@openhab-globals.js:2)

Who can help me in the right direction?

As the name of the event property implicates, the property holds the groupName, not the group.

Modify your code to get the trigger group:

const triggerGroup = items.getItem(event.groupName)

However I am not sure what you want to accomplish with your rule … if you want to control all group members, make sure to set the group type to the same type as the members and then you should be able to send a command to the group, which is then forwarded to all members.

Cannot read property “forEach” from undefined

This means

triggerGroup.members

is undefined (wrong expression).
Furthermore, I think you are still using DSL syntax ’[i | ]’.
Try:

items[triggerGroup].members.forEach( i => i.sendCommand(newValue));

I recently learned that this is no longer necessary. You can send a command to a Group without a type now. I’m not sure when this changed but it came up on another thread.

1 Like

This was the solution but need to go deeper because I only want the do i.sendCommand(newValue) to the members of type ColorItem and not the switchItem. If I log the value with

rules.JSRule({
    name: "Tougle Koelkastlampen",
    description: "test ",
    triggers: [triggers.GroupStateChangeTrigger('Koelkastlampen')], //triggers.GroupStateChangeTrigger('my_group', 'OFF', 'ON');
    execute: (event) => {
        const triggerItem = event.itemName;
        const triggerGroup = event.groupName;
        const triggerGroupName = items.getItem(event.groupName);
        const newValue = event.newState
        items[triggerGroup].members.forEach( i => { 
           console.log (i.type)
        );
    }
});   

I get ColorItem and SwitchItem.
I thought if I replace the log line with the expression below I get only the items with type ColorItem but that is not happening. I get in the log the error “ColorItem” is not defined.

  if (i.type.equals (ColorItem)) {
     console.log ("Only ColorItem: ", i.type)
  }    

Where in doc I should look to find the correct way to find this?

Found it. It is a string. Below is working

  if (i.type.equals ("ColorItem")) {
     console.log ("Only ColorItem: ", i.type)
  } 
    items[event.groupName].members.filter( i => i.type.equals('ColorItem') )
                                  .forEach( i => i.sendCommand(event.newState) );

You can also create a separate Group for the ColorItems and just send the command to the ColorItem Group. Items can be members of more than one Group.

Thanks all.
Technically this is working but my approach i wrong. This is creating a loop :frowning:

What is the source of the loop? Does commanding the Items change the state of the Group? I assume this Group has an aggregation function?

Unfortunately the Group’s state gets incrementally calculated so one change in the Group’s members may result in up to N-1 changes to the Group where N is the number of members of the Group. In practice that usually doesn’t happen.

But it’s not really clear how this is working at all because usually the aggregation function doesn’t work well when the Group has members of different types. Though if they are all Switch, Dimmer, and Color Items maybe it works.

It’s not entirely clear exactly what you are trying to accomplish but if it’s the keep all the members of this Group in sync you might want to use a member of trigger, maybe member changed or even better received command if that will work. Then if you test the Item’s state before trying to command it and skip commanding those that are already in the right state.

If your lights gradually change and report their transitions then you might need to set something up more complicated because this rule will cause lots and lots of changes for one command.

Keeping a bunch of Items in sync can be a challenging problem.

In the group I have a switch and multiple lights. I thought reading the documentation that the line below was only resulting in a trigger if the switch was changed (doc talkes about OFF and ON) but see now that a ColorItem also triggers this event. Buy switching the lights on the ColorItem is changed what results in a loop.

triggers: [triggers.GroupStateChangeTrigger('Koelkastlampen')], //triggers.GroupStateChangeTrigger('my_group', 'OFF', 'ON');

If you just want the Switch to trigger the rule, just use the Switch in the trigger. If you use a GroupStateChangeTrigger, that will trigger the rule if any member of the Group changes.

If you want the rule to change when the Group Item changes state.

It might work to change to use two triggers, one triggers.GroupStateChangeTrigger('Koelkastlampen', null, 'ON') and triggers.GroupStateChangeTrigger('Koelkastlampen', null, 'OFF'). Since a ColorItem’s state is not ON/OFF that might work to ignore the changes from the ColorItems. But that’s not guaranteed to work because in most other ways a ColorItem can be treated the same way as a Switch so OH might have logic to make it work those triggers too.

But ultimately this is the wrong approach. If you just want the switch to control the ColorItems:

  1. Remove the Switch from the Group
  2. Set the Switch to trigger the rule using an ItemStateChange trigger
  3. The rule’s logic becomes items.Koelkastlampen.sendCommand(event.newState)

Even if you leave the Switch Item in the Group you can fix the loop by only having the Switch trigger the rule.

However, if you want to keep everything in sync you do need to use the GroupStateChangeTrigger and you will need to add logic to prevent loops. Note that for the one change the rule will end up triggering for each member of the Group that changed as a result of that first run of the rule.

I’m making small steps. At the moment I’m structuring to convert a HUE color rule to a javascript function.
If I was a experience js developer I assume I could write this with the documentation I found State (openHAB Core 4.2.0-SNAPSHOT API) but that is not the case
This is the DSL rule

rule "Keuken lampkleur kookplaat"
when
     Item KoelkastLampenKleur received command
then
    var hue = (KoelkastLampenKleur.state as HSBType).getHue
    var sat = (KoelkastLampenKleur.state as HSBType).getSaturation
    var b = (KoelkastLampenKleur.state as HSBType).getBrightness
    val HSBType color = new HSBType(hue, sat, b)
    Koelkastlampen.members.forEach[ i | sendCommand(i, color.toString)]
end

This is my start of the function. I hope somebody can help.

function changeLightsColor(theColor, theGroup) {
  console.log("Binnen functie changeLightsColor: " + theColor.state );
  currentHSBType = Java.type('org.openhab.core.library.types.HSBType'); // https://www.openhab.org/javadoc/latest/org/openhab/core/types/state

  theGroup.members.forEach( i => { 
    console.log("loop: ", i);
    i.sendCommand(...);
  })  
} 

This was the solution

function changeLightsColor(theColor, theGroup) {
  var HSBType = Java.type('org.openhab.core.library.types.HSBType');
  newHSB = new HSBType(theColor.state);
  theGroup.members.forEach( i => { 
    i.sendCommand(newHSB);
  })  
}