// **********************************************************************************************************
// Moteino GPS Receiver & Telemetry
// Ver 12
// 2015-01-09 (C) luisr320@gmail.com
// **********************************************************************************************************
// It receives data from remote Moteino sensors and display data on an Nokia 5110 LCD
// **********************************************************************************************************
// Creative Commons Attrib Share-Alike License
// You are free to use/extend this code/library but please abide with the CCSA license:
// http://creativecommons.org/licenses/by-sa/3.0/
// **********************************************************************************************************

#include <Adafruit_GFX.h>//Required for the Nokia 5110 LCD display
#include <Adafruit_PCD8544.h>//Required for the Nokia 5110 LCD display
#include <RBD_Timer.h>  // https://github.com/alextaujenis/RBD_Timer
#include <RBD_Button.h> // https://github.com/alextaujenis/RBD_Button
#include <RFM69.h>
#include <SPI.h>
#include <SPIFlash.h>
#include <avr/wdt.h>
#include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming/tree/master/WirelessHEX69



#define NODEID 1 //unique for each node on same network
#define GROUNDSTATION 1
#define NETWORKID 200 //the same on all nodes that talk to each other
#define FREQUENCY RF69_433MHZ //match with the correct radio frequency
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
#define ACK_TIME 50 //max # of ms to wait for an ack
#define BUZZ A2 //Buzzer output pin
#define SERIAL_BAUD 115200 //To comunicate with serial monitor for debug
#define BUTPIN1 14 //Analog pin assigned to FIX button
#define BUTPIN2 15 //Analog pin assigned to MENUS SCROLL button
#define MPAGES 3 //Number of menu pages
#define FLASH_SS 8 // and FLASH SS on D8
#define VERSION "12.00B"

// Initiate the display instance - Software SPI (slower updates, more flexible pin options):
// pin 6 - Serial clock out (SCLK)
// pin 5 - Serial data out (DIN)
// pin 4 - Data/Command select (D/C)
// pin 3 - LCD chip select (CS)
// pin 1 - LCD reset (RST)
Adafruit_PCD8544 display = Adafruit_PCD8544(6, 5, 4, 3, 1);

//Define Hardware
/////////////////////////////////////////////////////////////////////////////
// flash(SPI_CS, MANUFACTURER_ID)
// SPI_CS          - CS pin attached to SPI flash chip (8 in case of Moteino)
// MANUFACTURER_ID - OPTIONAL, 0x1F44 for adesto(ex atmel) 4mbit flash
//                             0xEF30 for windbond 4mbit flash
//                             0xEF40 for windbond 16/64mbit flash
/////////////////////////////////////////////////////////////////////////////
SPIFlash flash(FLASH_SS, 0xEF30); //EF40 for 16mbit windbond chip
RFM69 radio;// Initialize the radio instance

// Initiate the button debouncer instance on the specified digital pin - input pullup enabled by default
RBD::Button button1(BUTPIN1); //FIX button
RBD::Button button2(BUTPIN2); //menu scroll button

// promiscuousmode must be off for wireless programming
bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network

// variables setup
unsigned long int newtimer; //for dataloss timeout calculation 
unsigned long int oldtimer; //for dataloss timeout calculation
unsigned long int newbuzztimer;
unsigned long int oldbuzztimer;
byte fixStatus; //variable that will hold the GPS FIX status - 0: no fix - 1: fix ok
byte menuPage; //hold the actual page number on the menu
float homelat; //variable that will hold the FIX latitude
float homelon; //variable that will hold the FIX longitude
float highspeed; //variable that will hold the highest speed achieved
long int alt; //current GPS altitude
long major1, minor1;
long major2, minor2;
long major3, minor3;
long major4, minor4;
long major5, minor5;
char botstr[30]; //to hold the formated azimute and distance

//Defining some constants
const float pi = 4.0 * atan(1.0); //Pi - 3.1415...
const float re = 6367e3; //mean Radius of Earth in meters
const float deg2rad = pi / 180.0; //degree to radians

// Define the data packet struct that will be received from the GPS transmitter
typedef struct
{
  float altitude;
  float airspeed;
  float latitude;
  float latitudedeg;
  float longitude;
  float longitudedeg;
  char latlon[2];
  int satellites;
  int fix;
}Payload;
Payload Data;

void setup()
{
  Serial.begin(SERIAL_BAUD);//Initialize the Serial port at the specified baud rate
  delay(10);//Wait for the Serial port to settle down

  oldtimer = millis();//Set Data link loss timeout timer to zero
  highspeed = 0;//Set the Maximum Achieved Speed memory to zero
  menuPage = 0;//Show menu page 1 at startup
  fixStatus = 0;//Reset FIX status
  oldbuzztimer = 0;//Reset buzzer timer

  pinMode(BUZZ, OUTPUT); //Make BUZZ pin an output

  //LCD Setup
  display.begin();
  display.setContrast(60); // you can change the contrast around to adapt the display for the best viewing!
  display.print(" Ver. "); display.println(VERSION);
  display.display(); // show splashscreen
  delay(2000);

  //Radio Setup
  radio.initialize(FREQUENCY,NODEID,NETWORKID); //Initialize the radio
  radio.setHighPower();// only for RFM69HW
  radio.encrypt(ENCRYPTKEY);//Turn the radio Encryption ON
  radio.promiscuous(promiscuousMode);//Set Promiscuous mode according to what's in the promiscuousMode variable
}


