As computing becomes more ubiquitous in our objects, designers need to be more aware of how to design meaningful interactions into electronically enhanced objects. At the University of Washington, a class of junior Interaction Design majors is exploring this question. These pages chronicle their efforts.

Wednesday, June 12, 2019

FerrWeather DESIGN 325


This project was created by Lulu McRoberts and Bryan Ford for DESGIN 325. 



High Level Intro
FerrWeather is a weather station that brings the mystery back to weather. While precision weather predictions backed by atmospheric science and complex weather models are valuable in many applications, they remove the mystery and ambiguity of weather. For people who just want to gauge the mood of the day, FerrWeather uses ferrofluid, a weird, goopy magnetic fluid to visualize the predicted weather pattern, giving you all the information you need while preserving the mystery of weather.

Weather Prediction
To determine the weather prediction, a barometric pressure sensor was used. While this sensor is much less accurate than a weather station, it provides a weather prediction with a resolution similar to what the ferrofluid simulation can show. Any more complicated system would produce data that would be lost in the weird, chaotic ferrofluid animation. Moreover, this product is not about predicting the weather with objective accuracy, but rather reminding the user of the mysterious and unpredictable qualities of weather.

Control
To control the ferrofluid, we briefly considered using electromagnets but found they are expensive and would need a considerably amount of power. Instead, we chose to use motors to physically move a neodymium magnet around the bottle. A DC motor harvested from an old printing head was used with a belt drive to move the magnet back and forth along the length of the bottle while a servo motor move the magnet up and down.
 

Ferrofluid
A square container was chosen so that its surface would be flush with the enclosure and not distort the image of the ferrofluid. While we considered making our own ferrofluid, resources online suggested to use industry grade EFH1 fluid for best results. We chose brine water as our suspension fluid and let it sit in the glass container for 2 days before introducing the ferrofluid. We believe this gives the sodium time to diffuse into glass to prevent the ferrofluid from staining the bottle.

Stormy
This state occurs weather when Barometric pressure is very low or low but dropping quickly. To simulate this weather pattern, the Arduino brings the magnets down to the surface of the glass and moves them back and forth rapidly. This causes parts of the ferrofluid glob to break off and spin around the bottle in the turbulent wake.

Rainy
This state occurs when Barometric pressure is low or approaching a low pressure quickly. To simulate this weather pattern, the Arduino moves the magnet along the surface of the glass while slowly raising it, allowing ferrofluid to sink down to the bottom, simulating rain falling from the sky. When all of the fluid has sunk to the bottom, the servo brings the magnets back down, quickly grabs the ferrofluid and repeats the cycle.

Partly Cloudy
This state occurs when Barometric pressure is in a medium range or approaching this range quickly. To simulate this weather pattern, the Arduino moves the magnet along the surface of the glass very slowly while slightly raising it to allow some of the fluid to sink towards the bottom. However, before all of the fluid sinks down, it brings the magnet back down, drawing the fluid back towards the top. This simulates cloud patterns emerging/disappearing on partly cloud days.

Sunny
This state occurs when Barometric pressure is high or approaching this range quickly. To simulate this weather pattern, the Arduino brings the magnet up to allow the ferrofluid to sink to the bottom. Then it slowly brings the magnet down while moving it horizontally across the glass. Before most of the ferrofluid reaches the top, it brings the magnet back up, allowing the fluid to fall back down and complete the cycle. Since a sunny days means clear sky, it lets the fluid sink to the bottom but still puts on an interesting animation.


Libraries Used

Wire.h   // I2C Library for communicating with the BMP280 Barometric pressure sensor
Servo.h // Library to control a servo motor
CircularBuffer.h // Allows the Arduino to store pressure readings in a circular buffer
Adafruit_BMP280.h // For reading and processing data from the BMP280
Adafruit_Sensor.h // Library for using Adafruit’s sensors.

Code

#include <Wire.h>  //I2C Library
#include <Servo.h>
#include <CircularBuffer.h>
#include <Adafruit_BMP280.h>
#include <Adafruit_Sensor.h>
#define LED_PIN 4
#define MOTOR_IN1 5                             // 1st pin of motor in pin 5
#define MOTOR_IN2 6                             // 2nd pin of motor is pin 6


typedef CircularBuffer<int, 4>  CircularBufferType;
CircularBufferType weatherBuffer;               //Circular buffer initialization
Servo myservo;                                  //Create servo object
Adafruit_BMP280 bme;                            //Create BMP280 barometric pressure object

enum weatherState {
  stormy,
  rainy,
  pcloudy,
  sunny,
};

enum changeState {
  rising,
  falling,
  same
};

int stormLow = 930;
int stormHigh = 961;
int rainLow = 969;
int rainHigh = 982;
int partLow = 996;
int partHigh = 1023;
int sunLow = 1036;
int sunHigh = 1200;
int BaromP;
int BaromParr[36];
int count = 0;

