Arduino Monitoring System

After creating a PogoPlug Linux server using ArchLinux ARM and completing some Arduino Uno projects, I combined the two exciting projects creating a monitor for my home and aquarium. The PogoPlug Linux server, Arduino, code and sensors can be used for any home automation project. Using the Arduino Uno and a waterproof temperature sensor, it would send the data to the PogoPlug and harness the advanced Linux operating system to log, analyze, alert and report on the data received from the Arduino Uno. I chose the PogoPlug Linux server over the Arduino Ethernet Shield and Data Logging Shield for both cost and functionality.

The current version includes temperature monitoring of the fishtank and other home automation features. The application can send reports and alerts via email and text. I incorporated the Arduino Uno into the case of the PogoPlug so that I had one compact server that runs on only 4 watts of power. This project requires basic knowledge of microcontrollers and good working knowledge of Linux and programming.

If you find this project documentation useful or have any questions on the content, please use the Contact link at the top to send me a note.

Project Goal: Home Monitoring System using an Arduino and a PogoPlug ArchLinux Server

Features in Version 13.03.24 (Version History)

  • Integration of PogoPlug ArchLinuxARM, Arduino Uno
  • Support for Temperature Sensors (DS18B20)
  • Email and Text Alerts for High and Low Temperatures
  • Bidirectional communication between PogoPlug Linux server and Arduino
  • Web access from Apple iPhone and iPad
  • Data Logging and Analysis using RRDTool
  • Integration with Philips hue LED Zigbee Lightbulb system
  • Webcam Support
  • Email Support (POP and SMTP)
  • Weather Support
  • FTP Support for uploading graphs
  • Reporting Module
  • Custom Configuration Options

Project Parts 

  • Required:
    • PogoPlug (POGO-E02 recommended) - Retails for $99. I found V2s for $30 US or less online.
    • 2-8gb USB Flash Drive - $5-10 US
    • Arduino Uno R2 or R3 - $25-30 US
    • Temperature Sensor Waterproof DS18B20 - $10-12 US
    • Jumper Wires - $5-10 US
  • Optional:
    • 16x2 or 20x4 LCD - $10-15 US
    • Creative Webcam VF-0050 - $40 US
    • Philips hue LED lightbulbs - $60 each US
  • Tools:
    • Breadboard and Jumper Wires
    • Soldering Iron
    • Screwdriver

Project Build

Arduino

My Arduino development platform is Windows 7 since the PogoPlug does not have a monitor or keyboard. There are postings online on how to add a video card, however I found it easier to use a PC for the development and deploy it to the Arduino when the code is ready for production.

I am going to start the project documentation from the point that you have a PC or laptop running the Arduino IDE v1.0.1 and have successfully connected the Arduino Uno to your PC via USB. You should test a few sketches before proceeding. If you are new to Arduino, there are many resources online. I referenced several websites and blogs creating this project. Check out the Arduino site for a lot of material at http://www.arduino.cc. Companies such as Adafruit Industries http://www.adafruit.com/ and Sparkfun http://www.sparkfun.com/ have great starter kits and online tutorials. I purchased a starter kit and went through several online tutorials from both sites. I highly recommend Adafruit Industries. Limor Fried has a lot of great online tutorials and is committed to providing online educational resources for electronics.

So let's get started with the Arduino configuration. As you see in the picture I have an LCD connected to the Arduino board. This is not required for the project. I added the LCD so that could work on future projects and have the ability to write status messages to the LCD when the PC was not connected. My Arduino sketch will contain the code for the LCD, however it will be commented out with a DEFINE. You can always enable by changing the DEFINE to a 1 it if you want to add an LCD. The latest version of the Arduino and Python applications are coded for a 20x4 LCD. You will have to edit the code to support a 16x2 LCD or disable the functionality. I also posted a tutorial on how to build your own Arduino LCD Shield.

I updated my LCD Library with Francisco Malpartida's LCD Library:

https://bitbucket.org/fmalpartida/new-liquidcrystal/

The DS18B20 temperature sensor is a digital sensor that is more accurate that than analog version found in most starter kits. It is a little more complicated to interface, but gives much better results. Thankfully some great programmers created libraries for interfacing electronics. To interface this sensor to the Arduino Uno I used the Dallas Temperature Control Library and OneWire Library. The source for the Dallas Temperature Control Library can be found at http://www.milesburton.com/Dallas_Temperature_Control_Library. I also updated my OneWire library using the documentation found at Paul Stoffregen's site at http://www.pjrc.com/teensy/td_libs_OneWire.html.

Once you have installed both the Dallas Temperature Control Library and the updated OneWire Library using the online documentation we have to connect the temperature sensor.

The temperature sensor has 3 wires. The data line is the white wire on the sensor from Adafruit. I used the documentation on Dallas Temperature site above to connect the black and red wires to the ground and the white wire to the 5V power supply and data pin #6 on the Arduino through the resistor. 

After you have connected the resistor to the power supply and feed the data line to the Arduino digital pin #6, let's load the Arduino sketch.

Before you load the script below, please make sure you have selected the correct board type, in this case the Arduino Uno, and port (COMx) in the IDE.

Here is my Arduino sketch

/*
 Code:           Sketch_BBArduino
 Version:        13.03.24
 Author:         Benjamin Bordonaro
 Library Credits:
 Library originally added 18 Apr 2008 by David A. Mellis
 Library modified 5 Jul 2009 by Limor Fried (http://www.ladyada.net)
 Based on example added 9 Jul 2009 by Tom Igoe
 Modified 22 Nov 2010 by Tom Igoe
 Circuit Layout:
 * DS18B20 (temperature) data to digital pin 6
 * --- LCD PINS ---
 * Set LCD Pins (RS, EN, D4, D5, D6, D7) 
 * LiquidCrystal lcd(7, 8, 2, 3, 4, 9);
 * X10 Pins - For future use
 * zcPin 11
 * dataPin 10
 */

#include <LiquidCrystal.h> 
#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into pin 6 on the Arduino
#define ONE_WIRE_BUS 6

// Define X10 Pins - For later use
#define zcPin 11
#define dataPin 10

