String Group - How to Count?

I have an Evohome setup connected to my OpenHab and I want to count how many zones are following the programmed schedule. I have 9 zones.

I have created the following group with 9 string members (in .items)

Group:String:COUNT("FollowSchedule") ZonesFollowingSchedule

Now I want to count in my .rules file how many zones are following the schedule (out of the total zones)

rule "ZonesFollowingSchedule count"
when
    Member of ZonesFollowingSchedule received update
then
    val nZonesCount = ZonesFollowingSchedule.members.size
    val nZonesFollowingScheduleCount = ZonesFollowingSchedule.members.filter[ i | i.state == "FollowSchedule" ].size
    logInfo("aaa", nZonesCount.toString())
    logInfo("bbb", nZonesFollowingScheduleCount.toString())
    logInfo("ccc", ZonesFollowingSchedule.state().getClass().getName())
end

I can count the zones following the schedule with “members.filter”, but is there a simpler method?
When I use the “state” member, it is returning the wrong type (org.eclipse.smarthome.core.library.types.DateTimeType), but it shows 4 seconds since 1970. So the value is correct (4 zones are following the schedule).

So my question:

  • What is the correct way to get ‘count’ out of the group?

That’s laying a trap for you. In parallel to your rule, the member update will trigger a recalculation of Group state, which is not instantaneous.
If you read Group state in rule, you may get “before” or “after” state.

Simplest circumvention is a short delay, say 50mS

But you want to get a count i.e.numbers
The type is about the type of the Group, not its members
Group:Number:COUNT("FollowSchedule") ZonesFollowingSchedule

Note - that does restrict you if you use the Group to distribute commands to members, a Number type Group will not accept stringy commands.

Thanks for your feedback.

As to the original question. Changing the type to Number does not work as it should be counting strings “FollowSchedule”. openhab.log gives an error on the change. Or do you mean something else?

Group:String:COUNT("FollowSchedule") ZonesFollowingSchedule

// State 'FollowSchedule' is not valid for a group item with base type 'Number'

As for adding the sleep, is that really necessary?
I read on OpenHab Rules the received command triggers before a state update. So, I would assume that received update would be after a state update?

Yes it’s necessary. Here’s a potential sequence of events. Item Foo updates which triggers the Rule. In parallel the Group aggregation function starts calculating the Group’s new state. The Rule starts running and it gets to the line where you access the Group’s state before the aggregation function is done calculating the Group’s state.

The key is the rule is triggered by the Group’s member’s update, not the update to the Group Item itself.

rossko57’s advice is correct. The type for the Group needs to match the type returned by the aggregation function. So it should be Number, not String. The docs say this as well.

The error though seems to indicate that the COUNT aggregation function is not actually counting the Items whose state matches “FollowSchedule” which might point to a bug in that function. Are any of the members of the Group that are NULL o UNDEF? I think those sometimes cause unexpected behavior.

Thanks for the clarification, I understand now why. It’s just that I don’t like sleep’s in my code.

After I restarted the docker container with OpenHAB, it works as expected. I don’t get the error anymore and I can use the state() method to retrieve the count. Thanks for the help!

Correction.
I still get 2 WARN messages (not an error) on the Group:Number:Count during boot or whenever I change the *.items file. I do not see any problems with the behavior though.

2020-10-12 19:57:21.777 [WARN ] [arthome.core.items.dto.ItemDTOMapper] - State 'FollowSchedule' is not valid for a group item with base type 'Number'
2020-10-12 19:57:21.780 [WARN ] [arthome.core.items.dto.ItemDTOMapper] - State 'FollowSchedule' is not valid for a group item with base type 'Number'

I did actually try this out (in OH 2.5.0) and did get those messages too at Group editing - ignored them because I was first playing with String type, thinking an editing artefact.

But yep, looks like unwanted behaviour at Group initialization. There is no reason for the framework to attempt to set Group state to the target “string”, it is only a WARN but not needed, certainly not needed twice.

Duh, when looking at creating a Github issue I find I’ve been through this before


so should get fixed at OH3
1 Like

Thanks, I was just starting out with OpenHab and thought I was doing something incorrect.
Good to know it’s an issue.

Often, but not always, you can change the trigger to not need it. Instead of triggering the rule every time a member receives and update, what if you trigger only when the Group itself changes? This of course assumes that the COUNT is working correctly and you have some rule that does more than the above posted rule which doesn’t really do anything.

