/** Battery Charger / Alarm LED Monitor. Send Notification to Mobile phone.  WeMos D1 Mini. (c) July 2023 vwlowen.co,uk **
   
    LEDs:
    WiFi Connected (RED)    Flashing:  Connected. Waiting for start button.
                            Steady:    Connected. Device monitoring normally.
    Sensor (GREEN)          Off:       LED on 'host device (eg Charger) is off.
                            On:        LED on host device is on.
    Notification (Yellow)   Off:       Idle. Waiting for change of Sensor status.
                            On:        Sensor status changed.. Notification request successfully sent to PushSafer.
                            Flashing:  As 'On' but only a few notifications are still available from PushSafer before top_up.        
    Test Mode(Blue)         Flashing:  Device is in TEST mode. Opertes normally but doesn't send notification request.
     
    JUMPERS:
    Test Jumper             Closed for test mode.
    Falling Jumper          Closed:    Device sends notification request when Sensor LED goes OFF (falling).
                            Open:      Device sends notification request when Sensor LED goes ON  (rising).              **/

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

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


#define NOTIFICATION_SENT_LED D1                          // Define I/O pins for LEDs.
#define CHARGING_LED  D2                                  
#define CONNECTED_LED D3
#define TEST_MODE_LED D4                                  // Built-in LED is connected to output pin D4.

#define START_BUTTON D5                                   // Define I/O pins for the Start button, TEST jumper and FALLING jumper.
#define TEST_JUMPER D6
#define NOTIFY_ON_FALL D7                                 // Jumper/Switch closed (LOW) sends notification when Charging LED goes out. 
                                                          // Jumper/Switch open (HIGH) sends notification when Charging LED comes on.

#define WIFI_TIMEOUT 10000                                // Define WiFi connection attempt timeout (10 seconds)
#define SENSOR_TIMER 2000                                 // Read photo-diode sensor every 2 seconds.

#define THRESHOLD 25                                      // Charging LED on/off threshold. (Depends on charger's LED
                                                          // brightness and photo-diode's sensitivity).
                                                          
#define ATTEMPTS 3                                        // Maximum number of tries to contact PushSafer.
#define NOTIFICATIONS_LOW 10                              // Flash Notification Sent LED when numnber of available 
                                                          // notifications falls below this figure. (Time to top up!)
                                                           
/*** Change the following two lines to your own router's WiFi SSID and Password  ******/

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

String wifi_hostname = "charger-alert";                   // Hostname *might* show in router's 'connected' list


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

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

String title = "ESP8266 Charger Monitor";       
String message = "Charge complete.";

String icon = "175";                                      // Car Icon for notification on phone.

String pushSaferPath;

const int adc = A0;                                       // D1 Mini's only analogue port.
int adc_value = 0;  

bool charging = false;                                    // Initialise flags and attempts counter.
bool notification_Sent_Flag = false;
bool test_flag = false;
bool falling = true;

int notifyAttempts = 0;

bool few_left = false;
int few_left_state = LOW;

unsigned long few_left_timer = 0;                          // Timer to flash Notification_Sent LED when only a few notifications available.
unsigned long wifi_timer = 0;                              // Timer to flash WiFi  LED.
unsigned long test_timer = 0;                              // Timer to blink built-in LED when in Test Mode.
unsigned long read_timer = 0;                              // Timer to read photo-diode.


int readSensor() {  
  int adc_avg = 0;
  int raw = 0;
  for (int i = 0; i< 50; i++) {
    raw = raw + analogRead(adc);
    delay(10);
  }
  adc_avg = raw / 50;
   
  if (test_flag) {
   Serial.print("ADC value: ");                             // Print the value from the sensor for debugging.
   Serial.println(adc_avg);  
  }

  return adc_avg;
}


void blinkTestLED() {                                     // Blink  built-in LED if in Test Mode.
   if (millis() - test_timer >= 1000) {
      test_timer = millis();
      digitalWrite(TEST_MODE_LED, LOW);  
      delay(10); 
      digitalWrite(TEST_MODE_LED, HIGH); 
   }  
}


