Hello @devinaconley
I modified your "listener" source code attached below to be able, by pressing the space key, to toggle time-stamped recording of all variables on hard disk, in a date&time-stamped CSV - text file. first line gives the names of the variables stored in the file.
rgds
/*
This listener is the main processing script that corresponds to the Arduino Plotter
library for Arduino. This driver script handles serial port information and manages a
set of Graph objects to do the actual plotting.
The library stores and handles all relevant graph information and variable references,
and transfers information via the serial port to a listener program written with the
software provided by Processing. No modification is needed to this program; graph placement,
axis-scaling, etc. are handled automatically.
Multiple options for this listener are available including stand-alone applications as well
as the source Processing script.
The library, these listeners, a quick-start guide, documentation, and usage examples are
available at:
// FLAG FOR DEBUG MODE
final boolean DEBUG = false;
//CONSTANTS
final char OUTER_KEY = '#';
final int MARGIN_SZ = 20; // between plots
final int BG_COL = 75; // background
final int PORT_INTERVAL = 5000; // time to sit on each port
final int CONNECT_TIMEOUT = 2000; // force timeout on connecting to serial port
final int BAUD_RATE = 115200;
// Setup and config Globals
int h;
int w;
int numGraphs;
String configCode = "This will not be matched!";
String lastLabels = "Also will not be matched";
boolean configured = false;
int lastConfig;
int lastPortSwitch;
int portIndex;
Serial port;
ArrayList graphs;
// variables to create file name, and manage recording ( see keypressed at the bottom ). The space bar will toggle ON-OFF simultaneous recording in a text file during graph display
PrintWriter output;
int d = day(); // Values from 1 - 31
int m = month(); // Values from 1 - 12
int y = year(); // 2003, 2004, 2005, etc.
int sec = second(); // Values from 0 - 59
int min = minute(); // Values from 0 - 59
int hr = hour(); // Values from 0 - 23
boolean Recording=false; // We do NOT record initially. Only pressing the space bar will toggle recording. Esc to get out will flush the outstanding data to file.
void setup()
{
// Canvas
size( 1430, 830 );
surface.setResizable( true );
h = height;
w = width;
frameRate(20); // 20 (50ms) instead of 50 frames/s = 4ms
// Serial comms
while ( Serial.list().length < 1 )
{
text( "No serial ports available. Waiting...", 20, 20 );
delay( 100 );
}
portIndex = 0;
lastPortSwitch = millis();
attemptConnect( portIndex );
// creating a "time-stamped" file name : "listeneryyyy-mm-dd hh.mm.ss.txt" including a full date to be able to recover files easilyt later...
String sd = str(y) + "-" + nf(m,2) + "-" + nf(d,2) + " " + nf(hr,2) + "." + nf(min,2) + "." + nf(sec,2);
output = createWriter("listener"+sd+".txt");
output.print(" time(ms), "); // Write the legend to the first line of text file
}
void draw()
{
//PLOT ALL
try
{
background( BG_COL );
if ( configured )
{
for( int i = 0; i < graphs.size(); i++ )
{
graphs.get(i).Plot();
}
}
else
{
// Continue to scan ports if not configuring
text( "Scanning serial ports... (" + Serial.list()[portIndex] + ")", 20, 20 );
if ( millis() - lastPortSwitch > PORT_INTERVAL )
{ // Go to next port
portIndex++;
if ( portIndex >= Serial.list().length )
{
portIndex = 0;
}
logMessage( "Trying next port... index: " + portIndex + ", name: " + Serial.list()[portIndex],
true );
attemptConnect( portIndex );
}
}
// Resize if needed
if ( h != height || w != width)
{
h = height;
w = width;
float[][] posGraphs = setupGraphPosition( numGraphs );
for ( int i = 0; i < numGraphs; i++ )
{
graphs.get(i).Reconfigure( posGraphs[i][0], posGraphs[i][1], posGraphs[i][2], posGraphs[i][3] );
}
}
}
catch ( Exception e )
{}
}
void serialEvent( Serial ser )
{
// Listen for serial data until #, the end of transmission key
try
{
String message = ser.readStringUntil( OUTER_KEY );
if ( message == null || message.isEmpty() || message.equals( OUTER_KEY ) )
{
return;
}
JSONObject json = parseJSONObject( message );
if ( json == null )
{
return;
}
// ********************************************************* //
// ************* PLOT SETUP FROM CONFIG CODE *************** //
// ********************************************************* //
String tempCode = "";
boolean config = false;
if ( json.hasKey( "ng" ) && json.hasKey( "lu" ) )
{
tempCode = Integer.toString( json.getInt( "ng" ) ) + Integer.toString( json.getInt( "lu" ) );
config = true;
}
// If config code has changed, need to go through setup again
if ( config && !configCode.equals( tempCode ) )
{
lastPortSwitch = millis(); // (likely on the right port, just need to reconfigure graph layout)
// Check for size of full transmission against expected to flag bad transmission
numGraphs = json.getInt( "ng" );
JSONArray jsonGraphs = json.getJSONArray( "g" );
if ( jsonGraphs.size() != numGraphs )
{
return;
}
configured = false;
String concatLabels = "";
// Setup new layout
float[][] posGraphs = setupGraphPosition( numGraphs );
graphs = new ArrayList<Graph>();
// Iterate through the individual graph data blocks to get graph specific info
for ( int i = 0; i < numGraphs; i++ )
{
JSONObject g = jsonGraphs.getJSONObject( i );
String title = g.getString( "t" );
boolean xvyTemp = g.getInt( "xvy" ) == 1;
int maxPoints = g.getInt( "pd" );
int numVars = g.getInt( "sz" );
String[] labelsTemp = new String[numVars];
int[] colorsTemp = new int[numVars];
concatLabels += title;
JSONArray l = g.getJSONArray( "l" );
JSONArray c = g.getJSONArray( "c" );
for ( int j = 0; j < numVars; j++ )
{
labelsTemp[j] = l.getString( j );
output.print(labelsTemp[j]); // Write the legend to the first line of text file
if (j < numVars-1) output.print(", ");
colorsTemp[j] = COLORMAP.get( c.getString( j ) );
if ( colorsTemp[j] == 0 )
{
logMessage( "Invalid color: " + c.getString( j ) + ", defaulting to green.", true );
colorsTemp[j] = COLORMAP.get( "green" );
}
concatLabels += labelsTemp[j];
}
output.println("");
if ( xvyTemp )
{
numVars = 1;
}
// Create new Graph
Graph temp = new Graph( this, posGraphs[i][0], posGraphs[i][1], posGraphs[i][2], posGraphs[i][3],
xvyTemp, numVars, maxPoints, title, labelsTemp, colorsTemp );
graphs.add( temp );
}
// Set new config code
if ( concatLabels.equals( lastLabels ) ) // Only when we're sure on labels
{
configCode = tempCode;
lastConfig = millis();
logMessage( "Configured " + graphs.size() + " graphs", false );
}
lastLabels = concatLabels;
logMessage( "Config code: " + configCode + ", Label config: " + concatLabels, true );
}
else
{
// Matching a code means we have configured correctly
configured = true;
// *********************************************************** //
// ************ NORMAL PLOTTING FUNCTIONALITY **************** //
// *********************************************************** //
int tempTime = json.getInt( "t" );
JSONArray jsonGraphs = json.getJSONArray( "g" );
for ( int i = 0; i < numGraphs; i++ )
{
JSONArray data = jsonGraphs.getJSONObject( i ).getJSONArray( "d" );
double[] tempData = new double[ data.size() ];
if (Recording) output.print(millis() + ", ");
// Update graph objects with new data
for ( int j = 0; j < data.size(); j++ )
{
tempData[j] = data.getDouble( j );
if (Recording)
{
output.print(str((float)tempData[j]));
if (j < data.size()-1) output.print(", ");
}
}
graphs.get( i ).Update( tempData, tempTime );
if (Recording) output.println("");
}
}
}
catch ( Exception e )
{
logMessage( "Exception in serialEvent: " + e.toString(), true );
}
}
// Helper method to calculate bounds of graphs
float[][] setupGraphPosition( int numGraphs )
{
// Determine orientation of each graph
int numHigh = 1;
int numWide = 1;
// Increase num subsections in each direction until all graphs can fit
while ( numHigh * numWide < numGraphs )
{
if ( numWide > numHigh )
{
numHigh++;
}
else if ( numHigh > numWide+1 )
{
numWide++;
}
else if ( height >= width )
{
numHigh++;
}
else
{
// Want to increase in high first
numHigh++;
}
}
float[][] posGraphs = new float[numGraphs][4];
float subHeight = round( h / numHigh );
float subWidth = round( w / numWide );
// Set bounding box for each subsection
for(int i = 0; i < numHigh; i++)
{
for (int j = 0; j < numWide; j++)
{
int k = i * numWide + j;
if ( k < numGraphs )
{
posGraphs[k][0] = i*subHeight + MARGIN_SZ / 2;
posGraphs[k][1] = j*subWidth + MARGIN_SZ / 2;
posGraphs[k][2] = subHeight - MARGIN_SZ;
posGraphs[k][3] = subWidth - MARGIN_SZ;
}
}
}
return posGraphs;
}
void attemptConnect( int index )
{
// Attempt connect on specified serial port
if ( index >= Serial.list().length )
{
return;
}
String portName = Serial.list()[portIndex];
logMessage( "Attempting connect on port: " + portName, false );
// Wrap Serial port connect in future to force timeout
ExecutorService exec = Executors.newSingleThreadExecutor();
Future<Serial> future = exec.submit( new ConnectWithTimeout( this, portName, BAUD_RATE ) );
try
{
// Close port if another is open
if ( port != null && port.active() )
{
port.stop();
}
// Do connect with timeout
port = future.get( CONNECT_TIMEOUT, TimeUnit.MILLISECONDS );
lastPortSwitch = millis(); // at end so that we try again immediately on invalid port
logMessage( "Connected on " + portName + ". Listening for configuration...", false );
}
catch ( TimeoutException e )
{
future.cancel( true );
logMessage( "Timed out.", true );
}
catch ( Exception e )
{
logMessage( "Exception on connect: " + e.toString(), true );
}
exec.shutdownNow();
}
// Callable class to wrap Serial connect
class ConnectWithTimeout implements Callable
{
private final PApplet parent;
private final String portName;
private final int baudRate;
public ConnectWithTimeout( PApplet parent, String portName, int baud )
{
this.parent = parent;
this.portName = portName;
this.baudRate = baud;
}
@Override
public Serial call() throws Exception
{
return new Serial( this.parent, this.portName, baudRate );
}
void keyPressed() {
if (key==ESC) // to exit listener
{
output.flush(); // Writes the remaining data to the file
output.close(); // Finishes the file
exit(); // Stops the program
}
else if (key==' ') // to toggle record - otehr keys can be implemented to do other things
Recording=!Recording;
}
Hello @devinaconley I modified your "listener" source code attached below to be able, by pressing the space key, to toggle time-stamped recording of all variables on hard disk, in a date&time-stamped CSV - text file. first line gives the names of the variables stored in the file. rgds
/*
This listener is the main processing script that corresponds to the Arduino Plotter library for Arduino. This driver script handles serial port information and manages a set of Graph objects to do the actual plotting.
The library stores and handles all relevant graph information and variable references, and transfers information via the serial port to a listener program written with the software provided by Processing. No modification is needed to this program; graph placement, axis-scaling, etc. are handled automatically. Multiple options for this listener are available including stand-alone applications as well as the source Processing script.
The library, these listeners, a quick-start guide, documentation, and usage examples are available at:
https://github.com/devinaconley/arduino-plotter
Arduino Plotter Listener Modified PhDV61 : On-off recording in a text file for excel off-line analysis) v2.2.1 https://github.com/devinaconley/arduino-plotter by Devin Conley
*/
import processing.serial.*; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException;
// FLAG FOR DEBUG MODE final boolean DEBUG = false;
//CONSTANTS final char OUTER_KEY = '#'; final int MARGIN_SZ = 20; // between plots final int BG_COL = 75; // background final int PORT_INTERVAL = 5000; // time to sit on each port final int CONNECT_TIMEOUT = 2000; // force timeout on connecting to serial port final int BAUD_RATE = 115200;
final HashMap<String, Integer> COLORMAP = new HashMap<String, Integer>() { { put( "red", color( 255, 0, 0 ) ); put( "green", color( 0, 255, 0 ) ); put( "blue", color( 0, 0, 255 ) ); put( "orange", color( 255, 153, 51 ) ); put( "yellow", color( 255, 255, 0 ) ); put( "pink", color( 255, 51, 204 ) ); put( "purple", color( 172, 0, 230 ) ); put( "cyan", color( 0, 255, 255 ) ); } };
// Setup and config Globals int h; int w; int numGraphs; String configCode = "This will not be matched!"; String lastLabels = "Also will not be matched"; boolean configured = false; int lastConfig; int lastPortSwitch; int portIndex; Serial port; ArrayList graphs;
// variables to create file name, and manage recording ( see keypressed at the bottom ). The space bar will toggle ON-OFF simultaneous recording in a text file during graph display PrintWriter output; int d = day(); // Values from 1 - 31 int m = month(); // Values from 1 - 12 int y = year(); // 2003, 2004, 2005, etc. int sec = second(); // Values from 0 - 59 int min = minute(); // Values from 0 - 59 int hr = hour(); // Values from 0 - 23 boolean Recording=false; // We do NOT record initially. Only pressing the space bar will toggle recording. Esc to get out will flush the outstanding data to file.
void setup() { // Canvas size( 1430, 830 ); surface.setResizable( true ); h = height; w = width; frameRate(20); // 20 (50ms) instead of 50 frames/s = 4ms
}
void draw() { //PLOT ALL try { background( BG_COL );
}
void serialEvent( Serial ser ) { // Listen for serial data until #, the end of transmission key try { String message = ser.readStringUntil( OUTER_KEY ); if ( message == null || message.isEmpty() || message.equals( OUTER_KEY ) ) { return; }
}
// Helper method to calculate bounds of graphs float[][] setupGraphPosition( int numGraphs ) { // Determine orientation of each graph
int numHigh = 1; int numWide = 1; // Increase num subsections in each direction until all graphs can fit while ( numHigh * numWide < numGraphs ) { if ( numWide > numHigh ) { numHigh++; } else if ( numHigh > numWide+1 ) { numWide++; } else if ( height >= width ) { numHigh++; } else { // Want to increase in high first numHigh++; }
}
}
void attemptConnect( int index ) {
// Attempt connect on specified serial port if ( index >= Serial.list().length ) { return; } String portName = Serial.list()[portIndex]; logMessage( "Attempting connect on port: " + portName, false );
}
// Callable class to wrap Serial connect class ConnectWithTimeout implements Callable
{
private final PApplet parent;
private final String portName;
private final int baudRate;
}
// Logger helper void logMessage( String message, boolean debugOnly ) { if ( DEBUG || !debugOnly ) { String level = debugOnly ? "DEBUG" : "STATUS"; println( "[Time: " + millis() + " ms]" + "[" + level + "] " + message ); } }
void keyPressed() { if (key==ESC) // to exit listener { output.flush(); // Writes the remaining data to the file output.close(); // Finishes the file exit(); // Stops the program
} else if (key==' ') // to toggle record - otehr keys can be implemented to do other things Recording=!Recording; }