BWSN Uploading to Xively

Here is the video for Chapter 8 “More to Love”.

I was hoping to get the processing sketch to upload a week or two of sensor data until I made a post about it, but I kept running into issues. (Laugh)

You can see my Xively device page here. This show’s my XBee temperature sensor network at room, fridge and freezer temperatures.

So the original source code for this project can be found on the author’s website here. Unfortunately this code is not up to date. The book and the downloaded code upload to Pachube. BUT Pachube changed its name twice since this book was writtien, first to Cosm then to Xively.

In light of these changes, I’ve fixed the processing sketch to upload to Xively using the EEML library. So step by step:
1) Login to Xively and create an account. Once you have a free developer account/login, create a device. The device page will give you a FeedID and an API Key which will need to be placed in the Processing sketch.
2) Download the code from the author’s website here.
3) Download the EEML(Extended Environments Markup Language) jar library from here and place it in the “code” directory of the Processing sketch.
4) Replace the Processing sketch provided by the author’s website and use this sketch instead. Replace the FeedID and apiKey with the ones provided by your Xively device webpage:

/*
 * Draws a set of thermometers for incoming XBee Sensor data
 * by Rob Faludi http://faludi.com
 *
 * Oct 2013 - Updated to use EEML, upload to Xively and display both Celsius and
 *   Fahrenheit display numbers.
 */

// Using EEML for Xively connection http://xively.com/
// EEML Processing.org library available at http://www.eeml.org/library/
import eeml.*;

// used for communication via xbee api
import processing.serial.*; 

// xbee api libraries available at http://code.google.com/p/xbee-api/
// Download the zip file, extract it, and copy the xbee-api jar file 
// and the log4j.jar file (located in the lib folder) inside a "code" 
// folder under this Processing sketch’s folder (save this sketch, then 
// click the Sketch menu and choose Show Sketch Folder).
import com.rapplogic.xbee.api.ApiId;
import com.rapplogic.xbee.api.PacketListener;
import com.rapplogic.xbee.api.XBee;
import com.rapplogic.xbee.api.XBeeResponse;
import com.rapplogic.xbee.api.zigbee.ZNetRxIoSampleResponse;

String version = "1.10";

//  *** Replace with the Serial Port (Com Port) Number for your local Xbee *** 
int SerialPortNumber = 1;


// *** REPLACE WITH YOUR OWN PACHUBE API KEY AND FEED ID ***
String XivelyBaseURL="https://api.xively.com/v2/feeds/";
String apiKey="ADD_THE_XIVELY_APP_KEY_FOR_YOUR_DEVICE_HERE";
String feedID="ADD_THE_XIVELY_FEEDID_FOR_YOUR_DEVICE_HERE" + ".xml";

DataOut dOut;  // Data object used to upload to Xively

// create and initialize a new xbee object
XBee xbee = new XBee();

int error=0;

// Defines the thermometer ID starting at zero and incrementing.
// Used to differentiate streams on Xively
int thermID = 0;  

// used to record time of last data post
float lastUpdate;

// make an array list of thermometer objects for display
ArrayList thermometers = new ArrayList();
// create a font for display
PFont font;

void setup() {
  size(800, 600); // screen size
  smooth(); // anti-aliasing for graphic display


  // You’ll need to generate a font before you can run this sketch.
  // Click the Tools menu and choose Create Font. Click Sans Serif,
  // choose a size of 10, and click OK.
  font =  loadFont("SansSerif-10.vlw");
  textFont(font); // use the font for text

  // The log4j.properties file is required by the xbee api library, and 
  // needs to be in your data folder. You can find this file in the xbee
  // api library you downloaded earlier
  PropertyConfigurator.configure(dataPath("")+"log4j.properties"); 
  // Print a list in case the selected one doesn't work out
  println("Available serial ports:");
  println(Serial.list());
  try {
    // opens your serial port defined above, at 9600 baud
    xbee.open(Serial.list()[SerialPortNumber], 9600);
  }
  catch (XBeeException e) {
    println("** Error opening XBee port: " + e + " **");
    println("Is your XBee plugged in to your computer?");
    println(
      "Did you set your COM port in the code near line 27?");
    error=1;
  }
  
  // Create the XivelyURL and setup
  String FeedURL = XivelyBaseURL + feedID;
  dOut = new DataOut(this, FeedURL, apiKey); 
}


