[SOLVED] Group for String item as a switch and Switch

Hi, i have two lights , on defined as string and the second as a switch.

i want to setup a group to turn OFF both as below

My items

Group:String:OR(ON, OFF)    glight      "Lights" 	              <light>		["Switchable"]
Switch SwitchDoucheRight "Douche Spots"    (glight)       {channel="zwave:device:4f0192e8:node18:switch_binary2"}
String SwitchDressing                       (glight) 

/but both displayed as string in My Sitemap Below

Switch item=SwitchDoucheRight 	label="Spots"  icon="light"      mappings=[ ON="ON",OFF="OFF"]
Switch item=SwitchDressing label="Dressing" icon="light" mappings=[ ON="ON",OFF="OFF"]	 

but it do not work, the “Group item definition” will include only the items according to the definition.

2019-06-30 16:48:02.860 [ome.event.ItemCommandEvent] - Item 'SwitchDressing' received command OFF

2019-06-30 16:48:03.307 [vent.ItemStateChangedEvent] - SwitchDressing changed from ON to OFF

2019-06-30 16:48:03.351 [GroupItemStateChangedEvent] - glight changed from ON to OFF through SwitchDressing

Am i missing something?

I thought the Group item was only for display in the BasicUI, along with sitemaps.
It can report a status of the children, but not control, AFAIK.

Not it’s also possible to send a command to a grop

That’s probably the least useful capability of a Group (seeing as you cannot control order, colors, widget type etc.)

Quite right. The Group will then copy out the command to each of its members.
It is permissible for Group members to be different types of Items, but of course there are limitations.
I’m not entirely sure what happens when a Group of type Switch passes a command along to a String type member, whether it goes as an OnOffType or a StringType. ON is not the same as “ON”, though you cannot see the difference in the events log.

Anyway, the Group aggregation function - OR(ON, OFF) - is not expected to work properly when you have mixed type members. How could that work with a string set to “banana”?

Maybe you can change your String type Item to a Switch type, or use a proxy Switch.

That is by far the least useful use for Groups. Groups are most powerful when used in Rules and in Persistence. Look at the Design Pattern posts and you will find very few that do not utilize Groups.

And you can indeed control the members of a Group. When you send a command to a Group that command gets forwarded to all of it’s members. So, for example, one can create a Group:Switch:OR(ON,OFF) AllLights and if you AllLights.sendCommand(OFF) all members of AllLights will receive an OFF command.

Oh, and what’s the deal with the OR(ON,OFF)? Groups can aggregate the state of all of it’s members with a set of functions. In this example it means “if any member is ON, the Group’s state is ON, otherwise the Group’s state is OFF”. Put another way, the Group’s state will be ON if any member of the Group is ON.

@nakh_Home, I don’t think you can do this without a Rule. But we might be able to get this to work.

The big thing is that we cannot send ON or OFF as a command to a String Item. But we can send "ON" and "OFF" as a command to a Switch Item. And it does look like you tried to take that into account, at least on your sitemap. But I don’t think there is a way to aggregate the states of the two different types of Items on the one Group because the String will have state “ON” and the Switch ON and “ON” != ON.

In order to handle this I think you will need to create a Proxy Item (make it a Switch) that you send the command to and then a Rule to forward the proper version of the command to the members.

You will probably also want a Rule to update the Proxy Switch when any members of the Group change.

rule "Light's proxy received a command"
when
    Item AllLightsProxy received command
then
    glight.members.forEach[ light | light.sendCommand(receivedCommand.toString) ]
end

rule "Update AllLightsProxy"
when
    Member of glight changed
then
    AllLightsProxy.postUpdate(glight.members.map[ state.toString ].reduce[ result, st | if(result == "ON" || st == "ON") "ON" else "OFF" ])
end

See Design Pattern: Working with Groups in Rules for an explanation of the list operations performed above.

The first Rule loops through the members of glight and sends the received command sent to the proxy Item as a command in String format. In the case of Rules, the String will be parsed and converted to OnOffType for you. This is the part that is failing when you just send the command to the Group.

The second Rule triggers when any member of glight changes and loops through all the members of glight and updates the Proxy Item if any one of them is ON or “ON”.

Thanks for this clear and detailed explanation

it’s well working but i am getting this error

2019-07-02 12:48:30.324 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Update  glightProxy': null
2019-07-02 12:48:30.355 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Update  glightProxy': null
2019-07-02 12:48:43.818 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Update  glightProxy': null
2019-07-02 12:48:44.248 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Update  gParentsProxy': null
2019-07-02 12:50:18.157 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Update  glightProxy': null
2019-07-02 12:50:24.806 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Update  glightProxy': null

I have well understood the design but i do not understand yet this part

.map[ state.toString ].reduce[ result, st | if(result == "ON" || st == "ON") "ON" else "OFF" ])

