The World of XBee

Things about Xbees  (this will get edited as I discover things so expect it to change and grow).  I start off with an explanation of why things don't work for many people.  Scroll down to see devices that I've made using these great little radios.   




If Things Don't Work Right
There's about a million tutorials on XBee devices on the web and I'm not going to rehash other people's work in this area.  The problem is that they seem to repeat themselves too much, and many times the examples only work once.  You reprogram the radio and the system stops working.  I had this happen several times and finally almost threw them in a box on the shelf in pursuit of something that actually worked.  Blind stubbornness helped me overcome all the problems I ran into and I can now make them work.  Not that I have a huge sophisticated network, but I can make them talk to each other.


First, some things that you need to know that aren't mentioned:
  • The router and end devices have to be commissioned into the network.  This has to be done every time the coordinator is reprogrammed. This takes 4 pulses on pin 20.  This is because the data that is used to communicate is lost during the reprogramming process.  
  • When the commission finishes, the NI parameter (name) is blank on the router, the router must be reset to restore this.  Of course you can reprogram the name back into the router as well
  • The best software that I have worked with so far is ZigBee using a XB24-ZB modem.   This is one of the choices offered by X-CTU
  • A network reset (ATNR 1) will blow off all the devices on the network and you have to recommission each of them.
  • The coordinator has to be running for the router and end device to commission into the network.  So, start the coordinator first then commission the various routers.
As I mentioned, reprogramming the devices removes the settings that make them work.  There are 4 of them that will make your xbees stop talking and all of them can be affected by reprogramming, especially if you get an error while the software is setting the AT commands; this happens frequently.

OP Read the operating 64-bit PAN ID.
OI Read the operating 16-bit PAN ID.
CH Read the operating channel.
ZS Read the stack profile.

Now, most of us don't mess with the ZS command and zero seems to work fine so I leave it alone.  The OI is set by the coordinator and we don't get to choose it when we first start up.  The CH (channel) is also chosen by the coordinator.  The only one we are usually told to mess with is the OP when we set the ID.  So, what you have to do is record these when you get a working network and then keep them around to fix the network when you reprogram a coordinator or accidentally issue a network reset.



So let me illustrate how you can reprogram a coordinator node and then get the network back up and working.  The assumptions are that you have:
  • Two or more XBees already working in AT mode - this is the 'transparent' mode where you issue commands to the modem using some terminal program or X-CTU itself.
  • These little guys are running Zigbee software with the modem set to XB24-ZB.  The particular set is dependent on what the XBee's role is:  Coordinator, Router or End Device.  For simplicity I'm just going to consider coordinator and routers.
  • Since you have already gotten a network set up, you've already come up with a way to talk to them using some terminal program or X-CTU.  Remember, if you have two or more unused USB ports you can plug them into your computer and use multiple instances of X-CTU to control and monitor the devices.
So, look at the coordinator, issue the commands below and record the results.  Yes, get a pencil and actually write something down.


ATOP (returns up to 16 hex digits for example 1234)
ATOI  (returns up to 4 hex digits  for example ABCD)
ATCH (returns the actual channel being used, 1 hex digit for example C)
ATZS ( returns one hex digit, usually zero)


Each of these will return a value that is necessary to a working network.  So after reprogramming the coordinator to something (usually going from API mode to AT mode) you have to put these values back in.  However, you don't just use the same command, since most of these are read only values.  The commands below are used to put the values back into the reprogrammed coordinator:


ATID 1234     - this sets the PAN to the value you read from the OP parameter above.
ATII ABCD     - this sets the initial operating 16 bit id to the value read from the OI above
ATZS 0           - this sets the stack profile as above (this is not a read only value)
ATSC 2          - this is a bitfield that specifies the channels that may be scanned.  0x02 allows 
                          only the channel 0x0C to be used.  This prevents the coordinator from moving
                          off the channel.


If you want to be sure the values get recorded to eeprom, issue the ATWR command, but the commands above should save the commands.  I always do the write command because I want to be darn sure it worked.


