Crit 3: Visual

For the Visual Crit, I used Arduino, Rhino, Grasshopper, and GH plug-in Firefly to visualize sensor data over a topographical landscape. The topography model was initially generated digitally with Rhino and Grasshopper, so it could be used later in the model visualization. Contours were exported to 2d CAD to cut the physical model.

The physical model was constructed of lasercut chipboard, with enough variation in height to imitate a landscape that could plausibly have multiple microclimates, which can form due to differences in light, drainage, and airflow. Changes in topography can drive these shifts in environmental conditions. Light sensors will installed in different areas of the model.

Uploaded to the arduino is this Firmata code (which has been edited since the 2015 version to account for some differences in Arduino):

/* 
 Created by Andrew Payne and Jason Kelly Johnson
 Latest Update March 25th, 2015 
 Copyright 2015 | All Rights Reserved
 
 This Firmata allows you to control an Arduino board from Rhino/Grasshopper/Firefly.
 Updates, Questions, Suggestions visit: http://www.fireflyexperiments.com
 
 1. Plug Arduino boards into your USB port; confirm that your Arduino's green power LED in on
 2. Select your specific Arduino Board and Serial Port (Tools > Board; Tools > Serial Port) *Take note of your Serial Port COM #
 3. Verify (play button) and Upload (upload button) this program to your Arduino, close the Arduino program
 4. then open ... Rhino/Grasshopper/Firefly
 
 Note: The Firefly Firmata sets the following pins to perform these functions:
 
 *****ON STANDARD BOARDS (ie. Uno, Diecimila, Duemilanove, Lillypad, Mini, etc.)*****
 ANALOG IN pins 0-5 are set to return values (from 0 to 1023) for analog sensors
 DIGITAL IN pins 2,4,7 will return 0's or 1's; for 3 potential digital sensors (buttons, switches, on/off, true/false, etc.)
 DIGITAL/ANALOG OUT pins 3,5,6,11 (marked with a ~) can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 8,9,10,12,13 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 
 *****ON MEGA BOARDS (ie. ATMEGA1280, ATMEGA2560)*****
 ANALOG IN pins 0-15 will return values (from 0 to 1023) for 16 analog sensors 
 DIGITAL IN pins 22-31 will return 0's or 1's; for digital sensors (buttons, switches, on/off, true/false, etc.) 
 DIGITAL/ANALOG OUT pins 2-13 can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 32-53 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 
 *****ON LEONARDO BOARDS*****
 ANALOG IN pins 0-5 are set to return values (from 0 to 1023) for analog sensors
 DIGITAL IN pins 2,4,7 will return 0's or 1's; for 3 potential digital sensors (buttons, switches, on/off, true/false, etc.)
 DIGITAL/ANALOG OUT pins 3,5,6,11 (marked with a ~) can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 8,9,10,12,13 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 
  *****ON DUE BOARDS (ie. SAM3X8E)*****
 ANALOG IN pins 0-11 will return values (from 0 to 4095) for 12 analog sensors 
 DIGITAL IN pins 22-31 will return 0's or 1's; for digital sensors (buttons, switches, on/off, true/false, etc.) 
 DIGITAL/ANALOG OUT pins 2-13 can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 32-53 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 DAC0 and DAC1 can be used to output an analog voltage on those pins (only available on DUE boards)
 */

#include <Servo.h>            // attach Servo library (http://www.arduino.cc/playground/ComponentLib/Servo)
#include <pins_arduino.h>     // attach arduino pins header file to determine which board type is being used

#define BAUDRATE 115200       // Set the Baud Rate to an appropriate speed
#define BUFFSIZE 512          // buffer one command at a time

/*==============================================================================
 * GLOBAL VARIABLES
 *============================================================================*/

char buffer[BUFFSIZE];        // declare buffer
uint8_t bufferidx = 0;        // a type of unsigned integer of length 8 bits
char *parseptr;
char buffidx;

int counter = 0;
int numcycles = 1000;

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)                                                // declare variables for STANDARD boards
  Servo Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3;
  Servo SERVO_CONFIG[] = {Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3};       // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {13,12,11,10,9,8,6,5,3}; 
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5};
  int READ_DPIN_CONFIG[] = {2,4,7}; 
#endif

#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)                                               // declare variables for LEONARDO board
  Servo Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3;       
  Servo SERVO_CONFIG[] = {Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3};       // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {13,12,11,10,9,8,6,5,3}; 
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5};
  int READ_DPIN_CONFIG[] = {2,4,7};
#endif

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)                        // declare variables for MEGA boards
  Servo Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53;
  Servo SERVO_CONFIG[] = {Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53};  // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {2,3,4,5,6,7,8,9,10,11,12,13,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53}; 
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
  int READ_DPIN_CONFIG[] = {22,23,24,25,26,27,28,29,30,31};
#endif

