/* * File: YTM_2s.ino
 * Version: 3.7.0
 * Date: 2026-03-20
 * Project: YTM Weather - Enhanced WiFi & NTP Reporting
 */

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <TFT_eSPI.h>
#include <time.h>

// --- Metadata ---
const char* SKETCH_NAME = "YTM_2s.ino";
const char* VERSION = "3.7.0";
const char* COMPILE_DATE = __DATE__ " " __TIME__;

// --- Polling Interval ---
const unsigned long WEATHER_UPDATE_INTERVAL = 900000; // 15 Minutes

// --- Configuration ---
static const char* WIFI_SSID_1 = "NETGEAR55";         
static const char* WIFI_PASS_1 = "cloudycoconut818";  
static const char* WIFI_SSID_2 = "DPiPhone15Plus";    
static const char* WIFI_PASS_2 = "911911911";
const char* lat = "45.4558"; 
const char* lon = "-75.5047";

#define PIR_PIN 35         
#define LCD_BACKLIGHT 21   
unsigned long gLastActivityMs = 0;
unsigned long gActiveUntilMs = 0;
const unsigned long MOTION_TIMEOUT = 60000; 
bool gDisplayActive = true;

TFT_eSPI tft = TFT_eSPI();
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = -18000; 
const int   daylightOffset_sec = 3600;

// --- SM Verbosity Helpers ---
const char* getWiFiStatusName(wl_status_t status) {
    switch (status) {
        case WL_IDLE_STATUS: return "IDLE (0)";
        case WL_NO_SSID_AVAIL: return "NO SSID AVAIL (1)";
        case WL_SCAN_COMPLETED: return "SCAN COMPLETED (2)";
        case WL_CONNECTED: return "CONNECTED (3)";
        case WL_CONNECT_FAILED: return "CONNECT FAILED (4)";
        case WL_CONNECTION_LOST: return "CONNECTION LOST (5)";
        case WL_DISCONNECTED: return "DISCONNECTED (6)";
        default: return "UNKNOWN";
    }
}

String getLogTime(bool includeDate = false) {
    struct tm timeinfo;
    if(!getLocalTime(&timeinfo)) return "[NO SYNC]";
    char timeStringBuff[32];
    if (includeDate) strftime(timeStringBuff, sizeof(timeStringBuff), "[%a %b %d %H:%M:%S]", &timeinfo);
    else strftime(timeStringBuff, sizeof(timeStringBuff), "[%H:%M:%S]", &timeinfo);
    return String(timeStringBuff);
}

// --- Connection Logic with SSID Feedback ---
bool connectToWiFi() {
    auto attempt = [](const char* ssid, const char* pass) -> bool {
        Serial.printf("%s WIFI: Starting connection to SSID: %s\n", getLogTime().c_str(), ssid);
        WiFi.begin(ssid, pass);
        int retry = 0;
        while (WiFi.status() != WL_CONNECTED && retry < 25) {
            delay(500);
            Serial.printf("WIFI: Status: %s | Attempt: %d\n", getWiFiStatusName(WiFi.status()), retry + 1);
            retry++;
        }
        if (WiFi.status() == WL_CONNECTED) {
            // New: Explicitly reporting the final connected SSID
            Serial.printf("%s WIFI: Success! Connected to: %s\n", getLogTime().c_str(), WiFi.SSID().c_str());
            Serial.printf("WIFI: IP Address: %s | RSSI: %d dBm\n\n", WiFi.localIP().toString().c_str(), WiFi.RSSI());
            return true;
        }
        return false;
    };

    if (attempt(WIFI_SSID_1, WIFI_PASS_1)) return true;
    WiFi.disconnect(); delay(1000);
    Serial.println("WIFI: Primary failed. Checking Backup...");
    return attempt(WIFI_SSID_2, WIFI_PASS_2);
}

void syncTime() {
    Serial.printf("%s NTP: Requesting Sync from %s...\n", getLogTime().c_str(), ntpServer);
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
    struct tm timeinfo;
    int retry = 0;
    while(!getLocalTime(&timeinfo) && retry < 15) {
        delay(1000); retry++;
    }
    if (getLocalTime(&timeinfo)) {
        Serial.printf("NTP: Sync Verified! Current Wall Clock: %s\n\n", getLogTime(true).c_str()); 
    }
}

