Back to Parent

Processing and Arduino code that the system runs. (Please note the Arduino code is included below the Processing code, commented out.)
/*

Price of Tea in China artwork

An Arduino drives six stepper motors, each with a different physical token dangling from a monofilament
line on a reel. The tokens ride up and down as the real-world data changes value. This sketch fetches
the data from different APIs, scales it appropriately for the stepper motor positions of all six of
the remote motors, formats it for appropriate transmission, and sends the data via serial connection.

There are a few keyboard commands that can be employed to adjust the positions of the tokens, which
can be found in the void keyPressed() section at the bottom of the sketch.

The Arduino runs a companion sketch specially designed to parse the data this sketch sends it. That
sketch is included at the bottom of this one for reference.

Project for Making Things Interactive class taught by Jake Marsico at Carnegie Mellon University,
Pittsburgh, PA

code released to the public domain
Robert Zacharias, rz@rzach.me 10-27-15

*/

import processing.serial.*;
Serial myPort;

Table pesoTable;
Table euroTable;
Table nomeTable;
Table sanjuanTable;
Table solarwindTable;
Table teaTable;
JSONObject marsJSON;

// values used in floatToMotorPos function
float motorMax = 75000.0; //longest extension of motor string, in steps EMPIRICALLY FOUND FOR PHYS COMP LAB UNISTRUT INSTALLATION 10/23/15 TO BE ~75000
float motorMin = 1000.0; // shortest pull-in of motor string, in steps

long timer;
int wait = 1000*60*15; // fifteen minutes; this is the autoupdate interval

String inString;

void setup() {

  //println(Serial.list()); // for looking up the appropriate value for the next line's array index
  String portName = Serial.list()[2]; // bluetooth module on my laptop is 2
  myPort = new Serial(this, portName, 9600);

  // Using bluetooth, so give port time to set up? Seems necessary for reliable performance
  delay(5000);

  lookupandsendvalues("f"); // f for fast, so upon startup it goes to initial positions rapidly

  timer = millis();
}

void draw() {

  // update values every 15 minutes
  if (millis() - timer > wait) {
    lookupandsendvalues("h"); // h is "header" and runs motors at regular (very slow) speed
    timer = millis();
  }

  // for reading diagnostic data sent back from Arduino
  if ( myPort.available() > 0) {
    inString = myPort.readString();
    println(inString);
  }
}

void lookupandsendvalues(String speedPrefix) {
  
  float pesoVal, nomeVal, solarwindVal, teaVal, euroVal, marsVal;

  float pesoMin = 0.0217;
  float pesoMax = 0.0225;
  float nomeMin = 37.0;
  float nomeMax = 44.5;
  float solarwindMin = 350.0;
  float solarwindMax = 500.0;
  float teaMin = 42.0;
  float teaMax = 37.0;
  float euroMin = 1.08;
  float euroMax = 1.15;
  float marsMax = 950.0;
  float marsMin = 850.0;

  pesoTable = loadTable("https://www.kimonolabs.com/api/csv/ef0n8ira?apikey=JsgCimv5j9A5YO0zzpCRpql5wdpjoAOp", "csv");
  pesoVal = pesoTable.getFloat(2, 0);
  println("pesoVal = "+pesoVal);

  euroTable = loadTable("https://www.kimonolabs.com/api/csv/ef845qbw?apikey=JsgCimv5j9A5YO0zzpCRpql5wdpjoAOp", "csv");
  euroVal = euroTable.getFloat(2, 0);
  println("euroVal = "+euroVal);

  // this seems to conk out every once in a while, but I can't figure out how to handle the exception smoothly.
  // neither using try{} nor wrapping the thing in an if(nomeTable.getColumnCount() > 0 ) does the trick, because
  // both cause nomeVal to appear unitialized down at the port.write command (odd, because even when I initialize it
  // up at the top of the function it still doesn't matter?)
  nomeTable = loadTable("http://tidesandcurrents.noaa.gov/api/datagetter?station=9468756&date=latest&product=water_temperature&units=english&format=csv&time_zone=gmt", "csv");
  //try {
  nomeVal = nomeTable.getFloat(0, 1); // (row,column)
  println("nomeVal = "+nomeVal);
  //}
  //catch (Exception e)
  //{
  //}



  solarwindTable = loadTable("https://www.kimonolabs.com/api/csv/c44o2x24?apikey=JsgCimv5j9A5YO0zzpCRpql5wdpjoAOp", "csv");
  //if (solarwindTable.getColumnCount() > 0) { // in case it doesn't load, don't hang looking for a value that isn't there
  solarwindVal = solarwindTable.getFloat(2, 0);
  println("solarwindVal = "+solarwindVal);
  //}

  marsJSON = loadJSONObject("http://marsweather.ingenology.com/v1/latest/?format=json");
  JSONObject marsObject = marsJSON.getJSONObject("report");
  marsVal = marsObject.getFloat("pressure");
  println("marsVal = " + marsVal);

  teaTable = loadTable("https://www.kimonolabs.com/api/csv/c1a4707o?apikey=JsgCimv5j9A5YO0zzpCRpql5wdpjoAOp", "csv");
  teaVal = teaTable.getFloat(2, 0);
  println("teaVal = "+teaVal);

  myPort.write
    (speedPrefix+
    floatToMotorPos(pesoVal, pesoMin, pesoMax)+" "
    +floatToMotorPos(nomeVal, nomeMin, nomeMax)+" "
    +floatToMotorPos(solarwindVal, solarwindMin, solarwindMax)+" "
    +floatToMotorPos(teaVal, teaMin, teaMax)+" "
    +floatToMotorPos(euroVal, euroMin, euroMax)+" "
    +floatToMotorPos(marsVal, marsMin, marsMax)+"\n");

  println("timestamp "+hour()+":"+minute()+":"+second()+"\n");
}


