Integrating Pushover into Raspberry Pi Project
In my previous post I talked about using the Pushover service for sending notifications from the Raspberry Pi to my mobile devices. Now it is time to actually define some of these events.
Event | Condition | Interval |
---|---|---|
New Record | New highest/lowest temperature detected | 5 min |
No Data Logged | No new data since at least 5 minutes | 5 min |
Temperature Difference | Rise/Drop >= 1°C | 30 sec |
In order to impact the collecting process not too much we will perform the check for high/low values in a separate process. Also the frequency for these events will be different from the collecting process. The collecting process is performed roughly every 30 seconds, but for the events it would be fine to check it every 5 minutes or so.
Setup
Since I am providing all the code on GitHub as well I didn’t want to store my APP_TOKEN and USER_KEY for all to see. So instead I made a change to the file /etc/environment
and added two environment variables to it, PUSHOVER_APP_TOKEN
and PUSHOVER_USER_KEY
. These environment variables are (after reboot of the Raspberry Pi) available system wide.
Content of /etc/environment
, the values presented here are random values and only serve as an example. Replace with values based on information provided by Pushover
PUSHOVER_APP_TOKEN=ilIAgHYJKLi2Sc8iICUoRekFSILzYF
PUSHOVER_USER_KEY=P5zZCVhaKTl8yPHumZgNehjACNjoZO
I noticed that when you are using the sudo command these environment variables are not available. To inherent the ‘current’ environment variables into the sudo-session use the -E option of the sudo command. Like so
sudo -E <command to be executed>
So to list the environment variables (using the env
command) would look like this
sudo -E env
Libraries for Python and PHP
Python
# pushover.py
import httplib
import urllib
import os
APP_TOKEN = os.environ['PUSHOVER_APP_TOKEN']
USER_KEY = os.environ['PUSHOVER_USER_KEY']
def send_notification(msg,title,url,url_title):
conn = httplib.HTTPSConnection("api.pushover.net:443")
conn.request("POST", "/1/messages.json",
urllib.urlencode({
"token": APP_TOKEN,
"user": USER_KEY,
"title": title,
"message": msg,
"url": url,
"url_title": url_title
}), { "Content-type": "application/x-www-form-urlencoded" })
conn.getresponse()
PHP
<?php
/* pushover.php */
define( 'APP_TOKEN', getenv( 'PUSHOVER_APP_TOKEN' ) );
define( 'USER_KEY', getenv( 'PUSHOVER_USER_KEY' ) );
function send_notification( $msg, $title, $url, $url_title ) {
curl_setopt_array( $ch = curl_init()
, array( CURLOPT_URL => "https://api.pushover.net/1/messages.json"
, CURLOPT_RETURNTRANSFER => true
, CURLOPT_POSTFIELDS => array( "token" => APP_TOKEN
, "user" => USER_KEY
, "title" => $title
, "message" => $msg
, "url" => $url
, "url_title" => $url_title
)
)
);
curl_exec( $ch );
curl_close( $ch );
}
?>
From the code above we can see that it is using cURL, so when you get an error similar to the following
PHP Fatal error: Call to undefined function curl_setopt_array() in /home/pi/raspberrypi/pushover.php on line 8
cURL support for PHP5 has not been installed. You can install it using the following command
sudo apt-get install php5-curl
Table for tracking high/low values
To keep track of the last known (reported) high/low values we need an additional table within the MySQL database
create table sensor_high_low(
sensor_id integer not null
, min_value real
, max_value real
, primary key (sensor_id)
, constraint foreign key (sensor_id) references sensors(sensor_id)
);
Modified version of collection script
Added code to send out notifications if there was a drop or rise in temperature of 1°C or higher. I will be using the DS18B20 sensor (sensor_id = 1
) for the data checks. Changes are located at lines 9-10 (import of modules), 21-22 (initialize Rules) and 98-106 (execute rules and send notification via Pushover).
#!/usr/bin/env python
import glob
import time
import re
import RPi.GPIO as GPIO
import subprocess
import MySQLdb as sql
import pushover
from Rules import Rules
# Determine location of first found DS18B20 sensor
base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob( base_dir + '28*' )[0]
device_file = device_folder + '/w1_slave'
# Configure GPIO
GPIO.setmode( GPIO.BCM )
GPIO.setwarnings( False )
# Setup Rules
rf = Rules( 'localhost', 'rpi', 'rpi', 'sensordata' )
# Log data to the MySQL database
def logData( id, value):
try:
# Insert new data
cur.execute( "insert into sensor_data(sensor_id,value) values( {0}, {1} )".format( id, value ) )
# Save changes
con.commit()
return
except:
if con:
con.rollback()
return
# Controle state for LED pin (turn on/off the connected LED)
def ledMode( PiPin, mode ):
GPIO.setup( PiPin, GPIO.OUT )
GPIO.output( PiPin, mode )
return
# Read data from the raw device
def read_temp_raw():
f = open(device_file, 'r')
lines = f.readlines()
f.close()
return lines
# Determine temperature and humidity from the DHT22/AM2302 sensor
def read_dht22( PiPin ):
output = subprocess.check_output(["/home/pi/raspberrypi/Adafruit_DHT", "2302", str(PiPin)])
matches = re.search("Temp =\s+([0-9.]+)", output)
if ( matches ):
logData( 2, float(matches.group(1)) )
matches = re.search("Hum =\s+([0-9.]+)", output)
if ( matches ):
logData( 3, float(matches.group(1)) )
return
# Determine temperature from the DS18B20 sensor
def read_temp():
lines = read_temp_raw()
while lines[0].strip()[-3:] != 'YES':
time.sleep( 0.2 )
lines = read_temp_raw()
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float( temp_string ) / 1000.0
return temp_c
# Turn off all LEDs
ledMode( 14, GPIO.LOW )
ledMode( 15, GPIO.LOW )
ledMode( 18, GPIO.LOW )
while True:
# Connect to MySQL database
con = sql.connect( host = "localhost", user = "rpi", passwd = "rpi", db = "sensordata" )
cur = con.cursor()
# Read DS18B20 (Temperature)
temp_c = read_temp()
logData( 1, temp_c )
# Update LED based on temperature
ledMode( 14, GPIO.HIGH if temp_c < 27 else GPIO.LOW )
ledMode( 15, GPIO.HIGH if temp_c >= 27 and temp_c < 29 else GPIO.LOW )
ledMode( 18, GPIO.HIGH if temp_c >= 29 else GPIO.LOW )
# Read DHT22 (Temperature, Humidity)
read_dht22( 22 )
# Close MySQL connection
con.close()
# Run rule
rf.run_rule( 1 )
# Send notification
if ( rf.getOutput() is not None ):
pushover.send_notification( rf.getDescription(), \
rf.getOutput(), \
"http://littlegemsoftware.com:314/chart-sensor.html", \
"RPi Sensor Data" )
# Wait seconds for next collection
time.sleep( 30 )
Script for high/low and values
The following script will run the rules for
<?php
/* check_rules.php */
require_once( 'Rules.php' );
require_once( 'pushover.php' );
define( 'URL', 'http://littlegemsoftware.com:314/chart-sensor.html' );
define( 'URL_TITLE', 'RPi Sensor Data' );
/* Setup Rules */
$rf = new Rules( 'localhost', 'rpi', 'rpi', 'sensordata' );
/* Run rule - New Record */
$rf->run_rule( 2 );
if ( strlen( $rf->getOutput() ) > 0 ) {
send_notification( $rf->getDescription(), $rf->getOutput(), URL, URL_TITLE );
}
/* Run rule - No Data Logged */
$rf->run_rule( 3 );
if ( strlen( $rf->getOutput() ) > 0 ) {
send_notification( $rf->getDescription(), $rf->getOutput(), URL, URL_TITLE );
}
?>
Job to trigger high/low script
To trigger the script for the high/low values every 5 minutes we will been using a cron job. Issue the following command to edit the crontab jobs
sudo crontab -e
The syntax for a job in the crontab is as follows
# +---------------- minute (0 - 59)
# | +------------- hour (0 - 23)
# | | +---------- day of month (1 - 31)
# | | | +------- month (1 - 12)
# | | | | +---- day of week (0 - 6) (Sunday=0 or 7)
# | | | | |
* * * * * command to be executed
To get crontab to run the task every 5 minutes we enter the following command
*/5 * * * * php /home/pi/raspberrypi/check_rules.php
Issues encountered
Initially I had some issues getting the collecting python code to execute properly from the crontab. The problem when starting jobs from crontab it is very hard to figure out what was going. In my case I had (at least) two issues
- Executing
modprobe w1-*
looked not to be working (fromds18b20.py
) - crontab jobs were started before all other processes had been loaded (one in particular being MySQL)
To solve the first issue I removed the code to load the one-wire modules and added them to the file /etc/modules
so they get loaded automatically when the system boots.
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
# Parameters can be specified after the module name.
snd-bcm2835
w1-gpio
w1-therm
The second one is solved by adding a sleep when the collecting process is started. Before the python jobs is executed the system waits 60 seconds, which is enough time to give all the other processes time to finish starting up
@reboot sleep 60 && python /home/pi/raspberrypi/ds18b20.py
Rule definitions and Rules class
The definition of the rules and the Rules class has been detailed in a later post titled ‘Rule Definitions’.