C library for Sun/Moon effects

Do you have a question on how to do something.
Ask in here.
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: C library for Sun/Moon effects

Post by rimai »

I think you are overflowing the variable:

Code: Select all

static long CloudMaster[15];
You have it declared for 15, but you are using 16:

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;
               }
Roberto.
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: C library for Sun/Moon effects

Post by rimai »

Actually the correct term should be you are going out of bound and not overflowing.

Sent from my SPH-D700 using Tapatalk 2
Roberto.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

YEP..... I thought I had fixed that... but at 1:40 am when I stopped working on it... that was the last thing I addressed and my brain remembered that its 16 spots but not that small part about zero based... DAMN.

Thanks! I cannot test this now, but if thats the fix (and really likely to be so) then I think I might be very happy. I looked at that loop a few times because I was having issue with the zero fill... and once I got a clean serial debugging and saw zeros to the end, I never really went over that part again. Awesome catch. Your amazingly quick.

Once its actually fully working I will need to ask for some help on the time synch, I started writing this by essentially neutering some code that had two way serial implemented, so thats "there" but I have no idea if its right. Next step is this if your catch fixes the problem and all else is good. I will look at all arrays again to be sure this is the only instance, somehow after two months or more working with c++ I still screw this up. Dang!

Thanks
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok-

Deleted a couple useless update posts for real information...

I rewrote the strike portion of the code, its MUCH less complex now although perhaps the logic is a bit more obtuse but not so bad to follow.

Question I have is does this look right.

In the weather function when your in a storm, I randomly generate numbers to increase or decrease light on the tank. In this bit of code, a random number that is generated to increment light intensity in a random walk (i.e. the number is accumulated and may increase or decrease cloud intensity (i.e. how much light gets through). If that number happens to fall in the upper or lower end of the bounds of the random call I generate a lightning strike sequence by running though a for loop and populating StrikeMaster - an array (int StrikeMaster[18]; is the initialization as a global variable).

It should be populated with a delay in milliseconds then the strike duration.. BUT.... The only "funky" part of the first code is that StrikeMaster at positions 0,2,4,6 is assembled such that position 0 is a random interval between the bounds of random(750,2001) while position 2 is the addition of position 0 with a new random call, position 4 is 0+2+new random etc... this is where I am kinda unsure if I screwed this up. I have looked at it many times and I *think* I have this correct. Position 1,3,5,7 are just random strike durations (the time the white light will be on for)

PLEASE see if this is correct, I keep looking at stuff like this, convincing myself its good and corrupting memory and killing the whole thing until Roberto tells me I did something stupid...

So you might get
500,75,1200,85,2000,100,3200,90,4500,60 in sequence from this code as StrikeMaster.
Which would theoretically generate a strike sequence of- strike 500 ms after the true condition was set, for 75 ms of white light, then again after 1200 ms elapsed for 85 msec etc.. the actual strike generation code which uses the array is posted after the code that generates it.


THIS CODE IS IN WEATHER which is called from the loop continuously but only runs when your in a storm

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;
            }
        
I think this looks good... any comments

And finally... the almost more important question.

This bit runs in the loop.
When StrikeNow==true which is set after the sequence is loaded in StrikeMaster in the weather function (above)
then it should be picked up the next time through the loop.

What I want to happen, is for there to be some fungibility in time sequencing here. The program seems to run pretty quickly through the loop (I have not timed it exactly, but the loop prints to serial MANY times per second. So what I would like to happen, is if AT LEAST the delay specified in the StrikeMaster (0,2,4,6 etc) has occurred, then turn on the white channel for the correct strike duration (strikeMaster 1,3,5,7 positions etc). But also, be sure that once it executes the first strike, its checking for the delay in the Second strike position in StrikeMaster[2] and executing the lightning strike duration for the second strike (StrikeMaster[3]) and so on through the sequence. I also want to minimize the use of delay(), so I hope I have it structured such that when the time has elapsed to meet the first delay (position 0 in Strike master) it will strike by setting whites to an intensity for a duration of position 1 in strike master using a delay call (but its MAX value is only 200 ms). Then it should increment a counter, causing it to look for an elapsed delay equal to the 2, 4, 6 etc position of StrikeMaster after the 1st, 2nd and 3rd strike etc..

Please check for me if you have a bit.... I keep screwing this stuff up

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;
        }
    }
