Thursday, 5 March 2015

Raspberry Pi GPIO Sensors Part 2: Record

The following post is a section of the book 'Raspberry Pi: Measure, Record, Explore'.  The entire book can be downloaded in pdf format for free from Leanpub or you can read it online here.
Since this post is a snapshot in time. I recommend that you download a copy of the book which is updated frequently to improve and expand the content.
---------------------------------------
This is the second of three posts working through a project looking at Measuring Recording and Exploring information via the GPIO pins on the Raspberry Pi. The first can be found here.

Record

To record the data that we are measuring we will use a Python program that uses our sensor like a trigger and writes an identifier for the sensor into our MySQL database. At the same time a time stamp will be added automatically. You may be wondering why we’re not recording the value of the sensor (0 or 1) and that’s a valid question. What we are going to use our sensor for is to determine when it has been triggered. For all intents and purposes, we can make the assumption that our sensor represents a device that can be in one of two states. We are going to be interested when it changes state, not when it is steady. In particular we are going to be interested in when it changes from low to high (0 to 1) and therefore represents a ‘rising’ signal.
for this project to work, our Python script has to run continuously and will only write to our database when triggered.

Database preparation

First we will set up our database table that will store our data.
Using the phpMyAdmin web interface that we set up, log on using the administrator (root) account and select the ‘measurements’ database that we created as part of the initial set-up.
Create the MySQL Table
Enter in the name of the table and the number of columns that we are going to use for our measured values. In the screenshot above we can see that the name of the table is ‘events’ and the number of columns is ‘2’.
We will use two columns so that we can store an event name and the time it was recorded.
Once we click on ‘Go’ we are presented with a list of options to configure our table’s columns. Don’t be intimidated by the number of options that are presented, we are going to keep the process as simple as practical.
For the first column we can enter the name of the ‘Column’ as ‘dtg’ (short for date time group) the ‘Type’ as ‘TIMESTAMP’ and the ‘Default’ value as ‘CURRENT_TIMESTAMP’. For the second column we will enter the name ‘event’ and the type is ‘VARCHAR’ with a ‘Length/Values’ of 30.
Configure the MySQL Table Columns
Scroll down a little and click on the ‘Save’ button and we’re done.
Save the MySQL Table Columns
Why did we choose those particular settings for our table?
Our ‘dtg’ column needs to store a value of time that includes the date and the time, so the advantage of selecting TIMESTAMP in this case is that we can select the default value to be the current time which means that when we write our data to the table we only need to write the ‘event’ name and the ‘dtg’ will be entered automatically for us. The disadvantage of using ‘TIMESTAMP’ is that it has a more limited range than DATETIME. TIMESTAMP can only have a range between ‘1970-01-01 00:00:01’ to ‘2038-01-19 03:14:07’.
The event names are simply descriptions of the type of events we want to capture, so we will use a variable type ‘VARCHAR’ which is for characters. We can also specify the maximum length of the information stored in the database to make things a little more efficient. In theory we could use the ‘CHAR’ type which is more efficient, but in this instance I prefer ‘VARCHAR’ which will allow the length of the recorded information to be flexible.

Record the events

The following Python code (which is based on the code that is part of the great blog series on FIRSIM) is a script which allows us to check the state of a sensor and write an entry to our database when an event occurs.
The full code can be found in the code samples bundled with this book (events.py).
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Import the python libraries
import RPi.GPIO as GPIO
import logging
import MySQLdb as mdb

logging.basicConfig(filename='/home/event_error.log',
  level=logging.DEBUG,
  format='%(asctime)s %(levelname)s %(name)s %(message)s')
logger=logging.getLogger(__name__)

# Function called when GPIO.RISING
def storeFunction(channel):
  print("Signal detected")
  
  con = mdb.connect('localhost', \
                    'pi_insert', \
                    'xxxxxxxxxx', \
                    'measurements');
  
  try:
    cur = con.cursor()
    cur.execute("""INSERT INTO events(event) VALUES(%s)""", ('catflap'))
    con.commit()
    
  except mdb.Error, e:
    logger.error(e)
    
  finally:    
    if con:    
      con.close()

# use the BCM GPIO numbering
GPIO.setmode(GPIO.BCM)

# Definie the BCM-PIN number
GPIO_PIR = 2

print "Sensor event monitoring (CTRL-C to exit)"

# Define pin as input with standard high signaal
GPIO.setup(GPIO_PIR, GPIO.IN, pull_up_down = GPIO.PUD_UP)

# Register event GPIO.RISING and corresponding function
GPIO.add_event_detect(GPIO_PIR, \
                      GPIO.RISING, \
                      callback=storeFunction, \
                      bouncetime=1000)

try:
  # Loop till CTRL-C
  while True :
    # Read status
    Current_State = GPIO.input(GPIO_PIR)

except KeyboardInterrupt:
  # Reset the GPIO settings
  GPIO.cleanup()
This script can be saved in our home directory (/home/pi) and can be run by typing;
sudo python events.py
The observant amongst you will notice that we need to run the program as the superuser (by invoking sudo before the command to runevents.py). This is because the GPIO library requires the accessing of the GPIO pins to be done by the superuser.
Once the command is run the pi will generate a warning to let us know that there is a pull up resister fitted to channel 2. That’s fine. From here if we move our magnet towards and away from the sensor we should see the Signal detected notification appear in the terminal per below.
pi@raspberrypi ~ $ sudo python event.py
Sensor event monitoring (CTRL-C to exit)
event.py:23: RuntimeWarning: A physical pull up resistor is fitted on this ch\
annel!
  GPIO.setup(GPIO_PIR, GPIO.IN, pull_up_down = GPIO.PUD_UP)