// Global Variables to turn on and off hardware control
#define LCD_ENABLE 1    // LCD
#define LED_ENABLE 0    // LED
#define EML_ENABLE 1    // Email Checking and Display
#define TMP_ENABLE 1    // Temperature Sensor Display

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

// Set LCD Pins (RS, EN, D4, D5, D6, D7) 
LiquidCrystal lcd(7, 8, 2, 3, 4, 9);

DeviceAddress ts1, ts2, ts3, ts4;
// function to print a device address
void printAddress(DeviceAddress deviceAddress)
{
  for (uint8_t i = 0; i < 8; i++)
  {
    // zero pad the address if necessary
    if (deviceAddress[i] < 8) lcd.print("0");
    lcd.print(deviceAddress[i]);
  }
}

void serialReader(){
  int makeSerialStringPosition;
  int inByte;
  String serialReadString;
  String stringOne;
  const int terminatingChar = 13; //Terminate lines with CR

  inByte = Serial.read();
  makeSerialStringPosition=0;

  if (inByte > 0 && inByte != terminatingChar) { //If we see data (inByte > 0) and that data isn't a carriage return
    delay(100); //Allow serial data time to collect (I think. All I know is it doesn't work without this.)

    while (inByte != terminatingChar && Serial.available() > 0){ // As long as EOL not found and there's more to read, keep reading
      serialReadString[makeSerialStringPosition] = inByte; // Save the data in a character array

      makeSerialStringPosition++; //Increment position in array
      //if (inByte > 0) Serial.println(inByte); // Debug line that prints the charcodes one per line for everything recieved over serial
      inByte = Serial.read(); // Read next byte
    }

    if (inByte == terminatingChar) //If we terminated properly
    {
      serialReadString[makeSerialStringPosition] = 0; //Null terminate the serialReadString (Overwrites last position char (terminating char) with 0
        if (makeSerialStringPosition > 1) {
          stringOne = serialReadString;
          int firstListItem = stringOne.indexOf("|");
          int secondListItem = stringOne.indexOf("|", firstListItem + 1 );
          String Line1 = "";
          String Line2 = "";
          String Line3 = "";
          for (int strPos = 0; strPos < firstListItem; strPos++) { 
            Line1 = Line1 + stringOne.charAt(strPos);
          }
          //Serial.println(Line1);
          lcd.setCursor(0, 0);
          lcd.print(Line1);
          for (int strPos = firstListItem +1 ; strPos < secondListItem; strPos++) { 
            Line2 = Line2 + stringOne.charAt(strPos);
          }
          //Serial.println(Line2);
          lcd.setCursor(0, 1);
          lcd.print(Line2);
              for (int strPos = secondListItem +1 ; strPos < stringOne.length(); strPos++) { 
            Line3 = Line3 + stringOne.charAt(strPos);
          }
          //Serial.println(Line3);
          lcd.setCursor(0, 2);
          lcd.print(Line3);
        }
    }
  } 
}


void setup()
{
  Serial.begin(9600);  
  // IC Default 9 bit. If you have troubles consider upping it 12. 
  // Up the delay giving the IC more time to process the temperature measurement
  if (TMP_ENABLE)
  {  
    sensors.begin();
    int sensorCnt = sensors.getDeviceCount();
    sensors.getAddress(ts1, 0);
  }
  if (LCD_ENABLE)
  {
    // Setup the LCD
    //lcd.begin(16, 2); 
    lcd.begin(20, 4); 
    lcd.setCursor(0, 3);
    lcd.print("Temp (F)");
  }
}

void loop()
{
  if (TMP_ENABLE)
  {  
    delay(1000);          //waiting 1 second
    // request to all devices on the bus
    sensors.requestTemperatures(); // Send the command to get temperatures
    float temperature1 = (((sensors.getTempCByIndex(0)) * 1.8 ) + 32);
    int tempSensorValue = int(temperature1);
    if (temperature1 > 0)
    {
      Serial.println(temperature1); 
      if (LCD_ENABLE)
      { 
        lcd.setCursor(0, 3);
        lcd.print("Temp (F)");
        lcd.setCursor(15, 3);
        lcd.print(temperature1);
      }
    }
    else
    {
      Serial.println(temperature1);
    }
    delay(5000);  //waiting 5 seconds
  }
  // See if any data is on the serial port
  lcd.setCursor(0, 1);
  serialReader();
}

After you load the sketch into the Arduino IDE, you can compile and upload using the IDE Tool menu.  

If everything was installed, connected and uploaded successfully, you should see the temperature displayed in the Serial Monitor window every 5 seconds. To launch the Serial Monitor, click the Serial Monitor icon in the upper right hand corner of the Arduino IDE. The serial monitor will close every time you upload a new sketch so you will have to relaunch it to see the output.

If you do not see the temperature displaying in the Serial Monitor, STOP here and go back over the steps before continuing to the Linux server configuration. If it does not display on the Serial Monitor, it will not log correctly nor display on the Linux webserver.

Linux Server

The first step is to install ArchLinux ARM on a PogoPlug. I have complete documentation on the installation of my ArchLinux ARM server here. Like the comment above, I recommend testing the Linux installation, ssh access and default Apache webpage before continuing. It will limit the amount of troubleshooting if you verify the project at each step in the configuration.

After you have successfully installed and have ArchLinux ARM up and running make sure you have updated all packages with

     pacman -Syu

and install the following additional packages if you have not installed it as part of the PogoPlug Linux installation

    python-pyserial

    python2-pyserial

This package allows the linux server to communicate with the Arduino over the USB serial connection. You can install the package with the command

     pacman -S python-pyserial python2-pyserial

 

Configuring Arduino on ArchLinux

All of the Arduino scripts are located in /srv/arduino.

Before installing the Python scripts that collect the data from the Arduino, test the ArchLinux ARM server to ensure that the Arduino is properly communicating with the operating system. I used the Arduino Wiki page on the ArchLinux site for reference. Also the Arduino Playground site for Linux. Even though I was not installing the Arduino IDE on Linux, the Arduino Playground site offered some great testing and troubleshooting commands to sue. My Arduino connected to

     /dev/ttyACM0

You can check what port it connects to in Linux by using the command

     dmesg | tail

You should see an entry like this

If your Arduino does not connect on /dev/ttyACM0, note this because it will need to be changed in the python scripts later

