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());
}
}
}
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:
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.
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.
Related
I am trying to run this Processing code taken from this website to create a wavelength meter.
/***************************************************************************
Created by dbc0301
***************************************************************************/
import processing.serial.*;
Serial port;
PFont myFont;
int tmp;
//int begin='$';//begin
int end='\r';//end
char rev[] = new char[15];//datas
int revFlag=0;
int[] data=new int[10];//F1,F2,F3,F4,F5,F6,F7,F8,Clear,NIR
//int F1,F2,F3,F4,F5,F6,F7,F8,Clear,NIR;
//Stimulus Function
float[] Fx={0.07763, 0.34806, 0.09564, 0.02910, 0.51205, 1.02630, 0.64240, 0.04677};
float[] Fy={0.00218, 0.02980, 0.13902, 0.60820, 1.00000, 0.75700, 0.26500, 0.01700};
float[] Fz={0.37130, 1.78260, 0.81295, 0.11170, 0.00575, 0.00110, 0.00005, 0.00000};
int textHight=25;
float rt=1;//Length scaling
void receiveDatas(){
for(int i=0;port.available()>0;i++){
tmp=port.read();
if(tmp!=end){
rev=char(tmp);
}else{
rev=char(tmp);
revFlag=1;
tmp=port.read();
break;
}
}
}
void setup(){
//size(1074,241);
size(1250,301);
background(0);//white255 black0
noStroke();
myFont = createFont("Microsoft YaHei", 20);
textFont(myFont);
println(Serial.list()[0]);
port = new Serial(this,Serial.list()[0],115200);
}
void draw(){
receiveDatas();
if(revFlag==1){
String[] m=match(new String(rev), "(.*?):(.*?)\r");//Regular Expression Matching
//printArray(m);
try{
if(m[1].equals("F1")){
data[0]=int(m[2]);
}else if(m[1].equals("F2")){
data[1]=int(m[2]);
}else if(m[1].equals("F3")){
data[2]=int(m[2]);
}else if(m[1].equals("F4")){
data[3]=int(m[2]);
}else if(m[1].equals("F5")){
data[4]=int(m[2]);
}else if(m[1].equals("F6")){
data[5]=int(m[2]);
}else if(m[1].equals("F7")){
data[6]=int(m[2]);
}else if(m[1].equals("F8")){
data[7]=int(m[2]);
}else if(m[1].equals("Clear")){
data[8]=int(m[2]);
}else if(m[1].equals("NIR")){
data[9]=int(m[2]);
}else{
print("Wrong datas!");
}
}catch(NullPointerException e){
println(rev);
printArray(m);
}finally{}
revFlag=0;
//printArray(data);
//delay(10);
}
/*Display*/
//rectMode(CORNER);
background(0);
textSize(20);
fill(#8b3dc5);
rect(0,0,data[0]*rt,30);//F1
text(data[0], data[0]*rt, 0+textHight);//textHeight: veritcal height of text
fill(#00528e);
rect(0,30,data[1]*rt,30);//F2
text(data[1], data[1]*rt, 30+textHight);
fill(#00b1ed);
rect(0,60,data[2]*rt,30);//F3
text(data[2], data[2]*rt, 60+textHight);
fill(#01ffcd);
rect(0,90,data[3]*rt,30);//F4
text(data[3], data[3]*rt, 90+textHight);
fill(#00af50);
rect(0,120,data[4]*rt,30);//F5
text(data[4], data[4]*rt, 120+textHight);
fill(#ffff01);
rect(0,150,data[5]*rt,30);//F6
text(data[5], data[5]*rt, 150+textHight);
fill(#ffc000);
rect(0,180,data[6]*rt,30);//F7
text(data[6], data[6]*rt, 180+textHight);
fill(#c10005);
rect(0,210,data[7]*rt,30);//F8
text(data[7], data[7]*rt, 210+textHight);
fill(#ffffff);
rect(0,240,data[8]*rt,30);//Clear
text(data[8], data[8]*rt, 240+textHight);
fill(#888888);
rect(0,270,data[9]*rt,30);//F8
text(data[9], data[9]*rt, 270+textHight);
//delay(1);
/*Color Temperaure*/
float X,Y,Z,x,y,n,temp;
X=(data[0]*Fx[0] + data[1]*Fx[1] + data[2]*Fx[2] + data[3]*Fx[3] + data[4]*Fx[4] + data[5]*Fx[5] + data[6]*Fx[6] + data[7]*Fx[7]);//20/1000;//20 is the interval of integration. 1000 is used to converted the value into a real number between 0~1
Y=(data[0]*Fy[0] + data[1]*Fy[1] + data[2]*Fy[2] + data[3]*Fy[3] + data[4]*Fy[4] + data[5]*Fy[5] + data[6]*Fy[6] + data[7]*Fy[7]);//20/1000;//But it is meaningless to multiply this number here since it will be reduced in the next step
Z=(data[0]*Fz[0] + data[1]*Fz[1] + data[2]*Fz[2] + data[3]*Fz[3] + data[4]*Fz[4] + data[5]*Fz[5] + data[6]*Fz[6] + data[7]*Fz[7]);//20/1000;
x=X/(X+Y+Z);
y=Y/(X+Y+Z);
n=(x-0.3320)/(0.1858-y);
temp=437*n*n*n+3601*n*n+6831*n+5517;
print(temp);
println("K\n");
}
It is giving me this error on line 24 and 26:
Type mismatch, “char” does not match with “char[]”
The problem is that rev is an array. If you want to put tmp in rev, do something like rev[0] = char(tmp). I don't understand well your code, but if you want every letter you press to be added to tmp, you could make a variable to take count of how much letters were pressed, and do rev[typed] = char(tmp) where typed is the variable in question. Also, this code will probably confuse most of the community, go to discourse.processing.org to ask Processing questions. Hope i could help, though!
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:
instantiate a table (and optionally its columns)
when new data arrives, make a new TableRow, populate it with the 3 values and add it to the table
when required (e.g. exiting sketch / pressing '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();
}
GAS is quite powerful and you could write a full fledged web-app using a Google Sheet as the DB back-end. There are many reasons not to do this but I figure in some cases it is okay.
I think the biggest issue will be performance issues when looking for rows based on some criteria in a sheet with a lot of rows. I know there are many ways to "query" a sheet but I can't find reliable information on which is the fastest.
One of the complexities is that many people can edit a sheet which means there are a variable number of situations you'd have to account for. For the sake of simplicity, I want to assume the sheet:
Is locked down so only one person can see it
The first column has the row number (=row())
The most basic query is finding a row where a specific column equals some value.
Which method would be the fastest?
I have a sheet with ~19k rows and ~38 columns, filled with all sorts of unsorted real-world data. That is almost 700k rows so I figured it would be a good sheet to time a few methods and see which is the fastest.
method 1: get sheet as a 2D array then go through each row
method 2: get sheet as a 2D array, sort it, then using a binary search algorithm to find the row
method 3: make a UrlFetch call to Google visualization query and don't provide last row
method 4: make a UrlFetch call to Google visualization query and provide last row
Here are the my query functions.
function method1(spreadsheetID, sheetName, columnIndex, query)
{
// get the sheet values excluding header,
var rowValues = SpreadsheetApp.openById(spreadsheetID).getSheetByName(sheetName).getSheetValues(2, 1, -1, -1);
// loop through each row
for(var i = 0, numRows = rowValues.length; i < numRows; ++i)
{
// return it if found
if(rowValues[i][columnIndex] == query) return rowValues[i]
}
return false;
}
function method2(spreadsheetID, sheetName, columnIndex, query)
{
// get the sheet values excluding header
var rowValues = SpreadsheetApp.openById(spreadsheetID).getSheetByName(sheetName).getSheetValues(2, 1, -1, -1);
// sort it
rowValues.sort(function(a, b){
if(a[columnIndex] < b[columnIndex]) return -1;
if(a[columnIndex] > b[columnIndex]) return 1;
return 0;
});
// search using binary search
var foundRow = matrixBinarySearch(rowValues, columnIndex, query, 0, rowValues.length - 1);
// return if found
if(foundRow != -1)
{
return rowValues[foundRow];
}
return false;
}
function method3(spreadsheetID, sheetName, queryColumnLetterStart, queryColumnLetterEnd, queryColumnLetterSearch, query)
{
// SQL like query
myQuery = "SELECT * WHERE " + queryColumnLetterSearch + " = '" + query + "'";
// the query URL
// don't provide last row in range selection
var qvizURL = 'https://docs.google.com/spreadsheets/d/' + spreadsheetID + '/gviz/tq?tqx=out:json&headers=1&sheet=' + sheetName + '&range=' + queryColumnLetterStart + ":" + queryColumnLetterEnd + '&tq=' + encodeURIComponent(myQuery);
// fetch the data
var ret = UrlFetchApp.fetch(qvizURL, {headers: {Authorization: 'Bearer ' + ScriptApp.getOAuthToken()}}).getContentText();
// remove some crap from the return string
return JSON.parse(ret.replace("/*O_o*/", "").replace("google.visualization.Query.setResponse(", "").slice(0, -2));
}
function method4(spreadsheetID, sheetName, queryColumnLetterStart, queryColumnLetterEnd, queryColumnLetterSearch, query)
{
// find the last row in the sheet
var lastRow = SpreadsheetApp.openById(spreadsheetID).getSheetByName(sheetName).getLastRow();
// SQL like query
myQuery = "SELECT * WHERE " + queryColumnLetterSearch + " = '" + query + "'";
// the query URL
var qvizURL = 'https://docs.google.com/spreadsheets/d/' + spreadsheetID + '/gviz/tq?tqx=out:json&headers=1&sheet=' + sheetName + '&range=' + queryColumnLetterStart + "1:" + queryColumnLetterEnd + lastRow + '&tq=' + encodeURIComponent(myQuery);
// fetch the data
var ret = UrlFetchApp.fetch(qvizURL, {headers: {Authorization: 'Bearer ' + ScriptApp.getOAuthToken()}}).getContentText();
// remove some crap from the return string
return JSON.parse(ret.replace("/*O_o*/", "").replace("google.visualization.Query.setResponse(", "").slice(0, -2));
}
My binary search algorithm:
function matrixBinarySearch(matrix, columnIndex, query, firstIndex, lastIndex)
{
// find the value using binary search
// https://www.w3resource.com/javascript-exercises/javascript-array-exercise-18.php
// first make sure the query string is valid
// if it is less than the smallest value
// or larger than the largest value
// it is not valid
if(query < matrix[firstIndex][columnIndex] || query > matrix[lastIndex][columnIndex]) return -1;
// if its the first row
if(query == matrix[firstIndex][columnIndex]) return firstIndex;
// if its the last row
if(query == matrix[lastIndex][columnIndex]) return lastIndex;
// now start doing binary search
var middleIndex = Math.floor((lastIndex + firstIndex)/2);
while(matrix[middleIndex][columnIndex] != query && firstIndex < lastIndex)
{
if(query < matrix[middleIndex][columnIndex])
{
lastIndex = middleIndex - 1;
}
else if(query > matrix[middleIndex][columnIndex])
{
firstIndex = middleIndex + 1;
}
middleIndex = Math.floor((lastIndex + firstIndex)/2);
}
return matrix[middleIndex][columnIndex] == query ? middleIndex : -1;
}
This is the function I used to test them all:
// each time this function is called it will try one method
// the first time it is called it will try method1
// then method2, then method3, then method4
// after it does method4 it will start back at method1
// we will use script properties to save which method is next
// we also want to use the same query string for each batch so we'll save that in script properties too
function testIt()
{
// get the sheet where we're staving run times
var runTimesSheet = SpreadsheetApp.openById("...").getSheetByName("times");
// we want to see true speed tests and don't want server side caching so we a copy of our data sheet
// make a copy of our data sheet and get its ID
var tempSheetID = SpreadsheetApp.openById("...").copy("temp sheet").getId();
// get script properties
var scriptProperties = PropertiesService.getScriptProperties();
// the counter
var searchCounter = Number(scriptProperties.getProperty("searchCounter"));
// index of search list we want to query for
var searchListIndex = Number(scriptProperties.getProperty("searchListIndex"));
// if we're at 0 then we need to get the index of the query string
if(searchCounter == 0)
{
searchListIndex = Math.floor(Math.random() * searchList.length);
scriptProperties.setProperty("searchListIndex", searchListIndex);
}
// query string
var query = searchList[searchListIndex];
// save relevant data
var timerRow = ["method" + (searchCounter + 1), searchListIndex, query, 0, "", "", "", ""];
// run the appropriate method
switch(searchCounter)
{
case 0:
// start time
var start = (new Date()).getTime();
// run the query
var ret = method1(tempSheetID, "Extract", 1, query);
// end time
timerRow[3] = ((new Date()).getTime() - start) / 1000;
// if we found the row save its values in the timer output so we can confirm it was found
if(ret)
{
timerRow[4] = ret[0];
timerRow[5] = ret[1];
timerRow[6] = ret[2];
timerRow[7] = ret[3];
}
break;
case 1:
var start = (new Date()).getTime();
var ret = method2(tempSheetID, "Extract", 1, query);
timerRow[3] = ((new Date()).getTime() - start) / 1000;
if(ret)
{
timerRow[4] = ret[0];
timerRow[5] = ret[1];
timerRow[6] = ret[2];
timerRow[7] = ret[3];
}
break;
case 2:
var start = (new Date()).getTime();
var ret = method3(tempSheetID, "Extract", "A", "AL", "B", query);
timerRow[3] = ((new Date()).getTime() - start) / 1000;
if(ret.table.rows.length)
{
timerRow[4] = ret.table.rows[0].c[0].v;
timerRow[5] = ret.table.rows[0].c[1].v;
timerRow[6] = ret.table.rows[0].c[2].v;
timerRow[7] = ret.table.rows[0].c[3].v;
}
break;
case 3:
var start = (new Date()).getTime();
var ret = method3(tempSheetID, "Extract", "A", "AL", "B", query);
timerRow[3] = ((new Date()).getTime() - start) / 1000;
if(ret.table.rows.length)
{
timerRow[4] = ret.table.rows[0].c[0].v;
timerRow[5] = ret.table.rows[0].c[1].v;
timerRow[6] = ret.table.rows[0].c[2].v;
timerRow[7] = ret.table.rows[0].c[3].v;
}
break;
}
// delete the temp file
DriveApp.getFileById(tempSheetID).setTrashed(true);
// save run times
runTimesSheet.appendRow(timerRow);
// start back at 0 if we're the end
if(++searchCounter == 4) searchCounter = 0;
// save the search counter
scriptProperties.setProperty("searchCounter", searchCounter);
}
I have a global variable searchList that is an array of various query strings -- some are in the sheet, some are not.
I ran testit on a trigger to run every minute. After 152 iterations I had 38 batches. Looking at the result, this is what I see for each method:
| Method | Minimum Seconds | Maximum Seconds | Average Seconds |
|---------|-----------------|-----------------|-----------------|
| method1 | 8.24 | 36.94 | 11.86 |
| method2 | 9.93 | 23.38 | 14.09 |
| method3 | 1.92 | 5.48 | 3.06 |
| method4 | 2.20 | 11.14 | 3.36 |
So it appears that, at least for my data-set, is using Google visualization query is the fastest.
so I am new to Processing and basically I am doing a program that when it runs, it opens a window with 4 different images, each of the image have description underneath. In the methods below, I created two random methods, one for the reviews number and the other for the review comments, I would like the comments to not be generated all the time for every film - more like popping up randomly, because it cause too much chaos trying to read them all. Also to check weather I can add arrays together of string and integer for the average value of the review number.
Below is the code, would appreciate your help. thanks.
import g4p_controls.*;
import ddf.minim.*;
PFont font;
PImage img1,img2; // background images for two different windows
PImage fimg1, fimg2, fimg3, fimg4, fimg5, fimg6; //images of movies
int rectX,rectY;
GButton btn;
GWindow window;
Minim minim;
AudioPlayer player;
String[] rev_film1 = {"The Best Wolverine Movie","Logan is another level","Disappointment","Such a sad farewell"}; //Logan
String[] rev_film2 = {"A scary movie that isn't scary.","Terrifyingly brilliant.","The perfect blend of comedy and horror","The IT Factor"}; //IT
String[] rev_film3 = {"Soul-less,Confused,Loud.","Devastatingly Disappointed","A technical masterpiece","A visual wonder that lacks depth"}; //Dunkirk
String[] rev_film4 = {"Disgrace", "Worst Star Wars movie", "TERRIBLE","A Betrayal to the Legacy"}; //Starwars
int[] rat_film1 = {9,8,2,2};
float r;
void setup()
{
size(1150,600,JAVA2D);
surface.setTitle(" - The Theatre of Dreams Database - ");
font = loadFont("BerlinSansFB-Reg-48.vlw");
img1 = loadImage("film2.jpg");
background(img1);
btn = new GButton(this,900,350,100,50, "Enter Website");
minim = new Minim(this);
player = minim.loadFile("sound.mp3");
player.play();
}
void draw()
{
fill(255);
textFont(font,32);
text("Welcome to", 850, 85);
text("The Theatre of Dreams", 760, 175);
text("Database", 870, 220);
}
void handleButtonEvents(GButton button, GEvent event)
{
if (button == btn && event == GEvent.CLICKED)
{
createWindow();
btn.setEnabled(false);
}
}
void createWindow() // creating new window with mouse click
{
window = GWindow.getWindow(this, " - Top 4 Movies in 2017 - ", 100, 50, 1150, 600, JAVA2D);
window.addDrawHandler(this, "windowDraw");
window.addOnCloseHandler(this, "windowClosing");
window.setActionOnClose(GWindow.CLOSE_WINDOW);
}
void windowDraw(PApplet app, GWinData data)
{
img2 = loadImage("film3.jpg");
app.background(img2);
app.text(" - Top 4 Movies in 2017 - ",440,85);
app.fill(255);
app.textFont(font,25);
fimg1 = loadImage("logan.jpg");
fimg2 = loadImage("it.jpg");
fimg3 = loadImage("dunkirk.jpg");
fimg4 = loadImage("starwars.jpg");
//////////Film 1 - LOGAN
app.image(fimg1,5,140,180,170);
app.text("Rating: 8.1 / 10",5,340); //fixed rating
app.text("Genres: Action | Drama", 5, 365);
app.text("| Sci-Fi | Thriller",5,390);
//Ratings that are constantly changing using the random function
for (int i = 0; i < 50; i++)
{
r = random(0, 6);
}
String user = "Ratings by users: " + nf(r,0,1) + " / 10";
app.text(user, 5,430);
// the random function of the comments
int index = int(random(rev_film1.length));
String user11 = "Reviews: " + "\n" + rev_film1[index];
app.text(user11, 5,460);
////////////////////Film 2 - IT
app.image(fimg2,960,360,180,170);
app.text("Rating: 7.6 / 10", 700,400);
app.text("Genres: Drama | Horror",700,430);
app.text("| Thriller",700,460);
//Ratings that are constantly changing using the random function
for (int i = 0; i < 50; i++)
{
r = random(5, 10);
}
String user2 = "Ratings by users: " + nf(r,0,1) + " / 10";
app.text(user2, 700,500);
int index2 = int(random(rev_film2.length)); // the random function of the comments
String user22 = "Reviews: " + "\n" + rev_film2[index2];
app.text(user22, 700,540);
/////////Film 3 - DUNKIRK
app.image(fimg3,320,250,180,170);
app.text("Rating: 8.1 / 10",320,445); //fixed rating
app.text("Genres: Action | Drama", 320, 470);
app.text("| History | Thriller | War",320,495);
//Ratings that are constantly changing using the random function
for (int i = 0; i < 50; i++)
{
r = random(0, 5);
}
String user3 = "Ratings by users: " + nf(r,0,1) + " / 10";
app.text(user3, 320,530);
int index3 = int(random(rev_film3.length)); // the random function of the comments
String user33 = "Reviews: " + "\n" + rev_film3[index3];
app.text(user33, 320,560);
/////////////Film 4 - STAR WARS
app.image(fimg4,570,120,180,170);
app.text("Rating: 7.6 / 10", 760,140); //fixed rating
app.text("Genres: Action | Adventure | Fantasy ", 760,168);
app.text("| Sci-Fi", 760,195);
//Ratings that are constantly changing using the random function
for (int i = 0; i < 50; i++)
{
r = random(0, 2);
}
String user4 = "Ratings by users: " + nf(r,0,1) + " / 10";
app.text(user4, 760,220);
int index4 = int(random(rev_film4.length)); // the random function of the comments
String user44 = "Reviews: " + "\n" + rev_film4[index4];
app.text(user44, 760,250);
}
public void windowClosing(GWindow w)
{
btn.setEnabled(false);
player.close();
minim.stop();
exit();
}
Please try to post a MCVE instead of your full program. For example, try creating a simple sketch that shows a circle every X seconds. That way we can focus on your problem instead of all the extra stuff that has nothing to do with your question.
But to answer your question, you can use the millis() function or the frameCount variable to check how much time has gone by, then do something every X seconds or every X frames.
Related posts:
How to make a delay in processing project?
How can I draw only every x frames?
Removing element from ArrayList every 500 frames
Timing based events in Processing
How to add +1 to variable every 10 seconds in Processing?
How to create something happen when time = x
making a “poke back” program in processing
Processing: How do i create an object every “x” time
Timer using frameRate and frame counter reliable?
Please also consult the Processing reference for more information.
If you still can't get it working, please post a MCVE (not your full project!) in a new question and we'll go from there. Good luck.
This is code to run automated blinds. However, it appears that there is some memory leak going on inside of loop as my program will not run. I am using XOBXOB to input data. Look below for where it really breaks.
void loop()
{
Serial.println("loop");
// New XOB request every 4 seconds (if previous response has been received)
if (lastResponseReceived && (abs(millis() - lastRequestTime) > 4*1000)) {
// if the connection has dropped, reconnect
while (!XOB.connected()) XOB.connect();
// Reset timer and response flags. Then, request "XOB" contents
lastResponseReceived = false;
lastRequestTime = millis();
Serial.println("requesting xob");
//enter name of XOB
XOB.requestXOB("XOB");
Serial.println("XOB requested!");
}
// Load response a character at a time when it is available.
// If loadStreamedResponse returns true, a completed response has been received
// Get the "switch" message from the XOB and turn the LED on/off
// NOTE: The message contents are returned with quotes. So, include them
// in the comparison statement.
// Serial.println("LSR: ");
// Serial.print(XOB.loadStreamedResponse());
This is where the loop does not run. It seems that the stream of data from XOBXOB cannot be stored and therefore loadStreamedResponse returns false.
if (!lastResponseReceived && XOB.loadStreamedResponse()) {
Serial.println("requesting reponse");
lastResponseReceived = true;
Serial.println("Get Xob Messages");
//Gets the string from value.
String str_value = XOB.getMessage("value");
Serial.println(str_value);
//Gets the string from switch
String str_switch = XOB.getMessage("switch");
Serial.println(str_switch);
String str_text = XOB.getMessage("text");
Serial.println(str_text);
//If time is not set, get the time from text xob.
if(time_set == false) {
Serial.println("setting time");
// insert code to parse time and set it from the text xob.
time_set = true;
}
// parse the hour from value and convert to minute for wake hour
String string_hour = str_value.substring(1,3);
int wake_hour = string_hour.toInt();
//Serial.println(wake_hour);
// parse minute from value and convert to int for wake minute
String string_minute = str_value.substring(3,5);
int wake_minute = string_minute.toInt();
// Serial.println(wake_minute);
// compare wake_minute and wake_hour with current time
// convert both to minutes for comparison
int wake_time = wake_minute + (60 * wake_hour);
int current_time = 738; //minute() + (hour() * 60);
Serial.println("checking wake condtions");
// rise up blinds slowly if the wake time is + or - 1 minute and if the blinds are closed.
if((wake_time == current_time || wake_time == (current_time - 1) || wake_time == (current_time + 1))
&& blinds_open == false ) {
Serial.println("Natural Wakeup");
rollup_blinds_slow();
blinds_open = true; // blinds should be open now.
}
// Check for opening up blinds condition. Blinds must be closed, to open them
Serial.println("Checking Open Condition");
if (str_switch.equalsIgnoreCase("\"ON\"") && blinds_open == false) {
Serial.println("Blinds Up");
rollup_blinds_fast();
blinds_open = true;
Serial.println(blinds_open);
}
Serial.println("Check closing condition");
// Check for closing blinds condition. Blinds must be closed, to open them
if (str_switch.equalsIgnoreCase("\"OFF\"") && blinds_open == true) {
Serial.println("Blinds down");
rolldown_blinds_fast();
blinds_open = false;
}
}