/* OpenWeather weather monitor (with second server option) */

#include <GxEPD.h>                                   // https://github.com/ZinggJM/GxEPD
#include <Adafruit_GFX.h>                            // https://github.com/adafruit/Adafruit-GFX-Library

#include <GxGDEW042T2/GxGDEW042T2.h>                 // display class 4.2" b/w

#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>

// FreeFonts from Adafruit_GFX
#include <Fonts/FreeMonoBold9pt7b.h>

#include "myFonts/FreeMonoBold7pt7b.h"
#include "myFonts/DSEG7Classic_Bold20pt7b.h"         // https://github.com/keshikan/DSEG/releases/tag/v0.46
#include "myFonts/DSEG7Classic_Bold8pt7b.h"

#include "bitmaps/bitmaps.h"                                 // Weather symbols bitmaps

#include <TimeLib.h>                                 // https://github.com/geekfactory/TimeLib
#include <Arduino_JSON.h>                            // https://github.com/arduino-libraries/Arduino_JSON

#include <HTTPClient.h>  

// for SPI pin definitions see e.g.:
// C:\Users\xxx\Documents\Arduino\hardware\espressif\esp32\variants\lolin32\pins_arduino.h

 GxIO_Class io(SPI, /*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16);       // arbitrary selection of 17, 16
 GxEPD_Class display(io, /*RST=*/ 16, /*BUSY=*/ 4);              // arbitrary selection of (16), 4


#define WEATHER_SERVER_PIN 32                        // Select Local server or OpenWeathermap (Option)
#define ADC_PIN 35                                   // ADC Pin for battery voltage

#define FLAT_BATT  3.3                               // Battery at lowest safe discharge level - sleep forever.
#define LOW_BATT 3.5                                 // Battery considered 'low' below this value
#define READS 20                                     // How many times to read the ADC pin.

#define WIFI_TIMEOUT 10000                           // 10 seconds in milliseconds
#define DEEP_SLEEP_TIME 15                           // Sleep time in Minutes (will double if battery is < LOW_BATT volts)

/*------------------------ Only change values between these lines -------------------- */

#define DEBUGGING false                              // Set to false when sketch is finished.
#define SHOW_LOCAL_TIME true                         // Set to false to display GMT (UTC)

#define CONV_FACTOR 1.76                             // ADC value conversion factor (tweak for accurate volts).

#define DST "BST"                                    // Define local name for your daylight saving time 
                                                     // ( https://www.timeanddate.com/time/zones/ )

const char* ssid = "*******";                        // Your WiFi router SSID 
const char* password = "**********";                 // Your WiFi router password

String openWeatherMapApiKey = "********************************";       // Your openweathermap Api Key 
                                                                        // ( https://home.openweathermap.org/users/sign_up )

String lat = "53.4";                                 // Your latitude & longitude (Google maps)
String lon = "-3.1";

String serverPathLocal = "";                         // Leave this line as an empty String 
                                                     //  **SEE TEXT**

/*------------------------------------------------------------------------------------*/

String OpenWeather = "http://api.openweathermap.org/data/2.5/weather";

String serverPathOpenWeather = OpenWeather + "?lat=" + lat + "&lon=" + lon + "&appid=" + openWeatherMapApiKey;

String serverPath;

String jsonBuffer;

bool useLocalServer = false;

double volts ;     

unsigned long runtime = millis();                               // Used for debugging to minimize time esp32 is active


