// Natural Reef Aquarium Lighting V2.6
// 16/06/2013
// Developed by J. Harp (nUm - RTAW Forums, Numlock10 - Reef Central Forums)
// Formulas based off of information from NOAA website for sunrise / sunset times.
// Includes Lunar Simulation.
// Compiled in Arduino 1.5.2
//
// Testing;
// Moon Correction (was inverted).
// Will not calculate string values if Channel count is 0 to save on processor time.
// Light will start ramping up at set value of degrees below the horizion (-18 is default) this value is custom to each string.
// Light will ramp set value from below horizion line to 0 line in PWM points (12.75 is the default = 10%)
// 
//
// Future Development:
// Weather Simulation
//
// Please feel free to use this and modify as you see fit, if you have any comments or suggestions please let me know via messages on the forums listed above.
//
#include <math.h>
#include <Wire.h>
#define DS1307_I2C_ADDRESS 0x68
// RTC variables
byte second, rtcMins, oldMins, rtcHrs, oldHrs, dayOfWeek, dayOfMonth, month, year, psecond;
// LED variables (Change to match your needs)
byte bluePins[]      =  {3, 9};      // PWM pins for blues
byte whitePins[]     =  {10, 11};    // PWM pins for whites
byte uvPins[]        =  {5};         // PWM pins for UVs
byte moonPins[]      =  {6};         // PWM pins for moonlights
byte blueChannels    =        2;    // how many PWMs for blues (count from above)
byte whiteChannels   =        2;    // how many PWMs for whites (count from above)
byte uvChannels      =        1;    // how many PWMs for uv (count from above)
byte moonChannels    =        1;    // how many PWMs from moon (count from above)
byte BluePWMHigh[]          =       {255, 255};        // High value for Blue PWM each vale is for each string - if your values are noraml this is 255, if your values are inverted this is 0
byte BluePWMLow[]           =       {0, 0};            // Low value for Blue PWM - if your values are noraml this is 0, if your values are inverted this is 255
float BlueFull[]            =       {25, 25};          // Value in degrees (sun angle) that each Blue string will be at max output (Larger = more sunlight)
float BlueLow[]             =       {-18, -18};        // Value in degrees (sun angle) that each Blue string will start
byte WhitePWMHigh[]         =       {255, 255};        // High value for White PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte WhitePWMLow[]          =       {0, 0};            // Low value for White PWM - if your values are noraml this is 0, if your values are inverted this is 255
float WhiteFull[]           =       {37.5, 37.5};      // Value in degrees (sun angle) that each White string will be at max output (Larger = more sunlight)
float WhiteLow[]            =       {-18, -18};        // Value in degrees (sun angle) that each White string will start
byte UVPWMHigh[]            =       {255};             // High value for UV PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte UVPWMLow[]             =       {0};               // Low value for UV PWM - if your values are noraml this is 0, if your values are inverted this is 255
float UVFull[]              =       {30};              // Value in degrees (sun angle) that each UV string will be at max output (Larger = more sunlight)
float UVLow[]               =       {-18};             // Value in degrees (sun angle) that each Blue string will start
byte MoonPWMHigh[]          =       {255};             // High value for Moon PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte MoonPWMLow[]           =       {0};               // Low value for Moon PWM - if your values are noraml this is 0, if your values are inverted this is 255
float LowValue               =       12.75;            // PWM steps to ramp from Low to 0 degrees (12.75 = 10%)
// Set for the location of the world you want to replicate.
float latitude = -19.770621;   // + to N  Defualt - (-19.770621) Heart Reef, Great Barrier Reef, QLD, Australia 
float longitude = 149.238532;  // + to E  Defualt - (149.238532)
int TimeZone = 10;             // + to E  Defulat - (10)
// Julian Century Varaiable
float JC;
// Sunlight Variables
int delayTime = 0;     // start time delay in minutes,  - will push the day back, + will bring the day forward
int SunLight (byte _ledPin, byte _ledHigh, byte _ledLow, float _lowSun, float _fullSun, float _lowValue, byte _year, byte _month, byte _day, byte _hour, byte _min, byte _sec)
{
  float a = floor((14 - _month)/12);
  float y = _year + 4800 - a;
  float m = _month + (12 * a) - 3;
  float AH;
  int result;
  
  JC = (((_day + floor(((153.0 * m) + 2.0) / 5.0) + (365.0 * y) + floor(y / 4.0) - floor(y / 100.0) + floor(y / 400.0) - 32045.0) + ((_hour / 24.0) + (_min / 1444.0) + (_sec / 86400.0))) - 2451556.08) / 36525.0;
  
  float GMLS = fmod(280.46646+JC*(36000.76983 + JC * 0.0003032),360);
  
  float GMAS = 357.52911 + JC * (35999.05029 - 0.0001537 * JC);
  
  float EEO = 0.016708634 - JC * (0.000042037 + 0.0000001267 * JC);
  
  float SEoC = sin((GMAS * M_PI)/180)*(1.914602 - JC * (0.004817 + 0.000014 * JC)) + sin(((2 * GMAS) * M_PI) / 180) * (0.019993 - 0.000101 * JC) + sin(((3 * JC) * M_PI) / 180) * 0.000289;
  
  float STL = GMLS + SEoC;
  
  float STA = GMAS + SEoC;
  
  float SRV = (1.000001018 * (1 - EEO * EEO)) / (1 + EEO * cos((STA * M_PI) / 180));
  
  float SAL = STL - 0.00569 - 0.00478 * sin(((125.04 - 1934.136 * JC) * M_PI) / 180);
  
  float MOE = 23 + (26 + ((21.448 - JC * (46.815 + JC * (0.00059 - JC * 0.001813)))) / 60) / 60;
  
  float OC = MOE + 0.00256 * cos(((215.04 - 1934.136 * JC) * M_PI) / 180);
  
  float SD = (asin(sin((OC * M_PI) / 180) * sin((SAL * M_PI) / 180))) * (180 / M_PI);
  
  float vy = tan(((OC / 2) * M_PI) / 180) * tan(((OC / 2) * M_PI) / 180);
  
  float EQoT = (4 * (vy * (sin(2 * ((GMLS * M_PI) / 180)) - 2 * EEO * sin((GMAS * M_PI) / 180) + 4 * EEO * vy * sin((GMAS * M_PI) / 180) * cos( 2 * ((GMLS * M_PI) / 180)) - 0.5 * vy * vy * sin(4 * ((GMLS * M_PI) / 180)) - 1.25 * EEO * EEO * sin(2 * ((GMAS * M_PI) / 180))))) * (180 / M_PI);
  
  float HAS = acos(cos((90.833 * M_PI) / 180) / (cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180)) - tan((latitude * M_PI) / 180) * tan((SD * M_PI) / 180)) * (180 / M_PI);
  float SN = (720 - 4 * longitude - EQoT + TimeZone * 60);
  float SR = SN - HAS * 4;
  float SS = SN + HAS * 4;
 
  float STD = 8 * HAS;
  
  float TST = fmod((((_hour) + (_min / 60.0) + (_sec / 3600.0)) / 24.0)*1440 + EQoT + 4 * longitude - 60 * TimeZone,1440)+delayTime;
 
  if (TST / 4 < 0)
  {
  AH = ((TST / 4.0) + 180);
  }
  else
  {
  AH = ((TST / 4.0) - 180);  
  }
  
  float SZA = (acos(sin((latitude * M_PI) / 180) * sin((SD * M_PI) / 180) + cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180) * cos((AH * M_PI) / 180))) * (180 / M_PI);
  
  int SEA = 90 - SZA;
  
  if (SEA <= _lowSun)  
  {
   result = _ledLow;    
  }
  
  if (SEA > _lowSun && SEA <= 0)
  {
  result = map(SEA,_lowSun,0,_ledLow,(_ledLow + _lowValue));
  }
  
  if (SEA > 0)
  {
  result = map(SEA,0,_fullSun,(_ledLow + _lowValue),_fullSun);
  }
  
  if (SEA >= _fullSun)  
  {
  result = _ledHigh;
  }
  
  analogWrite(_ledPin, result);  
  return result;
  
}
int MoonLight (float JC, byte _ledPin, byte _ledHigh, byte _ledLow)
{
 int result;
 
 float MS = fmod((2456318.69458333 - JC),29.530589);
 
 if(MS <= 14.7518)
 {
  result = map(MS,0,14.7518,_ledHigh,_ledLow);
 }
 
 if( MS > 14.7518)
 {
   result = map(MS,14.7518,29.530589,_ledLow,_ledHigh);
 }
 analogWrite(_ledPin, result); 
 return result;
}
 
