DougalPlummer
Published © GPL3+

Mega Solar Tracker

Building on and up! Using the lessons learned from the first tracker computer, we combine a bit of everything in this code.

AdvancedWork in progress24,928

Things used in this project

Hardware components

Arduino Mega 2560
Arduino Mega 2560
×1
Positioning (GNSS, GPS, Glonass, Galileo, Beidou)
u-blox Positioning (GNSS, GPS, Glonass, Galileo, Beidou)
Using a NEO 6 or 7 . These are very cool and dead easy to use.
×1
DUAL H-Bridge motor drive
Shield fans have been great actually ... one of my favorite stores. Most of these items can be found there
×1
HC-05 Bluetooth Module
HC-05 Bluetooth Module
×1
DS3231 I2C RTC
×1
Resistor 1k ohm
Resistor 1k ohm
×1
Resistor 2.21k ohm
Resistor 2.21k ohm
×1
Capacitor 100 nF
Capacitor 100 nF
×1
LED Matrix I2C interface
Prolly better to use a maxim one as lots of digital I/O on mega. No point in having more things on the I2C bus than necessary
×1
10 DOF gyro / accelerometer board
×1
Buck Power Supply
×1
Arduino Ethernet Shield 2
Arduino Ethernet Shield 2
Ended up doing two version one with the shield and one with the ESP
×1
Cable Gland
×1
IP65 Enclosure
Mine cost under $10 - undoubtedly a second but still fine
×1
Perspex Sheet
Got this from a rubbish bin (i'm a bit of a magpie)
×1
ESP8266 ESP-12E
Espressif ESP8266 ESP-12E
Like this but with level sifters to make life easy...
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Hole Punch or Hole saw
Hand Drill

Story

Read more

Custom parts and enclosures

East West axis View

Frame and Piviot

Mounting Frame

North Elevation

Remember this was build by someone from the SOUTHERN hemisphere. All you upside down people need to swap things around

Mounting Post

Yeah this is the pointy end... important to get this bit right

PV panel mounting with respect to the frame

Schematics

Tracker Schematic (mud map)

Code

TRACKER_MEGA_TOP_GPS_PWM_SOFT.ino

C/C++
Tracker for Ethernet shield uses both GPS and NTP to retrieve time
#include <SFE_BMP180.h>
#include <avr/wdt.h>
#include <Wire.h>
#include <SPI.h>
#include <LSM303.h>       // modified ... fixed a couple of bugs
#include <LiquidCrystal_I2C.h>
#include <L3G.h>
#include "ds3231.h"
#include <TimeLib.h>
#include "ht16k33.h"
#include <ModbusRtu.h>   // Modified ... this no longer a stock lib - Slave supports register translation/mapping
#include <EEPROM.h>
#include <math.h>
#include <Ethernet.h>
#include <SD.h>
#include <EthernetUdp.h>
#include <TinyGPS.h>

#define ID   1

#define BUFF_MAX 32
#define PARK_EAST 1
#define PARK_WEST 2
#define PARK_NORTH 3
#define PARK_SOUTH 4
#define PARK_FLAT 5

#define MOTOR_DWELL 100

#define MAX_MODBUS_DATA  70

#define HT16K33_DSP_NOBLINK 0   // constants for the half arsed cheapo display
#define HT16K33_DSP_BLINK1HZ 4
#define HT16K33_DSP_BLINK2HZ 2
#define HT16K33_DSP_BLINK05HZ 6


const byte ENCODER_PINA = 2;  // encoder connects - interupt pin
const byte ENCODER_PINB = 3;  // non interupt pin
const byte ENCODER_PB = 8;

const byte RELAY_YZ_DIR = 8; // DIR 1    Y+ Y- East / West       Was the X+ N relay   BROWN
const byte RELAY_YZ_PWM = 7; // PWM 2    Speed East / West       Was the Y- E relay   ORANGE
const byte RELAY_XZ_PWM = 6; // PWM 2    Speed North / South     Was the Y+ W relay   YELLOW
const byte RELAY_XZ_DIR = 5; // DIR 1    X+ X-  North / South    Was the X- S relay   BLUE 


const int chipSelect = 4;

const byte UNUSED09 =  9;  // cycle timer 26 Hz  38ms period
const byte UNUSED10 = 10;
const byte UNUSED11 = 11;
const byte WATCHDOG = 12;

IPAddress ip(192,168,2,177) ;
IPAddress MyDNS(139,130,4,4) ;
unsigned int localPort = 8888;                         // local port to listen for UDP packets
EthernetServer server(80) ;
EthernetClient client ;

byte mac[] = { 0x44, 0x6f, 0x75, 0x67, 0x61, 0x6c };   // Dougal in ASCII
char timeServer[] = "au.pool.ntp.org";                 // time.nist.gov NTP server
const int NTP_PACKET_SIZE = 48;                        // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE];                    // buffer to hold incoming and outgoing packets


EthernetUDP Udp;  // A UDP instance to let us send and receive packets over UDP

static bool hasSD = false;
static bool hasNet = false;
static bool hasGyro = false;
static bool hasRTC = false;
static bool hasPres = false ;

L3G gyro;
LSM303 compass;
LiquidCrystal_I2C lcd(0x27);  // Set the LCD I2C address I modified the lib for default wiring

SFE_BMP180 pressure;
HT16K33 HT;

TinyGPS gps;

float ha ;
float sunX ;
float sunrise ;
float sunset ;
Modbus mb_slave(ID, 2, 0); // this is slave ID and RS-232 or USB-FTDI
time_t chiptime ;        
uint8_t rtc_status ;
//uint8_t time[8];
int motor_recycle = 0 ;
char recv[BUFF_MAX];
unsigned int recv_size = 0;
unsigned long prev_millis;
uint8_t u8state; //   machine state
uint8_t u8query; //   pointer to message query
char buff[BUFF_MAX];
char trackername[18] ;
unsigned long  gpschars ; 
float heading ;          // MODBUS MAP
struct ts t;             // 
struct ts td;            //   
struct ts tg;            // 
struct ts tc;            //
int iNightShutdown ;     //
time_t setchiptime ;     // 68    if set to non zero this will trigger a time set event
float zAng ;             // 66
float xMul = 1.0 ;       // 64
float yMul = 1.0 ;       // 62
float zMul = 1.0 ;       // 60
int iXYS = 0 ;           // 59
int iSave = 0 ;          // 58    
int iDoSave = 0 ;        // 57
int iGPSLock = 0  ;      // 56 
unsigned long  fixage ;  // 54
float xRoll = 0.0 ;      // 52
float yRoll = 0.0 ;      // 50
float zRoll = 0.0 ;      // 48
float gT ;               // 46  temp from sensor
float Pr ;               // 44  presure sensor
float alt ;              // 42  altitude from GPS
float T;                 // 40  temperature of board (if has RTC)
float xzTarget ;         // 38  target for angles
float yzTarget ;         // 36
float xzH ;              // 34 hyserisis zone
float yzH ;              // 32
float xzAng;             // 30 current angles
float yzAng;             // 28
float xzOffset;          // 26 offset xz
float yzOffset;          // 24 offset yz
float dyPark;            // 22 parking position
float dxPark;            // 20 
float xMinVal ;          // 18 Min and Max values  X - N/S
float xMaxVal ;          // 16 
float yMinVal ;          // 14  Y  -- E/W
float yMaxVal ;          // 12 
float latitude;          // 10
float longitude;         // 8
int timezone;            // 7
int iDayNight ;          // 6
float solar_az_deg;      // 4
float solar_el_deg;      // 2
int iTrackMode ;         // 1 
int iMode ;              // 0

int iPMode;
int iPWM_YZ ;
int iPWM_XZ ;
int iPowerUp = 0 ;

unsigned long tempus;
int8_t state1 = 0;
int8_t rtc_hour = 0;
int8_t rtc_min = 0 ;
int8_t rtc_sec = 0 ;

void LoadParamsFromEEPROM(bool bLoad){
  if ( bLoad ) {
    xzH = LoadFloatFromEEPROM(0,0.1,20.0,4.0);  // hysterisis NS
    yzH = LoadFloatFromEEPROM(1,0.1,20.0,4.0);  //    ""      EW
  
    dyPark = LoadFloatFromEEPROM(2,-70.0,50.0,0);  
    dxPark = LoadFloatFromEEPROM(3,-5.0,50.0,0.0);  
    
    xzOffset = LoadFloatFromEEPROM(4,-90.0,90.0,0);  // NS
    yzOffset = LoadFloatFromEEPROM(5,-90.0,90.0,0);  // EW
  
    xzTarget = LoadFloatFromEEPROM(6,-90.0,90.0,0);  // NS
    yzTarget = LoadFloatFromEEPROM(7,-90.0,90.0,0);  // EW
  
    xMinVal = LoadFloatFromEEPROM(8,-10.0,60.0,0.0);   // NS
    xMaxVal = LoadFloatFromEEPROM(9,-10.0,60.0,45);
  
    yMinVal = LoadFloatFromEEPROM(10,-70.0,50.0,-65);  // EW
    yMaxVal = LoadFloatFromEEPROM(11,-70.0,50.0,45);
  
    iTrackMode = LoadIntFromEEPROM(12,-1,4,0);
      
    latitude = LoadFloatFromEEPROM(13,-90.0,90.0,-34.051219);
    longitude = LoadFloatFromEEPROM(14,-180.0,180.0,142.013618);
    timezone = LoadIntFromEEPROM(15,0,23,10);  
    xMul = LoadFloatFromEEPROM(16,-10,10,1);  
    yMul = LoadFloatFromEEPROM(17,-10,10,1);  
    zMul = LoadFloatFromEEPROM(18,-10,10,1);  
    iXYS = LoadIntFromEEPROM(19,0,1,0);
    if ( xMul == 0.0 )  // zero is rubbish value so take 1.0 as the default
      xMul = 1.0 ;
    if ( yMul == 0.0 )
      yMul = 1.0 ;
    if ( zMul == 0.0 )
      zMul = 1.0 ;
    iNightShutdown = LoadIntFromEEPROM(20,0,1,1);
    EEPROM.get(0 + (30 * sizeof(float)) , trackername );
  }else{
    EEPROM.put( 0 , xzH );
    EEPROM.put(0 + (1 * sizeof(float)) , yzH );
    EEPROM.put(0 + (2 * sizeof(float)) , dyPark );
    EEPROM.put(0 + (3 * sizeof(float)) , dxPark );
    EEPROM.put(0 + (4 * sizeof(float)) , xzOffset );
    EEPROM.put(0 + (5 * sizeof(float)) , yzOffset );
    EEPROM.put(0 + (6 * sizeof(float)) , xzTarget );
    EEPROM.put(0 + (7 * sizeof(float)) , yzTarget );
    EEPROM.put(0 + (8 * sizeof(float)) , xMinVal );
    EEPROM.put(0 + (9 * sizeof(float)) , xMaxVal );
    EEPROM.put(0 + (10 * sizeof(float)) , yMinVal );
    EEPROM.put(0 + (11 * sizeof(float)) , yMaxVal );
    EEPROM.put(0 + (12 * sizeof(float)) , iTrackMode );
    EEPROM.put(0 + (13 * sizeof(float)) , latitude );
    EEPROM.put(0 + (14 * sizeof(float)) , longitude );
    EEPROM.put(0 + (15 * sizeof(float)) , timezone );  
    EEPROM.put(0 + (16 * sizeof(float)) , xMul );
    EEPROM.put(0 + (17 * sizeof(float)) , yMul );
    EEPROM.put(0 + (18 * sizeof(float)) , zMul );
    EEPROM.put(0 + (19 * sizeof(float)) , iXYS );  
    EEPROM.put(0 + (20 * sizeof(float)) , iNightShutdown );  
    EEPROM.put(0 + (30 * sizeof(float)) , trackername);
  }
}

void setup() {
  int led ;
  
  Wire.begin();
  lcd.begin(20, 4);
  lcd.home();
  lcd.setBacklightPin(3, NEGATIVE);
  lcd.noCursor(); 
  lcd.clear();

  MCUSR &= ~_BV(WDRF);
  wdt_disable();
  
  compass.init();
  compass.enableDefault();
  compass.setTimeout(1000);

  Serial.begin(115200);   // program/debug port
  Serial1.begin(9600);    // GPS port
//  Serial2.begin(115200);  // Modbus Port to esp
  Serial3.begin(115200);  // EPS8266 serial converter
  
  if (gyro.init()) {
    gyro.enableDefault();
    hasGyro = true ;
  }
  if (pressure.begin()){
    Serial.println("BMP180 init success");
    hasPres = true ;
  }      
  pinMode(RELAY_XZ_DIR, OUTPUT); // Outputs for PWM motor control
  pinMode(RELAY_XZ_PWM, OUTPUT); // 
  pinMode(RELAY_YZ_PWM, OUTPUT); //
  pinMode(RELAY_YZ_DIR, OUTPUT); // 
  iPWM_YZ = 0 ;
  iPWM_XZ = 0 ;
  pinMode(13, OUTPUT); //
  digitalWrite(13, HIGH );
  ActivateRelays(0); // call an all stop first

  mb_slave.begin( 9600 ); // RS-232 to base of tower
  tempus = millis() + 100;

  pinMode(UNUSED09, OUTPUT);  // unused so dont leave floating set as output
  pinMode(UNUSED10, OUTPUT);  // 
  pinMode(UNUSED11, OUTPUT);  // 
  pinMode(WATCHDOG, OUTPUT);  // 
  digitalWrite(WATCHDOG,HIGH);

  compass.m_min = (LSM303::vector<int16_t>) {-3848, -1822, -1551 };   // calibration figures are empirical
  compass.m_max = (LSM303::vector<int16_t>) { +3353, +5127, +5300};

  LoadParamsFromEEPROM(true);
 
  DS3231_init(DS3231_INTCN); // look for a rtc
  DS3231_get(&tc);
  DS3231_get(&td);
  rtc_status = DS3231_get_sreg();
  if ((tc.mon == 0 )&& (tc.mday==0)){  // no rtc to load off
    setTime (0,0,0,21,9,2017) ; // midnight on the equinox (will deactivae motors till gets a valid time) ;  
  }else{
    setTime((int)tc.hour,(int)tc.min,(int)tc.sec,(int)tc.mday,(int)tc.mon,(int)tc.year ) ; // set the internal RTC
    hasRTC = true ;
  }

  HT.begin(0x00);
  for (led = 0; led < 127; led++) {
    HT.clearLedNow(led);
  }

  Serial.println("EtherNet init");
  if (SD.begin(chipSelect) || true){
    hasSD = true;
    Serial.println("SD OK");
    Ethernet.begin(mac,ip,MyDNS);
//    Ethernet.begin(mac,ip);
    if (Udp.begin(localPort)==1){
      hasNet = true ;
    }
    server.begin();
  }
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());


//  wdt_enable(WDTO_8S);
}

