Natural Reef Aquarium Lighting Controller

I would probably recommend a serial lcd display. It would only require the use of 2 pins, one for tx and one for rx. I have used one on the past and it shouldn't be difficult to update this sketch to utilize one.

I will have a bit of a look soon when I get some free time.

Cheers,
 
Hi Jason!
I've been running your code now for over a month and everything works perfect.
Now, I have a question about cloud simulation. Do you know how to put that into the sketch? It would be great. I have a feeling that it's quite important with cloud simulation for the corals. Especially when we run led with a lot of light hitting the corals.

//Patrik
 
That's good to hear, I haven't put much thought into the clouds yet. I have a couple of other requests to add to the code as well. Should be able to find some time soon to have a bit of a play.

Cheers,

Jason
 
Ok,

Had a chance to play a bit tonight

PHP:
// Natural Reef Aquarium Lighting V2.7.0
// 11/01/2014
// 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;
// Auxiliary pins for on/off switching of realys.
//
// 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;

// Minute Counter
int minCounter;  // Time counter in minutes

// 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

// Auxilery Pins

byte auxPins[] = { 4, 8, 12 };                // Pins

byte auxChannels = 3;                          // Total count of pins above

byte auxStart[] = { 510, 540, 480 };            // Start time of pins in minutes
byte auxStop[] = { 1230, 1140, 1200 };       // Stop time of pins in minutes
  
// 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)

// 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 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 AuxUpdate (byte _pin, int _mins, byte _start, byte _stop)
{
  boolean auxVal;
  if (_mins <= _start || _mins >= _stop)
  {
    auxVal = LOW;
  }
  if (_mins >= _start || _mins <= _stop)
  {
    auxVal = HIGH;
  }
 digitalWrite(_pin, auxVal);
 return auxVal;
} 