Basically, I know... I need to test it, but I cannot do it now and I would like to see if anyone can follow this and make sense of it. It seems compact to me, but might be done more easily. I
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: C library for Sun/Moon effects

Post by rimai »

If max is 200 ms, I think it should be fine :)
Roberto.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

I hope its 200 ms max. I had the old version running for a while and saw a few "strikes" or at least white light sequences during a storm but they went by so quickly, that the light never really got bright... so I moved the upper bound to 200 ms (instead of something like 80 ms which is a real lightning strike duration). I am thinking this should allow the LED's to hit their max intensity but still appear to "flash"- if its still weird I may need to either go longer yet or look at why they are not flashing appropriately.


I am kinda unsure about the LEDs (maybe?) but feel like the PMW board (absolutely) can do 100 ms cycles, the LED drivers.... I have not a clue- the Meanwell drivers may or may not be capable of this type of response, its not really fast as electronics go but its pretty quick... I hope it works. I am assuming that the technology in aggregate is more than capable of flashing, or producing a quick on cycle of 100-200 ms and reaching max light output during that cycle and then shutting off. The on/off seems possible, the ramp to max intensity I am unsure.

Any knowledge?

It would be nice to keep the max duration low (200 ms or less) as its using a delay. Honestly, it would not affect anything in the program, light intensity is only calculated every 1 second during a storm... and if it was slow you would not notice it really the lights would just not shift in intensity. PWM analog write (and interrupt driven serial comm- I think) is one of the few processes that are actually running in the background during a delay... which makes this work.


Oh... the rest of the program is pretty much completely debugged. I should have a truly functional non buggy code up soon. I have been through it A LOT of times and now seen a TON of serial output for debugging- its solid. Point of interest-in attempt to get more random I was (as in I am no longer doing this) running a call to randomseed every time through a loop and using millis() or other combinations of stuff to get it to be random. THIS DOES NOT WORK- it makes it a predictable number generator since millis() do NOT change fast enough to modify the seed point, you get the same numbers again, and again, and again... with very slow evolution away from that point... so I now use an analog read to pin0 in setup, and have tested it- it seems different most of the time- i.e. every reset I get different array populations, its floats around near to the center of the random distribution range.... but thats what it should do.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Just an update.

Working with help from Roberto (Rimai) to enable control of the ReefAngel PWM ports from this code which requires a bit more comm than I am capable of implementing without much delay and trial as well as requires a bit more in depth understanding of how this will interface with/ interfere with existing reef angel main .ino setups. So THANKS to Roberto for helping out in a very big way.

Also working to implement moon phase lighting during non daylight hours (I believe this is trivial and done but I may be slightly wrong about the done part)

Also finishing cloud effect fine tuning/debugging to get nice visuals.

When its all put together and working I will post the code here.

For those of you who have PM'd... you should have replies from me with pre configured code to try on your system once I am certain its 100% working on my end. I CANNOT test the reef angel ports fully as they are not plugged into lights on my system, so if your set up with LED PMW control on the PWM expansion port AND the reef angel controller, please PM me and you get to be the first person to try that :mrgreen:
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

YES!

IT WORKS. ALL OF IT so far as I can tell!!!!!! and the cloud and storms are awesome looking. I will post code tomorrow or the next day when I am SURE its not still one more thing. But I am very happy! I have learned a TON doing this.... aside from the fact that i now know how to do it, if I did it again it would take about 1/8 the time..... sadly this probably will not transfer completely to some new aspect of coding but I hope it helps a little bit.

Errors I made that killed me most often
#1) NOT immediately suspecting that an error I had previously found might be made again in a different place and cause similar behavior

For loops with incorrect bounds causing me to write to "random" memory in the stack (3-4 x)
Using incorrect variables and overflowing them causing them to roll (2 times plus 1 insidious version that was the last error I caught)
Using visual basic syntax

