Monday, October 22, 2012

Monitoring My XBee Network

In the previous post <link> I wrote about how I discovered broadcast mode can cause problems as you increase the number of nodes.  That led to a method of monitoring each node to see what it was actually doing, and by watching each of them in turn, seeing how the traffic patterns work out.

Well, I left it kind of up-in-the-air on exactly how I did it.  Basically, I used the examples that Akiba of Freaklabs released for various functions and created a sniffer of sorts.  This is necessary because the XBee only sends out the serial port data intended for the particular radio module you're plugged into.  That's what makes broadcast mode so useful, you can see all the traffic and chase problems.  With a Freakduino Chibi board, you can monitor all the devices on your network at once, but that is way too much information to try to wade through.  By setting the Chibi board to promiscuous mode and then writing code to filter by address, you can watch any node in the network to see what is going on with that particular device.  This makes it possible to see its traffic even though you aren't using an XBee.

Here's the code I came up with:

The Arduino Sketch
/*
This sketch enables promiscuous mode on the Freakduino boards and then
filters by device address to watch a particular node.

I had to change the buffer size in the chibi library for promiscuous mode
because, being set to 1024 with another buffer for the packet to be manipulated
caused the board to run out of RAM.  Just go into the chibi library and
find the receive buffer in chb_buf.h and set it to what you want.  I used the
value 500, and it seems to work fine

Before using this sketch, also go into the chibiUsrCfg.h file and
enable promiscuous mode. To do this, change the definition:

#define CHIBI_PROMISCUOUS 0
to
#define CHIBI_PROMISCUOUS 1

When not using promiscuous mode, disable this setting by changing
it back to 0.
*/

#include <chibi.h>
#include <avr/pgmspace.h>
#include <MemoryFree.h>

/*
Addresses of devices in my network
                    Long     Short
Garage Controller 407A 38AF  A019
Power Monitor     4031 566B  CD6B
Controller        4055 B0A6  97FA
Status Display    406F B6B5  9BE8
Pool Controller   4079 B2BD  9370
Temp1             406F 7FAC  1B1E
House Clock       4055 B094  0
Acid Pump         406F B7AF  7D58
*/
char *deviceName[] = {"Garage Controller",
                       "Power Monitor",
                       "Controller",
                       "Status Display",
                       "Pool Controller",
                       "Temp1",
                       "House Clock",
                       "Acid Pump"};
uint16_t deviceAddress[] = {0xA019,
                         0xCD6B,
                         0x97FA,
                         0x9BE8,
                         0x9370,
                         0x1B1E,
                         0x0,
                         0x7D58};
uint16_t listenTo;
byte Pbuf[500];
char Dbuf[50];
int longest = 0;

void showMem(){
  strcpy_P(Dbuf,PSTR("Mem = "));
  Serial.print(Dbuf);
  Serial.println(freeMemory());
}

/**************************************************************************/
// Initialize
/**************************************************************************/
void setup()
{
  // Init the chibi stack
  Serial.begin(115200);
  chibiInit();
  strcpy_P(Dbuf,PSTR("\n\r***************I'm alive**************"));
  Serial.println(Dbuf);
  unsigned int choice = 0;
  Serial.println("Pick a device to listen to");
  for(int i = 0; i < sizeof(deviceName)/sizeof(deviceName[0]); i++){
    Serial.print((char)(i + 'A'));
    Serial.print(" - ");
    Serial.println(deviceName[i]);
  }
  Serial.print("> ");
  // this only accepts a single character for the device choice.
  // that's because the chibi board runs at 8MHz on the internal clock and
  // internal clocks aren't accurate.  This means the higher baud rates sometimes
  // have problems.  When I worked on this I couldn't get the board to accept more
  // than one character correctly at baud rates over 57K.  Works fine at 9600,
  // but I want to output the data far faster than that.
  while(1){
    if(Serial.available() != 0){
      char c = Serial.read();
      Serial.write(c);
      Serial.println();
      choice = toupper(c) - 'A';
      if (choice < sizeof(deviceName)/sizeof(deviceName[0]))
        break;
      else {
        Serial.println(" oops, try again");
        Serial.print("> ");
      }
    }
  }
  listenTo = deviceAddress[choice];
  strcpy_P(Dbuf,PSTR("Starting Radio, Result = "));
  Serial.print(Dbuf);
  Serial.println(chibiSetChannel(12),DEC);
  strcpy_P(Dbuf,PSTR("Listening to "));
  Serial.print(Dbuf);
  Serial.print(deviceName[choice]);
  strcpy_P(Dbuf,PSTR(" at address "));
  Serial.print(Dbuf);
  Serial.println(listenTo, HEX);
  showMem();
}