Signal detected
Signal detected
We should then be able to check our MySQL database and see an entry for each time that the sensor triggered.
Save the MySQL Table Columns
Code Explanation
The script starts by importing the modules that it’s going to use for the process of reading and recording the measurements;
import RPi.GPIO as GPIO
import logging
import MySQLdb as mdb
Python code in one module gains access to the code in another module by the process of importing it. The import statement invokes the process and combines two operations; it searches for the named module, then it binds the results of that search to a name in the local scope.
Then the code sets up the logging module. We are going to use the basicConfig() function to set up the default handler so that any debug messages are written to the file /home/pi/event_error.log.
logging.basicConfig(filename='/home/event_error.log',
  level=logging.DEBUG,
  format='%(asctime)s %(levelname)s %(name)s %(message)s')
logger=logging.getLogger(__name__)
In the following function where we are getting our readings or writing to the database we write to the log file if there is an error.
Which brings us to our function storeFunction that will write information to our database when called later;
def storeFunction(channel):
  print("Signal detected")
  
  con = mdb.connect('localhost', \
                    'pi_insert', \
                    'xxxxxxxxxx', \
                    'measurements');
  
  try:
    cur = con.cursor()
    cur.execute("""INSERT INTO events(event) VALUES(%s)""", ('catflap'))
    con.commit()
    
  except mdb.Error, e:
    logger.error(e)
    
  finally:    
    if con:    
      con.close()
This is very much a rinse and repeat of the function found in the single temperature project. We configure our connection details, connect and write the name of this particular sensor (‘catflap’) into the database (Remember that when we store an event name into the database the timestamp dtg is added to the record automatically.). We then have some house-keeping code that will log any errors and then close the connection to the database.
The program can then issues the GPIO commands that start the interface to the sensor and get it configured (the code below has been condensed for clarity);
GPIO.setmode(GPIO.BCM)
GPIO_PIR = 2
GPIO.setup(GPIO_PIR, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.add_event_detect(GPIO_PIR, \
                      GPIO.RISING, \
                      callback=storeFunction, \
                      bouncetime=1000)
The GPIO Python module was developed and is maintained by Ben Croston. You can find a wealth of information on its usage at theofficial wiki site.
GPIO.setmode allows us to tell which convention of numbering the IO pins on the Raspberry Pi we will use when we say that we’re going to read a value off one of them. Observant Pi users will have noticed that the GPIO numbers don’t match the pin numbers. Believe it or not there is a good reason for this and in the words of the good folks from raspberrypi.org;
While there are good reasons for software engineers to use the BCM numbering system (the GPIO pins can do more than just simple input and output), most beginners find the human readable numbering system more useful. Counting down the pins is simple, and you don’t need a reference or have to remember which is which. Take your pick though; as long as you use the same scheme within a program then all will be well.
In our case we are using the BCM numbering (GPIO.setmode(GPIO.BCM)).
We then assign the GPIO channel as GPIO 2 to a variable (GPIO_PIR = 2).
Then we configure how we are going to read the GPIO channel with GPIO.setup. We specify the channel with GPIO_PIR, whether the channel will be an input or an output (GPIO.IN) and we configure the Broadcom SCO to use a pull up resistor in software (pull_up_down=GPIO.PUD_UP).
We then configure the channel to look for a specific type of event (GPIO.add_event_detect). We specify the channel (GPIO_PIR), we want to look for a rising edge of a change in state (going from 0 to 1), we define a callback function (callback=storeFunction) (to store our event) which is a function that will be run simultaneously with our main program (in a separate thread) and then we set a value of bouncetime (in this case (bouncetime=1000) milliseconds) which reduces the occurrence of false events in the case of the cat flap swinging when closing.
Then the code executes an ‘endless’ loop that asks itself “Is TrueTrue?”. While it is the program constantly reads the state of the GPIO channel.
try:
  # Loop till CTRL-C
  while True :
    # Read status
    Current_State = GPIO.input(GPIO_PIR)

except KeyboardInterrupt:
  # Reset the GPIO settings
  GPIO.cleanup()
Ultimately True stops being True when the program receives a ‘break’ which can occur with a ‘ctrl-c’ keypress. This results in any of the GPIO ports that have been used in the program being set back to input mode. This occurs because it is safer (for the equipment) to leave the ports as inputs which removes any extraneous voltages.

Start the code automatically at boot

While we can run our script easily from the command line, this is not going to be convenient when we deploy our cat flap activity logger. The alternative is to automatically start the script using rc.local in a similar way that we did with ‘tightvncserver’ in our initial set-up.
We will add the following command into rc.local;
python /home/pi/events.py
This command looks slightly different for the way that we have been running the script so far (sudo python events.py). This is because we do not need to use sudo (since rc.local runs as as the root user already, and we need to specify the full path to the script (/home/pi/events.py) as there is no environment set up in a home directory or similar (i.e. we’re not starting from /home/pi/).
To do this we will edit the rc.local file with the following command;
sudo nano /etc/rc.local
Add in our lines so that the file looks like the following;
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

# Start tightvncserver
su - pi -c '/usr/bin/tightvncserver :1'

# Start the event monitoring script 
python /home/pi/events.py

exit 0
(We can also add our own comment into the file to let future readers know what’s going on)
That should be it. We should now be able to test that the service starts when the Pi boots by typing in;
sudo reboot
Then check our MySQL database to see the events increment as we wave our magnet in front of the Hall effect sensor.
Nice job! We’re measuring and recording a change in a magnetic field!

The post above (and heaps of other stuff) is in the book 'Raspberry Pi: Measure, Record, Explore' that can be downloaded for free (or donate if you really want to :-)).