/******************************************************************************************
 * 
 * HAPPY BIRDS V2 compatible with TAA
 * 
 * Shelter camera software for ESP32-CAM
 * 
 * Version to be used with TAA Application
 * available on Apple Store and Google Play Store
 * 
 * (c) Papy et les Resistances 
 * 
 ****** Build instructions ******
 * Board: ESP32 Wrover module
 * Upload speed: 115200
 * Flash frequency: 80 MHz
 * Flash mode: QIO
 * Partition scheme: Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)
 * Core debug level: None
 * 
*******************************************************************************************/

/************* Libraries ******************************************************************/
#include "Arduino.h"
#include "FS.h"                 // SD Card ESP32
#include "SD_MMC.h"             // SD Card ESP32
#include "time.h"
#include "Battery.h"
#include "Camera.h"
#include "Firebase.h"
#include "Registers.h"
#include "Wifi.h"
#include "Eeprom.h"
#include "Setup.h"
#include <HTTPClient.h>
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>


String version = "1.9.1";

// 1.9.1
// Added mac address on console log
// 1.9.0
// Added firmware update OTA 
// 1.8.4
// Firebase_getPhotosCount: Set highest number to be returned in case Firebase call fails. This is to avoid exceeding the allowed max
// 1.8.3
// Update shelter node even when photos count exceeds the maximum
// 1.8.2
// Fixed battery reading: WakeUp pin influences Battery pin inputs 
// 1.8.1
// Reduced slightly snapshot time after boot
// 1.8.0
// Clean up, final version compatible with Happy Birds V2 and TAA
// 1.7.4 
// Change camera settings: only brightness. Contrast cannot be changed at the same time
// 1.7.3
// Moved camera init to beginning of initialization sequence: photo drastically improved
// 1.7.2
// Removed camera control feature. All settings are fixed.
// 1.7.1
// Fixed battery value again and removed gotoSleep after reset
// 1.7.0
// Added camera control feature
// 1.6.1
// Added random name generation in case local time cannot be obtained from server
// 1.6.0
// Customized version for TAA. RTDB Data model is different.
// 1.5.0
// Circular buffer of photos. If quota reached, will overwrite the oldest photo.
// 1.4.7
// Final fix for battery value
// 1.4.6
// Removed lat/long upload
// 1.4.5
// Added storage management
// 1.4.4
// Photo snapshot before starting wifi in order to limit power drain from wifi components 
// 1.4.3
// Fixed reading of battery value (must be done after SD card reader is disabled)
// 1.4.2
// All Bird Feeder settings are sent to Firebase including Lat/Long
// 1.4.0
// Changed Firebase calls to new nodes definition (Photos and BirdFeeder)
// Added full setup mode
// 1.3.0
// Major upgrade with Firebase access and refactoring of software
//


// Pin definition for Bird Feeder
#define PIN_WAKEUP  GPIO_NUM_13   // Deep sleep wake up pin from sensor 
#define PIN_MODE    GPIO_NUM_3    // To switch to Acces Point mode when setting up the application

// For time server
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 0;
const int   daylightOffset_sec = 3600;

// For Firebase database
String imageFile = "";
String imageURL = "";
String DateTime = "";

// Image buffer from camera
camera_fb_t *imageBuffer = NULL;
// Stored camera settings
RTC_DATA_ATTR CameraSettings cameraSettings; 


