/*
 * CAMETEO project
 * 
 * This is a personnal project of weather station with
 * automatic photo taking.
 * This code is the weather station part and is meant 
 * to be run on a PJRC Teensy 3.2 board.
 * 
 * Author : Arofarn
 * 
 * Licence : GPL v3
 * 
 */

/*
 * LIBRARIES
 */
 
//Protocols
#include <Wire.h>   // library used with I2C protocol
#include <SPI.h>    // SPI protocol

//Teensy3.x Real Time Clock
#include <TimeLib.h>

//SD card
#include <SD.h>

// Sensors
#include <Adafruit_Sensor.h>    // Generic
#include <Adafruit_BME280.h>  // BME280
#include <PWFusion_AS3935.h>

//GPS
//#include <Adafruit_GPS.h>       // Adafruit Ultimate GPS
#include <TinyGPS.h>            //Builtin GPS lib

/*
 * DEFINE
 * &
 * DECLARE
 */

//Special characteres
#define DEGREE      (char)176  //degree symbol in ISO 8859-1
#define DEGREE_LCD  (char)222  //degree symbol for lcd display

// Pins used
#define RX1_PIN         0     // Raspberry Pi2 serial comm. - Hard wired
#define TX1_PIN         1     // Raspberry Pi2 serial comm. - Hard wired
#define RPI_PWR_PIN     2     // Raspberry Pi power control - Hard wired
#define AS3935_CS_PIN   4     // SPI SD CS                  - Hard wired
#define GPS_EN_PIN      6     // GPS ENable
#define RX3_PIN         7     // GPS serial comm.
#define TX3_PIN         8     // GPS serial comm.
#define AS3935_IRQ_PIN  9     // Interrupts from AS3935 lightning sensor
#define SD_CS_PIN       10    // SPI CS AS3935 lightning sensor
#define SPI_DI_PIN      11    // SPI MOSI AS3935 lightning sensor
#define SPI_DO_PIN      12    // SPI MISO AS3935 lightning sensor
#define SPI_CK_PIN      13    // SPI clock SD + AS3935 lightning sensor + built-in LED
#define BATT_VOLT_PIN   16    // Battery voltage monitoring (Analog Input)
#define LOW_BATT_PIN    17    // Low Battery signal from charger (digital input)
#define I2C_SDA_PIN     18    // I2C SDA BME280 sensor
#define I2C_SCL_PIN     19    // I2C SCL BME280 sensor
#define GPS_BUT_PIN     20    // GPS switch power control (unused)

//I2C addresses
//#define TCN75A_ADDR 0x00   //Sensor I2C bus address
//#define BMP180_ADDR 0x77

//Serial over USB communication
#define SERIAL_PORT       Serial
#define SERIAL_BAUD_RATE  9600

//Raspberry Pi Serial Comm.
#define RPI_SERIAL_PORT       Serial1
#define RPI_SERIAL_BAUD_RATE  9600

//GPS
#define GPS_SERIAL_PORT       Serial3
#define GPS_SERIAL_BAUD_RATE  9600

//Sensors
Adafruit_BME280 bme;
float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA;
float bme280_press;
float bme280_temp;
float bme280_alti;
float bme280_humi;

// AS3935 Lightning sensor
#define AS3935_INDOORS       0
#define AS3935_OUTDOORS      1
#define AS3935_DIST_DIS      0
#define AS3935_DIST_EN       1
#define AS3935_CAPACITANCE   72      // 72pF for THIS board (from seller)
enum strike_sources { UNKNOWN_SRC, LIGHTNING, PERTURBATION, NOISE };
volatile int8_t AS3935_ISR_Trig = 0;      // Trigger for AS3935 lightning sensor

void AS3935_ISR() {
  AS3935_ISR_Trig = 1;
}

PWF_AS3935 lightning(AS3935_CS_PIN, AS3935_IRQ_PIN, 33);

int as3935_src;
int as3935_distance;
long lightning_nb_total = 0;
int lightning_nb_hour = 0;
int lightning_nb_day = 0;
int as3935_perturb_total = 0;
int as3935_noise_total = 0;
int as3935_unknown_total = 0;
char lightning_log_file[12] = "lghtnng.log";

