Sunday, February 23, 2014

Raspberry Pi, USB, and XBee

I have two XBees hooked to my Pi now, one to control the network I've been building for a long time hooked to the serial port, and the other acting as a controller for my Iris Smart Switch hooked into a USB port.  I like the Smart Switch, so I'll be using more of them and that meant I had to figure out a better way to hook two XBees to the Pi and make it work.

I really didn't want to get into the mess of trying to get another standard serial port working because I eventually want LEDs for indicators of things that are important and maybe even a display that I can put words on.  Heck, I could decide to put voice on it at some point.  So, since the Pi comes with two USB ports, I decided to move both of the XBees to USB.  I started out with the USB explorer that I have to program the XBees:


I've had this board a long time and it just works.  Never had a problem with it, so I just plugged it into one of the USB ports on the Pi and changed my code to point to it.  It worked after a tiny bit of messing around.  Now I could control my own stuff and read the Smart Switch as well.  That proved that it could be done so I got two of these:


I chose this board so I could use a short USB cable to connect it, avoiding the problem of the bigger board above not fitting.  With these plugged into the Pi, I could still control the house and have all my GPIO pins available for whatever else I decide to do someday.  But, as usual, there was a problem.  See, USB is designed to be plugged and unplugged so you can change devices on the Pi, that unfortunately means that the address of the XBee will change each time you unplug it from the USB port.  So, one time I would have the smart switch on /dev/ttyUSB0 and the next time it would show up on /dev/ttyUSB1.  Makes coding for it more complex, and I really don't want complex right now.

It turns out that there is another way to connect to it.  Under the directory /dev is another directory 'serial'; looking in /dev/serial, I found two other directories by-id and by-path.  The by-id directory holds the device ids of the USB to serial chip and that doesn't change as you plug and unplug it.  So, I changed my code to define the serial port to this:

XBEEPORT = '/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A901QLG3-if00-port0'

Yes, it's long and obscure, but that's what variables and comments are for.  It's also hard to type in, but that's what copy and paste are good for.  All in all, not a bad way to go as long as I don't reverse which little board I have the XBee plugged into.  Remember this little trick when your USB device seems to disappear.

So, now I have all the GPIO pins available; wonder what I'm going to plug into them.

Tuesday, February 11, 2014

Using the Iris Smart Switch to Measure Appliance Power

Since I finished the example of how to operate the Iris Smart Switch with a Raspberry Pi, I couldn't wait to hook it to something and do something real with it.  I decided to measure the power usage of my refrigerator.  First, about the refrigerator:  It's a refrigerator only, it doesn't have a freezer section; I have a separate freezer in the kitchen also.  There's a reason for this besides showing off.  Out here in the sticks where a trip to the grocery store is a major undertaking, we don't go very often.  That means plenty of food storage; I have a pantry, an upright freezer and separate refrigerator in the house and a chest freezer in the garage.  Yes, I can go about two weeks before I even have to go for bread and milk.  This makes things like a shopping list that is up to date a valuable skill.  I keep it up to date and every time I'm forced into town, it goes with me.  I don't want to waste a trip.

Anyway, I put together some code and read the switch, recorded the readings in my database and updated Xively with the data.  Then I suck the accumulated data from Xively and graph it to see what the appliance is doing.  I didn't implement the ability to turn the switch on and off since who wants to shut their refrigerator off??  Here's a days worth of data on the refrigerator:



Notice how it's only on for a short amount of time and that, other than the spike when the compressor starts, it doesn't use much power?  It's on roughly 15 minutes, and off for about 30 minutes in repeating cycles.  Here is an expansion of a portion of it:


The large spike on the first 'hump' is where the compressor kicks on.  It takes a lot of power to get the compressor going, but it drops off rapidly.  The granularity of the readings is only a minute, and if I was graphing every few seconds, you would see spikes on all the humps.  However, this is an accurate representation of what is happening overall.  Notice that the 550W spike is short and that the current draw decreases over time.  The average usage is probably around 110W or so for the period where the compressor is on.  That's the amount of energy used by some incandescent bulbs !  Let's see what a door open does to the chart:



Notice that the power usage jumps (the hump on the right) to 300W when the door is open?  It's pretty sobering that the power used by the lights when the door is open is actually more than the power used to keep the darn thing cold.  Now see why they say to close the door to save energy?  They just didn't tell us that the power loss was due to the bulbs, not the fact that we're warming up the fridge.  I'll probably get arguments from the energy nerds out there, but for my appliance, this is true.  Or, at least it is right now; I plan on getting LED bulbs to take care of that silly problem in the next couple of weeks.

Of course, there's the compressor kicking on right after I closed the door because I left the door open for a couple of minutes and it had to recover the temperature.  That's why the hump for the lights is on the far right, I actually went over and opened the door to get this reading.

All in all though, the fridge is not a huge power consumer.  It's less than running a 100W bulb all the time.  Actually, it's probably close to a 40W bulb since it's off twice as long as it's on in any given period.