Will delete this post and replace with appropriate working code SOON!!!!!

Everyone who has contacted me will get a PM with some bit of help in terms of instructions- this will occur after (maybe a day or two) I post the code, but its coming.
abhi_123
Posts: 216
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

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

Image
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Didn't look at the fully, but it appears you have the correct code to simply cut and past and name the file

put a new folder INSIDE your library folder for arduino
name the new folder SWFLTEK_EPHERMA

then copy that code into a file and name it SWFLTEK_EPHERMA.h

Then when you load the code (which I will post soon, just finishing the last bug kill but its working and looks AMAZING. I had a small problem in the way I was setting up cloud start and end times and got some overlap leading to very long (inappropriate) clouds... I think I fixed it but its a super complicated loop so I want to test it before posting code.

If you get the library ready when you get my code it should compile instantly. If it gives an error with something about the SWFLTEK library files in the message you need to check you got the correct names and that its a .h file etc

DO NOT pay attention to the SNIP HERE part of that library file... Its supposed to be a single file for our use. Unsure what the snip here part is indicating (perhaps C# usage.. beyond me so just use it intact it works for us this way)
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok... so I posted some storm video I just took to You Tube. Look for "storm first edition" or just follow this link... it is tagged with the following tags

LED
REEF
PMW
ReefAngel
C++
Arduino
DIY

here is the link

http://youtu.be/jEOpuVZ__Ik

I LIKE :mrgreen: :mrgreen: A LOT.

I have to say I am not certain yet if the storm ends.... so I may have a bug but it certainly gives nice light sequences in all color for clouds, it transitions to storms (too easily but this is a variable set point that will be customized by each user anyway..) and When the storms end and you go back to daylight or possibly the cloud until the cloud ends. Storms occur WITHIN clouds, then may END the cloud but not necessarily. They are called when the random walk in intensity hits a threshold, then the remaining length of the cloud is assessed and if its more than 5 minutes a storm will occur that will randomly be between probably 60-420 sec long (adjustable to take up to the entire remaining cloud length). Then in the storm, lightning is triggered based upon the random walk variable for light intensity changes exceeding a threshold (user specified). If this occurs a lightning strike sequence is set up with 2-9 lightning strikes. Each strike is between 45-100 msec long (each are randomly different) and occur at intervals from 175-1600 msec between strikes and each strike is a random intensity between about 30%-100% of the possible light output in the WHITE channels.

The code is below. BEWARE I HAVE A TON of serial debugging in here commented out. I THINK I GOT IT ALL. But.. if your serial starts loading up with "junk" well.... I might have missed one.

Try it out. If Q's let me know. Any more issues will be found through more using it. Mine is working but I only just NOW finished with my entire bug list. I would not be surprised if something I didn't know was wrong still existed (e.g. more bugs). But they are more likely to be triggered by events I have not fully explored- like length of time running, number of days, season changed, different Lat/long pairs for sun rise and sunset calculations etc.

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
User avatar
jsclownfish
Posts: 375
Joined: Mon Oct 24, 2011 7:52 pm
Location: Saint Louis

Re: C library for Sun/Moon effects

Post by jsclownfish »

Nice video, looks great! Not to change the subject, but what is in the mechanical thing in the middle of the tank that seems to sway back and forth?

-Jon
abhi_123
Posts: 216
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

Hi,

This code is designed for how many pwm channels?Suppose if i want to use it for only two channels on my module than what changes should i have to make?
Image
User avatar
JNieuwenhuizen
Posts: 96
Joined: Thu Feb 16, 2012 12:39 am
Location: South Africa

Re: C library for Sun/Moon effects

Post by JNieuwenhuizen »

Hi

I take it this has to change if it is your moon channel?

From

Code: Select all

                        case 10:
                            ChannelValue[5]=0;
                            //ChannelValue[0]=MoonPhase()
                            //Serial.println("Ch5 dark");
                            break;
To

Code: Select all

                        case 10:
                            ChannelValue[5]=0;
                            ChannelValue[5]=MoonPhase()
                            //Serial.println("Ch5 dark");
                            break;
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok... I have a request from someone using 5 channels of lights.

Channel 0-3 are mixed blue and white and go left to right across the tank with channel 1 on the far left and 3 on the far right. They told me channel 4 is mixed colors and goes down the middle of the entire tank. They want sun to rise from the LEFT and set to the RIGHT.

Since they do not have a blue ony string, I am curious how the storms look. Basically they need to choose which light strings to shut down during a storm (and thus use for lightning effects) and which to leave on.

To set up sunrise sunset offsets, you need to populate the following array.
ChOffest{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
This array is found in the CalcSun function and is VERY OBVIOUSLY LABELED AS SOMETHING YOU NEED TO EDIT.

I put in the 0,1, etc to highlight that the array has 16 positions. They are paired as sunrise, sunset offsets for 8 channels of lighting.

CHANNEL 0-5 (i.e. channel 1-6) take positions 0,1 - 2,3 - 4,5 - 6,7 - 8,9 - 10,11
The last two channels (6-7 (i.e. 7,8 if you count from 1) are not yet enabled but will be used to control the TWO PWM channels on the controller head unit for those people who have lights there. WHEN its enabled it will be possible to fully control both the PWM expansion module AND the 2 channels on the head unit from this program. BUT ITS NOT ENABLED YET.

To set up sunrise and sunset offsets. Since this individual has channel 0 on the far left and wants the sun to rise from channel 0-->1-->2-->3 then 4 and to set in the reverse order i.e. channel 0 goes off first, then 1-2-3 and 4 somewhere in there. Because 4 is colors (green, red etc) I think it might be best to have it rise last and set first so you don't end up with the last light string being colored only and the tank looking funny after all the white and blues have set.

Like I said, the array ChOffset is paired in rise,set. A value of 0 at any position in the array indicates that that channel will rise and set according to the exact value for sunrise and sunset as calculated for that day. Its probably easiest to say that the first light in the tank should occur at the real sunrise in this instance, since there are no blue only strings. I have blue strings and have them come on prior to "sunrise".

To do this. We will have channel zero rise at sunrise and set EARLY so that channel 3 goes off at sunset.

For example, using {0,-900,.....} will yield channel 0 turning on at exactly sunrise and setting 15 minutes early
as an example here is complete array to control channels 0-4 with 0 rise first and 3 set last using 15 minutes of time for the tank to transition from first light to full light (all channels)
{0,-900,300,-600,600,-300,900,900,0,0,0,0,0,0,0,0}
Since we are not using channels 5,6,7 (zero based array numbering i.e. channels 6-8) I have filled the array with zeros. YOU MUST FILL THE ARRY TO hold 16 positions, the ENTIRE array is used in calculations and if its NOT big enough you will end up grabbing random numbers from memory and possibly corrupting the program.

Now, to set up the light effects. We need to decide which lights to TURN off during a storm, and which order the lights will chase across the tank. I have to say that I am liking how the clouds look but don't "see" the chase effect so much... but it may be kinda subtle and enhancing things so I am leaving it in.

basically, during a cloud it sets a value for the lights and then pushes that value to any channels that have a dim order of 0. Then on the next round of calculations, it passes the old value to the lights with a dim order of 1 and recalculates a new value (additive but values are incremental values are random and may be positive or negative). The new value is then passed to all channels with dim order 0. And it repeats every 500 msec or so which is a value I have set that appears to give a nicely observable light pattern without being a disco ball.

So. To set of the left half to dim first (were talking about 500 msec differences here so its not that the left visually dims before the right, its just that it gets new values first so it might be as much as 20 % different in intensity at any given moment and then 500 msec later the right side catches up and the left takes on the next value in the random walk.

DimOrder is in the header of the code where I have all the global variables declared. Its labeled as something you need to change. To dim 0,1 together as the left side, and 2,3 second as the right side (where to put 4 is up to the person with this set up, I grouped it with 2,3 for ease.
DimOrder {0,0,1,1,1,0,0,0} again, the array must be zero filled and contain exactly 8 elements. It really does not matter if you had the last three as a value of 1... there are not any lights there so who cares.

This sets up the dimming pattern for the cloud and storm effect.

To set up which lights will go OFF during a storm and that will also be used for lightning strike effects... for those of you with blue only strings (or at least no whites in with the blues) you would pick the channels that use white and set them to be off during a storm. Do this by setting the following array. Again, this is in the commented section of the header grouped with other things you would need to change

Simply place a 1 in the array position of the strings you would like to shut off during a storm- they are then also automatically used for lightning strike effects.

Wchannel{ with 8 positions}

Finally- to set up light max intensity for the solar insolation modeling using a cosine function. I ignore the difference between true solar noon (max radiation intensity) and instead simply divide the day into exactly two portions such that at MidDay (calculated don't worry about it) the lights will peak at 100% of whatever setting you have in the ChMax array (again, 8 positions, zero fill unused channels of lighting).

ONE THING YOU NEED TO KNOW!!!!!

The ACTUAL max output set point of all lights during the day is.

Cosine function*ChMax+FLICKER!!!! (cosine goes 0->1 first 1/2 day and 1->0 second half)

THUS- YOUR ACTUAL DESIRED MAX INTENSITY is the SUM of ChMax and flicker array's!!!!!!!

so, if your lights flicker at 10% output on your PMW dimming your flicker point is probably about 30 (26 to be exact but add a little so its guaranteed not to flicker)- E.G. PWM intensity is a byte running from 0-255 so 10% is 26.

As an example. Lets say I want my channel 0 lights to hit a max PMW byte output of 210 and the flicker point is 30
ChMax{180,0,0,0,0,0,0,0}
flicker{30,0,0,0,0,0,0,0}
etc for the remaining channels

I am unsure about moon phase. Maybe Roberto can chime in here.

But.. I have set up in Insolation (function in the code) the following looking code

else if (elapsedTime<ChRiseSet){
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;

So... I am thinking that if you want moon lights and you want to use one of your channels (say an all blue channel as moon lighting) that you would simple comment OUT the ChannelValue[0]=0; and uncomment the
// CHannelValue[0]=MoonPhase(0) (by removing the //)
THIS MAY require that you add an additional #include statement to get the library files that compile the MoonPhase() function. I am unsure exactly where they are.... Roberto can help here and additionally can tell me if more than this is required. I am unsure exactly how the MoonPhase output scales... but I think this is all that is needed.

Please READ THIS and inspect the code.. then try it... if you have issues please post to this thread with accurate error messages (I suspect there will be some issues with people importing the SWFLTEK library) or odd behavior.

GOOD LUCK!
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

edit.... in response to the Q about what was moving. Its a veggie clip swaying in the current!
abhi_123
Posts: 216
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

So what changes do i need to make in the code?
Image
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

I have no idea what you need to change... I know from PM that your getting apparently a library error (I guess maybe)... Please post an exact description of either what error your getting (COPY and PASTE error) or read the above post which contains a lot of information, then look back through the code and try to make changes and see if it works. If that fails please post a detailed question about what problem you having with the setup.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

With regard to prior unanswered questions....

1st.. MoonPhase... its more complex but yes.. I had not really anticipated that working so I just pasted that into a place holder... I am trying it and use this... but continue reading before copy paste.

ChannelValue[5]=MoonPhase();
if (ChannelValue[5]<flicker[5]) ChannelValue[5]=0;


Two issues
MoonPhase is a Globals.h function in ReefAngel. So we need to add a #include <Globals.h> line to the header.. but then its going to ask you for a setup file...

The more important issue is the unfortunately the SWFLTEK.h file for the library (which I did not write) contains an IDENTICALLY named function, so we really cannot use the Globals.h function unless we edit the identically titled function out of the library SWFLTEK.h

I went into the SWFLTKE.h file and just uncapatalized the MoonPhase function declaration and Function (its in two spots in the file, easy to find). This solves the second problem.

I then decided I was lazy, and just copied the MoonPhase function into my .INO so in this way, (its like 15 lines) this code requires no .features file nor the Globals.h library. The ONE problem with this... its kinda the wrong way to go since it will not then capture and features or changes... but then again, if MoonPhase changed and broke this code... by using a function hard coded I protect against that. For now, I am using a hard coded version but am not totally certain its working. I am PM to Rimai to see what should be done. I will post my code with working moon Phase control when its sorted out.

And finally, I *THINK* MoonPhase outputs 0-255 for direct PMW input but I also think that the moon should be full tonight and MoonPhase is giving me a value of 73 on a serial debug... so.. whats the MoonPhase output supposed to span. 0-100 or 0-255? Anyone?

CODE IS FOR PMW MODULE ONLY ... would not even function on Main controller not to mention would total neuter your controller as this is 18,000 bytes alone (more than 50% of capacity I think). Again, Roberto may need to chime in here... but if your going to run this code, you MIGHT be able to do absolutely NOTHING to the controller- at all. If its trying to COMM with the PWM board.. well ... I am not sure.. so load and see if it runs. if the lights work and your controller is working your good i guess. Sorry I cannot help more here.

I also made numerous changes in the code to improve visuals, and generally fine tune it. When I get MoonPhase figured out I will post new code... you can then simply copy and paste your array settings into it... its just 2 places in the code so it should be easy for people to keep up with updates from us.

I am trying to get to Roberto's code to COMM to the controller but dealing with this stuff so eventually we will have COMM working... soon is not incredibly likely.
abhi_123
Posts: 216
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

this is what i am getting as an error.

in function 'void calSun()' :
error : 'latitude' was not declared in this scope
error : 'dmsToseconds' was not declared in this scope
error : 'longitude was not declared in this scope
error : 'sunrise' was not declared in this scope
error : 'sunset' was not declared in this scope.
Image
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok... In talking with Roberto he was OK with me bundling everything together. So.

I will be posting a COMPLETE CODE this evening.

YOU WILL NOT NEED ANY LIBRARIES. AT ALL>

This will greatly simplify its distribution.

MoonLighting according to Moon phase will be enabled.

I would suggest you simply wait a few hours and when I post it immediately download the new code. Just copy and paste your header information that has been customized over the header of the new file so you don't have to re code the ChMax, flicker, ChOffset, etc...

You can then DELETE any library files you were working on. It should work pretty much every time for everyone here.

The CODE loads on the PWM module ONLY. There are no modifications required on the head unit as of yet... but I would at least turn off any lighting control coming from there. Just to be safe. Probably it doesn't even matter. I have disabled he ability for the head unit to set PWM channel intensities in my code so at this point, my code doesn't even really know the controller exists but for a few lines of code (which may not even be working).
abhi_123
Posts: 216
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

Any updates?
Image
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

TWO big changes.

(1) You DO NOT NEED A LIBRARY FILE. DELETE yours if you made one. I bundled all functions into the .INO.
(2) MOON Lighting works! YIPEE

To use moon lighting, go through the code (search for MoonPhase()) until you find the part of the Insolation function that deals with what the lights do before sunrise, and after sunset-

THERE ARE TWO places you need to change things because the lights run in 4 segments of the day, BEFORE sunrise, first half of day, second half, and AFTER SUNSET. The code I have posted runs my tank, I use channel 4 as moon lights.

The MoonPhase function as I have it implemented (thanks Roberto for clarifying output) outputs from 0-255 depending on phase. Since my moon lights are really high power True Violet LEDs on a Meanwell driver, I do NOT want them running at 255 for Moon- tis a bit bright... so I chop the value in 1/2 like this

case 8:
//ChannelValue[4]=0;
ChannelValue[4]=(MoonPhase()/2);
if (ChannelValue[4]<flicker[4]) ChannelValue[4]=0;
break;
Also see case 9: statement for after sunset as example.

I think I have all the channels pre coded for you so you just need to decide if you want to run it 0-255 which is how it is (but commented out) or if you want to chop it down, like I did. Just look at the example above and then make sure that I didn't mistype on the other channels for moon use... I don't use them but I was nice enough to pre code them all (but I may have typos).

Finally, I took a video of the cloud effect. I have to say the video SUCKS but anyhow.. its better at 50+ seconds in
camera seriously dulls effect with exposure auto.
http://youtu.be/XpmG90fF7Jc

PLEASE POST VIDEOS if you get it running! I want to see it in action. SHOULD BE SUPER EASY NOW.

EDIT header (I gave a detailed explanation nothing has changed)
LOAD ON PWM EXPANSION MODULE

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);
}
DONE!
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

LET ME KNOW if your seeing enough storms... it has been redone so you should be in a cloud for 12-20 minutes before a storm will form (and it may never its random but about 2/3 of clouds can make storms) and then lightning frequency is also randomly intense storm to storm, its possible to get a "severe storm"... let me know if we need to back that off a bit... no sense in freaking out the fish too much. Incidently, mine could care less... tang, anthias pair, clown, goby, pseudochromis etc.... don't give a rats A$$ about the lightning strikes. Kinda funny... I figured after all this work they would at least act scared or something.... ingrates.
abhi_123
Posts: 216
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

Do we need to delete all lighting codes from our head unit codes?

one more question my country's time is +5:30 G.M.T. However i know U.S.A. is - 7:00 G.M.T so what to do for offset?
Image
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

I missed detailing that part in the longer post a page back where I went though how to set up a tank... I wish I could edit posts that have been replied to so I could correct that... oh well- sorry :shock:

With respect to the head unit... I don't think my program cares... but since we are going to be enabling comm... I would go ahead and try it first without any modification to the head unit... should run. Then, when your sure you want to use this continuously- delete lighting from the head unit- but I would recommend at least trying this for a while to be certain you want to run it before deleting your head unit lighting control code. We will restore some control functions to the head unit... such as say triggering a storm, displaying rise/set/cloud/storm status/light intensity set point/moon phase etc...but this will be a good bit before its working.

The GMToffset, and its actually called that in the code... is very very clearly marked as something you need to change and is grouped in with most of the rest of the variables that you will need to define to make your tank work based upon your lighting configuration.


In attempt to make this incredibly straight forward, I am going to post every part of the code that a first time user will need to modify or read. There really are only two places you even need to look. One is VERY NEAR the top of the code and looks like this


You will note that GMToffset is clearly labeled with instructions on how to modify it.
You will also note that you probably want to set the boolean ApplyDST to false since I seriously doubt your country uses the same (or any at all) rules for Day Light Savings time.

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

Then, a little bit further into the code, maybe 3 pages down you will find this- this is the set up to configure individual lighting channels to rise and set at times offset from the calculated rise and set times for the day. The variables are in SECONDS! Read the comments carefully to figure out how to make something rise early or late or set early or late... also read carefully the post a page ago detailing the setup (but for DST and GMT).

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********************************************   
        
        
Thats really all you need to pay attention to.

A warning- although I have made a significant effort to make this USER friendly, through extensive commenting and grouping of variables etc etc.... this is probably one of the more complex lighting programs on the ReefAngel controller- that being the case, there is more setup required to get this to work...
User avatar
JNieuwenhuizen
Posts: 96
Joined: Thu Feb 16, 2012 12:39 am
Location: South Africa

Re: C library for Sun/Moon effects

Post by JNieuwenhuizen »

Thank You for the code

My biggest challenge now will be to change this code on my RA to get it to show the Values of the channels

my current code is

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]);
    }
 
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok-

This is NOT enabled yet. Roberto has sent me a test code that should enable comm to the controller and allow your controller to display the current intensity set points, perhaps the cloud/storm status, the remaining time in the cloud or storm, the moon phase etc etc...

However, I have been spending all my time getting the program fully tested and compiled to make implementation easier.

At this point, run the program on the PMW board and just forget about your controller having anything whatsoever to do with this. My program completely ignores the presence of the main controller at this point, I MAY have the WDT enabled, but have not tested this and my comm module might not even be working. I need to spend some time (and its going to be a good bit) to get my system running with COMM back and forth from the main to the PMW unit. At this point, I started development of this program pretty much when I got the controller with a little bit of playing around with existing PWM programs prior to that, but my controller is basically doing NOTHING. I have it running but its not controlling ANYTHING- at all. So. I need to basically learn the controller side and get my own set up going before I can really even start to trouble shoot comm. Its going to be (I guess) about a month before I get this working, maybe I have more knowledge now and I can get it up in a day or two but I am not going to promise anything.

IF anyone here has more experience with the comm modules etc, I would be happy to include you in the development of this part and can forward your the test code once Roberto and I get it to base line work, then those with the controllers set up and running with custom displays for lights etc can help us to move forward more quickly. PM if you want to help.

However, the program will run perfectly well without knowing anything about the main (which is what its doing now).

Lets see some comments from people running this.. I would guess that at least 2-3 people will have this running today or tomorrow based upon PM's I am getting and posts here. I want to see it up on someone else's system before I start moving ahead on the comm in case I built in some stupid bug that causes problems. As far as I am concerned... the program is working perfectly... I caught a bug that was causing (about 1/2 of the time) a cloud to immediately become a storm and corrected it prior to posting the code last night, so if your using the code a few posts up (no library needed with MoonPhase enabled) it should be working 100% with no known bugs. I have been running it for a few weeks now and correcting issues but at this point its solid and does what I want it to do.

I may steal some of your head unit code if you had comm running with a custom display. Look for a PM when I start working on this and get a chance to look at what you posted above.
User avatar
JNieuwenhuizen
Posts: 96
Joined: Thu Feb 16, 2012 12:39 am
Location: South Africa

Re: C library for Sun/Moon effects

Post by JNieuwenhuizen »

Sounds good, so for now, no comms.'

I do have one question, if my lights are still on very late in the evening, past 10pm South Africa time, and the GMT is set to 7200(+2 hours) , what else could be wrong?

By now sun should have set already
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Yeah... my bad. I live in GMT -25200... and to take my local time and bring it to GMT I need to ADD to the value... in this line

SecInput=(newDay+GMToffset);

which feeds to the rise and set functions...

and then to correct BACK to local time I need to REMOVE the GMT offset so I do this

rise=(rise-GMToffset);
set=(set-GMToffset);


SO... if you just use a negative number (which is completely ass backwards) my program will work. you could wait it out tonight to see if it sets 4 hours late (i.e the 2 hour error is doubled because of the sign)... then reload the corrected code... or just correct it and when you load it the lights should NOT come on, but for moon lighting.... and the sun then would be expected to rise correctly tomorrow and match your window. I have not tested lat lon pairs other than in my region... should work perfectly but would be nice to see!

I guess I forgot to say in the header that the way I coded it is the GMT offset is the number of seconds you must add to local time to get GMT time, which can be negative depending on which side of that line you live on. I will eidt the header to make this more clear.

On the other hand... it sounds like you have lights! Awesome. Let me know if your sun sets etc and if you saw clouds. If you want to play with how many days are cloudy... look here.

//***************** 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;
}
what this code does is compare a random call (rainmaker) which has bounds of 0-100 to the CLoudChance which is currently set to 100%. If the random call is GREATER than the CloudChance variable (feel free to change this) you DO NOT GET A CLOUD that day.

Then it calculates the number of clouds for the day ( I skipped a few lines in the code but these are more or less together)
// 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));

and then it goes on to randomly assign cloud lengths based upon how overcast the day will be (i.e. what percentage of the day will be cloudy)... here....

// Average day is 50,000 secs so if 4 clouds and 10% that gets you 5,000 seconds of clouds (about 1250 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);

and finally it takes the total seconds of the day that will be cloudy.. .i.e. 50000 sec day 20% OVERCAST would yield 10,000 seconds of clouds. If the day had randomly generated 5 clouds each one would be 2,000 seconds long... but I randomize in pairs such that every two clouds takes up 2000*2=4000 seconds
but the first cloud is between 0.2 and 1.8 times the average cloud length... then the next cloud is 2-(0.2-1.8) seconds long (i.e. 2-frist cloud fractional length) then the spacing between clouds is similarly randomized....

Customize away!

SORRY about GMT- use a negative sign everyone on the other side of the planet!

DAMN
Post Reply