I am trying to take data from the serial port and use Processing to save it as a CSV
import processing.serial.*;
Serial myPort;
float[] sensorData= {0, 0, 0};
void setup() {
size(1043, 152);
background(255);
myPort = new Serial(this, "COM4", 9600);
myPort.bufferUntil('\n');
}
void draw() {
fill(0);
rect(10, 2, inStr[0], 46);
rect(10, 52, inStr[1], 46);
rect(10, 102, inStr[2], 46);
fill(255);
rect(390, 14, 265, 21);
fill(0);
textAlign(CENTER);
textSize(14);
text("Temperature: " + sensorData[0] + " Humidity: " + sensorData[1] + " Moisture: " + sensorData[2], width/2, 30);
}
void serialEvent(Serial myPort) {
if (myPort.available() > 0) {
String serialData= myPort.readStringUntil('\n');
if (serialData!= null) {
serialData = trim(serialData);
sensorData = float(split(serialData, ','));
print(sensorData);
}
}
}
The data however comes once every 15 minutes. Processing continues to check for data and throws an out of bounds exception when there is no data.
How can I make it so it will only run when new data comes through the serial port?
First off, where does inStr[]
come from ? Perhaps left over from prototyping/testing ? if not required, it should be deleted, otherwise changed to use an existing variable.
Because you use myPort.bufferUntil('\n')
you should be able to use myPort.readString()
instead of myPort.readStringUntil('\n');
.
You're on the right track by checking if serialData
is not null and trimming whitespace.
You might experience variable shadowing: float[] sensorData
declared at the top has the same name as String serialData
(serialEvent()
)
Additionally you can check if float(split(serialData, ','))
has the expected number of elements (e.g. if something goes wrong with serial communication and bytes are lost).
This probably won't change much, but you can choose to use 3 float variables instead of an array where you assign values from the float[]
parsed from strings. e.g.
import processing.serial.*;
Serial myPort;
float temperature, humidity, moisture;
void setup() {
size(1043, 152);
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();
}
}
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);
}
void serialEvent(Serial myPort) {
if (myPort.available() > 0) {
String serialDataString = myPort.readString();
if (serialDataString != null) {
serialDataString = trim(serialDataString);
float[] sensorData = float(split(serialDataString, ','));
print(sensorData);
// check if 3 values are received
if(sensorData.length == 3){
temperature = sensorData[0];
humidity = sensorData[1];
moisture = sensorData[2];
}else{
println("received <3 values");
}
}
}
}
One last think about is how many bytes are being sent. If you have a large number of floating point digits, each will take a character (byte).
If you display only 2 floating values, you can choose to format that format that data on the Arduino side first.
Additionally you can choose pack the data as bytes instead (e.g. keep the sensor values as 0-1023 values from analogRead()
which use 4 bytes each (2 16-bit words
): this should result in 12 bytes for the data and 1 byte for the terminator character (\n
/ 10
).The 0-1023
values can then be converted in Processing to floating point values.
Ideally you'd put together a basic byte based serial protocol that is more roboust (e.g. byte header with packet length, checksum, data payload), but this may not be beginner friendly. It would avoid pitfalls such as using \n
as both the terminator character, but also part of the data (and Processing not knowing the difference between a a byte which should mean the value 10 from a sensor and the \n character). (Although it will a tiny bit of overhead on the Arduino side, it's worth looking into the PacketSerial Arduino library if you want to avoid making your own binary serial protocol. (In the past I've managed to use PacketSerial with SLIP encoding to parse data as easy to parse OSC messages by sacrificing a bit of Arduino RAM))
Update Regarding CSV you can make use of Processing's Table class:
TableRow
, populate it with the 3 values and add it to the table's'
), saveTable()
Here's a modified version of the above, saving in TSV (tab separated values) format, just in case there's a conflict between commas in each value and separating table values with the same text character:
import processing.serial.*;
Serial myPort;
float temperature, humidity, moisture;
// table to store sensor data into
Table sensorDataTable;
void setup() {
size(1043, 152);
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();
}
// init table
sensorDataTable = new Table();
// nice to have: table column names
sensorDataTable.addColumn("temperature");
sensorDataTable.addColumn("humidity");
sensorDataTable.addColumn("moisture");
}
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);
}
void serialEvent(Serial myPort) {
if (myPort.available() > 0) {
String serialDataString = myPort.readString();
if (serialDataString != null) {
serialDataString = trim(serialDataString);
float[] sensorData = float(split(serialDataString, ','));
print(sensorData);
// check if 3 values are received
if(sensorData.length == 3){
temperature = sensorData[0];
humidity = sensorData[1];
moisture = sensorData[2];
// add data to table (using column names)
TableRow newRow = sensorDataTable.addRow();
newRow.setFloat("temperature", temperature);
newRow.setFloat("humidity" , humidity);
newRow.setFloat("moisture" , moisture);
}else{
println("received <3 values");
}
}
}
}
// save on 's'
void keyPressed(){
if(key == 's'){
// save table to disk as .TSV instead of .CSV
// potentially avoiding .CSV and float values conflicts (depending on sys.language)
saveTable(sensorDataTable, "data/sensorData.tsv");
}
}
// save on exit
void exit(){
saveTable(sensorDataTable, "data/sensorData.tsv");
super.exit();
}