// Natural Reef Aquarium Lighting V2.5.4
// 23/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
//
// 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)
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)
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)
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
// 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
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 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 moon = fmod((2456318.69458333 - JC), 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;
}
/***** 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;
	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();
	}
}