// 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();
}
}