//GPS
//Adafruit_GPS GPS(&GPS_SERIAL_PORT);
TinyGPS GPS;
float gps_latitude, gps_longitude, gps_altitude;       // returns +- latitude/longitude in degrees
float gps_speed, gps_course;
unsigned long gps_time, gps_date, gps_fix_age;
int gps_year;
byte gps_month, gps_day, gps_hour, gps_minutes, gps_second, gps_hundreths;
unsigned long gps_chars, gps_hdop;
unsigned short gps_sentences, gps_failed_checksum, gps_satellites;

//Miscellaneous
bool rpi_status = true;
int  batt_voltage;
bool low_battery_flag = false;

// Tasks timers
elapsedMillis since_bme280;
elapsedMillis since_gps;
elapsedMillis since_display;
elapsedMillis since_serial_send;
elapsedMillis since_sd_log;
elapsedMillis since_batt_chk;


void BootMessage(String s) {
  char c[13];     // char buffer for conversion String->char
  s.toCharArray(c, sizeof(s));
  SERIAL_PORT.printf("%-12s", c);
}

void BootOK() {
  SERIAL_PORT.println("OK");
}

void BootError() {
  SERIAL_PORT.println("EE");
}

void sendDataOnSerial() {
  
  //Print date & hour on serial port
  SERIAL_PORT.printf("%04d/%02d/%02d_%02d:%02d:%02d\n", year(), month(), day(), hour(), minute(), second());
  
  //if (bme280_event.pressure) { 
    SERIAL_PORT.printf("Temperature:%8.2f %cC | Pressure: %8.2f hPa | Altitude:%8.2f m\n", 
                       bme280_temp, DEGREE, bme280_press, bme280_alti); 
//  }
//  else { 
//    SERIAL_PORT.printf("BMP180 Sensor error\n"); 
//  }

  SERIAL_PORT.printf("Lightning strikes (from start) : %d | Perturb.: %d | Noise : %d | Unknown detect.: %d\n", lightning_nb_total,
                                                                                                                as3935_perturb_total,
                                                                                                                as3935_noise_total,
                                                                                                                as3935_unknown_total);
  
  //SERIAL_PORT.printf("Temperature:%8.2f %cC\n", tcn75a_temp, DEGREE);

  SERIAL_PORT.printf("GPS data:  %04d/%02d/%02d_%02d:%02d:%02d (%d)\n", gps_year, gps_month, gps_day, 
                                                                        gps_hour, gps_minutes, gps_second,
                                                                        gps_fix_age);
  SERIAL_PORT.printf(" Latitude:  %11.8f | Longitude: %11.8f | Altitude: %5.2f m\n", gps_latitude, gps_longitude, gps_altitude);
  SERIAL_PORT.printf(" Speed:       %4.1f km/h | Course : %4.1f %c\n", gps_speed, gps_course, DEGREE);
  SERIAL_PORT.printf(" Chars:     %11d | Sentences: %11d | Failed cheksum: %4d\n", gps_chars, gps_sentences, gps_failed_checksum);

  SERIAL_PORT.printf("Battery : %10d mV | Low Battery : %d\n", batt_voltage, low_battery_flag);
}


bool isStartedPI() {
  //Return true if the R-Pi respond to an simple request
  return rpi_status;
}

void startRPI() {
  //Start the Raspberry Pi via the MOSFET (grid connected to the RPI_PWR_PIN)
  if (!isStartedPI()) {
    pinMode(RPI_PWR_PIN, OUTPUT);
    digitalWrite(RPI_PWR_PIN, LOW);
    rpi_status = true;  
  }
}

void stopRPI() {
  //Stop the Raspberry Pi via the MOSFET (grid connected to the RPI_PWR_PIN)

  pinMode(RPI_PWR_PIN, INPUT);      //Eteint via le MOSFET
  rpi_status = false;
}