// draw loop executes continuously
void draw() {
  background(224); // draw a light gray background
  // report any serial port problems in the main window
  if (error == 1) {
    fill(0);
    text("** Error opening XBee port: **\n"+
      "Is your XBee plugged in to your computer?\n" +
      "Did you set your COM port in the code near line 20?", width/3, height/2);
  }
  SensorData data = new SensorData(); // create a data object
  data = getData(); // put data into the data object
  //data = getSimulatedData(); // uncomment this to use random data for testing

  // check that actual data came in:
  if (data.value >=0 && data.address != null) { 

    // check to see if a thermometer object already exists for this sensor
    int i;
    boolean foundIt = false;
    for (i=0; i <thermometers.size(); i++) {
      if ( ((Thermometer) thermometers.get(i)).address.equals(data.address) ) {
        foundIt = true;
        break;
      }
    }

    // *** ENABLE THIS CODE FOR LM335 temperature sensor ****
    // process the data value into a Celsius temperature reading for
    // LM335 with a 1/3 voltage divider
    //   (value as a ratio of 1023 times max ADC voltage times 
    //    3 (voltage divider value) divided by 10mV per degree
    //    minus zero Celsius in Kevin)
    // float temperatureCelsius = (data.value/1023.0*1.2*3.0*100)-273.15;

    //    // *** ENABLE THIS CODE FOR TMP36 temperature sensor ****
    //    // process the data value into a Celsius temperature reading for
    //    // TMP36 with no voltage divider
    //    //   (value as a ratio of 1023 times max ADC voltage times 
    //    //    minus 500 mV reading at zero Celsius
    //    //    times 100 to scale for 10mv per degree C)
    float temperatureCelsius = ((data.value/1023.0*1.25 - .5) *100); 
    
    println(" temp: " + round(temperatureCelsius) + "˚C");

    // update the thermometer if it exists, otherwise create a new one
    if (foundIt) {
      ((Thermometer) thermometers.get(i)).temp = temperatureCelsius;
    }
    else if (thermometers.size() < 10) {
      thermometers.add(new Thermometer(data.address,35,450,
      (thermometers.size()) * 75 + 40, 20, data.numericAddr, thermID++));
      ((Thermometer) thermometers.get(i)).temp = temperatureCelsius;
      
      int thermometerFeedID = ((Thermometer) thermometers.get(i)).thermometerID;
      println("Init thermometerFeedID: " + thermometerFeedID);
      
      // Init Xively here for each Temp Bar
      dOut.addData(thermometerFeedID,
                       "TempSensor_" + thermometerFeedID ,
                      (float)40.0, (float)-10.0);
      dOut.setUnits(thermometerFeedID, "Celsius","C","basicSI");
    }

    // draw the thermometers on the screen
    for (int j =0; j<thermometers.size(); j++) {
      ((Thermometer) thermometers.get(j)).render();
    }
    // post data to Xively every minute
    if ((millis() - lastUpdate) > 60000) {
      for (int j =0; j<thermometers.size(); j++) {
        ((Thermometer) thermometers.get(j)).dataPost();
      }
      lastUpdate = millis();
    }
  }
} // end of draw loop


// defines the data object
class SensorData {
  int value;
  String address;
  long numericAddr;
}


// defines the thermometer objects
class Thermometer {
  int sizeX, sizeY, posX, posY;
  float maxTemp = 40; // max of scale in degrees Celsius
  float minTemp = -10; // min of scale in degress Celsius
  float temp; // stores the temperature locally
  String address; // stores the address locally
  long numAddr; // stores the numeric version of the address
  int thermometerID;  // Feed ID of this object
  float temp_f;  // temp in F