#if defined(__SAM3X8E__)                 // declare variables for DUE boards
  Servo FDAC0, FDAC1, Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53;  
  Servo SERVO_CONFIG[] = {FDAC0, FDAC1, Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53};  // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53}; //Note: first two values correspond to the DAC pins
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5,6,7,8,9,10,11};
  int READ_DPIN_CONFIG[] = {22,23,24,25,26,27,28,29,30,31};
#endif

/*==============================================================================
 * SETUP() This code runs once
 *============================================================================*/
void setup()
{ 
  Init();                       //set initial pinmodes
  Serial.begin(BAUDRATE);       // Start Serial communication
  #if defined(__SAM3X8E__)      //if the connected board is an Arduino DUE
    analogReadResolution(12);   //Set the analog read resolution to 12 bits (acceptable values between 1-32 bits).  This is only for DUE boards
    analogWriteResolution(12);  // Set the analog write resolution to 12 bits (acceptable values between 1-32 bits).  This is only for DUE boards
  #endif
}

/*==============================================================================
 * LOOP() This code loops
 *============================================================================*/
void loop()
{
  if(Serial){
    ReadSerial();                       // read and parse string from serial port and write to pins
    if (counter >= numcycles){          // Wait every nth loop 
      ReadInputs();                     // get input data and print data to the serial port
      counter = 0;                      // reset the counter
    }
    counter ++;                         // increment the writecounter
  }
}

/*==============================================================================
 * FUNCTIONS()
 *============================================================================*/

/*
* Initializes the digital pins which will be used as inputs
*/
void Init(){
  int len = sizeof(READ_DPIN_CONFIG)/sizeof(READ_DPIN_CONFIG[0]); //get the size of the array
  for(int i = 0; i < len; i++){
    pinMode(READ_DPIN_CONFIG[i], INPUT);
  }
}

/* 
* Reads the incoming ADC or digital values from the corresponding analog and digital input  
* pins and prints the value to the serial port as a formatted commma separated string
*/
void ReadInputs(){ 
  int len = sizeof(READ_APIN_CONFIG)/sizeof(READ_APIN_CONFIG[0]); //get the size of the array
  for(int i = 0; i < len; i++){
    int val = analogRead(READ_APIN_CONFIG[i]);  //read value from analog pins
    Serial.print(val); Serial.print(",");   
  }
  len = sizeof(READ_DPIN_CONFIG)/sizeof(READ_DPIN_CONFIG[0]); //get the size of the array
  for(int i = 0; i < len; i++){
    int val = digitalRead(READ_DPIN_CONFIG[i]); //read value from digital pins
    Serial.print(val); Serial.print(",");   
  }
  Serial.println("eol");  //end of line marker
}

/*
* Retrieve the latest incoming serial value and split the string at the comma delimeter.
* When a comma is found, the value is offloaded to a temporary variable and written
* to the corresponding digital pin.
*/
void ReadSerial(){
  char c;    // holds one character from the serial port
  if (Serial.available()) {
    c = Serial.read();         // read one character
    buffer[bufferidx] = c;     // add to buffer
    if (c == '\n') {  
      buffer[bufferidx+1] = 0; // terminate it
      parseptr = buffer;       // offload the buffer into temp variable
      int len = sizeof(WRITE_PIN_CONFIG)/sizeof(WRITE_PIN_CONFIG[0]); //get the size of the array
      for(int i = 0; i < len; i++){
        //parse all incoming values and assign them to the appropriate variable
        int val = parsedecimal(parseptr);       // parse the incoming number
        if(i != len - 1) parseptr = strchr(parseptr, ',')+1;   // move past the ","
        WriteToPin(WRITE_PIN_CONFIG[i], val, SERVO_CONFIG[i]);         //send value out to pin on arduino board
      }    
      bufferidx = 0;                             // reset the buffer for the next read
      return;                                    // return so that we don't trigger the index increment below
    }                                            // didn't get newline, need to read more from the buffer
    bufferidx++;                                 // increment the index for the next character
    if (bufferidx == BUFFSIZE-1) bufferidx = 0;  // if we get to the end of the buffer reset for safety
  }
}

