Friday, January 24, 2014

Arduino and the Iris Zigbee switch, part 2

Edit: The last portion of this investigation is here <link>

I've had a few days to play with the Iris switch <link> and I like it.  However it doesn't play well with others.  In that I mean I tried to bring it up on the network with my other devices and managed to kill my network of XBees.  Yes, I was down with all my devices not talking to each other and I had to visit each device and fix it.  This is totally avoidable, I was an idiot.  Do not follow in my footsteps in this.

First there are some things to know.  As I pointed out in my last post <link> about this switch, it takes special settings on the XBee for it to work with an Iris switch:

ZigBee Stack Profile 2
Encryption Enable 1
Encryption Options 1
API Enable 2
API Output Mode 3
Some Encryption Key
XBee Modem XB24-ZB, ZIGBEE Coordinator API, Version 21A7 (or better)

This is radically different from what I have been using in the past.  First, the Zigbee Stack Profile is 2, which means use the ZigBee Pro stack; I normally use a 0 in that spot.  When I changed it to profile 2, it stopped passing all the messages from my existing XBees through to the serial port.  Naturally, I thought that I could simply change the other devices to use the same profile parameter and everything should be fine.  Not so.

It seems the API Output Mode has a similar effect.  When you change API Output mode to something other than 0, you lose all the messages from other XBees except the ones that are explicitly allowed.  The message: ZigBee Explicit Rx Indicator (frame id=91) comes through fine, but you lose the ZigBee Receive Packet (frame id=90) which is what you normally get.  That means my remote XBees were happily sending messages and the receiver didn't pass them on to the serial port.  Like I said, I was an idiot because the setting clearly indicates either 'traditional' or 'explicit'.  I should have gotten a clue from this before I messed with the XBee network coordinator.

So, I couldn't see anything that was being sent by my other devices.  Naturally, I found this out AFTER I had reprogrammed my XBee coordinator.  My whole network was down.  The various XBee devices had saved their connection with the coordinator as it was set up before and I had to visit each one of them and make them drop the connection and establish a new one.  What a royal pain in the behind.

While I was doing that I decided there had to be a way to keep from having this happen in the future.  It turns out there is.  By adjusting some of the parameters, the remote XBees will sense that the coordinator is gone and automatically hunt for a new one.  This is a mixed blessing because if your coordinator dies, your remote devices will stop talking to each other.  I've had exactly zero problems with the XBee coordinator, so I decided to set the network up this way so that future experiments like this won't mean taking things apart to get to the XBee and forcing it to initialize the network.  The parameters are:

Network Watchdog Timeout 1
Channel Verification 1 (enabled)

The watchdog timeout causes the XBee to reestablish the network connection if it hasn't heard from the coordinator in three minutes.  Since my coordinator is sending the time repeatedly, all devices will hear from it often enough that the network shouldn't ever have a problem.  The Channel Verification being enabled means that any time the power fails the XBee will check to be sure there is an XBee coordinator out there when the power comes back on.  If it can't find the coordinator, it'll go looking for one.

These changes mean that if my coordinator dies, the network dies.  Fine, I can live with that.  It also means that there may be a very slight pause whenever the power comes back on from a failure.  When I tested that by unplugging a device, it took less than a second for it to find the coordinator and get back online.  So, now if I mess up the coordinator with some experiment, I won't have to go visit each darn XBee and make it talk to the new coordinator set up.  They'll do it by themselves after a three minute timeout, or I can unplug the power and make them do it right now.  Too bad I didn't discover this a couple of years ago.

Now, these changes may not be the right thing for you to do.  However, if you have an XBee in a hard to get to location, or setting way out there in the field hooked to a solar charger, it might be a good idea.  So, you understand your needs better than I can; make an informed decision.

All this information means something else: The Iris switch and my network won't work together.  Simple, I'll just set up a coordinator for the Iris Zigbee devices I may eventually have separate from the network I am using now and control them separately.  I should be able to interface an Arduino that has the code for Iris to anything I want to and go merrily on my way.  That may be the next project.

So, there was something good that came out of messing up my network of devices.  I learned a lot about modifying the network in general.  I also got to visit each of my XBees and clean out the spider webs and dead flies.  There was live scorpion in one of them, but since it's cool right now and the arthropod couldn't move very fast, I won.  I also decided to retire the separate Arduino for the Acid Pump and move the code and connection over to my Pool Controller.  Since I was already in the code for the Pool Controller, I hooked in the Septic Tank Float so I can get an alarm in the house if the tank has problems.  These were chores that I've been putting aside for months now.

How many people do you know that have a septic tank that can send them email?

Thursday, January 16, 2014

Arduino and the Iris Zigbee switch

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.

Edit: But before you go and implement this, take a look at my second post on this experiment, there are some things to avoid <link>.

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); 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 

Have fun.


Wednesday, January 15, 2014

Belkin's Wemo Light Switch Part 2

