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?