int floatToMotorPos(float x, float in_min, float in_max) // small in value to long string, big in value to min (very short distance)
{
  return int(constrain(((x - in_min) * (motorMin - motorMax) / (in_max - in_min) + motorMax), motorMin, motorMax));
}

void keyPressed() {

  if (key == '0') {
    println("sending 0 command, tells the motors their current position is 0");
    myPort.write('0');
  }

  if (key == 's') {
    println("sending s command, telling all motors to return to their own zero at high speed");
    myPort.write('s');
  }

  if (key == 'p') {
    println("sending p command, pulling motors in exactly one revolution regardless of current position");
    myPort.write('p');
  }

  if (key == ' ') {
    println("manual update requested");
    lookupandsendvalues("h");
  }
}


/////////// Companion Arduino code below //////////


///*
//Price of Tea in China artwork

//An Arduino drives six stepper motors, each with a different physical token dangling from a monofilament
//line on a reel. The tokens ride up and down as the real-world data changes value. This sketch interprets
//properly formatted serial commands from a companion Processing sketch and moves all six motors at the speed
//indicated to the positions indicated.

//Project for Making Things Interactive class taught by Jake Marsico at Carnegie Mellon University,
//Pittsburgh, PA

//code released to the public domain
//Robert Zacharias, rz@rzach.me 10-27-15
//*/

//#include <AccelStepper.h>

//// AccelStepper mystepper(1, pinStep, pinDirection) for a full driver board like A4988
//AccelStepper peso(1, A4, A5);
//AccelStepper nome(1, 3, 2);
//AccelStepper solarwind(1, 5, 4);
//AccelStepper tea(1, 7, 6);
//AccelStepper euro(1, 9, 8);
//AccelStepper mars(1, 11, 10);

//long pesoPos;
//long nomePos;
//long solarwindPos;
//long teaPos;
//long euroPos;
//long marsPos;


//int slowSpeed = 50;
//int slowAccel = 50;
//int fastSpeed = 5000;
//int fastAccel = 5000;

//bool speedFlag = 0; // 0 is low speed and 1 is high speed; start slow by default

//void setup() {

//  Serial.begin(9600);

//  makeSpeed(speedFlag);

//}

//void loop() {

//  if (Serial.available() > 0) {

//    if (Serial.peek() == 'h') { // h for header; will be followed by 6 motor position values

//      Serial.read();

//      pesoPos = Serial.parseInt();
//      nomePos = Serial.parseInt();
//      solarwindPos = Serial.parseInt();
//      teaPos = Serial.parseInt();
//      euroPos = Serial.parseInt();
//      marsPos = Serial.parseInt();

//      Serial.print("read ");
//      Serial.print(pesoPos);
//      Serial.print(", ");
//      Serial.print(nomePos);
//      Serial.print(", ");
//      Serial.print(solarwindPos);
//      Serial.print(", ");
//      Serial.print(teaPos);
//      Serial.print(", ");
//      Serial.print(euroPos);
//      Serial.print(", ");
//      Serial.println(marsPos);

//      speedFlag = 0; // slow
//    }

//    if (Serial.peek() == 'f') { // f for fast; will be followed by 6 motor position values

//      Serial.read();

//      pesoPos = Serial.parseInt();
//      nomePos = Serial.parseInt();
//      solarwindPos = Serial.parseInt();
//      teaPos = Serial.parseInt();
//      euroPos = Serial.parseInt();
//      marsPos = Serial.parseInt();

