//Geiger Counter GPS Logger v1.0 by Simon Maxwell - 29/03/2016
//simon{at}dreamtechnologies{dot}co{dot}uk
//
//License:
//Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
//You are free to:
//
//    Share — copy and redistribute the material in any medium or format
//    Adapt — remix, transform, and build upon the material
//    for any purpose, even commercially.
//
//Please Attribute and Share-Alike. If you mod it or make it better, please send me a copy or post on the forum/blog you found this code! I'd appreciate that!
//
//This code is provided as-is. No warranty is given. If it breaks you get to keep all the parts. ;-)
//
//This is designed to add GPS and logging function to a NetIO GC-10 Geiger Counter, but should work for any
//Geiger counter that outputs CPM via RS232 port, such as those that talk to Radlog from Radmon.org
//It works by gathering GPS data  (date/time, latitude and longitude) from the GPS module, and CPM data from the Geiger Counter and saves it to an SD card every second.
//The data can (apparently) be imported into Google maps to view all samples on the map, along with date, time and CPM - How, I do not know at the time of writing this.
//
//This is based on the Arduino Pro-Mini but should work for Uno/Mega etc but may require a change in some pins.
//It uses a Catalex SD card module and GY-NEO6MV2. The bi-coloured LED is optional but handy to see to make sure it has a valid GPS lock.
//Please see ... for the schematic
//
//Please Note:
//You must disconnect the Geiger Counter RS232 pin from the Arduino before you upload.
//This is because the Geiger Counter RS232 uses the same Serial as the programmer.
//
//
//

//The libraries we need
#include <SoftwareSerial.h>
#include <TinyGPS.h> // http://arduiniana.org/libraries/tinygps/
#include <SPI.h>
#include <SD.h>
#include <stdlib.h>

//Setup our variables and libraries
TinyGPS GPS;
static char dToStrfBuffer[20];
const int chipSelect = 10;
String SDDateTime = "********";
String SDLat = "********";
String SDLon = "********";
SoftwareSerial ss1(4, 3);
const int greenLED = 8;
const int redLED = 9;
int LEDMode = 1;
char fileName[8];
bool fileNameIsSet = false;
bool SDCardIsInitialised = false;
String serialInData;

static void smartdelay(unsigned long ms);
static void print_float(float val, float invalid, int len, int prec, int SDVal);
static void print_int(unsigned long val, unsigned long invalid, int len);
static void print_date(TinyGPS &gps);
static void print_str(const char *str, int len);

//Vars for Geiger logging. uSv is only printed on the console and not logged.
int CPM = 0;
float uSv;
const float conversionFactor = 0.0057;
bool debug = false;

void setup()
{
  Serial.begin(9600); //Baud rate of the terminal and Geiger Counter device. They both use the same serial port. RX to receive CPM from Geiger Counter and TX to print to a terminal.
  ss1.begin(9600); //Baud rate of the GPS module
  pinMode(greenLED, OUTPUT);
  pinMode(redLED, OUTPUT);
  pinMode(chipSelect, OUTPUT);

  //Initialise to the SD Card
  if (!SD.begin(chipSelect))
  {
    SDCardIsInitialised = false;
    if (debug)
    {
      Serial.println("Card Failure - You did insert an SD card, right?");
    }
    return;
  }
  else
  {
    SDCardIsInitialised = true;
    if (debug)
    {
      Serial.println("Card Initialised!");
    }
  }
}