If you assume that COUNT is working correctly the rule in the OP is superfluous. The count is the state of the ZonesFollowingSchedule. If COUNT isn’t working than you would need the Rule above to filter and get the count in which case you wouldn’t use the COUNT at all and just rely on the filter.

In neither case is a sleep required. It’s only in this debug rule where you are trying to use both that the problem occurs and you have to rely on a sleep.

My use case is as follows. I have an EvoHome heating system and I want a single switch that reflects whether all the zones in the heating system are following the schedule (state “FollowSchedule”). I have it working but my rule activates roughly each 15 seconds (for all the items in the group) while there was no change to the group. If you have a suggestion for a better trigger, please let me know.

evo.items

Group:Number:COUNT("FollowSchedule") Evo_ZonesFollowingSchedule

String Evo_Living_SetPointStatus "Status" <heating> (Evo_SetPointStatus, Evo_ZonesFollowingSchedule) { channel="evohome:heatingzone:XXXX:YYYY0:SetPointStatus" }
String Evo_Kitchen_SetPointStatus "Status" <heating> (Evo_SetPointStatus, Evo_ZonesFollowingSchedule) { channel="evohome:heatingzone:XXXX:YYYY1:SetPointStatus" }
// etc

evo.rules

// Count how many zones are following the schedule and
// determine the state of the Evo_FollowSchedule switch
// members of Evo_ZonesFollowingSchedule can have the following values
//   FollowSchedule
//   PermanentOverride
//   TemporaryOverride
rule "Evo_ZonesFollowingSchedule controls switch Evo_FollowSchedule"
when
    Member of Evo_ZonesFollowingSchedule received update
then
    // circumvent thread update issues by adding a delay
    Thread::sleep(50)
    // update Evo_FollowSchedule
    var nZonesCount = Evo_ZonesFollowingSchedule.members.size
    var nZonesFollowingScheduleCount = Evo_ZonesFollowingSchedule.state
    if (Evo_FollowSchedule.state == ON) {
        if (nZonesFollowingScheduleCount < nZonesCount) {
            Evo_FollowSchedule.sendCommand(OFF)
        }
    }
    else if (Evo_FollowSchedule.state == OFF) {
        if (nZonesFollowingScheduleCount == nZonesCount) {
            Evo_FollowSchedule.sendCommand(ON)
        }
    }
end

Presumably you only need the rule to run when the situation changes.
In openHAB, update-to-same is still an update - but not a change.

Member of Evo_ZonesFollowingSchedule changed

It’s not clear to me if you might need the rule to run only when the total changes, and who cares what individual members are up to

Item Evo_ZonesFollowingSchedule changed

Excellent suggestion. I only need to know when the total changes.

OK, try this. I’ve no idea if it’ll work or not but I think it should:

Group:String:AND("FollowSchedule", "NotFollowingSchedule") Evo_ZonesFollowingSchedule

If that works, when all the members equal “FollowSchedule” Evo_ZonesFollowingSchedule’s state should be “FollowSchedule”. If any one is not “FollowSchedule” Evo_ZonesFollowingSchedule’s state should be “NotFollowingSchedule”.

That makes for a very elegant solution, thanks! I was already wondering if I could use the AND or OR statements for groups. It makes the logic in the rule a lot simpler.

rule "Evo_ZonesFollowingSchedule controls switch Evo_FollowSchedule"
when
    Member of Evo_ZonesFollowingSchedule changed
then
    var state = Evo_ZonesFollowingSchedule.state
    logInfo("evo.rules", String.format("Evo_ZonesFollowingSchedule. '%s' triggers '%s'", triggeringItem.name, state))
    if (Evo_FollowSchedule.state == ON && state != "FollowSchedule") {
        Evo_FollowSchedule.sendCommand(OFF)
    }
    else if (Evo_FollowSchedule.state == OFF && state == "FollowSchedule") {
        Evo_FollowSchedule.sendCommand(ON)
    }
end

While not really shorter you could simplify the rule a little by changing the order of the operations

    // convert the state to ON/OFF
    val new_state = if (Evo_ZonesFollowingSchedule.state == "FollowSchedule") ON else OFF
    if(Evo_FollowSchedule.state != new_state) EvoFollowSchedule.sendCommand(new_state)

I left out the log statement but you can easily add it back.

Thanks, shorter is better. I wasn’t aware that you could assign ON or OFF to a variable.