// Arduino doesnt have these to we define from a sandard libruary
float arcsin(float x) {
  return (atan(x / sqrt(-x * x + 1)));
}
float arccos(float x) {
  return (atan(x / sqrt(-x * x + 1)) + (2 * atan(1)));
}
// fractional orbital rotation in radians
float gama(struct ts *tm) {
  return ((2 * PI / 365 ) *  DayOfYear(tm->year , tm->mon , tm->mday , tm->hour , tm->min ));
}
// equation of rime
float eqTime(float g) {
  return (229.18 * ( 0.000075 + ( 0.001868 * cos(g)) - (0.032077 * sin(g)) - (0.014615 * cos (2 * g)) - (0.040849 * sin(2 * g))));
}
// declination of sun in radians
float Decl(float g) {
  return ( 0.006918 - (0.399912 * cos(g)) + (0.070257 * sin(g)) - (0.006758 * cos(2 * g)) + ( 0.000907 * sin(2 * g)) - ( 0.002697 * cos(3 * g)) + (0.00148 * sin(3 * g)) );
}
float TimeOffset(float longitude , struct ts *tm ,  int timezone ) {
  float dTmp ;
  dTmp = (-4.0 * longitude ) + (60 * timezone) - eqTime(gama(tm)) ;
  return (dTmp);
}

float TrueSolarTime(float longitude , struct ts *tm ,  int timezone ) {
  float dTmp ;
  dTmp = ( 60.0 * tm->hour ) + (1.0 * tm->min) + (1.0 * tm->sec / 60) - TimeOffset(longitude, tm, timezone) ;
  return (dTmp);
}
float HourAngle(float longitude , struct  ts *tm ,  int timezone) {
  float dTmp;
  dTmp = (TrueSolarTime(longitude, tm, timezone) / 4 ) - 180 ; // 720 minutes is solar noon -- div 4 is 180
  return (dTmp);
}
// Hour angle for sunrise and sunset only
float HA (float lat , struct ts *tm ) {
  float latRad ;
  latRad = lat * 2 * PI / 360 ;
  return ( acos((cos(90.833 * PI / 180 ) / ( cos(latRad) * cos(Decl(gama(tm)))) - (tan(latRad) * tan(Decl(gama(tm)))))) / PI * 180  );
}

float Sunrise(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude + HA(lat, tm))) + (60 * timezone) - eqTime(gama(tm))  ) ;
}
float Sunset(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude - HA(lat, tm))) + (60 * timezone) - eqTime(gama(tm))  ) ;
}
float SNoon(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude  + (60 * timezone) - eqTime(gama(tm))))  ) ;
}

float SolarZenithRad(float longitude , float lat , struct ts *tm , int timezone) {
  float latRad ;
  float decRad ;
  float HourAngleRad ;
  float dTmp ;

  latRad = lat * 2 * PI / 360 ;
  decRad = Decl(gama(tm));
  HourAngleRad = HourAngle (longitude , tm , timezone ) * PI / 180 ;
  dTmp = acos((sin(latRad) * sin(decRad)) + (cos(latRad) * cos(decRad) * cos(HourAngleRad)));
  return (dTmp) ;

}
float SolarElevationRad(float longitude , float lat , struct ts *tm ,  int timezone ) {
  return ((PI / 2) - SolarZenithRad(longitude , lat , tm , timezone )) ;
}

float SolarAzimouthRad(float longitude , float lat , struct ts *tm ,  int timezone) {
  float latRad ;
  float decRad ;
  float solarzenRad ;
  float HourAngleRad ;
  float dTmp ;
  latRad = lat * 2 * PI / 360 ;
  decRad = Decl(gama(tm));
  solarzenRad = SolarZenithRad ( longitude , lat , tm , timezone ) ;
  HourAngleRad = HourAngle (longitude , tm , timezone ) * PI / 180 ;
  dTmp = acos(((sin(decRad) * cos(latRad)) - (cos(HourAngleRad) * cos(decRad) * sin(latRad))) / sin(solarzenRad)) ;
  if ( HourAngleRad < 0 ) {
    return (dTmp) ;
  } else {
    return ((2 * PI) - dTmp) ;
  }
}


void StopYZ(){
  iPWM_YZ=0 ;
  motor_recycle = MOTOR_DWELL ;
}
void StopXZ(){
  iPWM_XZ=0 ;
  motor_recycle = MOTOR_DWELL ;
}