Once you have successfully installed ArchLinux ARM, python-pyserial and tested the Arduino connection, you can copy the following Python script to your /srv/arduino directory

#! /usr/bin/python2
#######################################################
# File: ben.py
# Author: Benjamin Bordonaro
# Copyright: copyright 2011-2013   
# Source: http://www.benjaminbordonaro.com
# Version: 13.03.24
# License: GNU GPL v2 - http://www.gnu.org/licenses/gpl-2.0.html
#
# Features:
# - Integration of PogoPlug ArchLinuxARM, Arduino Uno and 
#   Temperature Sensors
# - Email and Text Alerts for High and Low Temperatures
# - Bidirectional communication between PogoPlug Linux server 
#   and Arduino
# - Web access from Apple iPhone and iPad
# - Data Logging and Analysis using RRDTool
# - Webcam support
# - Email Support (POP and SMTP)
# - FTP Support
# - Weather Support (upgraded to xml.dom)
# - Integration with Philips hue LED Zigbee Lightbulb system
# - Data file for most recent information
# - Reporting Module
# - Custom Configuration Options
#
# Code Credits:
# - Weather upgrade to use xml.dom
#   Matthew Petroff (http://www.mpetroff.net/)
#######################################################
# Import Libaries
import datetime                         # datetime library
import time				# for sleep function
import serial				# pyserial
from smtplib import SMTP		# email/smtp
import ftplib				# ftp (file transfer)
import poplib				# pop email access
import time				# date time
import rrdtool				# rrdtool
import platform				# platform info
import urllib2				# http url library
import ConfigParser			# to read ini file
from email.MIMEText import MIMEText     # email format
from xml.dom import minidom             # XML Library
import codecs				# Codec Library
from phue import Bridge			# Philips hue Library
#######################################################
# Read in configuration file using Config Parser
aConfig = ConfigParser.RawConfigParser()
aConfig.read('/srv/arduino/settings.cfg')
#aConfig.get('prd', 'config')
#######################################################
#Program Name - used for messages, alerts, etc.
myPN = aConfig.get('prd', 'appname')
myVer = aConfig.get('prd', 'appversion')
myAppVer = aConfig.get('prd', 'appversion')
myAppLabel = aConfig.get('prd', 'applabel')
myAppServer = aConfig.get('prd', 'appserver')
#######################################################
# App Configuation
#######################################################
# 12.03.08 moved configuration to settings.cfg
#Alerts level for Temperature
alertHighTemp = aConfig.getfloat('prd', 'alerthightemp')
alertLowTemp = aConfig.getfloat('prd', 'alertlowtemp')

# Web Camera (Enable = 1, Disable = 0)
myWebcamera = aConfig.getint('prd', 'webcamera')

# Arduino (Enable = 1, Disable = 0)
myArduinoIn = aConfig.getint('prd', 'arduinoin')
myArduinoOut = aConfig.getint('prd', 'arduinoout')
mySerial = aConfig.getint('prd', 'serial')
myTempSensor = aConfig.getint('prd', 'tempsensor')

# Debugging (Enable = 1, Disable = 0
myDebug = aConfig.getint('prd', 'debug')

# Email Checking (Enable = 1, Disable = 0)
myEmail = aConfig.getint('prd', 'email')

# FTP (Enable = 1, Disable = 0)
myFTP = aConfig.getint('prd', 'ftp')

# Internet IP Checking (Enable = 1, Disable = 0)
myIP = aConfig.getint('prd', 'ip')
myIPPort = aConfig.get('prd', 'hostipport')

# Write Message File (Enable = 1, Disable = 0)
myMsg = aConfig.getint('prd', 'messages')

# Retrieve Weather from Google (Enable = 1, Disable = 0)
myWeather = aConfig.getint('prd', 'weather')
myWeatherLon = aConfig.get('prd', 'weatherlon')
myWeatherLat = aConfig.get('prd', 'weatherlat')
myWeatherCity = aConfig.get('prd', 'weathercity')

# Build and save report (Enable = 1, Disable = 0)
# Text Alerts (Enable = 1, Disable = 0)
myReport = aConfig.getint('prd', 'reports')
myTxtAlerts = aConfig.getint('prd', 'txtalerts')
myAlertsAll = aConfig.getint('prd', 'alertsall')

# Time to Send Report via Email
myReportTime = aConfig.get('prd', 'reporttime')

# Philips hue Configuration
myHueIP = aConfig.get('prd', 'hueip')
myHueEnable = aConfig.getint('prd', 'hueenable')

#######################################################
# Variables
logdata1 = ""
msg1 = ""
myReportBody = ""
myReportBreak = "-------------------------------------------"
#######################################################
# serial port of Arduino on ArchLinux ARM server
# Usually it is /dev/ttyACM0
if mySerial == 1:
   ser = serial.Serial('/dev/ttyACM0', 9600)

#######################################################
#Functions
#######################################################
def ams_lcd_format(lcdline):
    retline = lcdline[:20]
    retline = retline.ljust(20)    
    return retline

def ams_email_check():
    mprt = 110
    mbox = poplib.POP3_SSL(aConfig.get('prd', 'emailpopsrvr1'))
    mbox.getwelcome()
    mbox.user(aConfig.get('prd', 'emailuser1'))
    mbox.pass_(aConfig.get('prd', 'emailpass1'))
    mstat = mbox.stat()
    print(mstat)
    messageCount = len(mbox.list()[1])
    print(str(messageCount) + " Email Message(s)")
    rvalue = str(messageCount)
    for mList in range(messageCount):
      eSub = ""
      eFrom = ""
      for msgl in mbox.retr(mList+1)[1]:
        if msgl.startswith("Subject"):
           #print msgl
           eSub = msgl[8:].strip()
        if msgl.startswith("From"):
           #print msgl
           eFrom = msgl.strip()
      if eFrom.find(eusr2) > -1:
        rvalue = eSub
      else:
        rvalue = "No Message"
    mbox.quit()
    return rvalue