void loop()
{
  if (SDCardIsInitialised)
  {
    //Set the filename:
    //If the filename has not been set, then we set it once the GPS date/time has been received from GPS.
    //This filename is used to create a unique file on the SD card each time a logging session has started.
    //The filename is generated from the GPS date/time and reduced to 8 characters in length by removing certain characters and the year/seconds.
    //The format for the 8 character filename is as follows: [day] [day] [month] [month] [hour] [hour] [minute] [minute] eg: 15031842
    if (fileNameIsSet == false) //Do we have a filename set yet?
    {
      if (SDDateTime != "********") //Do we have date/time from GPS yet?
      {
        String strFileName = SDDateTime;
        strFileName.replace(" ", ""); //Remove spaces in date/time
        strFileName.replace("/", ""); //Remove slashes in date/time
        strFileName.replace(":", ""); //Remove colons in date/time
        strFileName.remove(4, 4); //Remove the year - we have to do this to reduce the file name to 8.3 characters
        strFileName.remove(8, 2); //Remove the seconds
        strFileName += ".csv"; //Add the file extension
        strFileName.toCharArray(fileName, 13); //Convert to char
        if (debug)
        {
          Serial.print("Filename has been set from GPS date/time and is: ");
          Serial.println(fileName);
        }
        fileNameIsSet = true; //The file name is set and logging can now start
      }
    }

    String dataString = "";
    float flat, flon;
    unsigned long age, date, time, chars = 0;
    //Print some stuff to the terminal, just cos.
    print_int(GPS.satellites(), TinyGPS::GPS_INVALID_SATELLITES, 5);
    print_date(GPS);
    //print_int(GPS.hdop(), TinyGPS::GPS_INVALID_HDOP, 5);
    GPS.f_get_position(&flat, &flon, &age);
    print_float(flat, TinyGPS::GPS_INVALID_F_ANGLE, 10, 6, 1);
    print_float(flon, TinyGPS::GPS_INVALID_F_ANGLE, 11, 6, 2);

    //Get the satellite count and set the LED accordingly.
    //Mode 1 = Red = <3 satellites locked
    //Mode 1 = Orange = 3 satellites locked
    //Mode 1 = Green = >3 satellites locked
    int satelliteCount = GPS.satellites();
    if (satelliteCount > 50)
    {
      satelliteCount = 0;
    }
    if (satelliteCount <= 3)
    {
      LEDMode = 1;
    }
    else if (satelliteCount == 4)
    {
      LEDMode = 2;
    }
    else if (satelliteCount == 5)
    {
      LEDMode = 2;
    }
    else if (satelliteCount >= 6)
    {
      LEDMode = 3;
    }
    setLED(LEDMode);




    //Create the data string to be written to the SD card
    dataString = SDDateTime + "," + SDLat + "," + SDLon + "," + CPM;

    //Open data file and write data
    if (fileNameIsSet) //Only do this if we have a date/time from GPS. If the date has not yet been received by GPS then we don't want to write to the card yet.
      //We also use the date/time to create a unique file name every time the logger is started.
    {
      File dataFile = SD.open(fileName, FILE_WRITE); //Open the file. If there is no file it is created automatically using the filename we generated from date/time
      if (dataFile) //Check if we have a file to write to
      {
        dataFile.println(dataString); //Write some data to the SD card
        dataFile.close(); //Close the SD card file as it is only at this point that data is actually written. Until it is closed the data to be written is stored in a buffer.
        if (debug)
        {
          Serial.print(" ::: Logging: "); //Print some more stuff to the terminal.
          Serial.println(dataString);
        }
      }
      else
      {
        if (debug)
        {
          Serial.println("\nCouldn't open the log file!");
        }
      }
    }
    else
    {
      if (debug)
      {
        Serial.println("No valid GPS date received. Not writing to log file!");
      }
    }
    smartdelay(1000);
    //Receive serial data from Serial, set CPM var and print to Serial if debug
    //    if (Serial.available() > 0) { //Do we have serial data?
    //      CPM = Serial.parseInt(); //Grab the integer in the received data and discard the rest
    //      if (debug)
    //      {
    //        uSv = CPM * conversionFactor; //Convert to micro Sieverts
    //        Serial.print(CPM); //Print the CPM and uSv to the terminal
    //        Serial.print("    ");
    //        Serial.print(uSv);
    //      }
    //    }

    serialInData = "";
    if (Serial.available() > 0)
    {
      int h = Serial.available();
      for (int i = 0; i < h; i++)
      {
        serialInData += (char)Serial.read();
      }
    }
    CPM = serialInData.toInt();
    if (debug)
    {
      uSv = CPM * conversionFactor; //Convert to micro Sieverts
      Serial.print(CPM); //Print the CPM and uSv to the terminal
      Serial.print("    ");
      Serial.print(uSv);
    }

  }
  else
  {
    digitalWrite(redLED, HIGH);
    delay(200);
    digitalWrite(redLED, LOW);
    delay(200);
  }
}

