#include <Arduino.h>

#define _DEBUG_

//------------------------------------------------------------------------------
// Sdfat configuration

#define SDFAT_FILE_TYPE 1
#define USE_FAT_FILE_FLAG_CONTIGUOUS 1
#define ENABLE_DEDICATED_SPI 1
#define SPI_DRIVER_SELECT 1
#define USE_LONG_FILE_NAMES 1
#define USE_SEPARATE_FAT_CACHE 0
#define USE_MULTI_SECTOR_IO 0
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// GPSport configuration 

#define GPSport_h
#define gpsPort Serial
//#define GPS_PORT_NAME "Serial"
#define DEBUG_PORT Serial
//------------------------------------------------------------------------------

#define THROW_ERROR_IF_NOT_FAST  // for digitalWriteFast.h

#include <SPI.h>
#include <NMEAGPS.h>            // v4.2.9 - SlashDevin (NeoGps library)
#include <digitalWriteFast.h>   // v1.2.0 - Watterott/Joachimsmeyer

#include "SdFat.h"              // v2.2.2 - B. Greiman
SdFat SD;

File trkFile;

NMEAGPS  gps; // This parses the GPS characters
gps_fix  fix; // This holds on to the latest values

// SPI pins
//const byte SD_SCK = 13;
//const byte SD_MOSI = 11;
//const byte SD_MISO = 12;
//const byte SD_CS = 10;

const byte SD_LED = 6;
const byte PWR_LED = 7;
const byte BATT_IN = A0;
const byte SDLED_HI = 255;
const byte SDLED_LO = 64;

const int LOCAL_TIMEZONE = -3;
const int SECONDS_PER_HOUR = 3600;
const int32_t TIMEZONE_OFFSET = LOCAL_TIMEZONE * SECONDS_PER_HOUR;
const uint32_t DEBOUNCER_TIME = 20UL;
const uint32_t BATTERY_SAMPLE_TIME = 5 * 60000UL; // 5 min en mseg
const uint32_t BLINK_INTERVAL = 1000UL;
const uint32_t CHKBATT_INTERVAL = 1000UL;

bool validSDcard = true;
bool createFile = true;
bool validFix = false;
bool validTime = false;
bool validDate = false;
bool sdIndicator = false;
bool lowBattery = false;
bool chkBattery = false;

String fileName;

uint32_t tkpCounter;
uint32_t sdTime;
uint32_t repeatsCounter;
uint32_t repeatsControl;
uint32_t repeatsInterval;
uint32_t batteryTimeOld;
uint32_t blinkTimeOld;
uint32_t chkTimeOld;

struct memory_s {
  float lat;
  float lng;
  float altitude;
  float speed;
  float course;
} memory;
NeoGPS::time_t localTime;

//------------------------------------------------------------------------------
// convert UTC (from GPS) to local time

void get_local_time(NeoGPS::time_t dt) {
  NeoGPS::clock_t seconds = dt; // convert date/time structure to seconds
  seconds += TIMEZONE_OFFSET;
  localTime = seconds; // convert seconds back to a date/time structure
}
//------------------------------------------------------------------------------

void check_battery() {
  static int count = 0;
  static int samplesAcc = 0;

  if (count < 10) {
    samplesAcc += analogRead(A0);
    count++;
  }
  else {
    lowBattery = (samplesAcc / 10) <= 861;
    samplesAcc = 0;
    count = 0;
    chkBattery = false;
  }
}

void leer_datos_gps() {
  while (gps.available(gpsPort)) {
    fix = gps.read();
    validFix = gps.is_safe();
  }
}

//------------------------------------------------------------------------------
// call back for file timestamps
void dateTime(uint16_t* date, uint16_t* time) {
  // return date using FAT_DATE macro to format fields
  *date = FAT_DATE(localTime.year + 2000, localTime.month, localTime.date);

  // return time using FAT_TIME macro to format fields
  *time = FAT_TIME(localTime.hours, localTime.minutes, localTime.seconds);
}
//------------------------------------------------------------------------------

void setup() {
  pinModeFast(PWR_LED, OUTPUT);
  digitalWriteFast(PWR_LED, HIGH);
  pinModeFast(SD_LED, OUTPUT);
  digitalWriteFast(SD_LED, LOW);

  fileName.reserve(22);

// DEBUG_PORT.begin(19200);
  gpsPort.begin(19200);

  validSDcard = SD.begin(); //(SD_CS);
  digitalWriteFast(SD_LED, validSDcard ? HIGH : LOW);


  leer_datos_gps();
}