It's been over a month (well, almost two months) since I wrote about the Wemo switch.  During that time, Belkin has come out with the Insight switch and a couple of iterations of the Android software.  It still doesn't work well on my phone, their online server has problems when you add switches.  Seems they forget you already had other switches.  I'm not willing to reinitialize everything in my house each time I do something to one of them.  But:

I have three of the switches now and they're all hooked into my Raspberry Pi house controller.  Over the last couple of months I've found a couple of problems with the ouimeaux python library that the author is looking at, and of course, with my code to run them.  Unlike the Android software, the wall switches work really well.  Occasionally the switch that is at the edge of my Wifi network will fail to report and twice now I've had one of the switches respond to a command with an error.  Really good performance for a remote switch and far, far better than the X10 stuff they replaced.

The really cool thing is that the switches report when they change state.  So, if I change it over the phone, the switch tells my Pi that it happened.  If I push the button, same thing.  This is great because you can check the state of a switch at any time to see if you left the lights on.  Using my Pi to control them works great as well.  I don't have to rely on Belkin's server or that 'if this then that' website to control things.  I can set up as many schedules as I want and don't have to worry about somebody else's server having problems.

For you folk out there that would like to mess with these switches, here's the code I'm using to control them:

The Python Script
#! /usr/bin/python
import os
import signal
import sys
import logging
from apscheduler.scheduler import Scheduler
from threading import Thread
import datetime
from datetime import timedelta
import time
import sysv_ipc


from ouimeaux.upnp import UPnPLoopbackException
from ouimeaux.environment import Environment
from ouimeaux.config import get_cache, in_home, WemoConfiguration
from ouimeaux.utils import matcher

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

def lprint(text):
 print time.strftime("%A, %B, %d at %H:%M:%S"),text
 sys.stdout.flush()
 
def signal_handler(signal,frame):
 lprint (" Exit Signal")
 sys.exit(0)

def on_switch(switch):
 lprint (" Switch found! " + switch.name)

def on_motion(motion):
 lprint (" Motion found!" + switch.motion)
 
def outsideLightsOn():
 lprint (" Outside lights on")
 garageLightSwitch.on()
 cactusLightSwitch.on()
 fPorchLightSwitch.on()
 
def outsideLightsOff():
 lprint (" Outside lights off")
 garageLightSwitch.off()
 cactusLightSwitch.off()
 fPorchLightSwitch.off()
 
def fPorchToggle():
 if (int(fPorchLightSwitch.get_state()) == 1):
  fPorchLightSwitch.off()
 else:
  fPorchLightSwitch.on()
  
def garageToggle():
 if (int(garageLightSwitch.get_state()) == 1):
  garageLightSwitch.off()
 else:
  garageLightSwitch.on()
  
def cactusToggle():
 if (int(cactusLightSwitch.get_state()) == 1):
  cactusLightSwitch.off()
 else:
  cactusLightSwitch.on()
 
def updateDatabase(whichone, status):
  pass

def cactusSwitchChange(value):
 if (int(value) == 1):
  lprint (" Cactus Spot On")
  updateDatabase('cactusspot', 'On')
 elif (int(value) == 0):
  lprint (" Cactus Spot Off")
  updateDatabase('cactusspot', 'Off')
 else:
  lprint (" Cactus Spot Switch Unknown value: " + value)
  
def garageSwitchChange(value):
 if (int(value) == 1):
  lprint (" Garage Outside Light On")
  updateDatabase('outsidegarage', 'On')
 elif (int(value) == 0):
  lprint (" Garage Outside Light Off")
  updateDatabase('outsidegarage', 'Off')
 else:
  lprint (" Garage Outside Light Switch Unknown value: " + value)
  
def fPorchSwitchChange(value):
 if (int(value) == 1):
  lprint (" Front Porch Light On")
  updateDatabase('frontporch', 'On')
 elif (int(value) == 0):
  lprint (" Front Porch Light Off")
  updateDatabase('frontporch', 'Off')
 else:
  lprint (" Front Porch Light Switch Unknown value: " + value)
  
def handleCommand(command):
 #lprint(" " + str(command))
 # the command comes in from php as something like
 # ('s:17:"AcidPump, pumpOff";', 2)
 # so command[0] is 's:17:"AcidPump, pumpOff'
 # then split it at the "  and take the second item
 try:
  c = str(command[0].split('\"')[1]).split(',')
 except IndexError:
  c = str(command[0]).split(' ')    #this is for something I sent from another process
 #lprint(c)
 if (c[0] == 'OutsideLightsOn'):
  outsideLightsOn()
 elif (c[0] == 'OutsideLightsOff'):
  outsideLightsOff()
 elif (c[0] == 'fPorchToggle'):
  fPorchToggle()
 elif(c[0] == 'garageToggle'):
  garageToggle()
 elif (c[0] == 'cactusToggle'):
  cactusToggle()
 else:
  lprint(" Weird command = " + str(c))

def showTime():
 lprint("timecheck");