void loop()
{

  if (button1.onPressed())
  {
    Blink(BUZZ, 50);
    fixposition(); //run fixposition() function to reset all values to zero and memorize current position
  }
  else if (button2.onPressed())
  {
    Blink(BUZZ, 50);
    menuPage = menuPage + 1;
    if (menuPage > MPAGES)
    {
      menuPage = 1;;
    }
  }
  else if (radio.receiveDone())//If some packet was received by the radio, wait for all its contents to come trough
  {
    CheckForWirelessHEX(radio, flash, true); //If there is a new incomming Wireless program, load it now
    oldtimer = millis(); //For data link loss timeout calculation
    if (radio.TARGETID == GROUNDSTATION)//Check if the packet destination is the GROUNDSTN radio (NODE 1)
    {
      //Retrieve the data from the received Payload
      Data = *(Payload*)radio.DATA; //assume radio.DATA actually contains our struct and not something else 
     
      if (Data.fix == 0)//If there is no FIX
      {
        if (fixStatus == 1)//If it previously had a fix...
        {
          fixStatus = 0;//Change the fix status to 0 (No fix)
        }
         resetdisplay();
        display.println("Waiting for  GPS to aquireFIX...");
        display.println();
        display.print("[SAT:");display.print(Data.satellites); display.println("] ");
        display.print("[RX_RSSI:");display.print(radio.RSSI); display.print("] ");
        display.display();
        newbuzztimer = millis();
        if (newbuzztimer > (oldbuzztimer + 2000))
        {
          oldbuzztimer = newbuzztimer;
          Blink(BUZZ,5);
          delay(100);
          Blink(BUZZ,5);
        }  
      }
      if (Data.fix == 1)//If there is a FIX
      {
        while (menuPage == 0)
        {
          if (fixStatus == 0)
          {
            fixStatus = 1;
            Blink(BUZZ, 100);
            delay(50);
            Blink(BUZZ, 100);
            delay(50);          
            Blink(BUZZ, 100);
            delay(50);          
            Blink(BUZZ, 100);
            delay(50);          
            Blink(BUZZ, 100);
          }
           resetdisplay();
          display.println("GPS Has aquired a FIX.");
          display.println();
          display.println("Press FIX button to memorize position...");
          display.display();

          if (button1.onPressed())
          {
            Blink(BUZZ, 50);

            fixposition(); //run fixposition() function to reset all values to zero and memorize current position
            menuPage = 1;
          }
        }
        if (menuPage == 1)
        {
          if (fixStatus == 0)// if GPS lost fix and recovered it again
          {
            fixStatus = 1;
            Blink(BUZZ, 100);
            delay(50);
            Blink(BUZZ, 100);
            delay(50);          
            Blink(BUZZ, 100);
            delay(50);          
            Blink(BUZZ, 100);
            delay(50);          
            Blink(BUZZ, 100);
          }
           resetdisplay();

          GPSDist(homelat, homelon);//Run the distance calculating function
            
          display.print("ST:"); display.print(Data.satellites);
          display.print(" AL:"); display.print(Data.altitude - alt,0); display.println(" m");
          display.print("SP:"); display.print(Data.airspeed*1.852,0); display.println(" Km/h");
          display.println(botstr);
          if (Data.airspeed*1.852 < 1) Data.airspeed = 0;
          if (Data.airspeed*1.852 > highspeed)
          {
            highspeed = Data.airspeed*1.852;
          }
          display.setTextSize(2);
          display.print(highspeed,0); display.println(" KH");
          display.display();
        }
        if (menuPage == 2)
        {
           resetdisplay();
          display.println(" COORDINATES [DDDMIN.DEC:]"); 
          display.print("LAT:"); display.print(Data.latlon[0]); display.println(Data.latitude); 
          display.print("LON:"); display.print(Data.latlon[1]); display.println(Data.longitude);
          display.println("GOOGLE EARTH:"); 
          display.print("LAT:"); display.println(Data.latitudedeg, 8);
          display.print("LON:"); display.print(Data.longitudedeg, 8);
          display.display(); 
        }
        if (menuPage == 3)
        {
           resetdisplay();
          display.println("GOOGLE EARTH:"); 
          display.println();
          display.print("LAT:"); display.println(Data.latitudedeg, 8);
          display.print("LON:"); display.print(Data.longitudedeg, 8);
          display.display(); 
        }
      }
    }
  }
  else
  {
    newtimer = millis(); //start counting the time since LOS
    if (newtimer > (oldtimer + 5000))
    {
      resetdisplay();
      display.print("...");
      display.display(); 
      newbuzztimer = millis();
      if (newbuzztimer > (oldbuzztimer + 2000))
      {
        oldbuzztimer = newbuzztimer;
        Blink(BUZZ,5);
      }
    }
  }
}
//----------------------------------------------------------------------//
//        FUNCTIONS                                                     //
//----------------------------------------------------------------------//

