Esp32 climate control help !

Hi all

I'm doing a climate control with my esp32.

Cut the story short , humidity, temperature control with heating , and inlet fans.

What is happening is , the board will randomly reset. Initial I thought it was the relay opening would sent spiked back to the esp , and restart it. I changed them for SSRs. It did work. The esp stop restarting everytime I closed a relay.

I thought things would be sorted , but the esp will get into faulty state even when nothing is working or switching ( after a few hours ).
The AC cables are separated from the DC.

But , the power supply ( kind of laptop PSU) is connected to the same mains as the HV side. ( Could be some spikes for being in the same mains ?)

I've got a DHT22 with probably a 2 meter cable, do you guys think that would be an issue ?

The code works quite good , until the esp resets it self .

Maybe the JSON files are too small to hold the data ? And restart the esp?

But at the same time , I'm not storing any date , aparte from WiFi credentials , Setpoints , and calibration values ....

I'm wondering if this would be a wiring issue , or a code issue. But at this point , I think it should be a code issues.

Any advice is appreciated!

Based on what I can see that red wire looks short, and the code at line 84 should be fixed.
Seriously, read the pinned post re how to get the best from the forum.
p.s. 2 Metres? research what the acceptable length is for I2C.

3 Likes

dont be snippy :joy:

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

What I want to know is how much testing did you do as you developed this system and did the current problem only pop up after the last component was added to the system?

2 Likes

hi Paul

thanks for the reply .

yes , if i have everything set up without 220v , everything works perfect.

quite a hit and miss kind of thing... once i connected everything , the esp kept reseting everytime i turn on the heater. ( found out pin 12 is used for reboots or something ?) so changed for the 26 and stop that issue.

meanwhile still had random issues of the relays send back spikes, change them for ssrs , which did work.

but now , VERY randomly , the esp freezes. i can switch on and off whatever i want , and works fine. and from nowhere the board just freezes. ( i cant do nothing on the webpage , i try to turn on and off stuff and nothing , and lose all my temp and humd readings) .

Your wiring appears to be part of the problem. I’ll take a SWAG (Scientific Wild-Ass Guess) and suggest that you may not have used zero-crossing solid-state relays.

Additionally, the power supply could be contributing to the issue. Ensure it is properly bypassed, and keep the leads as short as possible. I’m assuming the power supply is located in the box on the left.

For better noise isolation, I recommend placing the ESP module inside a grounded metal enclosure within the system to help mitigate interference.

This sounds like it could be related to EMI, either conducted or radiated, not sure. While I can't confirm this is the problem without more information, I also can't rule it out. Most MOS logic circuits are fast and sensitive to edge transitions. Minimizing lead lengths (which act as antennas) to less than 10 inches (25 cm) is crucial, as longer leads can pick up frequencies up to about 750 MHz, not accounting for reflections and other effects.

Tips to Reduce EMI:

  1. Keep Connections Short: Both transmitters and receivers are affected by antenna length—longer antennas have greater gain and can pick up more interference. Use the KISS principle: Keep It Short & Simple.
  2. Avoid Parallel Lines: Ensure power and logic lines are not running parallel to each other to minimize crosstalk and interference.
  3. Use a Zero-Crossing Solid-State Relay: This can be a simple and effective solution to reduce EMI, especially when dealing with AC loads.

Historical Context: This issue has been known since the early 1960s with TCTL (Transistor-Coupled Transistor Logic), later known as TTL (Transistor-Transistor Logic). It prompted the development of line drivers and other technologies to manage EMI.

For further reading on related topics, check out these resources as the SSRs probably have triacs in them:

  • Triac Principles and Circuits, Part 1: Learn about triacs, which are often used in switching applications and can be susceptible to EMI.
    Read Part 1
  • Triac Principles and Circuits, Part 2: This article continues exploring triac applications and EMI considerations.
    Read Part 2
  • Arduino Forum Discussion on Flashing LEDs: An example of EMI considerations in a practical Arduino project.
3 Likes

Hi gilshultz

This was a great reply!

ngl EMI is still quite a grey topic for me, and this reply def help with that.

pretty sure , the SSRs im using are zero crossing , please see the link:

i actually used to have the esp inside of a metal case , i took it out , and put it there , cuz i thought it would be better ... ground the case to the main earth ?

i didnt know this 25cm thing... def a good advice. i think the only cables ive got bigger than that would be the PSU and the temp sensor( im prob lying , but i will check in a bit).
Not sure what im going to do with that cable , but with the sensor im going take a differente aproach.

im going to use another board (i feel like use another esp just for this is waste, but it may need to be ) , and use it to send the data to the main esp , and control the system from there. that would cut 3 huge cables off , and the code should not be that bad to change.

i just dont know what im going to do with the 12v cable.... i dont have that much space , and that is a bloody issue for sure.

edit: this may be weird. but , if i use say for example , cat 5 cable , would not that stop the emi ? they are shielded . im actually curious .

also , what do you mean by the PSU bein properly bypassed ?

OK .... i have some developments.

i may need to change my narrative here lol

well , i changed the psu for my bench psu ( old deskop psu with a cool amperimeter ) , just so i could eliminate the big cable.
i didnt notice any change tbh.

BUT, i notice the esp doesnt freeze out of thin air, but only when i switch off something on the fuse terminals ( i guess the switch causes a big spike that shuts down the board) . this is where things gets interesting . ive put a fail safe mode in the code , if the sensor is faulty the fans will run at 100, ive tested this 100s times , and works if i unplug the vcc , ground or signal from the sensor. if i connect it back , it will come back to his original state. which is not the case in here . i open the fuse terminal , the board enters in fail safe , and only if i turn the board off and on will get readings again.

and on top of that , i can actually use the overide bottons to open and close the relays , but the main logic doesnt start , unless i unplug the board ( reset doesnt work ).

hope this made sense

edit: stupid guess , can a spike shut down half of the board ? like for exemple stop th eeprom , so the main.ido cant get the data from the config .h ?

Hi, @pcanozo
Welcome to the forum.

I notice you have the DC supply from the PC pack running across the top of your cabinets and actually close to the switched AC current.
The power pack is on the mains side of the cabinets, I'd place it on the controller side.

Adding some extra bypassing on the power supply inside the cabinet may also help.

Tom.. :smiley: :+1: :coffee: :australia:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.