/*
* Send the incoming value to the appropriate pin using pre-defined logic (ie. digital, analog, or servo)
*/
void WriteToPin(int _pin, int _value, Servo _servo){
  if (_value >= 10000 && _value < 20000)            // check if value should be used for Digital Write (HIGH/LOW)
  {      
    if (_servo.attached()) _servo.detach();         // detach servo is one is attached to pin
    pinMode(_pin, OUTPUT);                       
    _value -= 10000;                                // subtract 10,000 from the value sent from Grasshopper 
    if (_value == 1) digitalWrite(_pin, HIGH);     
    else digitalWrite(_pin, LOW);   
  }   
  else if (_value >= 20000 && _value < 30000)       // check if value should be used for Analog Write (0-255)
  {
    if (_servo.attached()) _servo.detach();         // detach servo is one is attached to pin
    pinMode(_pin, OUTPUT);               
    _value -= 20000;                                // subtract 20,000 from the value sent from Grasshopper
    analogWrite(_pin, _value);                     
  }
  else if (_value >= 30000 && _value < 40000)       // check if value should be used for Servo Write (0-180)
  {
    _value -= 30000;                                // subtract 30,000 from the value sent from Grasshopper
    if (!_servo.attached())_servo.attach(_pin);     // attaches a Servo to the PWM pin (180 degree standard servos)                                    
    _servo.write(_value);                          
  }
  else if (_value >= 40000 && _value < 50000)       // check if value should be used for Analog Write (0-4096) for DACs
  {
    if (_servo.attached()) _servo.detach();         // detach servo is one is attached to pin
    pinMode(_pin, OUTPUT);               
    _value -= 40000;                                // subtract 40,000 from the value sent from Grasshopper
    WriteToDAC(_pin, _value);                     
  }
}

/*
* Parse a string value as a decimal
*/
uint32_t parsedecimal(char *str){
  uint32_t d = 0;
  while (str[0] != 0) {
    if ((str[0] > '50') || (str[0] < '0'))
      return d;
    d *= 10;
    d += str[0] - '0';
    str++;
  }
  return d;
}

/*
* Send the incoming value to the appropriate DAC for DUE boards. 
* Note: analogWrite resolution (default is 12 bits) is defined in the Setup function.
*/
//modification to original sketch to work with 2022 firefly and arduino
  void WriteToDAC(int _pin, int _value){
    #if defined(__SAM3X8E__) 
    if(_pin == 0) analogWrite(DAC0, _value);
    else if (_pin == 1) analogWrite(DAC1, _value);
    #endif
  }

Next, with Grasshopper/Firefly, the serial data from the light sensors is read. This data is used to generate points, which act as attractors in a domain field. The brighter the light, the smaller the circle illustrated in the domain field.

The final results can be viewed as a plan, or project onto the digital model surface to be viewed in 3d.

Light Readings, plan view, v1

Light Readings, 3d, v1

Light Readings, plan view, v2

Light Readings, plan view, v2

 

 

Visual Crit – Jud

Problem:

Many rooms in the basements of buildings are very depressing and hard to work in because of the lack of windows and access to nature. Looking out the window is a very comforting and calming action that one can take for granted when working on floors where there is plenty of access to windows. Not only being able to look out the window and see what the weather is currently, but also being able to see nature outside is very helpful to productivity. In order to solve this problem, this project attempts to make a virtual window that allows you to see the outside environment and also interact with it. The p5.js script reads the weather of the desired location and inputs that into the script which then decides what to do based on what the weather is. For example, if it’s raining outside, the visual feedback will be a rain simulation with the rain droplets accumulating at the bottom to show how much it has actually rained. The same thing happens with snow and other weather events that have a physical element falling to the ground. In addition to the simulation of the weather, the device will have a gyroscope inside reading the current tilt angle and sending that back into the p5 script. With this tilt information, the accumulated physical weather element will then move around the screen to be level with the ground.

The following images and videos show part of the system working with light rain, heavy rain, and snow. The current script is able to read the weather and report back to the user what it is outside. The simulation of the physical weather elements are also able to move around corresponding to the tilt angle accumulating at the bottom until the weather type changes.

Images:

Image of snow weather
Physical build of gyroscope sensor and teensy

Videos:

Video of Snow

Video of Light Rain

Video of Heavy Rain

Code:

Arduino

/*
    MPU6050 Triple Axis Gyroscope & Accelerometer. Simple Gyroscope Example.
    Read more: http://www.jarzebski.pl/arduino/czujniki-i-sensory/3-osiowy-zyroskop-i-akcelerometr-mpu6050.html
    GIT: https://github.com/jarzebski/Arduino-MPU6050
    Web: http://www.jarzebski.pl
    (c) 2014 by Korneliusz Jarzebski
*/

#include <Wire.h>
#include <MPU6050.h>

MPU6050 mpu;

double zPrev = 0;

void setup()
{
  Serial.begin(115200);

  // Initialize MPU6050
  Serial.println("Initialize MPU6050");
  while (!mpu.begin(MPU6050_SCALE_2000DPS, MPU6050_RANGE_2G))
  {
    Serial.println("Could not find a valid MPU6050 sensor, check wiring!");
    delay(500);
  }

  // If you want, you can set gyroscope offsets
     mpu.setAccelOffsetX(40);
     mpu.setAccelOffsetY(40);
     mpu.setAccelOffsetZ(40);

  // Calibrate gyroscope. The calibration must be at rest.
  // If you don't want calibrate, comment this line.
  mpu.calibrateGyro();

  // Set threshold sensivty. Default 3.
  // If you don't want use threshold, comment this line or set 0.
  mpu.setThreshold(3);

  // Check settings
  checkSettings();
}

