Code: Select all
static long CloudMaster[15];
Code: Select all
for (byte b=CloudsTotal*2; b<16;b++){//zero fill unused array positions on days with less than 8 clouds
CloudMaster[b]=0;
}
Code: Select all
static long CloudMaster[15];
Code: Select all
for (byte b=CloudsTotal*2; b<16;b++){//zero fill unused array positions on days with less than 8 clouds
CloudMaster[b]=0;
}
Code: Select all
byte stepsize;
stepsize=random(-20,21);
PriorCloudCover=CloudCover;
CloudCover=(CloudCover+stepsize);
if ((stepsize<(-15)) || (stepsize>15)){ //it is convient to use this random call to generate a strike during a storm for more frequent strikes adjust down
StrikeNumber=(random(2,10)); //random high =x-1 so max strike =9 each strike requires a duration and a delay thus StrikeMaster is 18 positions
//ensure the array is zeroed out past the last position required for this strike pattern so each pattern is only as long as generated
//Array is in pairs, position in arry of (0,1) (2,3) etc as strike (delay,duration)
for (byte a=0;a<18;a++){
if (a>=(StrikeNumber*2)){
StrikeMaster[a]=0;
}
if (a%2==0){
if (a==0){
StrikeMaster[a]=random(750,2001);
}
else {
StrikeMaster[a]=(StrikeMaster[(a-2)]+random(750,2000));//position 0,2,4,6,8.. is strike delay
}
}
else if(a%2!=0){
StrikeMaster[a]=random(100,300);//position 1,3,5,7,9... is strike duration (I tried real lightning strike durations and its too short this is adjusted for visual effect
}
}
StrikeNow=true; //Trigger to start strike sequence in loop
StrikeStart=millis();//set timer to "zero" now- sequence will start in loop after this function
StrikeCount=0;
}
Code: Select all
if (StrikeNow==true){
if ((millis()-StrikeStart)>=StrikeMaster[(StrikeCount*2)]){//check if time has passed the delay (position 0,2,4,6,8 etc in StrikeMaster)-StrikeCount is indexed up by 1 after each strike so we see positions 0,2,4,6,etc in sequence
byte intensity;
intensity=random(100,256);// this little bit should generate a randomly bright flash
for (byte b=0; b<6; b++){
if (Wchannel[b]==1) analogWrite(PWMports[b],intensity);// set all whites to random intensity
}
delay(StrikeMaster[((StrikeCount*2)+1)]);//index to +1 position in array from 0,2,4, etc to 1,3,5 etc
StrikeCount++;//so that the next time we look at elapsed time were looking at the right array position
if (StrikeCount==(StrikeNumber-1)) StrikeNow=false;
}
}
rufessor wrote:OK-
first thing I need to correct so that if you want to try this... you might be able to.
I suffered much confusion in getting the library written by Michael Rice at swfltek.com to work for me. This was entirely my fault and related to my jumping into a project that was clearly over my head in many ways when I started to code this.
I changed essentially NOTHING, the library is NOT my work... READ the above again if you want to know who to thank and shower with glowing praise for making this possible.
I post the library file that I am using, unaltered from Michael Rice below, I use the following directory and file name.
the relevant call in the header to the program I wrote is
I have added the following in my libraries as a .h file. Now what code should i have to add in my pwm module?
#include <SWFLTEK_EPHERMA.h>
which then looks for this directory /SWFLTEK_EPHERMA in the arduino library directory.
which has the following file in it
SWFLTEK_EPHERMA.h
When I started this thread I thought the library needed to be split into a .cpp file and a .h file, which yielded unending frustration with duplicate variable declaration (or some such error) and only when I simply used a single .h file was I able to get this to work. Don't ask me why, I can only make guesses informed by experience but the compilation structure of Arduino is beyond my grasp. This file works- I would just use it as its in fact even easier than making two files.
here is the entire code pulled from my SWFLTEK_EPHERMA.h file
The licensing for the source code from Michael was posted on the first page of this thread-
Code: Select all
/* SWFLTEK_Ephemera copyright 2010 by Michael Rice Swfltek Ephemera is a set of astronomical functions often of interest. Though written for avr-gcc, it should convert easily to other compilers. Most functions require an unsigned long time stamp as an argument. This 32 bit value represents the number of seconds elapsed since midnight Jan 1 2000 GMT. */ #ifndef SWFLTEK_EPHEMERA_h #define SWFLTEK_EPHEMERA_h // geographic coordinates, in seconds of arc North and East long latitude, longitude; // conveniences to convert two typical representations into seconds of arc long ddToSeconds(float); long dmsToSeconds(int, unsigned char, unsigned char); // return equation of time in seconds int equation_of_time(unsigned long); // adjust stamp to Solar noon void SolarNoon(unsigned long * ); // return solar declination in radians double SolarDeclination(unsigned long dt); // return seconds between sunrise and sunset unsigned long daylightseconds(unsigned long); // compute time of sun rise or sunset char SunRiseSet(unsigned long*, char); // shorthand form char SunRise(unsigned long*); char SunSet(unsigned long*); // convert from GMT to Mean Sidereal Time unsigned long GMSidereal(unsigned long); unsigned long LMSidereal(unsigned long); // approximate phase of the moon char MoonPhase(unsigned long); // season unsigned char season(unsigned long); /* ================================================================================================== SNIP HERE SNIP HERE SNIP HERE SNIP HERE SNIP HERE ================================================================================================== */ #include <stdlib.h> #include <math.h> // arc-seconds per radian #define _sec_rad 206264.806247096370813 // axial tilt of earth at epoch, in radians #define _tilt 0.409092804222329 // tropical year in seconds... rounding error accumulates to 26 seconds by the year 2136 #define _tropical_year 31556925 // 'zenith' of rising (setting) sun in radians (360 - 2 * 90.833 degrees) #define _zenith 3.11250383272322 //#include "SWFLTEK_EPHERMA.h" /*------------------------------------------------------------------------------------------------ convert degrees to seconds of arc */ // decimal degrees long ddToSeconds(float dd){ return dd * 3600.0; } //Degrees, minutes, seconds long dmsToSeconds(int d, unsigned char m, unsigned char s){ long ret; ret = labs((long)d); ret = ret * 3600L + 60L * m + s; ret = (d<0L) ? -ret : ret; return ret; } /* ------------------------------------------------------------------------------------------------ 'Equation of Time' We use the 'short for' equation, which has a theoretical accuracy of about 40 seconds. The returned value is in seconds. */ int equation_of_time(unsigned long dt){ double t; dt -= 192540UL; // refer to Jan 3 2000 05:29 (first periapsis) dt %= _tropical_year; t = dt; t /= _tropical_year; t *= 6.283185307179586; t = -459.27672 * sin(t) + 575.333472 * sin(2.0 * t + 3.588414); return t; } /* 'Solar Noon' adjusts the passed time stamp to the time (GMT) of local solar noon. The accuracy is about 40 seconds (set by the equation of time). */ void SolarNoon(unsigned long * dt){ long r; // Set stamp to noon GMT *dt /= 86400UL; *dt *= 86400UL; *dt += 43200UL; // adjust for equation of time, at noon GMT *dt -= equation_of_time(*dt); // rotate to our longitude r = longitude / 15L; *dt -= r; } /* ----------------------------------------------------------------------------------------------- 'Solar Declination' Returns declination in radians Accurate to within 50 arc-seconds */ double SolarDeclination(unsigned long dt){ double y; dt %= _tropical_year; y = dt; y /= _tropical_year; // fractional year y *= 6.283185307179586; y=0.006918-0.399912*cos(y)+0.070257*sin(y)-0.006758*cos(y*2)+0.000907*sin(y*2)-0.002697*cos(y*3)+0.00148*sin(y*3); return y; } /* ------------------------------------------------------------------------------------------------ Return the period between sunrise and sunset, in seconds. At high latitudes around the time of the solstices, this could be zero, or all day. */ unsigned long daylightseconds(unsigned long dt){ float l, d, e; long n; d = -SolarDeclination(dt); // will be positive in Northern winter l = latitude / _sec_rad; // latitude in radians e += 60.0 * l * tan(l + d); // latitudinal error d = tan(l) * tan(d); // if(d>1.0) return 86400UL; if(d < -1.0) return 0UL; d = acos(d); d /= _zenith; n = 86400UL * d; n += e; return n; } /* ------------------------------------------------------------------------------------------------ Modify the passed time stamp to the time of sunrise (or sunset if 'set' is non-zero). Returns 0 to signal 'normal' completion. If the position is in a polar circle, 1 will be returned if the sun is above the horizon all day, and -1 if the sun is below the horizon all day. */ char SunRiseSet(unsigned long * dt, char set){ unsigned long daylen; daylen = daylightseconds(*dt); if(daylen == 86400UL) return 1; // there is no 'night' today (midnight sun) if(daylen == 0UL) return -1; // there is no 'day' today *dt /= 86400UL; *dt *= 86400UL; *dt += 43200UL; // set the time stamp to 12:00:00 GMT *dt -= daylen / 2; // sunrise at the prime meridian if(set) *dt += daylen; // sunset at the prime meridian *dt -= equation_of_time(*dt); *dt -= longitude / 15.0; // rotate to our own meridian return 0; } // 'short' forms of SunRiseSet char SunRise(unsigned long* when){ return SunRiseSet(when, 0); } char SunSet(unsigned long* when){ return SunRiseSet(when, 1); } /* ------------------------------------------------------------------------------------------------ Mean Sidereal Time. Accurate to within 1 sidereal second. */ unsigned long GMSidereal(unsigned long gmt){ unsigned long long sidereal; sidereal = gmt * 10027379094LL; // multiply by 10 Billion times the ratio sidereal /= 10000000000LL; // and divide by 10 billion sidereal += 23992LL; // add sidereal time at the epoch return sidereal; } unsigned long LMSidereal(unsigned long gmt){ return GMSidereal(gmt ) + longitude / 15L; } /* ------------------------------------------------------------------------------------------------ An approximation of the moons phase. Magnitude of the result approximates the percentage of the moons illuminated surface. Sign of the result indicates a Waxing (+) or Waning (-) moon. It uses the mean lunar cycle, which may differ from the actual by many hours. As such, it may vary from the correct value by 20%. */ char MoonPhase(unsigned long d){ long r; // the first full moon of the epoch was Jan 21 at 04:40 // but the 'mean' full moon was 4 hours later d -= 1759365UL; // subtract time of first full moon d %= 2551443L; // mod by the mean lunar cycle r = d - 1275721L; r /= 12757L; return r; } // Season of the year // 0 = winter, 1 = spring, 2 = summer, 3 = fall unsigned char season(unsigned long dt){ dt += 838800UL;// refer to prior winter solstice dt %= _tropical_year; dt /= 7889400UL; // 91.3125 days if(latitude<0) dt += 2UL; return dt % 4; } /* ================================================================================================== SNIP HERE SNIP HERE SNIP HERE SNIP HERE SNIP HERE ================================================================================================== */ #endif // SWFLTEK_EPHEMERA_h
Code: Select all
//Written by Matthew Hockin.
//Intended for use and distribution to the open source Reef Angel community- but freely available to all.
//Caveat Emptor- buyer beware- no warranties implied or intended :)
//If you want to use this code or pieces of it elsewhere please simply acknowledge the source and author.
//You MUST have installed the SWFLTEK Ephmerma library- this library was written by Michael Rice (swfltek.com)
//its source code and distribution rights are stated in the .h file - Thanks to Michael for doing such a great job
#include <Time.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <avr/wdt.h>
#include <SWFLTEK_EPHERMA.h>
//CHANGE this to the number of seconds your time zone is offset from GMT (WITHOUT REGARD TO DAYLIGHT SAVINGS TIME)
int GMToffset=25200;
//GLOBAL Variables YOU NEED TO CHANGE
boolean ApplyDST=true;//CHANGE THIS IF YOUR NOT USING DST
byte ChMax[]={190,200,190,200,250,0,0,0};//Incremental value (Max-flicker) above flicker you want as max intensity-
byte flicker[]={30,30,30,30,40,0,0,0};//need to input actual values here for flicker point on all channels in PWM expansion box
boolean Wchannel[]={1,0,1,0,0,0,0,0}; //use 1 to designate white channel (i.e. off during storm and used for lightning). Array corresponds to PWM channel 0-5 in order
//Array to give direction to dimming. e.g. DimOrder[]={0,0,1,1,0,0} (cloud chase effects, just group channels you want to dim together during a cloud or storm
//as either a 0 or a 1, i.e. all left side channels are 0 all right are 1 or all front are 0 all back are 1 or whatever
//(which is zero or 1 will change who dims first). set them all to 0 if your tank has no left/right or front/back lights.
byte DimOrder[]={0,0,1,1,0,0,0,0};
//*******************GLOBAL VARIABLE DECLERATIONS*************************************
//Unless your planning on editing the program DO NOT CHANGE ANYTHING HERE
byte TrueIntensity[8];//array used to place hold values calculated by insolation
long elapsedTime;//used multiple places as elapsed since new day.
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long rise;//time in seconds from midnight for sunrise today
unsigned long set;//time in seconds from midnithgt for sunset today
unsigned long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long ChRiseSet[16];//times of rise and set for all 8 channels based upon offsets from calc rise and set values
float ChSlope[16];//slopes for 1/2 day calculations based upon time from offset to midday for channel 1-8
long CloudMaster[20];// Set up array to hold start and end times for clouds for the day- dont size it dynamically make it big enough for any day
long midDay;// exactly 1/2 way between rise and set, i.e. my take on solar noon- which its not...
byte PWMports[] ={
3,5,6,9,10,11};
byte ChannelValue[8];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)
unsigned long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[18];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator (8 strikes=16 positions)
byte StrikeNumber;//place to hold total number of strikes this sequence
boolean StrikeNow;//starts lightning strike sequence in loop state change made in weather/storm loop
byte StrikeCount;//Used to properly sequence strike sequence for delay between strikes
byte cmdnum=255;
byte datanum=255;
byte dow=0;//day of week
boolean trigger; //used a few places as a switch to ensure things only run 1x when needed trigger 2 is for daily reset of sunrise sunset
byte strikePattern, strikeTime;//used in Lightning() for timing of particular instance of strike
boolean isDST;//are we in DST
boolean Cloud;// are we in a cloud interval on days that have clouds
boolean CloudToday;//set in CalcSun if randomization yields a day with clouds.
boolean IsStorm;// are we in a storm
byte CloudsTotal;// how many clouds today
long lastmillis;// variable to track millis to enable cloud and insolation loop restriction by time
boolean CloudAdvance;//cloud timer for light effect advance
boolean StormAdvance;//storm timer for light effect advance
boolean InsolationAdvance;//when true we recalculate light intensity during clear sky
byte counter;//used to track millis advance for insolation,cloud trigger
//****************************************
//END HEADER/Global Variable declaration//
//****************************************
//Setup
void setup(){
Serial.begin(57600);
Wire.begin(8);
//Wire.onReceive(receiveEvent);
//Wire.onRequest(requestEvent);
pinMode(3,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
wdt_enable(WDTO_1S);
randomSeed(analogRead(0));//start random generator at a different point each time (not perfect but whatever its gonna be pretty damn random)
setSyncProvider(RTC.get); // the function to get the time from the RTC
setSyncInterval(SECS_PER_HOUR); // Changed to sync every hour.
isDST=false;//set day light savings time correction to false, is overridden by DST function if you choose to use it
dow=0;//set Day Of Week (dow) to a 0 value which is impossible (day()=1-7)... so we trigger calcSun on restart
StrikeNow=false;//no lightning strike yet
CloudToday=false;//set to no clouds so CalcSun can set correctly if should be true
Cloud=false;//set cloud to false
IsStorm=false;//set storm to false
lastmillis=millis();//start our millis timer now
counter=0;//used for lightning strike count in loop
StrikeCount=0;//Number of lightning strikes in the sequence.. set to zero until initialized in sequence
}
//End Setup
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Loop
void loop(){
wdt_reset();
if (cmdnum!=255){
ProcessCMD(cmdnum,datanum);
cmdnum=255;
datanum=255;
}
if (dow!=day()){ //be aware that during DST times the new day calculation will occur 1 hour early....but its corrected out
trigger=true;//on reset this will also be true as numbering is 1-31 max
//Serial.println("We set trigger to true");
dow=day();//now set dow to actual day numbering so that at midnight/new day we again calc sunrise sunset
}
//Use millis to enable tracking of time interval
//this will ensure at least minimally 1 sec intervals but may be longer if program is slow during a long calculation but at least never shorter.
if ((millis()-lastmillis)>=500){
lastmillis=millis();
counter+=1;
StormAdvance=true;
//now a bunch of stuff that may or may not be true at the same time but that all needs to happen when its true
if (counter==0) InsolationAdvance=true;//so that it runs on start up to provide light immediately
if (counter%2==0) CloudAdvance=true;//not currently using, may delete
if (counter%6==0) InsolationAdvance=true;
if (counter==251) counter=0;
}
// now run through rise/set calculation and then intensity set and finally weather overlay when required
if (trigger==true) CalSun();
if (InsolationAdvance==true) Insolation();
Weather();//due to variable storm vs cloud update timing this must be run from loop without condition
//check to see if were need to have a lightning strike
if (StrikeNow==true){
if ((millis()-StrikeStart)>=StrikeMaster[(StrikeCount*2)]){//check if time has passed the delay (position 0,2,4,6,8 etc in StrikeMaster)-StrikeCount is indexed up by 1 after each strike so we see positions 0,2,4,6,etc in sequence
byte intensity;
intensity=random(150,256);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
for (byte b=0; b<6; b++){
//if (Wchannel[b]==1)
analogWrite(PWMports[b],intensity);// set all whites to whatever random intensity was generated
}
delay(StrikeMaster[((StrikeCount*2)+1)]);//index to +1 position in array from 0,2,4, etc to 1,3,5 etc
StrikeCount++;//so that the next time we look at elapsed time were looking at the right array position
if (StrikeCount==(StrikeNumber-1)) StrikeNow=false;
}
}
//track time in seconds-dont move this part up in the loop it really should stay below the rest as DST is not calculated until CalSun is run 1 time.
elapsedTime=((now()-946684800)-newDay);//this is uncorrected for DST, the ONLY thing that is, since its all relative... is the actual sunrise and set seconds
//Now that we have generated our sun pattern, Weather as clouds, and possibly a storm, and possibly lightning each of which override the prior channel setting
//lets actually make some light.
for (byte a=0;a<6;a++){
analogWrite(PWMports[a],TrueIntensity[a]);//dont change this to 8 to refelct array for channels.. we only have 6 here!
}
}
//End Loop
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
void receiveEvent(int howMany) {
wdt_reset();
if (howMany==5){
byte cmd1, cmd2, cmd3, cmd4, cmd5;
cmd1=Wire.read();
cmd2=Wire.read();
cmd3=Wire.read();
cmd4=Wire.read();
cmd5=Wire.read();
if (cmd1=='$' && cmd2=='$' && cmd3=='$'){
cmdnum=cmd4;
datanum=cmd5;
//Serial.println(cmd4,DEC);
//Serial.println(cmd5,DEC);
}
}
else{
for (int a=0;a<howMany;a++){
Wire.read();
}
}
}
void ProcessCMD(byte cmd, byte data){
wdt_reset();
}
//End Standard Functions
//*********************************************************************************************************************************
// Function to run Epherma Calc for rise/set/noon/moon phase
void CalcDST(byte D, byte M, byte dow)
{
//January, february, and december are out.
if (M < 3 || M > 11){
isDST=false;
}
//April to October are in
else if (M > 4 && M < 11){
isDST=true;
}
else{
int previousSunday=(D-dow); // Sunday=1 Sat=7
if (M==3 && previousSunday>7){
isDST=true;
}
else if (M==11 && previousSunday<=0){
isDST=false;
}
}
}
void CalSun()
{
//Serial.println("CalSun Run Now");
trigger=false;
// Every day at midnight, we calculate rise, set, time of highest sun, moon phase, or we do this if system has reset
if (ApplyDST==true){
CalcDST(day(),month(),weekday());
}
//Calculate Latitude and Longitude converting from Degree, Minute, Seconds to decimal
latitude=dmsToSeconds(40,44,11); //Set to about the latitude of Salt Lake City
longitude=dmsToSeconds(-111,48,32); //Set to Salt lake City, or MST zone, USA or there abouts- actually the AT921 remote weather station in Salt Lake City UT. Random web grab.
unsigned long SecInput;//intermediate variable to set rise and set input times
unsigned long hours;//avoid casting and use UL
unsigned long minutes;//avoid casting and use UL
time_t t=now();
hours=hour(t);
minutes=minute(t);
//Serial.println("Hours:Mins");
//Serial.println(hours);
//Serial.println(minutes);
//Serial.print("CalSun now()=");
//Serial.println(now());
//Using time library from Arduino.time_t now(); returns seconds since 1970 in local time with NO DST correction
//Arduino Time.h Library states #seconds to subtract to bring to Jan 1 2000 as origin for time is
//#define SECS_YR_2000 (946684800) the time at the start of y2k
//Seems weird, but actually DO NOT correct new Day for DST
hours=(hours*3600);//seconds conversion saving a variable
minutes=(minutes*60);//seconds conversion saving a variable
newDay=(now()-(946684800+hours+minutes));//local time uncorrected for DST- hours and minutes were just converted to seconds in prior step
SecInput=(newDay+GMToffset);
rise=SecInput;// Dont screw with DST in sunrise sunset input its NOT part of GMT
set=SecInput;//were not converted yet- library uses POINTERS.. to modify these values
//Calculate rise time and set time using Epherma Library
SunRise(&rise);//call to Epherma Library using reference- memory is modified by Epherma
SunSet(&set);//Call to Epherma library using reference- memory position is modified by Epherma
if (isDST==true){//must correct DST NOW by subtracting 1 hour from the offset (i.e. clocks are 1 hr ahead and thus closer to GMT)
rise=(rise-(GMToffset-3600));//during DST rise is 1 hour earlier than calculated i.e. clocks ahead 1 hour 6 am occurs "at 5am" if that makes sense
set=(set-(GMToffset-3600));
}
else if (isDST==false){
rise=(rise-GMToffset);
set=(set-GMToffset);
}
//Serial.print("newDay=");
//Serial.println(newDay);
rise=(rise-newDay);//set to elapsed seconds today
set=(set-newDay);
/*Serial.println("Local Elapsed Seconds from NewDay--rise=");
Serial.println(rise);
Serial.println("Local Elapsed Seconds from NewDay--set=");
Serial.println(set);*/
//DONT MOVE THIS LINE ABOVE FINAL rise and set decleration it will SCREW up your rise and set during DST by 1 hour
if (isDST==true) newDay-=3600;//subtract an hour from the time that will be subtracted from GMT i.e. were one hour closer during DST
//*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
//channels 0-5 are PWM expansion board lights 6,7 are ReefAngel Controller PWM outputs
//offsets for rise/set all values in seconds offset from calculated rise or set value (-) am offset=longer day****** (-)pm offset=shorter day)
//array order is ch0 am offset, pm offset, ch1 am offset, pm offset etc..
//THESE values are the number of seconds that a particular channel will be offset from the rise/set time, i.e. negative to rise earlier/set earlier
int Choffset[]={
-600,0,-4200,5400,0,600,-3600,6000,-4500,7200,0,0,0,0,0,0};
//**********************ok now were done changing things here********************************************
//Calculate rise and set times for all channels in equivlants to elapsed seconds from midnight today
//populate array for chRise and Set as well as chSlope for 0.5pi/ half day lenght for each channel from midday (asymmetric days are allowed)
float deltaY=1.570796327;//1/2 * pi as integer by scaling* 10^9 to fill UL
midDay=(((set-rise)/2)+rise);
long HalfDayLength=((set-rise)/2);
//Serial.print("MidDay");
//Serial.println(midDay);
for (byte b=0;b<16;b++){//working as of April 5 2012 serial tested
if (b%2==0){
ChRiseSet[b]=rise+(Choffset[b]);
ChSlope[b]=(deltaY/(float)(HalfDayLength-(Choffset[b])));
//Serial.print("Rise/Set a=");
//Serial.println(b);
//Serial.print("Value=");
//Serial.print(ChRiseSet[b]);
//Serial.print("Slope");
//Serial.println(ChSlope[b], 12);
}
else if (b%2==1){
ChRiseSet[b]=set+(Choffset[b]);
ChSlope[b]=(deltaY/(float)(HalfDayLength+(Choffset[b])));
//Serial.print("Rise/Set a=");
//Serial.println(b);
//Serial.print("Value=");
//Serial.print(ChRiseSet[b]);
//Serial.print("Slope");
//Serial.println(ChSlope[b], 12);
}
}
//***************** to CHANGE THE chance of Clouds actually occuring on a given day************************
byte CloudChance=100;//% Chance of a Cloud every day
//****************************now were done- did you use a value from 0-100 without a decimal?****************
//once a day, after rise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
byte RainMaker=random(1,91);
if (RainMaker<=CloudChance){
CloudToday=true;//used to trigger weather function, can also be used to send flag to controller
}
else if (RainMaker>CloudChance){
CloudToday=false;//see above comment on CloudToday
//Serial.print("no cloud today");
return;
}
// to "simplify" things and avoid redundant calculation loops all cloud times are in fraction of day lengths
//*********** this is only executed 1x /day unless power outage and ONLY if last statement is true****************
///ALl cloud set up is done here... weather subroutine just implements it
/*The general strategy for this algorithim is as follows. Calculate random cloud cover as percent of day,
then randomly generate the # of discreet cloud instances for the day,
then determine cloud length by (daylight seconds * percent overcast)/#clouds
then randomize cloud lengths by splitting into groups of twos correcting for if we have odd # of clouds today*/
long dayLength=(set-rise);
//Serial.println("DayLength");
//Serial.println(dayLength);
// number of clouds possible for the day, max and min
byte CloudsMax=11;//DONT INCREASE BEYOND 11 or it will DIE, or increase array size to handle it
byte CloudsMin=5;//dont use 1 it may or may not work in the next loops and the cloud will likely be very long, i.e. daylength*overcast fraction
CloudsTotal=random(CloudsMin,(CloudsMax+1));
//Serial.println("CloudsTotal");
//Serial.println(CloudsTotal);
// Average day is 50,000 secs so if 4 clouds and 10% that gets you 5,000 seconds of clouds (about 1800 seconds length for each of the 4 clouds in independent segments (if 4 is # clouds)
byte OvercastMin=((CloudsTotal*10)/5);//Min cloud length will be about 1000 seconds (15 mins)- 1 hour min of clouds if you have 4, 2 hours if you have 8
byte OvercastMax=((CloudsTotal*10)/2);//max cloud length will be about 2500 seconds (45 mins)- 6 hours max of clouds if you have 8, 3 hours max if you have 4
float Overcast=random(OvercastMin,OvercastMax);
Overcast=(Overcast/100);
//Serial.println("Overcast");
//Serial.println(Overcast);
// set up start time for clouds
//The way this is done is to split the total lenght of time for clouds into equal segments and then to randomly chop or add time to the
//segments such that cloud length is variable. Then distribute into random parts of the day and fill array with start,duration pairs for clouds
int CloudLength;
CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
long SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));//average sun length between clouds
float CloudFraction=0;
float SunFraction=0;
/*Serial.print("CloudLength=");
Serial.println(CloudLength);
Serial.print("SunSegment=");
Serial.println(SunSegment);*/
//start by zero filling CloudMaster array
for (byte a=0; a<20; a++){
CloudMaster[a]=0;
}
byte b=0;//used to get pairs of fraction for SunFraction in for loop
byte c=0;//used to get pairs of fraction for CloudFraction in for loop
//now randomize cloud length and sunsegment length as pairs to get different looking days-
for (byte a=0; a<(CloudsTotal*2); a++){
if ((CloudsTotal%2!=0) && (a==((CloudsTotal*2)-1))){//if were at the last cloud in an odd cloud day make it full length
CloudMaster[a]=CloudLength;
}
else if (a%2==0){
if (b==0){
SunFraction=random(20,181);//vary each pair of SunSegments from 20%-180% of possible length such that every pair =2*SunSegment in length
SunFraction=(SunFraction/100);
CloudMaster[a]=(SunFraction*SunSegment);
b++;
}
else if (b==1){
SunFraction=(2-SunFraction);//were on the second part of a pair
CloudMaster[a]=(SunFraction*SunSegment);
b=0;//reset so next time we start a new fraction
}
}
else if (a%2==1){//if were in odd positions we need to determine cloud lengths in random pairs such that each pair =2*CloudLength in length
if (c==0){
CloudFraction=random(20,181);//vary each pair of SunSegments from 20%-180% of possible length such that every pair =2*SunSegment in length
CloudFraction=(CloudFraction/100);
CloudMaster[a]=(CloudFraction*CloudLength);
c++;
}
else if (c==1){
CloudFraction=(2-CloudFraction);
CloudMaster[a]=(CloudFraction*CloudLength);
c=0;//reset so next loop finds a new fraction
}
}
}
//serial debugging to examine array
/*Serial.println("here is cloud master in is entirety prior to forming start and end pairs");
for (byte a=0;a<20;a++){
Serial.println(CloudMaster[a]);
}*/
// now we have CloudMaster constructed as SunSegment,CloudLength,SunSegment,CloudLength....
//reframe it to generate cloud start, cloud end, cloud start, cloud end
for (byte a=0; a<(CloudsTotal*2); a++){
if (a==0){// if were starting our first cloud we need to add to rise value to first sun segment
CloudMaster[a]=rise+CloudMaster[a];
}
else {
CloudMaster[a]=(CloudMaster[a-1]+CloudMaster[a]);//just add prior values together e.g. (second position is cloud end so to find end add rise corrected start time with duration)
// subsequent start would be end of 1st cloud + next sunsegment fraction
}
}
//serial debugging to examine array
/*Serial.println("here is cloud master in is entirety as start and end pairs");
for (byte a=0;a<20;a++){
Serial.println(CloudMaster[a]);
}*/
}//END SunCalc FUNCTION
void Insolation()
{
InsolationAdvance=false;//reset this flag now that we have entered function
/*Serial.println("Insolation 3 sec elapsed");
Serial.print("Elapsed Time is=");
Serial.println(elapsedTime);*/
//define Pi as delta Y for slope since cos 0.5-1.5 Pi goes 0-1-0 in 0.5 pI increments slope of 1/2 day (0-1 intensity) delta Y is 1/2 Pi
float Pi=3.1415926;//scale to 10^8
float PiHalf=1.5707963;//scale to 10^8
float secSoFar;//variable to account for seconds elapsed for each channel 1/2 day period from rise-->midDay and midDay-->set
/* using -cos(pi/2+elapsedTime/slope) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light. Turn off lights after set or before rise etc.
by splitting into half days centered on midday (1/2 ofset-rise) we center exactly the cos function for every channel so color blends are maintained
throughout intensity ramp... more or less ... change intensity every 120 seconds throughout the day*/
if (elapsedTime<=midDay){
//Serial.println("in first half of day");
for (byte b=0;b<16;b=(b+2)){
if (elapsedTime>=ChRiseSet[b]){
secSoFar=(elapsedTime-ChRiseSet[b]);//just account for length of every channel 1/2 day and switch at midDay
switch (b){
case 0:
ChannelValue[0]=flicker[b]+ChMax[b]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println((float)ChMax[b],4);
//Serial.println(b);
//Serial.println(ChannelValue[0]);
//Serial.println((float)ChMax[b],4);
break;
case 2:
ChannelValue[1]=flicker[(b-1)]+ChMax[(b-1)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[1]);
break;
case 4:
ChannelValue[2]=flicker[(b-2)]+ChMax[(b-2)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[2]);
break;
case 6:
ChannelValue[3]=flicker[(b-3)]+ChMax[(b-3)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[3]);
break;
case 8:
ChannelValue[4]=flicker[(b-4)]+ChMax[(b-4)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[4]);
break;
case 10:
ChannelValue[5]=flicker[(b-5)]+ChMax[(b-5)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[5]);
break;
case 12:
ChannelValue[6]=flicker[(b-6)]+ChMax[(b-6)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[6]);
break;
case 14:
ChannelValue[7]=flicker[(b-7)]+ChMax[(b-7)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[7]);
break;
}
}
else if (elapsedTime<ChRiseSet[b]){
switch (b){
case 0:
ChannelValue[0]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch0 dark");
break;
case 2:
ChannelValue[1]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch1 dark");
break;
case 4:
ChannelValue[2]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch2 dark");
break;
case 6:
ChannelValue[3]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch3 dark");
break;
case 8:
ChannelValue[4]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch4 dark");
break;
case 10:
ChannelValue[5]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch5 dark");
break;
case 12:
ChannelValue[6]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch6 dark");
break;
case 14:
ChannelValue[7]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch7 dark");
break;
}
}
}
}
else if (elapsedTime>midDay){
//Serial.println("were in the second half of the day");
for (byte b=1;b<12;b=b+2){
if (elapsedTime<=ChRiseSet[b]){
secSoFar=(elapsedTime-midDay);
switch (b){
case 1:
ChannelValue[0]=flicker[(b-1)]+ChMax[(b-1)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[0]);
break;
case 3:
ChannelValue[1]=flicker[(b-2)]+ChMax[(b-2)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[1]);
break;
case 5:
ChannelValue[2]=flicker[(b-3)]+ChMax[(b-3)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[2]);
break;
case 7:
ChannelValue[3]=flicker[(b-4)]+ChMax[(b-4)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[3]);
break;
case 9:
ChannelValue[4]=flicker[(b-5)]+ChMax[(b-5)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[4]);
break;
case 11:
ChannelValue[5]=flicker[(b-6)]+ChMax[(b-6)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[5]);
break;
case 13:
ChannelValue[6]=flicker[(b-7)]+ChMax[(b-7)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[6]);
break;
case 15:
ChannelValue[7]=flicker[(b-8)]+ChMax[(b-8)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[7]);
break;
}
}
else if (elapsedTime>ChRiseSet[b]){
switch (b){
case 1:
ChannelValue[0]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch0 dark");
break;
case 3:
ChannelValue[1]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch1 dark");
break;
case 5:
ChannelValue[2]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch2 dark");
break;
case 7:
ChannelValue[3]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch3 dark");
break;
case 9:
ChannelValue[4]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch4 dark");
break;
case 11:
ChannelValue[5]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch5 dark");
break;
case 13:
ChannelValue[6]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch6 dark");
break;
case 15:
ChannelValue[7]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch7 dark");
break;
}
}
}
}
/*Serial.println("Insolation settings=");
Serial.println(ChannelValue[0]);
Serial.println(ChannelValue[1]);
Serial.println(ChannelValue[2]);
Serial.println(ChannelValue[3]);
Serial.println(ChannelValue[4]);
Serial.println(ChannelValue[5]);
Serial.println(ChannelValue[6]);
Serial.println(ChannelValue[7]);
Serial.println("ChMax Settings");
Serial.println(ChMax[0]);
Serial.println(ChMax[1]);
Serial.println(ChMax[2]);
Serial.println(ChMax[3]);
Serial.println(ChMax[4]);
Serial.println(ChMax[5]);
Serial.println(ChMax[6]);
Serial.println(ChMax[7]);*/
}//END function
//This is where modification of insolation by clouds or a storm occurs
//Its worth noting that the clouds simply modify (depending on how much cloud cover there is) the existing light set by insolation as a fractional value
void Weather ()
{
static float CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static float PriorCloudCover; //used to "delay" one side of the tank from the other in cloud passing effects
static long StormStart;
static long StormEnd;
static long CloudEnd;
static boolean wtrigger;//use to track the first run of functions to calculate random times that become fixed, see change from cloud to storm for useage
int LongestStorm;//used to pass max possible time to storm if loop from cloud loop within weather function
//check to see if were having a scheduled cloud
if (Cloud==false){
for (byte a=0; a<(CloudsTotal*2); a=(a+2)){//if its time for a cloud, run it
if ((elapsedTime>=CloudMaster[a]) && (elapsedTime<=CloudMaster[(a+1)])) {
CloudEnd=CloudMaster[(a+1)];//to avoid this loop running true during the compute cycles at the end of the cloud and before elapsedTime advances a second, actual cloud does not
Cloud=true;//this prevents the loop for running, and is not reset until we trully reach the end of a cloud
CloudCover=0;
PriorCloudCover=0;
break;//were starting this cloud so stop looking for another one
}
}
//just write ChannelVales to TrueIntensity without modification so that cloud/storm can later modify them
for (byte a=0; a<8; a++){
TrueIntensity[a]=ChannelValue[a];//this is where intensity is set for the PWM channel analog write in the loop... don't mess with this.
}
}
else if ((Cloud==true) && (IsStorm==false)){
if (StormAdvance==false){//use millis tracker to run this loop every 2 seconds
return;
}
StormAdvance=false;//reset to false when true so we run this once, until time advance is true again
//Serial.println("in a cloud doing stuff");
if (elapsedTime>=CloudEnd){
ClearSky(CloudCover, CloudEnd);
return;
}
/*Use fractional intensity to set minimum value for any channel. Dimming is proportional to actual intensity output
and constrained by flicker point. Random walk uses static variable "CloudCover" constrained to 0-100 to represent fractional intensity (i.e. (1-(CloudCover/100))*Insolation SetPoint
is how the current cloud intensity is set, i.e. cloud cover of 90 gives 10% insolation setpoint unless below flicker in which case = flicker*/
int stepsize;
stepsize=random(-16,16);// in Percent% (0-100) This is how much light intensity can change every 1 or 2 seconds (Whatever CloudAdvance is in this case);
//Serial.print("Stepsize=");
//Serial.println(stepsize);
PriorCloudCover=CloudCover;//this way its going to change every 1 second but the
//Serial.print("PriorCloudCover=");
//Serial.println(PriorCloudCover);
CloudCover=(CloudCover+stepsize);//Now add the % increment or decremement to intensity
//Serial.print("CloudCover=");
//Serial.println(CloudCover);
if (CloudCover>=100){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms increase stepsize boundry or change this value
CloudCover-=(stepsize*1.5);
//Serial.print("CLoudCover was over 100 and got corrected to=");
//Serial.println(CloudCover);
//Serial.print("we would have started a storm");
}
else if (CloudCover<=0){//i.e if were bright sky in a cloud.. uhh.. we need a cloud
//Serial.print("Cloud cover was negative so I fixed it and its now=");
CloudCover-=(stepsize*2);//reflect to less positive value, i.e. we had to be adding a negative so subtract it instead (i.e. add a positive)
//Serial.println(CloudCover);
}
if (CloudCover>98){
if ((CloudEnd-elapsedTime)>=300){//if storm can last 5 minutes before cloud would end anyways.. do it
IsStorm=true;
wtrigger=true;
// Serial.print("we started a storm");
LongestStorm=(CloudEnd-elapsedTime);
}
}
for (int a=0;a<8;a++){
if (DimOrder[a]==0){
//Serial.print("Initial Channel setting is= ");
//Serial.println(a);
//Serial.println(FullSun);
TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-((CloudCover/100)))));//max intensity is 65% of initial for better visual dimming performance
//Serial.print("DimOrder 0 setting is");
//Serial.println(TrueIntensity[a]);
}
else if (DimOrder[a]==1){
//Serial.print("Initial Channel setting is= ");
//Serial.println(a);
//Serial.println(FullSun);
TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-((PriorCloudCover/100)))));//max intensity is 65% of initial for better visual dimming performance
//Serial.print("DimOrder 1 setting is");
//Serial.println(TrueIntensity[a]);
}
}
}
//enable a flag sent from controller to triger a storm, i.e. IsStorm=true
// set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
else if (((Cloud==true) && (IsStorm==true)) || ((Cloud==false) && (IsStorm==true))){
//current else statement covers possibility of triggering storm from controller (i.e. not coming out of a cloud) but remember you need to flag wtrigger as TRUE when you do this
//do this next part exactly once per storm then loop past
if (wtrigger==true){
StormStart=elapsedTime;
LongestStorm-=120;//remove 2 mins from longest storm so that we end up with 2 minutes of cloud after the storm before the sky clears to daylight
//*****************To CHANGE THE LENGTH OF THE LONGEST POSSIBLE STORM DURING A CLOUD read next line comments****************************
if (LongestStorm>420) LongestStorm=420;//YOU CAN CHANGE THE value in the comparison and the define to be whatever you want your max lenght to be
////*************************EDIT ABOVE***************************************************************************************************
int StormDuration=random((LongestStorm/4),(LongestStorm+1));//set (min,max) seconds any single Storm can exist currently 1/4 length to full length remaining cloudtime
StormEnd=(StormStart+StormDuration);
wtrigger=false;
}
if (StormAdvance==false){//Every 1 second duing a storm change intensity, clouds are movin fast baby
return;
}
StormAdvance=false;//reset so we run again in 1 second.
//Serial.print("in a storm now");
if (elapsedTime>=StormEnd){ //if were done with the storm we need to stop this loop, but were probably still cloudy so dont mess with that here
IsStorm=false;
return;
}
int stepsize;
stepsize=random(-20,21);
PriorCloudCover=CloudCover;
CloudCover=(CloudCover+stepsize);
if (StrikeNow==false){
if ((stepsize<(-19)) || (stepsize>19)){ //it is convient to use this random call to generate a strike during a storm for more frequent strikes adjust down
StrikeNumber=(random(2,10)); //random high =x-1 so max strike =9 each strike requires a duration and a delay thus StrikeMaster is 18 positions
//ensure the array is zeroed out past the last position required for this strike pattern so each pattern is only as long as generated
//Array is in pairs, position in arry of (0,1) (2,3) etc as strike (delay,duration)
for (byte a=0;a<18;a++){
if (a>=(StrikeNumber*2)){
StrikeMaster[a]=0;
}
if (a%2==0){
if (a==0){
StrikeMaster[a]=random(300,1601);
}
else {
StrikeMaster[a]=(StrikeMaster[(a-2)]+random(175,1601));//position 0,2,4,6,8.. is strike delay
}
}
else if(a%2!=0){
StrikeMaster[a]=random(45,100);//position 1,3,5,7,9... is strike duration (I tried real lightning strike durations and its too short this is adjusted for visual effect
}
}
StrikeNow=true; //Trigger to start strike sequence in loop
StrikeStart=millis();//set timer to "zero" now- sequence will start in loop after this function
StrikeCount=0;
}
}
if (CloudCover>=100){//if were too dark reflect random path to light
CloudCover-=(stepsize*1.5);
}
else if (CloudCover<=0){
CloudCover-=(2.5*stepsize);//if were 100% intensity of light in a storm reflect random path to less intensity
}
for (int a=0;a<8;a++) {
if (Wchannel[a]==1){//if were white we need to be off in a storm
TrueIntensity[a]=0;
}
else if (Wchannel[a]==0){//if were blue, we chase as for a cloud
if (DimOrder[a]==0){
//Serial.print("In a storm and DimOrder0 channel values were");
//Serial.println(ChannelValue[a]);
//Serial.println(a);
TrueIntensity[a]=(flicker[a]+(((float)((ChannelValue[a]-flicker[a]))*0.75)*(1-((CloudCover/100)))));
//Serial.print("and now the channel is set to");
//Serial.println(TrueIntensity[a]);
}
else if (DimOrder[a]==1){
//Serial.print("In a storm and DimOrder1 channel values were");
//Serial.println(ChannelValue[a]);
//Serial.println(a);
TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a]))*0.75)*(1-((PriorCloudCover/100)))));
//Serial.print("and now the channel is set to");
//Serial.println(TrueIntensity[a]);
}
}
}
}//end of storm if loop
}//End Weather function
//THE LAST 2 mins of ANY Storm, or cloud, are used to ramp linearlly to full daylight setting, whatever that is at the given time of day
void ClearSky(float CloudCover, int CloudEnd)
{
// no need to restrict time to avoid resource hogging, its only called every x seconds from weather.
//basically, add 120 seconds to every cloud and use it to bring light up to full
int elapsed=(elapsedTime-CloudEnd);//counts up from zero forever... but we stop it by 120 to end cloud
//Since CloudCover is amount taken away as decimal fraction of InsolationSetting (i.e. 70 produces light at 30% of insolation setpoint... we need to come up 70 to get back.
float slope=(CloudCover/120);//cloud cover goes from 0 (Clear) to 90 (storm) in a cloud, since storms are forced to end before the cloud we know this is the limit
if (elapsed<=120){//at this point lights are back full on so cancel the cloud and start waiting for the next one
Cloud=false;
return;
}
byte LightAdvance;
LightAdvance=(CloudCover-(slope*elapsed));//were reducing CloudCover from start to zero over 120 seconds seconds...
for (byte a=0; a<8; a++){
TrueIntensity[a]=(byte)(((ChannelValue[a]-flicker[a])*(float)(1-((float)(LightAdvance/100))))+flicker[a]);
}
}//End Clear Sky function
Code: Select all
case 10:
ChannelValue[5]=0;
//ChannelValue[0]=MoonPhase()
//Serial.println("Ch5 dark");
break;
Code: Select all
case 10:
ChannelValue[5]=0;
ChannelValue[5]=MoonPhase()
//Serial.println("Ch5 dark");
break;
Code: Select all
//By Matthew Hockin 2012.
//SWFLTEK library functions, #includes, and #define were copied directly from the Epherma library
//Epherma (SWFLTEK.com) was written by Michael Rice- thanks Michael it works great.
//If you copy from this (its all open source) please
//acknowledge Michael for SWFLTEK library code (obviously labeled) or Matthew Hockin (for the rest).
//You MUST have installed the SWFLTEK Ephmerma library- this library was written by Michael Rice (swfltek.com)
//its source code and distribution rights are stated in the .h file - Thanks to Michael for doing such a great job
#include <Time.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <avr/wdt.h>
//includes for SWFLTEK functions
#include <stdlib.h>
#include <math.h>
//***********************************ARRAYS YOU MUST MODIFY TO MAKE YOUR TANK SET UP WORK*********************
//CHANGE this to the number of seconds your time zone is offset from GMT (WITHOUT REGARD TO DAYLIGHT SAVINGS TIME)
int GMToffset=25200;//USA MST time zone is 25,200 if that helps you
//GLOBAL Variables YOU NEED TO CHANGE
boolean ApplyDST=true;//CHANGE THIS IF YOUR NOT USING DST
byte ChMax[]={200,210,200,210,225,0,0,0};//Incremental value (Max-flicker) above flicker you want as max intensity-
byte flicker[]={30,28,30,28,25,0,0,0};//need to input actual values here for flicker point on all channels in PWM expansion box
boolean Wchannel[]={1,0,1,0,0,0,0,0}; //use 1 to designate white channel (i.e. off during storm and used for lightning). Array corresponds to PWM channel 0-5 in order
//Array to give direction to dimming. e.g. DimOrder[]={0,0,1,1,0,0} (cloud chase effects, just group channels you want to dim together during a cloud or storm
//as either a 0 or a 1, i.e. all left side channels are 0 all right are 1 or all front are 0 all back are 1 or whatever
//(which is zero or 1 will change who dims first). set them all to 0 if your tank has no left/right or front/back lights.
byte DimOrder[]={0,0,1,1,1,0,0,0};
//**********************************DONE CHANGING THINGS HERE BUT YOU MUST CHANGE ChOffset array IN CalcSUN function******
//defines for SWFLTEK functions
// arc-seconds per radian
#define _sec_rad 206264.806247096370813
// axial tilt of earth at epoch, in radians
#define _tilt 0.409092804222329
// tropical year in seconds... rounding error accumulates to 26 seconds by the year 2136
#define _tropical_year 31556925
// 'zenith' of rising (setting) sun in radians (360 - 2 * 90.833 degrees)
#define _zenith 3.11250383272322
//*******************GLOBAL VARIABLE DECLERATIONS FOR MHOCKIN Weather package*************************************
//Unless your planning on editing the program DO NOT CHANGE ANYTHING HERE
long latitude, longitude;
byte TrueIntensity[8];//array used to place hold values calculated by insolation
long elapsedTime;//used multiple places as elapsed since new day.
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long rise;//time in seconds from midnight for sunrise today
unsigned long set;//time in seconds from midnithgt for sunset today
unsigned long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long ChRiseSet[16];//times of rise and set for all 8 channels based upon offsets from calc rise and set values
float ChSlope[16];//slopes for 1/2 day calculations based upon time from offset to midday for channel 1-8
long CloudMaster[20];// Set up array to hold start and end times for clouds for the day- dont size it dynamically make it big enough for any day
long midDay;// exactly 1/2 way between rise and set, i.e. my take on solar noon- which its not...
byte PWMports[] ={
3,5,6,9,10,11};
byte ChannelValue[8];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)
unsigned long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[20];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator (8 strikes=16 positions)
byte StrikeNumber;//place to hold total number of strikes this sequence
boolean StrikeNow;//starts lightning strike sequence in loop state change made in weather/storm loop
byte StrikeCount;//Used to properly sequence strike sequence for delay between strikes
byte cmdnum=255;
byte datanum=255;
byte dow=0;//day of week
boolean trigger; //used a few places as a switch to ensure things only run 1x when needed trigger 2 is for daily reset of sunrise sunset
byte strikePattern, strikeTime;//used in Lightning() for timing of particular instance of strike
boolean isDST;//are we in DST
boolean Cloud;// are we in a cloud interval on days that have clouds
boolean CloudToday;//set in CalcSun if randomization yields a day with clouds.
boolean IsStorm;// are we in a storm
byte CloudsTotal;// how many clouds today
long lastmillis;// variable to track millis to enable cloud and insolation loop restriction by time
boolean StormAdvance;//storm timer for light effect advance
boolean InsolationAdvance;//when true we recalculate light intensity during clear sky
byte counter;//used to track millis advance for insolation,cloud trigger
//****************************************
//END HEADER/Global Variable declaration//
//****************************************
//Setup
void setup(){
Serial.begin(57600);
Wire.begin(8);
//Wire.onReceive(receiveEvent);
//Wire.onRequest(requestEvent);
pinMode(3,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
wdt_enable(WDTO_1S);
randomSeed(analogRead(0));//start random generator at a different point each time (not perfect but whatever its gonna be pretty damn random)
setSyncProvider(RTC.get); // the function to get the time from the RTC
setSyncInterval(SECS_PER_HOUR); // Changed to sync every hour.
isDST=false;//set day light savings time correction to false, is overridden by DST function if you choose to use it
dow=0;//set Day Of Week (dow) to a 0 value which is impossible (day()=1-7)... so we trigger calcSun on restart
StrikeNow=false;//no lightning strike yet
CloudToday=false;//set to no clouds so CalcSun can set correctly if should be true
Cloud=false;//set cloud to false
IsStorm=false;//set storm to false
lastmillis=millis();//start our millis timer now
counter=0;//used in weather for triggering a storm, triggering lightning in a storm.
StrikeCount=0;//Number of lightning strikes in the sequence.. set to zero until initialized in sequence
}
//End Setup
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Loop
void loop(){
wdt_reset();
if (cmdnum!=255){
ProcessCMD(cmdnum,datanum);
cmdnum=255;
datanum=255;
}
if (dow!=day()){ //be aware that during DST times the new day calculation will occur 1 hour early....but its corrected out
trigger=true;//on reset this will also be true as numbering is 1-31 max
//Serial.println("We set trigger to true");
dow=day();//now set dow to actual day numbering so that at midnight/new day we again calc sunrise sunset
}
//Use millis to enable tracking of time interval
//this will ensure at least minimally 1 sec intervals but may be longer if program is slow during a long calculation but at least never shorter.
if ((millis()-lastmillis)>=300){
lastmillis=millis();
counter+=1;
StormAdvance=true;
//now a bunch of stuff that may or may not be true at the same time but that all needs to happen when its true
if (counter==0) InsolationAdvance=true;//so that it runs on start up to provide light immediately
if (counter%10==0){
InsolationAdvance=true;
//Serial.println (elapsedTime);
}
if (counter==230) counter=0;
}
// now run through rise/set calculation and then intensity set and finally weather overlay when required
if (trigger==true) CalSun();
if (InsolationAdvance==true) Insolation();
Weather();//due to variable storm vs cloud update timing this must be run from loop without condition
//check to see if were need to have a lightning strike
if (StrikeNow==true){
if ((millis()-StrikeStart)>=StrikeMaster[(StrikeCount*2)]){//check if time has passed the delay (position 0,2,4,6,8 etc in StrikeMaster)-StrikeCount is indexed up by 1 after each strike so we see positions 0,2,4,6,etc in sequence
byte intensity;
intensity=random(150,256);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
for (byte b=0; b<6; b++){
if (Wchannel[b]==1) analogWrite(PWMports[b],intensity);// set all whites to whatever random intensity was generated
}
delay(StrikeMaster[((StrikeCount*2)+1)]);//index to +1 position in array from 0,2,4, etc to 1,3,5 etc
StrikeCount++;//so that the next time we look at elapsed time were looking at the right array position
if (StrikeCount==(StrikeNumber-1)) StrikeNow=false;
}
}
//track time in seconds-dont move this part up in the loop it really should stay below the rest as DST is not calculated until CalSun is run 1 time.
elapsedTime=((now()-946684800)-newDay);//this is uncorrected for DST, the ONLY thing that is, since its all relative... is the actual sunrise and set seconds
//Now that we have generated our sun pattern, Weather as clouds, and possibly a storm, and possibly lightning each of which override the prior channel setting
//lets actually make some light.
for (byte a=0;a<6;a++){
analogWrite(PWMports[a],TrueIntensity[a]);//dont change this to 8 to refelct array for channels.. we only have 6 here!
}
}
//End Loop
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
void receiveEvent(int howMany) {
wdt_reset();
if (howMany==5){
byte cmd1, cmd2, cmd3, cmd4, cmd5;
cmd1=Wire.read();
cmd2=Wire.read();
cmd3=Wire.read();
cmd4=Wire.read();
cmd5=Wire.read();
if (cmd1=='$' && cmd2=='$' && cmd3=='$'){
cmdnum=cmd4;
datanum=cmd5;
//Serial.println(cmd4,DEC);
//Serial.println(cmd5,DEC);
}
}
else{
for (int a=0;a<howMany;a++){
Wire.read();
}
}
}
void ProcessCMD(byte cmd, byte data){
wdt_reset();
}
//End Standard Functions
//*********************************************************************************************************************************
void CalSun(){
//Serial.println("CalSun Run Now");
trigger=false;
// Every day at midnight, we calculate rise, set, time of highest sun, moon phase, or we do this if system has reset
if (ApplyDST==true){
CalcDST(day(),month(),weekday());
}
//Calculate Latitude and Longitude converting from Degree, Minute, Seconds to decimal
latitude=dmsToSeconds(40,44,32); //Set to about the latitude of Salt Lake City
longitude=dmsToSeconds(-111,48,11); //Set to Salt lake City, or MST zone, USA or there abouts- actually the AT921 remote weather station in Salt Lake City UT. Random web grab.
unsigned long SecInput;//intermediate variable to set rise and set input times
unsigned long hours;//avoid casting and use UL
unsigned long minutes;//avoid casting and use UL
time_t t=now();
hours=hour(t);
minutes=minute(t);
//Serial.println("Hours:Mins");
//Serial.println(hours);
//Serial.println(minutes);
//Serial.print("CalSun now()=");
//Serial.println(now());
//Using time library from Arduino.time_t now(); returns seconds since 1970 in local time with NO DST correction
//Arduino Time.h Library states #seconds to subtract to bring to Jan 1 2000 as origin for time is
//#define SECS_YR_2000 (946684800) the time at the start of y2k
//Seems weird, but actually DO NOT correct new Day for DST
hours=(hours*3600);//seconds conversion saving a variable
minutes=(minutes*60);//seconds conversion saving a variable
newDay=(now()-(946684800+hours+minutes));//local time uncorrected for DST- hours and minutes were just converted to seconds in prior step
SecInput=(newDay+GMToffset);
rise=SecInput;// Dont screw with DST in sunrise sunset input its NOT part of GMT
set=SecInput;//were not converted yet- library uses POINTERS.. to modify these values
//Calculate rise time and set time using Epherma Library
SunRise(&rise);//call to Epherma Library using reference- memory is modified by Epherma
SunSet(&set);//Call to Epherma library using reference- memory position is modified by Epherma
if (isDST==true){//must correct DST NOW by subtracting 1 hour from the offset (i.e. clocks are 1 hr ahead and thus closer to GMT)
rise=(rise-(GMToffset-3600));//during DST rise is 1 hour earlier than calculated i.e. clocks ahead 1 hour 6 am occurs "at 5am" if that makes sense
set=(set-(GMToffset-3600));
}
else if (isDST==false){
rise=(rise-GMToffset);
set=(set-GMToffset);
}
//Serial.print("newDay=");
//Serial.println(newDay);
rise=(rise-newDay);//set to elapsed seconds today
set=(set-newDay);
Serial.println("Local Elapsed Seconds from NewDay--rise=");
Serial.println(rise);
Serial.println("Local Elapsed Seconds from NewDay--set=");
Serial.println(set);
//DONT MOVE THIS LINE ABOVE FINAL rise and set decleration it will SCREW up your rise and set during DST by 1 hour
if (isDST==true) newDay-=3600;//subtract an hour from the time that will be subtracted from GMT i.e. were one hour closer during DST
//*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
//channels 0-5 are PWM expansion board lights 6,7 are ReefAngel Controller PWM outputs
//offsets for rise/set all values in seconds offset from calculated rise or set value (-) am offset=longer day****** (-)pm offset=shorter day)
//array order is ch0 am offset, pm offset, ch1 am offset, pm offset etc..
//THESE values are the number of seconds that a particular channel will be offset from the rise/set time, i.e. negative to rise earlier/set earlier
int Choffset[]={
-600,0,-4200,5400,0,600,-3600,6000,-4500,7200,0,0,0,0,0,0};
//**********************ok now were done changing things here********************************************
//Calculate rise and set times for all channels in equivlants to elapsed seconds from midnight today
//populate array for chRise and Set as well as chSlope for 0.5pi/ half day lenght for each channel from midday (asymmetric days are allowed)
float deltaY=1.570796327;//1/2 * pi as integer by scaling* 10^9 to fill UL
midDay=(((set-rise)/2)+rise);
long HalfDayLength=((set-rise)/2);
//Serial.print("MidDay");
//Serial.println(midDay);
for (byte b=0;b<16;b++){//working as of April 5 2012 serial tested
if (b%2==0){
ChRiseSet[b]=rise+(Choffset[b]);
ChSlope[b]=(deltaY/(float)(HalfDayLength-(Choffset[b])));
//Serial.print("Rise/Set a=");
//Serial.println(b);
//Serial.print("Value=");
//Serial.print(ChRiseSet[b]);
//Serial.print("Slope");
//Serial.println(ChSlope[b], 12);
}
else if (b%2==1){
ChRiseSet[b]=set+(Choffset[b]);
ChSlope[b]=(deltaY/(float)(HalfDayLength+(Choffset[b])));
//Serial.print("Rise/Set a=");
//Serial.println(b);
//Serial.print("Value=");
//Serial.print(ChRiseSet[b]);
//Serial.print("Slope");
//Serial.println(ChSlope[b], 12);
}
}
//***************** to CHANGE THE chance of Clouds actually occuring on a given day************************
byte CloudChance=100;//% Chance of a Cloud every day
//****************************now were done- did you use a value from 0-100 without a decimal?****************
//once a day, after rise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
byte RainMaker=random(1,101);
if (RainMaker<=CloudChance){
CloudToday=true;//used to trigger weather function, can also be used to send flag to controller
}
else if (RainMaker>CloudChance){
CloudToday=false;//see above comment on CloudToday
//Serial.print("no cloud today");
return;
}
// to "simplify" things and avoid redundant calculation loops all cloud times are in fraction of day lengths
//*********** this is only executed 1x /day unless power outage and ONLY if last statement is true****************
///ALl cloud set up is done here... weather subroutine just implements it
/*The general strategy for this algorithim is as follows. Calculate random cloud cover as percent of day,
then randomly generate the # of discreet cloud instances for the day,
then determine cloud length by (daylight seconds * percent overcast)/#clouds
then randomize cloud lengths by splitting into groups of twos correcting for if we have odd # of clouds today*/
long dayLength=(set-rise);
//Serial.println("DayLength");
//Serial.println(dayLength);
// number of clouds possible for the day, max and min
byte CloudsMax=11;//DONT INCREASE BEYOND 11 or it will DIE, or increase array size to handle it
byte CloudsMin=4;//dont use 1 it may or may not work in the next loops and the cloud will likely be very long, i.e. daylength*overcast fraction
CloudsTotal=random(CloudsMin,(CloudsMax+1));
//Serial.println("CloudsTotal");
//Serial.println(CloudsTotal);
// Average day is 50,000 secs so if 4 clouds and 10% that gets you 5,000 seconds of clouds (about 1800 seconds length for each of the 4 clouds in independent segments (if 4 is # clouds)
byte OvercastMin=((CloudsTotal*10)/5);//Min cloud length will be about 1000 seconds (15 mins)- 1 hour min of clouds if you have 4, 2 hours if you have 8
byte OvercastMax=((CloudsTotal*10)/2);//max cloud length will be about 2500 seconds (45 mins)- 6 hours max of clouds if you have 8, 3 hours max if you have 4
float Overcast=random(OvercastMin,OvercastMax);
Overcast=(Overcast/100);
//Serial.println("Overcast");
//Serial.println(Overcast);
// set up start time for clouds
//The way this is done is to split the total lenght of time for clouds into equal segments and then to randomly chop or add time to the
//segments such that cloud length is variable. Then distribute into random parts of the day and fill array with start,duration pairs for clouds
int CloudLength;
CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
long SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));//average sun length between clouds
float CloudFraction=0;
float SunFraction=0;
/*Serial.print("CloudLength=");
Serial.println(CloudLength);
Serial.print("SunSegment=");
Serial.println(SunSegment);*/
//start by zero filling CloudMaster array
for (byte a=0; a<20; a++){
CloudMaster[a]=0;
}
byte b=0;//used to get pairs of fraction for SunFraction in for loop
byte c=0;//used to get pairs of fraction for CloudFraction in for loop
//now randomize cloud length and sunsegment length as pairs to get different looking days-
for (byte a=0; a<(CloudsTotal*2); a++){
if ((CloudsTotal%2!=0) && (a==((CloudsTotal*2)-1))){//if were at the last cloud in an odd cloud day make it full length
CloudMaster[a]=CloudLength;
}
else if (a%2==0){
if (b==0){
SunFraction=random(20,181);//vary each pair of SunSegments from 20%-180% of possible length such that every pair =2*SunSegment in length
SunFraction=(SunFraction/100);
CloudMaster[a]=(SunFraction*SunSegment);
b++;
}
else if (b==1){
SunFraction=(2-SunFraction);//were on the second part of a pair
CloudMaster[a]=(SunFraction*SunSegment);
b=0;//reset so next time we start a new fraction
}
}
else if (a%2==1){//if were in odd positions we need to determine cloud lengths in random pairs such that each pair =2*CloudLength in length
if (c==0){
CloudFraction=random(20,181);//vary each pair of SunSegments from 20%-180% of possible length such that every pair =2*SunSegment in length
CloudFraction=(CloudFraction/100);
CloudMaster[a]=(CloudFraction*CloudLength);
c++;
}
else if (c==1){
CloudFraction=(2-CloudFraction);
CloudMaster[a]=(CloudFraction*CloudLength);
c=0;//reset so next loop finds a new fraction
}
}
}
//serial debugging to examine array
/*Serial.println("here is cloud master in is entirety prior to forming start and end pairs");
for (byte a=0;a<20;a++){
Serial.println(CloudMaster[a]);
}*/
// now we have CloudMaster constructed as SunSegment,CloudLength,SunSegment,CloudLength....
//reframe it to generate cloud start, cloud end, cloud start, cloud end
for (byte a=0; a<(CloudsTotal*2); a++){
if (a==0){// if were starting our first cloud we need to add to rise value to first sun segment
CloudMaster[a]=rise+CloudMaster[a];
}
else {
CloudMaster[a]=(CloudMaster[a-1]+CloudMaster[a]);//just add prior values together e.g. (second position is cloud end so to find end add rise corrected start time with duration)
// subsequent start would be end of 1st cloud + next sunsegment fraction
}
}
//serial debugging to examine array
/*Serial.println("here is cloud master in is entirety as start and end pairs");
for (byte a=0;a<20;a++){
Serial.println(CloudMaster[a]);
}*/
}//END SunCalc FUNCTION
void Insolation()
{
InsolationAdvance=false;//reset this flag now that we have entered function
/*Serial.println("Insolation 3 sec elapsed");
Serial.print("Elapsed Time is=");
Serial.println(elapsedTime);*/
//define Pi as delta Y for slope since cos 0.5-1.5 Pi goes 0-1-0 in 0.5 pI increments slope of 1/2 day (0-1 intensity) delta Y is 1/2 Pi
float Pi=3.1415926;//scale to 10^8
float PiHalf=1.5707963;//scale to 10^8
float secSoFar;//variable to account for seconds elapsed for each channel 1/2 day period from rise-->midDay and midDay-->set
/* using -cos(pi/2+elapsedTime/slope) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light. Turn off lights after set or before rise etc.
by splitting into half days centered on midday (1/2 ofset-rise) we center exactly the cos function for every channel so color blends are maintained
throughout intensity ramp... more or less ... change intensity every 120 seconds throughout the day*/
if (elapsedTime<=midDay){
//Serial.println("in first half of day");
for (byte b=0;b<16;b=(b+2)){
if (elapsedTime>=ChRiseSet[b]){
secSoFar=(elapsedTime-ChRiseSet[b]);//just account for length of every channel 1/2 day and switch at midDay
switch (b){
case 0:
ChannelValue[0]=flicker[b]+ChMax[b]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println((float)ChMax[b],4);
//Serial.println(b);
//Serial.println(ChannelValue[0]);
//Serial.println((float)ChMax[b],4);
break;
case 2:
ChannelValue[1]=flicker[(b-1)]+ChMax[(b-1)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[1]);
break;
case 4:
ChannelValue[2]=flicker[(b-2)]+ChMax[(b-2)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[2]);
break;
case 6:
ChannelValue[3]=flicker[(b-3)]+ChMax[(b-3)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[3]);
break;
case 8:
ChannelValue[4]=flicker[(b-4)]+ChMax[(b-4)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[4]);
break;
case 10:
ChannelValue[5]=flicker[(b-5)]+ChMax[(b-5)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[5]);
break;
case 12:
ChannelValue[6]=flicker[(b-6)]+ChMax[(b-6)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[6]);
break;
case 14:
ChannelValue[7]=flicker[(b-7)]+ChMax[(b-7)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
//Serial.println(b);
//Serial.println(ChannelValue[7]);
break;
}
}
else if (elapsedTime<ChRiseSet[b]){
switch (b){
case 0:
ChannelValue[0]=0; //to enable moon phase lighting for any channel comment out this part and uncomment the next 2 lines
//ChannelValue[0]=MoonPhase(); //this is the after sunset part I told you to look for when you set the before sunrise moon phase channels
//if (ChannelValue[0]<flicker[0]) ChannelValue[0]=0;
break;
case 2:
ChannelValue[1]=0;
//ChannelValue[1]=MoonPhase();
//if (ChannelValue[1]<flicker[1]) ChannelValue[1]=0;
break;
case 4:
ChannelValue[2]=0;
//ChannelValue[2]=MoonPhase();
//if (ChannelValue[2]<flicker[2]) ChannelValue[2]=0;
break;
case 6:
ChannelValue[3]=0;
//ChannelValue[3]=MoonPhase();
//if (ChannelValue[3]<flicker[3]) ChannelValue[3]=0;
break;
case 8:
//ChannelValue[4]=0;
ChannelValue[4]=(MoonPhase()/2);
if (ChannelValue[4]<flicker[4]) ChannelValue[4]=0;
break;
case 10:
ChannelValue[5]=0;
//ChannelValue[5]=MoonPhase();
//if (ChannelValue[5]<flicker[5]) ChannelValue[5]=0;
break;
case 12:
ChannelValue[6]=0;
//ChannelValue[6]=MoonPhase();
//if (ChannelValue[6]<flicker[6]) ChannelValue[6]=0;
break;
case 14:
ChannelValue[7]=0;
//ChannelValue[7]=MoonPhase();
//if (ChannelValue[7]<flicker[7]) ChannelValue[7]=0;
break;
}
}
}
}
else if (elapsedTime>midDay){
//Serial.println("were in the second half of the day");
for (byte b=1;b<12;b=b+2){
if (elapsedTime<=ChRiseSet[b]){
secSoFar=(elapsedTime-midDay);
switch (b){
case 1:
ChannelValue[0]=flicker[(b-1)]+ChMax[(b-1)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[0]);
break;
case 3:
ChannelValue[1]=flicker[(b-2)]+ChMax[(b-2)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[1]);
break;
case 5:
ChannelValue[2]=flicker[(b-3)]+ChMax[(b-3)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[2]);
break;
case 7:
ChannelValue[3]=flicker[(b-4)]+ChMax[(b-4)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[3]);
break;
case 9:
ChannelValue[4]=flicker[(b-5)]+ChMax[(b-5)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[4]);
break;
case 11:
ChannelValue[5]=flicker[(b-6)]+ChMax[(b-6)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[5]);
break;
case 13:
ChannelValue[6]=flicker[(b-7)]+ChMax[(b-7)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[6]);
break;
case 15:
ChannelValue[7]=flicker[(b-8)]+ChMax[(b-8)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
//Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
//Serial.println(ChannelValue[7]);
break;
}
}
else if (elapsedTime>ChRiseSet[b]){
switch (b){
case 1:
ChannelValue[0]=0;
//ChannelValue[0]=MoonPhase();
//if (ChannelValue[0]<flicker[0]) ChannelValue[0]=0;
break;
case 3:
ChannelValue[1]=0;
//ChannelValue[1]=MoonPhase();
//if (ChannelValue[1]<flicker[1]) ChannelValue[1]=0;
break;
case 5:
ChannelValue[2]=0;
//ChannelValue[2]=MoonPhase();
//if (ChannelValue[2]<flicker[2]) ChannelValue[2]=0;
break;
case 7:
ChannelValue[3]=0;
//ChannelValue[3]=MoonPhase();
//if (ChannelValue[3]<flicker[3]) ChannelValue[3]=0;
break;
case 9:
//ChannelValue[4]=0;
ChannelValue[4]=(MoonPhase()/2);
if (ChannelValue[4]<flicker[4]) ChannelValue[4]=0;
break;
case 11:
ChannelValue[5]=0;
//ChannelValue[5]=MoonPhase();
//if (ChannelValue[5]<flicker[5]) ChannelValue[5]=0;
break;
case 13:
ChannelValue[6]=0;
//ChannelValue[6]=MoonPhase();
//if (ChannelValue[6]<flicker[6]) ChannelValue[6]=0;
break;
case 15:
ChannelValue[7]=0;
//ChannelValue[7]=MoonPhase();
//if (ChannelValue[7]<flicker[7]) ChannelValue[7]=0;
break;
}
}
}
}
/*Serial.println("Insolation settings=");
Serial.println(ChannelValue[0]);
Serial.println(ChannelValue[1]);
Serial.println(ChannelValue[2]);
Serial.println(ChannelValue[3]);
Serial.println(ChannelValue[4]);
Serial.println(ChannelValue[5]);
Serial.println(ChannelValue[6]);
Serial.println(ChannelValue[7]);
Serial.println("ChMax Settings");
Serial.println(ChMax[0]);
Serial.println(ChMax[1]);
Serial.println(ChMax[2]);
Serial.println(ChMax[3]);
Serial.println(ChMax[4]);
Serial.println(ChMax[5]);
Serial.println(ChMax[6]);
Serial.println(ChMax[7]);*/
}//END function
//This is where modification of insolation by clouds or a storm occurs
//Its worth noting that the clouds simply modify (depending on how much cloud cover there is) the existing light set by insolation as a fractional value
void Weather ()
{
static float CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static float PriorCloudCover; //used to "delay" one side of the tank from the other in cloud passing effects
static long StormStart;
static long StormEnd;
static long CloudEnd;
static boolean wtrigger;//use to track the first run of functions to calculate random times that become fixed, see change from cloud to storm for useage
static byte Counter;//used to trigger storms from cloud you can change its if loop comparison to decrease or increase storm rate see below in cloud==true if loop
int LongestStorm;//used to pass max possible time to storm if loop from cloud loop within weather function
static byte Severity;
static byte StormCount;// used to limit X storms per cloud and to choose which cloud can have a storm
//check to see if were having a scheduled cloud
if (Cloud==false){
for (byte a=0; a<(CloudsTotal*2); a=(a+2)){//if its time for a cloud, run it
if ((elapsedTime>=CloudMaster[a]) && (elapsedTime<=CloudMaster[(a+1)])) {
CloudEnd=CloudMaster[(a+1)];//to avoid this loop running true during the compute cycles at the end of the cloud and before elapsedTime advances a second, actual cloud does not
CloudEnd-=10;//remove 10 sec from cloud end to clear sky when done...
Cloud=true;//Clear sky function resets to false at time end of cloud
CloudCover=50;
Counter=0;
StormCount=random(0,3);//the number of storms MAX that may occur in this cloud (remember Random is range= -1 on high end)
break;//were starting this cloud so stop looking for another one
}
}
//just write ChannelVales to TrueIntensity without modification so that cloud/storm can later modify them
for (byte a=0; a<8; a++){
TrueIntensity[a]=ChannelValue[a];//this is where intensity is set for the PWM channel analog write in the loop... don't mess with this.
}
}
else if ((Cloud==true) && (IsStorm==false)){
if (StormAdvance==false){//use millis tracker to run this loop every 2 seconds
return;
}
StormAdvance=false;//reset to false when true so we run this once, until time advance is true again
//Serial.println("in a cloud doing stuff");
if (elapsedTime>=CloudEnd){
ClearSky(CloudCover, CloudEnd);
return;
}
/*Use fractional intensity to set minimum value for any channel. Dimming is proportional to actual intensity output
and constrained by flicker point. Random walk uses static variable "CloudCover" constrained to 0-100 to represent fractional intensity (i.e. (1-(CloudCover/100))*Insolation SetPoint
is how the current cloud intensity is set, i.e. cloud cover of 90 gives 10% insolation setpoint unless below flicker in which case = flicker*/
int stepsize;
stepsize=random(-15,16);// in Percent% (0-100) This is how much light intensity can change every 1 or 2 seconds (Whatever CloudAdvance is in this case);
//Serial.print("Stepsize=");
//Serial.println(stepsize);
PriorCloudCover=CloudCover;//this way its going to change every 250 msec
//Serial.print("PriorCloudCover=");
//Serial.println(PriorCloudCover);
CloudCover=(CloudCover+stepsize);//Now add the % increment or decremement to intensity
//Serial.print("CloudCover=");
//Serial.println(CloudCover);
if (CloudCover>=100){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms increase stepsize boundry or change this value
CloudCover=100;//since we see dark clouds better let it just sit here until it walks away
Counter++;
//Serial.print("CLoudCover was over 100 and got corrected to=");
//Serial.println(CloudCover);
}
else if (CloudCover<=0){//i.e if were bright sky in a cloud.. uhh.. we need a cloud
//Serial.print("Cloud cover was negative so I fixed it and its now=");
CloudCover-=(stepsize*1.5);// since we are supposed to be cloudy if we get too bright correct it down by amplifying the change that got us to a clear sky and inverting it
if (Counter>=2) Counter-=random(0,2); //reflect to less positive value, i.e. we had to be adding a negative so subtract it instead (i.e. add a positive)
//Serial.println(CloudCover);
}
if ((Counter>=115) && ((CloudEnd-elapsedTime)>=500)) {//this is where a storm is triggered. Counter indexes when cloud cover reaches 100 on the random walk
//to change the frequency of storms increase or decrease the number comparison for counter in the if statement above (larger #== less storms).
//if you change counter comparison here change it in the next loop as well
if (StormCount>0){
byte RandomStorm;
RandomStorm=random(0,11);//counter at 250 msec timing yeilds a storm in about 7 minutes.. this randomizes for longer clouds without storm, avg cloud is much longer
if (RandomStorm>=6){
IsStorm=true;
StormCount-=1;//count down by 1 the number of storms in this cloud
wtrigger=true;
Counter=0;
LongestStorm=(CloudEnd-elapsedTime);
// Serial.print("we started a storm");
}
// Serial.print("Random call was missed- not a storm");
}
}
else if ((Counter>=115) && ((CloudEnd-elapsedTime)<500)){
Counter=0;
}
for (int a=0;a<8;a++){
if (DimOrder[a]==0){
//Serial.print("Initial Channel setting is= ");
//Serial.println(a);
//Serial.println(FullSun);
TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-((CloudCover/100)))));//max intensity is 65% of initial for better visual dimming performance
//Serial.print("DimOrder 0 setting is");
//Serial.println(TrueIntensity[a]);
}
else if (DimOrder[a]==1){
//Serial.print("Initial Channel setting is= ");
//Serial.println(a);
//Serial.println(FullSun);
TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-((PriorCloudCover/100)))));//max intensity is 65% of initial for better visual dimming performance
//Serial.print("DimOrder 1 setting is");
//Serial.println(TrueIntensity[a]);
}
}
}
//enable a flag sent from controller to triger a storm, i.e. IsStorm=true
// set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
else if (((Cloud==true) && (IsStorm==true)) || ((Cloud==false) && (IsStorm==true))){
//current else statement covers possibility of triggering storm from controller (i.e. not coming out of a cloud) but remember you need to flag wtrigger as TRUE when you do this
//do this next part exactly once per storm then loop past
if (wtrigger==true){
StormStart=elapsedTime;
LongestStorm-=120;//remove 2 mins from longest storm so that we end up with 2 minutes of cloud after the storm before the sky clears to daylight
//*****************To CHANGE THE LENGTH OF THE LONGEST POSSIBLE STORM DURING A CLOUD read next line comments****************************
if (LongestStorm>500) LongestStorm=500;//YOU CAN CHANGE THE value in the comparison and the define to be whatever you want your max lenght to be
////*************************EDIT ABOVE***************************************************************************************************
int StormDuration=random((LongestStorm/4),(LongestStorm+1));//set (min,max) seconds any single Storm can exist currently 1/4 length to full length remaining cloudtime
StormEnd=(StormStart+StormDuration);
Severity=random(2,9);//changes lightning strike frequency in a storm from very frequent (every 10-15 sec or less) to about once in a minute or maybe less
Counter=0;
}
if (StormAdvance==false){//Every 1 second duing a storm change intensity, clouds are movin fast baby
return;
}
StormAdvance=false;//reset so we run again in 1 second.
//Serial.print("in a storm now");
if (elapsedTime>=StormEnd){ //if were done with the storm we need to stop this loop, but were probably still cloudy so dont mess with that here
IsStorm=false;
Counter=0;
return;
}
int stepsize;
stepsize=random(-19,20);
PriorCloudCover=CloudCover;
CloudCover=(CloudCover+stepsize);
if (CloudCover>=100){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms increase stepsize boundry or change this value
CloudCover=100;//since we see dark clouds better let it just sit here until it walks away
Counter++;
if (Counter>(Severity+1)) Counter=0;//allow if to accumulate on ocassion to train strike sequences 2-3 in a row but then dump it
//Serial.print("CLoudCover was over 100 and got corrected to=");
//Serial.println(CloudCover);
}
else if (CloudCover<=0){//i.e if were bright sky in a cloud.. uhh.. we need a cloud
//Serial.print("Cloud cover was negative so I fixed it and its now=");
CloudCover-=(1.5*stepsize);//stay within bounds
Counter-=random(0,2);
//Serial.println(CloudCover);
}
if ((Counter>=(Severity+random(-2,4))) && (StrikeNow==false)) {//this is where a storm is triggered. Counter indexes when cloud cover reaches 100 on the random walk
//to change the frequency of lightning strikes increase or decrease the number comparison for counter in the if statement above (larger #== less storms).
byte RandomStriker;
RandomStriker=random(0,11);
if (RandomStriker>3){
StrikeNumber=(random(2,11)); //random high =x-1 so max strike =12 each strike requires a duration and a delay thus StrikeMaster is 18 positions
//ensure the array is zeroed out past the last position required for this strike pattern so each pattern is only as long as generated
//Array is in pairs, position in arry of (0,1) (2,3) etc as strike (delay,duration)
for (byte a=0;a<20;a++){
if (a>=(StrikeNumber*2)){
StrikeMaster[a]=0;
}
if (a%2==0){
if (a==0){
StrikeMaster[a]=random(300,1601);//no need for random here but I am leaving it since I wrote it that way. This must be independent from a=2,4,6 etc...
}
else {
StrikeMaster[a]=(StrikeMaster[(a-2)]+random(160,1201));//position 0,2,4,6,8.. is strike delay
}
}
else if(a%2!=0){
StrikeMaster[a]=random(45,100);//position 1,3,5,7,9... is strike duration (I tried real lightning strike durations and its too short this is adjusted for visual effect
}
}
StrikeNow=true; //Trigger to start strike sequence in loop
StrikeStart=millis();//set timer to "zero" now- sequence will start in loop after this function
StrikeCount=0;
}
}
for (int a=0;a<8;a++) {
if (Wchannel[a]==1){//if were white we need to be off in a storm
TrueIntensity[a]=0;
}
else if (Wchannel[a]==0){//if were blue, we chase as for a cloud
if (DimOrder[a]==0){
//Serial.print("In a storm and DimOrder0 channel values were");
//Serial.println(ChannelValue[a]);
//Serial.println(a);
TrueIntensity[a]=(flicker[a]+(((float)((ChannelValue[a]-flicker[a])))*(1-((CloudCover/100)))));
//Serial.print("and now the channel is set to");
//Serial.println(TrueIntensity[a]);
}
else if (DimOrder[a]==1){
//Serial.print("In a storm and DimOrder1 channel values were");
//Serial.println(ChannelValue[a]);
//Serial.println(a);
TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])))*(1-((PriorCloudCover/100)))));
//Serial.print("and now the channel is set to");
//Serial.println(TrueIntensity[a]);
}
}
}
}//end of storm if loop
}//End Weather function
//THE LAST 2 mins of ANY Storm, or cloud, are used to ramp linearlly to full daylight setting, whatever that is at the given time of day
void ClearSky(float CloudCover, int CloudEnd)
{
// no need to restrict time to avoid resource hogging, its only called every x seconds from weather.
//basically, add 120 seconds to every cloud and use it to bring light up to full
int elapsed=(elapsedTime-CloudEnd);//counts up from zero forever... but we stop it by 120 to end cloud
//Since CloudCover is amount taken away as decimal fraction of InsolationSetting (i.e. 70 produces light at 30% of insolation setpoint... we need to come up 70 to get back.
float slope=(CloudCover/10);//cloud cover goes from 0 (Clear) to 90 (storm) in a cloud, since storms are forced to end before the cloud we know this is the limit
if (elapsed<=120){//at this point lights are back full on so cancel the cloud and start waiting for the next one
Cloud=false;
return;
}
byte LightAdvance;
LightAdvance=(CloudCover-(slope*elapsed));//were reducing CloudCover from start to zero over 120 seconds seconds...
for (byte a=0; a<8; a++){
TrueIntensity[a]=(byte)(((ChannelValue[a]-flicker[a])*(float)(1-((float)(LightAdvance/100))))+flicker[a]);
}
}//End Clear Sky function
byte MoonPhase()
{
int m,d,y;
int yy,mm;
long K1,K2,K3,J,V;
byte PWMvalue;
m = month();
d = day();
y = year();
yy = y-((12-m)/10);
mm = m+9;
if (mm>=12) mm -= 12;
K1 = 365.25*(yy+4712);
K2 = 30.6*mm+.5;
K3 = int(int((yy/100)+49)*.75)-38;
J = K1+K2+d+59-K3;
V = (J-2451550.1)/0.29530588853;
V -= int(V/100)*100;
V = abs(V-50);
PWMvalue = 4*abs(50-V); // 5.12=100% 4=~80%
//pinMode(lowATOPin,OUTPUT);
//return (PWMvalue*100)/255; //output is 0-100
return PWMvalue;
}
void CalcDST(byte D, byte M, byte dow)
{
//January, february, and december are out.
if (M < 3 || M > 11){
isDST=false;
}
//April to October are in
else if (M > 4 && M < 11){
isDST=true;
}
else{
int previousSunday=(D-dow); // Sunday=1 Sat=7
if (M==3 && previousSunday>7){
isDST=true;
}
else if (M==11 && previousSunday<=0){
isDST=false;
}
}
}
//********************** DO NOT MESS WITH THIS UNLESS YOU KNOW WHAT YOUR DOING****************************
//THE CODE BELOW THIS copied directly from the SWFLTEK Epherma library constructed by Michael Rice.
//this code is being used freely with attribution to Micahel Rice in accord with his request
// A big thank you for these library functions. Its great!
//convert degrees to seconds of arc
// decimal degrees
long ddToSeconds(float dd){
return dd * 3600.0;
}
//Degrees, minutes, seconds
long dmsToSeconds(int d, unsigned char m, unsigned char s){
long ret;
ret = labs((long)d);
ret = ret * 3600L + 60L * m + s;
ret = (d<0L) ? -ret : ret;
return ret;
}
/* ------------------------------------------------------------------------------------------------
'Equation of Time'
We use the 'short for' equation, which has a theoretical accuracy of about 40 seconds.
The returned value is in seconds.
*/
int equation_of_time(unsigned long dt){
double t;
dt -= 192540UL; // refer to Jan 3 2000 05:29 (first periapsis)
dt %= _tropical_year;
t = dt;
t /= _tropical_year;
t *= 6.283185307179586;
t = -459.27672 * sin(t) + 575.333472 * sin(2.0 * t + 3.588414);
return t;
}
/*
'Solar Noon' adjusts the passed time stamp to the time (GMT) of local solar noon.
The accuracy is about 40 seconds (set by the equation of time).
*/
void SolarNoon(unsigned long * dt){
long r;
// Set stamp to noon GMT
*dt /= 86400UL;
*dt *= 86400UL;
*dt += 43200UL;
// adjust for equation of time, at noon GMT
*dt -= equation_of_time(*dt);
// rotate to our longitude
r = longitude / 15L;
*dt -= r;
}
/* -----------------------------------------------------------------------------------------------
'Solar Declination'
Returns declination in radians
Accurate to within 50 arc-seconds
*/
double SolarDeclination(unsigned long dt){
double y;
dt %= _tropical_year;
y = dt;
y /= _tropical_year; // fractional year
y *= 6.283185307179586;
y=0.006918-0.399912*cos(y)+0.070257*sin(y)-0.006758*cos(y*2)+0.000907*sin(y*2)-0.002697*cos(y*3)+0.00148*sin(y*3);
return y;
}
/* ------------------------------------------------------------------------------------------------
Return the period between sunrise and sunset, in seconds.
At high latitudes around the time of the solstices, this could be zero, or all day.
*/
unsigned long daylightseconds(unsigned long dt){
float l, d, e;
long n;
d = -SolarDeclination(dt); // will be positive in Northern winter
l = latitude / _sec_rad; // latitude in radians
e += 60.0 * l * tan(l + d); // latitudinal error
d = tan(l) * tan(d); //
if(d>1.0) return 86400UL;
if(d < -1.0) return 0UL;
d = acos(d);
d /= _zenith;
n = 86400UL * d;
n += e;
return n;
}
/* ------------------------------------------------------------------------------------------------
Modify the passed time stamp to the time of sunrise (or sunset if 'set' is non-zero).
Returns 0 to signal 'normal' completion. If the position is in a polar circle, 1 will be
returned if the sun is above the horizon all day, and -1 if the sun is below the horizon
all day.
*/
char SunRiseSet(unsigned long * dt, char set){
unsigned long daylen;
daylen = daylightseconds(*dt);
if(daylen == 86400UL) return 1; // there is no 'night' today (midnight sun)
if(daylen == 0UL) return -1; // there is no 'day' today
*dt /= 86400UL;
*dt *= 86400UL;
*dt += 43200UL; // set the time stamp to 12:00:00 GMT
*dt -= daylen / 2; // sunrise at the prime meridian
if(set) *dt += daylen; // sunset at the prime meridian
*dt -= equation_of_time(*dt);
*dt -= longitude / 15.0; // rotate to our own meridian
return 0;
}
// 'short' forms of SunRiseSet
char SunRise(unsigned long* when){
return SunRiseSet(when, 0);
}
char SunSet(unsigned long* when){
return SunRiseSet(when, 1);
}
Code: Select all
//***********************************ARRAYS YOU MUST MODIFY TO MAKE YOUR TANK SET UP WORK*********************
//CHANGE this to the number of seconds your time zone is offset from GMT (WITHOUT REGARD TO DAYLIGHT SAVINGS TIME)
int GMToffset=25200;//USA MST time zone is 25,200 if that helps you
//GLOBAL Variables YOU NEED TO CHANGE
boolean ApplyDST=true;//CHANGE THIS IF YOUR NOT USING DST
byte ChMax[]={200,210,200,210,225,0,0,0};//Incremental value (Max-flicker) above flicker you want as max intensity-
byte flicker[]={30,28,30,28,25,0,0,0};//need to input actual values here for flicker point on all channels in PWM expansion box
boolean Wchannel[]={1,0,1,0,0,0,0,0}; //use 1 to designate white channel (i.e. off during storm and used for lightning). Array corresponds to PWM channel 0-5 in order
//Array to give direction to dimming. e.g. DimOrder[]={0,0,1,1,0,0} (cloud chase effects, just group channels you want to dim together during a cloud or storm
//as either a 0 or a 1, i.e. all left side channels are 0 all right are 1 or all front are 0 all back are 1 or whatever
//(which is zero or 1 will change who dims first). set them all to 0 if your tank has no left/right or front/back lights.
byte DimOrder[]={0,0,1,1,1,0,0,0};
//**********************************DONE CHANGING THINGS HERE BUT YOU MUST CHANGE ChOffset array IN CalcSUN function******
Code: Select all
//*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
//channels 0-5 are PWM expansion board lights 6,7 are ReefAngel Controller PWM outputs
//offsets for rise/set all values in seconds offset from calculated rise or set value (-) am offset=longer day****** (-)pm offset=shorter day)
//array order is ch0 am offset, pm offset, ch1 am offset, pm offset etc..
//THESE values are the number of seconds that a particular channel will be offset from the rise/set time, i.e. negative to rise earlier/set earlier
int Choffset[]={
-600,0,-4200,5400,0,600,-3600,6000,-4500,7200,0,0,0,0,0,0};
//**********************ok now were done changing things here********************************************
Code: Select all
if (ReefAngel.PWM.Channel[5] > 0);
{
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,70,105,MoonPhaseLabel());
}
else (ReefAngel.PWM.ExpansionChannel[0] > 0 && (hour() < 14 ) )
{
ReefAngel.LCD.DrawLargeText(COLOR_ORANGERED,255,3,65, "Sunrise",Font8x8);
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,3,75, ReefAngel.PWM.ExpansionChannel[0]);
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,20,74, ":");
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,24,75, ReefAngel.PWM.ExpansionChannel[1]);
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,41,74, ":");
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,45,75, ReefAngel.PWM.ExpansionChannel[2]);
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,66,75, "|");
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,70,75, ReefAngel.PWM.ExpansionChannel[3]);
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,87,74, ":");
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,91,75, ReefAngel.PWM.ExpansionChannel[4]);
}
else (ReefAngel.PWM.ExpansionChannel[0] > 0 && (hour() > 14 ))
{
ReefAngel.LCD.DrawLargeText(COLOR_ORANGERED,255,3,65, "Sunrise",Font8x8);
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,3,75, ReefAngel.PWM.ExpansionChannel[0]);
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,20,74, ":");
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,24,75, ReefAngel.PWM.ExpansionChannel[1]);
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,41,74, ":");
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,45,75, ReefAngel.PWM.ExpansionChannel[2]);
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,66,75, "|");
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,70,75, ReefAngel.PWM.ExpansionChannel[3]);
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,87,74, ":");
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,91,75, ReefAngel.PWM.ExpansionChannel[4]);
}