void loop() {

  
  if (validFix) validDate = fix.valid.date && ((fix.dateTime.year + 2000) > 2020);
  
  if (fix.valid.time && validDate) {
    get_local_time(fix.dateTime);
  }

  if (validSDcard) {
    if (createFile) {
      if (fix.valid.time && validDate) {
        char dt[20]  = "00000000_000000.csv";
        int loc_year = localTime.year + 2000;
        dt[0] = loc_year / 1000 + 48;
        dt[1] = loc_year % 1000 / 100 + 48;
        dt[2] = loc_year % 100 / 10 + 48;
        dt[3] = loc_year % 10 + 48;
        dt[4] = localTime.month / 10 + 48;
        dt[5] = localTime.month % 10 + 48;
        dt[6] = localTime.date / 10 + 48;
        dt[7] = localTime.date % 10 + 48;
        dt[9] = localTime.hours / 10 + 48;
        dt[10] = localTime.hours % 10 + 48;
        dt[11] = localTime.minutes / 10 + 48;
        dt[12] = localTime.minutes % 10 + 48;
        dt[13] = localTime.seconds / 10 + 48;
        dt[14] = localTime.seconds % 10 + 48;
        fileName = dt;
        // set date time callback function
        SdFile::dateTimeCallback(dateTime);
        trkFile = SD.open(fileName, FILE_WRITE);

        if (trkFile) {
#ifdef _DEBUG_
  DEBUG_PORT.println("File " + fileName + " created");
#endif
          sdIndicator = true;
          sdTime = millis();
          digitalWriteFast(SD_LED, LOW);
          trkFile.println(F("trackpoint,date,time,latitude,longitude,alt,speed,course"));
#ifdef _DEBUG_
  DEBUG_PORT.println(F("trackpoint,date,time,latitude,longitude,alt,speed,course"));
#endif
          //trkFile.sync();
          trkFile.close();
          createFile = false;
        }
      }
    }

    if (validFix) {
      bool newGpsData = fix.latitude() != memory.lat;
      newGpsData |= fix.longitude() != memory.lng;
      newGpsData |= fix.altitude() != memory.altitude;
      newGpsData |= fix.speed_kph() != memory.speed;
      newGpsData |= fix.heading() != memory.course;

      repeatsCounter++;
      repeatsControl++;

      if (repeatsControl > 340UL) repeatsInterval = 45UL;
      else if (repeatsControl > 160UL) repeatsInterval = 30UL;
      else if (repeatsControl > 70UL) repeatsInterval = 15UL;
      else if (repeatsControl > 10UL) repeatsInterval = 10UL;
      else repeatsInterval = 1UL;

      if ((newGpsData) || (repeatsCounter >= repeatsInterval)) {  // si los datos son repetitivos los guarda cada "repeatsInterval" seg
        repeatsCounter = 0;
        if (newGpsData) repeatsControl = 0;
        memory.lat = fix.latitude();
        memory.lng = fix.longitude();
        memory.altitude = fix.altitude();
        memory.speed = fix.speed_kph();
        memory.course = fix.heading();

        trkFile = SD.open(fileName, FILE_WRITE);
        if (trkFile) {
          sdTime = millis();
          sdIndicator = true;
          digitalWriteFast(SD_LED, LOW);

          trkFile.print(++tkpCounter);
          trkFile.print(F(","));
#ifdef _DEBUG_
  DEBUG_PORT.print(tkpCounter);
  DEBUG_PORT.print(F(","));
#endif
          char dt[12] = "0000/00/00,";
          int loc_year = fix.dateTime.year + 2000;
          dt[0] = loc_year / 1000 + 48;
          dt[1] = loc_year % 1000 / 100 + 48;
          dt[2] = loc_year % 100 / 10 + 48;
          dt[3] = loc_year % 10 + 48;
          //        dt[4] = '/';
          dt[5] = fix.dateTime.month / 10 + 48;
          dt[6] = fix.dateTime.month % 10 + 48;
          //        dt[7] = dt[4];
          dt[8] = fix.dateTime.date / 10 + 48;
          dt[9] = fix.dateTime.date % 10 + 48;
          //        dt[10] = ',';
          dt[11] = '\0';
          trkFile.print(dt);
#ifdef _DEBUG_
          DEBUG_PORT.print(dt);
#endif
          strncpy(dt, "00:00:00,", 12);
          dt[0] = fix.dateTime.hours / 10 + 48;
          dt[1] = fix.dateTime.hours % 10 + 48;
          // dt[2] = ':';
          dt[3] = fix.dateTime.minutes / 10 + 48;
          dt[4] = fix.dateTime.minutes % 10 + 48;
          // dt[5] = dt[2];
          dt[6] = fix.dateTime.seconds / 10 + 48;
          dt[7] = fix.dateTime.seconds % 10 + 48;
          // dt[8] = ',';
          dt[9] = '\0';
          trkFile.print(dt);

          trkFile.print(fix.latitude(), 6) ;
          trkFile.print(F(","));
          trkFile.print(fix.longitude(), 6) ;
          trkFile.print(F(","));
          trkFile.print(fix.altitude(), 2);
          trkFile.print(F(","));
          trkFile.print(fix.speed_kph(), 2) ;
          trkFile.print(F(","));
          trkFile.println(fix.heading(), 2) ;
#ifdef _DEBUG_
          DEBUG_PORT.print(dt);
          DEBUG_PORT.print(fix.latitude(), 6) ;
          DEBUG_PORT.print(F(","));
          DEBUG_PORT.print(fix.longitude(), 6) ;
          DEBUG_PORT.print(F(","));
          DEBUG_PORT.print(fix.altitude(), 2);
          DEBUG_PORT.print(F(","));
          DEBUG_PORT.print(fix.speed_kph(), 2) ;
          DEBUG_PORT.print(F(","));
          DEBUG_PORT.println(fix.heading(), 2) ;
#endif
          //trkFile.sync();
          trkFile.close();
        }
        else {
          createFile = true;
        }
      }
    }
  }

  if (sdIndicator && (millis() - sdTime >= 150UL)) {
    sdIndicator = false;
    digitalWriteFast(SD_LED, HIGH);
  }

  if (!lowBattery) {
    if (!chkBattery) {
      if (millis() - batteryTimeOld >= BATTERY_SAMPLE_TIME) {
        chkBattery = true;
        chkTimeOld = millis();
        batteryTimeOld += BATTERY_SAMPLE_TIME;
        check_battery();
      }
    }
    else {
      if (millis() - chkTimeOld >= CHKBATT_INTERVAL) {
        check_battery();
      }
    }
  }
  else {
    if (millis() - blinkTimeOld >= BLINK_INTERVAL) {
      digitalWriteFast(PWR_LED, digitalReadFast(PWR_LED) == HIGH ? LOW : HIGH);
      blinkTimeOld += BLINK_INTERVAL;
    }
  }
  validFix = false;
  validTime = false;
  validDate = false;
  leer_datos_gps();
}
GPS-EmulatorBreakout