// --- Display Functions ---
void drawCloud(int x, int y, uint16_t color) {
    tft.fillCircle(x, y - 4, 12, color); tft.fillCircle(x - 10, y + 4, 9, color);
    tft.fillCircle(x + 10, y + 4, 9, color); tft.fillRect(x - 10, y + 5, 20, 8, color);  
}
void drawSun(int x, int y) {
    tft.fillCircle(x, y, 12, TFT_YELLOW);
    for (int i = 0; i < 360; i += 45) {
        float rad = i * 0.01745;
        tft.drawLine(x + cos(rad)*15, y + sin(rad)*15, x + cos(rad)*22, y + sin(rad)*22, TFT_YELLOW);
    }
}
void drawWeatherIcon(int x, int y, int code) {
    tft.fillRect(x - 55, y - 35, 110, 75, TFT_BLACK); 
    if (code == 0) drawSun(x, y); 
    else if (code >= 1 && code <= 3) { 
        if (code == 1) { drawSun(x - 8, y - 8); drawCloud(x + 6, y + 6, TFT_SILVER); }
        else drawCloud(x, y, TFT_WHITE);
    } else { 
        drawCloud(x, y - 5, TFT_DARKGREY);
        for (int i = 0; i < 3; i++) tft.drawLine(x-12+(i*12), y+12, x-16+(i*12), y+22, TFT_BLUE);
    }
}

void drawLayout() {
    tft.fillScreen(TFT_BLACK);
    tft.drawFastVLine(95, 0, 90, TFT_SILVER);
    tft.drawFastVLine(225, 0, 90, TFT_SILVER);
    tft.drawFastHLine(0, 90, 320, TFT_SILVER); 
}

void refreshDateHeaders() {
    struct tm timeinfo;
    if(getLocalTime(&timeinfo)) {
        char buf[10]; tft.setTextColor(TFT_WHITE, TFT_BLACK);
        time_t now = time(NULL);
        strftime(buf, sizeof(buf), "%a", localtime(&now)); tft.drawCentreString(buf, 160, 2, 4);
        time_t y = now - 86400; strftime(buf, sizeof(buf), "%a", localtime(&y)); tft.drawCentreString(buf, 47, 2, 4);
        time_t t = now + 86400; strftime(buf, sizeof(buf), "%a", localtime(&t)); tft.drawCentreString(buf, 273, 2, 4);
    }
}

void updateWeather() {
    if(WiFi.status() != WL_CONNECTED) {
        Serial.printf("%s HTTP: Blocked (WiFi Down)\n", getLogTime().c_str());
        return;
    }
    HTTPClient http;
    http.setTimeout(8000); 
    String url = "https://api.open-meteo.com/v1/forecast?latitude=" + String(lat) + "&longitude=" + String(lon) + 
                 "&current=temperature_2m,relative_humidity_2m,surface_pressure,precipitation,weather_code" +
                 "&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max&past_days=1&timezone=auto";
    
    Serial.printf("%s HTTP: GET %s\n", getLogTime().c_str(), url.c_str());
    http.begin(url);
    int httpCode = http.GET();
    
    if (httpCode == 200) {
        Serial.printf("%s HTTP: Success. UI Refreshing...\n", getLogTime().c_str());
        StaticJsonDocument<5000> doc;
        deserializeJson(doc, http.getString());
        refreshDateHeaders();

        tft.setTextColor(TFT_ORANGE, TFT_BLACK);
        tft.drawCentreString("H: " + String((int)doc["daily"]["temperature_2m_max"][1]) + "c", 160, 32, 4);
        tft.drawCentreString("H: " + String((int)doc["daily"]["temperature_2m_max"][0]) + "c", 47, 32, 4);
        tft.drawCentreString("H: " + String((int)doc["daily"]["temperature_2m_max"][2]) + "c", 273, 32, 4);
        tft.setTextColor(TFT_YELLOW, TFT_BLACK);
        tft.drawCentreString(String((int)doc["current"]["temperature_2m"]) + "c", 160, 58, 6); 
        tft.setTextColor(TFT_CYAN, TFT_BLACK);
        tft.drawCentreString("L: " + String((int)doc["daily"]["temperature_2m_min"][1]) + "c", 160, 102, 4);
        tft.drawCentreString("L: " + String((int)doc["daily"]["temperature_2m_min"][0]) + "c", 47, 62, 4);
        tft.drawCentreString("L: " + String((int)doc["daily"]["temperature_2m_min"][2]) + "c", 273, 62, 4);
        tft.setTextColor(TFT_GREEN, TFT_BLACK);
        tft.drawCentreString(String((int)doc["current"]["relative_humidity_2m"]) + "%", 47, 105, 4);
        tft.drawCentreString("RH", 47, 130, 2);
        tft.setTextColor(TFT_MAGENTA, TFT_BLACK);
        tft.drawCentreString(String((int)doc["current"]["surface_pressure"]), 47, 155, 4);
        tft.drawCentreString("hPa", 47, 180, 2);
        tft.setTextColor(TFT_BLUE, TFT_BLACK);
        tft.drawCentreString(String((int)doc["daily"]["precipitation_probability_max"][1]) + "%", 273, 105, 4);
        tft.drawCentreString("PoP", 273, 130, 2);
        tft.setTextColor(TFT_WHITE, TFT_BLACK);
        tft.drawCentreString(String((float)doc["current"]["precipitation"], 1), 273, 155, 4); 
        tft.drawCentreString("mm", 273, 180, 2);
        drawWeatherIcon(160, 172, doc["current"]["weather_code"]); 
    } else {
        Serial.printf("%s HTTP: Error %d\n", getLogTime().c_str(), httpCode);
    }
    http.end();
}

