Aug 21 2013 0

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 1

# 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 1

<?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). 1

#!/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 1

<?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

  1. Executing modprobe w1-* looked not to be working (from ds18b20.py)
  2. 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. 1

# /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’.


Previous post
Improving MySQL query performance Performance for the charts pages was getting worse and worse the more data was collected from all the sensors connected to the Raspberry Pi. It was
Next post
DNSSEC Issues I recently noticed that when I was using Google Public DNS I could not reach my own domain at littlegemsoftware.com. Using other DNS services like
This blog is powered by Blot