void ActivateRelays(int iAllStop) {
  if (motor_recycle > 0 ){
    motor_recycle-- ;
  }
  if ( iAllStop == 0 ) {
    StopYZ() ;
    StopXZ() ;
  } else {
    if (( iPWM_YZ==0 ) && (motor_recycle == 0 )){
      if (((yzAng  ) < ( yzTarget - yzH )) ) {   // do Y ie E/W before N/S
        digitalWrite(RELAY_YZ_DIR, LOW) ;
        iPWM_YZ=2 ;
      }
      if (((yzAng ) > (yzTarget + yzH )) ) {
        digitalWrite(RELAY_YZ_DIR, HIGH) ;
        iPWM_YZ=2 ;
      }
    }
    if ( iPWM_YZ>0 ){
      if ((yzAng > yzTarget) && ( digitalRead(RELAY_YZ_DIR)==LOW )) {
        StopYZ() ;
      }
      if ((yzAng < yzTarget) && ( digitalRead(RELAY_YZ_DIR)==HIGH )) {
        StopYZ() ;
      }
    }

    if (( iPWM_YZ==0)) {  // if finished on E/W you can do N/S
        if (( iPWM_XZ==0 ) && (motor_recycle == 0 )){
          if ((xzAng < ( xzTarget - xzH ))  )  { // turn on if not in tolerance
            digitalWrite(RELAY_XZ_DIR, LOW) ;
            iPWM_XZ=2 ;
          }
          if ((xzAng > ( xzTarget + xzH )) ) { // turn on if not in tolerance
            digitalWrite(RELAY_XZ_DIR, HIGH) ;
            iPWM_XZ=2 ;
          }
        }
    }else{
      if ((iPWM_XZ>0 )){
        StopXZ() ;
      }
    }
    if ( iPWM_XZ>0 ){
      if ((xzAng > xzTarget ) && ( digitalRead(RELAY_XZ_DIR)==LOW ))  { // if on turn off
        StopXZ() ;
      }
      if ((xzAng < xzTarget ) && ( digitalRead(RELAY_XZ_DIR)==HIGH ))  { // if on turn off
        StopXZ() ;
      }
    }
  }
  if (iPWM_XZ>0){
    iPWM_XZ += 3 ;
  }
  if (iPWM_YZ>0){
    iPWM_YZ += 3 ;
  }
  iPWM_XZ = constrain(iPWM_XZ,0,254);  
  iPWM_YZ = constrain(iPWM_YZ,0,254);  
  analogWrite(RELAY_XZ_PWM,iPWM_XZ);
  analogWrite(RELAY_YZ_PWM,iPWM_YZ);
}

void FloatToModbusWords(float src_value , uint16_t * dest_lo , uint16_t * dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  *tf = src_value ;
  *dest_lo = tempdata[1] ;
  *dest_hi = tempdata[0] ;
}
float FloatFromModbusWords( uint16_t dest_lo , uint16_t dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  tempdata[1] = dest_lo ;
  tempdata[0] = dest_hi  ;
  return (*tf) ;
}


float LoadFloatFromEEPROM(int address,float minval,float maxval, float defaultval){
float tmp ;  
  EEPROM.get(0 + (address * sizeof(float)) , tmp );
  if (( tmp < minval ) || ( tmp > maxval )|| (NumberOK(tmp) == 1)) {
    tmp = defaultval ;
    EEPROM.put(0 + (address * sizeof(float)) , tmp );
  }
  return(tmp);  
}
int LoadIntFromEEPROM(int address,int minval,int maxval, int defaultval){
int tmp ;  
  EEPROM.get(0 + (address * sizeof(float)) , tmp );  // float.. yeah yeah I know... but it makes it compatible with the one above (easy on the brain)
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    EEPROM.put(0 + (address * sizeof(float)) , tmp );
  }
  return(tmp);  
}

int NumberOK (float target) {
  int tmp = 0 ;
  tmp = isnan(target);
  if ( tmp != 1 ) {
    tmp = isinf(target);
  }
  return (tmp);
}

int SetTimeFromGPS(){
  byte hundredths ;

  gps.crack_datetime((int *)&tg.year,(byte *)&tg.mon,(byte *) &tg.mday,(byte *) &tg.hour,(byte *) &tg.min,(byte *) &tg.sec , &hundredths, &fixage);
  setTime((int)tg.hour,(int)tg.min,(int)tg.sec,(int)tg.mday,(int)tg.mon,(int)tg.year ) ; // set the internal RTC from last GPS time  
  chiptime = now() ;                          // get it back again
  chiptime += (( timezone * SECS_PER_HOUR ) + ( fixage / 1000 )) ; // add the offset plus the fix age
  setTime(chiptime);                         // set it again  
   if (hasRTC) {
      tg.year = year();
      tg.mon = month() ;
      tg.mday = day();
      tg.hour = hour() ;
      tg.min = minute();
      tg.sec = second();
      DS3231_set(tg);                          //should also update this
    }
  return(0);
}