void checkSettings()
{
//  Serial.println();

  Serial.print(" * Sleep Mode:        ");
  Serial.println(mpu.getSleepEnabled() ? "Enabled" : "Disabled");

  Serial.print(" * Clock Source:      ");
  switch (mpu.getClockSource())
  {
    case MPU6050_CLOCK_KEEP_RESET:     Serial.println("Stops the clock and keeps the timing generator in reset"); break;
    case MPU6050_CLOCK_EXTERNAL_19MHZ: Serial.println("PLL with external 19.2MHz reference"); break;
    case MPU6050_CLOCK_EXTERNAL_32KHZ: Serial.println("PLL with external 32.768kHz reference"); break;
    case MPU6050_CLOCK_PLL_ZGYRO:      Serial.println("PLL with Z axis gyroscope reference"); break;
    case MPU6050_CLOCK_PLL_YGYRO:      Serial.println("PLL with Y axis gyroscope reference"); break;
    case MPU6050_CLOCK_PLL_XGYRO:      Serial.println("PLL with X axis gyroscope reference"); break;
    case MPU6050_CLOCK_INTERNAL_8MHZ:  Serial.println("Internal 8MHz oscillator"); break;
  }

//  Serial.print(" * Gyroscope:         ");
  switch (mpu.getScale())
  {
    case MPU6050_SCALE_2000DPS:        Serial.println("2000 dps"); break;
    case MPU6050_SCALE_1000DPS:        Serial.println("1000 dps"); break;
    case MPU6050_SCALE_500DPS:         Serial.println("500 dps"); break;
    case MPU6050_SCALE_250DPS:         Serial.println("250 dps"); break;
  }

  Serial.print(" * Gyroscope offsets: ");
  Serial.print(mpu.getGyroOffsetX());
  Serial.print(" / ");
  Serial.print(mpu.getGyroOffsetY());
  Serial.print(" / ");
  Serial.println(mpu.getGyroOffsetZ());

  Serial.println();
}

void loop()
{
  Vector rawGyro = mpu.readRawAccel();
  Vector normGyro = mpu.readNormalizeAccel();

  //  Serial.print(" Xraw = ");
  //  Serial.print(rawGyro.XAxis);
  //  Serial.print(" Yraw = ");
  //  Serial.print(rawGyro.YAxis);
  //  Serial.print(" Zraw = ");
  //  Serial.println(rawGyro.ZAxis);

  //  Serial.print(" Xnorm = ");
  double x = rawGyro.XAxis;
  double z = rawGyro.ZAxis;
  
  if (z < 20000) {
    z = z + 65000;
  }
  z = z*0.8 + 0.2*zPrev;
//  Serial.print(78000 - z);
//  Serial.print("\t");
  
  z = (78000 - z)/(78000 - 30000)*180;
  z = z - 130;
  Serial.print(z);
//  Serial.print("\t");

  zPrev = z;

  Serial.println();

  delay(10);
}

P5.JS

var minWidth = 600;   //set min width and height for canvas
var minHeight = 400;
var width, height;    // actual width and height for the sketch
var squareWidth = 100;

let serial;
let latestData = "waiting for data";
var outData;

// A wind direction vector
let wind;
// Circle position
let position;
var weather = 'sn';
var weather_String = 'Snow';
var startHeight = 50;
var currColor;

var currWidth = 0;
var currHeight = 0;

let angle = 0;

let windAngle = 0.0;
let windMag = 0.0;

var startTime = 0;
var waitTime = 1000;
var rainAccumulated = 0;
var numDrops = 0;

let numBalls = 0;
let spring = 0.01;
let gravity = 0.03;
let friction = -0.2;
let balls = [];

function setup() {
  if (window.innerWidth > minWidth){
  width = window.innerWidth;
  } else {
    width = minWidth;
  }
  if (window.innerHeight > minHeight) {
    height = window.innerHeight;
  } else {
    height = minHeight;
  }
  //set up canvas
  createCanvas(width, height);
  noStroke();
  // Request the data from metaweather.com
  // let url = 'https://cors-anywhere.herokuapp.com/https://www.metaweather.com/api/location/2357536/';
  // loadJSON(url,gotWeather);
  // // Circle starts in the middle
  // position = createVector(width/2, height/2);
  // // wind starts as (0,0)
  // wind = createVector();
  // noStroke();
  // fill(255, 204);

  serial = new p5.SerialPort();

  serial.list();
  serial.open('/dev/tty.usbmodem114760901');

  serial.on('connected', serverConnected);

  serial.on('list', gotList);

  serial.on('data', gotData);

  serial.on('error', gotError);

  serial.on('open', gotOpen);

  serial.on('close', gotClose);

  angleMode(DEGREES);

}

