Notifications in group design pattern

I am using pushover and email as notification if something happens in my home.
Before I only had if someone rang my doorbell and i were not home, however I would like to expand it this now:

So what I have now is groups which I would like to send out notfications, as well I would like to have warnings and alert .
With warnings I mean, notifications which is nice to get, but not worth waking up for, alerts immediate action!
Below is group names type and condition to trigger

TemperatureRooms warning >25 alert >30
CO2Rooms warning > 1000 alert >1500
WaterLevelOfPlant warning < 50
DoorBell warning DoorBell=ON and presenceAll=OFF

My question is can I loop through the groups instead of making tons of triggers?

rule "Temperature"
when Item TemperatureRooms changed 
then
	if (time.now<21.00 and time.now>09.00 ) //Warning
	{
		TemperatureRooms.members.forEach(s | if (s.state>25) {
		logInfo("Logging info", "Its too hot")
		sendEmail("Its too hot in room : " & s.name)
		pushover("Its too hot in room : " & s.name)
	}
		
	TemperatureRooms.members.forEach(s | if (s.state>30) { //Alert
		logInfo("Logging info", "Its too hot")
		sendEmail("Its too hot in room : " & s.name)
		pushover("Its too hot in room : " & s.name)
end

I guess your code is some sort of fake code?
First thing to know is, a Group gets only a state if set to an item Type, so Group myGroup does not suffice, but you have to define the group like

Group:Number TemperatureGroup
Number Temp1 (TemperatureGroup)
Number Temp2 (TemperatureGroup)
Number Temp3 (TemperatureGroup)
Number Temp4 (TemperatureGroup)
Number Temp5 (TemperatureGroup)

Second thing is, if triggering a rule with a Group Item, this rule will be triggered multiple times per change/update/command, so maybe it’s better to trigger the rule with the member items although using the group.

rule "warnings and alerts for temperature"
when
    Item Temp1 changed or
    Item Temp2 changed or
    Item Temp3 changed or
    Item Temp4 changed or
    Item Temp5 changed
then
    if (now.plusHours(3).getHourOfDay > 11) { // 9 to 21
    TemperatureRooms.members.filter(t|(t.state as DecimalType)>=25 && (t.state as DecimalType)<30).forEach(r|
        logInfo("tempRoom","Temp warn {}: {}°C",r.name,r.state)
        // other notifications
        )
    }
    TemperatureRooms.members.filter(t|(t.state as DecimalType)>=30).forEach(r|
        logInfo("tempRoom","Temp alert {}: {}°C",r.name,r.state)
        // other notifications
        )
end

Now to understand the rule:

First step is time. If time is 21:00 and I add 3 hours, it would be 00:00, so the hour of day would be 0, if time is 09:00 and I add 3, it would be 12:00, so the hour of day would be 12.

Second, the filter would let only pass those items, which fit, in case of warning, this would be 25 to 29.99.
We don’t want warnings for Items, which will also trigger an alert.

Third, logInfo will print one line per Item with the Item name and state. Because I used the {}, logInfo will do the type conversion :wink: which must be done in the filter statement.
Other notifications will work the same way.

Downside: every change will trigger the rule, so you will get many warnings and alerts when one item reaches the threshold.

Thanks!
I am not sure why group trigger will trigger more times then adding every item in the group as you suggested.

Good point, what if we add an item:
Switch WarningTempSent (AlarmSent)

rule "warnings and alerts for temperature"
when
    Item Temp1 changed or
    Item Temp2 changed or
    Item Temp3 changed or
    Item Temp4 changed or
    Item Temp5 changed
then
    if (now.plusHours(3).getHourOfDay > 11) { // 9 to 21
    WarningTempSent.state = OFF && TemperatureRooms.members.filter(t|(t.state as DecimalType)>=25 && (t.state as DecimalType)<30).forEach(r|
        WarningTempSent.sendCommand(On)
        logInfo("tempRoom","Temp warn {}: {}°C",r.name,r.state)

        // other notifications
        )
    }
    TemperatureRooms.members.filter(t|(t.state as DecimalType)>=30).forEach(r|
        logInfo("tempRoom","Temp alert {}: {}°C",r.name,r.state)
        // other notifications
        )
end

then we add a second cron rule

rule "reset alarms once a day"
when
rule "warnings and alerts for temperature"
when
Time cron "0 * * * "
then
AlarmSent.forEach(r|sendcommand(off)
end

Finally in the same cron rule it would be nice to check if the last time the was update was in the last 24 hours, otherwise, it might indicate low battery, wifi connection lost etc…

It is a side effect in how the state of the Group is calculated. And a recent change (as of 2.1) makes it so a Group that isn’t given a type (e.g. Group:Number) no longer receive any updates at all. This is why Udo recommends the need to add that to your Group definition.

So I do something similar. First, let’s break the problem down a little bit. I recommend separating the alerting code from your detection code. See the Separation of Behaviors Design Pattern for how I do this. The tl;dr is create an Alert Item and an Info Item and use Alet.sendCommand(“message”) to send an alert and the same with Info. Then implement the code that actually sends the alert messages out using pushover, email, what ever in a rule that triggers on Alert received command.

The nice thing about this approach is you can add in some complicated logic that will apply everywhere at once without requiring you to make changes everywhere. For example, during the day I send Infos using NotifyMyAndroid and for Alerts I use Cloud Connector’s Broadcast. At night my wife really really doesn’t want to be woken up by these sorts of alerts so I shift to send the alerts via NotifyMyAndroid at night.

Also, notice the use of the Time of Day Design Pattern.

Now for code to determine what alerts to make.

Assuming you fix your Group definitions so they all have the appropriate Type and Function:

Group:Number:MAX TemperatureRooms
Group:Number:MAX CO2Rooms
Number WaterLevelOfPlant // I'm assuming this is just one sensor, not a Group
Switch Doorbell // again, just one Switch, not a Group

you have two choices.

  1. Write your rule so it only triggers once per Item change
  2. Write your rule so it doesn’t matter that the Rule triggers more than once for each change to one of its members.

Solution 1 is Udo’s approach. You need to add each Item individually as a trigger. You can still loop through the Group and use Persistence tricks to figure out what Item triggered the rule or you can not care and just construct an alert that includes all the Items that are warning or alerting.

This would look like, if you implement Time of Day and Separation of Behaviors:

rule "warnings and alerts for temperature"
when
    Item Temp1 changed or
    Item Temp2 changed or
    Item Temp3 changed or
    Item Temp4 changed or
    Item Temp5 changed
then
    val message = new StringBuilder

    TemperatureRooms.members.filter[temp|temp.state as Number > 25].forEach[temp |
        message.append(temp.name + " is " + temp.state + "!\n")
    ]
    if(TemperatureRooms.state as Number > 30) aAlert.sendCommand(message.toString)
    if(TemperatureRooms.state as Number > 25 && vTimeOfDay.state.toString == "DAY") aInfo.sendCommand(message.toString)
end    

One neat trick is to use a MAP transform to convert the Item’s name into a friendly name. Create a map file and change the message.append to:

message.append(transform("MAP", "alert.map", temp.name) + " is " + temp.state + "!\n")

Solution 2 is to write the rule such that you don’t care if it gets triggered multiple times per change to TemperatureRooms’s members. There are lots of ways to go about this and which is best depends on a lot of personal requirements. In the case of TemperatureRoom’s we can take advantage of the use of the MAX function for the Group definition. We don’t need to trigger the rule every time one of the room’s changes temperature, we only need to trigger the rule when the maximum temperature of all the rooms changes so we can trigger the rule on changes to the Group rather than updates.

rule "Temperature"
when
    Item TemperatureRooms changed
then
    val message = new StringBuilder

    TemperatureRooms.members.filter[temp|temp.state as Number > 25].forEach[temp |
        message.append(transform("MAP", "alert.map", temp.name)  + " is " +transform("MAP", "alert.map", temp.state)  + "!\n")
    ]
    if(TemperatureRooms.state as Number > 30) aAlert.sendCommand(message.toString)
    if(TemperatureRooms.state as Number > 25 && vTimeOfDay.state.toString == "DAY") aInfo.sendCommand(message.toString)    
end

However, this is not always the case. For example, if you have multiple DoorBells and DoorBell is a Group then we can’t really rely on the aggregation function so we would need a way to filter out the multiple rule triggers. I just posted a new Design Pattern showing how to do this here:

I forgot to address this issue in my previous response. I’ve written up how I do this in the Event Limit Design Pattern with a more complete example in the Generic Is Alive Design Pattern. And since I’ve written those, I’ve started applying the Assocaited Items Design Pattern to store the boolean indicating whether the alert has been sent instead of in global variables.

Something like (note I’m currently testing this code, there may be logical errors):

import java.util.concurrent.locks.ReentrantLock

val ReentrantLock statusLock = new ReentrantLock

rule "A sensor changed its online state"
when
	Item gSensorStatus received update
then
    try {
    	statusLock.lock
    	Thread::sleep(100)
    	val recentUpdates = gSensorStatus.members.filter[sensor|sensor.lastUpdate("mapdb").isBefore(now.minusSeconds(1).millis)]
    	recentUpdates.forEach[sensor|
    		val alerted = gOfflineAlerted.members.filter[a|a.name==sensor.name+"_Alerted"].head
    		
    		if((alerted.state == ON && sensor.state == ON) ||
    		   (alerted.state == OFF && sensor.state == OFF)){
    			aInfo.sendCommand(transform("MAP", "admin.map", sensor.name) + " is now " + transform("MAP", "admin.map", sensor.state.toString) + "!")
    			alerted.postUpdate(if(sensor.state == ON) OFF else ON)
    		}
    	]
    }
    catch(Exception e){
    	logError("admin", "Error processing an online status change: " + e.toString)
    }
    finally {
    	statusLock.unlock
    }
end

In the above code, if the Associated Alerted Item is ON and the sensor has changed state to ON I send an alert and change the alerted flag to OFF. If the sensor has changed to OFF and alerted is OFF I send an alert and change the state to ON. If the sensor’s state is ON and alerted is OFF I know I’ve already sent an alert so no new alert needs to be sent. Similarly, if the alerted is ON and the sensor’s state is OFF I know I’ve already sent an alert so no new alert needs to be sent.

Because I’m using Items and restoreOnStartup this information gets persisted across OH reboots and rules edits.

I am to tired after a long day at work, so I need to read your post a few times to understand it :slight_smile:

Sorry for the typo:


    Group:Number:Min WaterLevelOfPlants
    Doorbell is just one so far