Appendix E Source code for logger.py Program
This appendix contains the source code for the logger.py file which is the main program which allows data logging.
#**********************************************************************************************************#
# Name: project.py #
# Description: Python program for the Data Logger #
# OS: Raspbian #
# Author: Scott Cairns #
# Notes: If any errors occur, ensure that installation guide has been followed properly #
#**********************************************************************************************************#
import os # used for terminal commands
import time # get timestamp to put on files and gpio
import datetime # used for timestamp
import RPi.GPIO as gpio # access to the GPIO
import spidev # for A/D converters
import multiprocessing # for threading/multiprocessing
import eeml # Markup language for COSM
from evdev import UInput, ecodes as e # Imitating a key event
import signal # for killing task
import termios # for key presses
import thread # for key presses thread
# reload spi drivers prevent spi failures
import subprocess
unload_spi = subprocess.Popen('sudo rmmod spi_bcm2708', shell=True, stdout=subprocess.PIPE)
start_spi = subprocess.Popen('sudo modprobe spi_bcm2708', shell=True, stdout=subprocess.PIPE)
# set up GPIO pins for 3 input (buttons) and 3 output (LEDs)
gpio.setmode(gpio.BCM) # Sets to BM mode which allows you to use visual pin number
gpio.setwarnings(False) # Sometimes 'channel already in use' warning shows, this disables it
redLED = 17 # variable to hold GPIO pin number for red LED
greenLED = 22 # GPIO pin number for green LED
yellowLED = 18 # GPIO pin number for yellow LED
gpio.setup(yellowLED, gpio.OUT) # GPIO ID for yellow LED (single image)
gpio.setup(redLED, gpio.OUT) # GPIO ID for red LED (stop recording)
gpio.setup(greenLED, gpio.OUT) # GPIO ID for green LED
gpio.setup(23, gpio.IN, pull_up_down=gpio.PUD_UP) # Set up button on Gertboard
gpio.setup(24, gpio.IN, pull_up_down=gpio.PUD_UP) # Set up button on Gertboard
gpio.setup(25, gpio.IN, pull_up_down=gpio.PUD_UP) # Set up button on Gertboard
# set all GPIO LEDs to be disabled by default except red
gpio.output(yellowLED, False)
gpio.output(redLED, True) # True to show it's not recording
gpio.output(greenLED, False)
# boolean to hold value if recording or not
recording = False
# variable to hold the recording method (store or stream)
method = "store"
# variable to hold the current detected key press
keyDetect = ''
# variable to hold whether we're looking for key presses or not
# (prevent it being called thousands of times)
keyRun = False
# Variables for using in console warning using ANSI escaped sequences
green = '\033[92m' # colour code for green
blue = '\033[94m' # colour code for blue
yellow = '\033[93m' # colour code for yellow
red = '\033[91m' # colour code for red
endc = '\033[0m' # end of console warning
# variable to hold the connection method (if remotely connected linux sets the 'DISPLAY'
# environment variable (such as localhost:10.0) if not then returns Empty variable ('None')
conVar = os.environ.get('DISPLAY')
if (conVar != None): # if connection method isn't 'None' ie remotely connected
conMethod = "remote" # set the method to 'remote'
print yellow + 'INFO: As you are remotely connected you can overwrite buttons' + endc # print info
print yellow + 'Key 1 = GPIO #23 (Record), Key 2 = GPIO #24 (Stop record), Key 3 = GPIO #25 (Capture)' + endc # print info
else: # if it's not remotely connected
conMethod = "local" # set method to 'local'
# COSM info - allows real time graphing over the internet
temp_api_key = 'R-TZoaYnOUWVnf-s_mLMHwL3LQeSAKxZY0tNdThiS0I2VT0g' # API key for COSM - Temp
temp_api_feed = 117505 # Feed ID for COSM - Temp
light_api_key = '_Ea8XluPFqxAv3IQF-Zhk_3VqBySAKwrZGxXT0hPQTREOD0g' # API key for COSM - Light
light_api_feed = 117510 # Feed ID for COSM - Light
temp_api_url = '/v2/feeds/{feednum}.xml'.format(feednum = temp_api_feed) # API URL
light_api_url = '/v2/feeds/{feednum}.xml'.format(feednum = light_api_feed) # API URL
# https://cosm.com/users/scottrpi - shows both feeds
# https://cosm.com/feeds/117510 - shows feed for light sensor
# https://cosm.com/feeds/117505 - shows feed for temp sensor
# Function to obtain the Analog to Digital conversion and return the value
def get_adc(channel):
spi = spidev.SpiDev() # use spidev python wrapper to communicate with SPI port
spi.open(0,0) # open channel 0 (ADC is on SPI channel 0 (CE0 / GPIO8)
#r = spi.xfer2([1,(3)<<4,0])
#adc_val = ((r[1]&3) << 8) + r[2]
if (channel == 0): # Channel 0 (actual AC1) is the Light sensor
# Send start bit, sgl/diff, odd/sign, MSBF
# channel = 0 sends 0000 0001 1000 0000 0000 0000
# channel = 1 sends 0000 0001 1100 0000 0000 0000
# sgl/diff = 1; odd/sign = channel; MSBF = 0
r = spi.xfer2([1,(2+channel)<<6,0])
# spi.xfer2 returns same number of 8 bit bytes
# as sent. In this case, 3 - 8 bit bytes are returned
# We must then parse out the correct 10 bit byte from
# the 24 bits returned. The following line discards
# all bits but the 10 data bits from the center of
# the last 2 bytes: XXXX XXXX - XXXX DDDD - DDDD DDXX
adc_val = ((r[1]&31) << 6) + (r[2] >> 2)
if (channel == 1): # Channel 1 (Actual AC0) is the Temp sensor
# Send start bit, sgl/diff, odd/sign, MSBF
# channel = 0 sends 0000 0001 1000 0000 0000 0000
# channel = 1 sends 0000 0001 1100 0000 0000 0000
# sgl/diff = 1; odd/sign = channel; MSBF = 0
r = spi.xfer2([1,(2+channel)<<6,0])
# spi.xfer2 returns same number of 8 bit bytes
# as sent. In this case, 3 - 8 bit bytes are returned
# We must then parse out the correct 10 bit byte from
# the 24 bits returned. The following line discards
# all bits but the 10 data bits from the center of
# the last 2 bytes: XXXX XXXX - XXXX DDDD - DDDD DDXX
adc_val = ((r[1]&31) << 6) + (r[2] >> 2)
return adc_val
# Function to request the digital value from temperature sensor and record it and graph it
def recordTemp():
try:
while 1: # Loop
timeInt = time.time() # Get current timestamp as an integer
timeStr = datetime.datetime.fromtimestamp(timeInt).strftime('%Y-%m-%d %H:%M:%S') # Convert it to a string
reading = get_adc(1) # get temp sensor digital reading
c_temp = (((reading * (3300.0 / 1024.0)) - 100.0) / 10.0) - 40.0 # Calculate temp in C
f_temp = ( c_temp * 9.0 / 5.0) + 32 # Calculate temp in F
c_temp = "%.1f" % c_temp # set C temp to 1 decimal place
f_temp = "%.1f" % f_temp # set F temp to 1 decimal place
print "Temperature: ", c_temp, "C", f_temp, "F" # print the temperatures into terminal
f = open('/home/pi/Desktop/logger/temperatures.txt', 'a') # Opens file 'temperatures' with method A for append
f.write("Time: " + timeStr + " Temperature: " + c_temp + "C " + f_temp + "F\n") # write temp to file
f.close() # close file once finished the write
#pac = eeml.Pachube(temp_api_url, temp_api_key) # using markup language to connect to COSM (previously Pachube)
#pac.update([eeml.Data(0, c_temp, unit=eeml.Celsius())]) # set the data and use Celsius unit
#pac.put() # do it
time.sleep(30) # Only record the temperatures every 30 seconds
return;
except KeyboardInterrupt: # Ctrl C on keyboard
os.system("reset") # Reset terminal to prevent hidden characters after program ends
gpio.cleanup() # reset GPIO ports if KeyboardInterrupt
# Function to request the digital value from light sensor and record it and graph it
def recordLight():
try:
while 1: # Loop
timeInt = time.time() # Get current timestamp as an integer
timeStr = datetime.datetime.fromtimestamp(timeInt).strftime('%Y-%m-%d %H:%M:%S') # Convert it to a string
reading = get_adc(0) # get light sensor digital reading
print "Light: ", reading # print light sensor reading
light = reading # set light to the return variable
light = "%.1f" % light # set light reading to 1 decimal place
f = open('/home/pi/Desktop/logger/light.txt', 'a') # Opens file 'light.txt' with method a for append
f.write("Time: " + timeStr + " Light: " + light + "\n") # write light info to file
f.close() # Close file once finished writing
#pac = eeml.Pachube(light_api_url, light_api_key) # using markup language to connect to COSM (previously Pachube)
#pac.update([eeml.Data(0, light)]) # run update, don't set a data type as it's just raw number
#pac.put() # do it
time.sleep(30) # Record light every 30 seconds
return;
except KeyboardInterrupt: # Ctrl C on keyboard
os.system("reset") # Reset terminal to prevent hidden characters after program ends
gpio.cleanup() # reset GPIO ports if KeyboardInterrupt
# Function to set variables required for recording and start the recording
def startRecord(method):
try:
# set up the variables required for the recording
serverConf = "/etc/ffserver.conf" # path to the ffserver config
logLevel = "error" # logging level, quiet, panic, fatal, error, warning, info, verbose, debug
rframeRate = "1" # Frame rate in Hz (for saving/recording)
sframeRate = "30" # Frame rate in Hz (for streaming)
frameSize = "352x288" # Set frame size in width x height (640x480, 352x288, 320x240, 176x144, 160x120 supported)
fileFormat = "video4linux2" # output format
inputName = "/dev/video0" # Input type, video0 for camera
outLocation = "http://localhost:8001/webcam.ffm" # Output location, over network
timeInt = time.time() # Get current timestamp as an integer
timeStr = datetime.datetime.fromtimestamp(timeInt).strftime('%Y-%m-%d~%H:%M:%S') # Convert it to a string
outLocationSave = "/home/pi/Desktop/logger/webcam-%s.avi" %timeStr
# Output location, in this case save under /home/pi/ with name 'webcam-timestamp.mpg'
vSync = "1" # video sync (make sure no duplicate frames etc
aSync = "1" # audio sync to fix wait at start
aFormat = "alsa" # format for audio: oss or alsa
aChannel = "1" # audio channels
aInput = "hw:1,0" # input device for audio (sound card #1 sub #0)
# Run terminal command to run ffserver and ffmpeg
#os.system("ffserver -f " + serverConf + " & ffmpeg -v " + logLevel +
# " -r " + frameRate + " -s " + frameSize + " -f " + fileFormat +
# " -i " + inputName + " -vcodec mjpeg -f alsa -ac 1 -ar 48000 -i hw:1,0 " + outLocation + " " + outLocationSave)
p = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE) # check open processes
out, err = p.communicate() # record them in 'out'
for line in out.splitlines(): # check each line
if 'ffmpeg' in line: # if ffmpeg is in it
print red + "Attempting to kill ffmpeg - May cause audio issues with last save" + endc
time.sleep(2) # show warning for 2 seconds before continuing
pid = int(line.split(None, 1)[0]) # get the process ID
os.kill(pid, signal.SIGKILL) # send kill signal
if (method == "store"): # if the selected method is to store it to the SD card (video + audio)
# print "Recording has started - Storing to SD card" # print appropriate message
# os.system("ffmpeg -v " + logLevel + " -r " + frameRate + " -s " + frameSize + " -f " + fileFormat +
# " -i " + inputName + " -f alsa -ac 1 -i hw:1,0 " + " " + outLocationSave)
os.system("ffmpeg -v " + logLevel + " -r " + rframeRate + " -s " + frameSize + " -f " + fileFormat +
" -vsync " + vSync + " -i " + inputName + " -f " + aFormat + " -ar 8000 -ac " + aChannel + " -async " +
aSync + " -i " + aInput + " " + outLocationSave)
elif (method == "stream"): # if the selected method is to strream it over network (video only)
print "Recording has started - Streaming across network" # print appropriate message
os.system("ffserver -f " + serverConf + " & ffmpeg -v " + logLevel +
" -r " + sframeRate + " -s " + frameSize + " -f " + fileFormat +
" -i " + inputName + " " + outLocation)
return;
except KeyboardInterrupt: # Ctrl C on keyboard
os.system("reset") # Reset terminal to prevent hidden characters after program ends
gpio.cleanup() # reset GPIO ports if KeyboardInterrupt
# Function to end the processes / recording
def stopRecord(recording):
try:
if (recording == True): # if recording has started
if (p1.is_alive()): # If process 1 (recordTemp) is alive
p1.terminate() # terminate process
if (p2.is_alive()): # If process 2 (recordLight) is alive
p2.terminate() # terminate process
if (p3.is_alive()): # If process 3 (startRecord) is alive
p3.terminate() # terminate process
# Imitate a key press, only works if running script from device (remote connection requires pressing q)
if (conMethod == "local"): # only do it if actually on the device
ui = UInput() # create new uinput
ui.write(e.EV_KEY,e.KEY_Q, 1) # key Q press down
ui.write(e.EV_KEY,e.KEY_Q, 0) # key Q press up
ui.syn() # do it
ui.close() # close uinput
print "Recorded finished" # print appropriate message
else: # if recording hasn't started
print "Recording has not yet started" # print appropriate message
return;
except KeyboardInterrupt: # Ctrl C on keyboard
os.system("reset") # Reset terminal to prevent hidden characters after program ends
gpio.cleanup() # reset GPIO ports if KeyboardInterrupt
# Function to set variables for an image and execute it
def singleImg():
frameSize = "352x288" # Set frame size in width x height
inputName = "/dev/video0" # Input type, video0 for camera
timeInt = time.time() # Get current timestamp as an integer
timeStr = datetime.datetime.fromtimestamp(timeInt).strftime('%Y-%m-%d~%H:%M:%S') # Convert it to a string
picOut = "/home/pi/Desktop/logger/image-%s.jpg" % timeStr # Output location for single image timestamp attached
p = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE) # check open processes
out, err = p.communicate() # record them in 'out'
for line in out.splitlines(): # check each line
if 'ffmpeg' in line: # if ffmpeg is in it
print red + "Attempting to kill ffmpeg - May cause audio issues with last save" + endc
time.sleep(2)
pid = int(line.split(None, 1)[0]) # get the process ID
os.kill(pid, signal.SIGKILL) # send kill signal
# Run terminal command which will take a single image and save it to picOut
os.system("fswebcam -r " + frameSize + " -d " + inputName + " " + picOut)
return;
def keyPress():
try:
term = open("/dev/tty", "r") # /dev/tty is the terminal for the current process, open in reading mode
fd = term.fileno() # returns integer file desciptor
old = termios.tcgetattr(fd) # return list containing tty attributes for file descriptor
new = termios.tcgetattr(fd) # return list containing tty attributes for file descriptor
new[3] &= ~termios.ICANON & ~termios.ECHO # canonical mode and echo input characters
termios.tcsetattr(fd, termios.TCSANOW, new) # sets the parameters associated with terminal, TCSANOW = immediately
key = None # current key isn't set
try: # try
key = os.read(fd, 1) # read key press
finally: # always execute this before leaving try statement
termios.tcsetattr(fd, termios.TCSAFLUSH, old) # set attrs again this time after all output is written to object has fd has been trasmitted
term.close() # close the file
global keyDetect # allow overwriting of variable outside function
global keyRun # allow overwriting of variable outside function
if key == '1': # if key pressed is '1'
keyDetect = '1' # then set it to 1
elif key == '2': # if key is '2'
keyDetect = '2' # set to 2
elif key == '3': # if key is '3'
keyDetect = '3' # set to 3
elif key == 'Q': # if key is 'Q'
print 'Please try again' # print message to try again
else: # otherwise
print 'Unusable key detected' # print to show unusuable key
keyRun = False # set it to false as it's not longer running
except KeyboardInterrupt: # Ctrl C on keyboard
os.system("reset") # Reset terminal to prevent hidden characters after program ends
gpio.cleanup() # reset GPIO ports if KeyboardInterrupt
try:
time.sleep(1) # prevent some misreading buttons during start up
print "Python Program loaded, press a button to continue"
while 1: # Infinite loop
if (((gpio.input(23) == False) or (keyDetect == '1')) and (recording == False)): # if record button is pressed and not recording, record
if (conMethod == "remote"): # give warning if remotely connected
print yellow + "INFO: As remotely connected you must press Q to end the webcam stream" + endc
print yellow + "WARNING: When ending the stream, it may take a few minutes depending on the length of recording" + endc
time.sleep(2) # give time to read the info
gpio.output(redLED, False) # disable the red LED
gpio.output(greenLED, True) # enable the LED to show it's recording (green)
recording = True # Set recording variable to true
p1 = multiprocessing.Process(target=recordTemp) # create a process with target as recordTemp
p2 = multiprocessing.Process(target=recordLight) # create a process with target as recordLight
p3 = multiprocessing.Process(target=startRecord, args=(method,)) # create a process with target as startRecord taking 'method' variable as param
p3.start() # Start process to startRecord
time.sleep(2) # wait a few seconds
p1.start() # Start process to recordTemp
time.sleep(3) # wait a few seconds
p2.start() # Start process to recordLight
keyDetect = '' # Reset keyDetect
time.sleep(1) # prevent button from being detected several times with 1 press
if (((gpio.input(23) == False) or (keyDetect == '1')) and (recording == True)): # if record button is pressed and recording, stop
gpio.output(greenLED, False) # disable the LED to show it's recording (green)
gpio.output(redLED, True) # enable the LED to show it's not recording (red)
stopRecord(recording); # Call the stopRecord function (pass recording boolean)
recording = False # Set recording variable to false
keyDetect = '' # reset keyDetect
time.sleep(1) # prevent button from being detected several times with 1 press
if (((gpio.input(24) == False) or (keyDetect == '2')) and (method == "store")): # if switch button is pressed and current method is store
method = "stream" # change method to stream
print "Recording method has changed to stream - The recording will be streamed across the network" # print appropriate message
keyDetect = '' # reset keyDetect
time.sleep(1) # sleep for 1 second
if (((gpio.input(24) == False) or (keyDetect == '2')) and (method == "stream")): # if switch button is pressed and current method is stream
method = "store" # change method to store
print "Recording method has changed to store - The recording will be saved to the SD card" # print appropriate message
keyDetect = '' # reset keyDetect
time.sleep(1) # sleep for 1 second
if ((gpio.input(25) == False) or (keyDetect == '3')):
gpio.output(yellowLED, True) # enable LED for short duration to show it's taken image (yellow)
time.sleep(1) # sleep for one second
gpio.output(yellowLED, False) # disable LED again
singleImg(); # Call the singleImg function
keyDetect = '' # reset keyDetect
time.sleep(1) # prevent button from being detected several times with 1 press
if ((conMethod == "remote") and (keyRun == False)): # if remotely connected start function to allow key detection
time.sleep(1) # allow time for q to be pressed
thread.start_new_thread( keyPress, ())
keyRun = True # set it to true so it won't call this statement each loop
except KeyboardInterrupt: # Ctrl C on keyboard
os.system("reset") # Reset terminal to prevent hidden characters after program ends
gpio.cleanup() # reset GPIO ports if KeyboardInterrupt
gpio.cleanup() # reset GPIO ports if exit
os.system("reset") # Reset terminal to prevent hidden characters after program ends