//Accompanying code for Instructable 'Arduino Star-Finder for Telescopes'
//Written by user DentDentArthurDent

//Here are the libraries that are needed for this sketch
#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <LiquidCrystal.h>
#include <Wire.h>
#include <Adafruit_ADS1015.h>

//Initialise GPS function from TinyGPS library
TinyGPS gps;
SoftwareSerial ss(51, 50);
static void smartdelay(unsigned long ms);

//Initialises the ADC library
Adafruit_ADS1015 ads1015;

//Attaching the LEDs to specific pins on the MEGA
const int ledup = 44;
const int leddown = 42;
const int ledleft = 40;
const int ledright = 46;

//Initialising the various variables used for the calculations as well as other buttons
int16_t altpot;
int16_t azpot;
int altpotgood;
int azpotgood;
int calibalt;
int calibaz;
double polarisalt;
double polarisaz;
const int central = 7;
int centralstate = 0;
double target;
double coordinates;
const int toggledown = 8;
const int toggleup = 9;
int n = 666;
double targetra;
double targetdec;
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7); //Initialises pins for the LCD screen
double ra; //These variables are used in the calculations, double/float/int/byte depending on the type of number needed.
double dec;
double juliandate;
double gstresult;
double lstresult;
float gpslat;
float gpslon;
int gpsyear;
byte gpsmonth, gpsday, gpshour, gpsminute, gpssecond, hundredths;
float thisday;
unsigned long age;
double extra;
double timenow;

void setup() {
  lcd.begin(16, 2);
  ads1015.begin();
  ads1015.setGain(GAIN_TWOTHIRDS); //scales the output of the ADC. See the Adafruit info page online for more info.
  pinMode(toggledown, INPUT);
  pinMode(toggleup, INPUT);
  pinMode(central, INPUT);
  pinMode(ledup, OUTPUT);
  pinMode(leddown, OUTPUT);
  pinMode(ledleft, OUTPUT);
  pinMode(ledright, OUTPUT);
  ss.begin(9600); //Software serial begin, baud rate 9600.
  lcd.setCursor(0, 0);
  lcd.print("Hello astronomer");
  lcd.setCursor(0, 1);
  lcd.print("Happy observing!");
  delay(4000); //Wait so that the user can read the welcome message.
  gps.f_get_position(&gpslat, &gpslon); //Reads current latitude and longitude
  while ((gpslat == 1000) || (gpslon == 1000)) { //1000 is the value that the tinyGPS library produces if no GPS signal is detected.
    gps.f_get_position(&gpslat, &gpslon); //This while loop will wait until the GPS acquires a signal.
    lcd.setCursor(0, 0);
    lcd.print("*NO GPS SIGNAL* ");
    lcd.setCursor(0, 1);
    lcd.print("                ");
    smartdelay(1000);
  }
  gpslat = (gpslat / 360) * (2 * PI); //Degrees to radians conversion.
}

void loop() { //This section reads the toggle switch and sets the value 'n' that corresponds to a particular item in the object database below.s
  coordinates = getradec();
  if (n != 666) {
    if ((digitalRead(toggledown) == HIGH) || (digitalRead(toggleup) == HIGH)) {
      if (digitalRead(toggledown) == HIGH) {
        if (n == 45) {
          n = 1;
        }
        else {
          n = n + 1;
        }
      }
      else {
        if (n == 1) {
          n = 45;
        }
        else {
          n = n - 1;
        }
      }
    }
  }
  gps.crack_datetime(&gpsyear, &gpsmonth, &gpsday, &gpshour, &gpsminute, &gpssecond, &hundredths, &age); //Reads time and date from the GPS unit.
  timenow = ((double)gpshour + ((((double)gpssecond / 60.0) + (double)gpsminute) / 60.0)); //Merges hours, minutes and seconds.
  juliandate = julian();
  gstresult = utctogst();
  lstresult = gsttolst();
  azalt();
  smartdelay(250);
  if (n == 666) {
    n = 1;
  }
}