int pos = 0;
int cal = 66;
int Time;
int Speed;
int ramp;
int ramprate;

int StormyTime = 40;
int StormySpeed = 100;
int Stormyramp = 10;
int Stormyramprate = 16;

int RainyTime = 70;
int RainySpeed = 60;
int Rainyramp = 10;
int Rainyramprate = 16;

int PcloudyTime = 70;
int PcloudySpeed = 60;
int Pcloudyramp = 10;
int Pcloudyramprate = 16;
int PcloudyStartAngle = 0;

int SunnyTime = 20;
int SunnySpeed = 75;
int Sunnyramp = 5;
int Sunnyramprate = 7;

int delta = 2;
enum weatherState weather;
enum changeState change;

void setup() {
  Serial.begin(9600);
  bme.begin();
  myservo.attach(11);
  myservo.write(cal);
  pinMode(MOTOR_IN1, OUTPUT);
  pinMode(MOTOR_IN2, OUTPUT);
  pinMode(4, OUTPUT);

  Time = 200;
  Speed  = 100;
  ramp = 5;
  ramprate = 8;
  Speed = map(Speed, 0, 100, 0, 255);
}

void loop() {
  ledOn();
  myservo.write(cal);

  weather = predictWeather();

  Serial.print("Count: "); Serial.println(count);
  Serial.print("Pressure: "); Serial.println(BaromP);

  if (change == rising) {
    Serial.println("Rising ");
  } else if (change == falling) {
    Serial.println("Falling ");
  } else if (change == same) {
    Serial.println("Same");
  }

  //startRainyAnimation();
  startStormyAnimation();
  //startPcloudyAnimation();
  //startSunnyAnimation();
  
  /*
    switch (weather) {
      case rainy:
        Serial.println("Rainy");
        startRainyAnimation();
        break;
      case stormy:
        Serial.println("Stormy");
        //random actuation in x && y axes
        startStormyAnimation();
        break;
      case pcloudy:
        Serial.println("Partly Cloudy");
        startPcloudyAnimation();
        break;
      case sunny:
        Serial.println("Sunny");
        startSunnyAnimation();
        break;
      default:
        break;
    }
  */
}



enum changeState weatherChange(int BaromP, int delta) {

  weatherBuffer.push(BaromP);
  for ( int i = 0; i < sizeof(weatherBuffer); i++) {
    if (BaromP > weatherBuffer[i] + delta) {
      change = rising;
      break;
    } else if (BaromP < weatherBuffer[i] - delta) {
      change = falling;
      break;
    }
    else {
      change = same;
    }
  }
  return change;
}

enum weatherState predictWeather() {
  Serial.println();
  Serial.println("Reading Pressure");

  BaromP = bme.readPressure() / 100;
  change = weatherChange(BaromP, delta);

  if (BaromP <= stormHigh && BaromP > stormLow) {
    weather = stormy;
  } else if (BaromP > stormHigh && BaromP < rainLow && change == falling) {
    weather = stormy;
  } else if (BaromP > stormHigh && BaromP < rainLow && change == rising) {
    weather = rainy;
  } else if (BaromP >= rainLow && BaromP <= rainHigh) {
    weather = rainy;
  } else if (BaromP > rainHigh && BaromP < partLow && change == falling) {
    weather = rainy;
  } else if (BaromP > rainHigh && BaromP < partLow && change == rising) {
    weather = pcloudy;
  } else if (BaromP >= partLow && BaromP <= partHigh) {
    weather = pcloudy;
  } else if (BaromP > partHigh && BaromP < sunLow && change == falling) {
    weather = pcloudy;
  } else if (BaromP > partHigh && BaromP < sunLow && change == rising) {
    weather = sunny;
  } else if (BaromP >= sunLow) {
    weather = sunny;
  }
  Serial.println("Determined weather state");
  return weather;
}

// MOVE MOTOR FORWARDS
void moveMotorForward(int Speed, int Time, int ramp, int ramprate) {
  // ramp up forward
  digitalWrite(MOTOR_IN1, LOW);
  for (int i = 0; i < Speed; i = i + ramprate) {
    analogWrite(MOTOR_IN2, i);
    delay(ramp);
  }
  // forward full speed for one second
  delay(Time);
  // ramp down forward
  for (int i = Speed; i >= 0; i = i - ramprate) {
    analogWrite(MOTOR_IN2, i);
    delay(ramp);
  }
}

//MOVE MOTOR BACK
void moveMotorBackward(int Speed, int Time, int ramp, int ramprate) {
  // ramp up backward
  digitalWrite(MOTOR_IN2, LOW);
  for (int i = 0; i < Speed; i = i + ramprate) {
    analogWrite(MOTOR_IN1, i);
    delay(ramp);
  }
  // backward full speed for one second
  delay(Time);
  // ramp down backward
  for (int i = Speed; i >= 0; i = i - ramprate) {
    analogWrite(MOTOR_IN1, i);
    delay(ramp);
  }
}

