// Natural Reef Aquarium Lighting V2.5.6
// 30/11/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
// Float Map for moonlight with corrected formula
//
// 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[] = { 6 };      // PWM pins for blues 
byte whitePins[] = { 10, 11 };    // PWM pins for whites 
byte uvPins[] = { 5, 9 };         // PWM pins for UVs 
byte moonPins[] = { 3 };         // PWM pins for moonlights
byte lightningPins [] = {5, 6, 9, 10, 11};   //PWM pins for lighning
byte blueChannels = 1;    // how many PWMs for blues (count from above) 
byte whiteChannels = 2;    // how many PWMs for whites (count from above) 
byte uvChannels = 2;    // how many PWMs for uv (count from above) 
byte moonChannels = 1;    // how many PWMs from moon (count from above)
byte lightningChannels = 5; //how many PWMs from lightning
byte BluePWMHigh[] = { 200 };        // 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 };            // Low value for Blue PWM - if your values are noraml this is 0, if your values are inverted this is 255 
float BlueFull[] = { 25 };          // Value in degrees (sun angle) that each Blue string will be at max output (Larger = more sunlight) 
byte WhitePWMHigh[] = { 150, 150 };        // 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) 
byte UVPWMHigh[] = { 200, 200 };             // High value for UV PWM - if your values are noraml this is 255, if your values are inverted this is 0 
byte UVPWMLow[] = { 0, 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, 30 };              // Value in degrees (sun angle) that each UV string will be at max output (Larger = more sunlight) 
byte MoonPWMHigh[] = { 40 };             // 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
byte LightningPWMHigh[] = {255}
byte LightningPWMLow[] = {50}
// Set for the location of the world you want to replicate. 
float latitude = -17.730211;   // + to N  Defualt - (-19.770621) Heart Reef, Great Barrier Reef, QLD, Australia  
float longitude = 177.127218;  // + to E  Defualt - (149.238532) 
int TimeZone = 12;             // + to E  Defulat - (10)
// Sunlight Variables
int delayTime = -150;     // start time delay in minutes,  - will push the day back, + will bring the day forward
float floatMap(float x, float in_min, float in_max, int out_min, int out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
int SunLight(byte _ledPin, byte _ledHigh, byte _ledLow, float _fullSun, byte _year, byte _month, byte _day, byte _hour, byte _min, byte _sec)
{
    float a = floor((14.0 - _month) / 12.0);
    float y = _year + 4800.0 - a;
    float m = _month + (12.0 * a) - 3.0;
    float AH;
    int result;
        float 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.0);
    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.0)*(1.914602 - JC * (0.004817 + 0.000014 * JC)) + sin(((2.0 * GMAS) * M_PI) / 180.0) * (0.019993 - 0.000101 * JC) + sin(((3.0 * JC) * M_PI) / 180.0) * 0.000289;
    float STL = GMLS + SEoC;
    float STA = GMAS + SEoC;
    float SRV = (1.000001018 * (1.0 - EEO * EEO)) / (1.0 + EEO * cos((STA * M_PI) / 180.0));
    float SAL = STL - 0.00569 - 0.00478 * sin(((125.04 - 1934.136 * JC) * M_PI) / 180.0);
    float MOE = 23.0 + (26.0 + ((21.448 - JC * (46.815 + JC * (0.00059 - JC * 0.001813)))) / 60.0) / 60.0;
    float OC = MOE + 0.00256 * cos(((215.04 - 1934.136 * JC) * M_PI) / 180.0);
    float SD = (asin(sin((OC * M_PI) / 180.0) * sin((SAL * M_PI) / 180.0))) * (180.0 / M_PI);
    float vy = tan(((OC / 2.0) * M_PI) / 180.0) * tan(((OC / 2.0) * M_PI) / 180.0);
    float EQoT = (4.0 * (vy * (sin(2.0 * ((GMLS * M_PI) / 180.0)) - 2.0 * EEO * sin((GMAS * M_PI) / 180.0) + 4.0 * EEO * vy * sin((GMAS * M_PI) / 180.0) * cos(2.0 * ((GMLS * M_PI) / 180.0)) - 0.5 * vy * vy * sin(4.0 * ((GMLS * M_PI) / 180.0)) - 1.25 * EEO * EEO * sin(2 * ((GMAS * M_PI) / 180))))) * (180 / M_PI);
    float HAS = acos(cos((90.833 * M_PI) / 180.0) / (cos((latitude * M_PI) / 180.0) * cos((SD * M_PI) / 180.0)) - tan((latitude * M_PI) / 180.0) * tan((SD * M_PI) / 180.0)) * (180.0 / M_PI);
    float SN = (720.0 - 4.0 * longitude - EQoT + TimeZone * 60.0);
    float SR = SN - HAS * 4.0;
    float SS = SN + HAS * 4.0;
    float STD = 8.0 * HAS;
    float TST = fmod((((_hour)+(_min / 60.0) + (_sec / 3600.0)) / 24.0) * 1440.0 + EQoT + 4.0 * longitude - 60.0 * TimeZone, 1440.0) + delayTime;
    if (TST / 4 < 0.0)
    {
        AH = ((TST / 4.0) + 180.0);
    }
    else
    {
        AH = ((TST / 4.0) - 180.0);
    }
    float SZA = (acos(sin((latitude * M_PI) / 180.0) * sin((SD * M_PI) / 180.0) + cos((latitude * M_PI) / 180.0) * cos((SD * M_PI) / 180.0) * cos((AH * M_PI) / 180.0))) * (180.0 / M_PI);
    float SEA = 90.0 - SZA;
    if (SEA <= 0.0)
    {
        result = _ledLow;
    }
    if (SEA > 0.0 && SEA < _fullSun)
    {
        result = map(SEA, 0, _fullSun, _ledLow, _ledHigh);
    }
    if (SEA >= _fullSun)
    {
        result = _ledHigh;
    }
    analogWrite(_ledPin, result);
    return result;
}
int MoonLight(byte _ledPin, byte _ledHigh, byte _ledLow, byte _year, byte _month, byte _day, byte _hour, byte _min, byte _sec)
{
    int result;
    float a = floor((14.0 - _month) / 12.0);
    float y = _year + 4800.0 - a;
    float m = _month + (12.0 * a) - 3.0;
    
        float mJDN = ((_day  + ((153.0 * m + 2.0) / 5.0) + (365.0 * y) + (y / 4.0) - ( y / 100.0) + (y / 400.0) - 32045.0) + 730483.71);
        float mJDR = (_hour / 24.0) + (_min / 1444.0) + (_sec / 86400.0);
        float mJD = mJDN + mJDR;
        
    float moon = fmod((mJD - 2456318.69458333), 29.530589);
    if (moon <= 14.7652945)
    {
        result = floatMap(moon, 0.0, 14.7652945, _ledHigh, _ledLow);
    }
    if (moon >= 14.7652946)
    {
        result = floatMap(moon, 14.7652946, 29.530589, _ledLow, _ledHigh);
    }
    analogWrite(_ledPin, result);
    return result;
}
int Lightning(byte _ledPin, byte _ledHigh, byte _ledLow, byte _year, byte _month, byte _day, byte _hour, byte _min, byte _sec)
/***** 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(){
    int i;
    byte value;
    if (blueChannels > 0){
        Serial.println("Blue LED's");
        for (i = 0; i < blueChannels; i++)
        {
            value = SunLight(bluePins[i], BluePWMHigh[i], BluePWMLow[i], BlueFull[i], 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], WhiteFull[i], 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], UVFull[i], 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++)
        {
            value = MoonLight(moonPins[i], MoonPWMHigh[i], MoonPWMLow[i], year, month, dayOfMonth, rtcHrs, rtcMins, second);
            Serial.print(map(value, MoonPWMLow[i], MoonPWMHigh[i], 0, 100));
            Serial.print("% ");
        }
        Serial.println();
    }
    
    if (lightningChannels > 0){
        Serial.println("Lightning Value");
        for (i = 0; i < lightningChannels; i++)
        {
            value = Lightning(lightningPins[i], LightningPWMHigh[i], LightningPWMLow[i], year, month, dayOfMonth, rtcHrs, rtcMins, second);
            Serial.print(map(value, LightningPWMLow[i], LightningPWMHigh[i], 0, 100));
            Serial.print("% ");
        }
        Serial.println();
    }
}