/*
============================================================
 IoT Rain Alert Umbrella
 Hardware:
   - Seeed XIAO ESP32-C6
   - LED1 -> D9
   - LED2 -> D10

 Part 1
============================================================
*/

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <time.h>
#include <esp_sleep.h>

//-----------------------------
// WiFi Credentials
//-----------------------------
const char* ssid = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";

//-----------------------------
// Location
//-----------------------------
const float LATITUDE = 23.8103;
const float LONGITUDE = 90.4125;

//-----------------------------
// LED Pins
//-----------------------------
const int LED1 = D9;
const int LED2 = D10;

//-----------------------------
// PWM Settings
//-----------------------------
const int PWM_FREQ = 5000;
const int PWM_RESOLUTION = 8;
const int LED_BRIGHTNESS = 60;   // 0-255

//-----------------------------
// Time
//-----------------------------
const char* ntpServer = "pool.ntp.org";
const long gmtOffset = 6 * 3600;
const int daylightOffset = 0;

//-----------------------------
// Sleep
//-----------------------------
const uint64_t SLEEP_TIME =
15ULL * 60ULL * 1000000ULL;

//-----------------------------
// Variables
//-----------------------------
int rainProbability = 0;
int blinkRate = 0;

//------------------------------------------------------------

bool connectWiFi()
{
    Serial.println("Connecting WiFi");

    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);

    unsigned long start = millis();

    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");

        if (millis() - start > 20000)
        {
            Serial.println("WiFi Timeout");
            return false;
        }
    }

    Serial.println();
    Serial.println("Connected");
    Serial.println(WiFi.localIP());

    return true;
}

//------------------------------------------------------------

void disconnectWiFi()
{
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);

    Serial.println("WiFi OFF");
}

//------------------------------------------------------------

bool syncTime()
{
    configTime(gmtOffset,
               daylightOffset,
               ntpServer);

    struct tm timeinfo;

    for (int i = 0; i < 20; i++)
    {
        if (getLocalTime(&timeinfo))
        {
            Serial.println("Time Synced");
            return true;
        }

        delay(500);
    }

    return false;
}

//------------------------------------------------------------

int currentHour()
{
    struct tm timeinfo;

    if (!getLocalTime(&timeinfo))
        return -1;

    return timeinfo.tm_hour;
}

//------------------------------------------------------------

String createURL()
{
    String url =
        "https://api.open-meteo.com/v1/forecast?latitude=";

    url += String(LATITUDE, 4);

    url += "&longitude=";

    url += String(LONGITUDE, 4);

    url += "&hourly=precipitation_probability";

    url += "&forecast_days=1";

    return url;
}

/*
============================================================
 Weather Download
 JSON Parsing
 PWM LED Control
============================================================
*/

//------------------------------------------------------------
// Download weather JSON
//------------------------------------------------------------
String downloadWeather()
{
    WiFiClientSecure client;
    client.setInsecure();      // Skip certificate validation

    HTTPClient https;

    String payload = "";

    String url = createURL();

    Serial.println("Requesting weather...");
    Serial.println(url);

    if (https.begin(client, url))
    {
        int httpCode = https.GET();

        if (httpCode == HTTP_CODE_OK)
        {
            payload = https.getString();

            Serial.println("Weather downloaded.");
        }
        else
        {
            Serial.print("HTTP Error: ");
            Serial.println(httpCode);
        }

        https.end();
    }
    else
    {
        Serial.println("HTTPS Connection Failed");
    }

    return payload;
}

//------------------------------------------------------------
// Parse Open-Meteo JSON
//------------------------------------------------------------
bool parseWeather(String json)
{
    if (json.length() == 0)
        return false;

    DynamicJsonDocument doc(24576);

    DeserializationError error =
        deserializeJson(doc, json);

    if (error)
    {
        Serial.print("JSON Error: ");
        Serial.println(error.c_str());
        return false;
    }

    int hour = currentHour();

    if (hour < 0 || hour > 23)
        return false;

    JsonArray probability =
        doc["hourly"]["precipitation_probability"];

    rainProbability = probability[hour];

    Serial.print("Current Hour: ");
    Serial.println(hour);

    Serial.print("Rain Probability: ");
    Serial.print(rainProbability);
    Serial.println("%");

    return true;
}

//------------------------------------------------------------
// Convert rain probability into blink rate
//------------------------------------------------------------
int getBlinkRate(int probability)
{
    if (probability >= 90)
        return 5;

    if (probability >= 80)
        return 3;

    if (probability >= 70)
        return 2;

    if (probability >= 60)
        return 1;

    return 0;
}