My rules

	rule "Light's proxy received a command"

	when
		Item glightProxy received command
	then
		glight.members.forEach[ i| i.sendCommand(receivedCommand.toString) ]
	end

	rule "Update  glightProxy"
	when
		Member of glight changed
	then
		 glightProxy.postUpdate(glight.members.map[ state.toString ].reduce[ result, st | if(result == "ON" || st == "ON") "ON" else "OFF" ])
	end


	rule "Light's proxy received a command"
	when
		Item gParentsLightProxy received command
	then
		gParentsLight.members.forEach[ i| i.sendCommand(receivedCommand.toString) ]
	end

	rule "Update  gParentsLightProxy"
	when
		Member of gParentsLight changed
	then
		 gParentsLightProxy.postUpdate(gParentsLight.members.map[ state.toString ].reduce[ result, st | if(result == "ON" || st == "ON") "ON" else "OFF" ])
	end


	rule "gParents's proxy received a command"
	when
		Item gParentsProxy received command
	then
		gParents.members.forEach[ i| i.sendCommand(receivedCommand.toString) ]
	end

	rule "Update  gParentsProxy"
	when
		Member of gParents changed
	then
		 gParentsProxy.postUpdate(gParents.members.map[ state.toString ].reduce[ result, st | if(result == "ON" || st == "ON") "ON" else "OFF" ])
	end

My items

Switch gParentsProxy
Switch gParentsLightProxy
Switch glightProxy

String SwitchDressing (gAll,gChart,glight,gHistory,gParents,gParentsLight)

Group:Switch:OR(ON, OFF)    gParentsLight      "Parents Lights" 	              <light>		 ["Switchable"]
Group:String:OR(ON, OFF)    glight      "Lights" 	              <light>		["Switchable"]
Group:String:OR(ON, OFF) 	gParents  	"Parents" 	              <light>		 ["Switchable"]

What i am missing?

additional question :

Should Switch gParentsLightProxy be part of the group gParentsLight ?

Shouldn’t that be glight.sendCommand(receivedCommand.toString)
with `Group:Switch glight “Lights” [“Switchable”] ?

no that’s the point, we want to iterate on For each member of the group

1 Like

No. Because that doesn’t work with a mix of String Items and Switch Items as the members of glight, what we are doing is looping through all the members of glight (forEach) and sending the command as a String to each member individually. The sendCommand method is smart enough to convert “ON” to ON for the Switch Items so it bypasses the problem caused by the mix of Item types.

If all the members were Switches then we wouldn’t need the Rule and we could put glight on the sitemap as a Switch and be done.

That’s not a very helpful error. :unamused:

I explain map/reduce in the Working with Groups in Rules DP I linked above. In short, the map essentially calls a method or performs some operation on all the members of the List and returns a list with the results. In this case the map is returning a List of all the states of the members as Strings. Without the map our reduce would look like:

.reduce[ result, it | if(result == "ON" || it.state.toString == "ON") "ON" else "OFF" ]

OK, so what does the reduce mean? The reduce is a way to aggregate all the members of the List into one value. Often it’s used to sum of a bunch of states or build a String out of a List or the like. In this case we want to look at each member of the list and see if any are “ON”.

But now that I look at it, there is a simpler approach. Hopefully this won’t throw an error.

   glightProxy.postUpdate(if(glight.members.filter[ light | light.state.toString == "ON" ].size >= 1) ON else OFF)

In this line we use filter to get a list of all the members of glight whose String version of the state is “ON”. If there is more than one in the result then we set the Proxy to ON.

That one line if statement is called a trinary operator. The above code written without a trinary operator would be something like

if(glight.members.filter[ light | light.state.toString == "ON" ].size >= 1) glightProxy.postUpdate(ON)
else glightProxy.postUpdate(OFF)

Thanks again. your explanation are so clear !!! it works

i am trying to display the number of lights ON

i have updated the rule as below

rule "Update  glightProxy"
when
    Member of glight changed
then

    if(glight.members.filter[ light | light.state.toString == "ON" ].size >= 1) glightProxy.postUpdate(ON)
        else glightProxy.postUpdate(OFF)
    glightcount.postUpdate(glight.members.filter[ light | light.state.toString == "ON" ].size)
end

glightcount is a number

it’s working and well displayed but getting an error in the log

2019-07-02 18:21:46.798 [vent.ItemStateChangedEvent] - glightProxy changed from OFF to ON
==> /var/log/openhab2/openhab.log <==
2019-07-02 18:21:46.828 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Update  glightProxy': null
2019-07-02 18:21:46.875 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Update  glightProxy': null

==> /var/log/openhab2/events.log <==
2019-07-02 18:21:46.920 [vent.ItemStateChangedEvent] - glightcount changed from 0 to 4

This is really odd because the last line of the Rule is executing according to events.log. So what’s generating the error? Add logging to the top and bottom of the Rule to see if perhaps it’s being triggered multiple times and the first two are generating the errors.

It’s working without error when commenting the last row

Should I cast size?

You cannot postupdate to a Group. A Group’s state, if any, is derived from its membership, when specified.

glightcount is a number not a group

Number glightcount

I didn’t mention it before, but typically only Group Items are named with a leading “g”. The “g” stands for Group. It’s a naming convention followed by most OH users.

I was looking at glightProxy in fact, but it turns out that’s now a Switch :crazy_face:

I think the double ‘null’ report is a clue, it’s failing inside the lambda which happens to be repeated twice in the rule. Error inside lambda is not breaking rule 
 or even subsequent iterations of the lambda it seems.
This part
[ light | light.state.toString == “ON” ]
I can’t imagine why. Every Item/group should return something for .state.toString, NULL or UNDEF are perfectly acceptable? It’s like there is a broken group member.

1 Like

I was thinking something along those same lines too but usually just a null error like that implies a Type problem and type problems usually kill the whole Rule, not just one run through the lambda. And like you point out, there shouldn’t be any type errors because the toString should work with any Item state. It’s a real mystery to me.