void moveServoUp(int ServoSpeed, int ServoStartAngle, int ServoStopAngle) {
  for (pos = cal + ServoStartAngle; pos <= cal + ServoStopAngle; pos += 5) {
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    Serial.println(pos);
    delay(205 - ServoSpeed);
  }
}

void moveServoDown(int ServoSpeed, int ServoStartAngle, int ServoStopAngle) {
  for (pos = cal + ServoStartAngle; pos >= cal + ServoStopAngle; pos -= 5) {
    myservo.write(pos);
    Serial.println(pos);
    delay(205 - ServoSpeed);        // delays a variable by the speed determined by the input argument
  }
}

//STORMY ANIMATION//
void startStormyAnimation() {
  Serial.println("Starting Stormy Animation");
  moveServoUp(200, 0, 10);
  moveMotorBackward(map(StormySpeed, 0, 100, 0, 255), 65, Stormyramp, Stormyramprate);
  for (int i = 0; i < 4; i++) {
    moveMotorForward(map(StormySpeed, 0, 100, 0, 255), StormyTime, Stormyramp, Stormyramprate);
    moveMotorBackward(map(StormySpeed, 0, 100, 0, 255), 65, Stormyramp, Stormyramprate);
  }
  delay(1200);
  moveMotorForward(map(StormySpeed, 0, 100, 0, 255), StormyTime, Stormyramp, Stormyramprate);
  for (int i = 0; i < 4; i++) {
    moveMotorBackward(map(StormySpeed, 0, 100, 0, 255), 65, Stormyramp, Stormyramprate);
    moveMotorForward(map(StormySpeed, 0, 100, 0, 255), StormyTime, Stormyramp, Stormyramprate);
  }
  moveServoDown(200, 10, 0);
  delay(1200);
}

//RAINY ANIMATION//
void startRainyAnimation() {
  Serial.println("Starting Rainy Animation (Pcloudy Prototype)");
  for (int i = 0; i < 20; i++) {
    moveMotorForward(map(RainySpeed, 0, 100, 0, 255), RainyTime, Rainyramp, Rainyramprate);
    moveServoUp(190, 5 * i, 5 * i + 5);
  }
  delay(500);
  moveServoDown(200, 100, 0);
  delay(1000);
  moveMotorBackward(map(RainySpeed, 0, 88, 0, 255), RainyTime * 15, Rainyramp, Rainyramprate);

  Serial.println("Motors");

  delay(200);

}

// PCLOUDY ANIMATION
void startPcloudyAnimation() {
  Serial.println("Starting Pcloudy Animation");
  delay(500);
  moveServoUp(190, 0, PcloudyStartAngle);
  for (int i = 0; i < 12; i++) {
    moveMotorForward(map(PcloudySpeed, 0, 100, 0, 255), PcloudyTime, Pcloudyramp, Pcloudyramprate);
    moveServoUp(190, 5 * i + PcloudyStartAngle, 5 * i + PcloudyStartAngle + 5);
  }
  for (int i = 12; i > 0; i--) {
    moveMotorForward(map(PcloudySpeed, 0, 100, 0, 255), PcloudyTime, Pcloudyramp, Pcloudyramprate);
    moveServoDown(190, 5 * i + PcloudyStartAngle, 5 * i + PcloudyStartAngle - 5);
  }
  for (int i = 0; i < 12; i++) {
    moveMotorBackward(map(PcloudySpeed, 0, 100, 0, 255), PcloudyTime, Pcloudyramp, Pcloudyramprate);
    moveServoUp(190, 5 * i + PcloudyStartAngle, 5 * i + PcloudyStartAngle + 5);
  }
  for (int i = 12; i > 0; i--) {
    moveMotorBackward(map(PcloudySpeed, 0, 100, 0, 255), PcloudyTime, Pcloudyramp, Pcloudyramprate);
    moveServoDown(190, 5 * i + PcloudyStartAngle , 5 * i + PcloudyStartAngle - 5);
  }
  moveServoDown(190, PcloudyStartAngle, 0);
}

//SUNNY ANIMATION
void startSunnyAnimation() {
  Serial.println("Starting Sunny Animation");
  moveServoUp(200, 0, 90);
  moveMotorForward(map(SunnySpeed, 0, 100, 0, 255), 50, Sunnyramp, Sunnyramprate);
  delay(3000);
  moveMotorBackward(map(SunnySpeed, 0, 100, 0, 255), SunnyTime, Sunnyramp, Sunnyramprate);
   delay(1000);
 moveServoDown(100, 90, 0 );
   delay(1000);

}

void ledOff(void) {
  digitalWrite(LED_PIN, LOW);
}

void ledOn(void) {
  digitalWrite(LED_PIN, HIGH);
}


No comments:

Post a Comment