1

I have a project where I have data come in via the serial port every 15 minutes. I am using processing to read this data and save it as a CSV.

I would like for a new file to be created every 12 hours. However, when the file switches from AM to PM the entire row gets saved in the PM file (all the previous AM values)

How can I reset the table and start saving to a new file?

      saveTable(dataTable, fileName());
      dataTable.clearRows();

I tried this but it just clears the CSV file.

String fileName() {
  String fileName = "";
  String month = "";
  String day = "";
  int m = month();
  int d = day();

  if (d < 10) {
    day = str(d);
    day = "-0" + day;
  } else {
    day = "-" + str(d);
  }

  switch(m) {
  case 1:
    month = "-JAN";
    break;
  case 2:
    month = "-FEB";
    break;
  case 3:
    month = "-MAR";
    break;
  case 4:
    month = "-APR";
    break;
  case 5:
    month = "-MAY";
    break;
  case 6:
    month = "-JUN";
    break;
  case 7:
    month = "-JUL";
    break;
  case 8:
    month = "-AUG";
    break;
  case 9:
    month = "-SEP";
    break;
  case 10:
    month = "-OCT";
    break;
  case 11:
    month = "-NOV";
    break;
  case 12:
    month = "-DEC";
    break;
  }

  if (hour() >= 12) {
    hour = "-PM";
  } else {
    hour = "-AM";
  }

  fileName = "SensorData_" + str(year()) + month + day  + hour + ".csv";
  return fileName;
}

Update: Code for collecting and saving data