void loop() {
  float P;
  float sunInc;
  float sunAng;
  float xzRatio;
  float yzRatio;
  float decl ;
  float eqtime ;
  float dTmp ;
  float heading ;
  float tst ;
  float flat, flon;
  unsigned short goodsent;
  unsigned short failcs;
//  int iYear , iMon , iMday , iHour , iMin , iSec ;


  if (minute() != rtc_min) { // do onlyonce a minute
    gps.stats(&gpschars, &goodsent , &failcs );
    gps.f_get_position(&flat, &flon,(long unsigned *) &fixage); // return in degrees
    if (hasPres){
      Pr = getPressure((double *)&gT) ;
    }
    if (hasRTC) {
      T = DS3231_get_treg();
    }
    rtc_min = minute() ;
    
    if ((fixage > 0 ) && ( fixage < 40000 )) {   // wait till our fix is valid before we use the values
      latitude = flat ;
      longitude = flon ;
      iGPSLock = gps.satellites() ;
      alt = gps.f_altitude() ;
      if (iPowerUp==0) {   // only do this at startup so we have a better position ref for next time
          EEPROM.put(0 + (13 * sizeof(float)) , latitude );
          EEPROM.put(0 + (14 * sizeof(float)) , longitude );
          iPowerUp = 1 ;
          if (!hasNet ){
            SetTimeFromGPS();
          }
      }
    }else{
      iGPSLock = 0 ;   // if no lock loook at internal clock
    }
  }
  tc.year = year();
  tc.mon = month() ;
  tc.mday = day();
  tc.hour = hour() ;
  tc.min = minute();
  tc.sec = second();
  
  compass.read();  // this reads all 6 channels
  if ( hasGyro ){
    gyro.read();
    xRoll = gyro.g.x ;
    yRoll = gyro.g.y ;
    zRoll = gyro.g.z ;
  }
  
  digitalWrite(UNUSED09,!digitalRead(UNUSED09));   // toggle this output so I can measure the cycle time with a scope
  
  heading = compass.heading((LSM303::vector<int>) { 1, 0, 0 });
  
  if (( compass.a.z != 0) && (!compass.timeoutOccurred() ))  {
    zAng = (float)compass.a.z ;
    if (iXYS == 0 ){                                            // Proper Job make it configurable 
      xzRatio = (float)compass.a.x * xMul / abs(zAng) ;         // Normal
      yzRatio = (float)compass.a.y * yMul / abs(zAng) ;
    }else{
      xzRatio = (float)compass.a.y * xMul / abs(zAng) ;         // Swapped
      yzRatio = (float)compass.a.x * yMul / abs(zAng) ;      
    }
    xzAng = ((float)atan(xzRatio) / PI * 180 ) + xzOffset ;   // good old offsets or fudge factors
    yzAng = ((float)atan(yzRatio) / PI * 180 ) + yzOffset ;
//    digitalWrite(13, LOW);
  }else{                                                       // try restarting the compass/accelerometer modual - cos he gone walkabout...
    Wire.begin();   // reset the I2C
    compass.init();
    compass.enableDefault();
    compass.setTimeout(1000);                                           // BTW I fixed up the int / long issue in the time out function in the LM303 lib I was using
    compass.m_min = (LSM303::vector<int16_t>) {-3848, -1822, -1551 };   // calibration figures are empirical (just whirl it around a bit and records the min max !!)
    compass.m_max = (LSM303::vector<int16_t>) { +3353, +5127, +5300 };
/*    if (tc.sec % 2 == 0 ) {
      digitalWrite(13, HIGH);
    } else {
      digitalWrite(13, LOW);
    }*/
    HT.begin(0x00);
  }
  
  if (setchiptime > 0) {    //  update the arduino time from the modbus register then clear it... also set RTC if fitted
    setTime(setchiptime) ;
    if (hasRTC) {
      tc.year = year();
      tc.mon = month() ;
      tc.mday = day();
      tc.hour = hour() ;
      tc.min = minute();
      tc.sec = second();
      DS3231_set(tc);                          //should also update this
    }    
    setchiptime = 0 ;
  }
  
  if ( hour() != rtc_hour){  // update our time every hour if we can
    if (iGPSLock == 0){
      if( hasNet ){
        sendNTPpacket(timeServer); // send an NTP packet to a time server every hour
      }else{
        if (hasRTC) {
          DS3231_get(&td);
          setTime((int)td.hour,(int)td.min,(int)td.sec,(int)td.mday,(int)td.mon,(int)td.year ) ; // set the internal RTC from Dallas RTC
       }
      }
    }else{
      if ((fixage > 0 ) && ( fixage < 10000 )) {         //        if the lock is less than 10 second old
        SetTimeFromGPS();
      }
    }      
    rtc_hour = hour() ; 
  }

  if ( rtc_sec != second() )  {     //only update once a second
    wdt_reset();                 // reset internal watchdog - good puppy

    if (( tc.sec > 8 ) && ( tc.sec < 58 )) {  // dont calculate arround the minute when time is updating from NTP or GPS as might get a not so funny result
      digitalWrite(13,!digitalRead(13));
      solar_az_deg = SolarAzimouthRad(longitude, latitude, &tc, timezone) * 180 / PI ;
      solar_el_deg = SolarElevationRad(longitude, latitude, &tc, timezone) * 180 / PI ;
    
      decl = Decl(gama(&tc)) * 180 / PI ;
      ha = HourAngle (longitude , &tc , timezone )  ;
      sunrise = Sunrise(longitude, latitude, &tc, timezone) ;
      sunset = Sunset(longitude, latitude, &tc, timezone);
      tst = TrueSolarTime(longitude, &tc, timezone);
      sunX = abs(latitude) + decl ;
      if (solar_el_deg >= 0 ){           // day
        iDayNight = 1 ;
      }else{                             // night
        iDayNight = 0 ;
      }
    }
    switch (iTrackMode) {
      case 4: // both axis to park
        yzTarget = dyPark ;  // night park position  E/W
        xzTarget = dxPark ;  // night park position  N/S
        break ;
      case 3: // both axis off no tracking
        break ;
      case 2: // xz tracking  NS
        if ( iDayNight == 1 ) {
          xzTarget = sunX ; // need to map the coordinate system correctly
        } else {
          xzTarget = dxPark ;  // night park position
        }
        break;
      case 1:  // yz tracking   EW
        if (iDayNight == 1) {
          yzTarget = ha ;
        } else {
          yzTarget = dyPark ;  // night park position
        }
        break;
      case -1: // set target to tracking and park both at nigh
        if (iDayNight == 1) {
          yzTarget = ha ;
          xzTarget = sunX ; // need to map the coordinate system correctly
        } else {
          yzTarget = dyPark ;  // night park position  E/W
          xzTarget = dxPark ;  // night park position  N/S
        }
        break;
      default: // set target to tracking
        if (iDayNight == 1) {
          yzTarget = ha ;
          xzTarget = sunX ; // need to map the coordinate system correctly
        } else {
          yzTarget = dyPark ;  // night park position (dont park the other - leave till morning)
        }
        break;
    }
    xzTarget = constrain(xzTarget,xMinVal,xMaxVal);   // constain function... very cool - dont leave home without it !
    yzTarget = constrain(yzTarget,yMinVal,yMaxVal);

    lcd.setCursor ( 0, 0 );   // Diags in case there is an LCD display attached
    lcd.print("X/Z ");
    if ( xzAng > 0 ) {
      lcd.print("+");
    }
    lcd.print(xzAng);
    lcd.setCursor ( 10, 0 );
    lcd.print("Y/Z ");
    if ( yzAng > 0 ) {
      lcd.print("+");
    }
    lcd.print(yzAng);
    lcd.setCursor ( 0, 1 );
    lcd.print("TX ");
    if (( xzTarget) > 0 ) {
      lcd.print("+");
    }
    lcd.print(( xzTarget));
    lcd.setCursor ( 10, 1 );
    lcd.print("TY ");
    if ((  yzTarget) > 0 ) {
      lcd.print("+");
    }
    lcd.print((  yzTarget));
    lcd.setCursor ( 0, 2 );
    lcd.print("DX ");
    dTmp = ( xzAng - xzTarget) ;
    if (dTmp > 0 ) {
      lcd.print("+");
    }
    lcd.print(dTmp);
    lcd.setCursor ( 10, 2 );
    lcd.print("DY ");
    dTmp = ( yzAng - yzTarget) ;
    if (dTmp > 0 ) {
      lcd.print("+");
    }
    lcd.print(dTmp);

    lcd.setCursor ( 0, 3 );       // line 3
    snprintf(buff, BUFF_MAX, "%02d:%02d:%02d", tc.hour, tc.min, tc.sec);
    lcd.print(buff) ;

    lcd.setCursor ( 9, 3 );       // line 3
    lcd.print( "S") ;
    if ( iDayNight == 0 ) {
      lcd.print( "-") ;
    }else{
      if (( tc.sec % 2 ) == 0 ) {
        lcd.print( "<") ;        
      }else{
        lcd.print( ">") ;
      }
    }
    if ( gps.satellites() > 9 ){
      lcd.print( "-" );
    }else{
      lcd.print(gps.satellites()) ;
    }

    lcd.setCursor ( 13, 3 );       // line 3
    snprintf(buff, BUFF_MAX, "%04X", fixage);
    lcd.print(buff) ;
    
    lcd.setCursor ( 18, 3 );       // line 3
    if ( iPWM_YZ == 0 ) {
      lcd.print( " ") ;
    }else{
      if (( digitalRead(RELAY_YZ_DIR) == LOW )) {
        lcd.print( "W") ;
      }else{
        lcd.print( "E") ;            
      }
    }
    lcd.setCursor ( 19, 3 );       // line 3
    if ( iPWM_XZ == 0 ) {
      lcd.print( " ") ;
    }else{
      if (( digitalRead(RELAY_XZ_DIR) == LOW )) {
        lcd.print( "N") ;
      }else{
        lcd.print( "S") ;            
      }
    }    
    
    rtc_sec = second() ;
    DisplayMeatBall() ;
  }

  if ( iDoSave == 2 ) {  // save them Active via web or 
    LoadParamsFromEEPROM(false);
    iDoSave = 0 ;  // only do once
  }
  if ( iDoSave == 3 ) {  // load them
    LoadParamsFromEEPROM(true);
    iDoSave = 0 ;  // only do once
  }

  if (((tc.hour > 19 ) || ( tc.hour < 5 )) && (iTrackMode < 3)) {
    if ( iNightShutdown != 0 ){
      ActivateRelays(1) ;        
    }else{
      ActivateRelays(0) ;  // power down at night if in tracking mode
    }
  }else{
    ActivateRelays(1) ;    
  }


  state1 = mb_slave.poll( (uint16_t*)&iMode, MAX_MODBUS_DATA );

  switch (state1) {
    case EXC_ADDR_RANGE:
      Serial.println("EXC_ADDR_RANGE PORT 2");
    break;
    case EXC_FUNC_CODE:
      Serial.println("EXC_FUNC_CODE PORT 2");
    break;
    case EXC_REGS_QUANT:
      Serial.println("EXC_REGS_QUANT PORT 2");
    break;
  }
  
  if ( hasNet ) {
    if ( Udp.parsePacket() ) { // check if NTP packet arrived back
      processNTPpacket();
    }
    
    client = server.available();  // process web server
    if (client) {
      processTCPClient(client);
    }  
  }
  
  while (Serial1.available()){ // process the gps buffer
    gps.encode(Serial1.read());
  }
}  //   end of loop 






float DayOfYear(uint16_t iYear , uint8_t iMon , uint8_t iDay , uint8_t iHour , uint8_t iMin ) {
  int i ;
  float iTDay ;

  iTDay = iDay - 1 ;  // this is zero referenced
  for ( i = 1 ; i < iMon ; i++ ) {
    switch (i) {
      case 1:
      case 3:
      case 5:
      case 7:
      case 8:
      case 10:
      case 12:
        iTDay += 31 ;
        break;
      case 4:
      case 6:
      case 9:
      case 11:
        iTDay += 30 ;
        break;
      case 2 :
        if ((iYear % 4) == 0 ) {
          iTDay += 29 ;
        } else {
          iTDay += 28 ;
        }
        break;
    }
  }
  iTDay += (( 1.0 * iHour - 12 ) / 24 ) ;
  //  iDay +=  1.0 * iMin  / 1440 ;
  return (iTDay);
}


int HrsSolarTime(float target) {
  int i ;
  i = target ;
  return (  i / 60 );
}
int MinSolarTime(float target) {
  int i ;
  i = target  ;
  return (   i % 60 );
}

float sign(float target) {
  if (target > 0 ) {
    return (1);
  } else {
    if (target < 0 ) {
      return (-1);
    } else {
      return (0);
    }
  }
}

