Hello, I am trying to realize the following with an Openhab2 rule:
Trigger an alarm if one of the smoke detectors switches to ON or one of the temperature sensors reports a temperature of >45 °C.
Send a notification via Telegram (already set up) naming the item that triggered the alarm
I have realized this with rules that check every possible trigger item via if-statements but this is far from elegant and quite error prone as the number of devices increases. I expect there to be a solution using groups (gTemperatures, gSmokeDetectors) but so far I had no success.
Looking forward to some ideas. Many thanks!
I think there are more than one possible solutions. I suggest an alarm trigger for the temperature sensors based on a group with a MAX function and a rule which checks the groups value like this:
Group:Number:MAX temperatureFireAlarm
Number TemperatureSensor1 "Sensor 1" (temperatureFireAlarm)
Number TemperatureSensor2 "Sensor 2" (temperatureFireAlarm)
...
// and so on
rule "fire alarm"
when
Item temperatureFireAlarm changed
then
if( temperatureFireAlarm.state > 45 ) {
// TODO send notification
}
end
For your your smoke detectors you can use a group with a Group:Switch:OR(ON,OFF) function and a rule to check if the groups value changed to ON.
Please be aware that this examples may contain errors or typos, I wrote it down untested.
See the Working with Groups in Rules Design Pattern (I’m having issues with the search function right now or I’d provide the link).
Put all your temp sensors in a Group like cweitkamp suggests:
Group:Number:MAX gTemperatures
Put all your alarms in a Group:
Group:Switch:OR(ON,OFF) gSmokeDetectors
In your rule:
rule "Alarm!"
when
Item gTemperatures changed or
Item gSmokeDetectors changed to ON
then
// if a change to gSmokeDetectors triggered the rule receivedCommand will be set to ON
// do nothing if the smoke detectors haven't triggered and all the temps are below 45
if(receivedCommand != ON && gTemperatures.state as Number <= 45) return;
val message = new StringBuilder()
message.append("The following alarms are active: ")
gTemperatures.members.filter[t|t.state as Number > 45].forEach[t |
message.append(transform("MAP", "alarm.map", t.name) + " = " + t.state.toString + ", ")
]
gAlarms.members.filter[a|a.state == ON].forEach[a |
message.append(transform("MAP", "alarm.map", a.name) + ", ")
]
message.delete(message.length - 2, message.length) // delete the extraneous ", "
// send message
end
It isn’t exactly what you are asking for but I personally like to have a message with all the Items that are in an alarm state, not one message per alarm.
Note the use of the transform. Create an alarm.map that maps your Item names to a friendly name:
A few questions regarding your code:
If I read it correctly the message is currently only generated if both a smoke detector is triggered AND a temperature sensor reads >45 °C. I would like the connection to be with OR so that in either case the message is created. The message would then contain the information on which device (smoke detector, temperature sensor or both) triggered the rule so I know where the fire started. How could I do that?
That is incorrect. The rule sends a message when either the temperature goes over 45 OR a smoke detector goes off.
If you set up persistence you can sort the alarms and temps by lastUpdate to get the message to list each in the order that they triggered.
Make sure all the triggering Items are persisted.
Then change the rule to:
import java.util.List
rule "Alarm!"
when
Item gTemperatures changed or // Trigger when the max temp of all sensors changes
Item gSmokeDetectors changed to ON // OR trigger when one or more of the smoke detectors goes off
then
// If there is no alarm state (i.e. all smoke detectors are OFF and all temperatures are <= 45) exit the rule
if(gSmokeDetectors.state != ON && gTemperatures.state as Number <= 45) return;
val List<GenericItem> alarms = newArrayList
// Get all the temperature Items that are above 45
alarms.addAll(gTemperatures.members.filter[t|t.state as Number > 45])
// Get all the smoke alarms that are ON
alarms.addAll(gSmokeDetectors.members.filter[s|s.state == ON])
// Sort them to be in the order they were most recently changed, oldest alarm listed first
val sorted = alarms.sortBy[lastUpdate]
val message = new StringBuilder()
// Build up the message listing the oldest alarms first and most recent last.
message.append("The following alarms are active: ")
sorted.forEach[i|
if(i instanceof NumberItem) message.append(transform("MAP", "alarm.map", i.name) + " = " + i.state.toString + ", ")
else message.append(transform("MAP", "alarm.map", i.name) + ", ")
]
message.delete(message.length - 2, message.length) // delete the extraneous ", "
// send message
end
Thanks again. Unfortunately I am still having trouble. This is my rule, I have added a few conditions in order to test it. I am getting errors in lines 11
Multiple markers at this line
- mismatched input '45' expecting '>'
- no viable alternative at input '='
- Type mismatch: cannot convert from Number to boolean
and 16, 19, 22
Type mismatch: cannot convert from Iterable<Item> to GenericItem[])
import java.util.List
rule "Fire Alarm!"
when
Item gTemperatures changed or // Trigger when the max temp of all sensors changes
Item gSmokeDetectors changed to ON or // OR trigger when one or more of the smoke detectors goes off
Item gLuminance changed or
Item Azimuth changed
then
// If there is no alarm state (i.e. all smoke detectors are OFF and all temperatures are <= 45) exit the rule
if(gSmokeDetectors.state != ON && gTemperatures.state as Number <= 45) return;
val List<GenericItem> alarms = newArrayList
// Get all the temperature Items that are above 45
alarms.addAll(gTemperatures.members.filter[t|t.state as Number > 45])
// Get all the smoke alarms that are ON
alarms.addAll(gSmokeDetectors.members.filter[s|s.state == ON])
// Get all the luminance items that are above 0
alarms.addAll(gLuminance.members.filter[t|t.state as Number > 0])
// Sort them to be in the order they were most recently changed, oldest alarm listed first
val sorted = alarms.sortBy[lastUpdate]
val message = new StringBuilder()
// Build up the message listing the oldest alarms first and most recent last.
message.append("The following alarms are active: ")
sorted.forEach[i|
if(i instanceof NumberItem) message.append(transform("MAP", "alarm.map", i.name) + " = " + i.state.toString + ", ")
else message.append(transform("MAP", "alarm.map", i.name) + ", ")
]
message.delete(message.length - 2, message.length) // delete the extraneous ", "
// send message
sendTelegram("Jonas", message)
// trigger alarm
sendCommand(Light_GF_Kitchen_Ceiling, ON)
end
Grrr. I wish they had used a non reserved word for Items in the Rules.
We can’t use the addAll method. Instead:
restore the <GenericItem> to the declaration of alarms.
replace the calls to addAll with
gTemperatures.members.filter[t|t.state as Number > 45].forEach[t|alarms.add(t)]
gSmokeDetectors.members.filter[s|s.state == ON].forEach[a|alarms.add(a)]
Really sorry to keep coming back but now I am getting this:
Type mismatch: cannot convert from Item to GenericItem
for the lines
gTemperatures.members.filter[t|t.state as Number > 45].forEach[t|alarms.add(t)]
gSmokeDetectors.members.filter[s|s.state == ON].forEach[a|alarms.add(a)]
I guess you are hearing this quite often but I really appreciate you taking the time to help non-coder such as me!
I tried to adjust the rule to make an alarm system using my door contact sensors. Unfortunately it does not execute.
This is the rule:
rule "Einbruch!"
when
Item gDoors changed to OPEN // Trigger when a door is opened
then
logInfo("FILE", "A door was opened")
// If the alarm system is not activated, exit the rule
if(AlarmSystem.state != OFF) return;
logInfo("FILE", "And the alarmsystem was activated")
val List<GenericItem> Break_In = newArrayList
// Get all the Doors that have been opened
gDoors.members.filter[d|d.state == OPEN].forEach[d|Break_In.add(d as GenericItem)]
// Sort them to be in the order they were most recently changed, oldest alarm listed first
val sorted = Break_In.sortBy[lastUpdate]
val message = new StringBuilder()
// Build up the message listing the oldest alarms first and most recent last.
message.append("Einbruch in: ")
sorted.forEach[i|
if(i instanceof NumberItem) message.append(transform("MAP", "alarmanlage.map", i.name) + " = " + i.state.toString + ", ")
else message.append(transform("MAP", "alarmanlage.map", i.name) + ", ")
]
message.delete(message.length - 2, message.length) // delete the extraneous ", "
// send message
sendTelegram("Jonas", message.toString)
sendTelegram("Johanna", message.toString)
// trigger alarm
gLights?.members.forEach[l|l.sendCommand(ON)Thread::sleep(500)]
end
And this the error message:
An error occurred during the script execution: Unhandled parameter types: [null, org.eclipse.xtext.xbase.interpreter.impl.DefaultEvaluationContext@4e7cd7, org.eclipse.xtext.util.CancelIndicator$1@4560ce]