{"id":122,"date":"2007-10-16T10:31:30","date_gmt":"2007-10-16T15:31:30","guid":{"rendered":"http:\/\/www.tigoe.net\/pcomp\/code\/category\/code\/processing\/122"},"modified":"2012-01-08T06:01:01","modified_gmt":"2012-01-08T11:01:01","slug":"data-graphing-program-that-saves-to-a-file","status":"publish","type":"post","link":"https:\/\/www.tigoe.com\/pcomp\/code\/Processing\/122\/","title":{"rendered":"Data graphing program that saves to a file"},"content":{"rendered":"<p>This Processing sketch takes data from the serial port, graphs it, and writes it to a text file with a time stamp if there&#8217;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.<\/p>\n<p>The text file it generates is tab-delimited, and can be read easily in a spreadsheet.<\/p>\n<p>A Wiring\/Arduino program to send data to this sketch follows at the end.<\/p>\n<p><!--more--><\/p>\n<pre class=\"brush:processing; light:true\">\r\n\/*\r\nGrapher Pro!\r\n by Tom Igoe\r\n \r\n This program takes raw bytes from the serial port at 9600 baud and graphs them.\r\n It expects five ASCII-encoded decimal values from 0-255, tab-delimited, ended by a \r\n newline and carriage return.\r\n \r\n To change which of the five channels is being shown in the graph, type 0 through 5.\r\n \r\n When any of the values changes by more than a set threshold, the program\r\n writes the data to a text file called dataFile.txt, which can be found in the \r\n sketch's directory.\r\n \r\n Created 20 April 2005\r\n Updated 15 October 2007\r\n *\/\r\n\r\nimport processing.serial.*;\r\n\r\nSerial myPort;                \/\/ The serial port\r\n\r\nint arrayLength = 5;                        \/\/ number of values to expect\r\nint[] sensorValues = new int[arrayLength];  \/\/ array to hold the incoming values\r\nint hPosition = 0;                          \/\/ horizontal position on the graph\r\nint displayChannel = 0;                     \/\/ which of the five readings is being displayed\r\nString dataSet;                             \/\/ string holding the incoming data    \r\nint threshold = 50;                         \/\/ threshold for whether or not to write \r\n                                            \/\/ data to a file\r\n\r\nvoid setup () {\r\n  size(400, 300);        \/\/ window size\r\n\r\n  \/\/ List all the available serial ports\r\n  println(Serial.list());\r\n  \/\/ I know that the third port in the serial list on my mac\r\n  \/\/ is always my  Keyspan adaptor, so I open Serial.list()[2].\r\n  \/\/ Open whatever port is the one you're using.\r\n  myPort = new Serial(this, Serial.list()[0], 9600);\r\n\r\n  \/\/ clear the serial buffer:\r\n  myPort.clear();\r\n  \/\/ don't generate a serialEvent() until you get a carriage return\r\n  myPort.bufferUntil('\\r');  \/\/ ASCII 13\r\n\r\n  \/\/ create a font with the second font available to the system:\r\n  PFont myFont = createFont(PFont.list()[1], 24 );\r\n  textFont(myFont);\r\n  \/\/ make the graphics smooth:\r\n  smooth();\r\n  \/\/ set inital background:\r\n  background(0);\r\n}\r\n\r\nvoid draw () {\r\n  \/\/ if the value for the given channel is valid, graph it:\r\n  if (sensorValues[displayChannel]  > 0 ) {\r\n    \/\/ print the name of the channel being graphed:\r\n    text(\"Channel: \" + displayChannel, 30, 30);\r\n    \/\/ draw the graph:\r\n    graph(sensorValues[displayChannel]);\r\n  }\r\n}\r\n\r\nvoid serialEvent(Serial myPort) {\r\n  \/\/ read incoming data until you get a newline:\r\n  String serialString = myPort.readStringUntil('\\n');\r\n  \/\/ if the read data is a real string, parse it:\r\n  if (serialString != null) {\r\n    \/\/ save the string in case you need to write it to a file:\r\n    dataSet = serialString;\r\n    \/\/ split it into substrings on the tab character:\r\n    String[] numbers = split(serialString, \"\\t\");\r\n    \/\/ convert each subastring into an int\r\n    for (int i = 0; i < numbers.length; i++) {\r\n      \/\/ make sure you're only reading as many numbers as \r\n      \/\/ you can fit in the array:\r\n      if (numbers.length <= arrayLength) {\r\n        \/\/ trim off any whitespace from the substring:\r\n        numbers[i] = trim(numbers[i]);\r\n        \/\/ find the difference between the current value and the incoming value:\r\n        int diff = abs(sensorValues[i] - int(numbers[i]));\r\n        \/\/ if the difference exceeds the threshold, write the data to a file:\r\n        if (diff > threshold) {\r\n          writeToFile(); \r\n        }\r\n        \/\/ save the new values in the array for use in graphing:\r\n        sensorValues[i] =  int(numbers[i]);\r\n      }\r\n    }\r\n  }\r\n}\r\n\r\n\r\nvoid graph (int numberToGraph) { \r\n  \/\/ draw the line:\r\n  stroke(0,255,0);\r\n  line(hPosition, height, hPosition, height - numberToGraph);\r\n  \/\/ at the edge of the screen, go back to the beginning:\r\n  if (hPosition >= width) {\r\n    hPosition = 0;\r\n    \/\/ wipe the screen clean:\r\n    background(0); \r\n  } \r\n  else {\r\n    \/\/ advance the horizontal position on the graph:\r\n    hPosition++;\r\n  }\r\n}\r\n\r\nvoid keyPressed() {\r\n  \/\/ if the key pressed is \"0\" through \"4\"\r\n  if ((48 <= key) &#038;&#038; (key <= 52)) {\r\n    \/\/ set the display channel accordingly\r\n    displayChannel = key - 48;\r\n    \/\/ wipe the screen:\r\n    background(0);\r\n  }\r\n}\r\n\r\n\r\n\r\nvoid writeToFile() {\r\n  \/\/ string for the new data you'll write to the file:\r\n  String[] newData = new String[1];\r\n  \/\/ add a time stamp:\r\n  newData[0] = timeStamp();\r\n  \/\/ add a tab:\r\n  newData[0] += \"\\t\";\r\n  \/\/ add the latest data from the serial port:\r\n  newData[0] += trim(dataSet);\r\n\r\n  \/\/ get the existing data from the file:\r\n  String[] dataSoFar = loadStrings(\"dataFile.txt\");\r\n  \/\/ if there's something there, dump it into an array\r\n  \/\/ and add the new data to it:\r\n  if (dataSoFar != null) {\r\n    \/\/ array needs to accommodate the old data and the new:\r\n    String[] dataToWrite = new String[dataSoFar.length + newData.length];\r\n    \/\/ dump the existing data from the file back in:\r\n    for (int s = 0; s < dataSoFar.length; s++) {\r\n      dataToWrite[s] = dataSoFar[s]; \r\n    }\r\n    \/\/ append the new data:\r\n    for (int s = dataSoFar.length; s < dataToWrite.length; s++) {\r\n      dataToWrite[s] = newData[s-dataSoFar.length]; \r\n    }\r\n    \/\/ dump the result back to the file:\r\n    saveStrings(\"dataFile.txt\", dataToWrite);\r\n  } \r\n  \/\/ if there's no existing data:\r\n  else {\r\n    \/\/ add a header to the file:\r\n    newData[0] = \"Time:\\tSensor 1\\tSensor 2\\tSensor 3\\tSensor 4\\tSensor 5\";\r\n    saveStrings(\"dataFile.txt\", newData);\r\n  }\r\n}\r\n\r\n\/\/ make up a timeStamp string for writing data to the file:\r\nString timeStamp() {\r\n  String now = hour()+ \":\" +  minute()+ \":\" + second()+ \" \" +\r\n    month() + \"\/\"  + day() + \"\/\" + year();\r\n  return now;\r\n}\r\n<\/pre>\n<p>Here's a Wiring\/Arduino program to send some serial data that the grapher can read:<\/p>\n<pre>\r\n\/*\r\n  Read in five analog sensor values, send them out\r\n as tab-delimited ASCCI-encoded decimal numbers,\r\n ended by a carriage return and newline\r\n \r\n created 15 Oct. 2007\r\n \r\n *\/\r\nvoid setup() {\r\n  \/\/ iniaialize the serial port:\r\n  Serial.begin(9600);\r\n}\r\n\r\nvoid loop() {\r\n  \/\/ count from 0 to 4 (five channels):\r\n  for (int channel=0; channel <=4; channel++) {\r\n    \/\/ read an analog input, divide the result by 4\r\n    \/\/ to limit it to 0-255:\r\n    int sensorValue = analogRead(channel)\/4;\r\n    \/\/ print it:\r\n    Serial.print(sensorValue, DEC);\r\n    \/\/ if it's not the last channel to be read,\r\n    \/\/ print a tab character:\r\n    if (channel < 4) {\r\n      Serial.print(\"\\t\");\r\n    }\r\n    \/\/ delay to let the analog-to-digital converter settle:\r\n    delay(8);\r\n  }\r\n  \/\/ once you've printed all the values, \r\n  \/\/ print a newline and carriage return:\r\n  Serial.println();\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>This Processing sketch takes data from the serial port, graphs it, and writes it to a text file with a time stamp if there&#8217;s a significant change in any of the incoming values&#8230;.  Created 20 April 2005 Updated 15 October 2007 *\/import processing.serial.*;Serial myPort; \/\/ The serial portint arrayLength = 5; \/\/ number of values to expectint[] sensorValues = new int[arrayLength]; \/\/ array to hold the incoming valuesint hPosition = 0; \/\/ horizontal position on the graphint displayChannel = 0; \/\/ which of the five readings is being displayedString dataSet; \/\/ string holding the incoming data int threshold = 50; \/\/ threshold for whether or not to write \/\/ data to a filevoid 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].<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[],"class_list":["post-122","post","type-post","status-publish","format-standard","hentry","category-Processing"],"_links":{"self":[{"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/posts\/122","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=122"}],"version-history":[{"count":4,"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/posts\/122\/revisions"}],"predecessor-version":[{"id":1036,"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/posts\/122\/revisions\/1036"}],"wp:attachment":[{"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/media?parent=122"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/categories?post=122"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tigoe.com\/pcomp\/code\/wp-json\/wp\/v2\/tags?post=122"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}