void setup() {
  Serial.begin(115200);                                   // Serial port useful for debugging response messages
                                                          // from PushSafer server.
  pinMode(CHARGING_LED, OUTPUT);
  pinMode(CONNECTED_LED, OUTPUT);
  pinMode(NOTIFICATION_SENT_LED, OUTPUT);
  pinMode(TEST_MODE_LED, OUTPUT);
  
  pinMode(START_BUTTON, INPUT_PULLUP);
  pinMode(TEST_JUMPER, INPUT_PULLUP);
  pinMode(NOTIFY_ON_FALL, INPUT_PULLUP);

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

  pushSaferPath = pushSaferURL +                          // String to send to PushSafer server.
                       "?d=" + deviceID + 
                       "&i=" + icon + 
                       "&t=" + title + 
                       "&m=" + message + 
                       "&k=" + pushSaferKey;
                       
  Serial.println("  ");
  Serial.println(pushSaferPath);                           // Check PushSafer URL on Serial Monitor for debugging.
                    
  WiFi.hostname(wifi_hostname.c_str());
  WiFi.begin(ssid, password);                              // Start WiFi
  
  unsigned long startAttemptTime = millis();  
   
  while (WiFi.status() != WL_CONNECTED &&                  // Attempt to connect to your home router.
    millis() - startAttemptTime < WIFI_TIMEOUT){
    delay(10);
  } 

  test_flag = digitalRead(TEST_JUMPER) == LOW;            // Set Test Flag if jumper is closed.
  falling = digitalRead(NOTIFY_ON_FALL) == LOW;           // Low (falling) means looking for charging LED to go out.  

  digitalWrite(TEST_MODE_LED, HIGH);  
  
  if (test_flag) {
    Serial.println("Waiting for Start Button");
  }

  while (digitalRead(START_BUTTON) == HIGH) {                         // Wait in a loop until the sensor is set up on the charger
    ESP.wdtFeed();                                                    // Keep the ESP8266 watchdog happy.
    adc_value = readSensor();
    digitalWrite(CHARGING_LED, (adc_value >= THRESHOLD));             // Show sensor value High or Low  (Echo Charger's LED)  
    
    if (WiFi.status() == WL_CONNECTED) {
      if (millis() - wifi_timer >= 200) {                             // Flash Connected LED status to indicate the device..
         wifi_timer = millis();                                       // is waiting for the Start button to be pressed.
         digitalWrite(CONNECTED_LED, !digitalRead(CONNECTED_LED));    // If it doesn't flash, WiFi isn't connected.
      }
    } else {
      digitalWrite(CONNECTED_LED, LOW);
    }
    
    if (test_flag) {                                                // Blink Built-in LED if device is in test mode.
       blinkTestLED();
    } 
  }
}


void loop() {
  
  digitalWrite(CONNECTED_LED, (WiFi.status() == WL_CONNECTED)); // Show WiFi Connected status.

  if (millis() - read_timer > SENSOR_TIMER) {                   // Read sensor every 2 seconds.
    adc_value = readSensor();                                   // Get ADC value from photo-diode.
    read_timer = millis();                                      // Reset delay timer.
    digitalWrite(CHARGING_LED, (adc_value >= THRESHOLD));       // Show value (high or low) on LED (Echo LED on charger)
  }

  if(WiFi.status()== WL_CONNECTED) {
      
    if (falling) {                                            // Looking for Charging LED to go out

       if (adc_value >= THRESHOLD) charging = true;           // Set 'charging' flag when charging LED on Charger lights.
       
                                                              // If charging light on charger goes out....
       if (charging && (notifyAttempts < ATTEMPTS) && (adc_value < THRESHOLD) && (notification_Sent_Flag == false)) {
          charging = false;

          if (!test_flag)  sendNotification();               // If the test jumper is open, send message to PushSafer.  

             
          digitalWrite(NOTIFICATION_SENT_LED, (notification_Sent_Flag || test_flag));        
         
          notifyAttempts++;                                  // Make only 3 attempts to contact PushSafer.
       }
    }

    else if (!falling) {                                     // Looking for charging LED to light

      if (adc_value <= THRESHOLD) charging = true;           // Set 'charging' flag when charging LED on Charger goes out.
      
                                                             // If cahrging light comes on....
         if (charging && (notifyAttempts < ATTEMPTS) && (adc_value > THRESHOLD) && (notification_Sent_Flag == false)) {
            charging = false;
            if (!test_flag)  sendNotification();               // If the test jumper is open, send message to PushSafer.
          
          digitalWrite(NOTIFICATION_SENT_LED, (notification_Sent_Flag || test_flag));        
         
          notifyAttempts++;                                 // Make only 3 attempts to contact PushSafer.
        }
     }
   }

   if (test_flag) {                                         // Blink Built-in LED if device is in test mode.
     blinkTestLED(); 
   } 


   if (few_left) {                                         // Flash Notification Sent LED if only a few Notifications left.

     if (millis() - few_left_timer >= 250) {
        few_left_timer = millis();
        few_left_state ? few_left_state = LOW  : few_left_state = notification_Sent_Flag;

        digitalWrite(NOTIFICATION_SENT_LED, few_left_state);
     }
  }
}


void sendNotification() {                                 // Send message to PushSafer to push notification to your phone.
   WiFiClient client;
   HTTPClient http;
      
   Serial.println("Making HTTP request to ...");
   Serial.println(pushSaferPath);
 
   http.begin(client, pushSaferPath.c_str());   

   int httpCode = http.GET();                                          // Send the request

     if (httpCode > 0) {                                               // Check the returning HTTP code
                      
       String payload = http.getString();                              // Get the response back from the server
       
       Serial.println("HTTP Response: ");                              // Print the response on Serial Monitor (for debugging)
       Serial.println(payload);
      
       JSONVar doc = JSON.parse(payload);                              // Parse the JSON data into JSONVar 'doc'

       int result = doc["status"];                                     // Get 'status' from JSON doc. (1 = success)
    
       notification_Sent_Flag = (result == 1);                         // Set notification_Sent flag if valid response from PushSafer

       if (notification_Sent_Flag) {                                   // Look for number of remaining notifications available
         
         int  numLeft = doc["available"];                              
         few_left = (numLeft <= NOTIFICATIONS_LOW);                    // Set "few_left" flag if available number is less than preset.

         Serial.print("Available: ");
         Serial.println(numLeft);
      } 
   } else {
     notification_Sent_Flag = false;                                   // No response received from PushSafer.
     charging = true;                                                  // reset 'Charging' flag to enable another attempt (max 3 attempts) 
   }

   http.end();                                                         // Close the HTTP connection  
   delay(5000); 
}