dont be snippy 
on a serious note tho , i think you have all the information below ! let me know if you need anything else!
the light shouldnt be part of the circuit on this board ( but im just doing an experiment, i may use another board to the light control (im a noob , and this code starts to be wayyyy out of my confort zone)
device connected : 600w bulb (ssr )
40w heater ( induction heater i think, also on a SSR)
extract fan i would say around 300w ( 1amp max current if im not mistaken, give and take)
for now i only have this 3 SSRs in the loop so i can narrow down the problems.
i think is pretty explanatory the pictures:
mains RCD , goes to terminal blocks for overcurrent protection , and to the ssrs.
i have one socket permanlty on , and one intake fan also directly from the mains.
regarding the IC2 cable lenght , it says it shoulndt be more than 50cm . but the readings looks very stable and are the same as my calibrated fluke temp sensor. BUT i know will be more prone to IMF issues, and the cable route is near the AC switch for the light of the room ( standard wall switch). but even with the lights off , it restarts itself .
I alsi bought cooper tape to use it as EMF shield , and put it beetn the rcd and the 12v supply terminals ( just in case )
#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include "config.h"
// Global objects initialization
DHT dht(DHT22_PIN, DHT22);
AsyncWebServer server(80);
Config config;
// NTP Client setup
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");
void loadConfiguration() {
File configFile = SPIFFS.open("/config.json", "r");
if (configFile) {
JsonDocument doc;
DeserializationError error = deserializeJson(doc, configFile);
if (!error) {
strlcpy(config.wifi_ssid, doc["wifi_ssid"] | "YOUR_SSID", sizeof(config.wifi_ssid));
strlcpy(config.wifi_password, doc["wifi_password"] | "YOUR_PASSWORD", sizeof(config.wifi_password));
config.temp_calibration = doc["temp_calibration"] | 0.0;
config.humidity_calibration = doc["humidity_calibration"] | 0.0;
config.temp_setpoint = doc["temp_setpoint"] | 25.0;
config.temp_hysteresis = doc["temp_hysteresis"] | 0.5;
config.heat_setpoint = doc["heat_setpoint"] | 20.0;
config.heat_hysteresis = doc["heat_hysteresis"] | 0.5;
config.heat_cycle_on = doc["heat_cycle_on"] | 600000;
config.heat_cycle_off = doc["heat_cycle_off"] | 300000;
config.intake1_start_hour = doc["intake1_start_hour"] | 8;
config.intake1_end_hour = doc["intake1_end_hour"] | 20;
config.intake2_start_hour = doc["intake2_start_hour"] | 8;
config.intake2_end_hour = doc["intake2_end_hour"] | 20;
config.intake1_enabled = doc["intake1_enabled"] | true;
config.intake2_enabled = doc["intake2_enabled"] | true;
config.timezone_offset = doc["timezone_offset"] | -18000;
config.humidity_setpoint = doc["humidity_setpoint"] | 60.0;
config.humidity_hysteresis = doc["humidity_hysteresis"] | 2.0;
// Load device states
JsonObject deviceStates = doc["device_states"].as<JsonObject>();
if (!deviceStates.isNull()) {
config.heater_state.mode = deviceStates["heater_mode"] | DEVICE_MODE_AUTO;
config.exhaust_fan_state.mode = deviceStates["exhaust_mode"] | DEVICE_MODE_AUTO;
config.intake1_state.mode = deviceStates["intake1_mode"] | DEVICE_MODE_AUTO;
config.intake2_state.mode = deviceStates["intake2_mode"] | DEVICE_MODE_AUTO;
config.dehumidifier_state.mode = deviceStates["dehumidifier_mode"] | DEVICE_MODE_AUTO;
}
}
configFile.close();
}
}
void saveConfiguration() {
File configFile = SPIFFS.open("/config.json", "w");
if (configFile) {
JsonDocument doc;
doc["wifi_ssid"] = config.wifi_ssid;
doc["wifi_password"] = config.wifi_password;
doc["temp_calibration"] = config.temp_calibration;
doc["humidity_calibration"] = config.humidity_calibration;
doc["temp_setpoint"] = config.temp_setpoint;
doc["temp_hysteresis"] = config.temp_hysteresis;
doc["heat_setpoint"] = config.heat_setpoint;
doc["heat_hysteresis"] = config.heat_hysteresis;
doc["heating_enabled"] = config.heating_enabled;
doc["heat_cycle_on"] = config.heat_cycle_on;
doc["heat_cycle_off"] = config.heat_cycle_off;
doc["intake1_start_hour"] = config.intake1_start_hour;
doc["intake1_end_hour"] = config.intake1_end_hour;
doc["intake2_start_hour"] = config.intake2_start_hour;
doc["intake2_end_hour"] = config.intake2_end_hour;
doc["intake1_enabled"] = config.intake1_enabled;
doc["intake2_enabled"] = config.intake2_enabled;
doc["timezone_offset"] = config.timezone_offset;
doc["humidity_setpoint"] = config.humidity_setpoint;
doc["humidity_hysteresis"] = config.humidity_hysteresis;
doc["dehumidifier_enabled"] = config.dehumidifier_enabled;
// Save device states
auto deviceStates = doc["device_states"].to<JsonObject>();
deviceStates["heater_mode"] = config.heater_state.mode;
deviceStates["exhaust_mode"] = config.exhaust_fan_state.mode;
deviceStates["intake1_mode"] = config.intake1_state.mode;
deviceStates["intake2_mode"] = config.intake2_state.mode;
deviceStates["dehumidifier_mode"] = config.dehumidifier_state.mode;
serializeJson(doc, configFile);
configFile.close();
Serial.println("Configuration saved");
}
}
void setupWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(config.wifi_ssid, config.wifi_password);
Serial.print("Connecting to WiFi");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi connected");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWiFi connection failed!");
}
}
void setupTime() {
timeClient.begin();
timeClient.setTimeOffset(config.timezone_offset);
Serial.println("Attempting to get time from NTP server");
int attempts = 0;
while (!timeClient.update() && attempts < 10) {
timeClient.forceUpdate();
Serial.print(".");
delay(500);
attempts++;
}
if (timeClient.isTimeSet()) {
Serial.println("\nTime synchronized with NTP server");
Serial.print("Current time: ");
Serial.println(timeClient.getFormattedTime());
} else {
Serial.println("\nFailed to get time from NTP server");
}
}
void setup() {
Serial.begin(115200);
// Initialize SPIFFS
if (!SPIFFS.begin(true)) {
Serial.println("SPIFFS initialization failed!");
return;
}
// Initialize DHT22
dht.begin();
// Setup pins
pinMode(EXHAUST_FAN_PIN, OUTPUT);
pinMode(HEATER_PIN, OUTPUT);
pinMode(INTAKE_FAN1_PIN, OUTPUT);
pinMode(INTAKE_FAN2_PIN, OUTPUT);
pinMode(DEHUMIDIFIER_PIN, OUTPUT);
// Start with everything off (relays deactivated)
digitalWrite(EXHAUST_FAN_PIN, HIGH);
digitalWrite(HEATER_PIN, HIGH);
digitalWrite(INTAKE_FAN1_PIN, HIGH);
digitalWrite(INTAKE_FAN2_PIN, HIGH);
digitalWrite(DEHUMIDIFIER_PIN, HIGH);
// Initialize device states to AUTO mode and OFF state
config.heater_state.mode = DEVICE_MODE_AUTO;
config.exhaust_fan_state.mode = DEVICE_MODE_AUTO;
config.intake1_state.mode = DEVICE_MODE_AUTO;
config.intake2_state.mode = DEVICE_MODE_AUTO;
config.dehumidifier_state.mode = DEVICE_MODE_AUTO;
config.heater_state.currentState = false;
config.exhaust_fan_state.currentState = false;
config.intake1_state.currentState = false;
config.intake2_state.currentState = false;
config.dehumidifier_state.currentState = false;
// Load configuration
loadConfiguration();
// Connect to WiFi
setupWiFi();
// Initialize and setup time
setupTime();
// Setup web server routes
setupWebServer();
Serial.println("Setup completed!");
Serial.println("Current configuration:");
Serial.print("Temperature setpoint: "); Serial.println(config.temp_setpoint);
Serial.print("Heat setpoint: "); Serial.println(config.heat_setpoint);
Serial.print("Temperature hysteresis: "); Serial.println(config.temp_hysteresis);
Serial.print("Heat hysteresis: "); Serial.println(config.heat_hysteresis);
Serial.print("Humidity setpoint: "); Serial.println(config.humidity_setpoint);
Serial.print("Humidity hysteresis: "); Serial.println(config.humidity_hysteresis);
}
void loop() {
static unsigned long lastNTPUpdate = 0;
static bool timeInitialized = false;
static unsigned long lastValidReading = 0;
static unsigned long lastHeaterStateChange = 0;
static const unsigned long SENSOR_TIMEOUT = 10000; // 10 seconds timeout
// Update time every 30 minutes once initialized, every minute until then
unsigned long updateInterval = timeInitialized ? 1800000 : 60000;
if (millis() - lastNTPUpdate >= updateInterval) {
if (timeClient.update()) {
timeInitialized = true;
Serial.print("Time updated. Current hour: ");
Serial.println(timeClient.getHours());
}
lastNTPUpdate = millis();
}
float currentTemp = dht.readTemperature() + config.temp_calibration;
float currentHumidity = dht.readHumidity() + config.humidity_calibration;
bool validReading = !isnan(currentTemp) && !isnan(currentHumidity);
int currentHour = timeClient.getHours();
if (validReading) {
lastValidReading = millis();
} else {
// Check if we've exceeded the timeout
if (millis() - lastValidReading >= SENSOR_TIMEOUT) {
// Failsafe mode - only if in auto mode
if (config.exhaust_fan_state.mode == DEVICE_MODE_AUTO) {
digitalWrite(EXHAUST_FAN_PIN, LOW); // Turn ON fan in failsafe
delay(100);
config.exhaust_fan_state.currentState = true;
}
if (config.intake1_state.mode == DEVICE_MODE_AUTO) {
digitalWrite(INTAKE_FAN1_PIN, LOW); // Turn ON fan in failsafe
delay(100);
config.intake1_state.currentState = true;
}
if (config.intake2_state.mode == DEVICE_MODE_AUTO) {
digitalWrite(INTAKE_FAN2_PIN, LOW); // Turn ON fan in failsafe
delay(100);
config.intake2_state.currentState = true;
}
if (config.heater_state.mode == DEVICE_MODE_AUTO) {
digitalWrite(HEATER_PIN, HIGH); // Turn OFF heater in failsafe
delay(100);
config.heater_state.currentState = false;
}
if (config.dehumidifier_state.mode == DEVICE_MODE_AUTO) {
digitalWrite(DEHUMIDIFIER_PIN, HIGH); // Turn OFF dehumidifier in failsafe
delay(100);
config.dehumidifier_state.currentState = false;
}
Serial.println("FAILSAFE MODE: Sensor readings invalid");
delay(2000);
return;
}
}
// Debug print for time and states
static unsigned long lastDebugPrint = 0;
if (millis() - lastDebugPrint >= 10000) { // Print every 10 seconds
Serial.println("------- Debug Info -------");
Serial.print("Current Hour: "); Serial.println(currentHour);
Serial.print("Temperature: "); Serial.println(currentTemp);
Serial.print("Humidity: "); Serial.println(currentHumidity);
Serial.print("Sensor Valid: "); Serial.println(validReading ? "Yes" : "No");
Serial.print("Heater State: "); Serial.println(config.heater_state.currentState ? "ON" : "OFF");
if (config.heater_state.currentState) {
Serial.print("Time since heater state change: ");
Serial.println((millis() - lastHeaterStateChange) / 1000);
}
lastDebugPrint = millis();
}
// Exhaust fan control (for cooling)
if (config.exhaust_fan_state.mode == DEVICE_MODE_AUTO) {
if (currentTemp >= config.temp_setpoint + config.temp_hysteresis) {
digitalWrite(EXHAUST_FAN_PIN, LOW); // Turn ON by setting LOW
delay(100);
config.exhaust_fan_state.currentState = true;
Serial.println("Exhaust fan turned ON - Temperature: " + String(currentTemp) + "°C");
} else if (currentTemp <= config.temp_setpoint - config.temp_hysteresis) {
digitalWrite(EXHAUST_FAN_PIN, HIGH); // Turn OFF by setting HIGH
delay(100);
config.exhaust_fan_state.currentState = false;
Serial.println("Exhaust fan turned OFF - Temperature: " + String(currentTemp) + "°C");
}
}
// Heater control in auto mode with cycling
if (config.heater_state.mode == DEVICE_MODE_AUTO && validReading) {
unsigned long currentTime = millis();
if (currentTemp < config.heat_setpoint - config.heat_hysteresis) {
// Temperature is too low, manage heating cycle
if (!config.heater_state.currentState) {
// Check if OFF time has elapsed
if (currentTime - lastHeaterStateChange >= config.heat_cycle_off) {
digitalWrite(HEATER_PIN, LOW); // Turn ON by setting LOW
delay(100);
config.heater_state.currentState = true;
lastHeaterStateChange = currentTime;
Serial.println("Heater turned ON - Temperature: " + String(currentTemp) +
"°C (After " + String(config.heat_cycle_off/1000) + "s OFF)");
}
} else {
// Check if ON time has elapsed
if (currentTime - lastHeaterStateChange >= config.heat_cycle_on) {
digitalWrite(HEATER_PIN, HIGH); // Turn OFF by setting HIGH
delay(100);
config.heater_state.currentState = false;
lastHeaterStateChange = currentTime;
Serial.println("Heater cycle OFF - Temperature: " + String(currentTemp) +
"°C (After " + String(config.heat_cycle_on/1000) + "s ON)");
}
}
} else if (currentTemp >= config.heat_setpoint + config.heat_hysteresis) {
// Temperature is high enough, turn off heater
if (config.heater_state.currentState) {
digitalWrite(HEATER_PIN, HIGH); // Turn OFF by setting HIGH
delay(100);
config.heater_state.currentState = false;
lastHeaterStateChange = currentTime;
Serial.println("Heater turned OFF - Temperature above setpoint: " +
String(currentTemp) + "°C");
}
}
}
// Intake fans schedule control
if (config.intake1_state.mode == DEVICE_MODE_AUTO && config.intake1_enabled) {
bool shouldBeOn = (currentHour >= config.intake1_start_hour &&
currentHour < config.intake1_end_hour);
if (shouldBeOn != config.intake1_state.currentState) {
digitalWrite(INTAKE_FAN1_PIN, shouldBeOn ? LOW : HIGH);
delay(100);
config.intake1_state.currentState = shouldBeOn;
Serial.println("Intake Fan 1 turned " + String(shouldBeOn ? "ON" : "OFF") +
" (Time: " + String(currentHour) + ":00)");
}
}
if (config.intake2_state.mode == DEVICE_MODE_AUTO && config.intake2_enabled) {
bool shouldBeOn = (currentHour >= config.intake2_start_hour &&
currentHour < config.intake2_end_hour);
if (shouldBeOn != config.intake2_state.currentState) {
digitalWrite(INTAKE_FAN2_PIN, shouldBeOn ? LOW : HIGH);
delay(100);
config.intake2_state.currentState = shouldBeOn;
Serial.println("Intake Fan 2 turned " + String(shouldBeOn ? "ON" : "OFF") +
" (Time: " + String(currentHour) + ":00)");
}
}
// Dehumidifier control
if (config.dehumidifier_state.mode == DEVICE_MODE_AUTO && validReading) {
if (currentHumidity >= config.humidity_setpoint + config.humidity_hysteresis) {
digitalWrite(DEHUMIDIFIER_PIN, LOW); // Turn ON by setting LOW
delay(100);
config.dehumidifier_state.currentState = true;
Serial.println("Dehumidifier turned ON - Humidity: " + String(currentHumidity) + "%");
} else if (currentHumidity <= config.humidity_setpoint - config.humidity_hysteresis) {
digitalWrite(DEHUMIDIFIER_PIN, HIGH); // Turn OFF by setting HIGH
delay(100);
config.dehumidifier_state.currentState = false;
Serial.println("Dehumidifier turned OFF - Humidity: " + String(currentHumidity) + "%");
}
}
delay(2000); // Check every 2 seconds
}
void setupWebServer() {
// Serve static files
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
// API endpoints for sensor data
server.on("/api/sensors", HTTP_GET, [](AsyncWebServerRequest *request) {
float temperature = dht.readTemperature() + config.temp_calibration;
float humidity = dht.readHumidity() + config.humidity_calibration;
bool validReading = !isnan(temperature) && !isnan(humidity);
String response = "{\"temperature\":" + String(validReading ? temperature : 0) +
",\"humidity\":" + String(validReading ? humidity : 0) +
",\"sensor_status\":\"" + String(validReading ? "ok" : "error") + "\"," +
"\"fan_state\":" + String(config.exhaust_fan_state.currentState) +
",\"fan_mode\":\"" + String(config.exhaust_fan_state.mode == DEVICE_MODE_AUTO ? "auto" :
config.exhaust_fan_state.mode == DEVICE_MODE_ON ? "on" : "off") + "\"," +
"\"heater_state\":" + String(config.heater_state.currentState) +
",\"heater_mode\":\"" + String(config.heater_state.mode == DEVICE_MODE_AUTO ? "auto" :
config.heater_state.mode == DEVICE_MODE_ON ? "on" : "off") + "\"," +
"\"dehumidifier_state\":" + String(config.dehumidifier_state.currentState) +
",\"dehumidifier_mode\":\"" + String(config.dehumidifier_state.mode == DEVICE_MODE_AUTO ? "auto" :
config.dehumidifier_state.mode == DEVICE_MODE_ON ? "on" : "off") + "\"," +
"\"intake1_state\":" + String(config.intake1_state.currentState) +
",\"intake1_mode\":\"" + String(config.intake1_state.mode == DEVICE_MODE_AUTO ? "auto" :
config.intake1_state.mode == DEVICE_MODE_ON ? "on" : "off") + "\"," +
"\"intake2_state\":" + String(config.intake2_state.currentState) +
",\"intake2_mode\":\"" + String(config.intake2_state.mode == DEVICE_MODE_AUTO ? "auto" :
config.intake2_state.mode == DEVICE_MODE_ON ? "on" : "off") + "\"}";
request->send(200, "application/json", response);
});
// API endpoint for device control
server.on("/api/device-control", HTTP_POST, [](AsyncWebServerRequest *request) {
if (!request->hasParam("device", true) || !request->hasParam("mode", true)) {
request->send(400, "text/plain", "Missing parameters");
return;
}
String device = request->getParam("device", true)->value();
String mode = request->getParam("mode", true)->value();
uint8_t modeValue = mode == "auto" ? DEVICE_MODE_AUTO :
mode == "on" ? DEVICE_MODE_ON : DEVICE_MODE_OFF;
// Update device mode and state with delays for relay switching
if (device == "heater") {
config.heater_state.mode = modeValue;
if (modeValue != DEVICE_MODE_AUTO) {
digitalWrite(HEATER_PIN, modeValue == DEVICE_MODE_ON ? LOW : HIGH);
delay(100); // Delay after heater relay switch
config.heater_state.currentState = modeValue == DEVICE_MODE_ON;
}
} else if (device == "exhaustFan") {
config.exhaust_fan_state.mode = modeValue;
if (modeValue != DEVICE_MODE_AUTO) {
digitalWrite(EXHAUST_FAN_PIN, modeValue == DEVICE_MODE_ON ? LOW : HIGH);
delay(50); // Smaller delay for fan relay
config.exhaust_fan_state.currentState = modeValue == DEVICE_MODE_ON;
}
} else if (device == "dehumidifier") {
config.dehumidifier_state.mode = modeValue;
if (modeValue != DEVICE_MODE_AUTO) {
digitalWrite(DEHUMIDIFIER_PIN, modeValue == DEVICE_MODE_ON ? LOW : HIGH);
delay(50); // Smaller delay for dehumidifier relay
config.dehumidifier_state.currentState = modeValue == DEVICE_MODE_ON;
}
} else if (device == "intake1") {
config.intake1_state.mode = modeValue;
if (modeValue != DEVICE_MODE_AUTO) {
digitalWrite(INTAKE_FAN1_PIN, modeValue == DEVICE_MODE_ON ? LOW : HIGH);
delay(50); // Smaller delay for fan relay
config.intake1_state.currentState = modeValue == DEVICE_MODE_ON;
}
} else if (device == "intake2") {
config.intake2_state.mode = modeValue;
if (modeValue != DEVICE_MODE_AUTO) {
digitalWrite(INTAKE_FAN2_PIN, modeValue == DEVICE_MODE_ON ? LOW : HIGH);
delay(50); // Smaller delay for fan relay
config.intake2_state.currentState = modeValue == DEVICE_MODE_ON;
}
}
saveConfiguration();
request->send(200);
});
// API endpoints for settings
server.on("/api/air-control", HTTP_GET, [](AsyncWebServerRequest *request) {
String response = "{\"temp_setpoint\":" + String(config.temp_setpoint) +
",\"heat_setpoint\":" + String(config.heat_setpoint) +
",\"temp_hysteresis\":" + String(config.temp_hysteresis) +
",\"heat_hysteresis\":" + String(config.heat_hysteresis) +
",\"heating_enabled\":" + String(config.heating_enabled) +
",\"heat_cycle_on\":" + String(config.heat_cycle_on) +
",\"heat_cycle_off\":" + String(config.heat_cycle_off) +
",\"humidity_setpoint\":" + String(config.humidity_setpoint) +
",\"humidity_hysteresis\":" + String(config.humidity_hysteresis) +
",\"dehumidifier_enabled\":" + String(config.dehumidifier_enabled) + "}";
request->send(200, "application/json", response);
});
server.on("/api/air-control", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("temp_setpoint", true)) {
config.temp_setpoint = request->getParam("temp_setpoint", true)->value().toFloat();
}
if (request->hasParam("heat_setpoint", true)) {
config.heat_setpoint = request->getParam("heat_setpoint", true)->value().toFloat();
}
if (request->hasParam("temp_hysteresis", true)) {
config.temp_hysteresis = request->getParam("temp_hysteresis", true)->value().toFloat();
}
if (request->hasParam("heat_hysteresis", true)) {
config.heat_hysteresis = request->getParam("heat_hysteresis", true)->value().toFloat();
}
if (request->hasParam("heating_enabled", true)) {
config.heating_enabled = request->getParam("heating_enabled", true)->value() == "true";
}
if (request->hasParam("heat_cycle_on", true)) {
config.heat_cycle_on = request->getParam("heat_cycle_on", true)->value().toInt();
}
if (request->hasParam("heat_cycle_off", true)) {
config.heat_cycle_off = request->getParam("heat_cycle_off", true)->value().toInt();
}
// Humidity settings
if (request->hasParam("humidity_setpoint", true)) {
config.humidity_setpoint = request->getParam("humidity_setpoint", true)->value().toFloat();
}
if (request->hasParam("humidity_hysteresis", true)) {
config.humidity_hysteresis = request->getParam("humidity_hysteresis", true)->value().toFloat();
}
if (request->hasParam("dehumidifier_enabled", true)) {
config.dehumidifier_enabled = request->getParam("dehumidifier_enabled", true)->value() == "true";
}
saveConfiguration();
request->send(200);
});
// Fan schedule endpoints
server.on("/api/fan-schedule", HTTP_GET, [](AsyncWebServerRequest *request) {
String response = "{\"intake1_start_hour\":" + String(config.intake1_start_hour) +
",\"intake1_end_hour\":" + String(config.intake1_end_hour) +
",\"intake2_start_hour\":" + String(config.intake2_start_hour) +
",\"intake2_end_hour\":" + String(config.intake2_end_hour) +
",\"intake1_enabled\":" + String(config.intake1_enabled ? "true" : "false") +
",\"intake2_enabled\":" + String(config.intake2_enabled ? "true" : "false") + "}";
request->send(200, "application/json", response);
});
server.on("/api/fan-schedule", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("intake1_enabled", true)) {
config.intake1_enabled = request->getParam("intake1_enabled", true)->value() == "true";
}
if (request->hasParam("intake1_start_hour", true)) {
config.intake1_start_hour = request->getParam("intake1_start_hour", true)->value().toInt();
}
if (request->hasParam("intake1_end_hour", true)) {
config.intake1_end_hour = request->getParam("intake1_end_hour", true)->value().toInt();
}
if (request->hasParam("intake2_enabled", true)) {
config.intake2_enabled = request->getParam("intake2_enabled", true)->value() == "true";
}
if (request->hasParam("intake2_start_hour", true)) {
config.intake2_start_hour = request->getParam("intake2_start_hour", true)->value().toInt();
}
if (request->hasParam("intake2_end_hour", true)) {
config.intake2_end_hour = request->getParam("intake2_end_hour", true)->value().toInt();
}
saveConfiguration();
request->send(200);
});
// Calibration endpoints
server.on("/api/calibration", HTTP_GET, [](AsyncWebServerRequest *request) {
String response = "{\"temp_calibration\":" + String(config.temp_calibration) +
",\"humidity_calibration\":" + String(config.humidity_calibration) + "}";
request->send(200, "application/json", response);
});
server.on("/api/calibration", HTTP_POST, [](AsyncWebServerRequest *request) {
if (request->hasParam("temp", true)) {
config.temp_calibration = request->getParam("temp", true)->value().toFloat();
}
if (request->hasParam("humidity", true)) {
config.humidity_calibration = request->getParam("humidity", true)->value().toFloat();
}
saveConfiguration();
request->send(200);
});
server.begin();
Serial.println("Web server started");
}
Config/h file
// config.h
#ifndef CONFIG_H
#define CONFIG_H
// Device state structure
struct DeviceState {
uint8_t mode = 0; // 0 = auto, 1 = on, 2 = off
bool currentState = false;
};
// WiFi Settings (these will be stored in SPIFFS)
struct Config {
char wifi_ssid[32] = "xxxxxx"; // Default SSID
char wifi_password[64] = "xxxxxxxxxx"; // Default password
// Temperature settings
float temp_calibration = 0.0; // Temperature sensor calibration offset
float temp_setpoint = 25.0; // Default 25°C cooling temperature
float temp_hysteresis = 0.5; // Default 0.5°C hysteresis for cooling
float heat_setpoint = 20.0; // Default 20°C heating temperature
float heat_hysteresis = 0.5; // Default 0.5°C hysteresis for heating
// Heating control
bool heating_enabled = false; // Heating system enabled/disabled
unsigned long heat_cycle_on = 600000; // Heat ON time in ms (10 minutes)
unsigned long heat_cycle_off = 300000;// Heat OFF time in ms (5 minutes)
// Humidity settings
float humidity_calibration = 0.0; // Humidity sensor calibration offset
float humidity_setpoint = 60.0; // Default 60% humidity setpoint
float humidity_hysteresis = 2.0; // Default 2% hysteresis
bool dehumidifier_enabled = false; // Dehumidifier control enabled/disabled
// Intake fans schedule
bool intake1_enabled = true; // Intake fan 1 schedule enabled
int intake1_start_hour = 8; // 8 AM
int intake1_end_hour = 20; // 8 PM
bool intake2_enabled = true; // Intake fan 2 schedule enabled
int intake2_start_hour = 8; // 8 AM
int intake2_end_hour = 20; // 8 PM
// Time settings
long timezone_offset = -18000; // Default to EST (-5 hours in seconds)
// Device states
DeviceState heater_state; // Heater control state
DeviceState exhaust_fan_state; // Exhaust fan control state
DeviceState intake1_state; // Intake fan 1 control state
DeviceState intake2_state; // Intake fan 2 control state
DeviceState dehumidifier_state; // Dehumidifier control state
};
// Pin Definitions
#define EXHAUST_FAN_PIN 15 // GPIO15 for exhaust fan
#define HEATER_PIN 26 // GPIO12 for heater
#define DHT22_PIN 4 // GPIO4 for DHT22 sensor
#define INTAKE_FAN1_PIN 13 // GPIO13 for intake fan 1
#define INTAKE_FAN2_PIN 14 // GPIO14 for intake fan 2
#define DEHUMIDIFIER_PIN 2 // GPIO2 for dehumidifier
// Device mode definitions
#define DEVICE_MODE_AUTO 0 // Device in automatic mode
#define DEVICE_MODE_ON 1 // Device forced on
#define DEVICE_MODE_OFF 2 // Device forced off
// Function declarations
void loadConfiguration(); // Load settings from SPIFFS
void saveConfiguration(); // Save settings to SPIFFS
void setupWiFi(); // Initialize WiFi connection
void setupTime(); // Initialize NTP time synchronization
void setupWebServer(); // Setup web server routes
// Make config variable available to other files
extern Config config;
#endif