//------------------------------------------------------------
// Initialize PWM
//------------------------------------------------------------
void setupPWM()
{
    ledcAttach(LED1, PWM_FREQ, PWM_RESOLUTION);
    ledcAttach(LED2, PWM_FREQ, PWM_RESOLUTION);

    ledcWrite(LED1, 0);
    ledcWrite(LED2, 0);
}

//------------------------------------------------------------
// LED ON
//------------------------------------------------------------
void ledON()
{
    ledcWrite(LED1, LED_BRIGHTNESS);
    ledcWrite(LED2, LED_BRIGHTNESS);
}

//------------------------------------------------------------
// LED OFF
//------------------------------------------------------------
void ledOFF()
{
    ledcWrite(LED1, 0);
    ledcWrite(LED2, 0);
}

//------------------------------------------------------------
// Blink once
//------------------------------------------------------------
void blinkOnce(unsigned long onTime,
               unsigned long offTime)
{
    ledON();
    delay(onTime);

    ledOFF();
    delay(offTime);
}

//------------------------------------------------------------
// Decide blink timing
//------------------------------------------------------------
void blinkPattern()
{
    switch (blinkRate)
    {
        case 1:
            blinkOnce(500, 500);
            break;

        case 2:
            blinkOnce(250, 250);
            break;

        case 3:
            blinkOnce(166, 167);
            break;

        case 5:
            blinkOnce(100, 100);
            break;

        default:
            delay(1000);
            break;
    }
}

/*
============================================================
 Main Program
============================================================
*/

#include <esp32-hal-cpu.h>

//------------------------------------------------------------
// Blink LEDs for 15 minutes
//------------------------------------------------------------
void blinkFor15Minutes()
{
    if (blinkRate == 0)
    {
        Serial.println("Rain probability below 60%");
        Serial.println("LED OFF for 15 minutes");

        ledOFF();
        delay(15UL * 60UL * 1000UL);
        return;
    }

    Serial.println("Start LED indication");

    unsigned long startTime = millis();

    while (millis() - startTime < (15UL * 60UL * 1000UL))
    {
        blinkPattern();
    }

    ledOFF();

    Serial.println("15 minute indication finished");
}

//------------------------------------------------------------
// Enter Deep Sleep
//------------------------------------------------------------
void goToSleep()
{
    Serial.println("Entering Deep Sleep...");

    ledOFF();

    esp_sleep_enable_timer_wakeup(SLEEP_TIME);

    Serial.flush();

    delay(100);

    esp_deep_sleep_start();
}

//------------------------------------------------------------

void setup()
{
    Serial.begin(115200);

    delay(1000);

    Serial.println();
    Serial.println("==============================");
    Serial.println(" IoT Rain Alert Umbrella");
    Serial.println("==============================");

    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);

    setupPWM();

    //--------------------------------------------------------
    // Connect WiFi
    //--------------------------------------------------------

    if (!connectWiFi())
    {
        Serial.println("Cannot connect WiFi");

        blinkRate = 0;

        disconnectWiFi();

        blinkFor15Minutes();

        goToSleep();
    }

    //--------------------------------------------------------
    // Sync Time
    //--------------------------------------------------------

    if (!syncTime())
    {
        Serial.println("Time Sync Failed");

        blinkRate = 0;

        disconnectWiFi();

        blinkFor15Minutes();

        goToSleep();
    }

    //--------------------------------------------------------
    // Download Weather
    //--------------------------------------------------------

    String weather = downloadWeather();

    if (!parseWeather(weather))
    {
        Serial.println("Weather Parsing Failed");

        blinkRate = 0;
    }
    else
    {
        blinkRate = getBlinkRate(rainProbability);

        Serial.print("Blink Rate = ");
        Serial.print(blinkRate);
        Serial.println(" Hz");
    }

    //--------------------------------------------------------
    // Turn WiFi completely OFF
    //--------------------------------------------------------

    disconnectWiFi();

    //--------------------------------------------------------
    // Reduce CPU Frequency
    //--------------------------------------------------------

    setCpuFrequencyMhz(80);

    Serial.println("CPU Frequency = 80 MHz");

    //--------------------------------------------------------
    // Blink LEDs for 15 minutes
    //--------------------------------------------------------

    blinkFor15Minutes();

    //--------------------------------------------------------
    // Deep Sleep
    //--------------------------------------------------------

    goToSleep();
}

//------------------------------------------------------------

void loop()
{
    // Never used

    delay(1000);
}
