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);
  }
}

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.