if __name__ == '__main__':
 #Catch Control-C input so it isn't ugly
 signal.signal(signal.SIGINT, signal_handler)
 logging.basicConfig(format='%(levelname)s:%(message)s',level=logging.WARNING)
 #When looking at a log, this will tell me when it is restarted
 lprint (" started")

 env = Environment(on_switch, on_motion, with_cache=False)
 env.start()
 lprint (" Starting device discovery")
 env.discover(seconds=5)
 lprint (" done with discovery")
 garageLightSwitch = env.get_switch('outsidegarage')
 cactusLightSwitch = env.get_switch('cactusspot')
 fPorchLightSwitch = env.get_switch('frontporch')
 # lprint (" testing lights")
 # garageLightSwitch.basicevent.SetBinaryState(BinaryState=1)
 # cactusLightSwitch.basicevent.SetBinaryState(BinaryState=1)
 # fPorchLightSwitch.basicevent.SetBinaryState(BinaryState=1)
 # time.sleep(2)
 # lprint (' garage ' + str(garageLightSwitch.basicevent.GetBinaryState()))
 # lprint (' cactus ' + str(cactusLightSwitch.basicevent.GetBinaryState()))
 # lprint (' front porch ' + str(fPorchLightSwitch.basicevent.GetBinaryState()))
 # garageLightSwitch.basicevent.SetBinaryState(BinaryState=0)
 # cactusLightSwitch.basicevent.SetBinaryState(BinaryState=0)
 # fPorchLightSwitch.basicevent.SetBinaryState(BinaryState=0)
 # lprint (' garage ' + str(garageLightSwitch.basicevent.GetBinaryState()))
 # lprint (' cactus ' + str(cactusLightSwitch.basicevent.GetBinaryState()))
 # lprint (' front porch ' + str(fPorchLightSwitch.basicevent.GetBinaryState()))
 # If I want to see the various attributes of the device
 # lprint (' ' + garageLightSwitch.explain())
 # lprint (" done testing")
 # examine the status of the lights right now and update the database
 lprint (' checking current state of switches')
 garageSwitchChange(garageLightSwitch.get_state())
 cactusSwitchChange(cactusLightSwitch.get_state())
 fPorchSwitchChange(fPorchLightSwitch.get_state())
 
 lprint (" Setting up the callbacks for the devices")
 cactusLightSwitch.register_listener(cactusSwitchChange)
 garageLightSwitch.register_listener(garageSwitchChange)
 fPorchLightSwitch.register_listener(fPorchSwitchChange)
 
 lprint (" Setting up scheduled items")
 #------------------Stuff I schedule to happen -----
 scheditem = Scheduler()
 # every day at 1900, turn the outside lights on
 #scheditem.add_cron_job(outsideLightsOn, hour=19, minute=01)
 # every day at 2200, turn the outside lights off
 #scheditem.add_cron_job(outsideLightsOff, hour=22, minute=0)
 #just an interval timer for putting time in the log
 scheditem.add_interval_job(showTime, minutes=30)
 lprint (" starting scheduler")
 scheditem.start()
 # Create the message queue where commands can be read
 # I just chose an identifier of 13 because the house monitor
 # already took the number 12.
 Cqueue = sysv_ipc.MessageQueue(13, sysv_ipc.IPC_CREAT,mode=0666)
 firstTime = True
 
 lprint (" Going into the processing loop")
 while True:
  try:
   if (firstTime):
    while(True):
     try:
      # commands could have piled up while this was 
      # not running.  Clear them out.
      junk = Cqueue.receive(block=False, type=0)
      print "purging leftover commands", str(junk)
     except sysv_ipc.BusyError:
      break
    firstTime=False
   newCommand = Cqueue.receive(block=False, type=0)
   # type=0 above means suck every message off the
   # queue.  If I used a number above that, I'd
   # have to worry about the type in other ways.
   # note, I'm reserving type 1 messages for 
   # test messages I may send from time to 
   # time.  Type 2 are messages that are
   # sent by the php code in the web interface.
   # Type 3 are from the event handler. This is just like
   # the house monitor code in that respect.
   # I haven't decided on any others yet.
   handleCommand(newCommand)
  except sysv_ipc.BusyError:
   pass # Only means there wasn't anything there

  # Turn control over to the Wemo environment handler for a second so
  # changes happening can be recorded and handled
  env.wait(time=1)
 
 

I cut out the code to update my database since that is very specific to my implementation, and you'll notice down at the bottom, that control of the lights is handled by Sys V messages.  I don't control the lights with a console, everything comes from other processes sending messages.  For example, my web server software sends a message to this process to turn the lights on and off.  Another process sends messages to turn them on and off at certain times of the day.  That kind of thing.

Just cut that code out and insert whatever kind of control you want to use.  I also made a change to the ouimeaux python library to support asyncronous operation.  The library as it was constructed hung and waited for changes to happen.  I modified it a bit to return after a env.wait call; that's what the very last line in the code is doing; calling the wait for a second then getting back control so it can do other things.

There's a ton of things that can be done to make my control code better, but for now, it works.  I'll loop back to this project some time and add more versatility.  Maybe when the ouimeaux library is changed.