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

35 comments:

  1. Chalk me up as another success. It took a little work as I had to jumper too with the official arduino xbee shield. the sparkfun shield didn't have this issue, since by default it uses pins 2/3 when switched. It also managed to see the Iris door/window switch, though it didn't know what to do with it. I hope it's ok, but I forwarded your pages to the developers of the Almond+ router. My hope is that your findings will help them make the iris plugs accessible via the Almond firmware, so more people can use them with it, otherwise, i'll have to do it as a post process thing. Thanks for figuring all this out.

    ReplyDelete
    Replies
    1. Remember, only a few of the Iris devices are Zigbee, most of them are Z-wave. What's up with the 'Z' thing anyway?

      Delete
    2. yeah, i know which are zigbee and which are z-wave.. the ZWave things I've noticed are all the 3rd party stuff like the locks, GE wall switches.. aka everything NOT made by AlertMe.co.uk. The Almond+ supports Zigbee and Z-Wave both.. I don't know what's up with the Z thing. You could ask the same thing about Digi calling their stuff XBee.

      Delete
    3. I think I'm 90% there, but having a puzzling problem. I'm using a Centralite Azela Appliance module, which seems virtually identical to the Lowes plug in appearance, function, and parameters. (http://www.docstoc.com/docs/159393119/Azela-Zigbee-HA-Appliance-Module-Installation-Guide---CentraLite). It pairs with the XBee in XCTU very nicely when the XBee has the parameters you recommend (and not otherwise). In the XCTU's network view with the XBee connected and the Centralite plug reset to get it to search, the two devices show up connected.

      I've worked with Arduinos a lot, and followed all your instructions explicitly. The sketch does setup, but then doesn't work at the very beginning of the loop: after the xbee.readPacket(), xbee.getResponse().isAvailable() never shows any data read. I've tried tons of variations and tests (hardware and software), but no good. It's puzzling because the plug is sending data and the XBee reads it when in XCTU. Nobody else seems to have this problem. Can someone help me diagnose this? Maybe a problem with SoftSerial?

      Delete
    4. Well, xbee.getResponse has to be working, otherwise you wouldn't have gotten it to join. Go into the XBee library and find the actual serial read and print out the data received. That should tell you what is going on.

      Delete
    5. Excellent suggestion--I forgot I could so easily change the library code. I put in the following:

      int XBee::readPacket() { //wrh was void XBee::readPacket() {
      int diag = 1; //wrh diagnostic
      // reset previous response
      if (_response.isAvailable() || _response.isError()) {
      // discard previous packet and start over
      resetResponse();
      }
      diag=2;//wrh
      while (available()) {
      diag++;//wrh
      b = read();
      etc.,
      then at the bottom I return the value of diag and print it in Serial. It was always 2, meaning that available() always returned false. I reset the plug during the test a few times and it was blinking to indicate searching, but still nothing except 2's.

      Then here is the code for available():
      bool XBee::available() {
      return _serial->available();
      }
      So _serial_available is always false. Is this just a case of SoftSerial not working properly? I have the very same setup as in your sketch, using D2 and D3 as SoftSerial Rx and Tx respectively connected to DOut and DIn (pins 2 and 3) on the XBee.

      This is puzzling because as I said, in XCTU the plug is quickly identified and connected, though in repeated scans it sometimes disconnects and then reconnects after a few more scans. When connected, it shows the other as its connected party. Is there a simple test for SoftwareSerial working?

      I hope this points at something obvious. I'm determined to solve this.

      Delete
    6. You can also go to the actual character by character read for the XBee and print whatever is coming in on a character basis. To test software serial is a bit tough depending on what you have around the house. There's example code you can get and then hook something up to the pins. Things like a GPS, another ftdi device, another arduino, things like that will work.

      In XBee.cpp, look for XBee::read() and stick a Serial.write() in there to see if you're actually getting anything. Sure, it might fill up your screen, but that's what you want.

      There's another possibility, with the very latest XBee library you have to do a listen() on the port sometimes for it to work. Take a look at the SoftwareSerial documentation to see what I mean.

      Delete
    7. In studying SoftwareSerial more, I found out something important: on a Mega2560 like I'm using, SS RX is only supported on pins 10 and above (only those support Change Interrupts). So I thought my problem was solved when I switched to using 10 and 11. Unfortunately, it still doesn't work, so I will keep studying, and hoping for advice.

      Delete
    8. Wait a minute, you're using a 2560? Just change the code a bit and use one of the uart ports and don't even bother with SS. A 2560 can support the XBee and a console and a couple of other things directly without any software tricks.

      Drop me a note by email and we can get into this deeply. My email address is under the 'about me' on the right hand side cleverly disguised so the spam bots don't pick up on it.

      Delete
    9. Based on your hacking of the IRIS smart plug do you see any vulnerabilities that would not make it a suitable device for use in a commercial/public environment? 

      We began using approximately 8 of them  for the purpose of allowing a last ditch hard reboot for some  unattended PCs in restaurants.  The Iris plugs were on their own private wifi network.  After about a month we noticed a lot of crashes and reboots on one of the machines which lead to us realizing all 8 of the computers were having the same problems (crashes and reboots).  We yanked all 8 of the Iris plugs and the symptoms imeadiatly went away.  Very strange and very disapointing as this seemed like a very affordable solution.

      Delete
    10. Just got back in town from a trip and noticed your posting. I'm at a loss here, how are you using wifi to control the Iris switches? The Iris switches are Zigbee and use the Zigbee protocol over completely different frequencies.

      If you have a hub connected to something, that is probably your problem. I have several of them around the house and they have been 100% for years now. No wifi involved in the control at all.

      Delete
    11. Hi Dave, thanks for the reply.  I was using the Iris smart plug IRIS-WSP1PA-LW (Lowe's item number 797379).  This is one of their few no Hub required devices.  You just configure it to your WiFi network and then you can control it with your phone from the app whether you're at home or away.  In our case the networks were all ssid hidden with wpa2/psk security.  All of the computers experienced the same issues, even though the locations were many miles away (the farthest being 100mi).  It makes me wonder if iris pushes firmware updates that causes the outlets to have momentary looses of power to the relay.  The only problem with this theory is that it implies iris is pushing updates daily, which seems unlikely.  Once we pulled all the iris outlets all of the problems stopped.  Any insight you might have is welcomed.

      Delete
    12. And one other thing...none of these outlets were on a schedule. They were just "ON" all the time unless we needed to hard reboot the computer.

      Delete
    13. They absolutely do push updates of some kind or other. I don't have direct experience with the specific device, but I've watched wifi based devices get updated TWICE a day. I've also seen them try to update, fail, try again after some hours later. Each attempt to update caused the device to reboot.

      Also, I played with one device that tried to contact its server, fail, reboot and then try to contact again. That was a real pain in the bottom one time when the internet was down.

      And they don't necessarily do a push, sometimes the end device polls to request updates. Tricky those programmers out there.

      That's why I hate cloud based devices for controlling something of any consequence.

      Delete
    14. Thanks Dave, I think you nailed it. I did a little poking around in the logs of some of the machines and i see a loose correlation of the dates of the reboots that confirms your observations. It really is a shame as these Iris outlets really looked to be an affordable solution for remote power cycling.

      Delete
    15. That gives me an idea I want to try out. I'm going to make a remote reboot device of my own that can reset my dsl modem. I still have the internal network running, and it should be really simple to just use that internal network to cycle the power on the modem.

      I'm going to use wifi for this even though I would normally use the zigbee network for that kind of thing. I want a little experience with the new wifi stuff that came out the last couple of years.

      Thanks for the idea.

      Delete
    16. Dave, I got an idea and I cut open one of the IRIS outlets. The outlet uses a SPST relay (Honga HF3FA/005-HTF). I removed the relay easily and it looks like There is enough room to replace the relay with the Honga HF3FA/005-ZSTF SPDT relay and then jumper the connection so that I am using the NC terminals. This will give my outlet reverse logic, off is on and on is off. Since I want power on all the time, except when I want to hard reboot the device, this will let me leave the IRIS outlet's relay in the de-engergized state when my device is ON. My thought is that if the IRIS device receives an update, and then reboots, it won't glitch the computer since the relay is already off and (hopefully) won't momentarily be energized at reboot. What do you think? A good hack or a crazy idea. The relay is fairly cheap, ~$1-$2, and this still makes this the cheapest network controllable power outlet I can find. What are you thinking of doing for your idea, building it from scratch?

      BTW last year my son and I worked through your remote weather station project/hack for a school project of his. He modified the project a little bit but he was able to get everything working. It was an exciting project as neither of us had ever tried reading directly from a usb device/port before. Thanks for all the cool stuff.

      Delete
    17. Doug, that sounds like a really good idea. Clever method of repurposing the switch. Let me know how it works out.

      Yes, I was thinking of building something from scratch. They have all those cool little wifi devices on the market that could do this kind of thing pretty easily, and it would be a chance to play with one or two of them. The idea of having full control of the code is really appealing.

      Delete
    18. Hello dave,
      First of all its great to read your arduino and iris zigbee switch series. really opened up a lot of possibilities for me. I tried with your xbee settings and your code but with a heiman zigbee ha1.2 smartplug. I was just getting the 'starting' on the serial monitor and then when i disconnect and connect the xbee i get 2 'got frame type: 8A' messages. when i continued just connecting and disconnecting the XBee (basically pulling out the ground and putting it back) I kept getting the same 2 messages each time and occasionally errorcode 1.

      Also When I put EO as 1, in XCTU, when I scan I can see the smart plug getting connected to the XBee with a particular 16 bit address and then no communication and then getting connected again with the different 16 bit address. But when I put EO as 2 (as suggested by digi https://forms.na1.netsuite.com/app/site/hosting/scriptlet.nl?script=457&deploy=2&compid=818164&h=5928a16f2b6f9582b799&articleid=386) then it stays connected with the same 16 bit adress.

      I have 2 more things to share and hopefully get a feedback.
      1. Have to tried to connect more than one devices to the same Xbee coordinator ?
      2. Have to tried dresdon elektronik's deconz ?
      Hope to hear from you.

      Delete
  2. Hello!

    I'm feeling like banging my head against a wall. I have same problem as William has. So far I have tried the following:

    1. One xbee connected to xctu running on PC and one xbee to arduino
    2. Communication works fine when both xbees are in AT mode. Messages are passed from xctu to arduino serial terminal via softwareserial
    3. Added serial.write to xbee.cpp
    uint8_t XBee::read() {
    Serial.write(_serial->read());
    return _serial->read();
    }
    4. Keeping the xbees in AT mode and running the sketch form you in arduino. Messages between the xbees are working ok
    5. Change the both xbees to API 2 mode (co-ordinator with arduino and router connected to xctu). xctu sees the devices. Nothing shows up in serial terminal screen. I also tried to pair the adhoco (yep, I'm the same dude who asked you about the adhoco devices) device but nothing comes through to arduinos serial terminal screen, even that the serial.write -line is still in xbee.cpp

    I also added line nss.listen();, before xbee.readpacket(). No help.

    Any ideas what to try next? Or have I understood something totally wrong. Just to confirm that is it so that every time I turn on the xbee router or make the adhoco device try to join to the network, some packet should show up in the xbee-arduinos terminal screen?

    Br.
    J

    ReplyDelete
    Replies
    1. OK, you may be having a basic problem here. In AT mode, whatever you send to the XBee gets sent to the destination, however in API mode, you have to shape the data into a special packet that gets sent. There's a lot more to sending and receiving, but you get all the features of the XBee.

      As for whether or not you should be seeing a message, that depends on the device. There's a lot of transmissions that take place between XBee devices that you won't see because the XBee handles them for you. Just getting a 16 bit address assigned takes several messages between the two of them, but you won't see any of those out the serial port.

      Delete
  3. Yes I understand the difference between AT and API mode. I used the AT mode just to make sure that the sofwareserial is working. I added the xbee router just increase the possibility to get something out of the xbee coordinators serial. So far the coordinators serial has be dead silent when xbees are in API mode.

    When I turn on my adhoco S2 unit, it first seems to connect to the xbee because the led indicator on S2 turns green. After about 10 seconds the leds start blinking (three time, pause, three times, pause, etc). This means that S2 hasn't joined the network and that there has been an error in the joining process. From your posts I have understood that, to make the S2 to join the network, I need to receive the Device Announce Message from S2 and generate proper reply message with right cluster ID, etc.

    The challenge is that, xbee doesn't transmit any of the received messages through the serial line. I don't actually know what of the received messages it should push to the serial line.

    If I use the Xbee coordinator through xctu and turn on the network discovery, the S2 show up for a brief moment (only if I have done full reset to the S2 unit). XCTU show the adhocos address and mode of the device (router in this case).

    The following code should print all traffic, which going through Xbee serial connection, to the terminal screen. Right?

    #include
    #define ssRX 2
    #define ssTX 3
    SoftwareSerial nss(ssRX, ssTX);
    void setup() {
    Serial.begin(9600);
    nss.begin(9600);
    Serial.println("started");
    }
    void loop() {
    if (nss.available() > 0) {
    Serial.write(nss.read());
    }
    }

    I noticed that you have used different API modes in different zigbee projects and Encryption options for iris and centralite devices. Iris uses API2 and EO 1 and centralite uses API1 and EO0. So the Iris using escape characters and centralite doesn't but what is the difference EO options?
    Any ideas what to try next?

    I could try to send a AT ND command to the coordinator throught terminal screen. I can create right message with the xctu message creating to…

    ReplyDelete
    Replies
    1. I'd forgotten about encryption, and the explanation in the XBee manual is the very best I've found for how it works, take a look there. However, it won't stop you from getting the very first Zigbee messages. They have to do some route record stuff before they can exchange keys so that should be showing up.

      You're right, the code you have should show up anything that comes in. I recommend going back to what you know works and make sure you can get things on the controller from anywhere and then change one item at a time and see where it stops working for you using only XBees. When you can receive api mode 3 messages, you should be receiving the Zigbee messages from the target device if it is sending them.

      I had some trouble getting API mode 3 to work, but I can't remember what it was and it was relatively easy to fix. I do remember it being a pain switching between API and AT modes to try and figure out what was going wrong since I had to reprogram them each time.

      Delete
    2. I have read key parts of the xbee manual and also read through the Building Wireless Sensor Networks book but I think that I will do it once more...

      One weird thing happened yesterday. I was once again play with the xbee trough XCTU and suddenly in the network topology there was about 12 adhoco devices. I have quite extensive adhoco system running already in my appartment. I save the logs but I forgot to save the xbee settings. Anyway somehow my xbee cordinator was able to read some of the traffic in the completely different zigbee network. I haven't able to duplicate this again...

      Anyway I won't give up until I have nailed this:)

      I read from the digi -forum that to get zigbee HA working more reliable, the xbee ZB SMT units should be used. The newest firmware of those units is dated to mid 2015.

      Delete
    3. If you were seeing the adhoco messages, you probably had the XBee programmed as a router instead of a controller. That's actually a plus in that you might be able to watch traffic somewhat between the devices.

      Delete
    4. Nope, it was programmed to coordinator. Here is the logs from XCTU I mentioned earlier:

      MAC,Role,Network Address,Parent,Last scan,Connections
      0013A200403B0CBD,Coordinator,0000,,1,"0013A20040E6EBE5 Router [1287] ? Active,0018E50000010140 Router [714E] ? Active,0018E5000001039D Router [7A65] ? Active,0018E500000100B5 Router [C16A] ? Active,0018E500000100CA Router [CAC8] ? Active,0018E50000002FDB Router [F58A] ? Active,0018E5000000547C Router [FB1F] ? Active"
      0013A20040E6EBE5,Router,1287,,1,"0013A200403B0CBD Coordinator [0000] ? Unknown"
      0018E50000010140,Router,714E,,1,"0013A200403B0CBD Coordinator [0000] ? Unknown"
      0018E5000001039D,Router,7A65,,1,"0013A200403B0CBD Coordinator [0000] ? Unknown"
      0018E500000100B5,Router,C16A,,1,"0013A200403B0CBD Coordinator [0000] ? Unknown"
      0018E500000100CA,Router,CAC8,,1,"0013A200403B0CBD Coordinator [0000] ? Unknown"
      0018E50000002FDB,Router,F58A,,1,"0013A200403B0CBD Coordinator [0000] ? Unknown"
      0018E5000000547C,Router,FB1F,,1,"0013A200403B0CBD Coordinator [0000] ? Unknown"

      and the com log:
      09-30-2015 17:31:43.067,25,RECV,7E0043910013A200403B0CBD0000000080310000012400070202B588E64C8CF755999D03010000E51800657A35020FF0B588E64C8CF75599B500010000E518006AC135020FFFFE
      09-30-2015 17:31:44.114,26,SENT,7E001611240013A200403B0CBDFFFE000000310000000024047B
      09-30-2015 17:31:44.130,27,SENT,7E001611250013A20040E6EBE5FFFE00000032000000002500CA
      09-30-2015 17:31:44.255,28,RECV,7E0043910013A200403B0CBD0000000080310000012400070402B588E64C8CF75599CA00010000E51800C8CA35020FFFB588E64C8CF75599DB2F000000E518008AF535020FFF68
      09-30-2015 17:31:44.255,29,RECV,7E00078B24000000000050
      09-30-2015 17:31:45.310,30,SENT,7E001611240013A200403B0CBDFFFE0000003100000000240679
      09-30-2015 17:31:45.467,31,RECV,7E002D910013A200403B0CBD0000000080310000012400070601B588E64C8CF755997C54000000E518001FFB35020FFF85
      09-30-2015 17:31:45.467,32,RECV,7E00078B24000000000050
      09-30-2015 17:31:45.795,33,RECV,7E00078B25FFFD0024012E
      09-30-2015 17:31:47.225,34,SENT,7E001611260018E50000010140FFFE0000003200000000260034
      09-30-2015 17:31:48.878,35,RECV,7E00078B26FFFD0C240121
      09-30-2015 17:31:50.316,36,SENT,7E001611270018E5000001039DFFFE00000032000000002700D3
      09-30-2015 17:31:51.989,37,RECV,7E00078B27FFFD0024012C
      09-30-2015 17:31:53.344,38,SENT,7E001611280018E500000100B5FFFE00000032000000002800BC
      09-30-2015 17:31:54.985,39,RECV,7E00078B28FFFD0024012B
      09-30-2015 17:31:56.407,40,SENT,7E001611290018E500000100CAFFFE00000032000000002900A5
      09-30-2015 17:31:58.058,41,RECV,7E00078B29FFFD0024012A
      09-30-2015 17:31:59.471,42,SENT,7E0016112A0018E50000002FDBFFFE00000032000000002A0064
      09-30-2015 17:32:01.113,43,RECV,7E00078B2AFFFD00240129
      09-30-2015 17:32:02.535,44,SENT,7E0016112B0018E5000000547CFFFE00000032000000002B009C
      09-30-2015 17:32:04.163,45,RECV,7E00078B2BFFFD00240128
      09-30-2015 17:32:09.884,46,SENT,7E00050846414F0021
      09-30-2015 17:32:09.967,47,RECV,7E00058846414F00A1

      This offer is still valid:
      hey, if you want I can ship one of these to you:
      http://www.lumiere.co.za/custpics/0/38/133/adhoco-c1-product-description.pdf

      I have some extras to share:)

      Delete
    5. Normally I'd jump on a chance to play with a new device, but I'm still up to my ears in projects around the house that I procrastinated on for too long. It would probably be a month or more before I could even think about playing with it.

      Delete
    6. No problem, I have extras to share. If you find the time before end of this year, that would be awesome, if not that's fine too. My target is to crack this problem before you anyway;) If you want that C1 sensor, send me you post address and I will ship the sensor to you.

      Delete
    7. Julli, drop me an email. My address is over in the 'about me' link on the right encoded slightly to stop the spam bots from flooding my mail.

      Delete
    8. The sensor is on it's way.

      Had some success with the adhoco s1 sensor. By setting the EE = 0, the S1 joins to the network. Xbee also receives the following api frames:

      Frame type: 91 (Explicit RX Indicator)
      64-bit source address: 00 18 E5 00 00 01 03 50
      16-bit source address: 0C 82
      Source endpoint: 0B
      Destination endpoint: 0B
      Cluster ID: 00 04
      Profile ID: 0F 05
      Receive options: 01
      RF data: 02 00 18 E5 00 00 01 03 50 06 02 06 0B 64 22 8E 00 56 31 2E 34 2E 32 00 31 2E 31 2E 30 00 B1 16

      Source endpoint: 00
      Destination endpoint: 00
      Cluster ID: 00 13
      Profile ID: 00 00
      Receive options: 02
      RF data: 00 82 0C 50 03 01 00 00 E5 18 00 0C

      Source endpoint: 0B
      Destination endpoint: 0B
      Cluster ID: 00 03
      Profile ID: 0F 05
      Receive options: 01
      RF data: 02 00 18 E5 00 00 01 03 50 06 02 06 31 00 00 00 00 87 FB

      If the EE is set to 1, S1 tries to join but quits and no messages is passed to xbee's UART. Strange...

      Sad thing is that the C1 sensors don't want to join at all.

      Delete
  4. hello for my senior design project as an electrical engineering student i am making my own smart hub with data analysis to log appliance power consumption in the home over the course of the month. i am using an iris smart plug, xbee smt module, xctu to configure the xbee, and an arduino as a hub to receive power data before sending it to an online database? to manage the large amount of data. i am downloading your code off of github now, originally was working with andrewrapps code but i think yours is better for my needs/ more updated. i am having trouble sending frames in xctu to the smart plug (which is already connected to the network) to see power consumption data. the whole packet sending (so many to choose from and what clusters have to be used and send in certain orders) is confusing to me. I am not sure if you still monitor this blog post but any help would be greatly appreciated. thanks!

    ReplyDelete
    Replies
    1. Yes, I still monitor the older posts. I actually go back and look at my own posts from time to time to refresh myself on how some of this stuff works.

      Trying to use XCTU to talk to a zigbee device is an exercise in pure frustration. I was able to actually do it only once, and I swore I would never even think about trying it again. Just too much there to try and duplicate. It's much easier to just grab an arduino or a pi and write some code that I can modify as I go through the process.

      After you get the code from github, start playing with it to see the interaction. You don't have to send something to get back power usage information, it comes on a regular basis; you just have to save it.

      If you switch over to using a Pi as the controlling and recording device, I have all the code necessary to do this out there for perusal. I actually do what you're going to do to monitor my fridge and two freezers real time. I also store this stuff in a data base and generate charts using grafana to analyze power usage and failures (there's been a few over the years).

      I recently put a subject listing at the upper right of the blog so you can click on appliances and see what I have going on. The listing is new, so it may not be complete yet, but it may help.

      Delete
    2. i will send you an email and hope you can help me out a bit. i appreciate it. i will include a few pics of our hardware setup and xctu settings. it will be from an @ wit . edu email. thanks!

      Delete