/**************************************************************************/
// Loop
/**************************************************************************/
void loop()
{
  // Check if any data was received from the radio. If so, then handle it.
  if (chibiDataRcvd() == true)
  {
    int len;
    uint16_t senderAddress;
 
    // send the raw data out the serial port in binary format
    len = chibiGetData(Pbuf);
    if (len > longest)
      longest = len;
    senderAddress = chibiGetSrcAddr();
    if (senderAddress == listenTo && len > 0){
      strcpy_P(Dbuf,PSTR("From: "));
      Serial.print(Dbuf);
      Serial.print(senderAddress,HEX);
      strcpy_P(Dbuf,PSTR(" Len: "));
      Serial.print(Dbuf);
      Serial.print(len);
      strcpy_P(Dbuf,PSTR(" Longest: "));
      Serial.print(Dbuf);
      Serial.println(longest);
      for (int i= 0; i < len; i++){
        uint8_t nibble = (uint8_t) (Pbuf[i] >> 4);
        if (nibble <= 9)
          Serial.write(nibble + 0x30);
        else
          Serial.write(nibble + 0x37);
     
        nibble = (uint8_t) (Pbuf[i] & 0x0F);
        if (nibble <= 9)
          Serial.write(nibble + 0x30);
        else
          Serial.write(nibble + 0x37);
        Serial.print(' ');
      }
      Serial.println();
      for (int i= 0; i < len; i++){
        uint8_t nibble = (uint8_t) (Pbuf[i] >> 4);
        Serial.write(' ');
        if (iscntrl(Pbuf[i]))
          Serial.write(' ');
        else
          Serial.write(Pbuf[i]);
        Serial.write(' ');
      }
      Serial.println();
    }
  }
}


A couple of things to keep in mind.  The Chibi board is only running at 8 Mhz, so it can't keep up with unlimited traffic flying through the air at 250Kb forever.  If you spend too much time in the code checking and decoding, you'll miss packets.  The Chibi library sets a buffer size of 1024 to handle packets being received to help with this, but you'll have to reduce this size because the MCU only has 2K of ram to begin with.  I point this out in the code.  The maximum size of a packet is 127 bytes, so make your parsing buffer longer than that.  I print the maximum packet length in the code above, and have never seen a packet larger, so it appears the library is working fine for separating the packets.  I also put a routine in the code to monitor  memory usage.  Use this to keep track so you don't run out with the various buffers and such.

Here's a sample of the output using the serial monitor from the Arduino IDE:


It doesn't wrap around, but that could be easily added.  I just use the scroll bar on the bottom to move around.  I output the hex values as well as the ascii so I can recognize the various packets as well as decode the binary portions of the data.

So, as your network grows, or you have problems right off the bat, consider this combination of things as a possible tool to help you get past it.  Of course, you're welcome to grab the code and modify it to your particular network and need for monitoring.

