#include <ESP32Servo.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h> // Ensure SPI library is included
#include <TinyGPSPlus.h> // Include the TinyGPSPlus library
// === TFT Display (ILI9341) ===
#define TFT_CS 14
#define TFT_DC 13
#define TFT_RST 12
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// === Sensors & Pins ===
#define TEMP_SENSOR_PIN 34 // Analog input for TMP36
#define GAS_ANALOG_PIN 35 // Analog input for MQ-series gas sensor (e.g., AOUT)
#define GAS_DIGITAL_PIN 17 // Digital input for MQ-series DOUT (often indicates smoke/high gas threshold)
#define FIRE_BUTTON_PIN 25 // Manual fire alarm button
#define GAS_BUTTON_PIN 26 // Manual gas alarm button
#define SLIDE_SWITCH_PIN 32 // GPIO for the slide switch (sw1)
// === Output Pins ===
#define BUZZER_PIN 16
#define RED_LED_PIN 19 // For danger/alert (Common Anode RGB - LOW to turn on)
#define GREEN_LED_PIN 5 // For safe status (Common Anode RGB - LOW to turn on) - This is for the "system is safe" state
#define BLUE_LED_PIN 2 // For extinguisher active status (Common Anode RGB - LOW to turn on)
#define RELAY_PIN 33 // To activate an external device like a pump/valve
#define SYSTEM_ON_GREEN_LED_PIN 27 // Dedicated green LED for "System On / No Danger" status - Controlled by Slide Switch
// === GPS Module ===
// Using HardwareSerial2 for GPS module on pins 16 (RX) and 17 (TX)
// IMPORTANT: These conflict with BUZZER_PIN and GAS_DIGITAL_PIN.
// We need to re-assign those if you use a GPS module that needs a dedicated UART.
// Let's re-assign BUZZER_PIN and GAS_DIGITAL_PIN to unused GPIOs.
// Re-assigning:
// BUZZER_PIN from 16 to 2 (formerly BLUE_LED_PIN)
// GAS_DIGITAL_PIN from 17 to 39 (input-only GPIO on ESP32, good for sensors)
// BLUE_LED_PIN from 2 to 21
// SYSTEM_ON_GREEN_LED_PIN from 27 to 15 (as it was conflicting with GPS TX from JSON previously)
// Re-defining pins to avoid conflicts with GPS on Serial2
#undef BUZZER_PIN
#define BUZZER_PIN 2 // Moved from 16, now GPIO2 is for buzzer
#undef BLUE_LED_PIN
#define BLUE_LED_PIN 21 // Moved from 2, now GPIO21 is for blue LED
#undef GAS_DIGITAL_PIN
#define GAS_DIGITAL_PIN 39 // Moved from 17, now GPIO39 is for digital gas sensor
#undef SYSTEM_ON_GREEN_LED_PIN
#define SYSTEM_ON_GREEN_LED_PIN 15 // Moved from 27, now GPIO15 is for System On LED
// GPS definitions (using Serial2 on default pins 16(RX) and 17(TX))
#define GPS_RX_PIN 16 // ESP32 RX connected to GPS TX
#define GPS_TX_PIN 17 // ESP32 TX connected to GPS RX (optional, if you need to send commands to GPS)
HardwareSerial GPS_Serial(2); // Use Serial2
TinyGPSPlus gps; // The TinyGPSPlus object
String gpsLatitude = "N/A";
String gpsLongitude = "N/A";
String gpsFixStatus = "No Fix";
// === Buzzer Settings (using tone/noTone) ===
#define BUZZER_TONE_FREQ 2000 // Hz, try 1000–4000 for buzzer tone
// === Servos ===
#define SERVO1_PIN 4
// #define SERVO2_PIN XX // If you need a second servo, define it here (e.g., 27, 21, 15)
// and then uncomment 'Servo servo2;' and its attach/write calls.
// === Thresholds ===
// TEMP Sensor: (Retaining your confirmed working thresholds)
#define EXTINGUISHER_TEMP_THRESHOLD 50.0 // Temperature at which extinguisher activates (from your *first* working code)
#define TEMP_DANGER_THRESHOLD 37.0 // Temperature for general "hazard" alert (from your *first* working code)
// MQ Sensor Thresholds: (Retaining your confirmed working thresholds from the *second* code)
#define GAS_ANALOG_THRESHOLD_GAS 4000 // Analog reading above this is considered high "gas"
#define GAS_ANALOG_THRESHOLD_SMOKE 3800 // Analog reading above this (but below GAS_ANALOG_THRESHOLD_GAS) is "smoke"
// === Display Timing ===
#define WELCOME_SCREEN_DURATION_MS 2000 // 2 seconds for welcome screen
#define INITIALIZING_SCREEN_DURATION_MS 1500 // 1.5 seconds for initializing screen
#define INITIALIZED_SCREEN_DURATION_MS 1500 // 1.5 seconds for system initialized screen
#define SAFE_SCREEN_UPDATE_INTERVAL_MS 1000 // How often "SYSTEM SAFE" updates (1 second)
// === Global Variables ===
Servo servo1;
// Servo servo2; // Uncomment if you add a second servo
// State machine for overall system behavior
enum SystemState {
BOOT_LOGO,
BOOT_WELCOME,
BOOT_INITIALIZING,
BOOT_INITIALIZED,
STATE_SAFE,
STATE_GAS_DANGER,
STATE_SMOKE_DANGER,
STATE_FIRE_DETECTED,
STATE_FIRE_EXTINGUISHER_ACTIVE
};
SystemState currentSystemState = BOOT_LOGO; // Start with the logo
unsigned long lastStateChangeTime = 0; // To manage timing for state transitions
unsigned long lastSafeDisplayTime = 0; // To manage refreshing the "SYSTEM SAFE" message
// Function to draw text centrally on the screen
void drawCenteredText(const char* text, int y, uint16_t color, uint8_t size) {
tft.setTextSize(size);
tft.setTextColor(color);
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h); // Get text bounding box
tft.setCursor((tft.width() - w) / 2, y); // Center horizontally
tft.println(text);
}
// Function to draw text for a message box (title and message)
void drawMessageBox(const char* title, const char* message, uint16_t titleColor, uint16_t msgColor, uint8_t titleSize, uint8_t msgSize) {
tft.fillScreen(ILI9341_BLACK); // Clear background
// Draw title
tft.setTextSize(titleSize);
tft.setTextColor(titleColor);
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(title, 0, 0, &x1, &y1, &w, &h);
tft.setCursor((tft.width() - w) / 2, 50); // Center title vertically at 50
tft.println(title);
// Draw message
tft.setTextSize(msgSize);
tft.setTextColor(msgColor);
tft.getTextBounds(message, 0, 0, &x1, &y1, &w, &h);
tft.setCursor((tft.width() - w) / 2, 100); // Center message below title
tft.println(message);
}
// Function to parse GPS data
void parseGPS() {
while (GPS_Serial.available() > 0) {
if (gps.encode(GPS_Serial.read())) {
if (gps.location.isValid()) {
gpsLatitude = String(gps.location.lat(), 6);
gpsLongitude = String(gps.location.lng(), 6);
gpsFixStatus = "FIX";
} else {
gpsFixStatus = "NO FIX";
gpsLatitude = "N/A";
gpsLongitude = "N/A";
}
}
}
}
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for Serial Monitor to connect
// Initialize TFT screen
tft.begin();
tft.setRotation(1); // Set display orientation to landscape
// Initialize output pins
pinMode(BUZZER_PIN, OUTPUT); // Buzzer pin as output for tone()
pinMode(RELAY_PIN, OUTPUT);
// Set LED pins as output
pinMode(RED_LED_PIN, OUTPUT);
pinMode(GREEN_LED_PIN, OUTPUT);
pinMode(BLUE_LED_PIN, OUTPUT);
pinMode(SYSTEM_ON_GREEN_LED_PIN, OUTPUT); // Dedicated System On Green LED
// Initialize input pins (using internal pull-ups for buttons and switch)
pinMode(FIRE_BUTTON_PIN, INPUT_PULLUP);
pinMode(GAS_BUTTON_PIN, INPUT_PULLUP);
pinMode(GAS_DIGITAL_PIN, INPUT);
pinMode(SLIDE_SWITCH_PIN, INPUT_PULLUP); // Slide switch connected to 3.3V/GND, so use PULLUP
// Servos setup
servo1.setPeriodHertz(50); // Standard 50Hz for hobby servos
servo1.attach(SERVO1_PIN);
servo1.write(90); // Set to center/idle position
// Ensure all outputs are off initially (HIGH for Common Anode LEDs to be OFF)
noTone(BUZZER_PIN); // Silence buzzer
digitalWrite(RELAY_PIN, LOW);
digitalWrite(RED_LED_PIN, HIGH); // OFF for Common Anode
digitalWrite(GREEN_LED_PIN, HIGH); // OFF for Common Anode
digitalWrite(BLUE_LED_PIN, HIGH); // OFF for Common Anode
digitalWrite(SYSTEM_ON_GREEN_LED_PIN, HIGH); // System On LED starts OFF
// --- Boot Sequence (handled sequentially in setup for simplicity) ---
lastStateChangeTime = millis(); // Record start time for boot sequence
// BOOT_LOGO (Placeholder Text Logo)
currentSystemState = BOOT_LOGO;
tft.fillScreen(ILI9341_BLACK);
drawCenteredText("E M B E R", 80, ILI9341_YELLOW, 4);
drawCenteredText("Q U E N C H", 120, ILI9341_YELLOW, 4);
delay(2000); // Display logo for 2 seconds
// BOOT_WELCOME
currentSystemState = BOOT_WELCOME;
tft.fillScreen(ILI9341_BLACK);
drawCenteredText("Welcome to", 80, ILI9341_WHITE, 3);
drawCenteredText("EMBERQUENCHED!", 120, ILI9341_ORANGE, 3);
digitalWrite(GREEN_LED_PIN, LOW); // Existing green light ON throughout welcome (as per your provided code)
delay(WELCOME_SCREEN_DURATION_MS); // Show for 2 seconds
// BOOT_INITIALIZING
currentSystemState = BOOT_INITIALIZING;
tft.fillScreen(ILI9341_BLACK);
digitalWrite(GREEN_LED_PIN, HIGH); // Turn off existing green LED temporarily during initializing
digitalWrite(SYSTEM_ON_GREEN_LED_PIN, HIGH); // Turn off system on LED during initializing
drawCenteredText("Initializing.....", 100, ILI9341_CYAN, 3);
delay(INITIALIZING_SCREEN_DURATION_MS); // Show for 1.5 seconds
// Initialize GPS Serial
GPS_Serial.begin(9600, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); // Common baud rate for GPS modules
// BOOT_INITIALIZED
currentSystemState = BOOT_INITIALIZED;
tft.fillScreen(ILI9341_BLACK);
drawCenteredText("Fire/Gas Detection", 80, ILI9341_GREEN, 2);
drawCenteredText("System Initialized.", 100, ILI9341_GREEN, 2);
delay(INITIALIZED_SCREEN_DURATION_MS); // Show for 1.5 seconds
// Transition to initial SAFE state for the main loop
currentSystemState = STATE_SAFE;
lastStateChangeTime = millis(); // Reset timer for loop logic
lastSafeDisplayTime = millis(); // Initialize safe display time
digitalWrite(RED_LED_PIN, HIGH); // OFF
digitalWrite(BLUE_LED_PIN, HIGH); // OFF
tft.fillScreen(ILI9341_BLACK); // Clear screen for first safe display
// --- Buzzer Test ---
// A brief beep to confirm buzzer is working via tone()
tone(BUZZER_PIN, 1000); // Play a 1kHz tone for a short period
delay(200);
noTone(BUZZER_PIN); // Silence the buzzer
delay(100);
// --- End Buzzer Test ---
}
void loop() {
// Read sensor values in every loop iteration
// Corrected TMP36 temperature conversion for 3.3V system
float voltage_mV = analogRead(TEMP_SENSOR_PIN) * (3300.0 / 4095.0); // Convert ADC reading to mV
float tempC = (voltage_mV / 10.0) - 50.0; // Convert mV to Celsius for TMP36 (datasheet typical)
int gasAnalog = analogRead(GAS_ANALOG_PIN); // Analog gas sensor (e.g., AOUT on MQ-series)
int gasDigital = digitalRead(GAS_DIGITAL_PIN); // Digital gas/smoke sensor (e.g., DOUT on MQ-series, HIGH when threshold met)
bool fireButtonState = digitalRead(FIRE_BUTTON_PIN) == LOW; // Button pressed = LOW (active low with PULLUP)
bool gasButtonState = digitalRead(GAS_BUTTON_PIN) == LOW; // Button pressed = LOW (active low with PULLUP)
bool slideSwitchState = digitalRead(SLIDE_SWITCH_PIN); // Slide switch is HIGH when connected to 3.3V, LOW when connected to GND
// Parse GPS data
parseGPS();
// --- DEBUGGING SENSOR VALUES ---
Serial.print("TempC: "); Serial.print(tempC);
Serial.print(" | Gas Analog: "); Serial.print(gasAnalog);
Serial.print(" | Gas Digital: "); Serial.print(gasDigital);
Serial.print(" | FireBtn: "); Serial.print(fireButtonState);
Serial.print(" | GasBtn: "); Serial.print(gasButtonState);
Serial.print(" | SlideSwitch: "); Serial.print(slideSwitchState);
Serial.print(" | GPS Lat: "); Serial.print(gpsLatitude);
Serial.print(" | GPS Lon: "); Serial.print(gpsLongitude);
Serial.print(" | GPS Fix: "); Serial.println(gpsFixStatus);
// --- END DEBUGGING SENSOR VALUES ---
// Control SYSTEM_ON_GREEN_LED_PIN directly based on the slide switch state.
// This happens *before* any other state logic, ensuring its behavior is dominant and isolated.
if (slideSwitchState == LOW) {
digitalWrite(SYSTEM_ON_GREEN_LED_PIN, LOW); // Slide switch OFF (LOW) -> System On Green LED ON
} else {
digitalWrite(SYSTEM_ON_GREEN_LED_PIN, HIGH); // Slide switch ON (HIGH) -> System On Green LED OFF
}
SystemState desiredNextState;
// Handle slide switch override first: If switch is LOW, force STATE_SAFE logic for main system
if (slideSwitchState == LOW) {
desiredNextState = STATE_SAFE; // Force main system into SAFE mode when slide switch is OFF
} else { // Slide switch is HIGH, allow sensor-based detection
// Fire detection has highest priority (Using your original working thresholds)
bool isExtinguisherNeeded = (tempC > EXTINGUISHER_TEMP_THRESHOLD); // Check if tempC is strictly greater than 50.0C
bool isFireDetected = (tempC > TEMP_DANGER_THRESHOLD || fireButtonState); // Check if tempC is strictly greater than 37.0C OR manual fire button is pressed
// Gas/Smoke detection logic (Using logic and thresholds from your *second* code, confirmed working)
bool isSmokeHazard = false;
bool isGasHazard = false;
// Prioritize digital smoke output if available and active
if (gasDigital == HIGH) {
isSmokeHazard = true; // Digital pin indicates smoke/high concentration
}
// MQ Sensor Logic: Direct acting (higher analog value = more gas/smoke)
if (gasAnalog > GAS_ANALOG_THRESHOLD_GAS) { // If very high analog (e.g., >4000), it's gas
isGasHazard = true;
isSmokeHazard = false; // If it's gas, it can't be just smoke (gas is more severe)
} else if (gasAnalog > GAS_ANALOG_THRESHOLD_SMOKE) { // If moderately high analog (e.g., >3800), it's smoke
isSmokeHazard = true;
}
// Manual gas button can also trigger gas hazard
if (gasButtonState) {
isGasHazard = true;
isSmokeHazard = false; // Manual gas button directly implies gas, not just smoke
}
// Determine desired state based on priority (Extinguisher > Fire > Gas > Smoke > Safe)
if (isExtinguisherNeeded) { // Highest priority
desiredNextState = STATE_FIRE_EXTINGUISHER_ACTIVE;
} else if (isFireDetected) { // Next priority
desiredNextState = STATE_FIRE_DETECTED;
} else if (isGasHazard) { // More severe than smoke
desiredNextState = STATE_GAS_DANGER;
} else if (isSmokeHazard) { // Less severe than gas
desiredNextState = STATE_SMOKE_DANGER;
} else {
desiredNextState = STATE_SAFE;
}
}
// State transition logic
if (desiredNextState != currentSystemState) {
currentSystemState = desiredNextState;
lastStateChangeTime = millis(); // Reset timer for new state
tft.fillScreen(ILI9341_BLACK); // Clear screen on state change
}
// Reset all common outputs (EXCEPT SYSTEM_ON_GREEN_LED_PIN) before setting state-specific ones
// SYSTEM_ON_GREEN_LED_PIN is controlled by the slide switch at the very top of loop.
noTone(BUZZER_PIN); // Silence buzzer
digitalWrite(RELAY_PIN, LOW);
digitalWrite(RED_LED_PIN, HIGH); // OFF for Common Anode
digitalWrite(GREEN_LED_PIN, HIGH); // OFF for Common Anode
digitalWrite(BLUE_LED_PIN, HIGH); // OFF for Common Anode
servo1.write(90); // Default servo position (idle/closed)
// Actions and display updates based on current state
switch (currentSystemState) {
case STATE_SAFE:
// In STATE_SAFE, the original GREEN_LED_PIN (GPIO 5) turns on.
digitalWrite(GREEN_LED_PIN, LOW); // Existing RGB green LED ON for Common Anode
// If slide switch is LOW, display "SWITCH OFF MODE" as requested.
// If slide switch is HIGH and current state is SAFE, it shows "NO DANGER".
if (slideSwitchState == LOW) { // Slide switch is OFF
if (millis() - lastSafeDisplayTime >= SAFE_SCREEN_UPDATE_INTERVAL_MS) {
tft.fillScreen(ILI9341_BLACK);
drawCenteredText("SYSTEM SAFE", 50, ILI9341_GREEN, 3);
drawCenteredText("SWITCH OFF MODE:", 100, ILI9341_WHITE, 2);
drawCenteredText("NO GAS, FIRE /", 120, ILI9341_WHITE, 3);
drawCenteredText("SMOKE!", 150, ILI9341_WHITE, 3);
// Display GPS data
tft.setCursor(10, 200);
tft.setTextSize(1);
tft.setTextColor(ILI9341_CYAN);
tft.print("Lat: "); tft.println(gpsLatitude);
tft.print("Lon: "); tft.println(gpsLongitude);
tft.print("Fix: "); tft.println(gpsFixStatus);
lastSafeDisplayTime = millis();
}
} else { // Slide switch is HIGH
if (millis() - lastSafeDisplayTime >= SAFE_SCREEN_UPDATE_INTERVAL_MS) {
tft.fillScreen(ILI9341_BLACK); // Clear for a visible "refresh"
drawCenteredText("SYSTEM SAFE", 50, ILI9341_GREEN, 3);
drawCenteredText("NO GAS, FIRE /", 100, ILI9341_WHITE, 3);
drawCenteredText("SMOKE!", 130, ILI9341_WHITE, 3);
// Display GPS data
tft.setCursor(10, 180);
tft.setTextSize(1);
tft.setTextColor(ILI9341_CYAN);
tft.print("Lat: "); tft.println(gpsLatitude);
tft.print("Lon: "); tft.println(gpsLongitude);
tft.print("Fix: "); tft.println(gpsFixStatus);
lastSafeDisplayTime = millis();
}
}
break;
case STATE_GAS_DANGER:
tone(BUZZER_PIN, BUZZER_TONE_FREQ); // Buzzer ON
digitalWrite(RED_LED_PIN, LOW); // ON for Common Anode (Red)
digitalWrite(GREEN_LED_PIN, LOW); // ON for Common Anode (Mix with RED for Orange)
digitalWrite(BLUE_LED_PIN, HIGH); // OFF
// SYSTEM_ON_GREEN_LED_PIN is already turned OFF by the slide switch check at the top
drawMessageBox("DANGER!!!", "GAS DETECTED!\nVACATE THE\nBUILDING!", ILI9341_ORANGE, ILI9341_RED, 3, 3);
Serial.print("🔥 Danger: Gas Level = "); Serial.print(gasAnalog);
Serial.println(" | VACATE THE BUILDING!");
break;
case STATE_SMOKE_DANGER:
tone(BUZZER_PIN, BUZZER_TONE_FREQ); // Buzzer ON
digitalWrite(RED_LED_PIN, LOW); // ON for Common Anode
digitalWrite(GREEN_LED_PIN, LOW); // ON for Common Anode (Mix with RED for Orange)
digitalWrite(BLUE_LED_PIN, HIGH); // OFF
// SYSTEM_ON_GREEN_LED_PIN is already turned OFF by the slide switch check at the top
drawMessageBox("DANGER!!!", "SMOKE DETECTED!\nVACATE THE\nBUILDING!", ILI9341_ORANGE, ILI9341_RED, 3, 3);
Serial.print("🔥 Danger: Smoke (Digital) | VACATE THE BUILDING!");
break;
case STATE_FIRE_DETECTED:
tone(BUZZER_PIN, BUZZER_TONE_FREQ); // Buzzer ON
digitalWrite(RED_LED_PIN, LOW); // ON for Common Anode (RED)
digitalWrite(GREEN_LED_PIN, HIGH); // OFF (as per your code, RED alone for Fire)
digitalWrite(BLUE_LED_PIN, HIGH); // OFF
// SYSTEM_ON_GREEN_LED_PIN is already turned OFF by the slide switch check at the top
drawMessageBox("FIRE DETECTED!", "VACATE THE\nBUILDING!", ILI9341_RED, ILI9341_WHITE, 3, 3);
Serial.print("🔥 Danger: Fire Detected! Temp = "); Serial.print(tempC);
Serial.println("°C | VACATE THE BUILDING!");
break;
case STATE_FIRE_EXTINGUISHER_ACTIVE:
tone(BUZZER_PIN, BUZZER_TONE_FREQ); // Buzzer ON
digitalWrite(RELAY_PIN, HIGH); // Activate extinguisher (e.g., pump, valve)
digitalWrite(RED_LED_PIN, HIGH); // OFF
digitalWrite(GREEN_LED_PIN, HIGH); // OFF
digitalWrite(BLUE_LED_PIN, LOW); // ON for Common Anode (Blue light)
// SYSTEM_ON_GREEN_LED_PIN is already turned OFF by the slide switch check at the top
servo1.write(0); // Move servo to active position (e.g., open extinguisher nozzles)
drawMessageBox("FIRE EMERGENCY!", "EXTINGUISHER\nACTIVATED!", ILI9341_RED, ILI9341_YELLOW, 3, 3); // "ACTIVATED!" dropped to new line
Serial.print("🔥🔥 FIRE EMERGENCY! Temp = "); Serial.print(tempC);
Serial.println("°C | EXTINGUISHER ACTIVATED!");
break;
}
// The slide switch override logic at the end of your original code is
// now mostly redundant for the LEDs and buzzer, as the SYSTEM_ON_GREEN_LED_PIN
// is handled at the top, and the main state machine handles the others.
// We keep the display logic here for "SWITCH OFF MODE".
if (slideSwitchState == LOW) {
// If we were not already in safe state, clear screen to show safe message
// This handles the transition from a danger state back to safe via switch
// AND ensures the display is correct if started with switch OFF.
// Also, ensure it re-draws periodically for the "static" effect.
if (currentSystemState != STATE_SAFE || (millis() - lastSafeDisplayTime >= SAFE_SCREEN_UPDATE_INTERVAL_MS)) {
tft.fillScreen(ILI9341_BLACK);
drawCenteredText("SYSTEM SAFE", 50, ILI9341_GREEN, 3);
drawCenteredText("SWITCH OFF MODE:", 100, ILI9341_WHITE, 2); // Indicate override
drawCenteredText("NO GAS, FIRE /", 120, ILI9341_WHITE, 3);
drawCenteredText("SMOKE!", 150, ILI9341_WHITE, 3);
// Display GPS data
tft.setCursor(10, 200);
tft.setTextSize(1);
tft.setTextColor(ILI9341_CYAN);
tft.print("Lat: "); tft.println(gpsLatitude);
tft.print("Lon: "); tft.println(gpsLongitude);
tft.print("Fix: "); tft.println(gpsFixStatus);
lastSafeDisplayTime = millis();
}
// Only the display logic remains crucial here. LED and buzzer control for override
// is now handled by the top-level SYSTEM_ON_GREEN_LED_PIN logic and the general
// reset of outputs at the start of loop.
}
delay(50); // Small delay for responsiveness, adjust if needed
}