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.
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
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.
// 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
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.