Real World Interaction: A Raspberry Pi as a Water Alarm System With Internet Connectivity

A couple of weeks ago I wrote about my re-discovery of the fascination and use of the electronics kits I experimented with in my youth and how I wanted to make good use of them again in combination with a Raspberry Pi. The project I had in mind and which has borne some fruits now is a water alarm system with Internet connectivity.

I'm a practical guy so playing around with new hardware and software always has to have an application for me. When you enter the kitchen in the morning and are welcomed by a pool of water on the floor you instantly know something is wrong. In my case it was a leak in the rooftop that subsequently proved to be a bit difficult to find so we went through a trial and error phase. During the trial and error phase I wanted to know at once when water started accumulating on the kitchen floor again to take the appropriate counter measures.

Pi-hardware1A perfect application for the Raspberry Pi that could warn me of a new water pool building on the floor via email. The Pi itself has some I/O pins that are, however, not well protected so I decided to buy one of the hardware extension boards that offers buffered and protected I/O ports. There are a number of different boards available and my choice fell on the Pi-Face as it's the same size as the Raspberry Pi and hence I could fit it into a small casing. As the Pi-Face is only a generic I/O board I needed additional hardware to detect water on the floor. This is were my electronics kit came in for prototyping a detector as seen on the first picture on the left.

Pi-hardware2Once this was working I decided to go for the real thing and build five of those sensors on a real board so I could place five detectors at different locations on the floor. The second picture on the right shows how the final solution looks like: The casing contains the Raspberry Pi with a tiny Wi-Fi adapter below the PiFace which is connected to the self made electronic board via a number of cables. From there 2x five 3m sensor cables leave the casing on the right to different locations on the floor. On the other end I just taped the uninsulated cables to the floor. Water between the ends of two cables change the resistance between the two cables which is detected by the detector on my self soldered board.

When one of the detectors on the board recognizes a change in resistance at the end of the cable it drives an input port on the PiFace which in turn is detected by a Phython program running on the Pi. The Phython program in turn will immediately send me an eMail to notify me of the event. The program also sends me regular status updates of all input ports and also notifies me in case an input is switched off again, i.e. the water has disappeared again.

Sending an email with Python by the way is pretty much straight forward as there are already libraries that can even handle encryption via secure SMTP. I've attached the source to this part of the program at the end of this post as this could come in quite handy for other projects you might to want to try.

Quite frankly I wouldn't have gone through the whole thing if I just had a water leak. But a RasPi project, real world interaction, connectivity to the Internet, a little electronics project and a real world problem to solve was too hard a thing to resist.

And here's the source for sending email in Python:

As I wanted the email transmission to be independent from the rest of the alarm system I decided to spawn the python email code in an independent process. If something fails here, the system would still work and continue to monitor the alarm sensors and show the result on the LEDs on the PiFace. Also should one email task get stuck for one reason or another I would still get informed of the problem with the next periodic status email. Here's the code to spawn a new independent task without waiting for it to be finished:

EMAIL_PASSWORD = "very-secret-of-course"
EMAIL_PORT = "587"

    syslog.syslog ('sending status email')
                      "System status: " + PrintString,
                      "Status of monitoring system " + PrintString,

And on the other end, looks like this:

#!/usr/bin/env python

# send a text email from the command line using python
# version 1.0
# Original code found at
# NOTE: if smtp username is "" then code will not use the smtp authentication method
# input parameters
#    sys.argv[1] is the sender email address
#    sys.argv[2] is the reciever email address,
#        this can be a comma separated string for multiple recievers
#    sys.argv[3] is the subject text
#    sys.argv[4] is the body text
#    sys.argv[5] is the smtp host
#    sys.argv[6] is the smtp username
#    sys.argv[7] is the smtp password
#    sys.argv[8] is the smtp port

import smtplib, email, sys, time
import syslog
from email.mime.text import MIMEText

# check to make sure the number of arguments is correct
if len(sys.argv) != 9:
  print 'Usage: <sender> <receiver> <subject> <bodyText> <smptHost> <username> <password> <port>'

# get the argv variables
sender = sys.argv[1]
receiver = sys.argv[2]
subj = sys.argv[3]
bodyText = sys.argv[4]
smtpHost = sys.argv[5]
username = sys.argv[6] # use "" if no SMTP authentication is required
passwd = sys.argv[7] # ignored if no SMTP authentication is required
port = sys.argv[8] # ignored if no SMTP authentication is required
# create a list from the receiver in case we have a comma separated string of multiple receivers
rList = []
rList = receiver.split(',');

# setup the message header
msg = MIMEText(bodyText)
msg['Subject'] = subj
msg['From'] = sender
msg['To'] = receiver

# determine if a passworded smpt host is being used and connect as necessary
if username == "":
    server = smtplib.SMTP(smtpHost) # smtp server is not password protected
    server = smtplib.SMTP(smtpHost, port)
    server.login(username, passwd)

failed = 0
failed = server.sendmail(sender, rList, msg.as_string())

# return the status
if failed:
  print ' Failed:', failed
  syslog.syslog(' Failed: ' + str(failed))
  print ' Finished with no errors.'
  syslog.syslog(' OK: ' + str(failed))

Have fun hacking!