void setup() {

  setCpuFrequencyMhz(80);                                       // Slow CPU should use less current.    
  Serial.begin(115200);

  pinMode(WEATHER_SERVER_PIN, INPUT_PULLUP);                    // Input to select optional weather server **SEE TEXT**
           
  useLocalServer = digitalRead(WEATHER_SERVER_PIN) == LOW;      // If Jumper JP1 is closed, use local server. **SEE TEXT**
  
  if (useLocalServer) {
     serverPath = serverPathLocal;
  } else
  serverPath = serverPathOpenWeather;

  WiFi.begin(ssid, password);

  if (DEBUGGING) {
    Serial.println();
    Serial.print("Free heap: ");
    Serial.println(ESP.getFreeHeap());                          // Check on memeory useage
    Serial.print("Connecting to ");
    Serial.println(serverPath);
  }

  unsigned long startAttemptTime = millis(); 
  volts = getBatteryVolts();
  
  while (WiFi.status() != WL_CONNECTED && 
    millis() - startAttemptTime < WIFI_TIMEOUT){
    delay(10);
  }

  // Make sure that we're actually connected, otherwise go to deep sleep
  if(WiFi.status() != WL_CONNECTED){
    Serial.println("FAILED");
    goToDeepSleep(volts);
  }

  if (DEBUGGING) {
    Serial.println("");
    Serial.print("Connecting time:");
    Serial.print((millis() - startAttemptTime) / 1000);
    Serial.println(" sec");
    Serial.print("Connected to WiFi network with IP Address: ");
    Serial.println(WiFi.localIP());
  }

  display.init();
  display.setTextColor(GxEPD_BLACK);
  display.setFont(&FreeMonoBold9pt7b);
  display.setRotation(0);
  display.fillScreen(GxEPD_WHITE);

 
  if(WiFi.status()== WL_CONNECTED){
      int rssi = WiFi.RSSI();    

      jsonBuffer = httpGETRequest(serverPath.c_str());                        // Get raw JSON data from server
      
      if (DEBUGGING) {
        Serial.println(jsonBuffer);
      }
      JSONVar doc = JSON.parse(jsonBuffer);                                   // Parse the JSON data into JSONVar 'doc'
  
      // JSON.typeof(jsonVar) can be used to get the type of the var
      if (JSON.typeof(doc) == "undefined") {
        Serial.println("Parsing input failed!");

        goToDeepSleep(volts);
      }

      if (DEBUGGING) {
        Serial.print("JSON object = ");
        Serial.println(doc);
        Serial.println();
      }

      double latitude = doc["coord"]["lat"];                                  // Get 'doc' values into variables.
      double longitude = doc["coord"]["lon"];
      double temp = doc["main"]["temp"];
      double windspeed = doc["wind"]["speed"];
      
      int wind_dir = doc["wind"]["deg"];
      int pressure = doc["main"]["pressure"];
      int humidity = doc["main"]["humidity"];
      int visibility = doc["visibility"];

      long time_now = doc["dt"];                        // Times are Unix times
      
      long sunrise = doc["sys"]["sunrise"];
      long sunset = doc["sys"]["sunset"];

      long timezone = doc["timezone"];

      const char* location = doc["name"];

      JSONVar weather = doc["weather"][0];
      int id = weather["id"];
      const char* wx = weather["main"];
      const char* description = weather["description"]; 

      double tempC = temp  - 273.15;                     // Convert Kelvin to Celcius              
      double windspeed_mph = windspeed  * 2.237;         // Convert m/s to mph

      if (SHOW_LOCAL_TIME) {                             // Add timezone if Local time is requested
        time_now = time_now + timezone;
        sunrise = sunrise + timezone;
        sunset = sunset + timezone;
      }
 
      display.drawRoundRect(25, 5, 170, 95, 5, GxEPD_BLACK);         // Draw rectangles for each weather value on diplay
      display.setCursor(45, 22);
      display.print("TEMPERATURE c");


      display.drawRoundRect(205, 5, 170,95, 5, GxEPD_BLACK);
      display.setCursor(230, 22);
      display.print("HUMIDITY %");


      display.drawRoundRect(25, 104, 170,95, 5, GxEPD_BLACK);  // weather

      display.drawRoundRect(205, 104, 170,95, 5, GxEPD_BLACK);
      display.setCursor(220, 123);
      display.print("PRESSURE mb");

      display.drawRoundRect(25, 203, 170, 95, 5, GxEPD_BLACK); // sunrise etc

      display.drawRoundRect(205, 203, 170,95, 5, GxEPD_BLACK);
      display.setCursor(290, 220);
      display.print("WIND"); 

      display.setFont(&FreeMonoBold7pt7b);
      display.setCursor(333, 250);

      display.print("mph");
      display.drawCircle(333, 270, 2, GxEPD_BLACK);

      display.setCursor(134, 118);
      display.print("vis:(m)");
      display.setCursor(134, 135);
      if (visibility == 0) {
        display.print("n/a");
      }
      else
        display.print(visibility);
   
      display.setCursor(30, 184);
      display.print(wx);
      display.setCursor(30, 194);

      display.print(description); 

      display.setCursor(143, 218);
      if ((SHOW_LOCAL_TIME )  && (timezone != 0)){
        display.print(DST);
      } else
        display.print("GMT");
      
      display.setCursor(135, 244);
      display.print(rssi);                                 // Display WiFi signal strength
      display.print(" dB");
  
      display.setFont(&DSEG7Classic_Bold20pt7b);

      int tempX;
      tempC < 10 ? tempX = 80 : tempX = 50; 

      display.setCursor(tempX, 80);
      display.print(tempC, 1);

      display.setCursor(250, 80);
      display.print(humidity);

      if (pressure <1000) {
        display.setCursor(220, 180);
      }
      else
        display.setCursor(210, 180);
      display.print(pressure);
     
      // Display "BBC Style" weather direction indicator
      display.setFont(&FreeMonoBold9pt7b);
      
      display.setCursor(227, 290);
 
      switch (wind_dir) {
          case 0 ... 11:    display.drawBitmap(gImage_n, 210, 205, 79, 73, GxEPD_BLACK);
                            display.print( " N");
                            break;
          case 12 ... 34:   display.drawBitmap(gImage_nne, 210, 205, 79, 73, GxEPD_BLACK);
                            display.print("NNE");
                            break;
          case 35 ... 56:   display.drawBitmap(gImage_ne, 210, 205, 79, 73, GxEPD_BLACK); 
                            display.print("NE");
                            break;
          case 57 ... 79:   display.drawBitmap(gImage_ene, 210, 205, 79, 73, GxEPD_BLACK);
                            display.print("ENE");
                            break;
          case 80 ... 101:  display.drawBitmap(gImage_e, 210, 205, 79, 73, GxEPD_BLACK);
                            display.print(" E");
                            break;
          case 102 ... 123: display.drawBitmap(gImage_ese, 210, 205, 79, 73, GxEPD_BLACK);
                            display.print("ESE");
                            break;
          case 124 ... 146: display.drawBitmap(gImage_se, 210, 205, 79, 73, GxEPD_BLACK);
                            display.print("SE");
                            break;
          case 147 ... 169: display.drawBitmap(gImage_sse, 210, 205, 79, 73, GxEPD_BLACK);
                            display.print("SSE");
                            break;
          case 170 ... 192: display.drawBitmap(gImage_s, 210, 205, 79, 73, GxEPD_BLACK);
                            display.print(" S");
                            break;
          case 193 ... 215: display.drawBitmap(gImage_ssw, 210, 205, 79, 73, GxEPD_BLACK);
                            display.print("SSW");
                            break;
          case 216 ... 238: display.drawBitmap(gImage_sw, 210, 205, 79, 73, GxEPD_BLACK); 
                            display.print("SW");
                            break;
          case 239 ... 261: display.drawBitmap(gImage_wsw, 210, 205, 79, 73, GxEPD_BLACK); 
                            display.print("WSW");
                            break;
          case 262 ... 283: display.drawBitmap(gImage_w, 210, 205, 79, 73, GxEPD_BLACK); 
                            display.print(" W");
                            break;
          case 284 ... 306: display.drawBitmap(gImage_wnw, 210, 205, 79, 73, GxEPD_BLACK);
                            display.print("WNW");
                            break;
          case 307 ... 329: display.drawBitmap(gImage_nw, 210, 205, 79, 73, GxEPD_BLACK); 
                            display.print("NW");
                            break;
          case 330 ... 352: display.drawBitmap(gImage_nnw, 210, 205, 79, 73, GxEPD_BLACK); 
                            display.print("NNW");
                            break;
          case 353 ... 360: display.drawBitmap(gImage_n, 210, 205, 79, 73, GxEPD_BLACK); 
                            display.print(" N");
                            break;
          default : display.drawBitmap(gImage_n, 210, 205, 79, 73, GxEPD_BLACK);
      }

      display.setFont(&DSEG7Classic_Bold8pt7b);     // Switch to small 7-segment font
      display.setCursor(290, 252);    

      display.println(windspeed_mph, 1);

      display.setCursor(290, 285);
      display.print(wind_dir);

      display.drawBitmap(gImage_sunrise, 30, 205, 25, 25, GxEPD_BLACK);
      display.drawBitmap(gImage_sunset, 30, 233, 25, 25, GxEPD_BLACK);
      
      display.setCursor(60, 230);
      char buff[32];
      if (sunrise != sunset) {
        
        sprintf(buff, "%02d:%02d", hour(sunrise), minute(sunrise));          // Convert unix to date time
        display.println(buff);  
 
        display.setCursor(60, 258);
        sprintf(buff, "%02d:%02d", hour(sunset), minute(sunset));            // Convert unix to date time
        display.println(buff);  
      }
      else {
        display.print("-:-");
        display.setCursor(60, 258);
        display.print("-:-");
      }

      display.drawBitmap(gImage_clock, 30, 265, 25, 25, GxEPD_BLACK);
      display.setCursor(60, 285);
      
      sprintf(buff, "%02d:%02d", hour(time_now), minute(time_now));          // Convert unix to date time
      display.println(buff);  

      display.drawBitmap(gImage_battery, 140, 250, 45, 15, GxEPD_BLACK);

      display.setCursor(136, 285);
      display.print(volts);
      display.setFont(&FreeMonoBold9pt7b);       
      display.print("v");

      float w = constrain(mapf(volts, 3.3, 4.2, 10, 40), 0, 40);            // Convert volts into width value and
      display.fillRect(142, 253, w, 9, GxEPD_BLACK);                        // 'fill' battery image by 'w' amount.

      bool daytime = (time_now >= sunrise) && (time_now < sunset);          // Work out if it's daytime or night time

      if (sunrise == sunset) daytime = true;                                // Line to handle 2nd (home) server

      int x = 50;
      int y = 108;

      // Display cloud conditions image based on the id sent from the server

      switch (id) {
        case 200 ... 232:  
          display.drawBitmap(gImage_11d_n, x, y, 77, 58, GxEPD_BLACK);          // Thunderstorm - day & night
          break;
        case 300 ... 321:
           display.drawBitmap(gImage_09d_n, x, y,81, 69, GxEPD_BLACK);          // Drizzle - day & night
           break;
        case 500 ... 504:
          if (daytime)  {
            display.drawBitmap(gImage_10d, x, y,73, 69, GxEPD_BLACK);            // Light rain - daytime
          } else { 
             display.drawBitmap(gImage_10n, x, y,76, 61, GxEPD_BLACK);           // Light rain - night
          }
          break; 
        case 511:
          display.drawBitmap(gImage_13d_n, x, y,60, 68, GxEPD_BLACK);           // freezing rain
          break;
        case 520 ... 531:  
           display.drawBitmap(gImage_09d_n, x, y,81, 69, GxEPD_BLACK);          // Drizzle - day & night
           break;         
        case 600 ... 622:
          display.drawBitmap(gImage_13d_n, x, y,60, 68, GxEPD_BLACK);           // Frost - day & night
          break;
        case 701 ... 781:
          display.drawBitmap(gImage_50d_n, x, y,43, 33, GxEPD_BLACK);           // Mist, fog - day & night
          break;        
        case 800:
          if (daytime)  {
            display.drawBitmap(gImage_01d, x, y, 51, 58, GxEPD_BLACK);             // claer sky - day
          } else {
             display.drawBitmap(gImage_01n, x, y,39, 39, GxEPD_BLACK);            // clear sky - night
          }
          break;
        case 801 ... 802:
          if (daytime)  {
            display.drawBitmap(gImage_02d, x, y, 75, 62, GxEPD_BLACK);            // few clouds - day
          } else {
           display.drawBitmap(gImage_02n, x, y,83, 56, GxEPD_BLACK);              // few clouds - night
          }
          break; 
        case 803 ... 804: 
            display.drawBitmap(gImage_03d_n, x, y,77, 42, GxEPD_BLACK);           // cloud - day and night    
            break;                     
        default:
          display.drawBitmap(gImage_09d_n, x, y,81, 69, GxEPD_BLACK);            // Drizzle - day & night
    }
   
    if (DEBUGGING) {
      Serial.print("Latitude: ");
      Serial.print(abs(latitude));
      if (latitude > 0) {
         Serial.println("N");
      }
      else Serial.println("S");
      
      Serial.print("Longitude: ");
      Serial.print(abs(longitude));
      if (longitude > 0) {
         Serial.println("E");
      }
      else Serial.println("W");

      Serial.print("Location: ");
      Serial.println(location);

      Serial.print("Weather id: ");
      Serial.println(id);
 
      Serial.print("Weather: ");
      Serial.print(wx);
      Serial.print(" - ");
      Serial.println(description);

     
      Serial.print("Temperature: ");
      Serial.print(tempC, 2);    
      Serial.println(" C");
      
      Serial.print("Pressure: ");
      Serial.print(pressure);
      Serial.println(" mbar");
      
      Serial.print("Humidity: ");
      Serial.print(humidity);
      Serial.println(" %");
      
      Serial.print("Wind Speed: ");
      Serial.print(windspeed_mph, 1); 
      Serial.println(" mph");
       
      Serial.print("Wind Direction: ");
      Serial.println(wind_dir); 

      sprintf(buff, "%02d:%02d:%02d", hour(time_now), minute(time_now), second(time_now));
      Serial.print("Data time: ");
      Serial.println(buff);  
       
      if (sunrise != sunset) {
        Serial.print("Sunrise; ");
        sprintf(buff, "%02d:%02d", hour(sunrise), minute(sunrise));
        Serial.println(buff);  
        Serial.print("Sunset; ");
        sprintf(buff, "%02d:%02d", hour(sunset), minute(sunset));
        Serial.println(buff);  
      }
      else {
        Serial.print("Sunrise: ");
        Serial.println("n/a");
        Serial.print("Sunset: ");
        Serial.println("n/a");
      } 
      Serial.println("");   
    }
  }
  else {
     Serial.println("WiFi Disconnected");
  }
 
  display.update();
    
  goToDeepSleep(volts);        // Tell DeepSleep the battery voltage so it can set the sleep time accordingly.

 }

