#include <ESP32Servo.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h> // Ensure SPI library is included
// ✅ Step 1: Add Required Libraries at the Top
#include <TinyGPSPlus.h>
#include <HardwareSerial.h>
// 🚀 NEW: Add HTTP Client for API calls and WiFi libraries
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h> // For constructing JSON payload
// === WiFi Credentials (Update with your actual WiFi details) ===
const char* ssid = "Wokwi-GUEST"; // Or your actual WiFi SSID
const char* password = ""; // Or your actual WiFi Password
// === API Endpoint (Update with your Cloudflare Tunnel URL) ===
const char* api_url = "https://camcorder-og-spectrum-art.trycloudflare.com/api/incidents/";
// === 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
// === 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)
// 🚀 NEW: API Call Timing
#define API_REPORT_INTERVAL_MS 5000 // Send API data every 5 seconds (adjust as needed)
unsigned long lastApiReportTime = 0;
// === 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
TinyGPSPlus gps;
HardwareSerial SerialGPS(2); // Use UART2 for receiving GPS data (RX pin 27)
#define GPS_RX_PIN 27 // ESP32 RX for GPS data (from GPS TX)
#define GPS_TX_PIN 0 // ESP32 TX for GPS commands (to GPS RX) - Note: This conflicts with default Serial TX
// 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);
}
// === Simulated UART GPS Message Sender (For Testing or Wokwi Virtual GPS) ===
void send_uart(const char* label, const char* message) {
Serial.print("[UART ");
Serial.print(label);
Serial.print("] ");
Serial.println(message); // Print GPS-like message to Serial Monitor
}
// 🚀 NEW Function: Send Alert to Django API (UPDATED AND MOVED HERE)
void sendAlertToAPI(const char* type, float temperature, bool gasDetected, bool smokeDetected, float latitude, float longitude) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(api_url);
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", "Token firekey123device"); // ✅ Your token for Django DRF Token Authentication
StaticJsonDocument<512> doc; // Increased size to 512 to be safe for more fields
// Generate a simple unique ID for the incident (e.g., "esp32_currentMillis")
// For a more robust solution, consider a UUID library or a sequential counter
doc["incident_id"] = "esp32_" + String(millis());
doc["location"] = "Monrovia, Montserrado, Liberia"; // Set a default location or make it dynamic if known
doc["temperature"] = temperature;
doc["latitude"] = latitude;
doc["longitude"] = longitude;
doc["type"] = type; // e.g., "Fire", "Gas", "Smoke", "Safe"
doc["gas_detected"] = gasDetected;
doc["smoke_detected"] = smokeDetected;
// timestamp will be auto-generated by Django, so we don't send it
String payload;
serializeJson(doc, payload);
Serial.print("Sending JSON: ");
Serial.println(payload);
int httpResponseCode = http.POST(payload);
if (httpResponseCode > 0) {
Serial.printf("[HTTP] POST... code: %d\n", httpResponseCode);
String response = http.getString();
Serial.println(response);
} else {
Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpResponseCode).c_str());
}
http.end();
} else {
Serial.println("WiFi not connected, cannot send API alert.");
}
}
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for Serial Monitor to connect
// ✅ Step 3: In setup(), initialize GPS Serial - CORRECTED HERE
// We explicitly assign UART2 for RX (pin 27) and UART0 for TX (pin 0).
// Note that using GPIO0 for GPS TX will prevent Serial.print from working reliably.
SerialGPS.begin(9600, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); // RX, TX pins
Serial.println("GPS Serial Initialized."); // This print might not appear if GPS_TX_PIN is 0
Serial.printf("GPS RX (Data In from GPS TX) on pin: %d\n", GPS_RX_PIN);
Serial.printf("GPS TX (Data Out to GPS RX) on pin: %d\n", GPS_TX_PIN);
// 🚀 NEW: Connect to WiFi
Serial.print("Connecting to WiFi: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
int connectAttempts = 0;
while (WiFi.status() != WL_CONNECTED && connectAttempts < 20) {
delay(500);
Serial.print(".");
connectAttempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi Connected!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWiFi Connection FAILED!");
}
// 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
// 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
// Existing Green LED will be turned ON in the loop's STATE_SAFE case or if switch is OFF
// SYSTEM_ON_GREEN_LED_PIN will be handled in the loop as well.
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 and process GPS data
while (SerialGPS.available() > 0) {
gps.encode(SerialGPS.read());
}
// --- DEBUGGING GPS VALUES (Optional, for Serial Monitor) ---
if (gps.location.isUpdated()) {
Serial.print("Latitude: "); Serial.println(gps.location.lat(), 6);
Serial.print("Longitude: "); Serial.println(gps.location.lng(), 6);
Serial.print("Speed (km/h): "); Serial.println(gps.speed.kmph());
Serial.print("Satellites: "); Serial.println(gps.satellites.value());
}
// --- END DEBUGGING GPS VALUES ---
// 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
// --- 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.println(slideSwitchState);
// --- END DEBUGGING SENSOR VALUES ---
// Control SYSTEM_ON_GREEN_LED_PIN directly based on the slide switch state.
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
bool isExtinguisherNeeded = (tempC > EXTINGUISHER_TEMP_THRESHOLD);
bool isFireDetected = (tempC > TEMP_DANGER_THRESHOLD || fireButtonState);
// Gas/Smoke detection logic
bool isSmokeHazard = false; // Initialize to false
bool isGasHazard = false; // Initialize to false
// Prioritize digital smoke output if available and active
if (gasDigital == HIGH) {
isSmokeHazard = true;
}
// MQ Sensor Logic: Direct acting (higher analog value = more gas/smoke)
if (gasAnalog > GAS_ANALOG_THRESHOLD_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) {
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
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)
// Variables for API payload
// Renamed from eventName to alertType to match Django model 'type' field better
const char* alertType = "UNKNOWN";
bool sendGasDetected = false;
bool sendSmokeDetected = false;
float currentLatitude = 0.0;
float currentLongitude = 0.0;
if (gps.location.isValid()) {
currentLatitude = gps.location.lat();
currentLongitude = gps.location.lng();
}
// Actions and display updates based on current state
switch (currentSystemState) {
case STATE_SAFE:
digitalWrite(GREEN_LED_PIN, LOW); // Existing RGB green LED ON for Common Anode
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);
if (gps.location.isValid()) {
String latStr = String(currentLatitude, 6);
String lonStr = String(currentLongitude, 6);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 220);
tft.print("Lat: " + latStr);
tft.setCursor(10, 230);
tft.print("Lon: " + lonStr);
} else {
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.setCursor(10, 220);
tft.println("GPS: No Fix");
}
lastSafeDisplayTime = millis();
}
} else { // Slide switch is HIGH
if (millis() - lastSafeDisplayTime >= SAFE_SCREEN_UPDATE_INTERVAL_MS) {
tft.fillScreen(ILI9341_BLACK);
drawCenteredText("SYSTEM SAFE", 50, ILI9341_GREEN, 3);
drawCenteredText("NO GAS, FIRE /", 100, ILI9341_WHITE, 3);
drawCenteredText("SMOKE!", 130, ILI9341_WHITE, 3);
if (gps.location.isValid()) {
String latStr = String(currentLatitude, 6);
String lonStr = String(currentLongitude, 6);
tft.setTextColor(ILI9341_WHITE); // FIX: Corrected ILI9141_WHITE to ILI9341_WHITE
tft.setTextSize(1);
tft.setCursor(10, 220);
tft.print("Lat: " + latStr);
tft.setCursor(10, 230);
tft.print("Lon: " + lonStr);
} else {
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.setCursor(10, 220);
tft.println("GPS: No Fix");
}
lastSafeDisplayTime = millis();
}
}
alertType = "Safe"; // Set alert type for API
sendGasDetected = false;
sendSmokeDetected = false;
break;
case STATE_GAS_DANGER:
tone(BUZZER_PIN, BUZZER_TONE_FREQ);
digitalWrite(RED_LED_PIN, LOW);
digitalWrite(GREEN_LED_PIN, LOW);
digitalWrite(BLUE_LED_PIN, HIGH);
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!");
if (gps.location.isValid()) {
String latStr = String(currentLatitude, 6);
String lonStr = String(currentLongitude, 6);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 180);
tft.print("Lat: " + latStr);
tft.setCursor(10, 190);
tft.print("Lon: " + lonStr);
} else {
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.setCursor(10, 180);
tft.println("GPS: No Fix");
}
alertType = "Gas"; // Set alert type for API
sendGasDetected = true;
sendSmokeDetected = false;
break;
case STATE_SMOKE_DANGER:
tone(BUZZER_PIN, BUZZER_TONE_FREQ);
digitalWrite(RED_LED_PIN, LOW);
digitalWrite(GREEN_LED_PIN, LOW);
digitalWrite(BLUE_LED_PIN, HIGH);
drawMessageBox("DANGER!!!", "SMOKE DETECTED!\nVACATE THE\nBUILDING!", ILI9341_ORANGE, ILI9341_RED, 3, 3);
Serial.print("🔥 Danger: Smoke (Digital) | VACATE THE BUILDING!");
if (gps.location.isValid()) {
String latStr = String(currentLatitude, 6);
String lonStr = String(currentLongitude, 6);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 180);
tft.print("Lat: " + latStr);
tft.setCursor(10, 190);
tft.print("Lon: " + lonStr);
} else {
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.setCursor(10, 180);
tft.println("GPS: No Fix");
}
alertType = "Smoke"; // Set alert type for API
sendGasDetected = false;
sendSmokeDetected = true;
break;
case STATE_FIRE_DETECTED:
tone(BUZZER_PIN, BUZZER_TONE_FREQ);
digitalWrite(RED_LED_PIN, LOW);
digitalWrite(GREEN_LED_PIN, HIGH);
digitalWrite(BLUE_LED_PIN, HIGH);
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!");
if (gps.location.isValid()) {
String latStr = String(currentLatitude, 6);
String lonStr = String(currentLongitude, 6);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 180);
tft.print("Lat: " + latStr);
tft.setCursor(10, 190);
tft.print("Lon: " + lonStr);
} else {
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.setCursor(10, 180);
tft.println("GPS: No Fix");
}
alertType = "Fire"; // Set alert type for API
sendGasDetected = false; // Fire detection doesn't inherently mean gas/smoke are also detected for the API payload
sendSmokeDetected = false; // Unless your logic specifically flags them
break;
case STATE_FIRE_EXTINGUISHER_ACTIVE:
tone(BUZZER_PIN, BUZZER_TONE_FREQ);
digitalWrite(RELAY_PIN, HIGH);
digitalWrite(RED_LED_PIN, HIGH);
digitalWrite(GREEN_LED_PIN, HIGH);
digitalWrite(BLUE_LED_PIN, LOW);
servo1.write(0);
drawMessageBox("FIRE EMERGENCY!", "EXTINGUISHER\nACTIVATED!", ILI9341_RED, ILI9341_YELLOW, 3, 3);
Serial.print("🔥🔥 FIRE EMERGENCY! Temp = "); Serial.print(tempC);
Serial.println("°C | EXTINGUISHER ACTIVATED!");
if (gps.location.isValid()) {
String latStr = String(currentLatitude, 6);
String lonStr = String(currentLongitude, 6);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 180);
tft.print("Lat: " + latStr);
tft.setCursor(10, 190);
tft.print("Lon: " + lonStr);
} else {
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.setCursor(10, 180);
tft.println("GPS: No Fix");
}
alertType = "Extinguisher Active"; // Set alert type for API
sendGasDetected = false; // Assuming extinguisher activation implies fire, not necessarily gas/smoke for API
sendSmokeDetected = false;
break;
}
// 🚀 NEW: Send data to API at intervals, or immediately on state change if desired
// We'll send it periodically based on API_REPORT_INTERVAL_MS
if (millis() - lastApiReportTime >= API_REPORT_INTERVAL_MS) {
// Only send if WiFi is connected
if (WiFi.status() == WL_CONNECTED) {
// Prioritize sending data even without GPS fix for critical events.
// Send 0.0 for lat/lon if not valid.
sendAlertToAPI(alertType, tempC, sendGasDetected, sendSmokeDetected, currentLatitude, currentLongitude);
lastApiReportTime = millis();
} else {
Serial.println("Warning: WiFi not connected, cannot send API reports.");
}
}
// 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 (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);
if (gps.location.isValid()) {
String latStr = String(currentLatitude, 6);
String lonStr = String(currentLongitude, 6);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 220);
tft.print("Lat: " + latStr);
tft.setCursor(10, 230);
tft.print("Lon: " + lonStr);
} else {
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.setCursor(10, 220);
tft.println("GPS: No Fix");
}
lastSafeDisplayTime = millis();
}
}
delay(50); // Small delay for responsiveness, adjust if needed
}