-
-
Notifications
You must be signed in to change notification settings - Fork 59
Tutorial Developer InstrumentSensorAlerts Client
This tutorial describes how you can instrument a Sensor Alert event in the AlertR system, process it and create a new sensor alert in the AlertR system if necessary. It is explained at a practical example that I used in my own network. For this to work, we assume you have set up the AlertR Server, the AlertR Sensor Client FIFO and the AlertR Alert Client Executer. Tutorials to set them up are linked.
Since version 0.700, there is a new way to instrument Sensor Alerts directly on the server which is explained in this tutorial.
The following gives you an example on how to instrument Sensor Alert events and process them externally with a script you wrote. This allows you to extend your alarm system to your needs in a flexible manner. The example shown in this tutorial is a simpler version of an instrumentation I deployed in my own network.
This is the same scenario used to explain the instrumentation on the server. Hence, you can easily compare both methods.
The scenario in this example is the following: I have multiple roof windows in my apartment (for example in my bedroom). When I open them after I woke up in order to get fresh air into the room, I often forget that I had opened them. In summer this is completely fine, however, in winter the room will cool down so much that it takes a lot of heating energy to get a mild temperature in it again. So, in order to remind me of the open window (and save energy in winter), I want a reminder that I have them still open. However, I only want this reminder in winter (or if we are really fancy, if the temperature outside is lower than a certain threshold). Since I have sensors on each window in my apartment already and a running AlertR system monitoring them, the question is: how can I easily instrument this without changing any code in AlertR?
AlertR already provides all necessary components to do this. As mentioned before, you need the AlertR server, AlertR Alert Client Executer and AlertR Sensor Client FIFO for it. The following graphic shows how the whole instrumentation works:
Some sensors in your network trigger a Sensor Alert for the Alert Level 1. This Sensor Alert is sent to the server in step 1). The server forwards this Sensor Alert to the AlertR Alert Client Executer configured to Alert Level 1 in step 2). The Alert Client Executer executes your instrumentation script and passes the Sensor Alert in json format as argument to this script in step 3). Next, your script can process the Sensor Alert in step 4). If your script decides the processing results in a Sensor Alert event, it triggers a Sensor Alert for Alert Level 2 in the AlertR Sensor Client FIFO in step 5). This client sends the Sensor Alert to the server in step 6) and the server forwards it to all clients registered for Alert Level 2 in step 7).
What does this mean in our scenario? Well, the bedroom window triggers the Sensor Alert in Alert Level 1 if it gets opened. The AlertR Alert Client Executer receives it and passes it to my instrumentation script. The instrumentation script checks if we do not have summer and waits 10 minutes. After the 10 minutes (and we are outside of summer), it triggers a Sensor Alert in the AlertR Sensor Client FIFO for Alert Level 2 which then notifies me (for example, sending a push notification, sending an email or playing a sound in the apartment).
So, how do we actually set everything up. Well, we use the alarm system context from our AlertR Server tutorial for the System Profiles and define new Alert Levels. We use the following configuration:
Alert Level | Name | Profiles | Functionality |
---|---|---|---|
1 | Window Opened | Activated, Deactivated | Used to signaling a open window. |
2 | Window Reminder | Activated, Deactivated | Used to remind about open window. |
Alert Level 1 is used to trigger our processing script that instruments the event that the window was opened. This Alert Level is member of both System Profiles and thus triggers always.
Alert Level 2 is used by our instrumentation script to be triggered. This can for example notify us about the open window by a push notification, playing a sound or whatever else we want. This Alert Level is member of both System Profiles and thus triggers always.
The Alert Level configuration of the server then looks something like this:
<alertLevels>
<alertLevel>
<general
level="1"
name="Window Opened"
triggerAlertTriggered="True"
triggerAlertNormal="True" />
<instrumentation
activated="False"
cmd="/path/to/script.py"
timeout="10" />
<profile>0</profile>
<profile>1</profile>
</alertLevel>
<alertLevel>
<general
level="2"
name="Window Reminder"
triggerAlertTriggered="True"
triggerAlertNormal="True" />
<instrumentation
activated="False"
cmd="/path/to/script.py"
timeout="10" />
<profile>0</profile>
<profile>1</profile>
</alertLevel>
</alertLevels>
The important part here is that the Alert Levels trigger always by being a member of all System Profiles. Otherwise, our instrumentation would only work if the alarm system is activated. Also, the Alert Level 1 has to be triggered for both states: triggered and normal. Otherwise, we only would detect if the window was opened or was closed, but not both cases. A complete AlertR Server example configuration can be seen in the tutorial in detail.
Next, we take a look at the AlertR Sensor Client Raspberry Pi configuration. An example configuration to read the switches of our window would look like this:
<sensors>
<sensor>
<general
id="0"
description="Window Bedroom"
alertDelay="0"
triggerAlert="True"
triggerAlertNormal="True" />
<alertLevel>1</alertLevel>
<gpio
type="polling"
gpioPin="24"
triggerState="1"
stateCounter="2" />
</sensor>
</sensors>
The setup of a Raspberry Pi with the AlertR sensor client is explained in detail in the AlertR Sensor Client Raspberry Pi tutorial. The important part in this configuration is that we trigger a Sensor Alert for both states: triggered and normal (caused by the options triggerAlert and triggerAlertNormal set to True). The Sensor Alert should be triggered for the Alert Level 1 which signalizes that the bedroom window was opened or closed.
Then we have to setup the AlertR Alert Client Executer to execute our instrumentation script:
<alerts>
<alert>
<general
id="0"
description="Open Window Processing" />
<alertLevel>1</alertLevel>
<executer
execute="/usr/bin/python3">
<triggered
activated="True">
<argument>/home/alertr/window_processing.py</argument>
<argument>$SENSORALERT$</argument>
</triggered>
<normal
activated="True">
<argument>/home/alertr/window_processing.py</argument>
<argument>$SENSORALERT$</argument>
</normal>
<off
activated="False">
</off>
</executer>
</alert>
</alerts>
Everything used here is explained in detail in the AlertR Alert Client Executer tutorial. The important part shortly summarized: When a Sensor Alert with Alert Level 1 is received (for state "triggered" as well as state "normal"), our Python3 instrumentation script is executed with the Sensor Alert in json format as argument (caused by the keyword $SENSORALERT$
). Of course, your instrumentation script can use any programming language you like. I just really like Python ;). When the alarm system is deactivated, we do not execute anything.
The last configuration step concerns the AlertR Sensor Client FIFO. The important configuration part looks like the following:
<sensors>
<sensor>
<general
id="0"
description="Processed Open Window"
alertDelay="0"
triggerAlert="True"
triggerAlertNormal="True" />
<alertLevel>2</alertLevel>
<fifo
umask="0000"
fifoFile="/home/alertr/processed_window.fifo"
dataType="0" />
</sensor>
</sensors>
The sensor triggers a Sensor Alert event for the Alert Level 2 and places the FIFO file under /home/alertr/processed_window.fifo
. Everything used here is explained in detail in the AlertR Sensor Client FIFO tutorial.
The last part missing is our instrumentation script. However, before we can write it we should take a look how the Sensor Alert argument looks like that is passed by the keyword $SENSORALERT$
in the AlertR Alert Client Executer. It looks like the following json string for a triggered Sensor Alert (window opened):
{"description": "Window Bedroom", "dataType": 0, "hasLatestData": false, "instrumentation_active": false, "instrumentation_cmd": null, "instrumentation_timeout": null, "sensorId": 48, "hasOptionalData": false, "data": {}, "optionalData": null, "alertLevels": [1], "state": 1, "changeState": true}
And it looks like the following for a normal Sensor Alert (window closed):
{"description": "Window Bedroom", "dataType": 0, "hasLatestData": false, "instrumentation_active": false, "instrumentation_cmd": null, "instrumentation_timeout": null, "sensorId": 48, "hasOptionalData": false, "data": {}, "optionalData": null, "alertLevels": [1], "state": 0, "changeState": true}
The difference between both is the state signaled. The triggered has the state 1 and the normal the state 0. A detailed explanation about each field in the message is given in the protocol documentation.
Now we can take a look at our script. It is a simplified version of the one I am using in my setting at home:
#!/usr/bin/python3
import sys
import json
import time
import fcntl
import datetime
state_file = "/tmp/alertr_filter_bedroom_state.txt"
wait_time = 600
alertr_fifo_file = "/home/alertr/processed_window.fifo"
def load_state():
old_state = 0
try:
with open(state_file, 'r') as fp:
old_state = int(fp.read())
except:
pass
return old_state
def write_state(curr_state):
with open(state_file, 'w') as fp:
fcntl.lockf(fp, fcntl.LOCK_EX)
fp.write("%d" % curr_state)
fcntl.lockf(fp, fcntl.LOCK_UN)
def wait_and_trigger():
# Wait 10 minutes before triggering sensor alert.
for _ in range(wait_time):
time.sleep(1)
# Check if the window was closed during our wait time.
curr_state = load_state()
if curr_state == 0:
return
result = dict()
result["message"] = "sensoralert"
payload = dict()
payload["state"] = 1
payload["hasOptionalData"] = False
payload["optionalData"] = {}
payload["dataType"] = 0
payload["data"] = {}
payload["hasLatestData"] = False
payload["changeState"] = False
result["payload"] = payload
with open(alertr_fifo_file, 'w') as fp:
fp.write(json.dumps(result))
def main():
old_state = load_state()
data = json.loads(sys.argv[1])
state = int(data["state"])
# Check reminder active
# (deactivated approximately from June to August).
curr_week_no = datetime.date.today().isocalendar()[1]
if 23 < curr_week_no < 37:
return
# Only consider state changes.
if old_state == state:
return
write_state(state)
# Window opened, trigger our reminder.
if state == 1:
wait_and_trigger()
if __name__ == '__main__':
# Check if we have an argument (the $SENSORALERT$ json string),
# otherwise do nothing.
if len(sys.argv) == 2:
main()
What does this script do. Well, first of all it checks if it has the $SENSORALERT$
json string argument. Otherwise, it does simply nothing. If it has, it checks if we have summer or not. Only outside of summer we want to get a reminder. Since the script tracks the state of the window, it checks if we have a new state (open or close). If the window was opened, then we start a counter that reminds us in 10 minutes. The protocol used to signal the FIFO client is described in detail in the protocol documentation.
The script is only a simple example script. The script I use has to distinguish between two windows in the bedroom and also checks if the temperature is under 18 degree Celsius. However, with this instrumentation capability of AlertR you can do anything. The only limitation is your imagination ;).
If you experience problems, please check the log file first. If it is not helpful, change the log level to DEBUG and check again. If no error can be seen, please start the AlertR client manually and check if an error occurs that is not printed into the log file. This can be done by just executing the AlertR client as the user that it normally runs with.
alertr@towel:~/AlertRInstance$ ./alertRclient.py
Also, let your script write the received $SENSORALERT$
argument into a file to check if everything is working fine here.
If you still have problems and do not know how to solve them, you can ask on the community page on reddit or you can use the Github Issues.