void sendDataToRPI() {
  //if (isStartedPI()) {
      //Print date & hour on serial port
      RPI_SERIAL_PORT.printf("%04d/%02d/%02d_%02d:%02d:%02d\n", year(), month(), day(), hour(), minute(), second());
      
      //if (bme280_event.pressure) { 
        RPI_SERIAL_PORT.printf("Temperature:%8.2f %cC | Pressure: %8.2f hPa | Altitude:%8.2f m\n", 
                           bme280_temp, DEGREE, bme280_press, bme280_alti); 
      //}
      //else { 
      //    RPI_SERIAL_PORT.printf("BMP180 Sensor error\n"); 
      //}
    
      RPI_SERIAL_PORT.printf("Lightning strikes (from start) : %d | Perturb.: %d | Noise : %d | Unknown detect.: %d\n", lightning_nb_total,
                                                                                                                    as3935_perturb_total,
                                                                                                                    as3935_noise_total,
                                                                                                                    as3935_unknown_total);
    
      RPI_SERIAL_PORT.printf("GPS data:  %04d/%02d/%02d_%02d:%02d:%02d (%d)\n", gps_year, gps_month, gps_day, 
                                                                            gps_hour, gps_minutes, gps_second,
                                                                            gps_fix_age);
      RPI_SERIAL_PORT.printf(" Latitude:  %11.8f | Longitude: %11.8f | Altitude: %5.2f m\n", gps_latitude, gps_longitude, gps_altitude);
      RPI_SERIAL_PORT.printf(" Speed:       %4.1f km/h | Course : %4.1f %c\n", gps_speed, gps_course, DEGREE);
      RPI_SERIAL_PORT.printf(" Chars:     %11d | Sentences: %11d | Failed cheksum: %4d\n", gps_chars, gps_sentences, gps_failed_checksum);
    
      RPI_SERIAL_PORT.printf("Battery : %10d mV | Low Battery : %d\n", batt_voltage, low_battery_flag);  
//  }
//  else {
//    //error RPI is not started
//  }
}


//SD Card
#define CONFIGFILE  "config.txt"

//Configuration
char data_file[13] = "datalog.csv"; 

  //Delays

unsigned int bme280_delay       = 500;
//unsigned int dht_delay          = 3000;
unsigned int gps_delay          = 100;
unsigned int serial_send_delay  = 5000;
unsigned int sd_log_delay       = 5000;
unsigned int batt_chk_delay     = 5000;

//Date and time
int TZ = 1;     //Time zone

/*
 * SETUP
 */

void setup() {

  //To be sure Raspberry-Pi won't be turned on unexpectedly
  stopRPI();

  pinMode(LOW_BATT_PIN, INPUT_PULLUP);
  low_battery_flag = !digitalRead(LOW_BATT_PIN);
  
  pinMode(GPS_BUT_PIN, INPUT_PULLUP);
  pinMode(GPS_EN_PIN, OUTPUT);
  gps_power();
  
  SERIAL_PORT.begin(SERIAL_BAUD_RATE);
  delay(1000);
  //while (!SERIAL_PORT) { };        //Wait for serial port to start
  SERIAL_PORT.printf("Serial Comm. OK\nNow booting...\n");

  RPI_SERIAL_PORT.begin(RPI_SERIAL_BAUD_RATE);
  delay(1000);
  //while (!RPI_SERIAL_PORT) { };        //Wait for serial port to start
  RPI_SERIAL_PORT.printf("Raspberry's Serial Comm. OK\nNow booting...\n");

  BootMessage("SDcard"); 
  // see if the card is present and can be initialized:
  if (!SD.begin(SD_CS_PIN)) {
    BootError();
    while(1);
  }
  BootOK();
  
  BootMessage("RT Clock");
  // set the Time library to use Teensy 3.0's RTC to keep time
  setSyncProvider(getTeensy3Time);
  if (timeStatus()!= timeSet) {
    BootError();
    while(1);
  }
  BootOK();


  BootMessage("BME280 (Pressure, Humidity and Temperature)");
  /* Initialise the sensor */
//  if(!bme.begin())
//  {
//    /* There was a problem detecting the BMP085 ... check your connections */
//    BootError();
//    while(1);
//  }
  BootOK();

  BootMessage("AS3935 (Lightning)");
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV16);   // SPI speed to SPI_CLOCK_DIV16/1MHz (max 2MHz, NEVER 500kHz!)
  SPI.setDataMode(SPI_MODE1);             // MAX31855 is a Mode 1 device --> clock starts low, read on rising edge
  SPI.setBitOrder(MSBFIRST);              // data sent to chip MSb first 
  lightning.AS3935_DefInit();            // set registers to default  
  // now update sensor cal for your application and power up chip
  lightning.AS3935_ManualCal(AS3935_CAPACITANCE, AS3935_OUTDOORS, AS3935_DIST_EN);                  
  // enable interrupt (hook IRQ pin to Arduino Uno/Mega interrupt input: 0 -> pin 2, 1 -> pin 3 )
  attachInterrupt(AS3935_IRQ_PIN, AS3935_ISR, RISING);
  BootOK();

  BootMessage("GPS");
  GPS_SERIAL_PORT.begin(GPS_SERIAL_BAUD_RATE);
  while (!GPS_SERIAL_PORT) {} ;
  BootOK();
  
}