As you issue these commands, the coordinator will exit the network and rejoin allowing the various other devices to join as well.  After completing the commands the coordinator is running on a known set of network parameters and can be troubleshot without nearly as much difficulty.  If you happen to notice, the ATII command is not documented in the command references that you find.  It is documented in the Digi documentation under replacing the coordinator.  This was hard to find and solved most of my problems when I came across it.  Look in:


http://ftp1.digi.com/support/documentation/90000976_G.pdf


Regarding the routers, the easiest way to program them is to ground pin 20 on the XBee 4 times like you are pushing a switch.  This will tell the device to leave the current network and rejoin.  This process is called commissioning.  The device will flash a couple of lights if you have them and join the network you just set up on the coordinator.  This is how you add a new device and make sure it can talk.  You can also program the router in the same fashion as above to make it join.  There's also the command ATCB 4 that will cause the same action as grounding the commission pin.


Power cycles, unplugging, hardware resets (like grounding the reset pin) don't change the settings and the devices will work fine under these conditions.  The problem now is that going from AT mode to API mode will always cause the coordinator to lose these items and they cannot be set with at commands because you just switched modes and typing AT commands doesn't work.  This is the situation where I was ready to stomp a couple of the devices into fine dust on the floor.


I put together a simple script to work with Xbees in API mode and observe the various actions.  You're welcome to grab it and modify it to your needs.  This script was thrown together in a hurry and has the bare minimum necessary to command and observe the devices, don't expect massive sophistication or features.  One thing that is not made clear in a lot of tutorials is that every device doesn't have to be in API mode.  If your coordinator is running in API and the routers all in AT, the network will work just fine. So you can move into higher levels of sophistication at your leisure.  A couple of things to note:  I did not use the Arduino XBee library, that thing is just too darn hard to understand for me.  Also, I used NewSoftSerial version 10C (in case you have trouble compiling).  This allows me to have the XBee and the serial port active at the same time.  Let's face it, switching the programming serial port around every time you test something slows testing down too much.   http://arduiniana.org/libraries/newsoftserial/


SKETCH
#include <NewSoftwareSerial.h>

SoftwareSerial xbeeSerial =  SoftwareSerial(3, 2);
/*
  at commands begin with a frame delimiter
 then the length in two bites
 the hex number 8 (meaning AT command
 the number 1 meaning I want a response back
 the command to be executed
 any parameters to the command
 a checksum
 */
// The command and parameters are here
char ATND[]={
  0x08, 0x01, 'N', 'D'};  //node discover
char ATID[]={
  0x08, 0x01, 'I', 'D', 0x12, 0x34};  // Pan id to use
char ATOP[]={
  0x08, 0x01, 'O', 'P'};  //read operating pan id
char ATII[]={
  0x08, 0x01, 'I', 'I', 0x17, 0xE8};  // Initial 16 bit PAN ID
char ATSC[]={
  0x08, 0x01, 'S', 'C', 0x02};  //only allow chanel 0x0C
char ATZS[]={
  0x08, 0x01, 'Z', 'S', 0x00};  //zigbee addressing type 0
char TX[]={0x10, 0x01,
           0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, // 64 bit hardware address
           0x88,0x0c, // network address
           0x00,0x00, // broadcast radius and options
           'A',};     // finally, the actual data
char cmdbuf [100];
unsigned int cmdlen;
byte checksum;

void sendATcommand(char* command, int length){
  // first calculate the length and fill it in
  checksum = 0;
  cmdbuf[0] = 0x7E;  //command start signal
  cmdlen = length; //the command characters & overhead
  cmdbuf[1] = cmdlen >> 8;
  cmdbuf[2] = cmdlen & 0xFF;
  //copy command string into buffer
  int i = 0;
  for ( ; i < length; i++){
    cmdbuf[i + 3] = command[i];
    checksum += command[i];
  }
  cmdbuf[i+3] = 0xFF - checksum;
  Serial.print("Packet sent ");
  for (i=0; i < cmdlen + 4; i++){
    printByteData(cmdbuf[i]);
    Serial.print(" ");
  }
  Serial.println();
  for(i=0; i < cmdlen+4; i++){
    xbeeSerial.print(cmdbuf[i]);
  }
}