  Thermometer(String _address, int _sizeX, int _sizeY, 
  int _posX, int _posY, long _numAddr, int _ID) { // initialize thermometer object
    address = _address;
    sizeX = _sizeX;
    sizeY = _sizeY;
    posX = _posX;
    posY = _posY;
    numAddr = _numAddr;
    thermometerID = _ID;
  }

  void dataPost() {
    // add and tag a datastream
    int thermometerFeedID = thermometerID;
    println("thermometerFeedID: " + thermometerFeedID);
    
    
    //Update Xively feed objects
    dOut.update(thermometerFeedID, (float) temp);
    println("posting to Xively...");
    int response = dOut.updatePachube(); // update the datastream
    println(response);  // Should be 200 OK success

  }

  void render() { // draw thermometer on screen
    noStroke(); // remove shape edges
    ellipseMode(CENTER); // center bulb
    float bulbSize = sizeX + (sizeX * 0.5); // determine bulb size
    int stemSize = 30; // stem augments fixed red bulb 
    // to help separate it from moving mercury
    // limit display to range
    float displayTemp = round( temp);
    if (temp > maxTemp) {
      displayTemp = maxTemp + 1;
    }
    if ((int)temp < minTemp) {
      displayTemp = minTemp;
    }
    // size for variable red area:
    float mercury = ( 1 - ( (displayTemp-minTemp) / (maxTemp-minTemp) ));
    // draw edges of objects in black
    fill(0); 
    rect(posX-3,posY-3,sizeX+5,sizeY+5); 
    ellipse(posX+sizeX/2,posY+sizeY+stemSize, bulbSize+4,bulbSize+4);
    rect(posX-3, posY+sizeY, sizeX+5,stemSize+5);
    // draw grey mercury background
    fill(64); 
    rect(posX,posY,sizeX,sizeY);
    // draw red areas
    fill(255,16,16);

    // draw mercury area:
    rect(posX,posY+(sizeY * mercury), 
    sizeX, sizeY-(sizeY * mercury));

    // draw stem area:
    rect(posX, posY+sizeY, sizeX,stemSize); 

    // draw red bulb:
    ellipse(posX+sizeX/2,posY+sizeY + stemSize, bulbSize,bulbSize); 

    // show text
    textAlign(LEFT);
    fill(0);
    textSize(10);

    // show sensor address:
    text(address, posX-10, posY + sizeY + bulbSize + stemSize + 4, 65, 40);

    // show maximum temperature: 
    temp_f = maxTemp * 9/5 + 32;
    text(temp_f + "˚F", posX+sizeX + 5, posY); 
    text(" " + maxTemp + "˚C", posX+sizeX + 5, posY + 10); 

    // show minimum temperature:
    temp_f = minTemp * 9/5 + 32;
    text(" " +temp_f + "˚F", posX+sizeX + 5, posY + sizeY);
    text(minTemp + "˚C", posX+sizeX + 5, posY + sizeY + 10);
    
    // Conver the temp to F before display
    temp_f = temp * 9/5 + 32;

    // show temperature:
    text(round(temp_f) + "˚F", posX+2,posY+(sizeY * mercury+ 14));
    text(round(temp)   + "˚C", posX+2,posY+(sizeY * mercury+ 24));
  }
}

// used only if getSimulatedData is uncommented in draw loop
//
SensorData getSimulatedData() {
  SensorData data = new SensorData();
  int value = int(random(650,670));
  String address = "00:13:A2:00:12:34:AB:C" + str( round(random(0,2)) );
  data.value = value;
  data.address = address;
  data.numericAddr = unhex(data.address.replaceAll(":", ""));
  delay(200);
  return data;
}