void setup()
{

  Registers_disableBrownoutDetector();
  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.println("BirdFeeder software version " + version);

  // First things first: initialise properly the camera at this stage and take a photo.
  // Cannot be done later, otherwise the photo will be too dark or greenish
  Camera_init();

  // To do before reading analog values
  Registers_saveRegB();

   // Disable SD card in order to reuse associated GPIO
  SD_MMC.begin("/sdcard", true);

  WRITE_PERI_REG(SENS_SAR_READ_CTRL2_REG, reg_b);

  // Read battery level
  pinMode(PIN_WAKEUP, OUTPUT);      // Mandatory instruction since the WakeUp pin influences Battery pin inputs.
  float batteryValue = Battery_getValue();
  Serial.println("Battery value = " +String(batteryValue));
  pinMode(PIN_WAKEUP, INPUT);       // Restore WakeUp pin input mode

  // Disable Flash LED
  int canal = 7;
  ledcSetup(canal, 5000, 8);
  ledcAttachPin(GPIO_NUM_4, canal);
  ledcWrite(canal, 0);
  
  // Setup EEPROM to read parameters
  Eeprom_init();
  
  // Detect current mode: if MODE push pbutton is on (GND), then setup the Wifi credentials by creating a Wifi access point and a local server
  pinMode(PIN_MODE, INPUT);
  boolean Mode = digitalRead(PIN_MODE) ;
  if (Mode == false) {
    
    // Pin is grounded by the push button
    Serial.println("Entering setup mode");
    Setup_getCredentials();   
    
  } else {
    
    // Normal mode: upload the photo to Firebase
    
    // Restore register b to allow Wifi
    Registers_restoreRegB();
    
    // Wifi initialisation
    uint8_t wifiConnectionStatus = Wifi_start();
    if (wifiConnectionStatus != WL_CONNECTED)
      gotoSleep();

    // Take the photo with the latest settings. For unknown reasons, the shoot cannot
    // be correct, with the right brightness, before wifi is ON. 
    Camera_set(cameraSettings);
    imageBuffer = Camera_shoot();       
    if (!imageBuffer) {
      Serial.println("Camera capture failed");
      delay(500);
      gotoSleep();
    } else {
        Serial.println("Camera capture successful");
    }  
      
      
    int wifiStrength = Wifi_getStrength();
  
    // Init and get the time to set the a unique file name for the photo
    DateTime = getLocalTime();
    Serial.println("UTC time = " + DateTime);
    imageFile = DateTime + ".jpeg";
     
    // Connection to Firebase backend
    Firebase_init();
    
    // Waiting for firebase readiness
    if (Firebase_isReady()) {
       Serial.println("Firebase ready"); 
    } else {
       Serial.println("Firebase not ready, timeout reached, going to sleep");
       gotoSleep();   
    }

    // Prepare shelter node update in any case
    FirebaseJson jsonBirdFeeder;
    jsonBirdFeeder.add("battery", String(batteryValue));
    jsonBirdFeeder.add("wifi", String(wifiStrength));
    jsonBirdFeeder.add("version", version);
    jsonBirdFeeder.add("mac", WiFi.macAddress());
          
    // Check if remaining space is available
    int photosCount = Firebase_getPhotosCount();
    int photosMaxCount = Firebase_getPhotosMaxCount();
    Serial.println("Remaining quota = " + String(photosMaxCount - photosCount) + " photos");
    if (photosCount < photosMaxCount) {
      
      // Allowed to upload

      // Get the latest camera settings and store for next time
      cameraSettings = Firebase_getCameraSettings();
      
      photosCount++;
      // Upload photo to Firebase storage
      Serial.print("Upload photo... ");
      if (Firebase_uploadImage(imageBuffer, imageFile)) {
        imageURL = Firebase_getImageURL();
      } else {
        Serial.println("Not allowed to send the photo");
        gotoSleep();
      }
    
      // Update the user node node in Firebase database
      //
      // Waiting for firebase readiness
      if (Firebase_isReady()) {
         Serial.println("Firebase ready"); 
      } else {
         Serial.println("Firebase not ready, timeout reached, going to sleep");
         gotoSleep();   
      }
    
      // Prepare the new Photo node with json structure
      FirebaseJson jsonPhoto;
      jsonPhoto.add("imageFile", imageFile);
      jsonPhoto.add("imageURL", imageURL);
    
      // Create the new Photo node
      Firebase_createPhotoNode(&jsonPhoto);
    
     // Waiting for firebase readiness
      if (Firebase_isReady()) {
         Serial.println("Firebase ready"); 
      } else {
         Serial.println("Firebase not ready, timeout reached, going to sleep");
         gotoSleep();   
      }
      
      // Prepare the user shelter node with new photos count
      jsonBirdFeeder.add("photosCount", photosCount);
 
    }
    
    // Update shelter node
    Firebase_updateBirdFeederNode(&jsonBirdFeeder);
    
    // All done, time to sleep again
    gotoSleep();
    
  } // Else Normal mode        
}

void loop()
{
  // Only used in setup mode for http server to listen to requests
}


void gotoSleep() {
  //
  // ESP32 go to deep sleep, waiting for sensor wake up
  //
  
  // Stop the wifi to save power during the deep sleep mode
  Wifi_stop();

 //Enable wake up when sensor output is low
  esp_sleep_enable_ext0_wakeup(PIN_WAKEUP, 0);
  Serial.println("Going to sleep now");

  // Hold the current value for GPIO 4 otherwise flash light will light on during deep sleep.
  gpio_hold_en(GPIO_NUM_4);

  // Go to sleep
  delay(1000);
  esp_deep_sleep_start();
  Serial.println("This will never be printed"); 
}


String getLocalTime(){
  //
  // This function is used to build a unique file name for the upload to firebase
  // Get the UTC time from ntp server. If server access failed, will return a random text
  //
  struct tm timeinfo;
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    long ran = random(1,2147000000);
    return String(ran);
  }
  //Serial.println(&timeinfo, "%Y-%B-%d" + "T" + "%H:%M:%S" + "Z");
  char dateTime[25];
  
  strftime(dateTime, sizeof(dateTime), "%Y-%m-%dT%H:%M:%SZ", &timeinfo);
  return String(dateTime);
}