function serverConnected() {
  print("Connected to Server");
 }
 
 function gotList(thelist) {
  print("List of Serial Ports:");
 
  for (let i = 0; i < thelist.length; i++) {
   print(i + " " + thelist[i]);
  }
 }
 
 function gotOpen() {
  print("Serial Port is Open");
 }
 
 function gotClose(){
  print("Serial Port is Closed");
  latestData = "Serial Port is Closed";
 }
 
 function gotError(theerror) {
  print(theerror);
 }
 
 function gotData() {
   let currentString = serial.readLine();
   trim(currentString);
   if (!currentString) return;
   console.log(currentString);
   latestData = Number(currentString);
   angle = latestData;
 }

function draw() {
  background(220);
  
  //Draw Text
  // translate(width/2, height/2);
  // rotate(angle);
  fill('black')
  textAlign(CENTER);
  textSize(50);
  text(weather_String, width/2, 60);
  textSize(20);
  var windText = 'Wind Speed: ' + windMag + '\t Wind Angle: ' + windAngle;
  text(windText, width/2, 100);
  text(latestData, width/2, 130);
  
  updateWeather();
  
  // angle = cos(millis()/1000);
  
  if (millis() - startTime > waitTime) {
    balls[numBalls] = new Ball(random(width), 0, random(20, 30), random(30, 50), numBalls, balls);
    numBalls +=1;
    startTime = millis();
  }
  
  balls.forEach(ball => {
    ball.collide();
    ball.move();
    ball.display();
  });
  
  
  // // This section draws an arrow pointing in the direction of wind
  // push();
  // translate(32, height - 32);
  // // Rotate by the wind's angle
  // rotate(wind.heading() + PI/2);
  // noStroke();
  // fill(255);
  // ellipse(0, 0, 48, 48);

  // stroke(45, 123, 182);
  // strokeWeight(3);
  // line(0, -16, 0, 16);

  // noStroke();
  // fill(45, 123, 182);
  // triangle(0, -18, -6, -10, 6, -10);
  // pop();
  
  // // Move in the wind's direction
  // position.add(wind);
  
  // stroke(0);
  // fill(51);
  // ellipse(position.x, position.y, 16, 16);

  // if (position.x > width)  position.x = 0;
  // if (position.x < 0)      position.x = width;
  // if (position.y > height) position.y = 0;
  // if (position.y < 0)      position.y = height;
}

function gotWeather(weather) {
  print(weather.title);
  let weather_today = weather.consolidated_weather[0]
  weather = weather_today.weather_state_abbr;
  weather_String = weather_today.weather_state_name;
  // print(weather);
  // print(weather_String);
  // Get the angle (convert to radians)
  windAngle = radians(Number(weather_today.wind_direction));
  // Get the wind speed
  windMag = Number(weather_today.wind_speed)/100;
  
  // Display as HTML elements
  // let temperatureDiv = createDiv(floor(weather_today.the_temp) + '&deg;C');
  // let windDiv = createDiv("WIND " + windmag + " <small>MPH</small>");
  
  // Make a vector
  wind = p5.Vector.fromAngle(angle);
}

function updateWeather() {
  switch(weather){
    case 'sn':
      waitTime = 300;
      fill('white')
      currWidth = 30;
      currHeight = 30;
      weather_String = 'Snow';
      break;

    case 'sl':
      weather_String = 'Sleet';
      break;
    case 'h':
      weather_String = 'Hail';
      break;
    case 't':
      weather_String = 'Thunderstorms';
      break;

    case 'hr':
      weather_String = 'Heavy Rain';
      waitTime = 100;
      fill(138, 183, 255)
      currWidth = 30;
      currHeight = 30;
      accumulate = true;
      break;

    case 'lr':
      weather_String = 'Light Rain';
      waitTime = 2000;
      fill(138, 183, 255)
      currWidth = 30;
      currHeight = 30;
      accumulate = true;
      break;

    case 's':
      weather_String = 'Showers';
      break;

    case 'hc':
      weather_String = 'Heavy Clouds';

      break;

    case 'lc':
      weather_String = 'Light Clouds';

      break;

    case 'c':
      weather_String = 'Clear';

      break;

    default:
      break;
  }
}

class Ball {
  constructor(xin, yin, win, hin, idin, oin) {
    this.x = xin;
    this.y = yin;
    this.vx = 0;
    this.vy = 0;
    this.w = win;
    this.h = hin
    this.id = idin;
    this.others = oin;
    // this.vxAdd = windMag*cos(windAngle);
    // this.vyAdd = windMag*sin(windAngle);
    this.vxAdd = 0;
    this.vyAdd = 0;
  }