/*
 * LOOP
 */

void loop() {

  //Power ON/OFF GPS
  gps_power();

  //Lightning detection
  if (AS3935_ISR_Trig != 0) {
    AS3935_ISR_Trig = 0;
    time_t t = now();
    int distance = -9999;
    int energy = -9999;
    switch (as3935_src) {
      case UNKNOWN_SRC:
        //source inconnue
        as3935_unknown_total++;
        SERIAL_PORT.printf("Interruption (AS3935) : unkown source (not lightning)\n");
        break;
      case LIGHTNING:
        //Foudre !!!
        lightning_nb_total++;
        distance = lightning.AS3935_GetLightningDistKm();
        energy = lightning.AS3935_GetStrikeEnergyRaw();
        SERIAL_PORT.printf("Interruption (AS3935) : Lightningbolt !!!\n");
        SERIAL_PORT.printf("Distance : %4d km | Energy : %d \n", distance, energy);
        break;
      case PERTURBATION:
        //Perturbation
        as3935_perturb_total++;
        SERIAL_PORT.printf("Interruption (AS3935) : perturbation...\n");
        break;
      case NOISE:
        //Trop de bruit électromagnétique
        as3935_noise_total++;
        SERIAL_PORT.printf("Interruption (AS3935) : Too much electromagnetic noise!\n");
        break;
    }
    writeLightningToSD(as3935_src, t, distance, energy);
  }

  if (since_batt_chk >= batt_chk_delay) {
    since_batt_chk -= batt_chk_delay;
    //Check battery status and voltage
    low_battery_flag = !digitalRead(LOW_BATT_PIN);
    batt_voltage = getBatteryVoltage();
  }

//  if (since_bme280 >= bme280_delay) {
//    since_bme280 = since_bme280 - bme280_delay;
//    
//    bme280_press = bme.readPressure() / 100.0F;
//    bme280_alti = bme.readAltitude(seaLevelPressure);
//    bme280_temp = bme.readTemperature();
//    bme280_humi = bme.readHumidity();
//  }

  if (since_gps >= gps_delay) {
    since_gps = since_gps - gps_delay;
    while (GPS_SERIAL_PORT.available()) {
      char c = GPS_SERIAL_PORT.read();
      if (GPS.encode(c)) {
        GPS.get_datetime(&gps_date, &gps_time, &gps_fix_age);
        GPS.crack_datetime(&gps_year, &gps_month, &gps_day, 
                           &gps_hour, &gps_minutes, &gps_second,
                           &gps_hundreths, &gps_fix_age);
        GPS.f_get_position(&gps_latitude, &gps_longitude, &gps_fix_age);
        gps_altitude = GPS.f_altitude();
        gps_speed = GPS.f_speed_kmph();
        GPS.stats(&gps_chars, &gps_sentences, &gps_failed_checksum);
        gps_satellites = GPS.satellites();
        gps_hdop = GPS.hdop();
      }
    }
  }
  
  if (since_sd_log >= sd_log_delay) {
    since_sd_log = since_sd_log - sd_log_delay;
    writeDataToSD();
  }

  if (since_serial_send >= serial_send_delay) {
    since_serial_send = since_serial_send - serial_send_delay;
    sendDataOnSerial();
    sendDataToRPI();
  }
  
}

/*
 * FUNCTIONS
 */

time_t getTeensy3Time() {
  return Teensy3Clock.get();
}

void gps_power() {
  if (digitalRead(GPS_BUT_PIN) == 0) { digitalWrite(GPS_EN_PIN, LOW); }
  else { digitalWrite(GPS_EN_PIN, HIGH); }
}

int getBatteryVoltage() {
  long mean = 0;
  int U = 0;
  int n = 10;
  int res = 12;

  analogReadResolution(res);
  
  for (int i=0; i<n; i++) {
    mean += analogRead(BATT_VOLT_PIN);
    //delay(2);
  }
  mean /= n;

  U = map(mean, 0, pow(2, res)-1, 0, 3300);  // Convert data from ADC into input voltage in mV
  U *= 2;                           //Multiple by 2 for the voltage divider
  
  return U;
}