void serialEvent(Serial myPort) {
  if (myPort.available() > 0) {
    String serialDataString = myPort.readString();
    if (serialDataString != null) {
      serialDataString = trim(serialDataString);
      float[] sensorData = float(split(serialDataString, ','));
      TableRow newRow = dataTable.addRow();
      if (sensorData.length == 4) {
        temperature = sensorData[0];
        humidity    = sensorData[1];
        moisture    = sensorData[2];
        int packet = int(sensorData[3]);

        if (packet < 10) {
          packets = "00" + str(packet);
        } else if (packet < 100) {
          packets = "0" + str(packet);
        }

        String time = str(hour()) + ":" + str(minute()) + ":" + str(second());
        String date = str(month()) + "/" + str(day());
        newRow.setFloat("Temperature", temperature);
        newRow.setFloat("Humidity", humidity);
        newRow.setFloat("Moisture", moisture);
        newRow.setString("Time", time);
        newRow.setString("Date", date);
      }
      saveTable(dataTable, fileName());
    }
  }
}
  • Unfortunately I won't be able to provide an ample answer at the moment. It's unclear if `dataTable.clearRows()` clears the data after saving is complete (expected) or before (which sounds like potentially buggy behaviour). It would be great if you could also share how you check time / every 12 hours (where probably the above snippet is called from), and also the definition of `fileName()` to double check it looks as expected. Although not a solution, a potential workaround could be adding a separate column for timestamps which should help post-processing/filtering csv data later on. – George Profenza Nov 05 '22 at 22:54
  • @GeorgeProfenza, I have updated the original post with the fileName() function. The table already includes a time row and date row. – Justin Wilker Nov 06 '22 at 04:04
  • Thank you for updating the question. `fileName()` function looks like ok on first glance. You could probably a similar output via `String fileName(){ return "SensorData_" + new SimpleDateFormat("yyyy-MMM-dd-aa").format(new Date()) + ".csv"; }` (after importing `java.util.Date` and `java.text.SimpleDateFormat`). If clearing after a save works as expected, that leaves checking the condition you use to check time / every 12 hours, right ? (It sounds like the code should save at 11:59:59 and 23:59:59 (if it isn't already)), (You might find `ScheduledExecutorService` useful) – George Profenza Nov 06 '22 at 05:24
  • Could you go into more detail of what `ScheduledExecutorService` would do? I haven't touched java much. Clearing after a save does not work as expected, that's why I initially created the post. Data comes in roughly every 15 minutes (sometimes changes). I want to save to a different file every 12 hours but can't figure out how to clear the table when changing file names. – Justin Wilker Nov 06 '22 at 15:37
  • Sorry I won't have the time for an ample answer. `ScheduledExecutorService` would help run a function at a specified interval (e.g. 15 minutes collect data, 12 hours write data to disk). You can find a guide [here](https://www.baeldung.com/java-executor-service-tutorial), but it's Java isn't your main language, maybe it's really not worth the complexity and you simply get away with [`millis()`](https://stackoverflow.com/questions/12417937/create-a-simple-countdown-in-processing/12421641#12421641). – George Profenza Nov 06 '22 at 22:07
  • I won't be able to look into the bug any time soon. Would using two `Table` instances: one for AM, one for PM make it easier to separate the data ? Hopefully this might allow to get over the clearing after a save issue ? It possible, I'd also post the logic you use to decide when to save a table. Others might suppport if they spot issues: extra pair of eyes might help. I suspect the Arduino is only outputting data every 15 minutes ? – George Profenza Nov 06 '22 at 22:10
  • That is correct, data only comes every 15 minutes. The table is saved every time it gets data. I will try to create separate tables and clear the AM table once I start saving to PM and vice versa – Justin Wilker Nov 06 '22 at 23:45
  • "The table is saved every time it gets data": easier to undertand through code, otherwise can only make (potentially wrong/inaccurate ) assumptions. This implies you get many csv files (one every 15 minutes) with timestamps (and AM/PM in the filename) and depending when the program was started it might miss one of the 15 minute messages. (that is assuming the Arduino has a real time clock RTC and sends data at every 15 minutes on the clock, otherwise it depends when it started, Processing also need to not miss those 15 minute windows?).Initially I thought you saved CSV files every 12 hours – George Profenza Nov 07 '22 at 00:00
  • ...if that's not the case and you save 96 files per day, that will be an interesting to put the data back together, whatever the final application may be. You could potentially read / append new entries every 15 minutes to the same am file, but need to ensure you don't overwrite data and correctly append first. Depending on time / scope, it might be worth exploring using a super basic database with a single table. Doing a quick search reveals [sql-library-processing](https://github.com/fjenett/sql-library-processing). Most SQL admin interfaces allow exporting CSV files... – George Profenza Nov 07 '22 at 00:04
  • ...while more complex, it offer flexibility (making your own queries and exporting data as needed). – George Profenza Nov 07 '22 at 00:05
  • Processing simply reads from the serial port and saves to a file when it gets new information. It does not worry about the timing, if it gets new data, it saves it. I would like it to have 2 files for each day, but save to the respective file when it gets new data. I have updated the post with the code for gathering and saving the data. – Justin Wilker Nov 08 '22 at 00:37

1 Answers1

1

In comments you've mentioned

Clearing after a save does not work as expected, To clarify, what I meant is, if you call clearRows(), previous data will be erased. Saving before clearRows() should save previous data only, saving after clearRows() should only save current data.

I wrote a basic sketch and to me it looks that this works as expected:

void setup() {
  // make new table, add 3 cols
  Table dataTable = new Table();
  dataTable.addColumn();
  dataTable.addColumn();
  dataTable.addColumn();
  // add 1, 2, 3
  TableRow newRow = dataTable.addRow();
  newRow.setInt(0, 1);
  newRow.setInt(1, 2);
  newRow.setInt(2, 3);
  // save to disk  (expecting 1, 2, 3)
  saveTable(dataTable,"test1.csv");

  // print (expecting 1, 2, 3)
  dataTable.print();
  // completely clear table
  dataTable.clearRows();

  // add 4, 5, 6
  newRow = dataTable.addRow();
  newRow.setInt(0, 4);
  newRow.setInt(1, 5);
  newRow.setInt(2, 6);
  // save again (expecting 4, 5, 6)
  saveTable(dataTable,"test2.csv");
  // print (expecting, 4, 5, 6)
  dataTable.print();
  
}

(It's also nice that saveTable() appends data (and doesn't overwrite data) in this case.)

This is how I understand how/when data flows in your setup:

  1. Arduino sends data over serial every 15 minutes. You haven't specified if the Arduino has a real time clock (RTC) and the code there uses it to only output data every 15 minutes on the clock (e.g. at :00, :15, :30, :45 past the hour, every hour). The assumption is there is no realtime clock and you're either using delay() or millis() so the actual time data gets sent out is relative to when the Arduino was powered.
  2. When Processing sketch starts, it reads this serial data (meaning any prior data is loest). The assumption is there is no time sync between Arduino and Processing. The first row of data from Arduino comes at Arduino's next 15 minute (not Processing's) after the sketch was started.

The issue you might be experiencing based on your short snippet,

saveTable(dataTable, fileName());
dataTable.clearRows();

if it gets called in serialEvent() is that you'll loose data. (Confusingly, it doesn't like you're calling clearRows() from serialEvent() ?)

One idea I can think is having some sort of event when the switch from AM/PM to then (first save any accumated data with the previous filename), then clear the the table and update the filename, otherwise (in serial event, save the data with the same filename).

A hacky approach is, once the AM/PM suffixed timestamp is generated to check if this suffix changes and only update filename/clear rows when this change occurs (e.g. manually "debouncing").

Here's a rough sketch to illustrate the idea:

Serial myPort;
float temperature, humidity, moisture; 
 
Table dataTable = new Table();

String packets;
int packet;

boolean isAM,wasAM;
String tableFileName;
 
public void setup() {
  
  textSize(14);
  
  try{
    myPort = new Serial(this, "COM4", 9600);
    myPort.bufferUntil('\n');
  }catch(Exception e){
    println("Error opening Serial port!\nDouble check the Serial port is connected via USB, the port name is correct and the port istn't already open in Serial Monitor");
    e.printStackTrace();
  }
  
  tableFileName = "SensorData_" + getDateStampString() + ".csv";
}
 
public void draw() {
  
  background(255);
  
  String sensorText = String.format("Temperature: %.2f Humidity: %.2f  Moisture: %.2f", temperature, humidity, moisture);
  float textWidth = textWidth(sensorText);
  float textX = (width - textWidth) / 2;
  fill(255);  
  rect(textX - 10, 14, textWidth + 20, 21);
  fill(0);
  text(sensorText, textX, 30);
  // get an update date string
  String dateStamp = getDateStampString();
  // check AM/PM switch and update
  isAM = dateStamp.endsWith("AM");
  if(!wasAM && isAM){
    println("changed PM to AM");
    updateTableAMPM(dateStamp);
    // update previous state for debouncing
    wasAM = true;
  }
  
  if(wasAM && !isAM){
    println("changed AM to PM");
    updateTableAMPM(dateStamp);
    wasAM = true;
  }
  
}

public void updateTableAMPM(String dateStamp){
  // saves current table (old filename): we're vaing data before the AM/PM switch
  saveDataTable();
  // clear rows so next 12 cycle starts fresh
  dataTable.clearRows();
  // update filename (for next save (serialEvent) to use)
  tableFileName = "SensorData_" + dateStamp + ".csv";
}

public String getDateStampString(){
  return new SimpleDateFormat("yyyy-MMM-dd-aa").format(new Date());
}

public void saveDataTable(){
  saveTable(dataTable, tableFileName);
  println("saved",tableFileName);
}

public void serialEvent(Serial myPort) {
  if (myPort.available() > 0) {
    String serialDataString = myPort.readString();
    if (serialDataString != null) {
      serialDataString = trim(serialDataString);
      float[] sensorData = PApplet.parseFloat(split(serialDataString, ','));
      TableRow newRow = dataTable.addRow();
      if (sensorData.length == 4) {
        temperature = sensorData[0];
        humidity    = sensorData[1];
        moisture    = sensorData[2];
        int packet = PApplet.parseInt(sensorData[3]);

        if (packet < 10) {
          packets = "00" + str(packet);
        } else if (packet < 100) {
          packets = "0" + str(packet);
        }

        String time = str(hour()) + ":" + str(minute()) + ":" + str(second());
        String date = str(month()) + "/" + str(day());
        newRow.setFloat("Temperature", temperature);
        newRow.setFloat("Humidity", humidity);
        newRow.setFloat("Moisture", moisture);
        newRow.setString("Time", time);
        newRow.setString("Date", date);
      }
      // save data, but don't change the filename
      saveDataTable();
    }
  }
}

Note the above isn't tested (so might contain errors), but hopefully it illustrates the ideas aforementioned.

(One minor note on packets (which I'm unsure where it's used): you can use nf() to easily pad a number with zeros (There are similar functions like nfc(), nfp(), nfs())).

Another option (similar to what I've mentioned in comments) is to use java utilities to call a function after a set time (e.g. the difference in time since the start of the sketch until either noon or midnight, whichever comes first), to then repeat at 12 hour intervals. You can check out TimerTask, or if your familiar with setTimeout in JS you can try this Thread based WIP setTimeout Processing workaround.

George Profenza
  • 50,687
  • 19
  • 144
  • 218