def ams_ftp_upload(ftpsdir1, file1):
    # Using Config File settings.cfg
    ftpuser1 = aConfig.get('prd', 'ftpuser1')
    ftppass1 = aConfig.get('prd', 'ftppass1')
    ftpsite1 = aConfig.get('prd', 'ftpsite1')
    #ftpsourcedir1 = aConfig.get('prd', 'ftpsourcedir1')
    ftpdestdir1 = aConfig.get('prd', 'ftpdestdir1')
    try:
       connftp = ftplib.FTP(ftpsite1)
       connftp.login(ftpuser1,ftppass1)
       connftp.cwd(ftpdestdir1)
       fhandle1 = open(ftpsdir1 + file1, "rb")
       connftp.storbinary('STOR ' + file1, fhandle1)
       fhandle1.close()
       connftp.quit()
    except:
       print("FTP Error")

def ams_email_alert(subject, message, sendto):
    # Using Config File settings.cfg
    msg = MIMEText(message)
    # msg['Subject'] = myPN + " " + subject
    msg['Subject'] = subject
    msg['From'] = aConfig.get('prd', 'emailuser2')
    msg['To'] = aConfig.get('prd', 'emailuser2')
    smtpObj = SMTP(aConfig.get('prd', 'emailsmtpsrvr2'),587)
    smtpObj.ehlo()
    smtpObj.starttls()
    smtpObj.ehlo()
    smtpObj.login(aConfig.get('prd', 'emailuser2'), aConfig.get('prd', 'emailpass2'))
    try:
       if len(sendto) > 1:
          smtpObj.sendmail(aConfig.get('prd', 'emailuser2'), sendto, msg.as_string())
       else:
          smtpObj.sendmail(aConfig.get('prd', 'emailuser2'), aConfig.get('prd', 'emailuser2'), msg.as_string())
    finally:         
       smtpObj.close()

def ams_write_to_log(file1, rw, line_to_write):
    if rw == "a":
     datafile1=open(file1,'a')
    if rw == "w":
     datafile1=open(file1,'w')
    datafile1.write(line_to_write + "\n")
    datafile1.close()

def ams_write_html_file(file_to_write, line_to_write):
    html_header = """
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
       <meta http-equiv="content-type" content="text/html; charset=utf-8" />
       <meta name="viewport" content="width=device-width, user-scalable=no" />
       <title>Arduino Monitor</title>
       <link rel ="stylesheet" href = "/iPhoneIntegration.css" />
    </head>
    <body>
    """
    html_footer = "<br><h2>Last Updated: " + tdnow + "</h2>\n</body></html>"
    html_all = html_header + line_to_write + html_footer
    ams_write_to_log(file_to_write,'w',html_all)

def ams_getWeather2():
    # Fetch data (change lat and lon to desired location)
    try:
       weather_xml = urllib2.urlopen('http://graphical.weather.gov/xml/SOAP_server/ndfdSOAPclientByDay.php?whichClient=NDFDgenByDay&lat=' + myWeatherLat + '&lon=' + myWeatherLon + '&format=24+hourly&numDays=4&Unit=e').read()
    except:
       return "Error: weather"
    dom = minidom.parseString(weather_xml)
    # Parse temperatures
    xml_temperatures = dom.getElementsByTagName('temperature')
    highs = [None]*4
    lows = [None]*4
    for item in xml_temperatures:
      if item.getAttribute('type') == 'maximum':
        values = item.getElementsByTagName('value')
        for i in range(len(values)):
            highs[i] = int(values[i].firstChild.nodeValue)
      if item.getAttribute('type') == 'minimum':
        values = item.getElementsByTagName('value')
        for i in range(len(values)):
            lows[i] = int(values[i].firstChild.nodeValue)

    # Parse icons
    xml_icons = dom.getElementsByTagName('icon-link')
    icons = [None]*4
    for i in range(len(xml_icons)):
      icons[i] = xml_icons[i].firstChild.nodeValue.split('/')[-1].split('.')[0].rstrip('0123456789')

    # Parse dates
    xml_day_one = dom.getElementsByTagName('start-valid-time')[0].firstChild.nodeValue[0:10]
    day_one = datetime.datetime.strptime(xml_day_one, '%Y-%m-%d')

    # Print some info for debugging
    if myDebug == 1:
      print("xml_day_one: " + str(xml_day_one))
      print("day_one:     " + str(day_one))
      print("High:        " + str(highs[0]))
      print("Low:         " + str(lows[0]))

    tdnow3 = time.strftime('%a', (time.localtime(time.time())))
    #weather = myWeatherCity + "|High: " + str(highs[0]) + "F  Low: " + str(lows[0]) + "F"
    weather = "High: " + str(highs[0]) + "F  Low: " + str(lows[0]) + "F"
    # if there was an error getting the condition, the city is invalid
    if weather == "<?xml version=":
        return "Invalid city"

    #return the weather condition
    return weather

def buildReport(rHeading, rText):
    myB = rHeading + "\n" + rText + "\n" + myReportBreak + "\n"
    return myB 

def ams_sethuecolor(rBulbNumber, rColor, rBlink):
    # Usage ams_sethuecolor(1,"blue",0)
    # Colors Supported: off, white, red, green, purple, pink, blue, yellow
    b = Bridge(myHueIP)
    lights = b.get_light_objects('id')
    # Moved off setting above light turn on so that the bulb does not
    # flicker if it is off and another off command comes across
    if rColor == "off":
        lights[rBulbNumber].on = False
    else:
        if (lights[rBulbNumber].on == False):
           lights[rBulbNumber].on = True
    if rColor == "white":
        lights[rBulbNumber].brightness = 250
        lights[rBulbNumber].hue = 14922
        lights[rBulbNumber].saturation = 144
        lights[rBulbNumber].xy = [0.4595, 0.4105]
    if rColor == "red":
        lights[rBulbNumber].brightness = 250
        lights[rBulbNumber].hue = 49746
        lights[rBulbNumber].saturation = 235
        lights[rBulbNumber].xy = [0.6449, 0.0329]
    if rColor == "purple":
        lights[rBulbNumber].brightness = 250
        lights[rBulbNumber].hue = 46033
        lights[rBulbNumber].saturation = 228
        lights[rBulbNumber].xy = [0.2336, 0.1129]
    if rColor == "pink":
        lights[rBulbNumber].brightness = 250
        lights[rBulbNumber].hue = 47793
        lights[rBulbNumber].saturation = 211
        lights[rBulbNumber].xy = [0.3627, 0.1807]
    if rColor == "yellow":
        lights[rBulbNumber].brightness = 250
        lights[rBulbNumber].hue = 19886
        lights[rBulbNumber].saturation = 254
        lights[rBulbNumber].xy = [0.4304, 0.5023]
    if rColor == "blue":
        lights[rBulbNumber].brightness = 250
        lights[rBulbNumber].hue = 47108
        lights[rBulbNumber].saturation = 254
        lights[rBulbNumber].xy = [0.167, 0.04]
    if rColor == "green":
        lights[rBulbNumber].brightness = 250
        lights[rBulbNumber].hue = 23543
        lights[rBulbNumber].saturation = 254
        lights[rBulbNumber].xy = [0.3919, 0.484]

