#include <WiFi.h>
#include <WiFiManager.h>
#include <Wire.h>
#include <SensirionI2cSht4x.h>
#include <Adafruit_NeoPixel.h>
#include <math.h>
// --- Pin Definitions ---
#define PIN_NEOPIXEL 0
#define NUM_LEDS 9
#define PIN_FAN_PWM 4
// SHT40 Inside (Hardware I2C)
#define SDA_IN 8
#define SCL_IN 9
// SHT40 Outside (Software I2C)
#define SDA_OUT 5
#define SCL_OUT 6
// --- Global Objects ---
Adafruit_NeoPixel strip(NUM_LEDS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
TwoWire BusIn = TwoWire(0);
TwoWire BusOut = TwoWire(1);
SensirionI2cSht4x shtIn;
SensirionI2cSht4x shtOut;
// PWM Settings for 120mm Fan
const int fanChannel = 0;
const int freq = 25000;
const int resolution = 8;
// --- Logic Constants ---
const float HUM_TARGET_MIN = 50.0; // Don't vent if inside is already dry (<50%)
const float AH_MARGIN = 0.5; // Inside must be at least 0.5g/m3 wetter than outside
const int MIN_FAN_PWM = 100; // Minimum torque to spin the fan
// --- Helper: Absolute Humidity Calculation (g/m3) ---
float calculateAH(float t, float rh) {
// Magnus-Tetens formula for Saturation Vapor Pressure
float svp = 6.112 * exp((17.67 * t) / (t + 243.5));
float vp = svp * (rh / 100.0);
return (vp * 216.74) / (t + 273.15);
}
void setup() {
// 1. Power Stability Delay
delay(500);
Serial.begin(115200);
// 2. Initialize LEDs (Yellow = WiFi Setup Mode)
strip.begin();
strip.setBrightness(40);
for(int i=0; i<NUM_LEDS; i++) strip.setPixelColor(i, 255, 150, 0);
strip.show();
// 3. WiFi Manager (AP Mode: "ESP32_Venting_Setup")
WiFiManager wm;
if(!wm.autoConnect("ESP32_Venting_Setup")) {
Serial.println("WiFi Timeout - Continuing Offline");
} else {
Serial.println("WiFi Connected!");
}
// 4. Initialize Sensors
// Boot indoor and outdoor SHT40 on separate I2C buses to avoid address conflict
BusIn.begin(SDA_IN, SCL_IN);
BusOut.begin(SDA_OUT, SCL_OUT);
shtIn.begin(BusIn, 0x44);
shtOut.begin(BusOut, 0x44);
// 5. Initialize PWM Fan
ledcSetup(fanChannel, freq, resolution);
ledcAttachPin(PIN_FAN_PWM, fanChannel);
Serial.println("System Ready.");
}
void loop() {
float tIn, hIn, tOut, hOut;
uint16_t error;
// Read Sensors
shtIn.measureHighPrecision(tIn, hIn);
shtOut.measureHighPrecision(tOut, hOut);
// Calculate Absolute Humidity
float ahIn = calculateAH(tIn, hIn);
float ahOut = calculateAH(tOut, hOut);
int pwmValue = 0;
int litPixels = 0;
// --- VENTING LOGIC ---
// If Inside > 50% RH AND Inside has more water mass than Outside
if (hIn > HUM_TARGET_MIN && ahIn > (ahOut + AH_MARGIN)) {
float diff = ahIn - ahOut;
// Map water difference (0.5g to 5.0g) to PWM (100 to 255)
pwmValue = map(constrain(diff, 0.5, 5.0), 0.5, 5.0, MIN_FAN_PWM, 255);
// Map PWM to number of LEDs (1 to 10)
litPixels = map(pwmValue, MIN_FAN_PWM, 255, 1, NUM_LEDS);
} else {
pwmValue = 0;
litPixels = 0;
}
ledcWrite(fanChannel, pwmValue);
// --- NEOPIXEL UI ---
// Color based on INSIDE Relative Humidity
uint8_t r, g, b;
if (hIn < 45) {
r = 0; g = 255; b = 200; // Cyan: Dry
} else if (hIn < 60) {
r = 0; g = 50; b = 255; // Blue: Good
} else {
r = 255; g = 0; b = 150; // Magenta: Humid
}
strip.clear();
if (litPixels > 0) {
for(int i = 0; i < litPixels; i++) {
strip.setPixelColor(i, r, g, b);
}
} else {
// System heartbeat: 1 dim pixel showing current humidity color
strip.setPixelColor(0, r/6, g/6, b/6);
}
strip.show();
// Debug Output
Serial.printf("IN: %.1fC %.1f%% (%.2fg) | OUT: %.1fC %.1f%% (%.2fg) | PWM: %d\n",
tIn, hIn, ahIn, tOut, hOut, ahOut, pwmValue);
delay(5000); // 5-second update interval
}Loading
esp32-c3-devkitm-1
esp32-c3-devkitm-1