static void smartdelay(unsigned long ms) //Defines smartdelay. 
{
  unsigned long start = millis();
  do
  {
    while (ss.available())
      gps.encode(ss.read());
  } while (millis() - start < ms);
}


float getradec() { //This section contains the object database.
  if (n == 666) { //Polaris is given an 'n' number outside of the normal range, so that it can only be summoned once, at the very beginning of the code.
    targetra = 37.95454;
    targetdec = 89.26411;
    lcd.setCursor(0, 0);
    lcd.print("ALIGN ON POLARIS"); //Polaris is unusual as it is used for the initial calibration procedure.
    lcd.setCursor(0, 1);
    lcd.print("AND PRESS BUTTON");
  }
  if (n == 1) {
    targetra = 6.02363;
    targetdec = -72.08128;
    lcd.setCursor(0, 0);
    lcd.print("47 TUCANAE, CLST");
  }
  if (n == 2) {
    targetra = 130.1;
    targetdec = 19.98333;
    lcd.setCursor(0, 0);
    lcd.print("BEEHIVE CLUSTER ");
  }
  if (n == 3) {
    targetra = 66.75;
    targetdec = 15.86667;
    lcd.setCursor(0, 0);
    lcd.print("HYADES CLUSTER  ");
  }
  if (n == 4) {
    targetra = 193.41254;
    targetdec = -60.36161;
    lcd.setCursor(0, 0);
    lcd.print("JEWEL BOX, CLSTR");
  }
  if (n == 5) {
    targetra = 245.89775;
    targetdec = -26.52536;
    lcd.setCursor(0, 0);
    lcd.print("M4 CLUSTER      ");
  }
  if (n == 6) {
    targetra = 251.81042;
    targetdec = -1.94781;
    lcd.setCursor(0, 0);
    lcd.print("M12 CLUSTER     ");
  }
  if (n == 7) {
    targetra = 264.40063;
    targetdec = -3.24594;
    lcd.setCursor(0, 0);
    lcd.print("M14 CLUSTER     ");
  }
  if (n == 8) {
    targetra = 322.49375;
    targetdec = 12.16681;
    lcd.setCursor(0, 0);
    lcd.print("M15 CLUSTER     ");
  }
  if (n == 9) {
    targetra = 351.20504;
    targetdec = 61.59994;
    lcd.setCursor(0, 0);
    lcd.print("M52 CLUSTER     ");
  }
  if (n == 10) {
    targetra = 189.86675;
    targetdec = -26.74281;
    lcd.setCursor(0, 0);
    lcd.print("M68 CLUSTER     ");
  }
  if (n == 11) {
    targetra = 116.12342;
    targetdec = -23.85483;
    lcd.setCursor(0, 0);
    lcd.print("M93 CLUSTER     ");
  }
  if (n == 12) {
    targetra = 248.13279;
    targetdec = -13.05356;
    lcd.setCursor(0, 0);
    lcd.print("M107 CLUSTER    ");
  }
  if (n == 13) {
    targetra = 154.40329;
    targetdec = -46.41103;
    lcd.setCursor(0, 0);
    lcd.print("NGC 3201 CLUSTER");
  }
  if (n == 14) {
    targetra = 194.89567;
    targetdec = -70.87453;
    lcd.setCursor(0, 0);
    lcd.print("NGC 4833 CLUSTER");
  }
  if (n == 15) {
    targetra = 201.69142;
    targetdec = -47.47681;
    lcd.setCursor(0, 0);
    lcd.print("OMEGA CEN., CLST");
  }
  if (n == 16) {
    targetra = 56.87117;
    targetdec = 24.10503;
    lcd.setCursor(0, 0);
    lcd.print("PLEIADES CLUSTER");
  }
  if (n == 17) {
    targetra = 269.63917;
    targetdec = 66.63314;
    lcd.setCursor(0, 0);
    lcd.print("CAT'S EYE NEBULA");
  }
  if (n == 18) {
    targetra = 100.24271;
    targetdec = 9.89558;
    lcd.setCursor(0, 0);
    lcd.print("CONE NEBULA     ");
  }
  if (n == 19) {
    targetra = 303.02700;
    targetdec = 38.35497;
    lcd.setCursor(0, 0);
    lcd.print("CRESCENT NEBULA ");
  }
  if (n == 20) {
    targetra = 299.90133;
    targetdec = 22.72150;
    lcd.setCursor(0, 0);
    lcd.print("DUMBBELL NEBULA ");
  }
  if (n == 21) {
    targetra = 274.70021;
    targetdec = -13.80694;
    lcd.setCursor(0, 0);
    lcd.print("EAGLE NEBULA    ");
  }
  if (n == 22) {
    targetra = 151.75675;
    targetdec = -40.43619;
    lcd.setCursor(0, 0);
    lcd.print("EIGHT-BURST, NEB");
  }
  if (n == 23) {
    targetra = 112.29492;
    targetdec = 20.91175;
    lcd.setCursor(0, 0);
    lcd.print("ESKIMO NEBULA   ");
  }
  if (n == 24) {
    targetra = 161.27513;
    targetdec = -59.86661;
    lcd.setCursor(0, 0);
    lcd.print("ETA CARINAE, NEB");
  }
  if (n == 25) {
    targetra = 337.41025;
    targetdec = -20.83686;
    lcd.setCursor(0, 0);
    lcd.print("HELIX NEBULA    ");
  }
  if (n == 26) {
    targetra = 204.89583;
    targetdec = -67.38083;
    lcd.setCursor(0, 0);
    lcd.print("HOURGLASS NEBULA");
  }
  if (n == 27) {
    targetra = 173.94717;
    targetdec = -63.01936;
    lcd.setCursor(0, 0);
    lcd.print("IC 2944 NEBULA  ");
  }
  if (n == 28) {
    targetra = 270.92504;
    targetdec = -24.38;
    lcd.setCursor(0, 0);
    lcd.print("LAGOON NEBULA   ");
  }
  if (n == 29) {
    targetra = 83.82163;
    targetdec = -5.39081;
    lcd.setCursor(0, 0);
    lcd.print("ORION NEBULA    ");
  }
  if (n == 30) {
    targetra = 168.69875;
    targetdec = 55.01914;
    lcd.setCursor(0, 0);
    lcd.print("OWL NEBULA      ");
  }
  if (n == 31) {
    targetra = 283.39588;
    targetdec = 33.02914;
    lcd.setCursor(0, 0);
    lcd.print("RING NEBULA     ");
  }
  if (n == 32) {
    targetra = 316.04513;
    targetdec = -11.36342;
    lcd.setCursor(0, 0);
    lcd.print("SATURN NEBULA   ");
  }
  if (n == 33) {
    targetra = 259.08780;
    targetdec = -59.4899;
    lcd.setCursor(0, 0);
    lcd.print("STINGRAY NEBULA ");
  }
  if (n == 34) {
    targetra = 84.67621;
    targetdec = -69.10064;
    lcd.setCursor(0, 0);
    lcd.print("TARANTULA NEBULA");
  }
  if (n == 35) {
    targetra = 270.63104;
    targetdec = -22.99944;
    lcd.setCursor(0, 0);
    lcd.print("TRIFID NEBULA   ");
  }
  if (n == 36) {
    targetra = 311.42513;
    targetdec = 30.71664;
    lcd.setCursor(0, 0);
    lcd.print("VEIL NEBULA     ");
  }
  if (n == 37) {
    targetra = 10.68471;
    targetdec = 41.26892;
    lcd.setCursor(0, 0);
    lcd.print("ANDROMEDA GALAXY");
  }
  if (n == 38) {
    targetra = 194.18217;
    targetdec = 21.68186;
    lcd.setCursor(0, 0);
    lcd.print("BLACK EYE GALAXY");
  }
  if (n == 39) {
    targetra = 148.88813;
    targetdec = 69.06533;
    lcd.setCursor(0, 0);
    lcd.print("BODE'S GALAXY   ");
  }
  if (n == 40) {
    targetra = 148.96796;
    targetdec = 69.67983;
    lcd.setCursor(0, 0);
    lcd.print("CIGAR GALAXY    ");
  }
  if (n == 41) {
    targetra = 80.89433;
    targetdec = -69.75611;
    lcd.setCursor(0, 0);
    lcd.print("LARGE MAGELLANIC");
  }
  if (n == 42) {
    targetra = 13.15838;
    targetdec = -72.80028;
    lcd.setCursor(0, 0);
    lcd.print("SMALL MAGELLANIC");
  }
  if (n == 43) {
    targetra = 189.99775;
    targetdec = -11.62294;
    lcd.setCursor(0, 0);
    lcd.print("SOMBRERO GALAXY ");
  }
  if (n == 44) {
    targetra = 23.46213;
    targetdec = 30.65986;
    lcd.setCursor(0, 0);
    lcd.print("TRIANGULUM, GLXY");
  }
  if (n == 45) {
    targetra = 202.46963;
    targetdec = 47.19519;
    lcd.setCursor(0, 0);
    lcd.print("WHIRLPOOL GALAXY");
  }
}