void DisplayMeatBall() {
  int  pos , led , x , y;
  float dx , dy ;
  float dxa , dya ;

  HT.setBrightness(15);

  dx = xzAng - xzTarget ;
  dy = yzAng - yzTarget ;
  dxa = abs(dx) ;
  dya = abs(dy) ;
  if (dxa < 6) {
    x = 0 ;
  } else {
    if (dxa < 12) {
      x = sign(dx);
    } else {
      if (dxa < 50) {
        x = 2 * sign(dx);
      } else {
        x = 3 * sign(dx);
      }
    }
  }
  if (dya < 6) {
    y = 0 ;
  } else {
    if (dya < 12) {
      y = sign(dy);
    } else {
      if (dya < 25) {
        y = 2 * sign(dy);
      } else {
        y = 3 * sign(dy);
      }
    }
  }
  pos = 27 ; // netral position
  pos += (y * 8) ; // add or sumtract the x in range of -3 to +3
  pos += (x ) ; // add or sumtract 8 * y or y in range of -3 to +3
  for (led = 0; led < 63; led++) {
    switch (led){
       case 0:
       case 7:
       case 56:
       case 63:
       break;
       default:
         HT.clearLedNow(MapLedNo(led));
       break;  
    }
  }

  if ( ++iPMode > 100 ) {
    iPMode = 1 ;
  }
  switch(gps.satellites()){
    case 255:
      HT.clearLedNow(MapLedNo(0)); // turn off four courners
      HT.clearLedNow(MapLedNo(7)); // turn off four courners
      HT.clearLedNow(MapLedNo(63)); // turn off four courners
      HT.clearLedNow(MapLedNo(56)); // turn off four courners
    break;
    case 1:
    case 2:
      HT.setLedNow(MapLedNo(0)); // turn on four courners
      HT.clearLedNow(MapLedNo(7)); // turn on four courners
      HT.clearLedNow(MapLedNo(63)); // turn on four courners
      HT.clearLedNow(MapLedNo(56)); // turn on four courners
    break;
    case 3:
      HT.setLedNow(MapLedNo(0)); // turn on four courners
      HT.setLedNow(MapLedNo(7));
      HT.clearLedNow(MapLedNo(63)); // turn on four courners
      HT.clearLedNow(MapLedNo(56)); // turn on four courners
...

This file has been truncated, please download it to see its full contents.

TRACKER_MEGA_TOP_GPS_PWM_SOFT_ESP.ino

C/C++
This is the ESP version .. ie shand Alone Soft AP no NTP requires GPS or RTC
#include <SFE_BMP180.h>
#include <avr/wdt.h>
#include <Wire.h>
#include <SPI.h>
#include <LSM303.h>       // modified ... fixed a couple of bugs
#include <LiquidCrystal_I2C.h>
#include <L3G.h>
#include "ds3231.h"
#include <TimeLib.h>
#include "ht16k33.h"
#include <ModbusRtu.h>   // Modified ... this no longer a stock lib - Slave supports register translation/mapping
#include <EEPROM.h>
#include <math.h>
#include <Ethernet.h>
#include <SD.h>
#include <EthernetUdp.h>
#include <TinyGPS.h>
#include "WiFiEsp.h"

#define ID   1

#define BUFF_MAX 32
#define PARK_EAST 1
#define PARK_WEST 2
#define PARK_NORTH 3
#define PARK_SOUTH 4
#define PARK_FLAT 5

#define MOTOR_DWELL 100

#define MAX_MODBUS_DATA  70

#define HT16K33_DSP_NOBLINK 0   // constants for the half arsed cheapo display
#define HT16K33_DSP_BLINK1HZ 4
#define HT16K33_DSP_BLINK2HZ 2
#define HT16K33_DSP_BLINK05HZ 6


const byte SPARE1 = 2;  
const byte FACTORY_RESET = 3;  
const byte SPARE2 = 8;

const byte RELAY_YZ_DIR = 8; // DIR 1    Y+ Y- East / West       Was the X+ N relay   BROWN
const byte RELAY_YZ_PWM = 7; // PWM 2    Speed East / West       Was the Y- E relay   ORANGE
const byte RELAY_XZ_PWM = 6; // PWM 2    Speed North / South     Was the Y+ W relay   YELLOW
const byte RELAY_XZ_DIR = 5; // DIR 1    X+ X-  North / South    Was the X- S relay   BLUE 

const int chipSelect = 4;
 
const byte UNUSED09 =  9;  // cycle timer 26 Hz  38ms period
const byte UNUSED10 = 10;
const byte UNUSED11 = 11;
const byte WATCHDOG = 12;

IPAddress ip(192,168,42,1) ;
IPAddress CurrentIP ;

char ssid[24] = {"North_Tracker\0"};         // your network SSID (name)
char pass[16] = {"password\0" };             // your network password
int status = WL_IDLE_STATUS;     // the Wifi radio's status
int reqCount = 0;                // number of requests received

static bool hasSD = false;
static bool hasNet = false;
static bool hasGyro = false;
static bool hasRTC = false;
static bool hasPres = false ;

L3G gyro;
LSM303 compass;
LiquidCrystal_I2C lcd(0x27);  // Set the LCD I2C address I modified the lib for default wiring

SFE_BMP180 pressure;
HT16K33 HT;

TinyGPS gps;

WiFiEspServer server(80);
RingBuffer buf(8);

Modbus mb_slave(ID, 2, 0); // this is slave ID and RS-232 or USB-FTDI
time_t chiptime ;        
uint8_t rtc_status ;
uint8_t time[8];
int motor_recycle_x = 0 ;
int motor_recycle_y = 0 ;
char recv[BUFF_MAX];
unsigned int recv_size = 0;
unsigned long prev_millis;
uint8_t u8state; //   machine state
uint8_t u8query; //   pointer to message query
char buff[BUFF_MAX];
char trackername[18] ;
unsigned long  gpschars ; 
float heading ;          // MODBUS MAP
struct ts t;             // 
struct ts td;            //   
struct ts tg;            // 
struct ts tc;            //
float ha ;
float sunX ;
float sunrise ;
float sunset ;
int iNightShutdown ;     //
int iMultiDrive   ;      // 69    do the axis drives run together 
time_t setchiptime ;     // 68    if set to non zero this will trigger a time set event
float zAng ;             // 66
float xMul = 1.0 ;       // 64
float yMul = 1.0 ;       // 62
float zMul = 1.0 ;       // 60
int iXYS = 0 ;           // 59
int iSave = 0 ;          // 58    
int iDoSave = 0 ;        // 57
int iGPSLock = 0  ;      // 56 
unsigned long  fixage ;  // 54
float xRoll = 0.0 ;      // 52
float yRoll = 0.0 ;      // 50
float zRoll = 0.0 ;      // 48
float gT ;               // 46  temp from sensor
float Pr ;               // 44  presure sensor
float alt ;              // 42  altitude from GPS
float T;                 // 40  temperature of board (if has RTC)
float xzTarget ;         // 38  target for angles
float yzTarget ;         // 36
float xzH ;              // 34 hyserisis zone
float yzH ;              // 32
float xzAng;             // 30 current angles
float yzAng;             // 28
float xzOffset;          // 26 offset xz
float yzOffset;          // 24 offset yz
float dyPark;            // 22 parking position
float dxPark;            // 20 
float xMinVal ;          // 18 Min and Max values  X - N/S
float xMaxVal ;          // 16 
float yMinVal ;          // 14  Y  -- E/W
float yMaxVal ;          // 12 
float latitude;          // 10
float longitude;         // 8
int timezone;            // 7
int iDayNight ;          // 6
float solar_az_deg;      // 4
float solar_el_deg;      // 2
int iTrackMode ;         // 1 
int iMode ;              // 0

int iPMode;
int iPWM_YZ ;
int iPWM_XZ ;
int iPowerUp = 0 ;

unsigned long tempus;
int8_t state1 = 0;
int8_t rtc_hour = 0;
int8_t rtc_min = 0 ;
int8_t rtc_sec = 0 ;

void LoadParamsFromEEPROM(bool bLoad){
  if ( bLoad ) {
    xzH = LoadFloatFromEEPROM(0,0.1,20.0,4.0);  // hysterisis NS
    yzH = LoadFloatFromEEPROM(1,0.1,20.0,4.0);  //    ""      EW
  
    dyPark = LoadFloatFromEEPROM(2,-70.0,50.0,0);  
    dxPark = LoadFloatFromEEPROM(3,-5.0,50.0,0.0);  
    
    xzOffset = LoadFloatFromEEPROM(4,-90.0,90.0,0);  // NS
    yzOffset = LoadFloatFromEEPROM(5,-90.0,90.0,0);  // EW
  
    xzTarget = LoadFloatFromEEPROM(6,-90.0,90.0,0);  // NS
    yzTarget = LoadFloatFromEEPROM(7,-90.0,90.0,0);  // EW
  
    xMinVal = LoadFloatFromEEPROM(8,-10.0,60.0,0.0);   // NS
    xMaxVal = LoadFloatFromEEPROM(9,-10.0,60.0,45);
  
    yMinVal = LoadFloatFromEEPROM(10,-70.0,50.0,-65);  // EW
    yMaxVal = LoadFloatFromEEPROM(11,-70.0,50.0,45);
  
    iTrackMode = LoadIntFromEEPROM(12,-1,4,0);
      
    latitude = LoadFloatFromEEPROM(13,-90.0,90.0,-34.051219);
    longitude = LoadFloatFromEEPROM(14,-180.0,180.0,142.013618);
    timezone = LoadIntFromEEPROM(15,0,23,10);  
    xMul = LoadFloatFromEEPROM(16,-10,10,1);  
    yMul = LoadFloatFromEEPROM(17,-10,10,1);  
    zMul = LoadFloatFromEEPROM(18,-10,10,1);  
    iXYS = LoadIntFromEEPROM(19,0,1,0);
    if ( xMul == 0.0 )  // zero is rubbish value so take 1.0 as the default
      xMul = 1.0 ;
    if ( yMul == 0.0 )
      yMul = 1.0 ;
    if ( zMul == 0.0 )
      zMul = 1.0 ;
    iNightShutdown = LoadIntFromEEPROM(20,0,1,1);
    iMultiDrive = LoadIntFromEEPROM(21,0,1,0);
    if (digitalRead(FACTORY_RESET)== LOW) {
        ip= IPAddress(192,168,42,1);
        sprintf(trackername,"Most Excellent\0");
        sprintf(ssid , "Configure\0") ;
        sprintf(pass, "password\0");      
    }else{
      EEPROM.get(0 + (22 * sizeof(float)) , ip );
      if ((( ip[0] == 255 ) && ( ip[1] == 255 ))) {
        ip= IPAddress(192,168,42,1);
      }
      EEPROM.get(0 + (30 * sizeof(float)) , trackername );
      if ( String(trackername).length() < 2 ){
        sprintf(trackername,"Most Excellent\0");
      }
      EEPROM.get(0 + (35 * sizeof(float)) , ssid );
      if (( String(ssid).length() < 2 ) || ((ssid[0] == ssid[11]) && (ssid[14] == ssid[15]) ) ){
        sprintf(ssid , "Configure\0") ;
        sprintf(pass, "password\0");
      }else{
        EEPROM.get(0 + (42 * sizeof(float)) , pass );
      }
    }
  }else{
    EEPROM.put( 0 , xzH );
    EEPROM.put(0 + (1 * sizeof(float)) , yzH );
    EEPROM.put(0 + (2 * sizeof(float)) , dyPark );
    EEPROM.put(0 + (3 * sizeof(float)) , dxPark );
    EEPROM.put(0 + (4 * sizeof(float)) , xzOffset );
    EEPROM.put(0 + (5 * sizeof(float)) , yzOffset );
    EEPROM.put(0 + (6 * sizeof(float)) , xzTarget );
    EEPROM.put(0 + (7 * sizeof(float)) , yzTarget );
    EEPROM.put(0 + (8 * sizeof(float)) , xMinVal );
    EEPROM.put(0 + (9 * sizeof(float)) , xMaxVal );
    EEPROM.put(0 + (10 * sizeof(float)) , yMinVal );
    EEPROM.put(0 + (11 * sizeof(float)) , yMaxVal );
    EEPROM.put(0 + (12 * sizeof(float)) , iTrackMode );
    EEPROM.put(0 + (13 * sizeof(float)) , latitude );
    EEPROM.put(0 + (14 * sizeof(float)) , longitude );
    EEPROM.put(0 + (15 * sizeof(float)) , timezone );  
    EEPROM.put(0 + (16 * sizeof(float)) , xMul );
    EEPROM.put(0 + (17 * sizeof(float)) , yMul );
    EEPROM.put(0 + (18 * sizeof(float)) , zMul );
    EEPROM.put(0 + (19 * sizeof(float)) , iXYS );  
    EEPROM.put(0 + (20 * sizeof(float)) , iNightShutdown );  
    EEPROM.put(0 + (21 * sizeof(float)) , iMultiDrive );  
    EEPROM.put(0 + (22 * sizeof(float)) , ip );  
    EEPROM.put(0 + (30 * sizeof(float)) , trackername);
    EEPROM.put(0 + (35 * sizeof(float)) , ssid);
    EEPROM.put(0 + (42 * sizeof(float)) , pass);
  }
}

void setup() {
  int led ;
  
  Wire.begin();
  lcd.begin(20, 4);
  lcd.home();
  lcd.setBacklightPin(3, NEGATIVE);
  lcd.noCursor(); 
  lcd.clear();

  MCUSR &= ~_BV(WDRF);
  wdt_disable();
  
  compass.init();
  compass.enableDefault();
  compass.setTimeout(1000);

  Serial.begin(115200);   // program/debug port
  Serial1.begin(9600);    // GPS port
//  Serial2.begin(9600);  // Modbus Port
  Serial3.begin(115200);  // EPS8266 serial converter
  
  if (gyro.init()) {
    gyro.enableDefault();
    hasGyro = true ;
  }
  if (pressure.begin()){
    Serial.println("BMP180 init success");
    hasPres = true ;
  }      
  pinMode(FACTORY_RESET,INPUT_PULLUP);
  pinMode(RELAY_XZ_DIR, OUTPUT); // Outputs for PWM motor control
  pinMode(RELAY_XZ_PWM, OUTPUT); // 
  pinMode(RELAY_YZ_PWM, OUTPUT); //
  pinMode(RELAY_YZ_DIR, OUTPUT); // 
  iPWM_YZ = 0 ;
  iPWM_XZ = 0 ;
  pinMode(13, OUTPUT); //
  digitalWrite(13, HIGH );
  ActivateRelays(0); // call an all stop first

  mb_slave.begin( 9600 ); // RS-232 to base of tower
  tempus = millis() + 100;

  pinMode(UNUSED09, OUTPUT);  // unused so dont leave floating set as output
  pinMode(UNUSED10, OUTPUT);  // 
  pinMode(UNUSED11, OUTPUT);  // 
  pinMode(WATCHDOG, OUTPUT);  // 
  digitalWrite(WATCHDOG,HIGH);

  compass.m_min = (LSM303::vector<int16_t>) {-3848, -1822, -1551 };   // calibration figures are empirical
  compass.m_max = (LSM303::vector<int16_t>) { +3353, +5127, +5300};

  LoadParamsFromEEPROM(true);
 
  DS3231_init(DS3231_INTCN); // look for a rtc
  DS3231_get(&tc);
  DS3231_get(&td);
  rtc_status = DS3231_get_sreg();
  if ((tc.mon == 0 )&& (tc.mday==0)){  // no rtc to load off
    setTime (0,0,0,21,9,2017) ; // midnight on the equinox (will deactivae motors till gets a valid time) ;  
  }else{
    setTime((int)tc.hour,(int)tc.min,(int)tc.sec,(int)tc.mday,(int)tc.mon,(int)tc.year ) ; // set the internal RTC
    hasRTC = true ;
  }

  HT.begin(0x00);
  for (led = 0; led < 127; led++) {
    HT.clearLedNow(led);
  }

  Serial3.begin(115200);    // initialize serial for ESP module
  WiFi.init(&Serial3);      // initialize ESP module

  // check for the presence of the shield
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
//    while (true); // don't continue
  }

  Serial.print("Attempting to start AP ");
  Serial.println(ssid);

  if (( ip[0] == 0 )&&( ip[1] == 0 )&&( ip[2] == 0 )&&( ip[3] == 0 ) && (digitalRead(FACTORY_RESET)== HIGH)){
    WiFi.begin((char*)ssid, (char*)pass) ; 
  }else{
    WiFi.configAP(ip);   // start access point   
    if ( String(pass).length() == 0 ) {
      status = WiFi.beginAP(ssid, 10);    
    }else{
      status = WiFi.beginAP(ssid, 10, pass, ENC_TYPE_WPA2_PSK);
    }
  }
  
  Serial.println("Access point started");
//  printWifiStatus();
    Serial.println("IP address: ");
    CurrentIP = WiFi.localIP() ;
    Serial.println(CurrentIP);
  
  // start the web server on port 80
  server.begin();
  Serial.println("Server started");

//  wdt_enable(WDTO_8S);
}

// Arduino doesnt have these to we define from a sandard libruary
float arcsin(float x) {
  return (atan(x / sqrt(-x * x + 1)));
}
float arccos(float x) {
  return (atan(x / sqrt(-x * x + 1)) + (2 * atan(1)));
}
// fractional orbital rotation in radians
float gama(struct ts *tm) {
  return ((2 * PI / 365 ) *  DayOfYear(tm->year , tm->mon , tm->mday , tm->hour , tm->min ));
}
// equation of rime
float eqTime(float g) {
  return (229.18 * ( 0.000075 + ( 0.001868 * cos(g)) - (0.032077 * sin(g)) - (0.014615 * cos (2 * g)) - (0.040849 * sin(2 * g))));
}
// declination of sun in radians
float Decl(float g) {
  return ( 0.006918 - (0.399912 * cos(g)) + (0.070257 * sin(g)) - (0.006758 * cos(2 * g)) + ( 0.000907 * sin(2 * g)) - ( 0.002697 * cos(3 * g)) + (0.00148 * sin(3 * g)) );
}
float TimeOffset(float longitude , struct ts *tm ,  int timezone ) {
  float dTmp ;
  dTmp = (-4.0 * longitude ) + (60 * timezone) - eqTime(gama(tm)) ;
  return (dTmp);
}

float TrueSolarTime(float longitude , struct ts *tm ,  int timezone ) {
  float dTmp ;
  dTmp = ( 60.0 * tm->hour ) + (1.0 * tm->min) + (1.0 * tm->sec / 60) - TimeOffset(longitude, tm, timezone) ;
  return (dTmp);
}
float HourAngle(float longitude , struct  ts *tm ,  int timezone) {
  float dTmp;
  dTmp = (TrueSolarTime(longitude, tm, timezone) / 4 ) - 180 ; // 720 minutes is solar noon -- div 4 is 180
  return (dTmp);
}
// Hour angle for sunrise and sunset only
float HA (float lat , struct ts *tm ) {
  float latRad ;
  latRad = lat * 2 * PI / 360 ;
  return ( acos((cos(90.833 * PI / 180 ) / ( cos(latRad) * cos(Decl(gama(tm)))) - (tan(latRad) * tan(Decl(gama(tm)))))) / PI * 180  );
}

float Sunrise(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude + HA(lat, tm))) + (60 * timezone) - eqTime(gama(tm))  ) ;
}
float Sunset(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude - HA(lat, tm))) + (60 * timezone) - eqTime(gama(tm))  ) ;
}
float SNoon(float longitude , float lat , struct ts *tm , int timezone) {
  return (720 - ( 4.0 * (longitude  + (60 * timezone) - eqTime(gama(tm))))  ) ;
}

