// ============================================================
// SMART WEATHER STATION + AUTO VENTILATION
// Arduino UNO | Wokwi Simulation
//
// Parts (all verified from Wokwi docs):
// wokwi-dht22 → temp + humidity sensor
// wokwi-lcd1602 (i2c) → 16x2 display
// wokwi-servo → auto vent flap (opens when hot)
// wokwi-led (x3) → status LEDs: green/yellow/red
// wokwi-buzzer → alarm when temp critical
// wokwi-pushbutton → cycle display screens
//
// Libraries: DHT sensor library, LiquidCrystal I2C, Servo
// ============================================================
#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Servo.h>
// ── Pin definitions ──────────────────────────────────────
#define DHT_PIN 7
#define DHT_TYPE DHT22
#define SERVO_PIN 9
#define LED_GREEN 2
#define LED_YELLOW 3
#define LED_RED 4
#define BUZZER_PIN 5
#define BTN_PIN 6
// ── Objects ───────────────────────────────────────────────
DHT dht(DHT_PIN, DHT_TYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo ventServo;
// ── State ─────────────────────────────────────────────────
float tempC = 0;
float humidity = 0;
float heatIndex = 0;
int ventAngle = 0; // 0 = closed, 90 = half, 170 = fully open
int screen = 0; // 0=temp/hum, 1=heat index, 2=vent status
bool alarmOn = false;
unsigned long lastRead = 0;
unsigned long lastBlink = 0;
unsigned long lastBuzz = 0;
bool blinkState = false;
bool prevBtn = HIGH;
unsigned long dbBtn = 0;
// ── Custom LCD characters ─────────────────────────────────
// Thermometer icon
uint8_t thermChar[8] = {
0b00100, 0b01010, 0b01010, 0b01110,
0b01110, 0b11111, 0b11111, 0b01110
};
// Droplet icon
uint8_t dropChar[8] = {
0b00100, 0b00100, 0b01010, 0b01010,
0b10001, 0b10001, 0b10001, 0b01110
};
// Fan icon
uint8_t fanChar[8] = {
0b00000, 0b01110, 0b11011, 0b00100,
0b11011, 0b01110, 0b00000, 0b00000
};
// Up arrow
uint8_t upArrow[8] = {
0b00100, 0b01110, 0b11111, 0b00100,
0b00100, 0b00100, 0b00100, 0b00000
};
// ── Helpers ───────────────────────────────────────────────
// Compute heat index (simplified Steadman)
float computeHeatIndex(float t, float h) {
return -8.78469 + 1.61139*t + 2.33855*h
- 0.14612*t*h - 0.01230*t*t
- 0.01642*h*h + 0.00221*t*t*h
+ 0.00072*t*h*h - 0.00000358*t*t*h*h;
}
// Map temperature to vent angle
int tempToVent(float t) {
if (t < 25.0) return 0; // cool → closed
if (t < 30.0) return map((int)(t*10), 250, 300, 0, 60); // warm → partial
if (t < 38.0) return map((int)(t*10), 300, 380, 60, 160); // hot → wide
return 170; // critical → fully open
}
// Draw a bar on LCD row using filled/empty blocks
void drawBar(int col, int row, int percent, int width) {
int filled = (percent * width) / 100;
lcd.setCursor(col, row);
for (int i = 0; i < width; i++) {
lcd.write(i < filled ? (uint8_t)255 : (uint8_t)'-');
}
}
// ── Screens ───────────────────────────────────────────────
void showScreenTemp() {
// Row 0: therm icon + temperature
lcd.setCursor(0, 0);
lcd.write((uint8_t)0); // thermometer
lcd.print(" Temp: ");
if (tempC < 10) lcd.print(" ");
lcd.print(tempC, 1);
lcd.print("\xDF""C ");
// Row 1: drop icon + humidity bar
lcd.setCursor(0, 1);
lcd.write((uint8_t)1); // droplet
lcd.print(" Hum:");
lcd.print((int)humidity);
lcd.print("% ");
drawBar(11, 1, (int)humidity, 5);
}
void showScreenHeatIdx() {
lcd.setCursor(0, 0);
lcd.print("Heat Index: ");
lcd.setCursor(0, 1);
if (heatIndex < 27) lcd.print("Comfort ");
else if (heatIndex < 32) lcd.print("Caution ");
else if (heatIndex < 41) lcd.print("Warning! ");
else lcd.print("DANGER!! ");
lcd.print(heatIndex, 1);
lcd.print("\xDF""C");
}
void showScreenVent() {
lcd.setCursor(0, 0);
lcd.write((uint8_t)2); // fan icon
lcd.print(" Vent: ");
int pct = map(ventAngle, 0, 170, 0, 100);
lcd.print(pct);
lcd.print("% ");
lcd.setCursor(0, 1);
lcd.print("[");
drawBar(1, 1, pct, 12);
lcd.setCursor(13, 1);
lcd.print("] ");
}
// ── Setup ─────────────────────────────────────────────────
void setup() {
dht.begin();
lcd.init();
lcd.backlight();
lcd.createChar(0, thermChar);
lcd.createChar(1, dropChar);
lcd.createChar(2, fanChar);
lcd.createChar(3, upArrow);
ventServo.attach(SERVO_PIN);
ventServo.write(0);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(LED_RED, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BTN_PIN, INPUT_PULLUP);
// Splash screen
lcd.setCursor(2, 0);
lcd.print("WeatherStation");
lcd.setCursor(3, 1);
lcd.print("Auto Vent v1");
delay(1800);
lcd.clear();
// Boot LED test
digitalWrite(LED_GREEN, HIGH); delay(200);
digitalWrite(LED_YELLOW, HIGH); delay(200);
digitalWrite(LED_RED, HIGH); delay(200);
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_RED, LOW);
lastRead = millis();
}
// ── Loop ──────────────────────────────────────────────────
void loop() {
unsigned long now = millis();
// ---- Read sensor every 2 s ----
if (now - lastRead >= 2000) {
lastRead = now;
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
tempC = t;
humidity = h;
heatIndex = computeHeatIndex(t, h);
}
// ---- Update vent servo ----
int targetAngle = tempToVent(tempC);
// Smooth movement: step 5° per cycle toward target
if (ventAngle < targetAngle) ventAngle = min(ventAngle + 5, targetAngle);
else if (ventAngle > targetAngle) ventAngle = max(ventAngle - 5, targetAngle);
ventServo.write(ventAngle);
// ---- Update LEDs ----
if (tempC < 28) {
digitalWrite(LED_GREEN, HIGH);
digitalWrite(LED_YELLOW, LOW);
digitalWrite(LED_RED, LOW);
alarmOn = false;
} else if (tempC < 35) {
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_YELLOW, HIGH);
digitalWrite(LED_RED, LOW);
alarmOn = false;
} else {
digitalWrite(LED_GREEN, LOW);
digitalWrite(LED_YELLOW, LOW);
alarmOn = true;
}
// ---- Refresh LCD screen ----
lcd.clear();
switch (screen) {
case 0: showScreenTemp(); break;
case 1: showScreenHeatIdx(); break;
case 2: showScreenVent(); break;
}
}
// ---- Red LED blink + buzzer when alarm ----
if (alarmOn && now - lastBlink >= 300) {
lastBlink = now;
blinkState = !blinkState;
digitalWrite(LED_RED, blinkState ? HIGH : LOW);
}
if (alarmOn && now - lastBuzz >= 800) {
lastBuzz = now;
tone(BUZZER_PIN, 1200, 120);
}
// ---- Button: cycle screens ----
bool btn = digitalRead(BTN_PIN);
if (prevBtn == HIGH && btn == LOW && now - dbBtn > 200) {
screen = (screen + 1) % 3;
dbBtn = now;
lcd.clear();
switch (screen) {
case 0: showScreenTemp(); break;
case 1: showScreenHeatIdx(); break;
case 2: showScreenVent(); break;
}
}
prevBtn = btn;
delay(20);
}