//      Serial.print("proceeding fast to run motors to initial positions: ");
//      Serial.print(pesoPos);
//      Serial.print(", ");
//      Serial.print(nomePos);
//      Serial.print(", ");
//      Serial.print(solarwindPos);
//      Serial.print(", ");
//      Serial.print(teaPos);
//      Serial.print(", ");
//      Serial.print(euroPos);
//      Serial.print(", ");
//      Serial.println(marsPos);

//      speedFlag = 1; // fast

//    }

//    else if (Serial.peek() == 's') { // "soft zeroing" command just sets all positions to zero immediately so they'll run there
//      Serial.read();
//      Serial.println("Soft zeroing command received. Turning up motor speeds and running to zero (blocking).");

//      speedFlag = 1; // fast

//      peso.moveTo(0);
//      nome.moveTo(0);
//      solarwind.moveTo(0);
//      tea.moveTo(0);
//      euro.moveTo(0);
//      mars.moveTo(0);

//      pesoPos = 0;
//      nomePos = 0;
//      solarwindPos = 0;
//      teaPos = 0;
//      euroPos = 0;
//      marsPos = 0;

//      Serial.println("Soft zeroing command complete. All motors at 0.");
//    }

//    else if (Serial.peek() == '0') { // tells all the motors their current position is 0
//      Serial.read();
//      peso.setCurrentPosition(0);
//      nome.setCurrentPosition(0);
//      solarwind.setCurrentPosition(0);
//      tea.setCurrentPosition(0);
//      euro.setCurrentPosition(0);
//      mars.setCurrentPosition(0);
//      pesoPos = 0;
//      nomePos = 0;
//      solarwindPos = 0;
//      teaPos = 0;
//      euroPos = 0;
//      marsPos = 0;
//      Serial.println("Set current position to 0 command received. Current motor positions are all now 0.");
//    }

//    else if (Serial.peek() == 'p') { // pull the motors in, one revolution at a time (at high speed)
//      Serial.read();

//      Serial.println("Pull in command received. All motors pulling one revolution in.");

//      speedFlag = 1; // fast

//      peso.move(-3200);
//      nome.move(-3200);
//      solarwind.move(-3200);
//      tea.move(-3200);
//      euro.move(-3200);
//      mars.move(-3200);

//      pesoPos = 0;
//      nomePos = 0;
//      solarwindPos = 0;
//      teaPos = 0;
//      euroPos = 0;
//      marsPos = 0;

//      Serial.println("Pull in completed.");
//    }

//    else(Serial.read()); // if any other data comes through, ignore it
//  }

//  // the bottom of the loop, which actually runs the motors

//  makeSpeed(speedFlag);

//  peso.moveTo(pesoPos);
//  nome.moveTo(nomePos);
//  solarwind.moveTo(solarwindPos);
//  tea.moveTo(teaPos);
//  euro.moveTo(euroPos);
//  mars.moveTo(marsPos);

//  peso.run();
//  nome.run();
//  solarwind.run();
//  tea.run();
//  euro.run();
//  mars.run();

//}

//void makeSpeed(bool spd) {
//  if (spd == 0) { //if slow
//    setRegularSpeed();
//  }

//  else if (spd == 1) { // if fast
//    setFastSpeed();
//  }
//}

//void setRegularSpeed() {
//  peso.setMaxSpeed(slowSpeed);
//  nome.setMaxSpeed(slowSpeed);
//  solarwind.setMaxSpeed(slowSpeed);
//  tea.setMaxSpeed(slowSpeed);
//  euro.setMaxSpeed(slowSpeed);
//  mars.setMaxSpeed(slowSpeed);

//  peso.setAcceleration(slowAccel);
//  nome.setAcceleration(slowAccel);
//  solarwind.setAcceleration(slowAccel);
//  tea.setAcceleration(slowAccel);
//  euro.setAcceleration(slowAccel);
//  mars.setAcceleration(slowAccel);
//}

//void setFastSpeed() {
//  peso.setMaxSpeed(fastSpeed);
//  nome.setMaxSpeed(fastSpeed);
//  solarwind.setMaxSpeed(fastSpeed);
//  tea.setMaxSpeed(fastSpeed);
//  euro.setMaxSpeed(fastSpeed);
//  mars.setMaxSpeed(fastSpeed);

//  peso.setAcceleration(fastAccel);
//  nome.setAcceleration(fastAccel);
//  solarwind.setAcceleration(fastAccel);
//  tea.setAcceleration(fastAccel);
//  euro.setAcceleration(fastAccel);
//  mars.setAcceleration(fastAccel);
//}
/ released to the public domain Click to Expand

Content Rating

Is this a good/useful/informative piece of content to include in the project? Have your say!

0