float SolarZenithRad(float longitude , float lat , struct ts *tm , int timezone) {
  float latRad ;
  float decRad ;
  float HourAngleRad ;
  float dTmp ;

  latRad = lat * 2 * PI / 360 ;
  decRad = Decl(gama(tm));
  HourAngleRad = HourAngle (longitude , tm , timezone ) * PI / 180 ;
  dTmp = acos((sin(latRad) * sin(decRad)) + (cos(latRad) * cos(decRad) * cos(HourAngleRad)));
  return (dTmp) ;

}
float SolarElevationRad(float longitude , float lat , struct ts *tm ,  int timezone ) {
  return ((PI / 2) - SolarZenithRad(longitude , lat , tm , timezone )) ;
}

float SolarAzimouthRad(float longitude , float lat , struct ts *tm ,  int timezone) {
  float latRad ;
  float decRad ;
  float solarzenRad ;
  float HourAngleRad ;
  float dTmp ;
  latRad = lat * 2 * PI / 360 ;
  decRad = Decl(gama(tm));
  solarzenRad = SolarZenithRad ( longitude , lat , tm , timezone ) ;
  HourAngleRad = HourAngle (longitude , tm , timezone ) * PI / 180 ;
  dTmp = acos(((sin(decRad) * cos(latRad)) - (cos(HourAngleRad) * cos(decRad) * sin(latRad))) / sin(solarzenRad)) ;
  if ( HourAngleRad < 0 ) {
    return (dTmp) ;
  } else {
    return ((2 * PI) - dTmp) ;
  }
}


void StopYZ(){
  iPWM_YZ=0 ;
  motor_recycle_y = MOTOR_DWELL ;
}
void StopXZ(){
  iPWM_XZ=0 ;
  motor_recycle_x = MOTOR_DWELL ;
}

