{"id":149,"date":"2008-02-15T16:10:44","date_gmt":"2008-02-15T21:10:44","guid":{"rendered":"http:\/\/www.tigoe.net\/pcomp\/code\/category\/code\/processing\/149"},"modified":"2008-03-19T15:52:19","modified_gmt":"2008-03-19T20:52:19","slug":"xbee-library-graphing-application-2","status":"publish","type":"post","link":"https:\/\/www.tigoe.com\/pcomp\/code\/Processing\/149\/","title":{"rendered":"XBee Library graphing and logging application"},"content":{"rendered":"<p>Here&#8217;s a program that uses Rob Faludi and Dan Shiffman&#8217;s <a href=\"http:\/\/www.faludi.com\/code\/xbee-api-library-for-processing\/\">XBee library for processing<\/a> to read three analog sensors from multiple remote <a href=\"http:\/\/www.digi.com\/products\/wireless\/point-multipoint\/xbee-series1-module.jsp\">XBee<\/a> radios and graph them. It also saves the data to a comma-delimited file. It also makes sounds when the value exceeds a given threshold. For this application, you need two or more XBee series 1 radios. One is attached to the serial port of the computer, and the other is remote, broadcasting three analog values.<\/p>\n<p>This also uses the <a href=\"http:\/\/www.sojamo.de\/libraries\/controlP5\/\">ControlP5 library<\/a> by Andreas Schlegel and the <a href=\"http:\/\/www.tree-axis.com\/Ess\/\">Ess library<\/a> by Krister Olsson.<\/p>\n<p><!--more--><\/p>\n<p>The settings for the radios are as follows:<\/p>\n<p>Base station radio:<\/p>\n<ul>\n<li>Personal Area Network (PAN) ID: <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">AAAA<\/span><\/span><\/span><\/li>\n<li>Source address: <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">ATMY<\/span><\/span><\/span> 0<\/li>\n<li>Baud rate 115200 bits per second: <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">ATBD<\/span><\/span><\/span> 7<\/li>\n<\/ul>\n<p>Remote radios:<\/p>\n<ul>\n<li>Personal Area Network (PAN) ID: <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">AAAA<\/span><\/span><\/span><\/li>\n<li>Source address: <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">ATMY<\/span><\/span><\/span> 1 (MY2, <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">MY3,<\/span><\/span><\/span> etc. for other remote radios)<\/li>\n<li>Destination address: <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">ATDL<\/span><\/span><\/span> 0<\/li>\n<li>Analog inputs activated:<\/li>\n<li style=\"list-style-type: none; list-style-position: initial; list-style-image: initial;\">\n<ul>\n<li><span class=\"caps\"><span class=\"caps\"><span class=\"caps\">ATD0<\/span><\/span><\/span> 2<\/li>\n<li><span class=\"caps\"><span class=\"caps\"><span class=\"caps\">ATD1<\/span><\/span><\/span> 2<\/li>\n<li><span class=\"caps\"><span class=\"caps\"><span class=\"caps\">ATD2<\/span><\/span><\/span> 2<\/li>\n<\/ul>\n<\/li>\n<li>Sample rate 80 milliseconds: <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">ATIR<\/span><\/span><\/span> 50 ( modify this by 5ms or so per radio for best results)<\/li>\n<li>1 sample per transmission: <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">ATIT<\/span><\/span><\/span> 1<\/li>\n<li>Baud rate 115200 bits per second: <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">ATBD<\/span><\/span><\/span> 7<\/li>\n<li style=\"list-style: none\"><\/li>\n<\/ul>\n<p>The code is separated into several pages for portability: The main methods, the Radio class, the graphing methods, the file IO methods, the <span class=\"caps\"><span class=\"caps\"><span class=\"caps\">GUI<\/span><\/span><\/span> methods, the XBee methods, and the sound methods.<\/p>\n<p>The main methods:<\/p>\n<pre>\n\/*\n  XBee sensor data graphing and logging sketch\n \n This sketch takes data in from multiple XBee radios and graphs the analog \n input values.  It's for XBee series 1 radios. It also plays a sound when\n the value exceeds a given threshold\n \n Xbee methods based on code from  Rob Faludi and Daniel Shiffman\n http:\/\/www.faludi.com\n http:\/\/www.shiffman.net \n \n ControlP5 methods based on code from Andreas Schlegel\n http:\/\/www.sojamo.de\/libraries\/controlP5\/\n \n Ess methods based on code from Krister Olsson\n http:\/\/www.tree-axis.com\/Ess\/\n \n created 2 Feb 2008\n by Tom Igoe  \n *\/\n\n\/\/import the xbee, serial, and GUI libraries:\nimport xbee.*;\nimport processing.serial.*;\nimport controlP5.*;\n\n\/\/ Your Serial Port\nSerial port;\n\/\/ Your XBee Reader object\nXBeeReader xbee;\n\nControlP5 gui;\n\/\/ set up a font for displaying text:\nPFont myFont;\nint fontSize = 12;\n\n\/\/ ArrayList to hold instances of Radio objects:\nArrayList radios = new ArrayList();  \n\n\/\/ data file name for saving to a file:\nString dataFileName = \"Untitled\";\n\n\/\/ set up Xbee parameters:\nint numSensors = 3;                 \/\/ number of sensors you actually plan to graph\n\nint xpos = 0;                       \/\/ graph x position\nint oldYPos = 0;                    \/\/ graph y position\nint yPos = 0;                       \/\/ previous y position\n\nint hitThreshold = 250;             \/\/ peak value to check hits against\nint hits = 0;                       \/\/ hit count\nint lastHitValue = 0;               \/\/ value of last hit\n\nint graphMin;                       \/\/ minimum value for graph (top of graph)\nint graphMax;                       \/\/ maximium value for graph (bottom of graph)\nboolean newData = false;            \/\/ new data flag   \n\n\/\/ which radio radio you're graphing, chosen by keystroke:\nRadio chosenRadio = new Radio(0); \n\nvoid setup() {\n  size(640, 480);                \/\/ window size\n  frameRate(60);                 \/\/ frame rate\n  smooth();                      \/\/ clean the jagged edges\n\n  \/\/ create a font with the third font available to the system:\n  myFont = createFont(PFont.list()[2], fontSize);\n  textFont(myFont);\n\n  \/\/ you might need a list of the serial ports to find yours:\n  println(\"Available serial ports:\");\n  println(Serial.list());\n  \/\/ open the first serial port.  Change this to match \n  \/\/ your serial port number in the list:\n  port = new Serial(this, Serial.list()[0], 115200);\n\n  \/\/ initialize the xbee library:\n  xbee = new XBeeReader(this,port);\n  xbee.startXBee();\n\n  \/\/ initialize sound:\n  startSound();\n  \/\/ black screen:\n\n  background(0);\n  \/\/ set graph minima and maxima:\n  graphMin = 160;\n  graphMax = height - 10;\n\n  \/\/ initialize the GUI controls:\n  guiSetup();\n}\n\nvoid draw() {\n  \/\/ if there's new data, graph it and write the status text:\n  if (newData) {\n    drawGraph(chosenRadio);\n    writeStatusText(chosenRadio);\n    newData = false;  \n  }\n}\n\nvoid keyReleased() {\n  \/\/ convert numerical ASCII values to actual values:\n  int thisKey = keyCode - 48;\n\n  \/\/ if the number typed is less than the number of radios\n  \/\/ you've heard from, use it to choose which one to listen to:\n  if (thisKey &amp;amp;amp;lt;= radios.size() &amp;amp;amp;&amp;amp;amp; thisKey &amp;amp;amp;gt; 0) {\n    chosenRadio = (Radio)radios.get(thisKey-1);\n  }\n}\n\nString getTimeStamp() {\n  \/\/ make a timestamp string:\n  String thisTime = hour() + \":\" + minute() + \":\" + second();\n  return thisTime;\n}\n\nvoid stop() {\n  \/\/ close any open file:\n  if (fileIsOpen) {\n    closeFile();\n  } \n  stopSound();\n} \n<\/pre>\n<hr \/>\n<p><\/p>\n<p>Radio class:<\/p>\n<pre>\n \/*\n  The Radio object keeps track of all the data from\n  a given Radio's radio: the sensor values, the previous values,\n  the address, the signal strength.\n\n*\/\n\npublic class Radio {\n  int[] sensorValues;\n  int[] previousValues;\n  int rssi;\n  int address;\n  boolean initialized = false;\n\n  public Radio(int thisAddress, int thisRssi, int[] theseValues) {\n    address = thisAddress;\n    rssi = thisRssi;\n    sensorValues = theseValues;\n    println(\"new Radio\");\n    initialized = true;\n  }\n\n  public Radio(int thisAddress) {\n    address = thisAddress;\n    sensorValues = new int[6];\n    previousValues  = new int[6];\n    rssi = 0;\n    initialized = true;\n  }\n\n  public int getAddress() {\n    return this.address;\n  }\n\n  public void setAddress(int thisAddress) {\n    this.address = thisAddress;\n  }\n\n  public int getRssi() {\n    return rssi;\n  }\n\n  public void setRssi(int thisRssi) {\n    rssi = thisRssi;\n  }\n\n  public int[] getSensors() {\n    return sensorValues;\n  }\n\n  public void setSensors(int[] theseSensors) {\n    arraycopy(sensorValues, previousValues);\n    arraycopy(theseSensors,sensorValues);\n  }\n\n  public int[] getPrevSensors() {\n    return previousValues;\n  }\n\n  public boolean isRadio() {  \n    return initialized;\n  }\n\n  public String getProperties() {\n    String dataToSend = getTimeStamp();\n    dataToSend += \",\";\n    dataToSend += address;\n    dataToSend += \",\";\n    dataToSend += rssi;\n    dataToSend += \",\";  \n    for (int i = 0; i &amp;amp;amp;lt; sensorValues.length; i++) {\n\n      dataToSend += sensorValues[i];\n      if (i &amp;amp;amp;lt; sensorValues.length-1) {\n        dataToSend += \",\";\n      }\n    }\n    dataToSend += \"&amp;amp;amp;#92;r&amp;amp;amp;#92;n\";\n    return dataToSend;\n  }\n}\n<\/pre>\n<hr \/>\n<p><\/p>\n<p>Graphing methods:<\/p>\n<pre>\nvoid drawGraph(Radio thisRadio) {\n  \/\/ if there are any radios to get data from:\n  if (radios.size() &amp;amp;amp;gt; 0) {\n    \/\/ if the given radio object has been initialized:\n    if (thisRadio.isRadio()) {\n      \/\/ iterate over the number of sensors to graph:\n      for (int thisSensor = 0; thisSensor &amp;amp;amp;lt; numSensors; thisSensor++) {\n        \/\/ if you have new data and it's valid (&amp;amp;amp;gt;0), graph it:\n        if (thisRadio.getSensors()[thisSensor] &amp;amp;amp;gt; -1) {\n\n          \/\/ map the sensor values to the graph rect:\n          yPos = int(map(thisRadio.getSensors()[thisSensor], 0, 1023, graphMin, graphMax));\n          oldYPos = int(map(thisRadio.getPrevSensors()[thisSensor], 0, 1023, graphMin, graphMax));\n\n          \/\/ if we get a big change, increment the hit counter:\n          if (abs(yPos - oldYPos) &amp;amp;amp;gt;= hitThreshold) {\n            \/\/ make a sound: \n            makeSound();\n            hits++;\n            \/\/ not the magnitude of the hit:\n            lastHitValue = abs(yPos - oldYPos);\n          }\n        }\n        \/\/ draw the graph axis: \n        stroke(255);\n        int xAxis = int(map(height\/2, 0, height, graphMin, graphMax));\n        line(0, xAxis, width, xAxis);\n\n        \/\/ use a different the graphing color for each axis:\n        switch (thisSensor) {\n        case 0:\n          stroke(255,0,0);\n          break;\n        case 1:\n          stroke(0,255,0);\n          break;\n        case 2: \n          stroke(0,0,255);\n          break;\n        }\n        \/\/ draw the graph line from last value to current:\n        line(xpos, oldYPos, xpos+1,yPos);\n      }\n      \/\/ if you're at the right of the screen, \n      \/\/ clear and go back to the left:\n      if (xpos &amp;amp;amp;gt;= width) {\n        xpos = 0;\n        background(0);\n      } \n      else {\n        xpos++;\n      }\n    } \n  } \n}\n\nvoid writeStatusText(Radio thisRadio) {\n  \/\/ write the text at the top of the screen:\n  noStroke();\n  fill(0);\n  rect(0, 0, width, graphMin);\n  fill(255);\n  text(\"From: \" + hex(thisRadio.getAddress()), 10, 20);\n  text (\"RSSI: \" + thisRadio.getRssi() + \" dBm\", 10, 40);  \n  text(\"X: \" + thisRadio.getSensors()[0] + \"  Y: \" + thisRadio.getSensors()[1] + \"  Z: \" + thisRadio.getSensors()[2], 10, 60);\n  text(\"Last hit value: \" + lastHitValue,  10, 80); \n  text(\"Filename: \" + dataFileName, 10, 100);\n  \/\/ if there's a file open to save data, let the user know:\n  if (fileIsOpen) {\n    fill(255,0,0);\n    text(\"LOGGING\", 200,100);\n  }\n} \n<\/pre>\n<hr \/>\n<p><\/p>\n<p>File IO methods:<\/p>\n<pre>\n \nPrintWriter writer;            \/\/ writer to file\nboolean fileIsOpen = false;    \/\/ whether or not file is open\n\nvoid openFile(String fileName) {\n  \/\/ create a file with the given filename\n  \/\/ in the \"data\" subdirectory of the sketch's directory:\n  writer = createWriter(\"data\/\" + fileName + \".csv\");\n  \/\/ put a timestamp at the beginning of the file:\n  writer.println(getTimeStamp()); \n  fileIsOpen = true;\n}\n\nvoid closeFile() {\n  \/\/ close the file:\n  writer.flush(); \n  writer.close();\n  fileIsOpen = false;\n}\n<\/pre>\n<hr \/>\n<p><\/p>\n<p><span class=\"caps\"><span class=\"caps\"><span class=\"caps\">GUI<\/span><\/span><\/span> methods<\/p>\n<pre>\n \nint guiX;          \/\/ left of the GUI space\nint guiY;          \/\/ top of the GUI space\n\nvoid guiSetup() {\n  \/\/ set the position of the GUI space:\n  guiX = width\/2;\n  guiY = 0;\n  \/\/ initialize the GUI:\n  gui = new ControlP5(this);\n  \/\/ add the GUI elements:\n  gui.addSlider(\"hit_thresh\",0,height\/2,hitThreshold, guiX+60,guiY+10,10,150).setId(1);\n  gui.addToggle(\"logData\",false, guiX+100,guiY+80,10,10).setId(2);\n  gui.addTextfield(\"filename\",guiX+100,guiY+120,150,20).setId(3);\n}\n\nvoid controlEvent(ControlEvent theEvent) {\n  \/\/ do something different depending on which control\n  \/\/ generated the event:\n  switch(theEvent.controller().id()) {\n    \/\/ if it was the slider, update the hit threshold:\n    case(1):\n    hitThreshold = int(theEvent.controller().value());\n    break;\n    \/\/ if it was the toggle, open or close the data file:\n    case(2):\n    int value = int(theEvent.controller().value());\n    boolean logging = boolean(value);\n    if (logging) {\n      openFile(dataFileName);\n    } \n    else {\n      closeFile();\n    }\n    break;  \n    \/\/ if it's the text box, update the file name\n    \/\/ with the textbox string:\n    case(3): \n    dataFileName = (theEvent.controller().stringValue());\n    break;\n  }\n}\n<\/pre>\n<hr \/>\n<p><\/p>\n<p>XBee methods:<\/p>\n<pre>\n \n\/*  \n This function works just like a \"serialEvent()\" and is \n called for you when data is available to be read from your XBee radio.\n *\/\n\npublic void xBeeEvent(XBeeReader xbee) {\n\n  \/\/ Grab a frame of data\n  XBeeDataFrame data = xbee.getXBeeReading();\n\n  \/\/ This version of the library only works with IOPackets\n  \/\/ For ZNet radios, you would say XBeeDataFrame.ZNET_IOPACKET\n  if (data.getApiID() == XBeeDataFrame.SERIES1_IOPACKET) {\n    \/\/ Get the transmitter address\n    Radio myRadio = checkAddress(data.getAddress16());\n\n    \/\/ Get the RSSI reading in dBM \n    myRadio.setRssi(data.getRSSI());\n\n    \/\/ Get the current state of each analog channel \n    \/\/ (-1 indicates channel is not configured);\n    myRadio.setSensors(data.getAnalog());\n\n    \/\/ if this is the chosenRadio, graph it:\n    if (myRadio == chosenRadio) {\n      newData = true;   \n    } \n    \/\/ if there are no radios in the ArrayList yet, make this the first:\n    else if (radios.size() == 1) {\n      chosenRadio = myRadio; \n    }\n    \/\/ if logging data, log it:\n    if (fileIsOpen) {\n      writer.println(myRadio.getProperties());\n    }\n  } \n  else {\n    \/\/ this is not an I\/O packet:\n    println(\"Not I\/O data: \" + data.getApiID());\n  }\n}\n\n\nRadio checkAddress(int thisAddress) {\n  boolean isRadio = false;         \/\/ whether the Radio object is initialized\n  Radio radioToReturn = null;      \/\/ which radio has the given address \n\n  \/\/ iterate over the playerList:\n  for (int r = 0; r &amp;amp;amp;lt; radios.size(); r++) {\n    \/\/ get the next object in the ArrayList and convert it\n    \/\/ to a Radio:\n    Radio thisRadio = (Radio)radios.get(r);\n\n    \/\/ if thisPlayer's address matches the one that generated \n    \/\/ the serverEvent, then this radio is already in the list:\n    if (thisRadio.getAddress() == thisAddress) {\n      \/\/ we already have this radio\n      isRadio = true;\n      radioToReturn = thisRadio;\n    }\n\n  }\n  \/\/ if the radio isn't already in the ArrayList, then \n  \/\/ make a new Radio and add it to the ArrayList:\n  if (!isRadio) {\n    Radio newRadio = new Radio(thisAddress);\n    radios.add(newRadio);\n    radioToReturn = newRadio;\n  }\n  return radioToReturn;\n} \n<\/pre>\n<p>Sound methods:<\/p>\n<pre>\n \/\/ import the library:\nimport krister.Ess.*;\n\n\/\/ set up an audio channel:\nAudioChannel myChannel;\n\n\/\/ flag for whether or not a sound is playing\nboolean soundPlaying = false;\n\nvoid startSound() {\n  \/\/ set up sound:\n  Ess.start(this);\n  Ess.masterVolume(1);\n  \/\/ load a file, give the AudioPlayer buffers that are 512 samples long\n  \/\/ load a sound into a new Channel\n  myChannel=new AudioChannel(\"punch.aif\");\n  \/\/ set the volume to max:\n  myChannel.volume(1);\n}\n\nvoid makeSound() {\n  \/\/ if there's an open sound channel and there's no sound playing:\n  if ((myChannel != null) &amp;amp;amp;&amp;amp;amp; (!soundPlaying)) {\n    myChannel.play();\n    soundPlaying = true;\n  } \n}\n\nvoid audioChannelDone(AudioChannel ch) {\n  \/\/ trip the soundPlaying flag when the sound stops:\n  soundPlaying = false;\n}\n\nvoid stopSound() {\n  \/\/ always close Minim audio classes when you are done with them\n  if (myChannel != null) {\n    Ess.stop();\n    super.stop();\n  }\n}\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Here&#8217;s a program that uses Rob Faludi and Dan Shiffman&#8217;s XBee library for processing to read three analog sensors from multiple remote XBee radios and graph them. It also saves the data to a comma-delimited file. It also makes sounds when the value exceeds a given threshold. For this application, you need two or more &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.tigoe.com\/pcomp\/code\/Processing\/149\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;XBee Library graphing and logging application&#8221;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,21],"tags":[],"class_list":["post-149","post","type-post","status-publish","format-standard","hentry","category-Processing","category-XBee"],"_links":{"self":[{"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/posts\/149","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/comments?post=149"}],"version-history":[{"count":0,"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/posts\/149\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/media?parent=149"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/categories?post=149"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/tags?post=149"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}