20 comments:

  1. Many thanks, I have been struggling with XBee in API mode for weeks. This really helped me make progress. Also a great blog that this gadget gal enjoyed reading.

    Chris



    ReplyDelete
  2. I almost gave up on XBees when I first started using them. Then, something clicked and they started working for me. That's one reason why I started blogging about them. They are really cool little tools, but only once you start getting them to actually talk to one another. If you haven't already, take a look at the posts on the Arduino XBee library. Now that the library supports the another serial port (either software or hardware), it makes a nice interface to the devices.

    ReplyDelete
  3. I just ordered a chibi for sniffing my network (thanks for all of the xbee ideas,examples and help) and upon looking into the chb_buf.h file to change the buffer size it already takes care of changing the buffer size for you. It's set to 768 if in promiscuous mode so I don't think there is a need for that step anymore. The file was updated 3 months ago so I'm assuming that they went in and took care of the problem you were having. Just trying to give a little back
    Dave

    ReplyDelete
  4. Thank you very much. The next person that reads this will save some time...or me when I make changes to it.

    ReplyDelete
  5. Could you post a link to where you learned about strcpy_P & PSTR? I've been having a look around and there seems to be a bit of conflicting and confusing views and usage of strings being stored in program memory, to the point where someone is giving out instructions on changing code in a library to make it work.

    ReplyDelete
  6. I came across it by messing with the code in the arduino playground. However, they put a relatively new page up that has more examples at:

    http://playground.arduino.cc/Main/Printf

    There's stuff in there that I haven't played with yet.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. try this bit of code out to help you with reading multiple characters. I use numbers but I'm sure it can be easily adapted to work with characters

    [CODE]
    // function reads user input and converts input array to an integer

    char read_array[4]; // array to store user input in menu make any size you want
    // Serial.flush(); used to clear the Rx buffer but doesn't work in 1.0.
    // I do this so that if the user accidentally enters something before they
    // are prompted to it won't be read.
    Serial.println("Whatever your table is."); // print user prompt
    while(Serial.available() > 0) // if buffer isn't clear
    Serial.read(); // clear it
    while (Serial.available() == 0); // wait here for user input
    //while(Serial.available() <= sizeof(read_array) - 1); // or wait for buffer to fill
    for (int i = 0; i <= sizeof(read_array); i++){
    read_array[i] = Serial.read(); // read incoming data into array
    delay(10); // breathe
    }
    int choice = atoi(read_array); // array to int
    // work some magic with choice!
    [CODE]

    ReplyDelete
    Replies
    1. Thank you. What baud rate are you using on the serial port? I'll give this a try in a week or two; I'm up to my neck in another project that is eating me alive right now.

      Delete
  9. I usually use 57600 but lately I've been running 115200. the datasheet for the 328p says that 57600 is the fastest with the lowest error rate which is why I started using it. I haven't had any issues with 115200 but I've been working with native serial ports and 16MHz so I don't know how it will respond using softSerial and/or 8MHz. I've also looked up .printf and now I'm wrapping all of my display text with F(); serial.print(F("Hello World")); It saves a ton of memory (I now use 328p where I needed a Mega2650 before). I haven't played around with it a lot but I haven't noticed any bugs that have come up from storing strings in flash.
    Cheers!

    ReplyDelete
    Replies
    1. I remember when I discovered that strings ate up most of my memory. From then on, my project size dropped a bunch. Watch out for the String datatype and various stream operations too. They eat memory at run time and can kill an Arduino. Scout around here for the little memory routine that I use and include it as needed. I use the heck out of it.

      Delete
  10. I worked on some quick code that lets you enter a string of characters into the serial monitor and it will get saved as an array. Does anyone know how to then take the array that was just made and turn it into a string and place it inside of another array? The thought is; you have your array that holds the names of all your nodes, well say you want to rename one of the nodes but while the program is running and not just through re-uploading the code with a different name.
    [CODE]
    byte numOfElem = 20;
    char readName[numOfElem] = {}; // character array
    Serial.print(F("Enter Name (Cannot Be Greater Than 20 Characters: "));
    while (Serial.available() > 0){ // if there is junk in the serial inbox
    Serial.read(); // read it out
    }
    while(Serial.available() == 0); // wait for user input
    byte i = 0;
    while(Serial.available()){
    readName[i] = Serial.read(); // place character into array
    Serial.print(readName[i]); // debug
    i++;
    }
    Serial.println();
    Serial.print(readName);
    [CODE]

    ReplyDelete
    Replies
    1. To turn it into a 'c' string, just put a null at the end. Right after the loop just stick the null in. To copy from one array to another , you can then use strcpy(). If you want to you could simple set the array to 0 first by using memset() to clear it all. Be sure to allow for an extra character at the end.

      Delete
  11. Ok I think I got this all figured out. remember this is a code snippet! this could possibly work great for a network that shuts down and then needs to find 16 bit network addresses again. Next I want to work on getting the chibi to print out to a display of some sort, not sure if I'll have enough memory for a lot of data to be displayed thinking a receipt printer might work better, but it could work for individual events and then the chibi could be a truly stand alone system monitor.
    thanks for the patience and support Dave!
    [CODE]
    byte numOfElem = 20;
    char readName[numOfElem] = {}; // character array
    Serial.print(F("Enter Name (Cannot Be Greater Than 20 Characters: "));
    while (Serial.available() > 0){ // if there is junk in the serial inbox
    Serial.read(); // read it out
    }
    while(Serial.available() == 0); // wait for user input
    byte i = 0;
    while(Serial.available()){
    readName[i] = Serial.read(); // place character into array
    Serial.print(readName[i]); // debug
    i++;
    }
    readName[i] = 0x00; // add null character
    Serial.println();
    Serial.print(readName);

    strncpy(deviceName[j], readname, sizeof(deviceName[j]); // copies readName to deviceName[j] address and will not allow readName to write past the memory allocation of deviceName[j]
    [CODE]

    ReplyDelete
    Replies
    1. I knew you'd get it working. Isn't it fun when you do something cool like that?

      Delete
  12. Sure is dude! Now I just have to get some free time to stitch all of these ideas together... and get my sous-vide working so that I can control the temperature of 10 gallons of water.
    My boss found this set of machine code (on the web somewhere) and it will return how many bytes of SRAM are free, and it doesn't require a library. I personally have no idea what's going on I've been just copying and pasting :/
    [CODE]
    // this function returns the number of bytes currently free in SRAM
    int freemem(){
    extern int __heap_start, *__brkval;
    int v;
    return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
    } // end int freemem()...
    [CODE]

    I noticed that in your serial monitor print out of each packet that there isn't a very strict repetition of protocol bytes. Also the XBee start byte never shows up. I'm not sure why this is.

    ReplyDelete
    Replies
    1. That code is the same as the c code to get the memory left. It'll run a tiny bit faster, but that doesn't really matter if it isn't run very often. You need to save time in stuff that is executed a lot, not something that is executed once in a while. But, it's cool; I'd use it just ... because.

      The start byte '7e' is tucked in there by the the XBee itself. It doesn't really exist over the air. That's one thing you'll have to get used to, the stuff over the air isn't exactly what is output over the serial port. If you have relay hops, you'll start to notice the packets getting longer as they move along. They carry the addresses of each forwarding device. It's kind of cool watching it, but I got tired of it and just started looking at the stuff that I needed to fix a particular problem.

      Nowadays I use it to see what's actually going on. I thought about changing the code a bit to just strip out the things I need for presentation and leave the other stuff out. Haven't had enough ambition to do it though.

      Delete
  13. This comment has been removed by the author.

    ReplyDelete
  14. Ok so I got the Chibi up and running and it was really simple, pretty much plug and play I had to change it promiscuous 1 like you say. I did modify your code a little bit because I want to look at everything that's going on (this helps to see if other stuff is broadcasting on the same channel). I didn't change any of the channels on the XBee network. On the Chibi I set it to channel 15 b/c Dave did and b/c that's the only channel that worked for my network (default is 11). This is the sketch I run and it should work for anyone that just wants to take a peek at their own:
    [CODE]
    /*
    Taken from code @ desert-home.com, thanks Dave!

    Before using this sketch, also go into the chibiUsrCfg.h file and
    enable promiscuous mode. To do this, change the definition:

    #define CHIBI_PROMISCUOUS 0
    to
    #define CHIBI_PROMISCUOUS 1

    When not using promiscuous mode, disable this setting by changing
    it back to 0.
    */

    #include
    #include
    #include

    int longest = 0;
    byte Pbuf[768]; // max buffer size of Chibi so shouldn't need to be bigger
    char *deviceName[] = {"Coordinator", "Router1", "Router2", "Router3"};
    unsigned int deviceAddr[] = { 0x0000, 0x3DAA, 0x7557, 0x9646};

    // this function returns the number of bytes currently free in SRAM
    int freeMem(){
    extern int __heap_start, *__brkval;
    int v;
    return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
    } // end int freemem()...


    /**************************************************************************/
    // Initialize
    /**************************************************************************/
    void setup()
    {
    // Init the chibi stack
    Serial.begin(57600);
    chibiInit();
    chibiSetChannel(15); // set channel to 2425 MHz
    Serial.print(F("\n\r***************I'm alive************** \n"));
    Serial.print(F("Init Program Mem = "));
    Serial.print(freeMem());
    Serial.println();
    }

    /**************************************************************************/
    // Loop
    /**************************************************************************/
    void loop(){
    // Check if any data was received from the radio. If so, then handle it.
    if (chibiDataRcvd() == true){
    int len;
    byte flag;
    uint16_t senderAddr;
    // send the raw data out the serial port in binary format
    len = chibiGetData(Pbuf);
    if (len > longest)
    longest = len;
    senderAddr = chibiGetSrcAddr(); // get address
    for(int i = 0; i < 4; i++){
    if(senderAddr == deviceAddr[i]){ // find device name
    Serial.print(F("From: "));
    Serial.print(deviceName[i]);
    Serial.print(F(" Address = "));
    Serial.print(deviceAddr[i], HEX);
    }
    }
    Serial.print(F(" Len: "));
    Serial.print(len);
    Serial.print(F(" Longest: "));
    Serial.print(longest);
    Serial.print(F(" Free Mem = "));
    Serial.println(freeMem());
    for (int i= len; i >= 0; i--){
    if (Pbuf[i]<0x10) // adds leading zero if necessary
    Serial.print("0");
    Serial.print(Pbuf[i], HEX);
    Serial.print(" ");
    }
    Serial.println();
    }
    }
    watching what goes on when data is sent and just the idle chatter really helps me understand whats going on. It almost makes me want to write a chibiXBeeArduino library so it is even easier to understand what all of the chatter is without having to freeze-frame and look up what all of the bytes code for, but I have way too many projects on my desk, in my basement, out on Lake Michigan, you get the idea. Enjoy all!
    Cheers

    ReplyDelete
    Replies
    1. Good job. If you want to preserve it, get a program off the web call putty. It's a really sophisticated terminal program that can grab the stuff as it comes in and save it to a log file. You'll stumble around a bit getting used to it because it has about a million option and configurations.

      Trust me, it's worth the effort. I use it a lot for this kind of thing since the stuff flies by a whole lot faster than I can read.

      Delete