void setup()  {
  Serial.begin(9600);
  // set the data rate for the SoftwareSerial port
  xbeeSerial.begin(9600);
  Serial.println("Serial ports initialized");
}

uint8_t received;
#define WaitForFrameStart 1
#define LengthHighByte 2
#define LengthLowByte 3
#define PayloadCapture 4
#define CheckChecksum 5
int framestatus = WaitForFrameStart;
int datalength = 0;
int savedDataLength;
char buf[100];
char *payloadbuffer;

char inputbuf[50], constructed[50];
char* bufptr = inputbuf;
int count = 0;

void loop()                     // run over and over again
{
  if(Serial.available()){
    switch (Serial.read()){
    case '1':
      sendATcommand(ATSC, sizeof(ATSC));
      break;
    case '2':
      sendATcommand(ATID, sizeof(ATID));
      break;
    case '3':
      sendATcommand(ATOP, sizeof(ATOP));
      break;
    case '4':
      sendATcommand(ATII, sizeof(ATII));
      break;
    case '5':
      sendATcommand(ATZS, sizeof(ATZS));
      break;
    case '6':
      sendATcommand(ATND, sizeof(ATND));
      break;
    case '7':
      sendATcommand(TX, sizeof(TX));
      break;
    default:
      break;
    }
    Serial.flush();
  }
  if(millis() % 10000 == 0)
    sendATcommand(TX, sizeof(TX));
  checkXbee();
}

void checkXbee(){

  if (xbeeSerial.available()) {
    received = (uint8_t)xbeeSerial.read();
    switch( framestatus ){
    case WaitForFrameStart:
      if(received != 0x7E)
        break;
      else {
        framestatus = LengthHighByte;
//        Serial.print("Frame start, ");
        checksum = 0;
        payloadbuffer = buf;
      }
      break;
    case LengthHighByte:
      datalength = received;
      framestatus = LengthLowByte;
      break;
    case LengthLowByte:
      datalength = (datalength * 256) + received;
      savedDataLength = datalength;
      Serial.print("length ");
      Serial.print(datalength);
      Serial.print(", ");
      framestatus = PayloadCapture;
      break;
    case PayloadCapture:
      *payloadbuffer++ = received;
      printByteData(received);
      Serial.print(" ");
      datalength--;
      checksum += received;
      if (datalength == 0){
        framestatus = CheckChecksum;
        *payloadbuffer = '\0';
        Serial.print("received, ");
      }
      break;
    case CheckChecksum:
      checksum += received;
      if (checksum == 0xFF){
        Serial.println("Checksum valid.");
        xbeeFrameDecode(buf, savedDataLength);
      }
      else {
        Serial.println("Checksum invalid.");
      }
      framestatus = WaitForFrameStart;
      break;
    default:
      break;
    }
  }
}

void xbeeFrameDecode(char* buffer, int length){

  switch ( *buffer){
   case 0x90: {
     Serial.println("Receive Data Frame");
     buffer++;                //skip over the frame type
     length--;
     Serial.print("Source 64 bit address: ");
     for(int i=0; i<8; i++){  //address the frame came from
       printByteData(*buffer);
       if (i == 3)
         Serial.print(" ");
       buffer++;
       length--;
     }
     Serial.println();
     Serial.print("Source 16 bit network address: ");
     for(int i=0; i<2; i++){  //16 bit network address the frame came from
       printByteData(*buffer);
       buffer++;
       length--;
     }
     Serial.println();
     Serial.print("Receive options: ");  // options byte
     printByteData(*buffer);
     Serial.println();
     buffer++;
     length--;
     while(length-- > 0){ //assuming the actual data is ascii
       Serial.print(*buffer++);
     }
     break;
   }
   case 0x88:{
     Serial.println("AT Command Response Frame");
     break;
   }
   case 0x8B: {
     Serial.println("Transmit Status Frame");
     break;
   }
   default: {
     Serial.println("Unimplemented Frame Type");
     break;
   }
 }
}