void setup() {
    Serial.begin(115200);
    delay(1000);
    Serial.println("\n\n######################################");
    Serial.printf("SKETCH:  %s\n", SKETCH_NAME);
    Serial.printf("VERSION: %s\n", VERSION);
    Serial.printf("BUILD:   %s\n", COMPILE_DATE);
    Serial.println("######################################\n");

    pinMode(LCD_BACKLIGHT, OUTPUT);
    digitalWrite(LCD_BACKLIGHT, HIGH); 
    pinMode(PIR_PIN, INPUT); 

    tft.init(); tft.setRotation(3);
    drawLayout();
    
    if (connectToWiFi()) syncTime();

    gLastActivityMs = millis();
    gActiveUntilMs = gLastActivityMs + MOTION_TIMEOUT;
}

void loop() {
    unsigned long now = millis();
    int currentPIR = digitalRead(PIR_PIN);

    static int lastPIR = -1;
    if (currentPIR != lastPIR) {
        Serial.printf("%s SENSOR: %s\n", getLogTime().c_str(), currentPIR ? ">> MOTION <<" : ".. STILL ..");
        lastPIR = currentPIR;
    }

    if (currentPIR == HIGH) { 
        gLastActivityMs = now;
        gActiveUntilMs = now + MOTION_TIMEOUT;
        if (!gDisplayActive) { 
            Serial.printf("%s SYSTEM: Waking display.\n", getLogTime().c_str());
            digitalWrite(LCD_BACKLIGHT, HIGH); 
            gDisplayActive = true; 
            refreshDateHeaders(); 
            updateWeather(); 
        }
    }

    if (gDisplayActive && (now > gActiveUntilMs)) {
        Serial.printf("%s SYSTEM: Screen timeout.\n", getLogTime().c_str());
        digitalWrite(LCD_BACKLIGHT, LOW);
        gDisplayActive = false;
    }

    static unsigned long lastClock = 0, lastWeather = 0;
    if (now - lastClock > 1000) { 
        if (gDisplayActive) {
            struct tm timeinfo;
            if(getLocalTime(&timeinfo)) {
                char dateStr[30], timeStr[20];
                strftime(dateStr, sizeof(dateStr), "%b %d, %Y", &timeinfo);
                strftime(timeStr, sizeof(timeStr), "%l:%M %p", &timeinfo);
                tft.fillRect(0, 210, 320, 30, TFT_BLUE); 
                tft.setTextColor(TFT_WHITE, TFT_BLUE);
                tft.drawCentreString(String(dateStr) + "    " + String(timeStr), 160, 215, 4);
            }
        } 
        lastClock = now; 
    }

    if (now - lastWeather > WEATHER_UPDATE_INTERVAL || (lastWeather == 0 && now > 12000)) { 
        updateWeather(); 
        lastWeather = now; 
    }
}