  collide() {
    for (let i = this.id + 1; i < numBalls; i++) {
      // console.log(others[i]);
      let dx = this.others[i].x - this.x;
      let dy = this.others[i].y - this.y;
      let distance = sqrt(dx * dx + dy * dy);
      let minDist = this.others[i].w / 2 + this.w / 2;
      //   console.log(distance);
      //console.log(minDist);
      if (distance < minDist) {
        //console.log("2");
        let angle = atan2(dy, dx);
        let targetX = this.x + cos(angle) * minDist;
        let targetY = this.y + sin(angle) * minDist;
        let ax = (targetX - this.others[i].x) * spring;
        let ay = (targetY - this.others[i].y) * spring;
        this.vx -= ax;
        this.vy -= ay;
        this.others[i].vx += ax;
        this.others[i].vy += ay;
      }
    }
  }

  move() {
    var ygravity = gravity*cos(angle);
    var xgravity = gravity*sin(angle);
    this.vy += ygravity + this.vyAdd;
    this.vx += xgravity + this.vxAdd;
    this.x += this.vx;
    this.y += this.vy;
    if (this.x + this.w / 2 > width) {
      this.vxAdd = 0;
      // print("Hello");
      this.x = width - this.w / 2;
      this.vx *= friction;
    } else if (this.x - this.w / 2 < 0) {
      this.vxAdd = 0;
      this.x = this.w / 2;
      this.vx *= friction;
    }
    if (this.y + this.w / 2 > height) {
      this.vyAdd = 0;
      this.y = height - this.w / 2;
      this.vy *= friction;
    } else if (this.y - this.w / 2 < 0) {
      this.y = this.w / 2;
      this.vy *= friction;
    }
  }

  display() {
    ellipse(this.x, this.y, this.w, this.h);
  }
}

 

Visual Crit – James Kyle

Description:

For this project, I aimed to create an interactive 8-bit type representation of a house that the user could interact with and change stuff about their environment with the interface. I decided to go with lights and represent their states in the virtual p5.js environment. The user can click the lamps in the virtual world and turn them on/off in the real world. The states of the lamps are reflected in the p5.js environment as well brightening up the surrounding environment when they are on. The user can also turn the lights on and off in the physical world and the light states would be again reflected in the virtual environment so the information goes both ways from p5.js to arduino and back.

No lights on
One lamp on
Both lamps on

 

Demo video:

https://youtu.be/KQ2YrnX2HL4http://

 

Circuit:

 

Arduino Code:

#include <string.h>

#define lamp1pin 10
#define lamp2pin 9
#define button1 3
#define button2 2


unsigned long debounceTimer1 = 0;
int debounceTime1 = 300;
bool DEBOUNCE1 = false;
bool firstCount1 = true;

unsigned long debounceTimer2 = 0;
int debounceTime2 = 300;
bool DEBOUNCE2 = false;
bool firstCount2 = true;


volatile int lamp1state = 0;
volatile int lamp2state = 0;


int inputData;

char buf[25];
String command;

int c;


void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);

  pinMode(lamp1pin, OUTPUT);
  pinMode(lamp2pin, OUTPUT);
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(button1), button1Pressed, CHANGE);
  attachInterrupt(digitalPinToInterrupt(button2), button2Pressed, CHANGE);

}


void button2Pressed() {
  if (!DEBOUNCE2) {
    DEBOUNCE2 = true;
    lamp2state = !lamp2state;
    digitalWrite(lamp2pin, lamp2state);
  }
}

void button1Pressed() {
  if (!DEBOUNCE1) {
    DEBOUNCE1 = true;
    lamp1state = !lamp1state;
    digitalWrite(lamp1pin, lamp1state);
  }
}

void loop() {

  int message1 = 10;
  int message2 = 20;

  //if main computer sent a command
  if ( Serial.available() > 0) {

    c = Serial.read();
    
//    if (c == 1) {
//      
//      digitalWrite(lamp1pin, HIGH);
//    }
    Serial.println(c);
    parseInput(c);
  }

  if (DEBOUNCE1 && firstCount1) {
    debounceTimer1 = millis();
    firstCount1 = false;
    message1 = 1 + lamp1state;
//    Serial.println(message1);
  }
  
  if (millis() - debounceTimer1 > debounceTime1) {
    DEBOUNCE1 = false;
    firstCount1 = true;
  }

  if (DEBOUNCE2 && firstCount2) {
    debounceTimer2 = millis();
    firstCount2 = false;
    message2 = 3 + lamp2state;
//    Serial.println(message2);
  }
  if (millis() - debounceTimer2 > debounceTime2) {
    DEBOUNCE2 = false;
    firstCount2 = true;
  }
  
  
}


void parseInput(int inputData) {


  int y = inputData;

    if (y == 1) { //1
      lamp1state = 1;
      digitalWrite(lamp1pin, HIGH);
    } else if (y == 2) { //2
      lamp1state = 0;
      digitalWrite(lamp1pin, LOW);
    }
    else if (y == 3) { //3
      lamp2state = 1;
      digitalWrite(lamp2pin, HIGH);
    } else if (y == 4) { //4
      lamp2state = 0;
      digitalWrite(lamp2pin, LOW);
    }

  
}

 

