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 BBCC_Plotter extends PApplet {/*
BBCC_Plotter
by Tim Hirzel, Feb 2008

with gratitude, based on:
Grapher Pro!
by Tom Igoe

 
This Processing application is designed to plot and display values 
coming from the BBCC PID controller for Arduino.  With minimal modification,
it could be setup to plot and show different incoming data

// All code released under
// Creative Commons Attribution-Noncommercial-Share Alike 3.0 
 */



// SETTINGS
int baudRate = 115200; 
char DELIM = ','; // the delimeter for parsing incoming data

char INIT_MARKER = '!';      //Look for this character before sending init message
int yBorder = 60;  // Size of the background area around the plot
int xBorder = 90;

String INIT_MESSAGE = "?gu";  //Send these characters to the arduino after init to start up messages
  // this gets the help string from teh device, and then turns on plotting mode with constant update strings

// the plot size
int plotHeight = 450;
int plotWidth = 750;
int ExpectUpdateSpeed = 200; // milliseconds.  This just allows the axis labels in the X direction accurate
int gridSpace = 50;  // the size of grid spacing


// These are calculated here, but could be changed if you wanted
int windowWidth = plotWidth + 2*xBorder;
int windowHeight = plotHeight +  2*yBorder;

// Define the location and size of the Legend area
int legendWidth = 125;
int legendHeight = 130;
int legendX = windowWidth - 140;
int legendY =  15;

// Define the size of the help area.  It always sits in the middle
int helpWidth = 600;
int helpHeight = 400;


String title = "Bare Bones Coffee Controller Tuning";    // Plot Title
String names = "Goal Curr P I D Pow";                    // The names of the values that get sent over serial
String yLabel = "T\ne\nm\np\ne\nr\na\nt\nu\nr\ne\n\nF";  // this is kind of a hack to make the vertical label
String xLabel = "Seconds";                               // X axis label
String fontType = "Courier";                             // Y axis label
boolean[] plotThisName = {
  true,true, false, false, false, false};                // For each of the values, you can choose if you want it plotted or not here

// end of Settings

String helpBase = "-Plotter Help-\n(all characters are case sensitive)\nh : toggle this help screen\nl : toggle the Legend\nS : save screen\nE : export shown data as text\n\n-Device Help-  \n";
String helpString = "";
Serial myPort;                // The serial port

boolean displayLegend = true;
boolean displayHelp = true;

int sensorCount = 6;                        // number of values to expect
float[][] sensorValues = new float[plotWidth][sensorCount];  // array to hold the incoming values
int currentValue = -1;
int hPosition = 1;                          // horizontal position on the plot
int displayChannel = 0;                     // which of the five readings is being displayed
int threshold = 50;                         // threshold for whether or not to write
// data to a file
boolean updatePlot = false;
//int [] lastSet  = new int[sensorCount];

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

PFont titleFont;
PFont labelFont;

public void setupColors() {
  // Thanks to colorbrewer for this pallete
  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 () {
  size(windowWidth, windowHeight);        // window size
  setupColors();
  smooth();
  //  println(PFont.list());
  titleFont = createFont(fontType, 18);
  labelFont = createFont(fontType, 14 );

  clearPlot();
  // List all the available serial ports
  println(Serial.list());
  
  // On my mac, the arduino is the first on this list.
  // Open whatever port is the one you're using.
  myPort = new Serial(this, Serial.list()[0], baudRate);
  // clear the serial buffer:
  myPort.clear();
}

public void draw () {
  // if the value for the given channel is valid, plot it:
  if (updatePlot) {
    // draw the plot:
    plot();
    updatePlot = false;
  }
}

public void clearPlot() {
  background(5);
  strokeWeight(1.5f);
  stroke(10);
  fill(40);
  // draw boundary
  rect(xBorder,yBorder,plotWidth, plotHeight);

  textAlign(CENTER);
  fill(70);
  textFont(titleFont);
  text(title, windowWidth/2, yBorder/2);

  textFont(labelFont);
  stroke(10);
  //draw grid  
  fill(70);
  textAlign(RIGHT);
  for (int i = plotHeight; i > 0; i-= gridSpace) {
    line(xBorder - 3, yBorder + i, xBorder + plotWidth - 1, yBorder + i);
    text(str(plotHeight - i), xBorder - 10, yBorder + i);
  }

  textAlign(LEFT);
  for (int i = 0; i < plotWidth ; i+= gridSpace) {
    line(xBorder + i, yBorder+1, xBorder + i, yBorder + plotHeight + 3);
    text(str(i/ (1000 / ExpectUpdateSpeed)), xBorder + i, yBorder + plotHeight + 20);
  }

  // Draw Axis Labels
  fill(70);
  text(yLabel, xBorder - 70,  yBorder + 100 );

  textAlign(CENTER);
  text(xLabel,  windowWidth/2, yBorder + plotHeight + 50);


}
public void plot () {
  clearPlot();
  // draw the line:
  for (int i = 0; i < sensorCount; i++) {
    stroke(colors[i][0], colors[i][1],colors[i][2]);

    for (int x = 1; x < currentValue; x++) {
      if(plotThisName[i]) {
        line(xBorder + x -1, 
        yBorder + plotHeight - sensorValues[x-1][i] - 1,
        xBorder + x,
        yBorder + plotHeight - sensorValues[x][i] - 1);

      }
    }
  }

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


  noStroke();
  // DRAW LEGEND
  if (displayLegend) {
    fill(128,128,128,80);
    rect(legendX, legendY, legendWidth, legendHeight);

    // print the name of the channel being graphed:
    String line;
    for (int i = 0; i < sensorCount; i++) {
      fill(colors[i][0], colors[i][1],colors[i][2]);
      textAlign(LEFT);
      text(split(names,' ')[i] , legendX+5, legendY + (i+1) * 20);
      textAlign(RIGHT);
      text(nf(sensorValues[currentValue][i], 0,2), legendX+legendWidth - 5, legendY + (i+1) * 20);
    }
  }

  if (displayHelp) {
    textAlign(LEFT);  
    fill(128,128,128,80);
    noStroke();
    rect(windowWidth/2 - helpWidth/2, windowHeight/2 - helpHeight / 2, helpWidth, helpHeight);
    fill(255,255,255);
    helpWidth -= 20;
    helpHeight -=20;
    text(helpString,windowWidth/2 - helpWidth/2, windowHeight/2 - helpHeight / 2, helpWidth, helpHeight);
    helpWidth += 20;
    helpHeight +=20;

  }
}

public void keyPressed() {
  // if the key pressed is "0" through "4"
  if (key == 'l') {
    // set the display channel accordingly
    displayLegend = ! displayLegend;  
    updatePlot = true;
  }
  if (key == 'h') {
    // set the display channel accordingly
    displayHelp = ! displayHelp;  
    updatePlot = true;
  }
  if (key == 'S') {
    // set the display channel accordingly
    save(str(hour()) + "h" + str(minute()) + "m" + str(second()) +  "s"  + str(month()) + "." + str(day()) + "." + str(year())+".jpg") ;
  }
  if (key == 'E') {
    exportText();
  }
  myPort.write(key);

}

public void exportText() {
  // string for the new data you'll write to the file:
  String[] outStrings = new String[currentValue+1];
  outStrings[0] = names;
  for (int i =0; i < currentValue; i++) {
    outStrings[i+1] = "";
    for (int j=0; j < sensorCount; j++) {
      outStrings[i+1]   += str(sensorValues[i][j]);
      if (j < sensorCount - 1) {
        outStrings[i+1] += ", ";
      }
    }
  }
  saveStrings(str(hour()) + "h" + str(minute()) + "m" + str(second()) +  "s"  + str(month()) + "." + str(day()) + "." + str(year())+".txt", outStrings);
}

// 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) && (serialString.charAt(serialString.length()-3) == INIT_MARKER)) {
      myPort.write(INIT_MESSAGE);
      helpString  = helpBase;

    } 
    else {
      // split it into substrings on the DELIM character:
      String[] numbers = split(serialString, DELIM);
      // convert each subastring into an int
      if (numbers.length == sensorCount) {
        currentValue += 1;
        if (currentValue >= plotWidth)
        {  
          currentValue = 0;
        }
        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 (i <= sensorCount) {
            // trim off any whitespace from the substring:
            numbers[i] = trim(numbers[i]);
            sensorValues[currentValue][i] =  PApplet.parseFloat(numbers[i]);
          }
        }
        updatePlot = true;
      } 
      else if (currentValue == -1){
        // The help string from the first '?' character gets appended to the plotter help string
        helpString += serialString;
      }
      else {
        // Things we don't handle in particular can get output to the text window
        print(serialString);
      }
    }
  }
}

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