//Super rough function for setting LED colours - Red, Orange, Green from a bi coloured LED
static void setLED(int mode)
{
  if (mode == 1)
  {
    digitalWrite(greenLED, LOW);
    digitalWrite(redLED, HIGH);
  }
  else if (mode == 2)
  {
    digitalWrite(greenLED, HIGH);
    digitalWrite(redLED, HIGH);
  }
  else if (mode == 3)
  {
    digitalWrite(greenLED, HIGH);
    digitalWrite(redLED, LOW);
  }
}

//Read the GPS data
static void smartdelay(unsigned long ms)
{
  unsigned long start = millis();
  do
  {
    while (ss1.available())
      GPS.encode(ss1.read());
  } while (millis() - start < ms);
}

//Formatting functions for printing GPS data in a friendly format to the terminal, with some additions that just set a couple of variables to let us log data pretty.
//This is mostly original code from the TinyGPS example so comments are few and far between from here.
static void print_float(float val, float invalid, int len, int prec, int SDVal)
{
  if (val == invalid)
  {
    while (len-- > 1)
    {
      if (debug)
      {
        Serial.print('*');
      }
    }
    if (debug)
    {
      Serial.print(' ');
    }
    if (SDVal == 1) SDLat = "********"; //Set SDLat and SDLon to ******** until we get proper data from GPS
    else if (SDVal == 2) SDLon = "********";
  }
  else
  {
    if (debug)
    {
      Serial.print(val, prec);
    }
    if (SDVal == 1) SDLat = dtostrf(val, 10, 6, dToStrfBuffer); //Set the SDLat and SDLon variables for us to use with logging
    else if (SDVal == 2) SDLon = dtostrf(val, 10, 6, dToStrfBuffer);
    int vi = abs((int)val);
    int flen = prec + (val < 0.0 ? 2 : 1); // . and -
    flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1;
    for (int i = flen; i < len; ++i)
      if (debug)
      {
        Serial.print(' ');
      }
  }
  smartdelay(0);
}

static void print_int(unsigned long val, unsigned long invalid, int len)
{
  char sz[32];
  if (val == invalid)
    strcpy(sz, "*******");
  else
    sprintf(sz, "%ld", val);
  sz[len] = 0;
  for (int i = strlen(sz); i < len; ++i)
    sz[i] = ' ';
  if (len > 0)
    sz[len - 1] = ' ';
  if (debug)
  {
    Serial.print(sz);
  }
  smartdelay(0);
}

static void print_date(TinyGPS &gps)
{
  int year;
  byte month, day, hour, minute, second, hundredths;
  unsigned long age;
  GPS.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
  if (age == TinyGPS::GPS_INVALID_AGE)
  {
    if (debug)
    {
      Serial.print("********** ******** ");
    }
    SDDateTime = "********"; //Set SDDateTime to ******** until we get proper data from GPS
  }
  else
  {
    char sz[32];
    sprintf(sz, "%02d/%02d/%02d %02d:%02d:%02d ",
            day, month, year, hour, minute, second);
    if (debug)
    {
      Serial.print(sz);
    }
    SDDateTime = sz; //Set the SDDateTime variable for us to use with logging
  }
  print_int(age, TinyGPS::GPS_INVALID_AGE, 5);
  smartdelay(0);
}

static void print_str(const char *str, int len)
{
  int slen = strlen(str);
  for (int i = 0; i < len; ++i)
    if (debug)
    {
      Serial.print(i < slen ? str[i] : ' ');
    }
  smartdelay(0);
}