p5.js Code:

/*


- RGB to HSV Conversion Function from http://www.javascripter.net/faq/rgb2hsv.htm


*/




let serial;
let latestData = "waiting for data";
var outData;
var message;

let newFont;
let lightsEnable = true;

let x = 100;
let y = 100;

let width = 1700;
let height = 900;

let color = 300;
let baseSaturation = 40;
let baseBrightness = 30;

let table1X = 970;
let table2X = 630;
let table1Y = 200;

//Lightsource vector [on/off, x, y, Lamp Radius, Shine Radius]
let lightSources = [];

let updateLightSource = true;


function setup() {
  createCanvas(width, height);

  //Lamps initiation
  lightSources[0] = [0, table1X, table1Y, 40, 200];
  lightSources[1] = [0, table2X, table1Y, 40, 200];
  lightSources[2] = [0, table1X, table1Y, 40, 200];
  
  
  serial = new p5.SerialPort();

  serial.list();
  serial.open('/dev/tty.usbmodem14301');

  serial.on('connected', serverConnected);

  serial.on('list', gotList);

  serial.on('data', gotData);

  serial.on('error', gotError);

  serial.on('open', gotOpen);

  serial.on('close', gotClose);
}

function serverConnected() {
  print("Connected to Server");
 }
 
 function gotList(thelist) {
  print("List of Serial Ports:");
 
  for (let i = 0; i < thelist.length; i++) {
   print(i + " " + thelist[i]);
  }
 }
 
 function gotOpen() {
  print("Serial Port is Open");
 }
 
 function gotClose(){
  print("Serial Port is Closed");
  latestData = "Serial Port is Closed";
 }
 
 function gotError(theerror) {
  print(theerror);
 }
 
 function gotData() {
   let currentString = serial.readLine();
   trim(currentString);
   if (!currentString) return;
   console.log(currentString);
   latestData = currentString;

   if (latestData/10 >= 2) {
     lightSources[1][0] = latestData - 20;
   } else {
    lightSources[0][0] = latestData - 10; 
   }
 }
 
function draw() {

  colorMode(HSB);
  background(128, 20, 70);

  push();
  rectMode(CENTER);
  fill(50);
  rect(width/2, height/2,width - 100, height - 100);
  pop();

  //Couch
  drawCouch(800, 200, 80, 200, 0, 50, 40, 80);

  //Chairs
  drawCouch(980, 300, 80, 80, PI/2, 50, 40, 80);
  drawCouch(620, 300, 80, 80, -PI/2, 50, 40, 80);

  //Coffee table
  drawTable(800, 300, 80, 200, PI, 30, 40, 30);

  //Lamp tables
  drawTable(table1X, table1Y, 80, 80, 0, 30, 40, 30);
  drawTable(table2X, table1Y, 80, 80, 0, 30, 40, 30);

  for (k = 0; k < lightSources.length; k++) {
    fill(200, 40, 60);
    drawLamp(lightSources[k][1], lightSources[k][2], lightSources[k][3]);
  }



  for (n = 0; n < lightSources.length; n++) {
    if (lightSources[n][0] == 1) {
      turnLightOn(lightSources[n][1], lightSources[n][2], lightSources[n][3], lightSources[n][4]);
    }
  }

  push();
  fill(0);
  textSize(14);
  text(latestData, 10, 20);
  pop();

  push();
  fill(0);
  textSize(14);
  text(message, 10, 40);
  pop();

  var d = 3;
  d = int(d);

  // serial.write(int(2));
 
}


function updateLightState(lightIndex) {
  lightIndex = Number(lightIndex);
  if (lightSources[lightIndex][0] == 1) {
      lightSources[lightIndex][0] = 0;
      if (lightIndex == 2) { 
        outData = 2;
      } else if (lightIndex == 1){
        outData = 4;
      }
      // print(message);
      serial.write(int(outData));
  }

  else if (lightSources[lightIndex][0] == 0) {
      lightSources[lightIndex][0] = 1;
      if (lightIndex == 2) { 
        outData = 1;
      } else if (lightIndex == 1){
        outData = 3;
      }
      // print(message);
      serial.write(int(outData));
  }
}

function checkIfLampClicked(x, y) {

  for (n = 0; n < lightSources.length; n++) {
    let currX = lightSources[n][1];
    let currY = lightSources[n][2];
    let currR = lightSources[n][3];

    let distance = sqrt(pow((currX-x),2) - pow((currY-y),2));

    if (distance <= currR) {
      //Toggle lamp state
      updateLightState(n);
      return;
    }
  }
}

function keyPressed() {

  updateLightState(key);

}



