A friend of mine is looking for a way to control a light remotely. His problem, and mine also, is that these darn things are expensive and require a controller. He specifically wanted something that used the Zigbee protocol because they are capable of being controlled by an XBee device. He pointed me to the Iris line at Lowe's. Being eaten up with curiosity, I went to Lowe's and looked at the devices.
They have an entire line of devices for lights, doors, garage, etc. The problem I saw was that, once again, you're stuck with their controller that you can't change, their website that will probably go down at the least useful time (and has a number of times), and a monthly charge that they can raise any time they want to. I even prowled through their terms of service (yuck) and it looks to me like they can use the data for anything they want to. Also, they can change their terms of service at any time, leaving your data to their use. I hate that crap. Here we go, buy their stuff and then are subject to whatever they want to do over the years. Why don't they just sell us the darn switches, publish a reasonable API that we can use, and let us live our lives outside their monthly charges and control?
What's needed here is for someone to figure out their switches, make them work with a little computer of some kind, and put how they did it on the web for the entire world to play with. That'll show them.
Welcome to my work hooking an Arduino to an XBee and controlling an Iris light switch remotely.
First, I chose their Iris Smart Plug <link> because it looks cool, can be tested without installing it into the wall, and measures the power going through it. Does this sound like my perfect device or what? I can use one of these to measure power anywhere in the house and have it report to my Raspberry Pi where I can forward it to a cloud server (or three, or four) for examination over time. This little device is right down my alley. Now, all I have to do is make it work ... right ?
I could have bought their controller and decoded the interaction with a sniffer and proceeded to duplicate it on my Pi, but there were some problems with this idea: I don't have a spare Pi right now, the interaction is encrypted, I don't have a sniffer; what the heck am I going to do with this $30 paperweight? Off to the web I go.
I only found one, yes one, place where anyone had any sort of success hacking this device. Over at Jeelabs a contributor, CapnBry, had managed to make it work using code on a Windows PC <link>, but his description of how it worked read like classic Greek; totally out of my league. Try as I might, there just wasn't anyplace else that turned up with something more comprehensible to me. I had to just bite the bullet and start trying to talk to the switch. After all, I've worked with XBees for years; how hard can it be?
At this point, you're probably thinking that this is just another post about how I tried to make it work and gave up because I just couldn't get enough information, didn't have the time, or the hardware didn't live up to expectations. Well, not so; I have the working switch being remotely controlled by an Arduino setting right over there. And yes, I'm going to tell you how I did it, and provide the code so you can repeat the experiment. Hopefully, you'll expand on my efforts and let me know about it so we can all benefit and move along.
Like I said earlier, I wanted to put this on a Raspberry Pi, but I didn't have a spare one to experiment with, so I got an Arduino and XBee out of my parts bin, slapped them together, and programmed the XBee as a controller. This is where I ran into my first obstacle. How to set up the XBee? First, you have to use a Series 2 XBee or better; none of this will work on a Series 1 XBee (now do you understand why I chose series 2)? The notable items in the setup of the XBee are:
ZigBee Stack Profile 2
Encryption Enable 1
Encryption Options 1
API Enable 2
API Output Mode 3
XBee Modem XB24-ZB, ZIGBEE Coordinator API, Version 21A7 (or better)
And, Encryption Key set to something that you can remember. You cannot read this register after you set it, so put in something you can't forget if you need it later. Once set, I haven't needed it since, but you never know.
All the parameters can be set using XCTU and it is relatively easy to set up for this once you know what you're doing. The other parameters can be matched to whatever you're used to using. Don't think this came easily! It took me almost a full day of messing around to get it working at all, so if you have trouble, double check everything.
Next, I wanted to use Andrew Rapp's XBee library for the Arduino, but he didn't put in support for the special messages needed to communicate with a ZigBee device. Specifically there are two messages that are used (almost) exclusively for ZigBee communications: Explicit Addressing ZigBee Command Frame 0x11 and ZigBee Explicit Rx Indicator 0x91; these are not supported by Andrew's library. However, the library is too nice to allow something like that to stop me, so I extended the library to support these two messages and added support to the ZBTxStatusResponse to be able to get the frame ID back (he missed that little thing). These changes allowed me to use the library and all its features to speed up the hack.
OK, armed with a library specially modified to work with ZigBee devices and an XBee that should be able to talk to them, I put some code together to monitor traffic. I immediately got traffic from the switch. There was a series of messages that I couldn't understand and a lot of bytes to figure out what they meant. Back to the link above where the guy stated that he had made it work. The problem was with language. He said things like, "You: (Endpoint=0x02, Profile=0xc216, Cluster=0x00f0) FrameControl=0x19 ClusterCmd=0xfa data=0x00 0x01" What is an Endpoint? A Profile? These things were totally foreign and strange. So I hunted down the ZigBee specification <link> and it was HUGE and filled with jargon specific to the protocol that made reading it an exercise in learning to speak ancient Sanskrit. I also found an Endpoint document that talked about the interaction of devices during a process called 'Joining'<link>. After reading a significant portion of these I started to understand. None of the web sites I visited actually described what most of this stuff was, but it boils down to this:
There's a device that supports ZigBee as an end point. It's things like light switches, door locks, devices that actually do something; these are called ZigBee Device Objects. There's things that control these devices, they're called servers. Each device has profiles, these profiles are code that are specific to the device. Each profile has clusters; these clusters are where the code to do something is hooked. So, you'll send a message to a device, profile something, cluster something, with some data about what you want to do. Add to this the fact that each device has a 64 bit address, a 16 bit address, and needs special formatting to the message and the task becomes a bit daunting ... a whole lot daunting.
After prowling around documentation for hours I started to get a glimmer of what was going on and I finally found a key to these things that was way down in the XBee user's guide. Round about page 122 the Digi folk talk about how to send and receive messages to a ZigBee device. That was the key that got me going. Now that I understood a little more than half of what the guy CapnBry was talking about, I started deconstructing the messages from the switch and looking at what was going on.
The switch sends a message announcing that it exists, then it sends another message that tells some stuff about what it can do. These messages come after the hardware itself gets set up. There are messages (we don't have to worry about) that take place just to get the XBee coordinator to recognize it. Then the little device sends another message specifically from the hardware. See, the ZigBee protocol is general purpose and has support for devices that haven't been invented yet, but the manufacturers ignore that and roll their own stuff under the 'Manufacturer Specific' provisions of the specification. That means that even if you support the Zigbee protocol, it won't work with the various devices because they hide everything under the special provisions ... jerks. So, you have to fiddle with things to get them to work. The manufacturer AlertMe is notable for this tactic, and AlertMe manufactures the devices that Lowe's sells.
So, these three messages come out of the switch and it's your responsibility to respond correctly to them and get the device to recognize your code as a valid controller; that's what meant by 'Joining'. What happens is that the switch has a hardware 64 bit address and randomly chooses a 16 bit address to send the messages with. If it doesn't get a proper response, it steps to another channel and tries the same thing again. It does this for quite a while before it give up and just shuts down. So you watch the messages, it stops a while and the messages start again. Eventually it just give up all together. Each time you see the set of three messages, it will have a different 16 bit address, so you have to save this address to respond to so that it will listen. If you're too slow, it won't pay attention because it has already changed the address it pays attention to.
The sequence is documented in the code below, but basically, you wait for the first two messages to come in then respond to both of them. Then, you interact with the device and it will join with your homemade controller. From that point on, it will report the power usage every three seconds with a summary every minute. There's other stuff that is sent by the switch, but I wasn't interested enough in it to bother decoding it; consider those items an exercise for the student.
Once you get it working, you'll find out how nice this switch is. It monitors the power and reports it every three seconds or so. It latches the state of the switch such that if the power fails, it comes back in the same state it was in when the power failed. The little light on it doesn't follow properly, but that can be controlled with software. It reports a state change back. That means that if I walk over and push the button, it will send a message that the light has been changed. You can ask the switch the state of the light and it will answer back to you. So you can check to see if the outside lights were left on. This switch is actually pretty nice.
But enough of the bragging. Here's the code, but remember, this is an example of how to do it. It isn't an example of coding style, proper formatting, or even the right way to do it. It's the actual code I used to figure out how the switch works. It will compile using the Arduino IDE 1.0.5 and needs the special modifications to the XBee library I added to support the Zigbee specific messages. Just let me know if you need the changes and I'll put them somewhere you can grab them. It also needs the latest SoftwareSerial library because I use software pins to connect to the XBee and the console to monitor and send commands.
Once you get it running you can turn the switch on by typing a 1 in the input line of the Arduino IDE terminal and clicking send. A zero will turn the switch off. That's all I really supported in this version, future work will obviously expand the capabilities. Also, if you kill the sketch, wait until at least one message has been sent by the switch. The code above needs the 16 and 64 bit address of the switch to work and I didn't put any provisions in to save it; it has to come from the switch. Every message from the switch carries the addresses, so just wait for one to come in before trying to send something. Since this is the very first version of this effort, the switch can sometimes fail to 'join'. This isn't a problem, just let the two devices interact for about 20 seconds or so, unplug the switch and plug it back in. It'll take off and work.
Edit: About an hour after I posted this I got a brainstorm and figured out how to get the switch to 'join' reliably. Now, you can reset the switch by unplugging it, press the button to discharge any caps, plug it in, and then press the switch 8 times within about 8 seconds. The switch will start all over in its interaction, and then start sending power readings. When (notice I didn't say 'if') I get another one, I'll have to modify this code to support two devices, but one works fine. The code box above has been updated to hold the latest.
Here's the output from the Arduino from first start up after joining. I turn the switch on and off during the session. Notice that the power usage is 83 (two little bulbs in a lamp) and that the switch is constantly sending its status over the network. There's an extra piece of debugging in this; I print all the bytes sent to the XBee, so the lines that begin with the 7E are lines that are actually being sent to the switch.
Have fun.
They have an entire line of devices for lights, doors, garage, etc. The problem I saw was that, once again, you're stuck with their controller that you can't change, their website that will probably go down at the least useful time (and has a number of times), and a monthly charge that they can raise any time they want to. I even prowled through their terms of service (yuck) and it looks to me like they can use the data for anything they want to. Also, they can change their terms of service at any time, leaving your data to their use. I hate that crap. Here we go, buy their stuff and then are subject to whatever they want to do over the years. Why don't they just sell us the darn switches, publish a reasonable API that we can use, and let us live our lives outside their monthly charges and control?
What's needed here is for someone to figure out their switches, make them work with a little computer of some kind, and put how they did it on the web for the entire world to play with. That'll show them.
Welcome to my work hooking an Arduino to an XBee and controlling an Iris light switch remotely.
First, I chose their Iris Smart Plug <link> because it looks cool, can be tested without installing it into the wall, and measures the power going through it. Does this sound like my perfect device or what? I can use one of these to measure power anywhere in the house and have it report to my Raspberry Pi where I can forward it to a cloud server (or three, or four) for examination over time. This little device is right down my alley. Now, all I have to do is make it work ... right ?
I could have bought their controller and decoded the interaction with a sniffer and proceeded to duplicate it on my Pi, but there were some problems with this idea: I don't have a spare Pi right now, the interaction is encrypted, I don't have a sniffer; what the heck am I going to do with this $30 paperweight? Off to the web I go.
I only found one, yes one, place where anyone had any sort of success hacking this device. Over at Jeelabs a contributor, CapnBry, had managed to make it work using code on a Windows PC <link>, but his description of how it worked read like classic Greek; totally out of my league. Try as I might, there just wasn't anyplace else that turned up with something more comprehensible to me. I had to just bite the bullet and start trying to talk to the switch. After all, I've worked with XBees for years; how hard can it be?
At this point, you're probably thinking that this is just another post about how I tried to make it work and gave up because I just couldn't get enough information, didn't have the time, or the hardware didn't live up to expectations. Well, not so; I have the working switch being remotely controlled by an Arduino setting right over there. And yes, I'm going to tell you how I did it, and provide the code so you can repeat the experiment. Hopefully, you'll expand on my efforts and let me know about it so we can all benefit and move along.
Like I said earlier, I wanted to put this on a Raspberry Pi, but I didn't have a spare one to experiment with, so I got an Arduino and XBee out of my parts bin, slapped them together, and programmed the XBee as a controller. This is where I ran into my first obstacle. How to set up the XBee? First, you have to use a Series 2 XBee or better; none of this will work on a Series 1 XBee (now do you understand why I chose series 2)? The notable items in the setup of the XBee are:
ZigBee Stack Profile 2
Encryption Enable 1
Encryption Options 1
API Enable 2
API Output Mode 3
XBee Modem XB24-ZB, ZIGBEE Coordinator API, Version 21A7 (or better)
And, Encryption Key set to something that you can remember. You cannot read this register after you set it, so put in something you can't forget if you need it later. Once set, I haven't needed it since, but you never know.
All the parameters can be set using XCTU and it is relatively easy to set up for this once you know what you're doing. The other parameters can be matched to whatever you're used to using. Don't think this came easily! It took me almost a full day of messing around to get it working at all, so if you have trouble, double check everything.
Next, I wanted to use Andrew Rapp's XBee library for the Arduino, but he didn't put in support for the special messages needed to communicate with a ZigBee device. Specifically there are two messages that are used (almost) exclusively for ZigBee communications: Explicit Addressing ZigBee Command Frame 0x11 and ZigBee Explicit Rx Indicator 0x91; these are not supported by Andrew's library. However, the library is too nice to allow something like that to stop me, so I extended the library to support these two messages and added support to the ZBTxStatusResponse to be able to get the frame ID back (he missed that little thing). These changes allowed me to use the library and all its features to speed up the hack.
OK, armed with a library specially modified to work with ZigBee devices and an XBee that should be able to talk to them, I put some code together to monitor traffic. I immediately got traffic from the switch. There was a series of messages that I couldn't understand and a lot of bytes to figure out what they meant. Back to the link above where the guy stated that he had made it work. The problem was with language. He said things like, "You: (Endpoint=0x02, Profile=0xc216, Cluster=0x00f0) FrameControl=0x19 ClusterCmd=0xfa data=0x00 0x01" What is an Endpoint? A Profile? These things were totally foreign and strange. So I hunted down the ZigBee specification <link> and it was HUGE and filled with jargon specific to the protocol that made reading it an exercise in learning to speak ancient Sanskrit. I also found an Endpoint document that talked about the interaction of devices during a process called 'Joining'<link>. After reading a significant portion of these I started to understand. None of the web sites I visited actually described what most of this stuff was, but it boils down to this:
There's a device that supports ZigBee as an end point. It's things like light switches, door locks, devices that actually do something; these are called ZigBee Device Objects. There's things that control these devices, they're called servers. Each device has profiles, these profiles are code that are specific to the device. Each profile has clusters; these clusters are where the code to do something is hooked. So, you'll send a message to a device, profile something, cluster something, with some data about what you want to do. Add to this the fact that each device has a 64 bit address, a 16 bit address, and needs special formatting to the message and the task becomes a bit daunting ... a whole lot daunting.
After prowling around documentation for hours I started to get a glimmer of what was going on and I finally found a key to these things that was way down in the XBee user's guide. Round about page 122 the Digi folk talk about how to send and receive messages to a ZigBee device. That was the key that got me going. Now that I understood a little more than half of what the guy CapnBry was talking about, I started deconstructing the messages from the switch and looking at what was going on.
The switch sends a message announcing that it exists, then it sends another message that tells some stuff about what it can do. These messages come after the hardware itself gets set up. There are messages (we don't have to worry about) that take place just to get the XBee coordinator to recognize it. Then the little device sends another message specifically from the hardware. See, the ZigBee protocol is general purpose and has support for devices that haven't been invented yet, but the manufacturers ignore that and roll their own stuff under the 'Manufacturer Specific' provisions of the specification. That means that even if you support the Zigbee protocol, it won't work with the various devices because they hide everything under the special provisions ... jerks. So, you have to fiddle with things to get them to work. The manufacturer AlertMe is notable for this tactic, and AlertMe manufactures the devices that Lowe's sells.
So, these three messages come out of the switch and it's your responsibility to respond correctly to them and get the device to recognize your code as a valid controller; that's what meant by 'Joining'. What happens is that the switch has a hardware 64 bit address and randomly chooses a 16 bit address to send the messages with. If it doesn't get a proper response, it steps to another channel and tries the same thing again. It does this for quite a while before it give up and just shuts down. So you watch the messages, it stops a while and the messages start again. Eventually it just give up all together. Each time you see the set of three messages, it will have a different 16 bit address, so you have to save this address to respond to so that it will listen. If you're too slow, it won't pay attention because it has already changed the address it pays attention to.
The sequence is documented in the code below, but basically, you wait for the first two messages to come in then respond to both of them. Then, you interact with the device and it will join with your homemade controller. From that point on, it will report the power usage every three seconds with a summary every minute. There's other stuff that is sent by the switch, but I wasn't interested enough in it to bother decoding it; consider those items an exercise for the student.
Once you get it working, you'll find out how nice this switch is. It monitors the power and reports it every three seconds or so. It latches the state of the switch such that if the power fails, it comes back in the same state it was in when the power failed. The little light on it doesn't follow properly, but that can be controlled with software. It reports a state change back. That means that if I walk over and push the button, it will send a message that the light has been changed. You can ask the switch the state of the light and it will answer back to you. So you can check to see if the outside lights were left on. This switch is actually pretty nice.
But enough of the bragging. Here's the code, but remember, this is an example of how to do it. It isn't an example of coding style, proper formatting, or even the right way to do it. It's the actual code I used to figure out how the switch works. It will compile using the Arduino IDE 1.0.5 and needs the special modifications to the XBee library I added to support the Zigbee specific messages. Just let me know if you need the changes and I'll put them somewhere you can grab them. It also needs the latest SoftwareSerial library because I use software pins to connect to the XBee and the console to monitor and send commands.
The Arduino Sketch
/*
This is an examination of Zigbee device communication using an XBee
Specifically using the Lowe's Iris switch. This device plugs into an outlet
and has a plug on the front for your appliance. It controls the on/off of the appliance
and measures the power usage as well. It's a lot like a remote control Kill-a-Watt.
*/
#include <XBee.h>
#include <SoftwareSerial.h>
XBee xbee = XBee();
XBeeResponse response = XBeeResponse();
// create response and command objects we expect to handle
ZBExpRxResponse rx = ZBExpRxResponse();
XBeeAddress64 switchLongAddress;
uint16_t switchShortAddress;
uint16_t myFrameId=1; // for debugging, it's nice to know which messages is being handled
// 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);
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
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.println();
// 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();
Serial.print("sent active endpoint request frame ID: ");
Serial.println(myFrameId-1);
//
// 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 join 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 something specific to the Endpoint devices
// and I haven't been able to find documentation on it
// anywhere
Serial.println("*** Cluster ID 0xf6");
}
if (clusterId == 0x00ef){
//
// This is a power report, there are two kinds, instant and summary
//
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){
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.println(atoi(&incomingByte), DEC);
if (atoi(&incomingByte) == 0){
// turn the light off
lightSet(0);
}
else if (atoi(&incomingByte) == 1){
// turn the light on
lightSet(1);
}
}
}
void lightSet(int val){
uint8_t payload1[] = {0x11,0x00,0x01,03};
uint8_t payload2[] = {0x11,0x00,0x02,0x00,0x01};
if (val==0){
Serial.println("Light Off");
}
else {
Serial.println("Light On");
payload2[3] = 0x01;
}
ZBExpCommand tx = ZBExpCommand(switchLongAddress,
switchShortAddress,
0, //src endpoint
2, //dest endpoint
0x00ee, //cluster ID
0xc216, //profile ID
0, //broadcast radius
0x00, //option
payload1, //payload
sizeof(payload1), //payload length
myFrameId++); // frame ID
xbee.send(tx);
Serial.println();
Serial.print("sent switch off 1 frame ID: ");
Serial.println(myFrameId-1);
tx = ZBExpCommand(switchLongAddress,
switchShortAddress,
0, //src endpoint
2, //dest endpoint
0x00ee, //cluster ID
0xc216, //profile ID
0, //broadcast radius
0x00, //option
payload2, //payload
sizeof(payload2), //payload length
myFrameId++); // frame ID
xbee.send(tx);
Serial.println();
Serial.print("sent switch off 2 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);
}
Once you get it running you can turn the switch on by typing a 1 in the input line of the Arduino IDE terminal and clicking send. A zero will turn the switch off. That's all I really supported in this version, future work will obviously expand the capabilities. Also, if you kill the sketch, wait until at least one message has been sent by the switch. The code above needs the 16 and 64 bit address of the switch to work and I didn't put any provisions in to save it; it has to come from the switch. Every message from the switch carries the addresses, so just wait for one to come in before trying to send something. Since this is the very first version of this effort, the switch can sometimes fail to 'join'. This isn't a problem, just let the two devices interact for about 20 seconds or so, unplug the switch and plug it back in. It'll take off and work.
Edit: About an hour after I posted this I got a brainstorm and figured out how to get the switch to 'join' reliably. Now, you can reset the switch by unplugging it, press the button to discharge any caps, plug it in, and then press the switch 8 times within about 8 seconds. The switch will start all over in its interaction, and then start sending power readings. When (notice I didn't say 'if') I get another one, I'll have to modify this code to support two devices, but one works fine. The code box above has been updated to hold the latest.
Here's the output from the Arduino from first start up after joining. I turn the switch on and off during the session. Notice that the power usage is 83 (two little bulbs in a lamp) and that the switch is constantly sending its status over the network. There's an extra piece of debugging in this; I print all the bytes sent to the XBee, so the lines that begin with the 7E are lines that are actually being sent to the switch.
started
Frame Type is 91
Got a Zigbee explicit packet from: 000D6F00 0237B25A (A6C4)
Source Endpoint: 02
Destination Endpoint: 02
Cluster ID: 00EF
Profile ID: C216
Length of RF Data: 5
RF Data Received: 09 00 81 53 00
Power Data, Instantaneous Power is: 83
Frame Type is 91
Got a Zigbee explicit packet from: 000D6F00 0237B25A (A6C4)
Source Endpoint: 02
Destination Endpoint: 02
Cluster ID: 00F0
Profile ID: C216
Length of RF Data: 16
RF Data Received: 09 00 FB 1C 26 25 DA 03 4A 32 00 00 CB EA 01 00
0
Light Off
7E 0 18 11 1 0 D 6F 0 2 37 B2 5A A6 C4 0 2 0 EE C2 16 0 0 11 0 1 3 E5
sent switch off 1 frame ID: 1
7E 0 19 11 2 0 D 6F 0 2 37 B2 5A A6 C4 0 2 0 EE C2 16 0 0 11 0 2 0 1 E5
sent switch off 2 frame ID: 2
Frame Type is 8B
Status Response: 0
To Frame ID: 2
Frame Type is 91
Got a Zigbee explicit packet from: 000D6F00 0237B25A (A6C4)
Source Endpoint: 02
Destination Endpoint: 02
Cluster ID: 00EE
Profile ID: C216
Length of RF Data: 5
RF Data Received: 09 70 80 06 E0
Light switched off
Frame Type is 91
Got a Zigbee explicit packet from: 000D6F00 0237B25A (A6C4)
Source Endpoint: 02
Destination Endpoint: 02
Cluster ID: 00EF
Profile ID: C216
Length of RF Data: 12
RF Data Received: 09 00 82 A4 0D 00 00 90 F6 00 00 00
Power Data, Minute Stat: 3492 watt seconds, Uptime: 63120 seconds, Reset Ind: 0
Frame Type is 91
Got a Zigbee explicit packet from: 000D6F00 0237B25A (A6C4)
Source Endpoint: 02
Destination Endpoint: 02
Cluster ID: 00EF
Profile ID: C216
Length of RF Data: 5
RF Data Received: 09 00 81 50 00
Power Data, Instantaneous Power is: 80
Frame Type is 91
Got a Zigbee explicit packet from: 000D6F00 0237B25A (A6C4)
Source Endpoint: 02
Destination Endpoint: 02
Cluster ID: 00EF
Profile ID: C216
Length of RF Data: 5
RF Data Received: 09 00 81 00 00
Power Data, Instantaneous Power is: 0
1
Light On
7E 0 18 11 3 0 D 6F 0 2 37 B2 5A A6 C4 0 2 0 EE C2 16 0 0 11 0 1 3 E3
sent switch off 1 frame ID: 3
7E 0 19 11 4 0 D 6F 0 2 37 B2 5A A6 C4 0 2 0 EE C2 16 0 0 11 0 2 1 1 E2
sent switch off 2 frame ID: 4
Frame Type is 8B
Status Response: 0
To Frame ID: 4
Frame Type is 91
Got a Zigbee explicit packet from: 000D6F00 0237B25A (A6C4)
Source Endpoint: 02
Destination Endpoint: 02
Cluster ID: 00EE
Profile ID: C216
Length of RF Data: 5
RF Data Received: 09 70 80 07 00
Light switched on
Frame Type is 91
Got a Zigbee explicit packet from: 000D6F00 0237B25A (A6C4)
Source Endpoint: 02
Destination Endpoint: 02
Cluster ID: 00EF
Profile ID: C216
Length of RF Data: 5
RF Data Received: 09 00 81 53 00
Power Data, Instantaneous Power is: 83
Frame Type is 91
Got a Zigbee explicit packet from: 000D6F00 0237B25A (A6C4)
Source Endpoint: 02
Destination Endpoint: 02
Cluster ID: 00F0
Profile ID: C216
Length of RF Data: 16
RF Data Received: 09 00 FB 1C 26 9D DA 03 4A 32 00 00 C6 F6 01 00