Let's review.  I hacked into the Lowe's smart switch using an arduino (it's what I had to play with)<link>, ported the code to python so I could run it on a Raspberry Pi <link>, and then hooked it up to an appliance and learned that it was actually doing a good job.  Actually better than I expected.  Was it worth it?  Of course it was.  Now folks out there can take my example and measure their own appliances to compare them to their own expectations and needs.

Soon, I'll get another Smart Switch and put it in the circuit for the freezer and see how much power it uses.  I expect it to be higher than the fridge because it has the same set of lights, a bigger compressor and the circuitry for auto defrost.  Auto defrost is a real power monger since it actually warms up some areas of the appliance and drains the water out to be evaporated away.  I also want to see how often it cycles since it could happen that the compressor on the freezer and the compressor on the fridge could overlap their cycles.  That would cause a spike in power usage for the overlap period, which in turn, could cause my demand usage to rise.  That's the kind of thing I want to keep track of.  Demand billing is a painful thing because a simple mistake in power usage could double your monthly power bill.  I have a long discussion about my experiences here <link>.

Now, go out and get your own system of monitors running.  Take control of your house.

Addendum: As anyone can tell, I'm all about measuring things around the house to keep it under control, Power, time, temperature, runtime, etc are awesome tools to get your bills under control. But I also have calipers, micrometers, rulers and that kind of thing.

But with a smartphone, a whole new realm opens up. Take a look here for some stuff that I never even knew existed until I started looking for it.

https://joyofandroid.com/best-measuring-apps-for-android/

And that's just the beginning.

Monday, February 10, 2014

Raspberry Pi and the Lowe's Iris Smart Switch

So, I got the Lowe's Iris Smart Switch working pretty well for the Arduino <link>.  The problem is that it isn't where I want the software to run.  I have a Raspberry Pi controlling the house and this software should go there.  As I mentioned, I don't have a spare Pi right now, so I worked up the software for the Arduino with the full intention of moving it to the Pi as soon as it worked and I had time to mess with it.

Well, I decided the cool thing to do was to port the test software directly to the Pi in python and run it there.  I approached this with a little trepidation; taking things from an Arduino to another platform and language can drive you nuts.  First, I already have an XBee attached to the Pi; it uses the (only) serial port on the little device, so I got a Sparkfun XBee explorer <link> and plugged it into the USB port.  Fully expecting to have to jump through a bunch of hoops to get it to work, I did a simple 'cat /dev/ttyUSB0' command and actually got output to the screen on the very first try!

Sure it was garbage and didn't mean much, but I got output that corresponded to pushing the button on the switch.  Step one was done.  Next I put together a little code using the python XBee library to catch a packet and see what happened.  Right off the bat, I got this printed on the console of the Pi:

{'profile': '\xc2\x16', 'source_addr': '+\xd1', 'dest_endpoint': '\x02', 'rf_data': '\t\x00\x81T\x00', 'source_endpoint': '\x02', 'options': '\x01', 'source_addr_long': '\x00\ro\x00\x027\xb2Z', 'cluster': '\x00\xef', 'id': 'rx_explicit'}

Holy Cow, the library already had support for the ZigBee specific messages!  Notice that the fields have names already, and the ones that it took me so long to figure out are already there.  This means I can jump right in and start taking the messages from the switch apart.  It worked like a charm; there were some understanding problems in that the XBee library returns the data as character strings inside a dictionary of the various fields in a message, but these can be overcome once you catch on to what is happening.  Once I could decode the messages and print the power values and state of the switch, I implemented the commands from the Arduino code and they worked quite well.  So, here are the same capabilities that I presented for the Arduino implemented on the Raspberry Pi:

The Python Script
#! /usr/bin/python
# This is the an implementation of controlling the Lowe's Iris Smart
# Switch.  It will join with a switch and allow you to control the switch
#
#  Only ONE switch though.  This implementation is a direct port of the 
# work I did for an Arduino and illustrates what needs to be done for the 
# basic operation of the switch.  If you want more than one switch, you can
# adapt this code, or use the ideas in it to make your own control software.
#
# Have fun

from xbee import ZigBee 
from apscheduler.scheduler import Scheduler
import logging
import datetime
import time
import serial
import sys
import shlex


#-------------------------------------------------
# the database where I'm storing stuff
DATABASE='/home/pi/database/desert-home'

# on the Raspberry Pi the serial port is ttyAMA0
XBEEPORT = '/dev/ttyUSB0'
XBEEBAUD_RATE = 9600

# The XBee addresses I'm dealing with
BROADCAST = '\x00\x00\x00\x00\x00\x00\xff\xff'
UNKNOWN = '\xff\xfe' # This is the 'I don't know' 16 bit address

switchLongAddr = '12'
switchShortAddr = '12'

#-------------------------------------------------
logging.basicConfig()

#------------ XBee Stuff -------------------------
# Open serial port for use by the XBee
ser = serial.Serial(XBEEPORT, XBEEBAUD_RATE)

# this is a call back function.  When a message
# comes in this function will get the data
def messageReceived(data):
#   print 'gotta packet' 
#   print data
    # This is a test program, so use global variables and
    # save the addresses so they can be used later
    global switchLongAddr
    global switchShortAddr
    switchLongAddr = data['source_addr_long'] 
    switchShortAddr = data['source_addr']
    clusterId = (ord(data['cluster'][0])*256) + ord(data['cluster'][1])
    print 'Cluster ID:', hex(clusterId),
    if (clusterId == 0x13):
        # This is the device announce message.
        # due to timing problems with the switch itself, I don't 
        # respond to this message, I save the response for later after the
        # Match Descriptor request comes in.  You'll see it down below.
        # if you want to see the data that came in with this message, just
        # uncomment the 'print data' comment up above
        print 'Device Announce Message'
    elif (clusterId == 0x8005):
        # this is the Active Endpoint Response This message tells you
        # what the device can do, but it isn't constructed correctly to match 
        # what the switch can do according to the spec.  This is another 
        # message that gets it's response after I receive the Match Descriptor
        print 'Active Endpoint Response'
    elif (clusterId == 0x0006):
        # Match Descriptor Request; this is the point where I finally
        # respond to the switch.  Several messages are sent to cause the 
        # switch to join with the controller at a network level and to cause
        # it to regard this controller as valid.
        #
        # First the Active Endpoint Request
        payload1 = '\x00\x00'
        zb.send('tx_explicit',
            dest_addr_long = switchLongAddr,
            dest_addr = switchShortAddr,
            src_endpoint = '\x00',
            dest_endpoint = '\x00',
            cluster = '\x00\x05',
            profile = '\x00\x00',
            data = payload1
        )
        print 'sent Active Endpoint'
        # Now the Match Descriptor Response
        payload2 = '\x00\x00\x00\x00\x01\x02'
        zb.send('tx_explicit',
            dest_addr_long = switchLongAddr,
            dest_addr = switchShortAddr,
            src_endpoint = '\x00',
            dest_endpoint = '\x00',
            cluster = '\x80\x06',
            profile = '\x00\x00',
            data = payload2
        )
        print 'Sent Match Descriptor'
        # Now there are two messages directed at the hardware
        # code (rather than the network code.  The switch has to 
        # receive both of these to stay joined.
        payload3 = '\x11\x01\x01'
        zb.send('tx_explicit',
            dest_addr_long = switchLongAddr,
            dest_addr = switchShortAddr,
            src_endpoint = '\x00',
            dest_endpoint = '\x02',
            cluster = '\x00\xf6',
            profile = '\xc2\x16',
            data = payload2
        )
        payload4 = '\x19\x01\xfa\x00\x01'
        zb.send('tx_explicit',
            dest_addr_long = switchLongAddr,
            dest_addr = switchShortAddr,
            src_endpoint = '\x00',
            dest_endpoint = '\x02',
            cluster = '\x00\xf0',
            profile = '\xc2\x16',
            data = payload4
        )
        print 'Sent hardware join messages'

    elif (clusterId == 0xef):
        clusterCmd = ord(data['rf_data'][2])
        if (clusterCmd == 0x81):
            print 'Instantaneous Power',
            print ord(data['rf_data'][3]) + (ord(data['rf_data'][4]) * 256)
        elif (clusterCmd == 0x82):
            print "Minute Stats:",
            print 'Usage, ',
            usage = (ord(data['rf_data'][3]) +
                (ord(data['rf_data'][4]) * 256) +
                (ord(data['rf_data'][5]) * 256 * 256) +
                (ord(data['rf_data'][6]) * 256 * 256 * 256) )
            print usage, 'Watt Seconds ',
            print 'Up Time,',
            upTime = (ord(data['rf_data'][7]) +
                (ord(data['rf_data'][8]) * 256) +
                (ord(data['rf_data'][9]) * 256 * 256) +
                (ord(data['rf_data'][10]) * 256 * 256 * 256) )
            print upTime, 'Seconds'
    elif (clusterId == 0xf0):
        clusterCmd = ord(data['rf_data'][2])
        print "Cluster Cmd:", hex(clusterCmd),
        if (clusterCmd == 0xfb):
            print "Temperature ??"
        else:
            print "Unimplemented"
    elif (clusterId == 0xf6):
        clusterCmd = ord(data['rf_data'][2])
        if (clusterCmd == 0xfd):
            print "RSSI value:", ord(data['rf_data'][3])
        elif (clusterCmd == 0xfe):
            print "Version Information"
        else:
            print data['rf_data']
    elif (clusterId == 0xee):
        clusterCmd = ord(data['rf_data'][2])
        if (clusterCmd == 0x80):
            print "Switch is:",
            if (ord(data['rf_data'][3]) & 0x01):
                print "ON"
            else:
                print "OFF"
    else:
        print "Unimplemented Cluster ID", hex(clusterId)
        print

def sendSwitch(whereLong, whereShort, srcEndpoint, destEndpoint, 
                clusterId, profileId, clusterCmd, databytes):
    
    payload = '\x11\x00' + clusterCmd + databytes
    # print 'payload',
    # for c in payload:
        # print hex(ord(c)),
    # print
    # print 'long address:',
    # for c in whereLong:
        # print hex(ord(c)),
    # print
        
    zb.send('tx_explicit',
        dest_addr_long = whereLong,
        dest_addr = whereShort,
        src_endpoint = srcEndpoint,
        dest_endpoint = destEndpoint,
        cluster = clusterId,
        profile = profileId,
        data = payload
        )
    
#------------------If you want to schedule something to happen -----
#scheditem = Scheduler()
#scheditem.start()

#scheditem.add_interval_job(something, seconds=sometime)

#-----------------------------------------------------------------

# Create XBee library API object, which spawns a new thread
zb = ZigBee(ser, callback=messageReceived)

print "started at ", time.strftime("%A, %B, %d at %H:%M:%S")
print "Enter a number from 0 through 8 to send a command"
while True:
    try:
        time.sleep(0.001)
        str1 = raw_input("")
        # Turn Switch Off
        if(str1[0] == '0'):
            print 'Turn switch off'
            databytes1 = '\x01'
            databytesOff = '\x00\x01'
            sendSwitch(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xee', '\xc2\x16', '\x01', databytes1)
            sendSwitch(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xee', '\xc2\x16', '\x02', databytesOff)
        # Turn Switch On
        if(str1[0] == '1'):
            print 'Turn switch on'
            databytes1 = '\x01'
            databytesOn = '\x01\x01'
            sendSwitch(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xee', '\xc2\x16', '\x01', databytes1)
            sendSwitch(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xee', '\xc2\x16', '\x02', databytesOn)
        # this goes down to the test routine for further hacking
        elif (str1[0] == '2'):
            #testCommand()
            print 'Not Implemented'
        # This will get the Version Data, it's a combination of data and text
        elif (str1[0] == '3'):
            print 'Version Data'
            databytes = '\x00\x01'
            sendSwitch(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xf6', '\xc2\x16', '\xfc', databytes)
        # This command causes a message return holding the state of the switch
        elif (str1[0] == '4'):
            print 'Switch Status'
            databytes = '\x01'
            sendSwitch(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xee', '\xc2\x16', '\x01', databytes)
        # restore normal mode after one of the mode changess that follow
        elif (str1[0] == '5'):
            print 'Restore Normal Mode'
            databytes = '\x00\x01'
            sendSwitch(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xf0', '\xc2\x16', '\xfa', databytes)
        # range test - periodic double blink, no control, sends RSSI, no remote control
        # remote control works
        elif (str1[0] == '6'):
            print 'Range Test'
            databytes = '\x01\x01'
            sendSwitch(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xf0', '\xc2\x16', '\xfa', databytes)
        # locked mode - switch can't be controlled locally, no periodic data
        elif (str1[0] == '7'):
            print 'Locked Mode'
            databytes = '\x02\x01'
            sendSwitch(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xf0', '\xc2\x16', '\xfa', databytes)
        # Silent mode, no periodic data, but switch is controllable locally
        elif (str1[0] == '8'):
            print 'Silent Mode'
            databytes = '\x03\x01'
            sendSwitch(switchLongAddr, switchShortAddr, '\x00', '\x02', '\x00\xf0', '\xc2\x16', '\xfa', databytes)
#       else:
#           print 'Unknown Command'
    except IndexError:
        print "empty line"
    except KeyboardInterrupt:
        print "Keyboard interrupt"
        break
    except NameError as e:
        print "NameError:",
        print e.message.split("'")[1]
    except:
        print "Unexpected error:", sys.exc_info()[0]
        break

print "After the while loop"
# halt() must be called before closing the serial
# port in order to ensure proper thread shutdown
zb.halt()
ser.close()

Just like the Arduino code, this will allow a switch to join and then it will constantly update based on messages from the switch.  Here's some sample output from a run of this code:

Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Minute Stats: Usage,  60285 Watt Seconds  Up Time, 1200 Seconds
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Minute Stats: Usage,  65266 Watt Seconds  Up Time, 1260 Seconds
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 84
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Minute Stats: Usage,  70250 Watt Seconds  Up Time, 1320 Seconds
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Minute Stats: Usage,  75230 Watt Seconds  Up Time, 1380 Seconds
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Minute Stats: Usage,  80213 Watt Seconds  Up Time, 1440 Seconds
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Minute Stats: Usage,  85194 Watt Seconds  Up Time, 1500 Seconds
Cluster ID: 0xef Instantaneous Power 84
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xf0 Cluster Cmd: 0xfb Temperature ??
Cluster ID: 0xef Instantaneous Power 83
Cluster ID: 0xef Instantaneous Power 83

I had a little light hooked up to it that has two 40W incandescent bulbs in it so there was something to show.

Now I have the basics of reading the switch, and all I have to do now is hook it up with the rest of the software in the House Controller.  Then I can place these things wherever I want either, control of the power, or a measurement of how much power is being used.  Very nice little switch; I couldn't have built one for the price off the shelf at Lowe's.  And most importantly to me, I have control of it, not some cloud server or control device that I have to rely on a corporation's whim to change to fit my needs.

Have fun.

Thursday, February 6, 2014

Arduino and the Iris Zigbee switch, part 3

I've been working with the Iris Smart Switch for three weeks or so and I believe I have enough hacked out of it to make a useful addition to my home automation.  As I mentioned before, it controls an appliance as well as measuring the power usage of the device plugged into it.  Below is the code I ended up with in testing the device.  It's pretty verbose in the output as well as having a ton of comments to help the next person that wants to dig into this switch.

There are eight selections that can be chosen by typing a number into the arduino IDE input line and pressing send:

0 - turn the switch off
1 - turn the switch on
2 - special command routine
3 - get version data
4 - get current switch state (on, off)
5 - reset switch to normal mode
6 - range test
7 - local lock mode
8 - silent Mode

The special command routine is where you can try various commands to the switch.  Simply add code to your requirements and have at it. I used this selection to find various commands.  Range test returns the RSSI value for the switch and can be useful to tell if you have an RF path back to the controller.  Local lock mode disables the button on the switch, it can still be remote controlled, in this mode it doesn't return the periodic power data.  Silent Mode allows local control with the button as well as remote control, but the periodic data is not returned.

There's probably commands and operational characteristics to be found, but I think this is the critical set to put the switch into use.  My next project will be to actually use the darn thing to do something useful.

The Arduino Sketch
/**
This is an examination of Zigbee device communication using an XBee
and an Iris Smart Switch from Lowe's
*/
 
#include <XBee.h>
#include <SoftwareSerial.h>
#include <Time.h>
#include <TimeAlarms.h>

XBee xbee = XBee();
XBeeResponse response = XBeeResponse();
// create reusable response objects for responses we expect to handle 
ZBExpRxResponse rx = ZBExpRxResponse();

// Define NewSoftSerial TX/RX pins
// Connect Arduino pin 2 to Tx and 3 to Rx of the XBee
// I know this sounds backwards, but remember that output
// from the Arduino is input to the Xbee
#define ssRX 2
#define ssTX 3
SoftwareSerial nss(ssRX, ssTX);

XBeeAddress64 switchLongAddress;
uint16_t switchShortAddress;
uint16_t payload[50];
uint16_t myFrameId=1;

void setup() {  
  // start serial
  Serial.begin(9600);
  // and the software serial port
  nss.begin(9600);
  // now that they are started, hook the XBee into 
  // Software Serial
  xbee.setSerial(nss);
  // I think this is the only line actually left over
  // from Andrew's original example
  setTime(0,0,0,1,1,14);  // just so alarms work well, I don't really need the time.
  Serial.println("started");
}

void loop() {
    // doing the read without a timer makes it non-blocking, so
    // you can do other stuff in loop() as well.  Things like
    // looking at the console for something to turn the switch on
    // or off (see waaay down below)
    xbee.readPacket();
    // so the read above will set the available up to 
    // work when you check it.
    if (xbee.getResponse().isAvailable()) {
      // got something
//      Serial.println();
//      Serial.print("Frame Type is ");
      // Andrew called the XBee frame type ApiId, it's the first byte
      // of the frame specific data in the packet.
//      Serial.println(xbee.getResponse().getApiId(), HEX);
      //
      // All ZigBee device interaction is handled by the two XBee message type
      // ZB_EXPLICIT_RX_RESPONSE (ZigBee Explicit Rx Indicator Type 91)
      // ZB_EXPLICIT_TX_REQUEST (Explicit Addressing ZigBee Command Frame Type 11)
      // This test code only uses these and the Transmit Status message
      //
      if (xbee.getResponse().getApiId() == ZB_EXPLICIT_RX_RESPONSE) {
        // now that you know it's a Zigbee receive packet
        // fill in the values
        xbee.getResponse().getZBExpRxResponse(rx);
        
        // get the 64 bit address out of the incoming packet so you know 
        // which device it came from
//        Serial.print("Got a Zigbee explicit packet from: ");
        XBeeAddress64 senderLongAddress = rx.getRemoteAddress64();
//        print32Bits(senderLongAddress.getMsb());
//        Serial.print(" ");
//        print32Bits(senderLongAddress.getLsb());
        
        // this is how to get the sender's
        // 16 bit address and show it
        uint16_t senderShortAddress = rx.getRemoteAddress16();
//        Serial.print(" (");
//        print16Bits(senderShortAddress);
//        Serial.println(")");
        
        // for right now, since I'm only working with one switch
        // save the addresses globally for the entire test module
        switchLongAddress = rx.getRemoteAddress64();
        switchShortAddress = rx.getRemoteAddress16();

        //Serial.print("checksum is 0x");
        //Serial.println(rx.getChecksum(), HEX);
        
        // this is the frame length
        //Serial.print("frame data length is ");
        int frameDataLength = rx.getFrameDataLength();
        //Serial.println(frameDataLength, DEC);
        
        uint8_t* frameData = rx.getFrameData();
        // display everything after first 10 bytes
        // this is the Zigbee data after the XBee supplied addresses
//        Serial.println("Zigbee Specific Data from Device: ");
//        for (int i = 10; i < frameDataLength; i++) {
//          print8Bits(frameData[i]);
//          Serial.print(" ");
//        }
//        Serial.println();
        // get the source endpoint
//        Serial.print("Source Endpoint: ");
//        print8Bits(rx.getSrcEndpoint());
//        Serial.println();
        // byte 1 is the destination endpoint
//        Serial.print("Destination Endpoint: ");
//        print8Bits(rx.getDestEndpoint());
//        Serial.println();
        // bytes 2 and 3 are the cluster id
        // a cluster id of 0x13 is the device announce message
//        Serial.print("Cluster ID: ");
        uint16_t clusterId = (rx.getClusterId());
        print16Bits(clusterId);
        Serial.print(": ");
        // bytes 4 and 5 are the profile id
//        Serial.print("Profile ID: ");
//        print16Bits(rx.getProfileId());
//        Serial.println();
//        // byte 6 is the receive options
//        Serial.print("Receive Options: ");
//        print8Bits(rx.getRxOptions());
//        Serial.println();
//        Serial.print("Length of RF Data: ");
//        Serial.print(rx.getRFDataLength());
//        Serial.println();
//        Serial.print("RF Data Received: ");
//        for(int i=0; i < rx.getRFDataLength(); i++){
//            print8Bits(rx.getRFData()[i]);
//            Serial.print(' ');
//        }
//        Serial.println();
        //
        // I have the message and it's from a ZigBee device
        // so I have to deal with things like cluster ID, Profile ID
        // and the other strangely named fields that these devices use
        // for information and control
        //
        if (clusterId == 0x13){
          Serial.println("*** Device Announce Message");
          // In the announce message:
          // the next bytes are a 16 bit address and a 64 bit address (10) bytes
          // that are sent 'little endian' which means backwards such
          // that the most significant byte is last.
          // then the capabilities byte of the actual device, but
          // we don't need some of them because the XBee does most of the work 
          // for us.
          //
          // so save the long and short addresses
          switchLongAddress = rx.getRemoteAddress64();
          switchShortAddress = rx.getRemoteAddress16();
          // the data carried by the Device Announce Zigbee messaage is 18 bytes over
          // 2 for src & dest endpoints, 4 for cluster and profile ID, 
          // receive options 1, sequence number 1, short address 2, 
          // long address 8 ... after that is the data specific to 
          // this Zigbee message
//          Serial.print("Sequence Number: ");
//          print8Bits(rx.getRFData()[0]);
//          Serial.println();
//          Serial.print("Device Capabilities: ");
//          print8Bits(rx.getRFData()[11]);
//          Serial.println();
        }
        if (clusterId == 0x8005){ // Active endpoint response
          Serial.println("*** Active Endpoint Response");
          // You should get a transmit responnse packet back from the
          // XBee first, this will tell you the other end received 
          // something.
          // Then, an Active Endpoint Response from the end device
          // which will be Source Endpoint 0, Dest Endpoint 0,
          // Cluster ID 8005, Profile 0
          // it will have a payload, but the format returned by the 
          // Iris switch doesn't match the specifications.
          //
          // Also, I tried responding to this message directly after
          // its receipt, but that didn't work.  When I moved the 
          // response to follow the receipt of the Match Descriptor
          // Request, it started working.  So look below for where I 
          // send the response
        }
        if (clusterId == 0x0006){ // Match descriptor request
          Serial.println("*** Match Descriptor Request");
          // This is where I send the Active Endpoint Request 
          // which is endpoint 0x00, profile (0), cluster 0x0005
          uint8_t payload1[] = {0,0};
          ZBExpCommand tx = ZBExpCommand(switchLongAddress,
            switchShortAddress,
            0,    //src endpoint
            0,    //dest endpoint
            0x0005,    //cluster ID
            0x0000, //profile ID
            0,    //broadcast radius
            0x00,    //option
            payload1, //payload
            sizeof(payload1),    //payload length
            myFrameId++);   // frame ID
          xbee.send(tx);
//          Serial.println();
          //sendSwitch(0, 0, 0x0005, 0x0000, 0, 0, 0);

          Serial.print("sent active endpoint request ");
          //
          // So, send the next message, Match Descriptor Response,
          // cluster ID 0x8006, profile 0x0000, src and dest endpoints
          // 0x0000; there's also a payload byte
          //
          // {00.02} gave clicks
          uint8_t payload2[] = {0x00,0x00,0x00,0x00,0x01,02};
          tx = ZBExpCommand(switchLongAddress,
            switchShortAddress,
            0,    //src endpoint
            0,    //dest endpoint
            0x8006,    //cluster ID
            0x0000, //profile ID
            0,    //broadcast radius
            0x00,    //option
            payload2, //payload
            sizeof(payload2),    //payload length
            myFrameId++);   // frame ID
          xbee.send(tx);
//          Serial.println();
          Serial.print("sent Match Descriptor Response frame ID: ");
          Serial.println(myFrameId-1);
            
          //
          // Odd hardware message #1.  The next two messages are related 
          // to control of the hardware.  The Iris device won't stay joined with
          // the coordinator without both of these messages
          //
          uint8_t payload3[] = {0x11,0x01,0x01};
          tx = ZBExpCommand(switchLongAddress,
            switchShortAddress,
            0,    //src endpoint
            2,    //dest endpoint
            0x00f6,    //cluster ID
            0xc216, //profile ID
            0,    //broadcast radius
            0x00,    //option
            payload3, //payload
            sizeof(payload3),    //payload length
            myFrameId++);   // frame ID
            xbee.send(tx);
//            Serial.println();
            Serial.print("sent funny hardware message #1 frame ID: ");
            Serial.println(myFrameId-1);
            //
            // Odd hardware message #2
            //
            uint8_t payload4[] = {0x19,0x01,0xfa,0x00,0x01};
            tx = ZBExpCommand(switchLongAddress,
              switchShortAddress,
              0,    //src endpoint
              2,    //dest endpoint
              0x00f0,    //cluster ID
              0xc216, //profile ID
              0,    //broadcast radius
              0x00,    //option
              payload4, //payload
              sizeof(payload4),    //payload length
              myFrameId++);   // frame ID
              xbee.send(tx);
//              Serial.println();
              Serial.print("sent funny hardware message #2 frame ID: ");
              Serial.println(myFrameId-1);
            
        }
        else if (clusterId == 0xf6){
          // This is The Range Test command response.
          Serial.print("Cluster Cmd: ");
          Serial.print(rx.getRFData()[2],HEX);
          Serial.print(" ");
          Serial.print("*** Cluster ID 0xf6 ");
          if (rx.getRFData()[2] == 0xfd){
            Serial.print("RSSI value: ");
            print8Bits(rx.getRFData()[3]);
            Serial.print(" ");
            print8Bits(rx.getRFData()[4]);
            Serial.print(" ");
            Serial.print((int8_t)rx.getRFData()[3]);
            Serial.println();
          }
          else if (rx.getRFData()[2] == 0xfe){
            Serial.println("Version information");
            // bytes 0 - 2 are the packet overhead
            // This can be decoded to give the data from the switch,
            // but frankly, I didn't know what I would do with it 
            // once decoded, so I just skip it.
          }
          // This is to catch anything that may pop up in testing
          else{
            Serial.print(rx.getRFData()[2],HEX);
            Serial.print(" ");
            for(int i=0; i < rx.getRFDataLength(); i++){
              print8Bits(rx.getRFData()[i]);
              Serial.print(' ');
            }
            Serial.println();
          }
        }
        if (clusterId == 0x00f0){
//          Serial.println("Most likely a temperature reading; useless");
//          for(int i=0; i < rx.getRFDataLength(); i++){
//              print8Bits(rx.getRFData()[i]);
//              Serial.print(' ');
//          }
//          Serial.println();
          Serial.print("Cluster Cmd: ");
          Serial.print(rx.getRFData()[2],HEX);
          Serial.print(" ");
          uint16_t count = (uint8_t)rx.getRFData()[5] + 
              ((uint8_t)rx.getRFData()[6] << 8);
          Serial.print("Count: ");
          Serial.print(count);
          Serial.print(" ");
          Serial.print(rx.getRFData()[12]);
          Serial.print(" ");
          Serial.print(rx.getRFData()[13]);
          Serial.print(" ");
          uint16_t temp =  (uint8_t)rx.getRFData()[12] + 
              ((uint8_t)rx.getRFData()[13] << 8);
          Serial.println(temp);
//          temp = (temp / 1000) * 9 / 5 + 32;
//          Serial.println(temp);

        }
        if (clusterId == 0x00ef){
          //
          // This is a power report, there are two kinds, instant and summary
          //
          Serial.print("Cluster Cmd: ");
          Serial.print(rx.getRFData()[2],HEX);
          Serial.print(" ");
          Serial.print("*** Power Data, ");
          // The first byte is what Digi calls 'Frame Control'
          // The second is 'Transaction Sequence Number'
          // The third is 'Command ID'
          if (rx.getRFData()[2] == 0x81){
            // this is the place where instant power is sent
            // but it's sent 'little endian' meaning backwards
            int power = rx.getRFData()[3] + (rx.getRFData()[4] << 8);
            Serial.print("Instantaneous Power is: ");
            Serial.println(power);
          }
          else if (rx.getRFData()[2] == 0x82){
            unsigned long minuteStat = (uint32_t)rx.getRFData()[3] + 
              ((uint32_t)rx.getRFData()[4] << 8) + 
              ((uint32_t)rx.getRFData()[5] << 16) + 
              ((uint32_t)rx.getRFData()[6] << 24);
            unsigned long uptime = (uint32_t)rx.getRFData()[7] + 
              ((uint32_t)rx.getRFData()[8] << 8) + 
              ((uint32_t)rx.getRFData()[9] << 16) + 
              ((uint32_t)rx.getRFData()[10] << 24);
            int resetInd = rx.getRFData()[11];
            Serial.print("Minute Stat: ");
            Serial.print(minuteStat);
            Serial.print(" watt seconds, Uptime: ");
            Serial.print(uptime);
            Serial.print(" seconds, Reset Ind: ");
            Serial.println(resetInd);
          }
        }
        if (clusterId == 0x00ee){
          //
          // This is where the current status of the switch is reported
          //
          // If the 'cluster command' is 80, then it's a report, there
          // are other cluster commands, but they are controls to change
          // the switch.  I'm only checking the low order bit of the first
          // byte; I don't know what the other bits are yet.
          if (rx.getRFData()[2] == 0x80){
            Serial.print("Cluster Cmd: ");
            Serial.print(rx.getRFData()[2],HEX);
            Serial.print(" ");
            if (rx.getRFData()[3] & 0x01)
              Serial.println("Light switched on");
            else
              Serial.println("Light switched off");
          }
        }
      }
      else if (xbee.getResponse().getApiId() == ZB_TX_STATUS_RESPONSE) {
        ZBTxStatusResponse txStatus;
        xbee.getResponse().getZBTxStatusResponse(txStatus);
        Serial.print("Status Response: ");
        Serial.println(txStatus.getDeliveryStatus(), HEX);
        Serial.print("To Frame ID: ");
        Serial.println(txStatus.getFrameId());
      }
      else {
        Serial.print("Got frame type: ");
        Serial.println(xbee.getResponse().getApiId(), HEX);
      }
    }
    else if (xbee.getResponse().isError()) {
      // some kind of error happened, I put the stars in so
      // it could easily be found
      Serial.print("************************************* error code:");
      Serial.println(xbee.getResponse().getErrorCode(),DEC);
    }
    else {
      // I hate else statements that don't have some kind
      // ending.  This is where you handle other things
    }
    if (Serial.available() > 0) {
      char incomingByte;
      
      incomingByte = Serial.read();
      Serial.print("Selection: ");
      Serial.println(atoi(&incomingByte), DEC);
      // This message set will turn the light off
      if (atoi(&incomingByte) == 0){
        uint8_t payload1[] = {0x01}; //
        uint8_t payloadOff[] = {0x00,0x01};
        sendSwitch(0x00, 0x02, 0x00ee, 0xc216, 0x01, payload1, sizeof(payload1));
        sendSwitch(0x00, 0x02, 0x00ee, 0xc216, 0x02, payloadOff, sizeof(payloadOff));
      }
      // This pair of messages turns the light on
      else if (atoi(&incomingByte) == 1){
        uint8_t payload1[] = {0x01}; //
        uint8_t payloadOn[] = {0x01,0x01};
        sendSwitch(0x00, 0x02, 0x00ee, 0xc216, 0x01, payload1, sizeof(payload1));
        sendSwitch(0x00, 0x02, 0x00ee, 0xc216, 0x02, payloadOn, sizeof(payloadOn));
      }
      // this goes down to the test routine for further hacking
      else if (atoi(&incomingByte) == 2){
        testCommand();
      }
      // This will get the Version Data, it's a combination of data and text
      else if (atoi(&incomingByte) == 3){
        uint8_t data[] = {0x00, 0x01};
        sendSwitch(0x00, 0x02, 0x00f6, 0xc216, 0xfc, data, sizeof(data));
      }
      // This command causes a message return holding the state of the switch
      else if (atoi(&incomingByte) == 4){
        uint8_t data[] = {0x01};
        sendSwitch(0x00, 0x02, 0x00ee, 0xc216, 0x01, data, sizeof(data));
      }
      // restore normal mode after one of the mode changess that follow
      else if (atoi(&incomingByte) == 5){ 
        uint8_t databytes[] = {0x00, 0x01};
        sendSwitch(0, 0x02, 0x00f0, 0xc216, 0xfa, databytes, sizeof(databytes));
      }
      // range test - periodic double blink, no control, sends RSSI, no remote control
      // remote control works
      else if (atoi(&incomingByte) == 6){ 
        uint8_t databytes[] = {0x01, 0x01};
        sendSwitch(0, 0x02, 0x00f0, 0xc216, 0xfa, databytes, sizeof(databytes));
      }
      // locked mode - switch can't be controlled locally, no periodic data
      else if (atoi(&incomingByte) == 7){ 
        uint8_t databytes[] = {0x02, 0x01};
        sendSwitch(0, 0x02, 0x00f0, 0xc216, 0xfa, databytes, sizeof(databytes));
      }
      // Silent mode, no periodic data, but switch is controllable locally
      else if (atoi(&incomingByte) == 8){ 
        uint8_t databytes[] = {0x03, 0x01};
        sendSwitch(0, 0x02, 0x00f0, 0xc216, 0xfa, databytes, sizeof(databytes));
      }
    }
    Alarm.delay(0); // Just for the alarm routines

}

uint8_t testValue = 0x00;

void testCommand(){
  Serial.println("testing command");
  return;
  Serial.print("Trying value: ");
  Serial.println(testValue,HEX);
  uint8_t databytes[] = {};
  sendSwitch(0, 0xf0, 0x0b7d, 0xc216, testValue++, databytes, sizeof(databytes));
  if (testValue != 0xff)
    Alarm.timerOnce(1,testCommand); // try it again in a second
}

/*
  Because it got so cumbersome trying the various clusters for various commands,
  I created this send routine to make things a little easier and less prone to 
  typing mistakes.  It also made the code to implement the various commands I discovered
  easier to read.
*/
void sendSwitch(uint8_t sEndpoint, uint8_t dEndpoint, uint16_t clusterId,
        uint16_t profileId, uint8_t clusterCmd, uint8_t *databytes, int datalen){
          
  uint8_t payload [10];
  ZBExpCommand tx;
//  Serial.println("Sending command");
  //
  // The payload in a ZigBee Command starts with a frame control field
  // then a sequence number, cluster command, then databytes specific to
  // the cluster command, so we have to build it up in stages
  // 
  // first the frame control and sequence number
  payload[0] = 0x11;
  payload[1] = 0;
  payload[2] = clusterCmd;
  for (int i=0; i < datalen; i++){
    payload[i + 3] = databytes[i];
  }
  int payloadLen = 3 + datalen;
  // OK, now we have the ZigBee cluster specific piece constructed and ready to send
  
  tx = ZBExpCommand(switchLongAddress,
    switchShortAddress,
    sEndpoint,    //src endpoint
    dEndpoint,    //dest endpoint
    clusterId,    //cluster ID
    profileId, //profile ID
    0,    //broadcast radius
    0x00,    //option
    payload, //payload
    payloadLen,    //payload length
    myFrameId++);   // frame ID
    
    xbee.send(tx);
    Serial.print("sent command: ");
    Serial.print(payload[2], HEX);
    Serial.print(" frame ID: ");
    Serial.println(myFrameId-1);
}

/*-------------------------------------------------------*/
// these routines are just to print the data with
// leading zeros and allow formatting such that it 
// will be easy to read.
void print32Bits(uint32_t dw){
  print16Bits(dw >> 16);
  print16Bits(dw & 0xFFFF);
}

void print16Bits(uint16_t w){
  print8Bits(w >> 8);
  print8Bits(w & 0x00FF);
}
  
void print8Bits(byte c){
  uint8_t nibble = (c >> 4);
  if (nibble <= 9)
    Serial.write(nibble + 0x30);
  else
    Serial.write(nibble + 0x37);
        
  nibble = (uint8_t) (c & 0x0F);
  if (nibble <= 9)
    Serial.write(nibble + 0x30);
  else
    Serial.write(nibble + 0x37);
}
Here's a chunk of the sample output:

00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00F0: Cluster Cmd: FB Count: 37753 213 159 40917
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00F0: Cluster Cmd: FB Count: 37873 214 159 40918
00EF: Cluster Cmd: 82 *** Power Data, Minute Stat: 0 watt seconds, Uptime: 58620 seconds, Reset Ind: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00F0: Cluster Cmd: FB Count: 37993 214 167 42966
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00F0: Cluster Cmd: FB Count: 38113 216 149 38360
00EF: Cluster Cmd: 82 *** Power Data, Minute Stat: 0 watt seconds, Uptime: 58680 seconds, Reset Ind: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00F0: Cluster Cmd: FB Count: 38233 216 146 37592
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00F0: Cluster Cmd: FB Count: 38353 217 147 37849
00EF: Cluster Cmd: 82 *** Power Data, Minute Stat: 0 watt seconds, Uptime: 58740 seconds, Reset Ind: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00F0: Cluster Cmd: FB Count: 38473 216 150 38616
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00F0: Cluster Cmd: FB Count: 38593 217 150 38617
00EF: Cluster Cmd: 82 *** Power Data, Minute Stat: 0 watt seconds, Uptime: 58800 seconds, Reset Ind: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
00EF: Cluster Cmd: 81 *** Power Data, Instantaneous Power is: 0
I didn't have anything plugged into it at the time, but you an see what the general output is.

Have fun