#######################################################
# Begin main program
#######################################################
#blank msg variables
msgLine1 = ""
msgLine2 = ""
msgLine3 = ""
msgLine4 = ""
htmlLine1 = ""
htmlLine2 = ""
htmlLine3 = ""
htmlLine4 = ""

#Store current date and time
tdnow = time.strftime('%m-%d-%Y %H:%M:%S', (time.localtime(time.time())))
tdnowlcd = time.strftime('%m-%d-%Y %I:%M%p', (time.localtime(time.time())))
tdnow2 = time.strftime('%b %d, %Y %I:%M%p', (time.localtime(time.time())))
tdreport = time.strftime('%H%M', (time.localtime(time.time())))
tdmin = time.strftime('%M', (time.localtime(time.time())))
tddow = time.strftime('%a', (time.localtime(time.time())))
tdweekday = time.strftime('%w', (time.localtime(time.time())))

msgLine1 = ams_lcd_format(tdnowlcd)
htmlLine1 = "<h1>Time</h1><ul><li>" + tdnowlcd + "</ul>"

#Commented for debugging
#print("tdnow:" + tdnow)
#print("tdnow2:" + tdnow2)
#print("tdreport:" + tdreport)
#print("tdmin:" + tdmin)
#print("tddow:" + tddow)
#print("tdweekday:" + tdweekday)

if myAlertsAll == 1:
   print("Alerts On")
else:
   print("Alerts Off")

# Begin data file
datasave = ConfigParser.SafeConfigParser()
datasave.read('/srv/arduino/data.dat')
datasave.set('data','time',str(tdnowlcd))
datasave.set('data','time',msgLine1)

#Moved debug info to the top so that we can seed the LCD with
#debug info if no data is present
if myDebug == 1:
    myReportBody = myReportBody + "System Information:\n"
    print("Debugging is enabled")
    print 'system   :', platform.system()
    datasave.set('data','platform-system',platform.system())
    myReportBody = myReportBody + platform.system() + " "
    print 'release  :', platform.release()
    datasave.set('data','platform-release',platform.release())
    msgLine2 = ams_lcd_format(platform.system() + " " + platform.release())
    myReportBody = myReportBody + platform.release() + "\n"
    print 'machine  :', platform.machine()
    datasave.set('data','platform-machine',platform.machine())
    myReportBody = myReportBody + platform.machine() + "\n"
    myReportBody = myReportBody + myReportBreak + "\n"

if myReport == 1:
    myReportBody = myReportBody + buildReport("Report Created:",tdnow)

timeforweather = 0

if myWeather == 1:
    # v13.03.10 Retrieve weather once an hour at xx:55
    if tdmin == "55":
      print("Time to retrieve the weather")
      timeforweather = 1
      weather1 = ams_getWeather2()
      datasave.set('data','weather',weather1) 
      print(weather1)
      fweather1 = "Current: " + weather1.replace("|","\n")
      ##########################################################
      # TODO: This section needs to be rewritten for LCD output
      ##########################################################
      #if myMsg == 1:
      #  msgOut = msgOut1 + weather1 
      #  ams_write_to_log('/srv/arduino/messages.cfg','w',msgOut)
      msgLine2 = ams_lcd_format(weather1) 
      if myReport == 1:
        myReportBody = myReportBody + buildReport("Weather Conditions:", fweather1)
        if myTxtAlerts == 1:
          if tdreport == myReportTime:
             ams_email_alert("Weather",fweather1,aConfig.get('prd', 'smstextuser1'))
          else:
             print("Not time to send weather email")
        else:
          print("Text alerts are not enabled")

# For timing reasons check email first and then 
# write it to the serial port later
if myEmail == 3:
   retEmail = ams_email_check()
   datasave.set('data','email',retEmail)

# Check Internet IP Address
if myIP == 1:
    ################################################
    # TODO: Needs to be rewritten. 
    # whatismyip is no longer a free active service
    ################################################
    pub_ip = urllib2.urlopen("http://automation.whatismyip.com/n09230945.asp").read()
    print pub_ip
    datasave.set('data','ip',pub_ip) 
    if myReport == 1:
       myReportBody = myReportBody + buildReport("My Internet IP:", pub_ip)

# Read the Arduino
if myArduinoIn == 1: 
    print("Reading data from the Arduino")
    # flush the serial input and read the data from Arduino
    ser.flushInput()
    sresult = ser.readline().strip()
    datasave.set('data','arduino-in',sresult)
    print(sresult)