float julian() { //For more info see 'Practical Astronomy With Your Calculator'.
  thisday = ((gpsday - 1.0) + (timenow / 24.0));
  if (gpsmonth == 1 || gpsmonth == 2) {
    gpsmonth = gpsmonth + 12;
    gpsyear = gpsyear - 1;
  }
  int a = floor ((double)gpsyear / 100);
  int b = 2 - a + floor (a / 4);
  long c = (365.25 * (double)gpsyear);
  float d = floor (30.6001 * ((double)gpsmonth + 1));
  float jd = b + c + d + (double)thisday + 1720994.5;
  return jd; //'jd' being the Julian Date.
}

float utctogst() { //Converts UTC (Univeral Time) to GST (Greenwich Sidereal Time). 
  double s = juliandate - 2451545.0;
  double t = s / 36525.0;
  double step1 = (2400.051336 * t);
  double step2 = (t * t);
  double step3 = (0.000025862 * step2);
  double to = (6.697374558 + step1 + step3);
  double n1 = floor (to / 24);
  to = to - (n1 * 24);
  double h1 = (timenow * 1.002737909);
  double n2 = floor ((to + h1) / 24.0);
  double gst = (to + h1) - (n2 * 24.0);
  return gst;
}

float gsttolst() { //Converts GST (Greenwich Sidereal Time) to LST (Local Sidereal Time).
  double diff = abs(gpslon);
  diff = (diff / 15);
  double lst;
  if ((gpslon * -1) > 0)
  {
    gstresult = gstresult - diff;
  }
  else
  {
    gstresult = gstresult + diff;
  }
  if (gstresult > 24)
  {
    lst = gstresult - 24;
  }
  else if ((gstresult * -1) > 0) {
    lst = gstresult + 24;
  }
  else
  {
    lst = gstresult;
  }
  return lst;
}

