/* River level Alert.  ESP32 -  1.54" e-Paper display - PushSafer push notification */

#include <GxEPD.h>
#include <GxGDEH0154D67/GxGDEH0154D67.h>       // 1.54" b/w epaper display

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

#include <Fonts/FreeSansBold18pt7b.h>
 
#include <WiFi.h>
#include <HTTPClient.h>

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

#define VOLTS_PIN 32

// Waveshare e-paper connections to ESP32

// Vcc -> 3v3
// GND -> GND
// DIN -> MOSI (GPIO 23)
// CLK -> CLK  (GPIO 18)
// CS  -> SS   (GPIO 5)
// DC  -> GPIO 17
// RST -> GPIO 16
// BUSY-> GPIO 4

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


String flood_data_url = "http://environment.data.gov.uk/flood-monitoring/id/stations/";

/*-*-*-*-*-* Edit values between these lines *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/

String station = "685517";         // Change station ID to river of interest. Station ID can be found at...
                                   // https://riverlevels.uk/levels

const float plotMax = 2.0;         // Maximum value shown on UK Government's flood-risk graph.
const float high_level = 1.2;      // Threshold at which to request PushSafer notification & lockout further requests.
const float low_level = 0.7;       // Reset notifications lockout when level drops to this level.
                                      

const char* ssid = "*******";                             // Your router's WiFi SSID
const char* password = "**********";                      // Your router's Wifi Password

String pushSaferKey = "********************";             // PushSaferKey and DeviceID are obtained from..
String pushSafer_deviceID = "*****";                      // ..your PushSafer Account.

String title = "Arrowebrook Level Monitor";               // Monitored river's name for notification header.

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

String High_level_icon = "48";                            // Rising graph Icon for notification.
String Low_battery_icon = "49";

String pushSaferURL = "http://www.pushsafer.com/api";     // PushSafer server URL.

String pushSaferFullPath;         // pushSafer full path will include your pushsafer account ID and river height.

                                                          // These values are retained through a wakeup from deep sleep.
RTC_NOINIT_ATTR bool high_level_flag = false;             // true = lockout further pushsafer high river level notifications.         
RTC_NOINIT_ATTR bool low_battery_flag = false;            // true = lockout further pushsafer low battery notifications.

RTC_NOINIT_ATTR byte arrayPointer = 0;                // Pointer to next data array element.
RTC_NOINIT_ATTR byte plotArray[200];                  // Save histogram data in plotArray.

RTC_NOINIT_ATTR bool first_run = true;
              
int sleep_interval = 15;              // minutes to wait between river height requests. Do not reduce this value.
int retry_interval = 1;               // Minutes to wait before retrying to connect WiFi.

double height = 0.0;                  // Initial river height.


float mVolts = 0.0;   
const float battery_low = 3300.0;           // Battery volatge low threshold to trigger notification.
const float battery_charging = 3900.0;      // Battery charging. Reset low battery lockout flag.  
         
long rssi;                                  // WiFi strength for display purposes only.

String jsonBuffer;                          // Buffer to store JSON river-level data received from Government website.

void setup() {
 Serial.begin(9600);
 
 
 if (first_run) {                   // No valid data in array so clear all array.
   first_run = false;
   arrayPointer = 0;
   for (int i = 0; i < 200; i++) {
     plotArray[i] = 0;
   }
 } 
 
 int vref = 1140;
 uint16_t v = analogRead(VOLTS_PIN);                    // Read adc value at R1/R2 junction for battery voltage.

 float mVolts = ((float)v / 4095.0) * 2 * 3.2 * vref;   // Convert value to mVolts.

 WiFi.begin(ssid, password);                            // Start the WiFi connection to your routerr

 int wifi_timeout = 0;                                  // Attempt to connect
 while (WiFi.status() != WL_CONNECTED) {
    delay(10);
    wifi_timeout++;
    if (wifi_timeout > 1000)   {                        // If connect to WiFi fails - deep sleep...
       deepSleep(retry_interval);                       // .. for the retry interval.
    }
 }
 
 if (WiFi.status() == WL_CONNECTED)  {                  // WiFi connected to router.
  
    rssi = WiFi.RSSI();                                 // Get WiFi signal strength for epaper display.
    
    HTTPClient  http1;

    flood_data_url = flood_data_url + station + "/measures";
  
    Serial.println(flood_data_url);
    
    http1.begin(flood_data_url);
    
    if (http1.GET() > 0) {                                // Data successfully received from Government website.
      String jsonBuffer = http1.getString();
      Serial.println(jsonBuffer);
      JSONVar doc = JSON.parse(jsonBuffer);

      height = doc["items"][0]["latestReading"]["value"];         // Extract river height from JSON buffer.
      
      Serial.print("Height: "); Serial.println(height, 3);

      updateHistogramData();                                      // Update the plotArray data.

      if (height <= low_level) high_level_flag = false;           // Reset high level flag to allow new notifications.

      if (mVolts >= battery_charging) low_battery_flag = false;   // Reset low battery flag to allow new notifications.
 
    }
    http1.end();

    if ((high_level_flag == false) && (height >= high_level)) {
       request_push_notification("HIGH LEVEL: " + String(height, 2) + "m", High_level_icon);   // Request a push notification.
    }

    if ((low_battery_flag == false) && (mVolts <= battery_low)) {
      request_push_notification("BATTERY LOW: " + String(mVolts/1000, 2) + "v", Low_battery_icon);   // Request a push notification 
    }

 }

 display.init();
 display.setRotation(45);
    
 display.setTextColor(GxEPD_BLACK);
  
 display.fillScreen(GxEPD_WHITE); 
 display.setTextSize(2); 
 display.setCursor(25, 0);
 display.print("RIVER HEIGHT");
 display.setCursor(45, 16);
 display.print("(metres) "); 
 
 display.drawLine(0, 34, 199, 34, GxEPD_BLACK);
 display.drawLine(0, 174, 199, 174, GxEPD_BLACK);
   
 display.setCursor(0, 186);                                // Show battery volts, high-level preset and WiFi strength..
 display.print(mVolts / 1000, 1);                          // .. on Status bar at bottom of display.
 display.print("v");

 display.setCursor(60, 186);
 display.print(high_level, 1); display.print("m");

 display.setCursor(140, 186);
 display.print(rssi);
 display.print("dB");
   
 display.setFont(&FreeSansBold18pt7b);                    // Show measured river height in large bold text.
 display.setCursor(25, 100);
 display.print(height, 2);
 display.setFont();                                       // Reset font to standard small.

 plotHistogram();                                         // Plot the river height histogram.
  
 display.update();                                        // Update display and put epaper display in very low power mode.

 deepSleep(sleep_interval);                               // Sleep the ESP32. epaper sleeps automatically.
      
}

void updateHistogramData() {
 if (arrayPointer >= 199) {                               // If array has been filled, move all values down one.
   for (int i = 0; i < 199; i++) {
     plotArray[i] = plotArray[i+1];
   }
   arrayPointer = 199;
 }      
      
 plotArray[arrayPointer] = round(height * (80 / plotMax));    // 
 if (arrayPointer < 199) arrayPointer++;                    // increment pointer to plot Array. 
 
}

void plotHistogram() {                                    // Function to re-draw the histogram.
                                                          // (strictly an "area chart" but let's not quibble).

 for (int i = 0; i <= 199;  i++){
   display.drawLine(i, 173 - plotArray[i], i, 173, GxEPD_BLACK);  // plot vertical histogram line.
 }

 int y =  round(175 - (high_level * (80 / plotMax)));     // Calculate vertical position of alert level.
      
  for (int x = 0; x < 190; x+= 20) {                      // Draw alert level line (threshold level).
    display.setCursor(x, y-6);
    display.print("- ");
  }
  display.fillTriangle(198, y-5, 198, y+5, 188, y, GxEPD_BLACK);    // Display triangle ◄ at alert level.
}


void request_push_notification(String message, String icon) {


//  title.replace(" ", "%20");                             // Replace spaces with %20 for use in PushSafer URL
//  message.replace(" ", "%20");

  title = urlEncode(title);
  message = urlEncode(message);


  pushSaferFullPath = pushSaferURL +                     // String to send to PushSafer server.
                       "?d=" + pushSafer_deviceID + 
                       "&k=" + pushSaferKey +
                       "&i=" + icon + 
                       "&t=" + title + 
                       "&m=" + message; 
                       
  Serial.println(pushSaferFullPath);                       


  
  HTTPClient http2;
 // String http2_request = pushSaferPath;
  http2.begin(pushSaferFullPath);
 
  int http2Code = http2.GET();
  if (http2Code > 0) {                                           // PushSafer has received push notificstion request.
    String response_str = http2.getString();
    Serial.println(response_str);
    if (response_str.indexOf("success") > 1) {                   // If successful response from PushSafer, set flags..
      if (icon == High_level_icon) high_level_flag = true;       // .. to stop further requests until measured values allow..
      if (icon == Low_battery_icon) low_battery_flag = true;     // .. flags to be reset.
    }
  }
  http2.end();
}


void deepSleep(int interval) {
  
  esp_sleep_enable_timer_wakeup(interval  * 60  *1000 * 1000);   
   
  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_deep_sleep_start(); 
}



void loop() {
  // Nothing to see here.
}