# Read the temperature from the Arduino
if myTempSensor == 1: 
    # flush the serial input and read the data from Arduino
    ser.flushInput()
    sresult = ser.readline().strip()
    #############################################################
    # 12.12.31 - Added for splitting multiple tempsensor results
    tMetric1, tMetric2, tMetric3 = sresult.split("|")
    print("Temperature1: " + tMetric1)
    print("Temperature2: " + tMetric2)
    print("Temperature3: " + tMetric3)
    #############################################################
    datasave.set('data','temperature',sresult) 
    #############################################################
    # Write to RRD Round Robin Database for analytics
    #############################################################
    #ret = rrdtool.update("/srv/arduino/ttemp.rrd","N:" + sresult)
    ret = rrdtool.update("/srv/arduino/ttemp2.rrd", "N:" + tMetric1 + ":" +  tMetric2 + ":" + tMetric3)
    if ret:
       print rrdtool.error()
    #############################################################
    print("Temperature: " + sresult)
    #Alerts checking for high and low temp
    #If temp is exactly 32 degrees or less something is wrong with 
    #reading the sensor
    if myAlertsAll == 1: 
       if float(sresult) > 32.00:
          if float(sresult) < alertLowTemp:
            print("Alert: Low Temp")
            ams_email_alert("Temperature", "High Temperature: " + str(sresult),"0")
          if float(sresult) > alertHighTemp:
            print("Alert: High Temp")
            ams_email_alert("Temperature","Low Temperature: " + str(sresult),"0")
    # used for debugging
    #sresult = "00.00"
    if myReport == 1:
       myReportBody = myReportBody + buildReport(myAppLabel + " Temperature:", str(sresult) + "F")
    htmlLine2 = "<h1>" + myAppLabel + " Temperature</h1><ul><li><a href=\"rrdgraph.html\">" + sresult + "</a></ul>"
    if myWebcamera == 1: 
       htmlLine2 = htmlLine2 + "<h1>Camera</h1><ul><li><img width=\"260\" src=\"webcam.jpg\"></ul>"
else:       
    print("ArduinoIn is not enabled")

# Write Application Version to the footer of the report
myReportBody = myReportBody + buildReport(myPN + " Version", myVer)
msgLine4 = ams_lcd_format("v" + myVer)
htmlLine4 = "<h1>Version</h1><ul><li>" + myAppLabel + " v" + myVer + "</ul>"

if myReport == 1:
    ams_write_to_log('/srv/arduino/report.txt','w',myReportBody)
    # Email Report out at myReporTime
    if tdreport == myReportTime:
       print("Sending Report")
       ams_email_alert("System Report",myReportBody,"0")
    else:
       print("Not sending report")

#Write HTML File - index.html
if myWeather == 1:
   if tdreport == myReportTime:
     htmlLine2 = "<h1>Weather</h1><ul><li><a href=\"message.html\">" + fweather1 + "</a></ul>"

#Philips hue API integration added v2013.03.02
if myHueEnable == 1:
    msgLine3 = ams_lcd_format("Philips hue enabled")
    htmlLine3 = "<h1>Philips hue</h1><ul><li><a href=\"hueschedule.html\">enabled</a></ul>"
    # Read file an execute hue api based on hueschedule.cfg
    huehtml = "<h1>Philips hue Schedule</h1>"
    for hline in open("/srv/arduino/benschedule.cfg","r").readlines():
      huehtml = huehtml + "<ul><li>" + hline + "</ul>"
      hAction, hDow, hBulb, hTime, hColor, hAlert = hline.split(",")
      if (hAction == "HUE" ):
        if (tdreport == hTime and hDow == "AL"):
          ams_sethuecolor(int(hBulb),hColor,hAlert)    
        if (int(tdweekday) > 0 and int(tdweekday) < 6):
          if (tdreport == hTime and hDow == "WD"):
	    ams_sethuecolor(int(hBulb),hColor,hAlert)
        if (int(tdweekday) < 1 or int(tdweekday) > 5):
          if (tdreport == hTime and hDow == "WE"):
	    ams_sethuecolor(int(hBulb),hColor,hAlert)
    ams_write_html_file('/srv/http/hueschedule.html',huehtml)

#FTP functionality added in v2012.09.11
if myFTP == 1:
    if tdmin == "05":
       print("FTPing images to website")
       ams_ftp_upload('/srv/http/img/','temp1d.png')
       ams_ftp_upload('/srv/http/img/','temp7d.png')
       ams_ftp_upload('/srv/http/img/','temp30d.png')
       ams_ftp_upload('/srv/http/img/','temp90d.png')

if myMsg == 1:
   msgOut = "|" + msgLine1 + "|" + msgLine2 + "|" + msgLine3 + "|" + msgLine4 + "|"
   ams_write_to_log('/srv/arduino/messages.cfg','w',msgOut)

ams_write_html_file('/srv/http/index.html',htmlLine1 + htmlLine2 + htmlLine3 + htmlLine4)

datasave.set('data','msgLine1',msgLine1)
datasave.set('data','msgLine2',msgLine2)
datasave.set('data','msgLine3',msgLine3)
datasave.set('data','msgLine4',msgLine4)

# Write Data File and Close
datafile = open('/srv/arduino/data.dat','w')
datasave.write(datafile)
datafile.close()


# If we are not checking email and the Arduino is active
# then write the msgLines to the Arduino
if myArduinoOut == 1:
    ser.flushOutput()
    ser.write("|" + ams_lcd_format(msgLine1) + "||||\r\n")
    print("|" + ams_lcd_format(msgLine1) + "||||\r\n")
    time.sleep(5)
    if (len(msgLine2) > 3): 
       ser.flushOutput()
       ser.write("|" + ams_lcd_format(msgLine1) + "|" + ams_lcd_format(msgLine2) + "|||\r\n")
       print("|" + ams_lcd_format(msgLine1) + "|" + ams_lcd_format(msgLine2) + "|||\r\n")
       time.sleep(5)
    if (len(msgLine3) > 3): 
       ser.flushOutput()
       ser.write("|" + ams_lcd_format(msgLine1) + "||" + ams_lcd_format(msgLine3) + "||\r\n")
       print("|" + ams_lcd_format(msgLine1) + "||" + ams_lcd_format(msgLine3) + "||\r\n")
       time.sleep(5)
    if (len(msgLine4) > 3): 
       ser.flushOutput()
       ser.write("|" + ams_lcd_format(msgLine1) + "|||" + ams_lcd_format(msgLine4) + "|\r\n")
       print("|" + ams_lcd_format(msgLine1) + "|||" + ams_lcd_format(msgLine4) + "|\r\n")
       time.sleep(5)
print("Script Completed.")

The software uses ConfigParser and all configuration settings are located in settings.cfg. Create a new file in the /srv/arduino directory. Here is a sample configuration file:

