At the grandma's house, where I currently live, I use a wood-burning central heating unit to stay warm during the winter months. This central heating unit uses a fan to provide fresh air to the fire, which turns off when the water (that's running through the radiators in the house) reaches the operating temperature (90 degrees Celsius). That's great, otherwise my pipes would explode.
What's controlling the fan, you may ask? A little knob on the side of the central heating unit that tells the fan (using a bimetalic strip) how cold the water has to be for it to start doing something.
That's great, but there is a small problem: when the fire dies down eventually, there is no mechanism in place to automatically tell the fan to not turn back on once the water temperature drops below the threshold set earlier by the knob… so you have to always go back to the central heating unit once the fire has started to turn the knob, in essence shutting down the fan for good.
I wanted to fix that :)
There is a lot of logic that controls the icons that are displayed in the interface, I'll stick them below in the code snippets because talking about them is really really boring, I just thought of all the possible cases and coded them in.
Initial checks when ESP32 starts (maybe the user resets it manually):
if (initialTempC >= 30.0) { // If temperature is already high, set servo to triggered position and fan OFF lastServoPos = SERVO_TRIGGER_POS; fanState = false; fireState = "off"; Serial.println("Initial state: Temperature above 30°C - Setting servo to 30° and fan OFF"); // Actually move the servo to 30° position myServo.attach(SERVO_PIN, 700, 2300); myServo.write(SERVO_TRIGGER_POS); delay(700); // Give servo time to move to position myServo.detach(); Serial.println("Servo physically positioned at 30° and detached"); } else { // If temperature is normal at startup, initialize to 0° position lastServoPos = SERVO_RESET_POS; fanState = true; fireState = "starting"; Serial.println("Initial state: Temperature below 30°C - Setting servo to 0° and fan ON"); // Actually move the servo to 0° position myServo.attach(SERVO_PIN, 700, 2300); myServo.write(SERVO_RESET_POS); delay(700); // Give servo time to move to position myServo.detach(); Serial.println("Servo physically positioned at 0° and detached"); // Clean database at startup Serial.println("Performing initial database cleanup..."); cleanupDatabase(); }
Standard checks:
if (Firebase.ready() && signupOK && (millis() - commandCheckPrevMillis > commandCheckDelay || commandCheckPrevMillis == 0)) { commandCheckPrevMillis = millis(); // Check for reset button commands if (Firebase.RTDB.getInt(&fbdo, "/commands/resetServo")) { int resetCommand = fbdo.intData(); Serial.print("Reset command received: "); Serial.println(resetCommand); if (resetCommand > 0) { Serial.println("Reset button pressed - checking if temperature allows reset..."); // Read latest temperature values when reset button is pressed sensors.requestTemperatures(); smokeSensors.requestTemperatures(); tempC = sensors.getTempCByIndex(0); smokeTempC = smokeSensors.getTempCByIndex(0); Serial.print("Water Temperature: "); Serial.print(tempC); Serial.println(" °C"); Serial.print("Smoke Temperature: "); Serial.print(smokeTempC); Serial.println(" °C"); // Check if temperature allows reset if (tempC < 30.0) { // If temperature is normal, set fan ON targetPos = SERVO_RESET_POS; fanState = true; fireState = "starting"; Serial.println("Reset accepted: Temperature is below 30°C - Moving servo to 0°"); // Clean the entire database Serial.println("Clearing all historical data..."); cleanupDatabase(); } else { // If temperature is high, don't allow reset - keep servo at 30° and fan OFF targetPos = SERVO_TRIGGER_POS; fanState = false; fireState = "off"; Serial.println("Reset rejected: Temperature is above 30°C - Servo remains at 30°"); // Even when reset is rejected, update the current data in Firebase immediately updateLatestFirebaseData(tempC, smokeTempC, targetPos); } // Verify that the temperature and servo position follow the rules: // - If temp ≥ 30°C, servo MUST be at 30° (safety critical) // - If reset was accepted, servo MUST be at 0° (just pressed) // - If reset was rejected, servo MUST be at 30° (just verified) Serial.println("RESET VERIFICATION: Checking temperature and servo consistency"); if (tempC >= 30.0 && targetPos != SERVO_TRIGGER_POS) { // This is a safety-critical error and needs immediate correction Serial.println("CRITICAL ERROR: Temperature ≥30°C but servo not at 30°!"); Serial.println("SAFETY ACTION: Overriding to ensure servo at 30° position!"); targetPos = SERVO_TRIGGER_POS; fanState = false; // Physically move servo to safe position myServo.attach(SERVO_PIN, 700, 2300); myServo.write(SERVO_TRIGGER_POS); lastServoPos = SERVO_TRIGGER_POS; delay(700); myServo.detach(); } else if (tempC < 30.0 && resetCommand > 0 && targetPos != SERVO_RESET_POS) { // Only fix position if reset was explicitly requested and temperature allows it Serial.println("ERROR: Reset requested with temp <30°C but servo not at 0°"); Serial.println("Fixing: Moving servo to 0° position"); targetPos = SERVO_RESET_POS; fanState = true; // Physically move servo myServo.attach(SERVO_PIN, 700, 2300); myServo.write(SERVO_RESET_POS); lastServoPos = SERVO_RESET_POS; delay(700); myServo.detach(); } else { Serial.println("VERIFICATION PASSED: Temperature and servo position are consistent"); } // Acknowledge the command by setting it to 0 Firebase.RTDB.setInt(&fbdo, "/commands/resetServo", 0); } } } // Always check smoke temperature - this takes precedence over Start Fire if (smokeTempC > 0) { // Only update if we've read the sensors during this loop if (smokeTempC >= 28.0) { // If smoke temp is high, always show fire icon regardless of fan state fireState = "on"; } else if (fanState) { // If smoke temp is low and fan is ON, show bolt fireState = "starting"; } else { // If smoke temp is low and fan is OFF, show extinguisher fireState = "off"; } } // --- Fan/fire logic based on temperatures --- if (tempC > 0) { // Only update if we've read the sensors during this loop if (tempC >= 30.0) { // CRITICAL: High temperature needs immediate servo action! if (lastServoPos != SERVO_TRIGGER_POS) { // Servo is not in correct position for this high temperature! targetPos = SERVO_TRIGGER_POS; fanState = false; // Always turn fan OFF when temperature is high Serial.println("HIGH TEMP SAFETY: Temperature >= 30°C with servo not at 30° - Moving servo NOW!"); } else { // Servo is already in correct position but emphasize high temp state Serial.println("Temperature still >= 30°C: Maintaining servo at 30° and fan OFF"); } } else if (tempC < 30.0) { // When temperature drops below 30°C, DO NOT automatically move servo to 0° // Only update fan state based on current servo position fanState = (lastServoPos == SERVO_RESET_POS); if (lastServoPos == SERVO_TRIGGER_POS) { Serial.println("Temperature now < 30°C but servo will remain at 30° until reset button is pressed"); } } } // Only move the servo if the target position is different from the current position if (targetPos != lastServoPos) { Serial.print("Servo position change needed: current = "); Serial.print(lastServoPos); Serial.print("°, target = "); Serial.print(targetPos); Serial.println("°"); // Attach servo, move it, then detach to prevent jitter and reduce power consumption myServo.attach(SERVO_PIN, 700, 2300); myServo.write(targetPos); // Update logical state lastServoPos = targetPos; // Update fan state based on servo position // SERVO_RESET_POS (0°) = fan ON, SERVO_TRIGGER_POS (30°) = fan OFF bool previousFanState = fanState; fanState = (targetPos == SERVO_RESET_POS); if (previousFanState != fanState) { Serial.print("Fan state updated to: "); Serial.println(fanState ? "ON" : "OFF"); } // Wait for servo to complete movement delay(500); // Detach servo to prevent jitter and reduce power consumption myServo.detach(); Serial.println("Servo detached after movement"); // Short delay after servo movement for stability delay(500); } // Small delay for loop stability - this doesn't affect command responsiveness delay(200); // Check if it's time to send sensor data (every 1 second) if (signupOK && Firebase.ready() && (millis() - lastSend > sensorDataDelay)) { lastSend = millis(); // Read sensors every time we check (every 1s) sensors.requestTemperatures(); smokeSensors.requestTemperatures(); tempC = sensors.getTempCByIndex(0); smokeTempC = smokeSensors.getTempCByIndex(0); Serial.print("Water Temperature: "); Serial.print(tempC); Serial.println(" °C"); Serial.print("Smoke Temperature: "); Serial.print(smokeTempC); Serial.println(" °C"); // IMPORTANT: Only move servo to 30° when temperature rises above 30°C // DO NOT move servo back to 0° automatically - this only happens with reset button if (tempC >= 30.0 && lastServoPos != SERVO_TRIGGER_POS) { // Critical temperature detected, servo needs to move! Serial.println("CRITICAL: Temperature ≥30°C but servo not at 30° - Moving servo now!"); targetPos = SERVO_TRIGGER_POS; fanState = false; // Move servo immediately - don't wait for next loop Serial.println("Moving servo to 30° position due to high temperature"); myServo.attach(SERVO_PIN, 700, 2300); myServo.write(targetPos); lastServoPos = targetPos; delay(500); // Wait for servo to complete movement myServo.detach(); Serial.println("Servo moved to 30° and detached"); } // We don't auto-reset to 0° when temperature drops - only reset button can do this // Now update Firebase with the latest data (including potentially updated servo position) updateLatestFirebaseData(tempC, smokeTempC, lastServoPos); }
I really hate just throwing code in here and calling it a day but I'm really tired and also otherwise the page would be empty.
The entire application is hosted in firebase using a realtime database that contains:
The entire communication between the ESP32 and the Firebase application is done using the realtime database:
How the hell can someone open a store that sells something and have no knowledge about it? There is one store in Pitesti that sells electronics and Arduino stuff, and they have no idea what they are selling. Literally. I went there asking for parts and some advice, and they asked me for the codes of stuff on their website. They even gave me the wrong thing because I wasn't paying attention. HOW? (It's also my fault, I suppose, but still)
Another challenge was getting the interface to be responsive:
And also all of the other stuff that can go wrong when using this hellish spawn of a platform called ESP32/Arduino, I really hope someone pays for the suffering they have upon the world.
Idk, stuff online I guess?