<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>@BrooklynRowHouse</title>
  <link rel="alternate" type="text/html" href="http://www.brooklynrowhouse.com/x10/page6"/>
  <link rel="self" type="application/atom+xml" href="http://www.brooklynrowhouse.com/node/56/atom/feed"/>
  <id>http://www.brooklynrowhouse.com/node/56/atom/feed</id>
  <updated>2006-08-25T11:10:02-04:00</updated>
  <entry>
    <title>Building a whole-house X10 controller</title>
    <link rel="alternate" type="text/html" href="http://www.brooklynrowhouse.com/x10/page6" />
    <id>http://www.brooklynrowhouse.com/x10/page6</id>
    <published>2006-08-25T11:10:02-04:00</published>
    <updated>2006-08-25T11:10:02-04:00</updated>
    <author>
      <name>Steve</name>
    </author>
    <category term="home automation" />
    <category term="x10" />
    <summary type="html"><![CDATA[X10 transmitters and tabletop controllers are nice but to really explore the potential of X10 you need a central controller.  If you've scanned the X10 ecommerce sites you've probably seen a few, along with their frightening price tags.  Most of them are basically just PCs running a closed-source application.  You've already got a PC so why invest in another one?
<br />    ]]></summary>
    <content type="html"><![CDATA[X10 transmitters and tabletop controllers are nice but to really explore the potential of X10 you need a central controller.  If you've scanned the X10 ecommerce sites you've probably seen a few, along with their frightening price tags.  Most of them are basically just PCs running a closed-source application.  You've already got a PC so why invest in another one?
<br /><br />
There are lots of commercial and open source packages that will let you control your X10 devices from your own PC.  These packages range from professional turnkey systems like <a href="http://www.x10.com/activehomepro/activehome-pro.html" target="_new">ActiveHome</a> and <a href="http://www.smarthome.com/1132cu.html" target="_new">Smarthome Essential</a> to open-source, soup-to-nuts hobbyist packages like <a href="http://misterhouse.sourceforge.net/" target="_new">MisterHouse</a> to  Unix command line programs like <a href="http://heyu.tanj.com/" target="_new">Heyu</a> and <a href="http://www.jabberwocky.com/software/xtend/" target="_new">Xtend</a>.  The first two packages come with the hardware interface you'll need to link your computer to your X10 network.  The rest require you to purchase an inexpensive serial port interface like the <a href="http://www.smarthome.com/1140.html" target="_new">CM11A</a> controller.
<br /><br />
Commercial packages are really hard to resist, especially for the convenience and their modest cost (under $100).  Conversely, open source/free packages can be a real bear to set up, especially for non-techies, and they require your computer to be left on 24/7.  The only justification for using something like <a href="http://misterhouse.sourceforge.net/" target="_new">MisterHouse</a> is if you want the ultimate in home automation and an extendable platform for adding functionality not supported by commercial software.  You'll also have to be pretty competent hacker and like spending hours downloading, installing and debugging third-party packages.
<br /><br />
I'm one of them, nevertheless <a href="http://misterhouse.sourceforge.net/" target="_new">MisterHouse</a> was much more program than I needed so I wrote my own X10 daemon based on the <a href="http://heyu.tanj.com/" target="_new">Heyu</a> CLI software.
<br /><br />
First, install a CM11A controller on a free serial port on your Linux/FreeBSD/*ix box.  Note: right there is a good reason to consider one of the above commercial programs because they use the USB port. A lot of modern computers, especially laptops, don't have serial ports.
<br /><br />
Next, download, compile and set up <a href="http://heyu.tanj.com/" target="_new">Heyu</a>.  Your home's X10 configuration is set up in /etc/heyu/x10.conf:
<pre>
#
# this file should contain x10 appliance aliases, one per line, as:
#   appliance-name  housecode  modulenumber
# for example:
#  mydesklamp   A       4
#  atticfan     B       3

bedroom_nightstand              A       1
bedroom_ceiling1                A       2
bedroom_ceiling2                A       3
computer_room_cabs              A       5
computer_room_desk              A       6
back_bedroom_sconce             A       8
livingroom_ceiling              A       9
cabinets                        A       10
entryway_ceiling                A       13
upstairs_sconce                 A       14
outside_front                   A       15
diningroom_ceiling              A       16
kitchen_ceiling                 B       1
kitchen_sconces                 B       2
backyard_floods                 B       5
kitchen_ext_spots               B       6
kitchen_ext_sconces             B       7
garage_ceiling                  B       9
basement_ceiling                C       1
dsl_modem                       G       1
wireless_router                 G       2

# set default housecode -- the one the switches will use
HOUSECODE       A
#
# Set the serial port the program will use.
TTY     /dev/cuad0
# The NEWFORMAT directive allows version 1.28 and higher to display more
# informative messages.
NEWFORMAT
#
# The file listed with MACROXREF is where HEYU will store the addresses
# where macros are loaded in the CM11A.  If you don't use this directive,
# The macros addresses will not be cross-referenced.
MACROXREF       .x10macroxref
</pre>
Then test the <i>heyu</i> CLI command to see if it's working with something like:
<pre>
% heyu turn computer_room_desk on
</pre>
Then, install the perl module <i>Astro::Sunrise</i> and copy this software to <i>x10_scheduler.pl</i>
<pre>
#!/usr/bin/perl -w
use Astro::Sunrise;
use Time::Local;
use Sys::Syslog;

# Config for your house
use constant LATITUDE       => 40.641;
use constant LONGITUDE      => -74.016;
use constant TZ             => -5;
use constant DST            => 1;


# X10 device setup
# Device aliases are set up in /etc/heyu/x10.conf.
# These are the devices we're interested in and how we want
# them controlled.
my @DEVICES = (
    [ 'bedroom_nightstand', 'sunset-45', 'on' ],
    [ 'upstairs_sconce', 'sunset-45', 'on' ],
    [ 'entryway_ceiling', 'sunset-45', 'on' ],
    [ 'outside_front', 'sunset-30', 'on' ],
    [ 'cabinets', 'sunset-45', 'on' ],

    [ 'garage_ceiling', '23:01', 'off' ],

    [ 'upstairs_sconce', '00:01', 'dim+12' ],
    [ 'entryway_ceiling', '00:01', 'dim+15' ],
    [ 'outside_front', '00:01', 'off' ],
    [ 'cabinets', '23:01', 'off' ],
    [ 'basement_ceiling', '00:01', 'off' ],

    [ 'upstairs_sconce', '01:00', 'off' ],
    [ 'entryway_ceiling', '01:00', 'off' ],
    [ 'basement_ceiling', '01:00', 'off' ],
    [ 'computer_room_cabs', '01:00', 'off' ],
    [ 'bedroom_nightstand', '01:00', 'off' ],
);

$HEYU_CMD   = '/usr/local/bin/heyu turn %s %s';

use constant    SYSLOG_INFO     => 0;
use constant    SYSLOG_WARNING  => 1;
use constant    SYSLOG_FATAL    => 2;

#####################
### Program begins ##
#####################

openlog "x10_scheduler", "cons,pid", "local1";
syslogger(SYSLOG_INFO, "started");

# Prime today's date
my @last_run_time = localtime;
&process_devices_daily;

# Runs until killed with SIGHUP

while (1) {

    @this_run_time = localtime;

    # If the day has changed, reprocess the devices array
    if ($last_run_time[3] != $this_run_time[3]) {
        &process_devices_daily;
    }

    $time_now = time;

    for (my $i=0; $i &lt; scalar(@DEVICES); $i++) {

        if ($DEVICES[$i][4] eq 'do it' && $DEVICES[$i][3] &lt; $time_now) {

            my $cmd;

            # Is it an on/off type device?
            if ($DEVICES[$i][2] eq 'on' || $DEVICES[$i][2] eq 'off') {
                $cmd = sprintf($HEYU_CMD, $DEVICES[$i][0], $DEVICES[$i][2]);
                $DEVICES[$i][4] = 'done';
            }

            #  It must be a dim/bright device
            else {
                my ($dim, $offset) = split(/\+/, $DEVICES[$i][2]);
                $offset = '' unless defined $offset;

                $cmd = sprintf($HEYU_CMD, $DEVICES[$i][0], "$dim $offset");
                $DEVICES[$i][4] = 'done';
            }

            # Run the command
            syslogger(SYSLOG_INFO, "Running $cmd");
            system($cmd);
        }
    }

    # Log our last run time and take a nap for a minute
    @last_run_time = localtime;
    sleep 60;
}

# Process/reprocess the daily X10 events list.
# In daemon operation, this list will reload automatically at midnight.
sub process_devices_daily
{
    my $trigger_time;
    my @today = localtime;
    my $syslog_sunset = 0;

    for (my $i=0; $i &lt; scalar(@DEVICES); $i++) {

        if ($DEVICES[$i][1] =~ /^sun/) {
            my ($sun, $op, $offset) = $DEVICES[$i][1] =~ /^(\w+)([\-\+])(.*)$/;
            $op = '' unless defined $op;
            $offset = '' unless defined $offset;

            my ($sunrise, $sunset) = sunrise($today[5]+1900, $today[4]+1,
               $today[3], LONGITUDE, LATITUDE, TZ, DST);
            my $trigger_str = ($sun eq 'sunset') ? $sunset : $sunrise;

            if ($syslog_sunset++ == 0) {
                syslogger(SYSLOG_INFO, sprintf("Sunrise: $sunrise Sunset: $sunset"));
            }

            my ($hour, $minute) = split(/:/, $trigger_str);

            my @temp = localtime;
            $temp[2] = $hour;
            $temp[1] = $minute;
            $temp[0] = 0;

            $trigger_time = timelocal(@temp);
            $offset *= 60;

            $trigger_time = ($op eq '-') ?  $trigger_time - $offset : $trigger_time + $offset;
        }
        else {
            my ($hour, $minute) = split(/:/, $DEVICES[$i][1]);

            my @temp = localtime;
            $temp[2] = $hour;
            $temp[1] = $minute;
            $temp[0] = 0;

            $trigger_time = timelocal(@temp);
        }

        $DEVICES[$i][3] = $trigger_time;

        if ($trigger_time &lt; time) {
            $DEVICES[$i][4] = 'done';
        }
        else {
            $DEVICES[$i][4] = 'do it';
            syslogger(SYSLOG_INFO,
                sprintf("X10 event today: $DEVICES[$i][0] ACTION=$DEVICES[$i][2] TIME= %02d:%02d",
                 (localtime($trigger_time))[2], (localtime($trigger_time))[1]));
        }
    }
}

sub syslogger
{
    if ($_[0] == SYSLOG_FATAL) {
        syslog('warning', "FATAL: $_[1]");
        exit 0;
    }
    if ($_[0] == SYSLOG_WARNING) {
        syslog('warning', "WARNING: $_[1]");
    }
    else {
        syslog('notice', "$_[1]");
    }
}
</pre>
After you've set up your configuration and X10 events in the program just launch it in the background:
<pre>
% perl x10_scheduler.pl&
</pre>
If you don't know your latitude/longitude you can get it <a href="http://jan.ucc.nau.edu/~cvm/latlon_find_location.html" target="_new">here</a>.
<br /><br />
By the way, if you have a real sharp eye you probably noticed the "dsl_modem" and "wireless_router" device aliases in my <i>/etc/heyu/x10.conf</i> file.  Both of these are on X10 appliance modules.  One of the FreeBSD boxes runs a shell script out of cron which periodically pings a set of IP addresses outside my network.  If they all fail, it runs a <i>heyu</i> task which turns them off for 15 seconds and turns them back on again, effectively rebooting them.
<br /><br />
    ]]></content>
  </entry>
</feed>