void  resetdisplay()
{
      display.clearDisplay();
      display.setTextSize(1);
      display.setTextColor(BLACK);
      display.setCursor(0,0);
}

void Blink(byte PIN, int DELAY_MS)//The BUZZ Blinking function
{
  digitalWrite(PIN,HIGH);
  delay(DELAY_MS);
  digitalWrite(PIN,LOW);
}

float GPSDist(float uselat, float uselon)
{

  //
  // compute distance and azimuth to waypoint (is in uselat/uselon)
  //
  
  float xh, yh, zh, xt, yt, zt;
  float lat = uselat * deg2rad;
  float lon = uselon * deg2rad;
  xh = cos(lon) * cos(lat);
  yh = sin(lon) * cos(lat);
  zh = sin(lat);
  d2i((float(Data.latitude) / 100.0), 100000.0, major1, minor1);
  d2i((float(Data.longitude) / 100.0), 100000.0, major2, minor2);
  float triplon = float(major2) + (float(min2dec(minor2)) / 100000.0);
  float triplat = float(major1) + (float(min2dec(minor1)) / 100000.0);
  if (Data.latlon[0] == 'S') triplat = -triplat;
  if (Data.latlon[1] == 'W') triplon = -triplon;
  lat = triplat * deg2rad;
  lon = triplon * deg2rad;
  xt = cos(lon) * cos(lat);
  yt = sin(lon) * cos(lat);
  zt = sin(lat);
  float distance = re * re * ((xh - xt) * (xh - xt) + (yh - yt) * (yh - yt) + (zh - zt) * (zh - zt));
  distance = sqrt(distance);
  float Dvar = distance / 2.0;
  float Evar = sqrt((re * re) - (Dvar * Dvar));
  float angle = atan(Dvar / Evar);
  distance = angle * re * 2.0; // (the 2.0 is because we computed the half distance)
  long azimaj, azimin; long dismaj, dismin;
  azimaj = 0;
  dismaj = 0; dismin = 0;

  // for distances <= 10 meter do not display the azimuth
  int kmflag = 0;
  if (fixStatus == 1) 
  {
    if (distance >= 8.0) 
    {
      float xn = yh * zt - zh * yt;
      float yn = zh * xt - xh * zt;
      float zn = xh * yt - yh * xt;
      float rn = sqrt( xn * xn + yn * yn + zn * zn);
      xn = xn / rn;
      yn = yn / rn;
      zn = zn / rn;
      float xm = -yh;
      float ym = +xh;
      float zm = 0.0;
      float rm = sqrt( xm * xm + ym * ym + zm * zm );
      xm = xm / rm; ym = ym / rm; zm = zm / rm;
      float azimuth = xm * xn + ym * yn + zm * zn;
      if (azimuth > 1.0) azimuth = 1.0;
      if (azimuth < -1.0) azimuth = -1.0;
      azimuth = acos( azimuth ) / deg2rad;
      if (triplon < uselon) azimuth = 360.0 - azimuth;  // bug fixed (originally homelon)
      azimuth = 360.0 - azimuth;
      if (azimuth < 0) azimuth = 999;
      if (azimuth > 360) azimuth = 999;
      d2i(azimuth, 1.0, azimaj, azimin);
    }
    if (distance >= 100000.0) 
    {
      distance = distance / 1000;
      kmflag = 1;
    }
    d2i(distance, 10.0, dismaj, dismin);
  }
  if (kmflag == 0) 
  {
    sprintf(botstr, "A%3ld D%ldm       ", azimaj, dismaj);
  } 
  else 
  {
    sprintf(botstr, "A%3ld D%ldkm       ", azimaj, dismaj);
  }
  return distance;
}


void d2i( float input, float factor, long &major, long &minor )
{
  long sign = 1; if (input < 0) sign = -1;
  input = abs(input);
  long leading = long(input);
  float remain = input - float(leading);
  remain = remain * factor;
  long remaining = long(remain);
  major = sign * leading;
  minor = remaining;
}

long min2dec( long minor )
{
  float minute = minor;
  minute = minute / 100.0;
  minute = minute / 60.0;
  minute = minute * 10000.0;
  return long(minute);
}

void fixposition()
{
  highspeed = 0;//Reset maximum speed memory
  homelat = Data.latitudedeg;//Memorize FIX latitude
  homelon = Data.longitudedeg;//Memorize FIX longitude
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.setCursor(0,0);
  display.println("FIX Position Memorized");
  display.println("LAT:"); display.println(homelat,8); display.println("LON:"); display.println(homelon,8);
  alt = Data.altitude;         
  display.display();
  delay (2000);
}
