So I automated the thermostats, and the pool; there are various sensors reporting things and also some X10 devices. That means something needs to watch over things for me and respond to my requests. I bought an Aduino Mega2560 and have it set up as my house controller. It really does very little autonomously....yet. But it will over time. Right now it handles the feed to Pachube where I collect data and has a web server that I use as an interface to control the house functions.
This is by far my most complex project, but not the hardest. I didn't have to invent anything for this, just interface with the other stuff around the house. The support devices were much harder because I had to invent most of them or study complex protocols to get them going. So, this will probably be the longest writeup I have. Scroll down to whatever interests you if you get bored with the narrative.
This is the device on a shelf working away. The display alternates between several items I want to watch, including data on its actual memory usage and current status.
Here's a little video of the initialization sequence and rotating display in action. Notice how the data is updating asynchronously from the various devices around the house. As each one reports, the corresponding LED flashes. I also track the updates to my Pachube feed so I can tell if data is going out for graphing later. And, yes, I have a parrot that is screeching in the background.
It takes a whole lot of code to do this and one long sketch was too darn hard to manipulate. I found that scrolling through a couple of hundred lines of code to find some variable was a real pain so I started using the tabs. I have 'modules' for the major functions: HandlePacube for the data feed code, Handle Ethernet for the raw ethernet interaction, HandleThermostats, HandleXbee, etc. Makes it much easier to find things.
I also used timers to control things. Timers are different from alarms; alarms fire at a certain time. So something that happens at noon every day is an alarm. Something that happens a few seconds from now, is a timer. So to flash an LED I turn it on and set a timer for one second from now to turn it off. That way I don't have to have delays that block actions and I can let the code go off doing other things until the timer fires. The TimeAlarm arduino library is great for this. Timers can also simplify things like command retries updating displays and anything that needs to happen in sequence.
For example, do thing 1, set a timer for thing 2. When the thing 2 timer fires, set a timer for thing 3, etc. This is basically how I handle a rotating display. I have a timer that fires every few seconds and a state machine that steps between items I want to show up on an LCD display. This way I can show the time, date, temperature, etc one after the other on a display without having to worry about timing it.
The main sketch:
This is where the initialization and main loop are. I have the web server code in here also. I keep certain items such as the last IP address and values of interest that I accumulate over time. Since there is a bug in the Arduino Mega2560 bootloader where the watchdog timer doesn't work, I use timer3 as an interrupt timer and monitor the board's activity. If the timer fires, I reboot the board. Since there are many libraries and heavy interaction, chasing down all the possible bugs and hardware hangups is practically impossible. Using the timer interrupt allows the board to reset itself if something hangs it up. This can be a problem sometimes when it reboots because of a lockup on the internet. Fortunately, this doesn't happen often, but I will have to investigate this in more detail over time.
OK, if you're going to have it on the web, you have to have a web address. Dyndns is a service that handles this for me. See, the address your DSL modem uses isn't associated with a name and Dyndns will take care of this association for you. However, since my ISP changes my darn address a couple of times a day, I have to actually get my new address and update the Dyndns server periodically. This code does this for me on a 10 minute interval. Again, this is done with a timer.
This is the ethernet hardware handling code. I have had a ton of trouble establishing a connection and keeping it working. The code in here can reset the board and retry the connection. It'll also cause a board reset if necessary because I need the ethernet to make things work properly. There's also a couple of cool little routines in here to compare IP addresses. The routines use the netmask so one can tell if the address incoming is on the local LAN. This is important since I really, really don't want someone on the internet being able to turn the heater on in the middle of summer. Additionally, I use it to tell if the public IP address of my house has changed. This lets me know if I need to update the address in the DynDNS database (see directly above for more information on this). The DHCP handling is in here as well. DHCP helps me keep track of addresses that used to be hard coded. This is a nice little piece of code that is available on the Arduino web site.
This handles the Pachube feed. It's not much code, but separating it like this keeps it out of the way when I make changes to something. There is actually a problem here. I followed my normal procedure of rebooting the controller if I can't connect in this code. Well, twice now Pachube has gone off line. This means the controller constantly reboots until Pachube comes back online. Sigh, I'll have to change this soon.
My two thermostats are web enabled and have a page on this blog describing their construction and code. This module talks to them and gathers their current status as well as passing on commands to enable their various functions. Since these devices are hardwired into my home ethernet, I don't use XBee communication on them. But, I do have to have retries and status checks on the network since even the hardwired ethernet has troubles. If I can't talk to the thermostats, the controller will reboot and try it again. When you look at the thermostat page, you'll see that it has the ability to reboot as well. Defensive programming carried to an extreme.
This code is what actually does all the asynchronous work. Things like the rotating display, data updates from various devices are handled by setting a timer, writing the routines and then forgetting about them. If I need to change the update frequency, I just change the timer. This code is called TimeAlarm and is available on the Arduino web site. I added a couple of routines to the library to handle some special items like counting the number of timers active. Using this I can make sure I don't run out during the asynchronous operations. I could easily have and XBee update, web update, web interrogation, all happen within a second. Currently I just display the maximum number of timers ever used, but I could also pause until something timed out and freed up one of the timers. It's unlikely I'll do that though, this is a Mega2560 and has pleanty of memory, at least for now.
The XBee protocol and interaction is in this module. I only implemented the portions of the protocol that I'm interested in. No, I didn't use the XBee library. The library doesn't handle the multiple serial ports on the 2560, nor does it handle the SoftwareSerial library. At least it doesn't without some serious changes. I decided to just write my own handler so I understood what was going on for later when a bug turned up. Also, I can use any level of protocol without having to adhere to some rules someone else made up about how to use it.
This module is where the data samples from an outdoor thermometer are captured and calculated. I also receive broadcasts from my Power Monitor and Satellite Clock. These data are displayed on the rotating display. I also receive status from the pool and send commands to the pool controller from this code.
I'm undecided exactly how to handle this portion of the code. The X10 modules don't provide status and you just have to trust them to do what you tell them to do. However, X10 is famous for problems with this. You can send an ON command and nothing happens because of some noise on the power line, flaky hardware or just about anything else. I suspect I'll just send the commands multiple times and hope for the best, but I'm just starting on this piece so check back later.