void ActivateRelays(int iAllStop) {
  if (motor_recycle_y > 0 ){
    motor_recycle_y-- ;
  }
  if (motor_recycle_x > 0 ){
    motor_recycle_x-- ;
  }
  if ( iAllStop == 0 ) {
    StopYZ() ;
    StopXZ() ;
  } else {
    if (( iPWM_YZ==0 ) && (motor_recycle_y == 0 )){
      if ((( yzAng  ) < ( yzTarget - yzH )) ) {   // do Y ie E/W before N/S
        digitalWrite(RELAY_YZ_DIR, LOW) ;
        iPWM_YZ=2 ;
      }
      if ((( yzAng ) > ( yzTarget + yzH )) ) {
        digitalWrite(RELAY_YZ_DIR, HIGH) ;
        iPWM_YZ=2 ;
      }
    }
    if ( iPWM_YZ>0 ){
      if ((yzAng > yzTarget) && ( digitalRead(RELAY_YZ_DIR)==LOW )) {
        StopYZ() ;
      }
      if ((yzAng < yzTarget) && ( digitalRead(RELAY_YZ_DIR)==HIGH )) {
        StopYZ() ;
      }
    }

    if (( iPWM_YZ==0) || ( iMultiDrive == 1 )) {  // if finished on E/W you can do N/S  or if we are doing multidrive
        if (( iPWM_XZ==0 ) && (motor_recycle_x == 0 )){
          if ((xzAng < ( xzTarget - xzH ))  )  { // turn on if not in tolerance
            digitalWrite(RELAY_XZ_DIR, LOW) ;
            iPWM_XZ=2 ;
          }
          if ((xzAng > ( xzTarget + xzH )) ) { // turn on if not in tolerance
            digitalWrite(RELAY_XZ_DIR, HIGH) ;
            iPWM_XZ=2 ;
          }
        }
    }else{
      if ((iPWM_XZ>0 )){
        StopXZ() ;
      }
    }
    if ( iPWM_XZ>0 ){
      if ((xzAng > xzTarget ) && ( digitalRead(RELAY_XZ_DIR)==LOW ))  { // if on turn off
        StopXZ() ;
      }
      if ((xzAng < xzTarget ) && ( digitalRead(RELAY_XZ_DIR)==HIGH ))  { // if on turn off
        StopXZ() ;
      }
    }
  }
  if (iPWM_XZ>0){
    iPWM_XZ += 2 ;
  }
  if (iPWM_YZ>0){
    iPWM_YZ += 2 ;
  }
  iPWM_XZ = constrain(iPWM_XZ,0,254);  
  iPWM_YZ = constrain(iPWM_YZ,0,254);  
  analogWrite(RELAY_XZ_PWM,iPWM_XZ);
  analogWrite(RELAY_YZ_PWM,iPWM_YZ);
}

void FloatToModbusWords(float src_value , uint16_t * dest_lo , uint16_t * dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  *tf = src_value ;
  *dest_lo = tempdata[1] ;
  *dest_hi = tempdata[0] ;
}
float FloatFromModbusWords( uint16_t dest_lo , uint16_t dest_hi ) {
  uint16_t tempdata[2] ;
  float *tf ;
  tf = (float * )&tempdata[0]  ;
  tempdata[1] = dest_lo ;
  tempdata[0] = dest_hi  ;
  return (*tf) ;
}


float LoadFloatFromEEPROM(int address,float minval,float maxval, float defaultval){
float tmp ;  
  EEPROM.get(0 + (address * sizeof(float)) , tmp );
  if (( tmp < minval ) || ( tmp > maxval )|| (NumberOK(tmp) == 1)) {
    tmp = defaultval ;
    EEPROM.put(0 + (address * sizeof(float)) , tmp );
  }
  return(tmp);  
}
int LoadIntFromEEPROM(int address,int minval,int maxval, int defaultval){
int tmp ;  
  EEPROM.get(0 + (address * sizeof(float)) , tmp );  // float.. yeah yeah I know... but it makes it compatible with the one above (easy on the brain)
  if (( tmp < minval ) || ( tmp > maxval )) {
    tmp = defaultval ;
    EEPROM.put(0 + (address * sizeof(float)) , tmp );
  }
  return(tmp);  
}

int NumberOK (float target) {
  int tmp = 0 ;
  tmp = isnan(target);
  if ( tmp != 1 ) {
    tmp = isinf(target);
  }
  return (tmp);
}

int SetTimeFromGPS(){
  byte hundredths ;
  tmElements_t tmegps;
  gps.crack_datetime((int *)&tg.year,(byte *)&tg.mon,(byte *) &tg.mday,(byte *) &tg.hour,(byte *) &tg.min,(byte *) &tg.sec , &hundredths, &fixage);
  tmegps.Year = tg.year - 1970 ;
  tmegps.Month = tg.mon ;
  tmegps.Day = tg.mday  ;
  tmegps.Hour = tg.hour ;
  tmegps.Minute = tg.min ;
  tmegps.Second = tg.sec ;
//  setTime((int)tg.hour,(int)tg.min,(int)tg.sec,(int)tg.mday,(int)tg.mon,(int)tg.year ) ; // set the internal RTC from last GPS time  
//  chiptime = now() ;                                                                    // get it back again
  chiptime = makeTime(tmegps);                                     // get the GPS as time_t
  chiptime += (( timezone * SECS_PER_HOUR ) + ( fixage / 1000 )) ; // add the offset plus the fix age
  setTime(chiptime);                         // set it again  
   if (hasRTC) {
      tg.year = year();
      tg.mon = month() ;
      tg.mday = day();
      tg.hour = hour() ;
      tg.min = minute();
      tg.sec = second();
      DS3231_set(tg);                          //should also update this
    }
  return(0);
}