void printByteData(uint8_t Byte){
  Serial.print((uint8_t)Byte >> 4, HEX);
  Serial.print((uint8_t)Byte & 0x0f, HEX);
}





The XBee Thermometer

It gets hot here in the desert and I would like to know how hot it is outside, and I want to be able to do it from my recliner, under the air conditioner, sipping a beer.  It would also be nice to record this somewhere and be able to point at it and brag.  So, prowling around the web I found several examples of temperature sensors hooked to arduinos and a few examples of the sensors hooked directly to an XBee.  Lady Ada has a great project page on the Tweet-a-Watt which is a power sensor hooked to an XBee.  So, I grabbed an XBee, programmed it to send analog input from one of its pins and stuck it to the side of a 5V wall wart.

It sends an analog reading every minute to my controller which does the calculations to convert from analog voltage to temperature and serves it on a web page.  The controller also sends the temperature to Pachube as a part of my set of data feeds.


No, it's not waterproof.  I'll look into a proper enclosure and protecting it from sun and rain after I get a little experience with it.  I have plenty of time, it doesn't rain very often and there is no morning dew.  I'm more likely to have a failure because a pack rat steals it and makes it part of his nest.

One thing that was interesting is that the analog inputs on the XBee have a 1.2V maximum input and the LM34 temperature sensor has a 10mv per degree F output; that makes my maximum temperature 120F.  That may not sound like much of a problem, but remember, this is Arizona.  We pretty regularly have days that peak over 120F during the hottest part of the year.  I used a pair of 10K resistors to divide it down so my max is 240F, that should be enough...

Let me note that setting this up was fairly easy, which means that one can have temperature sensors anywhere there is a power plug.  Heck, if you're willing to change batteries, you can put one anywhere at all.  That opens the possibility of placing sensors around the house to control recirculation fans for smoothing out temperatures.  An outdoor sensor could use a solar charger and be maintenance free for a considerable period of time.  

This device cost me around $28 to build and did not require a microprocessor at the sensor end.  I send everything to a device I already have and it puts the data on the network.  I could add another device somewhere else for the same cost.  I could add another sensor, perhaps a light sensor, to the same device for less than a dollar.


In the graph above you can see how the temperature spikes way up when the sun hits the wall the sensor is plugged into.  This is real, and my dogs sunbathe near that spot in the morning.  After the sun rises a bit the device is pretty accurate at measuring the air temperature.  It will be fun to watch this over time to see what happens as the summer season passes.  This graph is almost real time and reflects about a months readings.

4 comments:

  1. Awesome! I am trying to do something similar by creating a home temperature station containing both wired and wireless temperature sensors (with Xbee).

    I am using an Arduino with Ethernet shield to upload data to Nimbits and a google doc so I have the data logging and accessible from anywhere.

    ReplyDelete
  2. Don't you just love these little XBees? I've never looked at Nimbits, I'll have to check it out soon.

    ReplyDelete
  3. I do like the simplicity of your creation, Make sense to use the Xbee I/O's instead of a uC. Judging by the pic you posted and the Pachube log as well. I see you reach over 150 Degrees regularly. In my opinion I would not have the sensor mounted to the outside wall so closely. If the wall is in direct sunlight, it will start to collect and radiate heat from the sun. making your sensor reading in-accurate. Still very simple and sweet.
    Best Regards-

    ReplyDelete
  4. Yes, it's too darn close to the wall. And, the sensor is too darn close to the power supply. So I actually have two problems going on. I'm going to build one of those weather station enclosures and get the sensor away from the house and in the shade all day. Just have to get the time and inclination to continue on this project.

    It is nice being able to read the temperature any place in the house though.

    ReplyDelete