Building a Home Automation and Security System with Python
Listing 1. Employing the GetInputStatus Method
def GetInputStatus(self, Input = None): self.ser.write(Input + '\r\n') Expect = [Input, Input, '\r', '\n'] cnt = 0 while cnt <= 3: a = self.ser.read() if a == '' or a != Expect[cnt]: return -1 cnt += 1 val= self.ser.read() Expect = ['\r', '\n', '#'] cnt = 0 while cnt <= 2: a = self.ser.read() if a == '' or a != Expect[cnt]: return -1 cnt += 1 if val == '1': return 1 else: return 0
When activity is detected, MonitorInputs checks to make sure that input activity has not occurred within the input activity timeout. The timeout is used to keep the alarm threads, which simply send a plain-text e-mail, from executing too many times during a single input activity voltage on condition. The timeout is not the best solution, because the smoke and/or water alarm would still send a new e-mail every 60 seconds. This is acceptable to me, because if I receive a water alarm e-mail while at work, I'm going to rush home. The unused Serial I/O Kit relays could be used to correct this shortcoming, because each input positive connection could be routed through a relay, which could be turned off to disable the alarm voltage.
Another solution is to signal the GetInputStatus method to ignore input activity on a specific input. Either method will work, but a remote trigger mechanism will be required in either case, because the serial port connection is maintained by the home automation program. A possible solution adds a server thread to the home automation program that would accept simple string commands from a client connection. This would allow a simple Python CGI script to send commands that could control input monitoring and/or the relay states. Pyro is a Python distributed object system that provides another more complex solution using an event server. This is very similar to the client/server approach, but Pyro is more robust and provides opportunities beyond the scope of this article. One of these solutions will probably find its way into a future upgrade to the home automation program.
Now that the program is monitoring for input activity, it needs to produce notifications, such as a warning sound or e-mail when activity is detected. Smoke and water alarm activity is handled by the generic threaded Alarm class, and drive alert activity is handled separately. The Alarm class plays a WAV file using the PlayWav class, and it also sends a notification e-mail using the MailAttachment class. The PlayWav class uses a popen call to the wavcmd value (sox play command) set in the configuration file. The PlayWav class is threaded to prevent a busy sound device from holding up the e-mail notifications. The end result of all of the threaded classes is that the input activity is monitored almost continuously with only slight delays.
The DriveAlert class handles detected input activity for the drive alert signal. This class employs the GetImage (Listing 2), PlayWav and SSHRemote threaded classes. A new GetImage instance is created for each camera command (camcmd) set in the configuration file, so that images can be collected from each camera at about the same time. The GetImage class makes a popen call to the camera command and waits until it has completed. This is repeated until the number of images set in the configuration file have been collected and saved in the directories defined in the camdir section of the configuration file. Once all of the images have been collected, the GetImage class uses the ZipIt class to create a zip file via a popen call to the zip command. When all of the image files are zipped up, the MailAttachment routine is used to e-mail the zip files. If you would like to stagger the images collected from the cameras, you can add a camera image delay section to the configuration file and modify the GetImage class by adding a call to the sleep function using the preset camera delay as input.
Listing 2. DriveAlert Class
class GetImage(threading.Thread): def __init__(self, cam = None, numImages = 1): self.cam = cam self.JobBegin = -1 self.camCmd = CamCOMMANDS[cam] self.numImages = NumCamImages[cam] self.Zip = None threading.Thread.__init__(self) def run(self): for i in range(self.numImages): self.JobBegin = int(time.strftime("%H%M%S",time.localtime(time.time()))) if QUIET == 0: print 'Getting %s image' %self.cam filename = time.strftime("%H%M%S", time.localtime(time.time())) + '.jpg' execcmd = self.camCmd %filename self.p = popen2.Popen3("exec " + execcmd, 1024) self.errReader = PipeReader(self.p.childerr); self.errReader.start() self.outReader = PipeReader(self.p.fromchild); self.outReader.start() try: self.p.wait() except OSError, (errno, errnostr): if QUIET == 0: print 'ERROR: GetImage self.p.wait Errno %s: %s' %(`errno`, `errnostr`) except: if QUIET == 0: print 'ERROR: self.p.wait Unknown error' time.sleep(IMAGE_DELAY) #Popen complete - create zipfiles self.Zip = ZipIt(self.cam) self.Zip.start() self.Zip.join() #Wait on zip file creation
I briefly mention the SSHRemote class because the name is ambiguous. This class could be used to execute any command by replacing the ssh remote command in the configuration file with another one. I currently use it to play some tunes on my shop machine to make it appear that someone is home. The ssh call executes another simple Python script on the remote machine, which uses the play command to play all WAV files in a specified directory.
This article shows how Linux, Python and some cheap off-the-shelf hardware can be used to create a home automation system in a reasonable amount of time. The article focuses on the main parts of the system and cannot possibly describe the setup of all of the required components in detail. I must also stress that this system has not been tested in a production environment and therefore comes with no guarantees, express or implied, as to its suitability for any of the purposes listed above, so use it at your own risk. I am looking forward to making future enhancements, such as a voice modem that will dial a preset number and play a message. This will supplement the unreliable e-mail notifications, which are often delayed. I hope this article sparks your interest in simple monitoring systems and the flexibility of the Serial I/O Kit used in this project.
Resources for this article: /article/8696.
Fred Stelter has a BS in Computer Science from Baylor University in Waco. When he's not writing code for a local company, he likes to pop some tires at the local mountain bike trails, work on his hot rod or occasionally hit the water for some kneeboarding.
Special Reports: DevOps
Have projects in development that need help? Have a great development operation in place that can ALWAYS be better? Regardless of where you are in your DevOps process, Linux Journal can help!
With deep focus on Collaborative Development, Continuous Testing and Release & Deployment, we offer here the DEFINITIVE DevOps for Dummies, a mobile Application Development Primer, advice & help from the experts, plus a host of other books, videos, podcasts and more. All free with a quick, one-time registration. Start browsing now...
- Vigilante Malware
- Disney's Linux Light Bulbs (Not a "Luxo Jr." Reboot)
- Libreboot on an X60, Part I: the Setup
- Bluetooth Hacks
- Dealing with Boundary Issues
- Vagrant Simplified
- System Status as SMS Text Messages
- Non-Linux FOSS: Code Your Way To Victory!
- October 2015 Issue of Linux Journal: Raspberry Pi
- October 2015 Video Preview