/***** RTC Functions *******/
/***************************/
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}
// Gets the date and time from the ds1307
void getDateDs1307(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
  Wire.beginTransmission(DS1307_I2C_ADDRESS);
  Wire.write(0);
  Wire.endTransmission();
  Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
  *second     = bcdToDec(Wire.read() & 0x7f);
  *minute     = bcdToDec(Wire.read());
  *hour       = bcdToDec(Wire.read() & 0x3f);
  *dayOfWeek  = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month      = bcdToDec(Wire.read());
  *year       = bcdToDec(Wire.read());
}
void setup() {
delay(500);
Serial.begin(57600);
Wire.begin();
}
void loop() {
  getDateDs1307(&second, &rtcMins, &rtcHrs, &dayOfWeek, &dayOfMonth, &month, &year);
  if (psecond != second){
    psecond = second;
  Serial.print(rtcHrs);
  Serial.print(":");
  Serial.print(rtcMins);
  Serial.print(":");
  Serial.print(second);
  Serial.print(" ");
  Serial.print(dayOfMonth);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.println(year);
  update_leds();
  }
}
void update_leds ( void ){
  int i;
  byte value;
  int MS;
  if(blueChannels > 0){
  Serial.println("Blue LED's");
  for (i = 0; i < blueChannels; i++)
  {
    value = SunLight(bluePins[i],BluePWMHigh[i],BluePWMLow[i],BlueLow[i],BlueFull[i],LowValue,year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,BluePWMLow[i],BluePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  }
  if(whiteChannels > 0){
  Serial.println("White LED's");
  for (i = 0; i < whiteChannels; i++)
  {
    value = SunLight(whitePins[i],WhitePWMHigh[i],WhitePWMLow[i],WhiteLow[i],WhiteFull[i],LowValue,year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,WhitePWMLow[i],WhitePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  }
  if(uvChannels > 0){
  Serial.println("UV LED's");
  for (i = 0; i < uvChannels; i++)
  {
    value = SunLight(uvPins[i],UVPWMHigh[i],UVPWMLow[i],UVLow[i],UVFull[i],LowValue,year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,UVPWMLow[i],UVPWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  }
  if(moonChannels > 0){
  Serial.println("Moon Value");
  for (i = 0; i < moonChannels; i ++)
  {
  MS = MoonLight(JC, moonPins[i],MoonPWMHigh[i],MoonPWMLow[i]);
  Serial.print(map(MS,MoonPWMLow[i],MoonPWMHigh[i],0,100));
  Serial.print("% ");
  }
  Serial.println();
  }
}