// queries the XBee for incoming I/O data frames 
// and parses them into a data object
SensorData getData() {

  SensorData data = new SensorData();
  int value = -1;      // returns an impossible value if there's an error
  String address = ""; // returns a null value if there's an error

  try {
    // we wait here until a packet is received.
    XBeeResponse response = xbee.getResponse();
    // uncomment next line for additional debugging information
    //println("Received response " + response.toString()); 

    // check that this frame is a valid I/O sample, then parse it as such
    if (response.getApiId() == ApiId.ZNET_IO_SAMPLE_RESPONSE 
      && !response.isError()) {
      ZNetRxIoSampleResponse ioSample = 
        (ZNetRxIoSampleResponse)(XBeeResponse) response;

      // get the sender's 64-bit address
      int[] addressArray = ioSample.getRemoteAddress64().getAddress();
      // parse the address int array into a formatted string
      String[] hexAddress = new String[addressArray.length];
      for (int i=0; i<addressArray.length;i++) {
        // format each address byte with leading zeros:
        hexAddress[i] = String.format("%02x", addressArray[i]);
      }
      // join the array together for a numeric address:
      long numericAddress = unhex(join(hexAddress,""));
      data.numericAddr = numericAddress;
      print("numeric address: " + numericAddress);
      // join the array together with colons for readability:
      String senderAddress = join(hexAddress, ":"); 
      print("  sender address: " + senderAddress);
      data.address = senderAddress;
      // get the value of the first input pin
      value = ioSample.getAnalog0();
      print("  analog value: " + value ); 
      data.value = value;
    }
    else if (!response.isError()) {
      println("Got error in data frame");
    }
    else {
      println("Got non-i/o data frame");
    }
  }
  catch (XBeeException e) {
    println("Error receiving response: " + e);
  }
  return data; // sends the data back to the calling function
}

This Processing sketch will automatically create a thermometer when a new XBee address is found. It will then automatically create a device channel and begin uploading the last data sample it got from the Xbee device once a minute.

Some advice:
a) The XBee nodes are based on the Chapter 5 thermometer TMP36 remote sensor nodes.
b) The Processing sketch can be tested without hardware to verify that it uploads to Xively successfully. To do this comment in the “data = getSimulatedData();” line in the sketch. It will create three fake thermometers and fill them with random test data.
c) Be careful when restarting the Processing sketch. It will upload to channel one, two, three etc based on which XBee address appears first. So turn off the XBee’s before restarting the Processing sketch and power them on in the order you want them to appear on the website. First XBee sample found will thereafter be uploaded to channel one, second on channel two, etc. If your not careful, this can mess up your channel data.

Some issues:
i) I don’t know if I trust the temperature sample data coming out of the XBee’s. Looking at my Xively webpage and comparing the data provided to my other test devices, I can see that the room temperature given is warmer than it actually is in the room. Reads high 70’s when my weather station temperature reads low 70’s. The fridge temperature should be higher than zero because I don’t find any frozen water or juice when I pull them out of the fridge. And the freezer seems about 10 or so degree’s colder than I expect it to be. I don’t know what’s the issue with these readings. I’ll have to investigate that.
ii) The Processing sketch has died on me in two different ways.
1) The sketch kept running and uploading the last data sample to Xively but the console read “Unable to read start byte”. This tells me I need to update the text processing in the sketch to be more robust.
2) The sketch will stop processing sample data. I believe this is an issue with my USB drivers which I will have to look into.
iii) Be careful when restarting the Processing sketch. Sample data will be uploaded to Xively based on what XBee address appears first. This is something I will have to address in my future XBee software projects.

Last but not least, I really don’t need to use a Processing sketch to capture this sample data. If I have sample data appearing on a website, I won’t need to look at the thermometer graphs. I should be using the Connect port to push this data to Xively.

And thats a wrap! Chapter 8 “More to Love” was the last chapter in the “Building Wireless Sensor Networks” book. I have to say over the last two months where I worked through the projects that it has been a learning experience. Hope you enjoyed these posts.

Advertisements
This entry was posted in BWSN. Bookmark the permalink.

One Response to BWSN Uploading to Xively

  1. Pingback: XBee and Sensor Printed Circuit Board | Misadventures in software

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s