The Price of Tea in China

Made by Robert Zacharias

The Price of Tea in China physically represents the relationship (or lack of relationship) between different pieces of data around and outside of our world.

Created: December 14th, 2015

0
Price of Tea in China project
bobbyzacharias - https://www.youtube.com/watch?v=Nf1VU2duIEg
0

The Price of Tea in China displays data that's gathered from different sources around the web to show the changing relationships, or non-relationships, between: the value of the Euro, the velocity of the solar wind, the temperature of the ocean in Prudhoe Bay, Alaska, the value of of the Dominican Peso, the air pressure on the surface of Mars, and of course the price of tea in China.

As these things rise and fall, small tokens representing them rise and fall as well.

All of these data points are available freely on the web. What a strange thing to live in a time when you can find out updated answers to burning questions like "what's the air pressure doing on Mars these days" by fiddling with your hand-sized computer for a few moments!

The piece also begs, implicitly, for the viewer to interpret and interrelate the various data streams shown. Some surprising relationships between disparate data may be actually valid: for instance, high velocity solar winds can impair satellite communication and affect economic variables on Earth. But what might the air pressure on Mars have to do with the price of tea in China? Probably not much. But in a quantumly entangled universe, who can say?

0
Data flowchart
0
The small red rock representing Martian air pressure. (Actually a brick fragment found on the ground.)
Img 8825.jpg.thumb
0
A five-Euro note, representing the value of that currency.
Img 8826.jpg.thumb
0
A lasercut sheet of 1/8" yellow acrylic in the form of the Smithsonian Institution's emblem. Representing the velocity of the solar wind.
Img 8829.jpg.thumb
0
A small acrylic container of water with a sandy bottom. Representing the temperature of the ocean in Prudhoe Bay, Alaska.
Img 8831.jpg.thumb
0
A fifty Peso note, representing the value of the Dominican Peso.
Img 8832.jpg.thumb
0
A teabag, representing the price of tea in China. (Or a stock quote from the Ten Ren Tea Co. of Taiwan as a proxy.)
Img 8828.jpg.thumb
0
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
0
Electronic schematic. I was pleasantly surprised to find that a power supply of less than an amp at 12V is sufficient to run 6 stepper motors.
Price of tea in china schematic.thumb / public domain
x
Share this Project

Courses

48-739 Making Things Interactive

· 0 members

Making Things Interactive (MTI) is a studio course based on physical prototyping and computing. You will develop novel sensing, interaction and display techniques through projects and short assignm...more


About

The Price of Tea in China physically represents the relationship (or lack of relationship) between different pieces of data around and outside of our world.