Fire alarm trigger and notification

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!

Jonas

Hi Jonas,

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.

1 Like

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:

Hall_Smoke_Detector=Hall Smoke Detector
4 Likes

Very nice! I will try this once I am home from work.
Thanks a lot.

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

Put the gTemperatures.state as Number in parens.

Eliminate the <GenericItem> from the List declaration.

Thanks again. I have done that so my code looks like this:

 if(gSmokeDetectors.state != ON && (gTemperatures.state as Number) <= 20) return;
 val List alarms = newArrayList

I still get the following error

Type mismatch: cannot convert implicit first argument from Object to Item

for the line

val sorted = alarms.sortBy[lastUpdate]

and

The method or field name is undefined for the type Object

for the line

else message.append(transform("MAP", "alarm.map", i.name) + ", ")

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!

Add “as GenericItem” after the t and the a in the call to add.

Perfect, now it works.
One last thing: sending a string message via telegram (“Test”) works but

sendTelegram("Jonas", message)

does not. How do I refer to the string that we finally put together?

All objects can be converted to a steering by appending “.toString”.

I should have included a line showing that. I’m really glad you got it to work!

I am sorry, can you elaborate on that? Do I need a line to convert message to a string? How would I add the string to the Telegram command?

message.toString
MyItem.state.toString

Append toString to just about anything to convert it to a String.

Still not getting it. the final line to generate the message is

message.delete(message.length - 2, message.length) // delete the extraneous ", "

Now I need to convert message to a string

message.toString

An finally send the string via Telegram

sendTelegram("Jonas", ???)

How do I refer to the generated string in the Telegram command?

sendTelegram("Jonas", message.toString)
1 Like

Embarrassingly easy. Thanks a lot. Now it works!

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]

Any ideas what is wrong?