void loop() {
  float P;
  float sunInc;
  float sunAng;
  float xzRatio;
  float yzRatio;
  float decl ;
  float eqtime ;
  float dTmp ;
  float heading ;
  float tst ;
  float flat, flon;
  unsigned short goodsent;
  unsigned short failcs;
  String request ;

//  int iYear , iMon , iMday , iHour , iMin , iSec ;


  if (minute() != rtc_min) { // do onlyonce a minute
    gps.stats(&gpschars, &goodsent , &failcs );
    gps.f_get_position(&flat, &flon,(long unsigned *) &fixage); // return in degrees
    if (hasPres){
      Pr = getPressure((double *)&gT) ;
    }
    if (hasRTC) {
      T = DS3231_get_treg();
    }
    rtc_min = minute() ;
    
    if ((fixage > 0 ) && ( fixage < 40000 )) {   // wait till our fix is valid before we use the values
      latitude = flat ;
      longitude = flon ;
      iGPSLock = gps.satellites() ;
      alt = gps.f_altitude() ;
      if (iPowerUp==0) {   // only do this at startup so we have a better position ref for next time
          EEPROM.put(0 + (13 * sizeof(float)) , latitude );
          EEPROM.put(0 + (14 * sizeof(float)) , longitude );
          iPowerUp = 1 ;
          if (!hasNet ){
            SetTimeFromGPS();
          }
      }
    }else{
      iGPSLock = 0 ;   // if no lock loook at internal clock
    }
  }
  tc.year = year();
  tc.mon = month() ;
  tc.mday = day();
  tc.hour = hour() ;
  tc.min = minute();
  tc.sec = second();
  
  compass.read();  // this reads all 6 channels
  if ( hasGyro ){
    gyro.read();
    xRoll = gyro.g.x ;
    yRoll = gyro.g.y ;
    zRoll = gyro.g.z ;
  }
  
  digitalWrite(UNUSED09,!digitalRead(UNUSED09));   // toggle this output so I can measure the cycle time with a scope
  
  heading = compass.heading((LSM303::vector<int>) { 1, 0, 0 });
  
  if (( compass.a.z != 0) && (!compass.timeoutOccurred() ))  {
    zAng = (float)compass.a.z ;
    if (iXYS == 0 ){                                            // Proper Job make it configurable 
      xzRatio = (float)compass.a.x * xMul / abs(zAng) ;         // Normal
      yzRatio = (float)compass.a.y * yMul / abs(zAng) ;
    }else{
      xzRatio = (float)compass.a.y * xMul / abs(zAng) ;         // Swapped
      yzRatio = (float)compass.a.x * yMul / abs(zAng) ;      
    }
    xzAng = ((float)atan(xzRatio) / PI * 180 ) + xzOffset ;   // good old offsets or fudge factors
    yzAng = ((float)atan(yzRatio) / PI * 180 ) + yzOffset ;
//    digitalWrite(13, LOW);
  }else{                                                       // try restarting the compass/accelerometer modual - cos he gone walkabout...
    Wire.begin();   // reset the I2C
    compass.init();
    compass.enableDefault();
    compass.setTimeout(1000);                                           // BTW I fixed up the int / long issue in the time out function in the LM303 lib I was using
    compass.m_min = (LSM303::vector<int16_t>) {-3848, -1822, -1551 };   // calibration figures are empirical (just whirl it around a bit and records the min max !!)
    compass.m_max = (LSM303::vector<int16_t>) { +3353, +5127, +5300 };
/*    if (tc.sec % 2 == 0 ) {
      digitalWrite(13, HIGH);
    } else {
      digitalWrite(13, LOW);
    }*/
    HT.begin(0x00);
  }
  
  if (setchiptime > 0) {    //  update the arduino time from the modbus register then clear it... also set RTC if fitted
    setTime(setchiptime) ;
    if (hasRTC) {
      tc.year = year();
      tc.mon = month() ;
      tc.mday = day();
      tc.hour = hour() ;
      tc.min = minute();
      tc.sec = second();
      DS3231_set(tc);                          //should also update this
    }    
    setchiptime = 0 ;
  }
  
  if ( hour() != rtc_hour){  // update our time every hour if we can
    if (iGPSLock == 0){
        if (hasRTC) {
          DS3231_get(&td);
          setTime((int)td.hour,(int)td.min,(int)td.sec,(int)td.mday,(int)td.mon,(int)td.year ) ; // set the internal RTC from Dallas RTC
       }
    }else{
      if ((fixage > 0 ) && ( fixage < 10000 )) {         //        if the lock is less than 10 second old
        SetTimeFromGPS();
      }
    }      
    rtc_hour = hour() ; 
  }

  if ( rtc_sec != second() )  {     //only update once a second
    wdt_reset();                 // reset internal watchdog - good puppy

    if (( tc.sec > 8 ) && ( tc.sec < 58 )) {  // dont calculate arround the minute when time is updating from NTP or GPS as might get a not so funny result
      digitalWrite(13,!digitalRead(13));
      solar_az_deg = SolarAzimouthRad(longitude, latitude, &tc, timezone) * 180 / PI ;
      solar_el_deg = SolarElevationRad(longitude, latitude, &tc, timezone) * 180 / PI ;
    
      decl = Decl(gama(&tc)) * 180 / PI ;
      ha = HourAngle (longitude , &tc , timezone )  ;
      sunrise = Sunrise(longitude, latitude, &tc, timezone) ;
      sunset = Sunset(longitude, latitude, &tc, timezone);
      tst = TrueSolarTime(longitude, &tc, timezone);
      sunX = abs(latitude) + decl ;
      if (solar_el_deg >= 0 ){           // day
        iDayNight = 1 ;
      }else{                             // night
        iDayNight = 0 ;
      }
    }
    switch (iTrackMode) {
      case 4: // both axis to park
        yzTarget = dyPark ;  // night park position  E/W
        xzTarget = dxPark ;  // night park position  N/S
        break ;
      case 3: // both axis off no tracking
        break ;
      case 2: // xz tracking  NS
        if ( iDayNight == 1 ) {
          xzTarget = sunX ; // need to map the coordinate system correctly
        } else {
          xzTarget = dxPark ;  // night park position
        }
        break;
      case 1:  // yz tracking   EW
        if (iDayNight == 1) {
          yzTarget = ha ;
        } else {
          yzTarget = dyPark ;  // night park position
        }
        break;
      case -1: // set target to tracking and park both at nigh
        if (iDayNight == 1) {
          yzTarget = ha ;
          xzTarget = sunX ; // need to map the coordinate system correctly
        } else {
          yzTarget = dyPark ;  // night park position  E/W
          xzTarget = dxPark ;  // night park position  N/S
        }
        break;
      default: // set target to tracking
        if (iDayNight == 1) {
          yzTarget = ha ;
          xzTarget = sunX ; // need to map the coordinate system correctly
        } else {
          yzTarget = dyPark ;  // night park position (dont park the other - leave till morning)
        }
        break;
    }
    xzTarget = constrain(xzTarget,xMinVal,xMaxVal);   // constain function... very cool - dont leave home without it !
    yzTarget = constrain(yzTarget,yMinVal,yMaxVal);

    lcd.setCursor ( 0, 0 );   // Diags in case there is an LCD display attached
    lcd.print("X/Z ");
    if ( xzAng > 0 ) {
      lcd.print("+");
    }
    lcd.print(xzAng);
    lcd.setCursor ( 10, 0 );
    lcd.print("Y/Z ");
    if ( yzAng > 0 ) {
      lcd.print("+");
    }
    lcd.print(yzAng);
    lcd.setCursor ( 0, 1 );
    lcd.print("TX ");
    if (( xzTarget) > 0 ) {
      lcd.print("+");
    }
    lcd.print(( xzTarget));
    lcd.setCursor ( 10, 1 );
    lcd.print("TY ");
    if ((  yzTarget) > 0 ) {
      lcd.print("+");
    }
    lcd.print((  yzTarget));
    lcd.setCursor ( 0, 2 );
    lcd.print("DX ");
    dTmp = ( xzAng - xzTarget) ;
    if (dTmp > 0 ) {
      lcd.print("+");
    }
    lcd.print(dTmp);
    lcd.setCursor ( 10, 2 );
    lcd.print("DY ");
    dTmp = ( yzAng - yzTarget) ;
    if (dTmp > 0 ) {
      lcd.print("+");
    }
    lcd.print(dTmp);

    lcd.setCursor ( 0, 3 );       // line 3
    snprintf(buff, BUFF_MAX, "%02d:%02d:%02d", tc.hour, tc.min, tc.sec);
    lcd.print(buff) ;

    lcd.setCursor ( 9, 3 );       // line 3
    lcd.print( "S") ;
    if ( iDayNight == 0 ) {
      lcd.print( "-") ;
    }else{
      if (( tc.sec % 2 ) == 0 ) {
        lcd.print( "<") ;        
      }else{
        lcd.print( ">") ;
      }
    }
    if ( gps.satellites() > 9 ){
      lcd.print( "-" );
    }else{
      lcd.print(gps.satellites()) ;
    }

    lcd.setCursor ( 13, 3 );       // line 3
    snprintf(buff, BUFF_MAX, "%04X", fixage);
    lcd.print(buff) ;
    
    lcd.setCursor ( 18, 3 );       // line 3
    if ( iPWM_YZ == 0 ) {
      lcd.print( " ") ;
    }else{
      if (( digitalRead(RELAY_YZ_DIR) == LOW )) {
        lcd.print( "W") ;
      }else{
        lcd.print( "E") ;            
      }
    }
    lcd.setCursor ( 19, 3 );       // line 3
    if ( iPWM_XZ == 0 ) {
      lcd.print( " ") ;
    }else{
      if (( digitalRead(RELAY_XZ_DIR) == LOW )) {
        lcd.print( "N") ;
      }else{
        lcd.print( "S") ;            
      }
    }    
    
    rtc_sec = second() ;
    DisplayMeatBall() ;
  }

  if ( iDoSave == 2 ) {  // save them Active via web or 
    LoadParamsFromEEPROM(false);
    iDoSave = 0 ;  // only do once
  }
  if ( iDoSave == 3 ) {  // load them
    LoadParamsFromEEPROM(true);
    iDoSave = 0 ;  // only do once
  }

  if (((tc.hour > 19 ) || ( tc.hour < 5 )) && (iTrackMode < 3)) {
    if ( iNightShutdown != 0 ){
      ActivateRelays(1) ;        
    }else{
      ActivateRelays(0) ;  // power down at night if in tracking mode
    }
  }else{
    ActivateRelays(1) ;    
  }


  state1 = mb_slave.poll( (uint16_t*)&iMode, MAX_MODBUS_DATA );

  switch (state1) {
    case EXC_ADDR_RANGE:
      Serial.println("EXC_ADDR_RANGE PORT 2");
    break;
    case EXC_FUNC_CODE:
      Serial.println("EXC_FUNC_CODE PORT 2");
    break;
    case EXC_REGS_QUANT:
      Serial.println("EXC_REGS_QUANT PORT 2");
    break;
  }
  
  
  while (Serial1.available()){ // process the gps buffer
    gps.encode(Serial1.read());
  }

  WiFiEspClient client = server.available();  // listen for incoming clients
  if (client) {                               // if you get a client,
    ActivateRelays(0);                        // deactive motor while we do web
    Serial.println("New client");             // print a message out the serial port
    buf.init();                               // initialize the circular buffer
    request = "" ;
    while (client.connected()) {              // loop while the client's connected
      if (client.available()) {               // if there's bytes to read from the client,
        char c = client.read();               // read a byte, then
        buf.push(c);                          // push it to the ring buffer
        request += c ;

// you got two newline characters in a row
// that's the end of the HTTP request, so send a response
        
        if (buf.endsWith("\r\n\r\n")) {
//        if (request.endsWith("\r\n\r\n")){
//          Serial.print(request);
          processRequest(request);
          if (request.indexOf("favicon.ico")>0){
            sendHttpResponseNG(client);
          }else{
            sendHttpResponse(client);
          }
/*          Serial.println(request.endsWith("\r\n\r\n"));
          Serial.println(request.indexOf("\r\n\r\n"));  */
          client.flush();
          break;
        }
      }
    }
    
    // give the web browser time to receive the data
    delay(10);

    //close the connection
    client.stop();
    Serial.println("Client disconnected");
  }  
}  //   end of loop 






float DayOfYear(uint16_t iYear , uint8_t iMon , uint8_t iDay , uint8_t iHour , uint8_t iMin ) {
  int i ;
  float iTDay ;

  iTDay = iDay - 1 ;  // this is zero referenced
  for ( i = 1 ; i < iMon ; i++ ) {
    switch (i) {
      case 1:
      case 3:
      case 5:
      case 7:
      case 8:
      case 10:
      case 12:
        iTDay += 31 ;
        break;
      case 4:
      case 6:
      case 9:
      case 11:
        iTDay += 30 ;
        break;
      case 2 :
        if ((iYear % 4) == 0 ) {
          iTDay += 29 ;
        } else {
          iTDay += 28 ;
        }
        break;
    }
  }
  iTDay += (( 1.0 * iHour - 12 ) / 24 ) ;
  //  iDay +=  1.0 * iMin  / 1440 ;
  return (iTDay);
}


int HrsSolarTime(float target) {
  int i ;
  i = target ;
  return (  i / 60 );
}
int MinSolarTime(float target) {
  int i ;
  i = target  ;
  return (   i % 60 );
}

float sign(float target) {
  if (target > 0 ) {
    return (1);
  } else {
    if (target < 0 ) {
...

This file has been truncated, please download it to see its full contents.

Credits

DougalPlummer

DougalPlummer

14 projects • 93 followers
I write code for a living, on everything from PLC's to web and accounting systems. I'm also very talented at farming prickles :)

Comments