[prd]
appname = Arduino Monitor
appversion = 13.03.12
applabel = Aquarium
appserver = 02
alertsall = 0
alerthightemp = 90.00
alertlowtemp = 75.00
webcamera = 0
arduino = 1
serial = 1
hueenable = 0
hueip = 192.168.0.5
debug = 1
email = 0
ip = 1
ftp = 1
hostipport = 8018
messages = 1
weather = 1
weathercity = PaloAlto+CA
weatherlon = -122.14
weatherlat = 37.44
reports = 1
reporttime = 0800
txtalerts = 0
ftpuser1 = user1
ftppass1 = password1
ftpsite1 = ftp.mysite.com
ftpsourcedir1 = /srv/http/img/
ftpdestdir1 = /website/img
emailuser1 = user1@email.com
emailpass1 = password1
emailpopsrvr1 = pop.email.com
emailsmtpsrvr1 = smtp.email.com
emailuser2 = user2@email.com
emailpass2 = password2
emailpopsrvr2 = pop.email.com
emailsmtpsrvr2 = smtp.email.com
smstextuser1 = 2125551212@txt.att.net
smstextuser2 = 2125551212@txt.att.net

 

To make the script executable, use the following command

     chmod 755 scriptname.py

 

Webcam (optional)

The ArchLinux site (http://www.archlinux.org) is my source for Linux documentation. I have been using the following webcam tutorial

http://wiki.archlinux.org/index.php/Webcam_Setup

I was unsuccessful with the streamer application due to an ArchLinux segmentation fault:

https://bugs.archlinux.org/task/18555

The quality of the images from my Intel CS110 was bad, so I switched to a Creative Live Cam VF-0050 and the fswebcam application. You can install fswebcam with

     pacman -S fswebcam

I created a bash script called webcam.sh in the /srv/http directory and scheduled it through cron to run every 15 minutes.

     webcam.sh:

#! /bin/bash
LD_PRELOAD=/usr/lib/libv4l/v4l2convert.so fswebcam -r 640x480 -d /dev/video0 -v /srv/http/img/webcam.jpg -t "Aquarium Webcam" -D 3 -s brightness=90%

Here is an updated screenshot of my cron schedule

 

Philips hue integration (optional)

Version 13.03.12 and above includes integration support for the Philips hue LED lighting system. You can control the LED bulbs for alerts and notifications. The script includes the necessary wrapper functions and a default color pallete to work with the Python library. You will need to download the Python hue Library and copy it to the /srv/arduino directory.

Configuration file options:

hueip = 192.168.0.2 (IP of your Philips hue controller)

hueenable = 1 (1 = enable, 0 = disable)

The script reads a configuration file called benschedule.cfg. The format of the configuration file is:

Action, Day of Week Code, Bulb Number, Time, Color Code, Alert (for future use)

Action Code: HUE

Day of Week Codes: AL = All days of the week, WD = Weekdays, WE = Weekends

Color Codes: off, white, red, purple, pink, yellow, blue, green.

Sample configuration file:

HUE, AL,1,0800,white,0

HUE, WD,1,1000,red,0

HUE, WD,1,1700,off,0

Sample configuration file actions

Line1 - lightbulb #1 turns on with a white color at 8:00a

Line2 - lightbulb #1 turns on with a red color at 10a on weekdays

Line3 - lightbulb #1 turns off on weekdays at 5p

For more information on the Python library or for test scripts, please visit my Philips hue and Python Libary post

RRDTool

The software utilizes RRDTool to log and graph the data over time. The RRDTool site and documentation can be found at http://oss.oetiker.ch/rrdtool/

If you did not install it above, you must install the rrdtool using the command:

    pacman -S rrdtool

Once you have installed the RRDTool we need to create the RRD database.

Here is my RRD database create script:

#! /bin/bash 
# rrdcrt2.sh version 12.09.19

rrdtool create /srv/arduino/ttemp.rrd -s 300 DS:temp:GAUGE:600:0:100 RRA:AVERAGE:0.5:1:2016 RRA:MIN:0.5:1:2016 RRA:MAX:0.5:1:2016 RRA:AVERAGE:0.5:6:1344 RRA:MIN:0.5:6:1344 RRA:MAX:0.5:6:1344 RRA:AVERAGE:0.5:24:2190 RRA:MIN:0.5:24:2190 RRA:MAX:0.5:24:2190 RRA:AVERAGE:0.5:144:3650 RRA:MIN:0.5:144:3650 RRA:MAX:0.5:144:3650 

#This creates an RRD database with the following attributes: 
# 5 minute step (base interval with which data will be fed into the RRD)
# 1 data source (called temp) 
# 10 minute heartbeat for the data source 
# 7 days of 5 minute averages 
# 4 weeks of 1/2 hour averages 
# 6 months of 2 hour averages 
# 5 years of 12 hour averages

This will create an RRD database in the /srv/arduino directory

The main Python script above will insert the temperature into the RRD database every 5 minutes as long as you have cron configured correctly to run every 5 minutes.

The last configuration for the RRDTool is to create the graphs. There is a nice integration of the RRDTool into Python. I created my bash script called benrrd.py in the /srv/arduino directory with the following code:

#! /usr/bin/python2
#######################################################
# File:     	        benrrd.py
# Author:   	Benjamin Bordonaro
# Copyright:      copyright 2012-2013   
# Source:           http://www.benjaminbordonaro.com
# Version:  	13.01.19
# Features:
# - Graphing of Arduino Monitoring System RRDTool DB
#######################################################
import rrdtool
import datetime
import time

def ams_rrd_graph(graphtime, graphname, graphtext):
    tdnow = time.strftime('%m-%d-%Y %H:%M', (time.localtime(time.time())))
    tdnow2 = time.strftime('%m-%d-%Y %H:%M:%S', (time.localtime(time.time())))

    ret = rrdtool.graph( "/srv/http/img/%s.png" %(graphname),
 	"--start", "%s" %(graphtime),
        "--end", "now", 
    	"--height", "125", 
       	"--slope-mode", 
       	"--title", graphtext,
       	"--watermark", tdnow,
      	"DEF:temp1=/srv/arduino/ttemp.rrd:temp1:AVERAGE",
	"DEF:min1=/srv/arduino/ttemp.rrd:temp1:MIN",
	"DEF:max1=/srv/arduino/ttemp.rrd:temp1:MAX",
        "LINE1:temp1#AA0000:Temperature",
	"GPRINT:temp1:MIN:Room Min\: %6.2lf",
	"GPRINT:temp1:MAX:Max\: %6.2lf",
	"GPRINT:temp1:AVERAGE:Avg\: %6.2lf", 
	"GPRINT:temp1:LAST:Current\: %6.2lf \\r",

ams_rrd_graph("end-1h","temp1h","Temperature - Last Hour")
ams_rrd_graph("end-4h","temp4h","Temperature - Last 4 Hours")
ams_rrd_graph("end-1d","temp1d","Temperature - Last 24 Hours")
ams_rrd_graph("end-7d","temp7d","Temperature - Last 7 Days")
ams_rrd_graph("end-30d","temp30d","Temperature - Last 30 Days")
ams_rrd_graph("end-90d","temp90d","Temperature - Last 90 Days")
ams_rrd_graph("end-365d","temp365d","Temperature - Last Year")

After creating the script, I changed the script to executable with the chmod 755 command and scheduled it to run every hour. Here is my crontab configuration.

 

The website references the iPhone style sheet. It will need to be installed in the /srv/http directory. Here is the link to the iPhoneIntegration.css file.

You will have to create an img directory under /srv/http. Use the following command:

mkdir img

Running the Script

Change the directory to /srv/arduino and run the script with the command

     ./scriptname.py

The script will wait up to 5 seconds to read the data from the serial port. Upon exiting, it should create the HTML file index.html. You should have your Apache webserver installed and running

After you have the CSS located in the /srv/http directory, browse to

http://yourservername/

Here is my server accessed from an iPhone with and without the webcam active

Once you system starts to collect data, you should see graphs created in the img directory

 

Automating the script on ArchLinux ARM

You can schedule the script to run through cron. Here is my cron configuration. I run the Python script every 5 minutes.

If you need help with cron, please visit the ArchLinux cron documentation

     https://wiki.archlinux.org/index.php/Cron

Wrapping Up

At this point you can install your Arduino to a project box, strap it to the side of the PogoPlug or install both the PogoPlug Board and Arduino Uno into a new project box. I opted to modify the PogoPlug hardware and add the Arduino Uno inside the PogoPlug case.

Integrating Arduino into the PogoPlug case (optional)

This integration was not too difficult. I am going to monitor the internal temperature to ensure that I did not take up too much of the PogoPlug space that was used for heat dissipation. The modification required taking apart the PogoPlug, mounting the Arduino Board inside the PogoPlug Case. Taking apart the case took a little effort. Remove the faceplate first and then with some pressure slide the inner case and separate it from the clear plastic housing. I used a plastic mount with screws for the Arduino Uno board. I attached the plastic to the side of the case with double stick tape lining it up to the plastic tabs on the front of the PogoPlug case.

Then I drilled a hole for the temperature sensor on the rear panel and used a Dremel to cut the hole for the Arduino USB connector on the front panel. I was going to make the Arduino connector internal, but then I would not have access to reprogram the Arduino with new code. I may just move both the PogoPlug board and Arduino to a new project box for a future version, but this works nicely for now.

Here is the final product with the Arduino Uno and temperature sensor installed in the PogoPlug case.

 

I installed temporarily on top of the fishtank until I can lengthen the sensor cable to move the PogoPlug under the aquarium.

I hope this was helpful as you build your own monitoring and automation project. I will continue to update this documentation. If you have any questions or comments on the documentation, please use the Contact link to send me an email.

Version History: (changed to date versions YY-MM-DD)

  • Version 13.03.24
    • Moved debug code higher in the script to use the variables
    • Created ams_lcd_format for proper LCD line output formatting
    • Fixed bug in serial/LCD write output. added flush output
    • Fixed bug with data.dat and added additional data saves
    • Updated ams_sethuecolor to not turn on light if an off command is sent so that the light does not flicker
    • Updated ams_write_html_file to add a file_to_write parameter so that we can create more html than just index.html
    • Added a feature to export the hue schedule and link it to the html page for read only viewing on an iOS / Android device
    • Change hueschedule.cfg to benschedule.cfg for future scheduling of other tasks. Added hue as the first parameter in the file
  • Version 13.03.12
    • Integration with the Philips hue LED lightbulbs
    • Enhancements to the HTML file write function
    • Updates to the Arduino write out function
    • Minor bug fixes and enhancements
  • Version 13.01.19
    • Rewrite of rrdgrapht.sh in Python
    • Added additional configuration options to settings.cfg
    • Minor bug fixes
  • Version 12.09.19
    • Upgraded to Arduino IDE 1.0.1
    • Rewrite of Weather module to use xml.dom
    • Changed Weather module to use weather.gov due to Google taking down service
    • Added FTP support to upload graphs to a website
    • Standardized functions with the format ams_xxxxxxx
    • Added additional configuration options to settings.cfg
    • Code cleanup
    • Minor bug fixes
  • Version 12.03.10
    • LCD Extension over Category 5e Ethernet Cable (link)
    • Upgraded to a 20x4 LCD
    • Bidirectional communication with the Arduino
    • Enhanced email module with POP/SSL and SMTP support
    • Weather module V1
    • Reporting module V1
    • Enhanced platform debugging
    • Moving application configuration to a settings.cfg file and implementing ConfigParser
    • Bug fixes and enhancements
  • Version 12.02.12
    • Updated Arduino LCD library
    • Added POP Mail support for LCD Display and future alert action usage
    • Moved Email usernames and passwords to a settings.cfg file in /srv/arduino
    • Updated webcam.sh with better tuned parameters and moved webcam.jpg to /srv/http/img
    • Updated rrdtool graph commands with MIN, MAX and current temperature display
    • Moved Bash and Python scripts to /srv/arduino
    • Moved RRDTool database to /srv/arduino
  • Version 12.01.15
    • Updated the RRDTool database structure and graphing code
    • Linked graphs from temperature in HTML
    • Fixed minor issues
  • Version 12.01.12
    • RRDTool integration for data logging temperature graphing
    • Configuation options in the Python script for alerts and webcam
  • Version 12.01.06
    • Arduino Uno R3 Tested Successfully
    • Webcam Support
    • High/Low Temperature Email Alerts
  • Version 11.12.31
    • Hardware Integration of PogoPlug, ArchLinux ARM and Arduino Uno R2
    • Temperature Sensor Readings
    • Web Access from iPhone and iPad

 

Last Updated: 3/24/2013