/***** 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);
        int i = 0;
        for (i = 0; i < auxChannels; i++)
        {
          pinMode(auxPins[i], OUTPUT);
        }
	Serial.begin(9600);
	Wire.begin();
}

void loop() {
        minCounter = rtcHrs * 60 + rtcMins; 
	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();
                update_aux();
	}
}

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

void update_aux(){
  int i;
  boolean value;
  for (i = 0; i < auxChannels; i++)
  {
    value = AuxUpdate(auxPins[i], minCounter, auxStart[i], auxStop[i]);
  }
 }

This code adds the ability to turn on and off relays at set time.
I haven't run this code, but it does compile OK so please let me know if there are any issues.

I will start working on the weather simulation soon.

Cheers,

Jason
 
do the AUX channels work in this code seems to not for me or the guy above in the last post
 
Give this one a try and let me know if there is any difference.

PHP:
// Natural Reef Aquarium Lighting V2.7.1 
// 5/04/2014 
// 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; 
// Auxiliary pins for on/off switching of realys. 
// 
// 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; 

// Minute Counter 
int minCounter;  // Time counter in minutes 

// 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 

// Auxilery Pins 

byte auxPins[] = { 4, 8, 12 };                // Pins 

byte auxChannels = 3;                          // Total count of pins above 

byte auxStart[] = { 510, 540, 480 };            // Start time of pins in minutes 
byte auxStop[] = { 1230, 1140, 1200 };       // Stop time of pins in minutes 
   
// 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) 

// 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 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 AuxUpdate(byte _pin, int _mins, byte _start, byte _stop) 
{ 
  boolean auxVal; 
  if (_mins <= _start || _mins >= _stop) 
  { 
    auxVal = LOW; 
  } 
  if (_mins >= _start || _mins <= _stop) 
  { 
    auxVal = HIGH; 
  } 
 digitalWrite(_pin, auxVal); 
 return auxVal; 
}  



/***** 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); 
        int i = 0; 
        for (i = 0; i < auxChannels; i++) 
        { 
          pinMode(auxPins[i], OUTPUT); 
        } 
    Serial.begin(9600); 
    Wire.begin(); 
} 

void loop() { 
        minCounter = rtcHrs * 60 + rtcMins;  
    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(); 
                update_aux(); 
    } 
} 

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

void update_aux(){ 
  int i; 
  boolean value; 
  for (i = 0; i < auxChannels; i++) 
  { 
    value = AuxUpdate(auxPins[i], minCounter, auxStart[i], auxStop[i]); 
  } 
 }
 
A little off topic, but is there any way I can use this to output a cycle that I could manually program into my LEDs?

I'm sure a lot of people would want to do similar things with other lights...
 
Very interesting, thank you for sharing.

Would you be able to provide some insight on how you do the calculations per channel, How would i modify the code to support 6 channels?

Thanks!
 
Very interesting, thank you for sharing.

Would you be able to provide some insight on how you do the calculations per channel, How would i modify the code to support 6 channels?

Thanks!

PHP:
// 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

If you want to have 6 channels you just need to adjust the pin numbers in the section above.

PHP:
// LED variables (Change to match your needs)  
byte bluePins[] = { 3, 9, 12, 13, 14 };      // 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

example above would have 5 blue , 2 white, 1 UV and 1 moon.

You are only really limited by the number of PWM pins. I think the Arudion Mega has 12 PWM pins...?

Just make sure you also adjust the following sections as well

PHP:
byte blueChannels = 5;    // 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)

and the arrays for the blue channels must have 5 values as well

PHP:
byte BluePWMHigh[] = { 255, 255, 255, 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, 0, 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, 25, 25, 25 };          // Value in degrees (sun angle) that each Blue string will be at max output (Larger = more sunlight)

Each value in the array can be unique as well if you would like, the numbers correspond to the order the pins are placed in the Pins array.

The descriptions should help you understand what each value does.

Cheers,
 
A little off topic, but is there any way I can use this to output a cycle that I could manually program into my LEDs?

I'm sure a lot of people would want to do similar things with other lights...

This code does not allow for manual programming of the light cycle. It is purely based off of the geo-coordinates that you provide to the program. You can make small modifications to the start and finish times as well as the light ramp up and down, but other then that you cant change much.

If you wanted to create your own schedule you should look at my previous code

http://www.reefcentral.com/forums/showpost.php?p=19324414&postcount=474

It allows you to program you own start and finish times based on the months.

Cheers,
 
Thanks for the explanation, I got 6 channels: cool white, blue, royal blue, violet/indigo, red, green
 
Thanks for the explanation, I got 6 channels: cool white, blue, royal blue, violet/indigo, red, green

To be honest you could place all of them in the same group, as long as you have unique values for each sting in the arrays it doesn't really matter. I just split a few off for ease of entry.
 
Had an opportunity to do some testing today and think I have found the problem. Please let me know if this works.

PHP:
// Natural Reef Aquarium Lighting V2.7.2
// 9/04/2014
// 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;
// Auxiliary pins for on/off switching of realys modification.
//
// 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;

// Minute Counter
int minCounter;  // Time counter in minutes

// 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

// Auxilery Pins

byte auxPins[] = { 4, 8, 12 };

byte auxChannels = 3;

int auxStart[] = { 510, 540, 480 };
int auxStop[] = { 1230, 1140, 1200 };
  
// 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)

// 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 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 AuxUpdate (byte _pin, int _mins, int _start, int _stop)
{
  byte auxVal;
  if (_mins < _start || _mins > _stop)
  {
    auxVal = LOW;
  }
  if (_mins >= _start && _mins <= _stop)
  {
    auxVal = HIGH;
  }
 digitalWrite(_pin, auxVal);
 return auxVal;
} 



/***** 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);
        for (int i = 0; i < auxChannels; i++)
        {
          pinMode(auxPins[i], OUTPUT);
        }
	Serial.begin(9600);
	Wire.begin();
}

void loop() {
        minCounter = rtcHrs * 60 + rtcMins; 
	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_aux();
                update_leds();
	}
}

void update_leds(){
	if (blueChannels > 0){
		Serial.println("Blue LED's");
		for (int i = 0; i < blueChannels; i++)
		{
			byte 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 (int i = 0; i < whiteChannels; i++)
		{
			byte 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 (int i = 0; i < uvChannels; i++)
		{
			byte 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 (int i = 0; i < moonChannels; i++)
		{
			byte 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();
	}
}

void update_aux(){
  Serial.print(minCounter);
  Serial.println();
  for (int i = 0; i < auxChannels; i++)
  {
    byte value = AuxUpdate(auxPins[i], minCounter, auxStart[i], auxStop[i]);
    Serial.print("Pin ");
    Serial.print(auxPins[i]);
    Serial.print(" Value ");
    Serial.print(value);
    Serial.println();
  }
 }

Did the old code always hold the relays on or off?

Thanks,
 
HI a few post back you explained to djmx2000 that he could add more channels to lighting could I add more to AUX channels for a total of 6 I am not really interested in lighting as I am the ability to turn on and off a single relay several times a day for a out door pond feeder on and off for 2 to 4 minute interval's 4 to 6 times a day
 
Back
Top