Data graphing program that saves to a file

This Processing sketch takes data from the serial port, graphs it, and writes it to a text file with a time stamp if there’s a significant change in any of the incoming values. It expects five values between 0-255 in ASCII, separated by tabs, and ended by a carriage return and newline.

The text file it generates is tab-delimited, and can be read easily in a spreadsheet.

A Wiring/Arduino program to send data to this sketch follows at the end.

/*
Grapher Pro!
 by Tom Igoe
 
 This program takes raw bytes from the serial port at 9600 baud and graphs them.
 It expects five ASCII-encoded decimal values from 0-255, tab-delimited, ended by a 
 newline and carriage return.
 
 To change which of the five channels is being shown in the graph, type 0 through 5.
 
 When any of the values changes by more than a set threshold, the program
 writes the data to a text file called dataFile.txt, which can be found in the 
 sketch's directory.
 
 Created 20 April 2005
 Updated 15 October 2007
 */

import processing.serial.*;

Serial myPort;                // The serial port

int arrayLength = 5;                        // number of values to expect
int[] sensorValues = new int[arrayLength];  // array to hold the incoming values
int hPosition = 0;                          // horizontal position on the graph
int displayChannel = 0;                     // which of the five readings is being displayed
String dataSet;                             // string holding the incoming data    
int threshold = 50;                         // threshold for whether or not to write 
                                            // data to a file

void setup () {
  size(400, 300);        // window size

  // List all the available serial ports
  println(Serial.list());
  // I know that the third port in the serial list on my mac
  // is always my  Keyspan adaptor, so I open Serial.list()[2].
  // Open whatever port is the one you're using.
  myPort = new Serial(this, Serial.list()[0], 9600);

  // clear the serial buffer:
  myPort.clear();
  // don't generate a serialEvent() until you get a carriage return
  myPort.bufferUntil('\r');  // ASCII 13

  // create a font with the second font available to the system:
  PFont myFont = createFont(PFont.list()[1], 24 );
  textFont(myFont);
  // make the graphics smooth:
  smooth();
  // set inital background:
  background(0);
}

void draw () {
  // if the value for the given channel is valid, graph it:
  if (sensorValues[displayChannel]  > 0 ) {
    // print the name of the channel being graphed:
    text("Channel: " + displayChannel, 30, 30);
    // draw the graph:
    graph(sensorValues[displayChannel]);
  }
}

void serialEvent(Serial myPort) {
  // read incoming data until you get a newline:
  String serialString = myPort.readStringUntil('\n');
  // if the read data is a real string, parse it:
  if (serialString != null) {
    // save the string in case you need to write it to a file:
    dataSet = serialString;
    // split it into substrings on the tab character:
    String[] numbers = split(serialString, "\t");
    // convert each subastring into an int
    for (int i = 0; i < numbers.length; i++) {
      // make sure you're only reading as many numbers as 
      // you can fit in the array:
      if (numbers.length <= arrayLength) {
        // trim off any whitespace from the substring:
        numbers[i] = trim(numbers[i]);
        // find the difference between the current value and the incoming value:
        int diff = abs(sensorValues[i] - int(numbers[i]));
        // if the difference exceeds the threshold, write the data to a file:
        if (diff > threshold) {
          writeToFile(); 
        }
        // save the new values in the array for use in graphing:
        sensorValues[i] =  int(numbers[i]);
      }
    }
  }
}


void graph (int numberToGraph) { 
  // draw the line:
  stroke(0,255,0);
  line(hPosition, height, hPosition, height - numberToGraph);
  // at the edge of the screen, go back to the beginning:
  if (hPosition >= width) {
    hPosition = 0;
    // wipe the screen clean:
    background(0); 
  } 
  else {
    // advance the horizontal position on the graph:
    hPosition++;
  }
}

void keyPressed() {
  // if the key pressed is "0" through "4"
  if ((48 <= key) && (key <= 52)) {
    // set the display channel accordingly
    displayChannel = key - 48;
    // wipe the screen:
    background(0);
  }
}



void writeToFile() {
  // string for the new data you'll write to the file:
  String[] newData = new String[1];
  // add a time stamp:
  newData[0] = timeStamp();
  // add a tab:
  newData[0] += "\t";
  // add the latest data from the serial port:
  newData[0] += trim(dataSet);

  // get the existing data from the file:
  String[] dataSoFar = loadStrings("dataFile.txt");
  // if there's something there, dump it into an array
  // and add the new data to it:
  if (dataSoFar != null) {
    // array needs to accommodate the old data and the new:
    String[] dataToWrite = new String[dataSoFar.length + newData.length];
    // dump the existing data from the file back in:
    for (int s = 0; s < dataSoFar.length; s++) {
      dataToWrite[s] = dataSoFar[s]; 
    }
    // append the new data:
    for (int s = dataSoFar.length; s < dataToWrite.length; s++) {
      dataToWrite[s] = newData[s-dataSoFar.length]; 
    }
    // dump the result back to the file:
    saveStrings("dataFile.txt", dataToWrite);
  } 
  // if there's no existing data:
  else {
    // add a header to the file:
    newData[0] = "Time:\tSensor 1\tSensor 2\tSensor 3\tSensor 4\tSensor 5";
    saveStrings("dataFile.txt", newData);
  }
}

// make up a timeStamp string for writing data to the file:
String timeStamp() {
  String now = hour()+ ":" +  minute()+ ":" + second()+ " " +
    month() + "/"  + day() + "/" + year();
  return now;
}

Here's a Wiring/Arduino program to send some serial data that the grapher can read:

/*
  Read in five analog sensor values, send them out
 as tab-delimited ASCCI-encoded decimal numbers,
 ended by a carriage return and newline
 
 created 15 Oct. 2007
 
 */
void setup() {
  // iniaialize the serial port:
  Serial.begin(9600);
}

void loop() {
  // count from 0 to 4 (five channels):
  for (int channel=0; channel <=4; channel++) {
    // read an analog input, divide the result by 4
    // to limit it to 0-255:
    int sensorValue = analogRead(channel)/4;
    // print it:
    Serial.print(sensorValue, DEC);
    // if it's not the last channel to be read,
    // print a tab character:
    if (channel < 4) {
      Serial.print("\t");
    }
    // delay to let the analog-to-digital converter settle:
    delay(8);
  }
  // once you've printed all the values, 
  // print a newline and carriage return:
  Serial.println();
}