import processing.core.*; import processing.serial.*; import java.applet.*; import java.awt.*; import java.awt.image.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.text.*; import java.util.*; import java.util.zip.*; import javax.sound.midi.*; import javax.sound.midi.spi.*; import javax.sound.sampled.*; import javax.sound.sampled.spi.*; import java.util.regex.*; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.dom.*; import javax.xml.transform.sax.*; import javax.xml.transform.stream.*; import org.xml.sax.*; import org.xml.sax.ext.*; import org.xml.sax.helpers.*; public class BBC_Grapher extends PApplet {/*
BBCC_Grapher
 by Tim Hirzel, Feb 2008
 With gratitude, base on:
 Grapher Pro!
 by Tom Igoe
 (Created 20 April 2005
 Updated 15 October 2007)
 
 
 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.
 */




char DELIM = ',';

char INIT_MARKER = '!';      //Look for this character before sending init message
String INIT_MESSAGE = "gu";  //Send these characters to the arduino after init to start up messages

int windowWidth = 1000;
int windowHeight = 600;

int xBoundary = 60;
int yBoundary = 60;

int graphHeight, graphWidth;

int legendWidth = 200;
int legendHeight = 100;
int legendX = windowWidth - 220;
int legendY =  20;


int gridSpace = 50;
String title = "Bare Bones Coffee Controller Tuning";
String names = "Goal Curr P I D";
boolean[] graphThisName = {true,true, false, false, false};


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 = 1;                          // 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
boolean newData = false;
int [] lastSet  = new int[arrayLength];

int[][] colors = new int[arrayLength][3];


public void setupColors() {
  colors[0][0] = 102;  
  colors[0][1] =194; 
  colors[0][2] = 165;
  colors[1][0] = 252;  
  colors[1][1] = 141; 
  colors[1][2]= 98;
  colors[2][0] = 141;  
  colors[2][1] = 160; 
  colors[2][2]= 203;
  colors[3][0] = 231;  
  colors[3][1] = 138; 
  colors[3][2]= 195;
  colors[4][0] = 166;  
  colors[4][1] = 216; 
  colors[4][2]= 84;
  //colors[5][0] = 255;  
  //colors[5][1] = 217; 
  //colors[5][2]= 47;
}

public void setup () {
  lastSet[0] = -1;

  size(windowWidth, windowHeight);        // window size
  setupColors();

  smooth();
  println(PFont.list());
  PFont myFont = createFont("Courier", 14 );
  textFont(myFont);
  clearGraph();

  // 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('\n');  // ASCII 13
  // create a font with the second font available to the system:


  // make the graphics smooth:  
}

public void draw () {

  // if the value for the given channel is valid, graph it:
  if (newData) {
    // print the name of the channel being graphed:

    // draw the graph:
    graph(sensorValues);
    newData = false;
  }
}

public void clearGraph() {
  graphHeight = windowHeight - yBoundary*2;
  graphWidth =windowWidth-2*xBoundary;

  background(10);
  strokeWeight(1.5f);
  stroke(10);
  fill(30);
  // draw boundary
  rect(xBoundary,yBoundary,graphWidth, graphHeight);
textAlign(RIGHT);
text(title, windowWidth/2);

  stroke(10);
  //draw grid  
   fill(40);
  textAlign(RIGHT);
  for (int i = graphHeight; i > 0; i-= gridSpace) {
    line(xBoundary - 3, yBoundary + i, windowWidth - xBoundary - 1, yBoundary + i);
    text(str(graphHeight - i), xBoundary - 10, yBoundary + i);
  }



  textAlign(LEFT);
  for (int i = 0; i < graphWidth ; i+= gridSpace) {
    line(xBoundary + i, yBoundary+1, xBoundary + i, windowHeight - yBoundary + 3);
    text(str(i), xBoundary + i, windowHeight - yBoundary + 20);
  }
  

  // DRAW LEGEND
  fill(255,255,255,50);
     rect(legendX, legendY, legendWidth, legendHeight);

  // print the name of the channel being graphed:
  for (int i = 0; i < arrayLength; i++) {
    fill(colors[i][0], colors[i][1],colors[i][2]);
    text(split(names,' ')[i] , windowWidth-xBoundary+5, yBoundary + (i+1) * 20);

  }



}

public void graph (int [] numbersToGraph) {
  // draw the line:
  for (int i = 0; i < numbersToGraph.length; i++) {
    if (graphThisName[i]) {
    stroke(colors[i][0], colors[i][1],colors[i][2]);
    //   point(hPosition, height - numbersToGraph[i]);
    if (lastSet[0] != -1) {
      line(xBoundary + hPosition-1, 
      windowHeight - yBoundary - lastSet[i] - 1,
      xBoundary + hPosition,
      windowHeight - yBoundary - numbersToGraph[i] - 1);

    }      // at the edge of the screen, go back to the beginning:
    lastSet[i] = numbersToGraph[i];
    }
  }


  if (hPosition >= graphWidth) {
    hPosition = 1;
    // wipe the screen clean:
    clearGraph();
  } 
  else {
    hPosition += 1;  
  }


}

public 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);
  }
  else {
    myPort.write(key);
  }
}

public 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] += DELIM;
  // 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 1tSensor 2tSensor 3tSensor 4tSensor 5";
    saveStrings("dataFile.txt", newData);
  }

}

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


public 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) {
    //println(serialString.charAt(serialString.length()-3));
    // println(serialString.charAt(serialString.length()-2));
    if (serialString.length() > 2) {
      if (serialString.charAt(serialString.length()-3) == INIT_MARKER) {
        println("sent");
        myPort.write(INIT_MESSAGE);
      }
    }
    // save the string in case you need to write it to a file:
    dataSet = serialString;
    println(dataSet);
    // split it into substrings on the tab character:

    String[] numbers = split(serialString, DELIM);
    // 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 incomgin value:
        int diff = abs(sensorValues[i] - PApplet.parseInt(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] =  PApplet.parseInt(numbers[i]);
      }

    }
    newData = true;
  }
}


  static public void main(String args[]) {     PApplet.main(new String[] { "BBC_Grapher" });  }}