void azalt() { //This section calculates the Azimuth and the Altitude of the target object.
  ra = (targetra / 15);
  double h = 15.0 * (lstresult - ra);
  h = (h / 360) * (2 * PI);
  dec = ((targetdec / 360) * (2 * PI));
  double sindec = sin(dec);
  double sinlat = sin(gpslat);
  double cosdec = cos(dec);
  double coslat = cos(gpslat);
  double jeremy = cos(h);
  double sinalt = (sindec * sinlat) + (cosdec * coslat * jeremy);
  double alt = asin(sinalt);
  double cosalt = cos(alt);
  alt = ((alt / (2 * PI)) * 360);
  double cosaz = (sindec - (sinlat * sinalt)) / (coslat * cosalt);
  double az = ((acos(cosaz)) * 4068) / 71;
  double sinhh = sin(h);
  if ((sinhh * -1) > 0) {
    az = az;
  }
  else
  {
    az = 360.0 - az;
  }

  if (n == 666) { //This variable will only equal 666 when the Arduino is first powered on, so it triggers the calibration sequence.
    centralstate = digitalRead(central);
    while (centralstate == LOW) { //Keep reading the potentiometer values until the central button is pressed.
      altpot = ads1015.readADC_SingleEnded(1);
      azpot = ads1015.readADC_SingleEnded(0);
      centralstate = digitalRead(central);
    }
    calibalt = altpot; 
    calibaz = azpot;
    polarisalt = alt;
    polarisaz = az;
    lcd.setCursor(0, 0);
    lcd.print("    COMPLETE    "); //Calibration sequence is complete.
    lcd.setCursor(0, 1);
    lcd.print("                ");
    delay(2000);
  }

  else {
    if ((alt * -1) > 0) { //If altitude is below 0 degrees, then the object is below the observer's horizon.
      lcd.setCursor(0, 1);
      lcd.print("IS BELOW HORIZON"); 
      digitalWrite(ledup, LOW);
      digitalWrite(leddown, LOW);
      digitalWrite(ledright, LOW);
      digitalWrite(ledleft, LOW);
    }
    else {
      lcd.setCursor(0, 1); //This starts the next text to be printed at a specific character on the LCD screen.
      lcd.print(az, 3);
      lcd.setCursor(7, 1);
      lcd.print(" / ");
      lcd.setCursor(10, 1);
      lcd.print(alt, 3);
      altpot = ads1015.readADC_SingleEnded(1);
      azpot = ads1015.readADC_SingleEnded(0);
      if (alt < polarisalt) {
        altpotgood = (calibalt + ((polarisalt - alt) * 6.47777));
      }
      else if (alt > polarisalt) {
        altpotgood = (calibalt - ((alt - polarisalt) * 6.47777));
      }
      if (az < polarisaz) {
        if (az > 180) {
          azpotgood = (calibaz + ((polarisaz - az) * 4.88529));
        }
        else if (az < 180) {
          azpotgood = (calibaz - (((az + 360) - polarisaz) * 4.88529));
        }
      }
      else if (az > polarisaz) {
        if (az > 180) {
          azpotgood = (calibaz + (((polarisaz + 360.0) - az) * 4.88529));
        }
        else if (az < 180) {
          azpotgood = (calibaz - ((az - polarisaz) * 4.88529));
        }
      }
      if (altpot == altpotgood) { //This section controls which of the four LEDs are lit, to indicate which direction the telescope needs to be moved in.
        digitalWrite(ledup, HIGH);
        digitalWrite(leddown, HIGH);
      }
      else {
        if (altpot > altpotgood) {
          digitalWrite(ledup, HIGH);
          digitalWrite(leddown, LOW);
        }
        else if (altpot < altpotgood) {
          digitalWrite(ledup, LOW);
          digitalWrite(leddown, HIGH);
        }
      }
      if (azpot == azpotgood) {
        digitalWrite(ledright, HIGH);
        digitalWrite(ledleft, HIGH);
      }
      else {
        if (azpot > azpotgood) {
          digitalWrite(ledright, HIGH);
          digitalWrite(ledleft, LOW);
        }
        else if (azpot < azpotgood) {
          digitalWrite(ledright, LOW);
          digitalWrite(ledleft, HIGH);
        }
        if (azpot == azpotgood) {
          digitalWrite(ledright, HIGH);
          digitalWrite(ledleft, HIGH); 
        }
      }
    }
  }
  delay(250); //Wait 250ms and the repeat.
}