function turnLightOn(x, y, lampRadius, shineRadius) {
  var centerX = x;
  var centerY = y;


  //Iterating through each point in square to brighten color if inside lamp shine radius
  for (i = 0; i < 2*shineRadius; i = i+5) {
    for (j = 0; j < 2*shineRadius; j = j+5) {
      var currX = centerX - shineRadius + i;
      var currY = centerY - shineRadius + j;

      var dist2Center = sqrt(pow((centerX - currX),2) + pow((centerY - currY),2));

      if (dist2Center <= shineRadius) {
        var currColor = get(currX, currY);
        hsvConversion = rgb2hsv(currColor[0], currColor[1], currColor[2]);

        colorMode(HSB);
        stroke(hsvConversion[0], hsvConversion[1]+40, hsvConversion[2]+20);
        strokeWeight(7);
        point(currX, currY);
      }

    }
  }
 
  strokeWeight(0);
}



function drawCouch(x, y, width, height, theta, couchH, couchS, couchB) {

  let couchHeight = width;
  let couchWidth = height;
  let armWidth = couchWidth*1/5;
  let armHeight = couchHeight*4/5;
  let backWidth = couchWidth;
  let backHeight = couchHeight*2/5;

  //Base Couch
  push();
  angleMode(RADIANS);
  rectMode(CENTER);
  translate(x, y);
  rotate(theta);
  // translate(transRadius - transRadius*cos(theta), -transRadius*sin(theta));

  strokeWeight(2);
  stroke(0);
  fill(couchH, couchS, couchB);
  rect(0, 0, couchWidth, couchHeight, 0, 0, 10, 10);

  //Arm rests
  fill(couchH, couchS, couchB - 20);
  rect(-couchWidth/2, armHeight - couchHeight, armWidth, armHeight, 10, 10, 10, 10);
  rect(couchWidth/2, armHeight - couchHeight, armWidth, armHeight, 10, 10, 10, 10);

  //Backing
  fill(couchH, couchS, couchB - 25);
  rect(0, (armHeight/2 - couchHeight/2) + (backHeight/2 - couchHeight/2), backWidth, backHeight, 0, 0, 30, 30);
  strokeWeight(0);


  pop();

}

function drawTable(x, y, width, height, theta, couchH, couchS, couchB) {

  let tableHeight = width;
  let tableWidth = height;

  //Base Couch
  push();
  angleMode(RADIANS);
  rectMode(CENTER);
  translate(x, y);
  rotate(theta);
  // translate(transRadius - transRadius*cos(theta), -transRadius*sin(theta));

  //Table top
  strokeWeight(2);
  stroke(0);
  fill(couchH, couchS, couchB);
  rect(0, 0, tableWidth, tableHeight);

  //Top pattern
  strokeWeight(2);
  stroke(0);
  fill(couchH, couchS, couchB + 40);
  rect(0, 0, tableWidth - 10, tableHeight - 10);

  pop();

}


function drawLamp(x, y, radius, couchH, couchS, couchB) {

  let tableHeight = width;
  let tableWidth = height;

  //Base Couch
  push();

  //Lamp
  fill(couchH, couchS, couchB);
  ellipse(x, y, radius);

  pop();

}



function rgb2hsv (r,g,b) {
 var computedH = 0;
 var computedS = 0;
 var computedV = 0;

 //remove spaces from input RGB values, convert to int
 var r = parseInt( (''+r).replace(/\s/g,''),10 );
 var g = parseInt( (''+g).replace(/\s/g,''),10 );
 var b = parseInt( (''+b).replace(/\s/g,''),10 );

 if ( r==null || g==null || b==null ||
     isNaN(r) || isNaN(g)|| isNaN(b) ) {
   alert ('Please enter numeric RGB values!');
   return;
 }
 if (r<0 || g<0 || b<0 || r>255 || g>255 || b>255) {
   alert ('RGB values must be in the range 0 to 255.');
   return;
 }
 r=r/255; g=g/255; b=b/255;
 var minRGB = Math.min(r,Math.min(g,b));
 var maxRGB = Math.max(r,Math.max(g,b));

 // Black-gray-white
 if (minRGB==maxRGB) {
  computedV = minRGB;
  return [0,0,computedV];
 }

 // Colors other than black-gray-white:
 var d = (r==minRGB) ? g-b : ((b==minRGB) ? r-g : b-r);
 var h = (r==minRGB) ? 3 : ((b==minRGB) ? 1 : 5);
 computedH = 60*(h - d/(maxRGB - minRGB));
 computedS = (maxRGB - minRGB)/maxRGB;
 computedV = maxRGB;
 return [computedH,computedS*100,computedV*100];
}

 

Virtual Window for Windowless Rooms Ideation

The idea for this project comes from an earlier project in this class from someone else involving sensing the weather outside and reporting it to the user. Where this project differs, however, is it will incorporate a visual representation of the weather inside a screen. The general functionality will include sensing the weather either through sensors or getting data from online and then interpreting that into visual representation that can either be displayed through p5.js or an LED screen controlled by a teensy. The two different styles will work the same way just with slightly different implementations.