void writeDataToSD() {
  
  char dir[20];
  char path[60];
  
  String directory = dayDirectory();
  
  directory.toCharArray(dir, sizeof(directory));
  sprintf(path, "%s/%s", dir, data_file);

  // Test to know if the file exists before opening it, if it doesn't exist
  // we will write first an header
  bool no_header = SD.exists(path);
  
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  File dataFile = SD.open(path, FILE_WRITE);

  // if the file is available, write to it:
  if (dataFile) {
    if (!no_header) {
      SERIAL_PORT.printf("Creating file with CSV header : %s\n", path);
      dataFile.printf("Date");
      dataFile.printf(";BME280_Pressure(hPa);BME280_Temperature(degC);BME280_Humidity(%);BME280_Altitude(m)");
      dataFile.printf(";TotalLightningCount;TotalPerturbationEvents;TotalNoiseDetection;TotalUnknownDetection");
      dataFile.printf(";GPS_Latitude;GPS_Longitude;GPS_Altitude;GPS_Satellites;GPS_HDOP;GPS_Date");
      dataFile.printf(";Battery_voltage(mV);Low_Battery_Status");
      dataFile.printf("\n");
    }
    dataFile.printf("%04d/%02d/%02d_%02d:%02d:%02d", year(), month(), day(), hour(), minute(), second());
    dataFile.printf(";%.2f;%.2f;%.0f;%.1f", bme280_press, bme280_temp, bme280_humi, bme280_alti);
    dataFile.printf(";%d;%d;%d;%d", lightning_nb_total, as3935_perturb_total, as3935_noise_total, as3935_unknown_total);
    dataFile.printf(";%.8f;%.8f;%.2f;%d;%d;%04d/%02d/%02d_%02d:%02d:%02d", gps_latitude, gps_longitude, gps_altitude, 
                                                                     gps_satellites, gps_hdop,
                                                                     gps_year, gps_month, gps_day, 
                                                                     gps_hour, gps_minutes, gps_second);
    dataFile.printf(";%d;%d", batt_voltage, low_battery_flag);
    dataFile.printf("\n");
    
    dataFile.close();
  }
  SERIAL_PORT.printf("Data writen : %s\n", path);
}

void writeLightningToSD(int type, time_t t, int dist, int energy) {
  
  char dir[20];
  char path[60];
  
  String directory = dayDirectory();
  
  directory.toCharArray(dir, sizeof(directory));
  sprintf(path, "%s/%s", dir, lightning_log_file);

  // Test to know if the file exists before opening it, if it doesn't exist
  // we will write first an header
  bool no_header = SD.exists(path);
  
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  File dataFile = SD.open(path, FILE_WRITE);

  // if the file is available, write to it:
  if (dataFile) {
    if (!no_header) {
      SERIAL_PORT.printf("Creating file with CSV header : %s\n", path);
      dataFile.printf("Date");
      dataFile.printf(";distance(km);energie_raw");
      dataFile.printf(";TotalLightningCount;TotalPerturbationEvents;TotalNoiseDetection;TotalUnknownDetection");
      dataFile.printf(";BME280_Pressure(hPa);BME280_Temperature(degC);BME280_Humidty(%);BME280_Altitude(m)");
      dataFile.printf("\n");
    }
    dataFile.printf("%04d/%02d/%02d_%02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t));
    dataFile.printf(";%d;%d;%d", type, dist, energy);
    dataFile.printf(";%d;%d;%d;%d", lightning_nb_total, as3935_perturb_total, as3935_noise_total, as3935_unknown_total);
    dataFile.printf(";%.2f;%.2f;%.0f;%.1f", bme280_press, bme280_temp, bme280_humi, bme280_alti);
    dataFile.printf("\n");
    
    dataFile.close();
  }
  SERIAL_PORT.printf("Data writen : %s\n", path);
}

String dayDirectory() {
  
  char dir[20] ;
  
  sprintf(dir, "data/%04d/%02d/%02d/", year(), month(), day());
  
  if (!SD.exists(dir))
  {
    SERIAL_PORT.printf("Creating directory : %s ...", dir);
    SD.mkdir(dir);
    SERIAL_PORT.printf("   DONE!\n");
  }
  
  return String(dir);
}

