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