#12: Building an Arduino home automation controller

    30th May 14

    Control physical devices using an Arduino based home automation controller that connects to your network and lets you switch things on and off using a web browser. This episode shows the construction sequence of a controller that combines an Arduino-compatible board, Power-over-Ethernet, and relay driver shields to create a self-contained controller that can serve up its own web interface so you can click buttons in your browser to turn devices on and off.

     

    Parts Required

    Assemble Hardware

    As shown in the video, decide how your device will be mounted and where you want external connections such as the Ethernet cable to be located. Drill suitable holes in the case and mount your Arduino in place. It can be a good idea to use plastic screws as shown in the video to prevent any possibility of short circuits to the outside of the case.

    Software

    There are many options for software to control the device. For example, you could have it subscribe to an MQTT broker (server) and update its outputs based on publications from other devices. Or you could provide a menu via the serial console to control it manually from a computer connected via USB.

    For this example I've provided a sketch that runs as a web server via Ethernet, and serves up a web page that includes "on" and "off" buttons for each of 16 outputs. By loading the web page in your browser you can simply click the appropriate buttons to activate and deactivate devices connected to those outputs. This example uses two Relay 8 shields, but it works just fine with a single shield as well. There's no harm having the extra buttons visible and nothing will be damaged if the Arduino tries to address outputs that don't exist, but you can trim it down to only 8 outputs if you prefer. It can also be scaled up to 24, 32, etc outputs if required.

     

    /**
     * CurtainControllerRelay8x2.pde
     */
    
    #include <SPI.h>
    #include "Ethernet.h"
    #include "WebServer.h"
    #include "Wire.h"
    
    #define SHIELD_1_I2C_ADDRESS  0x20  // 0x20 is the address with all jumpers removed
    #define SHIELD_2_I2C_ADDRESS  0x21  // 0x21 is the address with a jumper on position A0
    
    #define MAC_I2C_ADDRESS       0x50  // Microchip 24AA125E48 I2C ROM address
    
    /* If channelInterlocks is set to true, the channels are grouped into
     * pairs starting at 1 (ie: channels 1 & 2, 3 & 4, etc) and only one
     * channel in each pair can be on at any time. For example, if channel
     * 1 is on and 2 is set to on, channel 1 will be turned off first and
     * vice versa. This is to allow control of dual-active devices such as
     * electric curtain motors which must only be driven in one direction
     * at a time. */ 
    const byte channelInterlocks = false;
    
    /* CHANGE THIS TO YOUR OWN UNIQUE VALUE.  The MAC number should be
     * different from any other devices on your network or you'll have
     * problems receiving packets. Can be replaced automatically below
     * using a MAC address ROM. */
    static uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
    
    /* CHANGE THIS TO MATCH YOUR HOST NETWORK.  Most home networks are in
     * the 192.168.0.XXX or 192.168.1.XXX subrange.  Pick an address
     * that's not in use and isn't going to be automatically allocated by
     * DHCP from your router. Can be replaced automatically using DHCP. */
    static uint8_t ip[] = { 192, 168, 1, 15 };
    
    #define PREFIX "/control"  // This will be appended to the IP address as the URL
    WebServer webserver(PREFIX, 80);
    
    byte shield1BankA = 0; // Current status of all outputs on first shield, one bit per output
    byte shield2BankA = 0; // Current status of all outputs on second shield, one bit per output
    
    /* This command is set as the default command for the server.  It
     * handles both GET and POST requests.  For a GET, it returns a simple
     * page with some buttons.  For a POST, it saves the value posted to
     * the buzzDelay variable, affecting the output of the speaker */
    void serverCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete)
    {
      /* If we've received a POST request we need to process the submitted form values */
      if (type == WebServer::POST)
      {
        bool repeat;
        char name[20], value[16];
        do
        {
          /* readPOSTparam returns false when there are no more parameters
           * to read from the input.  We pass in buffers for it to store
           * the name and value strings along with the length of those
           * buffers. */
          repeat = server.readPOSTparam( name, 20, value, 16);
    
          /* This is a standard string comparison function.  It returns 0
           * when there's an exact match. */
          if (strcmp( name, "On" ) == 0)
          {
            setLatchChannelOn( atoi(value) );
          }
          
          if (strcmp( name, "Off" ) == 0)
          {
            setLatchChannelOff( atoi(value) );
          }
          
          if (strcmp( name, "AllOff" ) == 0)
          {
            sendRawValueToLatch1(0);
            sendRawValueToLatch2(0);
          }
          
        } while (repeat);  
        
        // After procesing the POST data, tell the web browser to reload
        // the page using a GET method. 
        server.httpSeeOther(PREFIX);
        return;
      }
    
      /* for a GET or HEAD, send the standard "it's all OK" headers */
      server.httpSuccess();
    
      /* Don't output the body for a HEAD request, only for GET */
      if (type == WebServer::GET)
      {
        /* store the HTML in program memory using the P macro */
        P(message) = 
          "<html><head><title>Relays</title>"
          "<body>"
          "<form action='/control' method='POST'>"
          
          "<p><button name='AllOff' value='0'>All Off</button></p>"
          
          "<p><button type='submit' name='On' value='1'>1 On</button><button type='submit' name='Off' value='1'>1 Off</button></p>"
          "<p><button type='submit' name='On' value='2'>2 On</button><button type='submit' name='Off' value='2'>2 Off</button></p>"
          "<p><button type='submit' name='On' value='3'>3 On</button><button type='submit' name='Off' value='3'>3 Off</button></p>"
          "<p><button type='submit' name='On' value='4'>4 On</button><button type='submit' name='Off' value='4'>4 Off</button></p>"
          "<p><button type='submit' name='On' value='5'>5 On</button><button type='submit' name='Off' value='5'>5 Off</button></p>"
          "<p><button type='submit' name='On' value='6'>6 On</button><button type='submit' name='Off' value='6'>6 Off</button></p>"
          "<p><button type='submit' name='On' value='7'>7 On</button><button type='submit' name='Off' value='7'>7 Off</button></p>"
          "<p><button type='submit' name='On' value='8'>8 On</button><button type='submit' name='Off' value='8'>8 Off</button></p>"
          
          "<p><button type='submit' name='On' value='9'>9 On</button><button type='submit' name='Off' value='9'>9 Off</button></p>"
          "<p><button type='submit' name='On' value='10'>10 On</button><button type='submit' name='Off' value='10'>10 Off</button></p>"
          "<p><button type='submit' name='On' value='11'>11 On</button><button type='submit' name='Off' value='11'>11 Off</button></p>"
          "<p><button type='submit' name='On' value='12'>12 On</button><button type='submit' name='Off' value='12'>12 Off</button></p>"
          "<p><button type='submit' name='On' value='13'>13 On</button><button type='submit' name='Off' value='13'>13 Off</button></p>"
          "<p><button type='submit' name='On' value='14'>14 On</button><button type='submit' name='Off' value='14'>14 Off</button></p>"
          "<p><button type='submit' name='On' value='15'>15 On</button><button type='submit' name='Off' value='15'>15 Off</button></p>"
          "<p><button type='submit' name='On' value='16'>16 On</button><button type='submit' name='Off' value='16'>16 Off</button></p>"
          
          "</form></body></html>";
    
        server.printP(message);
      }
    }
    
    /**
     */
    void setup()
    {
      Wire.begin(); // Wake up I2C bus
      Serial.begin( 38400 );
      Serial.println("SuperHouse.TV Output Controller starting up. v1.0, 16 channel (2 shields)");
      
      Serial.print("Getting MAC address from ROM: ");
      mac[0] = readRegister(0xFA);
      mac[1] = readRegister(0xFB);
      mac[2] = readRegister(0xFC);
      mac[3] = readRegister(0xFD);
      mac[4] = readRegister(0xFE);
      mac[5] = readRegister(0xFF);
      char tmpBuf[17];
      sprintf(tmpBuf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
      Serial.println(tmpBuf);
      
      // setup the Ethernet library to talk to the Wiznet board
      //Ethernet.begin(mac, ip);  // Use static address defined above
      Ethernet.begin(mac);      // Use DHCP
      
      // Print IP address:
      Serial.print("My URL: http://");
      for (byte thisByte = 0; thisByte < 4; thisByte++) {
        // print the value of each byte of the IP address:
        Serial.print(Ethernet.localIP()[thisByte], DEC);
        if( thisByte < 3 )
        {
          Serial.print(".");
        }
      }
      Serial.println("/control");
    
      /* Register the default command (activated with the request of
       * http://x.x.x.x/control */
      webserver.setDefaultCommand(&serverCmd);
    
      /* start the server to wait for connections */
      webserver.begin();
    
      /* Set up the Relay8 shields */
      initialiseShield(SHIELD_1_I2C_ADDRESS);
      sendRawValueToLatch1(0);  // If we don't do this, channel 6 turns on! I don't know why
      
      initialiseShield(SHIELD_2_I2C_ADDRESS);
      sendRawValueToLatch2(0);  // If we don't do this, channel 6 turns on! I don't know why
      
      Serial.println("Ready.");
    }
    
    /**
     */
    void loop()
    {
      // Process incoming connections one at a time forever
      webserver.processConnection();
    }
    
    /**
     */
    void initialiseShield(int shieldAddress)
    {
      // Set addressing style
      Wire.beginTransmission(shieldAddress);
      Wire.write(0x12);
      Wire.write(0x20); // use table 1.4 addressing
      Wire.endTransmission();
    
      // Set I/O bank A to outputs
      Wire.beginTransmission(shieldAddress);
      Wire.write(0x00); // IODIRA register
      Wire.write(0x00); // Set all of bank A to outputs
      Wire.endTransmission();
    }
    
    /**
     */
    void toggleLatchChannel(byte channelId)
    {
      if( channelId >= 1 && channelId <= 8 )
      {
        byte shieldOutput = channelId;
        byte channelMask = 1 << (shieldOutput - 1);
        shield1BankA = shield1BankA ^ channelMask;
        sendRawValueToLatch1(shield1BankA);
      }
      else if( channelId >= 9 && channelId <= 16 )
      {
        byte shieldOutput = channelId - 8;
        byte channelMask = 1 << (shieldOutput - 1);
        shield2BankA = shield2BankA ^ channelMask;
        sendRawValueToLatch2(shield2BankA);
      }
    }
    
    /**
     */
    void setLatchChannelOn (byte channelId)
    {
      if( channelInterlocks == true )
      {
        if ( (channelId % 2) == 0)  // This is an even number channel, so turn off the channel before it
        {
          setLatchChannelOff( channelId - 1 );
        } else {                    // This is an odd number channel, so turn off the channel after it
          setLatchChannelOff( channelId + 1 );
        }
      }
      
      if( channelId >= 1 && channelId <= 8 )
      {
        byte shieldOutput = channelId;
        byte channelMask = 1 << (shieldOutput - 1);
        shield1BankA = shield1BankA | channelMask;
        sendRawValueToLatch1(shield1BankA);
      }
      else if( channelId >= 9 && channelId <= 16 )
      {
        byte shieldOutput = channelId - 8;
        byte channelMask = 1 << (shieldOutput - 1);
        shield2BankA = shield2BankA | channelMask;
        sendRawValueToLatch2(shield2BankA);
      }
    }
    
    
    /**
     */
    void setLatchChannelOff (byte channelId)
    {
      if( channelId >= 1 && channelId <= 8 )
      {
        byte shieldOutput = channelId;
        byte channelMask = 255 - ( 1 << (shieldOutput - 1));
        shield1BankA = shield1BankA & channelMask;
        sendRawValueToLatch1(shield1BankA);
      }
      else if( channelId >= 9 && channelId <= 16 )
      {
        byte shieldOutput = channelId - 8;
        byte channelMask = 255 - ( 1 << (shieldOutput - 1));
        shield2BankA = shield2BankA & channelMask;
        sendRawValueToLatch2(shield2BankA);
      }
    }
    
    /**
     */
    void sendRawValueToLatch1(byte rawValue)
    {
      Wire.beginTransmission(SHIELD_1_I2C_ADDRESS);
      Wire.write(0x12);        // Select GPIOA
      Wire.write(rawValue);    // Send value to bank A
      shield1BankA = rawValue;
      Wire.endTransmission();
    }
    
    /**
     */
    void sendRawValueToLatch2(byte rawValue)
    {
      Wire.beginTransmission(SHIELD_2_I2C_ADDRESS);
      Wire.write(0x12);        // Select GPIOA
      Wire.write(rawValue);    // Send value to bank A
      shield2BankA = rawValue;
      Wire.endTransmission();
    }
    
    /**
     * Required to read the MAC address ROM
     */
    byte readRegister(byte r)
    {
      unsigned char v;
      Wire.beginTransmission(MAC_I2C_ADDRESS);
      Wire.write(r);  // Register to read
      Wire.endTransmission();
    
      Wire.requestFrom(MAC_I2C_ADDRESS, 1); // Read a byte
      while(!Wire.available())
      {
        // Wait
      }
      v = Wire.read();
      return v;
    }
    
    

     


    #11: Processing and storing IP camera footage

    22nd Sep 13

    Commonly available IP cameras are great if you just want something that lets you quickly view a live stream from your laptop or smartphone, but if you want to keep footage more permanently (such as for a security recording) you can do that too without too much trouble. In this episode I show you how to use an Ubuntu Linux machine on your network to run as an FTP server to accept snapshots from your cameras, process those snapshots to add timestamp watermarks on the images, and then periodically convert them into a time-lapse video to quickly see what happened over a period of time.

     

    Parts Required

    • IP camera with FTP upload capability (eg: Foscam FS8918W)
    • Computer running Linux (I use Ubuntu in this demonstration)
    • ProFTPd (FTP server software)
    • Imagemagick (image processing software)
    • Mencoder (movie encoder software)

    Setting Up An FTP Server

    If you already have access to an FTP server you can skip this step, and go straight on to "Configuring Your IP Camera". You can even use a third-party FTP server located off-site if you like, but my preference is to run a local server. This has a few advantages, including limiting use of the very insecure FTP to your local network, and giving you the opportunity to do some processing of the images on the server.

    On Ubuntu, installing ProFTPd is as simple as opening a terminal and typing:

    sudo apt-get install proftpd-basic

    Ubuntu will install the FTP server and even start it for you automatically.

    However, there's one extra step you can take to increase security just a little. By default, ProFTPd does not jail users within a specific directory: that means anyone logging into your FTP server can see the whole filesystem. Let's lock that down a bit.

    Using a text editor, open the following file:

    /etc/proftpd/proftpd.conf

    Around line 34 you'll find the following entry:

    #DefaultRoot    ~

    Remove the leading crosshatch to enable that option, so the line shows:

    DefaultRoot    ~

    Save the file, then restart ProFTPd so it will see the changes:

    sudo /etc/init.d/proftpd restart

    Now users who connect by FTP will be jailed inside their home directories.

    I prefer to create separate users for each camera so they all end up compartmentalised, but that's totally up to you. If you want to create a new user for a camera, you can enter the following:

    sudo adduser camera41

    Of course you can substitute your own username convention to suit your requirements. In the example shown in the video I set up a user for camera 41, so I just made that the username for convenience.

    Configuring Your IP Camera

    Configuration options may vary depending on your particular model of camera and firmware version, but look for a section titled "FTP Service Settings" or similar.

    You'll need to enter the address of your FTP server: usually the IP address of the Linux machine if you're running your own server, otherwise it may be the hostname if it's set up in DNS.

    You'll also need to enter the FTP username and password you just configured a moment ago, and optionally specify a target directory name if you want uploaded images to be put into a specific location. Some cameras create the target directory automatically if they find it there, otherwise you may need to create it yourself on the server.

    In the case of Foscam cameras, you can't test the setup or configure an upload rate until you save the settings. Click the submit button, tick the "Upload image now" checkbox, and enter a sensible value for the upload interval. In my example I set the upload interval to 1 second.

    Watermarking Images With Timestamp

    Most cameras upload snapshot images with the filename set to something useful such as the timestamp. In the case of Foscam cameras, the filename is the camera identifier, then the timestamp, then a sequence number. That's useful information so I like to place the filename as a watermark in the image itself.

    You can process a collection of images using ImageMagick. If you don't have it installed on your Ubuntu Linux box already, open a terminal and enter:

    sudo apt-get install imagemagick

    ​This will give you the "convert" program which can be used to place text on images.

    In a terminal, go to the directory containing uploaded images and enter:

    for name in *.jpg; do convert "$name" -font courier -pointsize 20 -draw "gravity south fill black text 0,12 '$name' fill white text 1,11 '$name'" "$name"; echo $name; done

    That command can all be entered as one line, although it's wrapped here in the browser.

    This is a "for" loop that will step through all the files with names matching "*.jpg", and write the filename into them near the bottom center first in black and then in white. This helps the text stand out whether the background is light or dark.

    If you open the images after running that command you should see text added to the bottom that shows the filename. Here's one from the example shown in the video:

    Converting Images To A Time-Lapse Movie

    Now that you have a collection of watermarked images taken at whatever interval you configured in your camera, you can process them periodically into a time-lapse movie so you can quickly scan through what happened while the camera was recording.

    There are a variety of tools for doing this, but one that's easy to use is mencoder. If you don't have it installed, open a terminal on your Ubuntu Linux machine and type:

    sudo apt-get install mencoder

    ​Now you can go into the directory containing the watermarked images are run the following command:

    mencoder mf://*.jpg -mf w=640:h=480:fps=25:type=jpg -ovc lavc -lavcopts vcodec=mpeg4:mbd=2:trell -oac copy -o output.avi

    Once again that's all one line.

    This tells mencoder to operate on multi-file (mf) input, that it's processing images of type JPG at 640x480, setting the output video codec (ovc) to libavcodec, setting mpeg4 compression, and writing the output file to "output.avi".

    Here's the example video I created for the demo:

    Wrapping It All Up

    Of course you probably don't want to run those commands manually all the time, so you could combine them into a shell script and execute it periodically using cron. You'll probably also want to include a sequence number or (better still) timestamp in the name of the output file, so that you can come back later and easily see what period is covered by each movie.

    Got suggestions or feedback? Discuss this episode in the forum: forum.freetronics.com/superhouse


    #10: Workshop Tour

    5th Sep 13

    This episode is a bit different: instead of featuring a specific project, I pull back the curtain and give you a candid view of my home office / workshop so you can see where I work.


    #9: Power-over-Ethernet for IP cameras

    22nd Jul 13

    IP cameras are really handy, except for one thing: getting power to them. Learn how to hack an IP camera to add an internal voltage regulator and run power to it over the network cable.


    #8: Implantable RFID

    9th May 13

    In 2007 there was a lot of argument about putting RFID tags into Australian passports, and many people were saying we were destined for a future where everyone was implanted with RFID so they could be tracked by the government and large corporations. I wanted to understand the technology and the risks involved, so I did the only logical thing: I implanted an RFID chip in myself!

    I've shown use of my implanted RFID chip in previous SuperHouseTV episodes, but this is the first time I've shown the footage of me actually implanting the chip in myself.


    #7: Control door locks with RFID and Arduino

    3rd Apr 13

    Now that I've modified the Lockwood Nexion door lock on my workshop for Arduino control, the next step is to add an RFID reader near the door so that I can scan the RFID chip implanted in my arm to unlock the door.

    Note that I deliberately did not go into any detail of the RFID implantation in this episode, so it's safe to watch even if you don't want to see me doing home surgery on myself! I'll cover that in a separate episode so anyone who doesn't want to see it can avoid it.


    #6: Electronic door locks

    27th Feb 13

    Door locks are one of the most useful things to control from your home automation system. Using electronic locks you can give your house central locking just like your car, control them from your phone or via the Internet, or even control them using an RFID tag or implant.

    I modified a Lockwood Nexion keyless lock to allow it to be controlled by an Arduino, and linked it to my home automation system.

    View on YouTube: www.youtube.com/watch?v=mqEcJO0UafA

    Discuss this episode in the forum: forum.freetronics.com/viewforum.php?f=30

    "EtherMega" (Arduino Mega with onboard Ethernet) mentioned in the video: www.freetronics.com/ethermega


    #5: Installing conduit under an existing footpath

    9th Dec 12

    Electrical conduit allows you to run cables outside, underground, and in other nasty places. Sometimes though you have to get cable into places that look impossible, such as under an existing concrete path or driveway that you can't rip up. It's not as hard as you may think. I used a technique called "water boring" to install a conduit under a concrete footpath in just a few minutes, without having to dig up the path.

    View on YouTube: www.youtube.com/watch?v=9rXFU59D1uE

    Discuss this episode in the forum: forum.freetronics.com/viewforum.php?f=30


    #4: Making Home Automation Gear Quieter

    16th Sep 12

    I've found that the second-hand Ethernet switches I use in my home automation system have annoyingly loud fans, so I modified them to run the fans more slowly and connect to my Arduino-based environmental monitoring system.

    Links for this episode:

    Discuss this episode in the forum: forum.freetronics.com/viewforum.php?f=30


    #3: Power-over-Ethernet for Arduino Home Automation

    7th May 12

    I'm using Ethernet extensively in my Arduino home automation system for communication with devices distributed around the house, so being able to also provide power to those devices over the same network cable is a big time saver. Until now I've been using a "DIY" approach to Power-over-Ethernet (PoE) with midspan injectors sending about 10-12V down the wire, but I'm now converting it all over to use Netgear switches with 802.3af (48V) PoE support built in.

    This episode covers some of the options for PoE with Arduino, and demonstrates how you can do it both in a cheap DIY method and using commercial PoE switches.

    Links for this episode:

    Discuss this episode in the forum: forum.freetronics.com/viewforum.php?f=30


    #2: Arduino-Controlled Home Automation Switchboard

    23rd Apr 12

    The automation switchboards in my house have Ethernet interfaces thanks to an EtherTen (just like an Arduino Uno, but with built-in Ethernet and PoE) mounted inside. This episode shows the switchboard internals, including how the EtherTens switch output loads around the house. It also shows the termination of the house network that starts out as a bit of a mess, but is much neater by the end of the video.

    Links for this ep:

    Discuss this episode in the Freetronics forum: http://forum.freetronics.com/viewforum.php?f=30


    #1: Home Automation System Architecture

    12th Apr 12

    A basic introduction to the approach I'm taking with linking high-voltage devices in my house to the automation system, and how logical inputs are associated with outputs using MQTT.

    Apology: The "threat" I attribute to Andy in the video is not something he's ever said, or something I'd expect him to say. He's a peaceful soul, and it was purely hyperbole on my part when I was recording the video!

    Links for this ep:

    Discuss this episode in the Freetronics forum: http://forum.freetronics.com/viewforum.php?f=30

    Music credits: the track used in the intro and ending is "Interface" by ElectroLatrine, www.electrolatrine.net. Used with permission.


©2013 Jonathan Oxer Privacy Policy | Terms of Use | Site Map