void goToDeepSleep(double volts){

   if (DEBUGGING) {
      Serial.print("Total round-trip time: ");
      Serial.print((millis() - runtime) / 1000); 
      Serial.println(" seconds");
      Serial.print("Batt: ");
      Serial.println(volts);
   }
    
   // Configure the timer to wake up!

   if (volts <= FLAT_BATT) {
     display.setFont(&FreeMonoBold9pt7b);
     display.fillScreen(GxEPD_WHITE);
     display.setCursor(130, 150);
     display.print("CHARGE BATTERY");
     display.update();
     esp_sleep_enable_timer_wakeup(8760ULL * 60 * 60 * 1000 * 1000);              // Battery flat. Set a 1 year interval
   }
   else if (volts < LOW_BATT)  {
     esp_sleep_enable_timer_wakeup(DEEP_SLEEP_TIME * 120L * 1000 * 1000);         // Battery low. Set 30 minute inteval
   } else {
     esp_sleep_enable_timer_wakeup(DEEP_SLEEP_TIME * 60L * 1000 * 1000);          // Battery good. Set 15 mins interval.          
   }
   
   esp_sleep_pd_config(ESP_PD_DOMAIN_MAX, ESP_PD_OPTION_OFF);
   esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
   esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
   esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);

   Serial.flush();
   esp_deep_sleep_start(); 
}
  

String httpGETRequest(const char* serverName) {
   WiFiClient client;
   HTTPClient http;
    
  // Your IP address with path or Domain name with URL path 
   http.begin(client, serverName);
  
  // Send HTTP POST request
   int httpResponseCode = http.GET();
  
   String payload = "{}"; 
  
   if (httpResponseCode>0) {
     if (DEBUGGING) {
        Serial.print("HTTP Response code: ");
        Serial.println(httpResponseCode);
     }
     payload = http.getString();
   }
   else {
     Serial.print("Error code: ");
     Serial.println(httpResponseCode);
   }
  // Free resources
   http.end();

   return payload;
}



double mapf(double val, double in_min, double in_max, double out_min, double out_max) {
    return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

double getBatteryVolts() {
  double totalVal = 0;
  double averageVal = 0;

  for (int i = 0; i < READS; i++ ) {
     totalVal += analogRead(ADC_PIN);
  }
  averageVal = totalVal / READS;
  volts = averageVal * CONV_FACTOR / 1000;
  return volts;
}

void loop() {
 //
}