C library for Sun/Moon effects

Do you have a question on how to do something.
Ask in here.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

THANK YOU. I would not have thought of that. THAT is the easy way, for sure.

I kept at it and could never quite get around the fact that even if you set a flag as a global and trigger it with seconds, the processor simply out runs your logic. Millis will solve that problem instantly.



One step closer to seeing it in action.


AND SOO CLOSE... seriously, if the time accounting is working, all the cloud setup, insolation, and the program is running cleanly without corrupting memory (which is seems to be) I am down to a single function to trouble shoot (weather), and that one is the least complex- its just watching elapsed time and triggering a few things.... this really should be working soon. I need perhaps a couple more nights working on it unless something turns out to be broken that I thought was working...
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok... So I think this should run... but I need to load it.

Couple things I am nervous about that I want to ask about here prior to loading.

I totally reworked the strike pattern generation.

Please let me know if this should work.

Basically, I create a random number of instances of a lightning strike called StrikeNumber which is an byte that
can be between 2-9- i.e. lights will flash in random sequence 2-9 times.

I set up an array called StrikeMaster that is StrikeNumber-1 long.
The array has random delay values (mills) of 100-700 milliseconds between strikes which occupy array positions 0,2,4,6,8... to fill array based upon StrikeNumber size required.
The values for 1,3,5,7.... are the duration of the strike, which are also randomly generated and vary from 30-80 mills (which is about how long a real lightning strike can be in nature.

Thus, an array for StrikeMaster might look like this (for StrikeNumber==3)
{500,50,250,35,600,70}

I am trying to get it to strike only 1 time per delay interval... so what I ended up doing it creating a counter for the loop called StrikeCount which starts at 0 and indexes by 2 every time a strike is written to the PWM board... and then use that value to sum the total delay across the StrikeMaster up to the point for the next strike... thus using elapsed mills when it is greater than the additive delay it will strike, then advance StrikeCount and then the next iteration of the loop will have a greater delay and should not trigger until milles has elapsed up to or past the scheduled delay (in this way its also time flexible since i am not requiring equality.. so if it takes a few 10's of mills to loop through all the other functions we will still get another strike with a bit of wobble in the actual delay)

So can someone help me with looking this over and seeing if you think it would work.

here is the entire code for the loop which is mostly just this with calls to CalcSun, Insolation, and Weather and a time tracker for triggering a boolean variable to run segments of insolation every 3 seconds, cloud every 2 seconds and storm every 1 seconds.... this is a neat way to get them to run only then... based in part on prior suggestions.

Finally, does this look right to actually write the values of the ChannelValue array to the PWM board at the bottom of every loop....
I.E. since I use a delay, the strike will happen, then it will hit the final analog.Write at the end of the loop, which will shut off the whites during a storm... so the flash should end immediately after the delay when it hits the loop segment of the analog.Write.

I assume this bit

for (byte a=0;a<6;a++){
analogWrite(PWMports[a],ChannelValue[a]);
}
will function without a define statement for PMWPorts, i.e. this is correct syntax and is in the library somewhere in terms of defining what to do with this.

here is the loop

I have another question which is below this part if you can help with that too...

Code: Select all

//Loop
void loop()
{
  wdt_reset();
  //Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255){
    ProcessCMD(cmdnum,datanum);    
    cmdnum=255;
    datanum=255;
  }
//Use millis to enable tracking of time interval
  if ((millis()-lastmillis)>1000) {
    StormAdvance=true;
    lastmillis=millis();
    counter++;//use this to see if time has moved ahead 2, or, 3 seconds from first check which is 1 second advance
      switch (counter){
        case (2):
          CloudAdvance=true;
          break;
        case (3):
          InsolationAdvance=true;
          counter=0;
          break;
      }
  }     
  
// now run through rise/set calculation and then intensity set and finally weather overlay
  CalSun();
  Insolation();
  Weather();
    if (StrikeNow==false){
      StrikeStart=millis();
      StrikeCount=0;
    }
    if (strikeNow==true){
      elapsedMillis=(millis()-StrikeStart);
        
      if (StrikeCount>StrikeNumber){
        strikeNow=false;
        StrikeCount=0;
        elapsedMillis=0;
      }  
      int additiveDelay;//value combining dealys across array to the stike number were at
      for (byte a=0; a=StrikeCount;a++){//StrikeCount goes by 2 and starts at zero... so just add it up
          additiveDelay+=StrikeMaster[a];//if were at zero we get 1 val... if were at 2 we get first two delays
      }
      if (elapsedMillis>=additiveDelay{
        intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
        for (byte a=0; a<6; a++){
          if (Wchannel[a]==1) analogWrite(PWMports[a],intensity);  
          }  
        delay(StikeMaster[(strikeCount+1)];
        StrikeCount+=2;
       }
    }
    
  //track time in seconds-dont move this up in the loop, CalSun, DST must run first or your going to get a error with uninitialized variables
  if (isDST==true){
    elapsedTime=(((now()-946684800)+3600)-newDay);
  }
  else{
    elapsedTime=((now()-946684800)-newDay);
  }
   
  //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],ChannelValue[a]);
  }
}
//End Loop
//*********************************************************************************************************************************

The whole flow to the program is this.
1 time per day (at midnight or upon a restart of the PWM board) run calcsun and determine sunrise and sunset as well as calculate if we will have clouds, determine how many, determine their start and end points.. all these are global arrays

then insolation gets called which determines how far into the day we are and sets the base line intensity for every channel based upon the cos function for solar insolation and takes into account the desired offsets for rise/set for every channel, i.e. if you want you blues on early and to stay late etc...
Insolation just takes the 1/2 day length of every channel (i.e. 1/2 sunrise+sunset) plus morning or evening offsets and converts the time of day to an intensity setting (fraction of MAX) using the calculated 1/2 day length to (using a slope function) time to span a value from 1/2 pi (the number pi) at sunrise (i.e. -cos(1/2 pi)==0 to at midday -cos(pi)=1 so if you simply use this decimal value of -cos(pi conversion) you get 0-100% lights according to a cosine function over the morning to "noon" and then simply continues adding to pi over the pm 1/2 of the day to get from -cos(pi) at noon to -cos(1.5pi) at sunset==0

These decimals are simply used as floating point conversions- i.e. max desired intensity*float -cos(val) =set point through day in 3 second interval (rate value is recalculated) and are written to the ChannelValue array

Then weather gets called.... which modifies the values in ChannelValue by introducing cloud cover which can both reduce or increase intensity based upon a random walk, but I limit (duh) values to 100%of the insolation value, or the flicker point of the light.

Finally within weather a storm can happen if your channel intensities get low enough (by successive addition of random walk variables which can be either positive or negative) to trigger a storm, at which point whites get turned off (in ChannelValue array) and then a strike can happen... which is the code above.

So, since all light but for the strike which uses a direct analog.Write with a delay, is occurring ONLY at the end to the loop with the an along.Write and everyone uses ChannelValue[a] to set values, everything should work out to a coherent change...

Here is the part I am concerned with....

I am unsure about float conversions in terms of math

I use a lot of functions that require float math to establish the actual PWMChannel set points using stuff that I made to look like this...

ChannelValue is a BYTE array
flicker is a BYTE array
CLoudCover is a BYTE value and is ALWAYS positive (its reflected back if it tries to go negative in the random walk)

What happens is I convert cloud cover to a fraction (possible values are 0-80) by CloudCover/100 and use that
to set intensity, so I need that as a FLOAT. But I want to write a BYTE to the final PWM controller from ChannelValue

ChannelValue[a]=(byte)(((ChannelValue[a]-flicker[a])*(float)(1-((float)CloudCover/100)))+flicker[a]);

basically I want to write a byte to ChannelValue (0-255).

This is part of the cloud function in weather() which takes a set point (ChannelValue[a])
and modifies it based upon how cloudy we are which is CloudCover that can run from 0-100%

at 0% cloud cover, you get 1-0=1+flicker from the right part (past the (float) and since we subtracted flicker from the left part, we simply get back the original value

whereas when cloud cover is set to say 50%...
we get 50/100=0.5
1-0.5=0.5
0.5*ChannelValue-flicker=((50% of light past flicker point)+flicker)=set point for 50% cloudy.

is the notation I am using (float) correct, my understanding is that if forces a type conversion to the nearest float value (which is more exact) than the final which is the byte conversion.

I have not ever done this before but started adding all this in with all the trouble I have had with math occurring in unexpected ways based upon the variable type... Hope this is not too confusing of a question.

I will sit down and look at values but I am trying to figure out if this is the way to go

I tried integer conversion for all the intensity set point values, but it will not work due to the way my slope function works... you always end up producing a decimal so I cannot get around floats.

What I want to be sure, is that the FINAL analog.Write value is a BYTE (I know it will be between 0-255, if the conversions work).

What would happen if I wrote a float? I assume it would simply truncate... so do I need to worry about that???

Does it look like I have the math right.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Sorry for the long questions.... its really done... so this should be the last bit of trouble shooting and I am rather hoping we are good to go if I can get all the math right. I am relatively confident in all the "complex" conversions and routines- if I took the algorithms and plugged it all into excel, I am confident It would produce exactly what I want (some of that has been done) but I am really not too sure about the whole arduino/c++ type casting and the effects on the math.

Anyhow, this will be a little work to grasp and actually help me out, so THANKS if your able! Happy to move to email if things require more back and forth or whatever.

Finally, I think that once I have it running which may be today... should I start a new thread and post working code with some instructions on what needs to be modified or keep it here in the development thread. I note that among other things, the way I have presented the library files for the EPHMERA library is WRONG and I had to create a single file, rather than the split .h .cpp files as I posted... so there is just enough WRONG about this thread as an ongoing debugging effort that a new thread that is more user friendly may be appropriate with working code posted from the beginning?

Thoughts from Roberto/Kurt or others with experience would be great.
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: C library for Sun/Moon effects

Post by rimai »

Lost of casting :)
I think it should work.
Roberto.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok- its working :roll: :D . I played with the code a little bit more last night, and actually removed a lot of the casting and rearranged the parenthetical expressions for much of the insolation loop to calculate the sun "path" via cosine functions.

As of 6:20 this morning, when I loaded the code up... I didn't get to watch sunrise as that occurred at about 20,000 seconds into the day, but the lights were on, the PWM settings looked correct (i.e. 58 as a byte value written to PWMChannel which makes sense for about being about 20-30 minutes from sunrise). The insolation function runs every 3 seconds as intended and CalSun runs only 1/day. I was able to see that a cloud was scheduled and I think it started but morning and late day clouds are hard to "see" because the cloud effect is constrained to being within the current insolation MAX channel value, so 30 min after sunrise, the cloud can only dim from 58-25 as byte values written to the PWMChannel- which your not going to be able to see as much of an effect... during the mid day it can dim from basically 100% PWM out to the flicker point, which you will see.... so obviously much has yet to be confirmed working but the "difficult" parts would appear more or less functional and the PWM board seems to EASILY handle the program.

Its currently about 20,500 bytes compiled leaving about 10,000 bytes available for more lines, so we can add comm to the main board etc easily.

Have not seen a storm, and since this is pretty much entirely random it may be a bit before I am there to observe it and the attendant random lightning strikes (I would be surprised if I did not need to adjust the chance for any one of these events to occur- it may create a storm every time a cloud appears (kinda doubt it) or have extremely infrequent strikes etc etc... but these are simply changes to variable ranges allowed and everyone can tune to their desire.

I will write up a description of the specific configurations that need to be set so that others can try this without having to read the entire program line by line and then try to figure out what needs to be changed.

Anyhow, its running all day unattended for the first time, but I am at work.... oh well. Will post code when I get a chance. May be a little bit for description of what to change.


Maybe its just me... but the lights just looked so much better this morning :mrgreen:
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: C library for Sun/Moon effects

Post by rimai »

Cool :)
Can't wait to read your observations during the next few days :)
Roberto.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

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

#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

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

Re: C library for Sun/Moon effects

Post by rufessor »

Here is the current version of my source code.

I am beta testing it, its working but I have no idea whats going to happen through an entire day cycle, its a working beta and likely has bugs. I am running it unattended (which is optimistic), when I catch bugs I will post a description of the bug, and the new fixed source code.

You likely will need to change a few lines to get this to work for your tank as it is unlikely in the extreme that we have identical channel lay outs etc etc.

I tried to comment the code heavily (for my own sanity and in hope that it would ease adoption).

I have NOT gone through the comments lately so there are probably many that are now in error as the code evolved SIGNIFICANTLY during development.

I will post a HOW TO guide tonight- but here is what I am now considering a beta version of the code. It would be awesome to see someone else try this out-

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 <Globals.h>
#include <ReefAngel_Features.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <avr/wdt.h>
#include <SWFLTEK_EPHERMA.h> 


//*********************************************************************************************************************************
//IF YOU DO NOT WISH TO USE the U.S.A. Day Light Savings Time rules to modify the time stamp for rise/set calculations...set to false- i.e. if you dont live in the USA etc
boolean ApplyDST=true;
long elapsedTime;//used multiple places as elapsed since new day.
unsigned long elapsedMillis;//used in strike timing
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long rise;
unsigned long set;
unsigned long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long ChRiseSet[12];
float ChSlope[12];
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[6];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)


//set up channel max intensity set points
byte ChMax[]={255,255,255,255,255,0};//max values you want to hit at solar noon scaled as byte values for LED output PWM write

byte flicker[]={25,25,25,25,10,0};//need to input actual values here for your flicker points for EACH channel.. 
byte ChInt[5];
//YOU MUST CHANGE THIS- designate channels of white light  (channels you want off during Storm and used for lightning strikes)
//use 1 to designate white channel.  Array corresponds to PWM channel 0-5 in order
boolean Wchannel[]={1,0,1,0,0,0,0}; 
unsigned long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[17];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte StrikeCount;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop

byte cmdnum=255;
byte datanum=255;

boolean trigger; //used a few places as a switch to ensure things only run 1x when needed
byte strikePattern, strikeTime;//used in Lightning() for timing of particular instance of strike 
boolean isDST, Cloud, Storm, CloudToday, StrikeNow; // what they seem, is it DST?, Are we in a cloud?
byte CloudsTotal; 
byte strikeCount;//required to know how large the array for CloudMaster will be-
long *pCloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
unsigned long lastmillis;// variable to track millis to enable cloud and insolation loop restriction by time
boolean CloudAdvance=false;//cloud timer for light effect advance
boolean StormAdvance=false;//storm timer for light effect advance
boolean InsolationAdvance=false;//when true we recalculate light intensity during clear sky
byte counter;//used to track millis advance for insolation,cloud trigger

//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,};
//****************************************
//END HEADER/Global Variable declaration//
//****************************************
//Setup
void setup()
{
  Serial.begin(57600);
// Wire.begin(8);
// Wire.onReceive(receiveEvent);
// Wire.onRequest(requestEvent);
  randomSeed(analogRead(0));
  pinMode(3,OUTPUT);
  pinMode(5,OUTPUT);
  pinMode(6,OUTPUT);
  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);
  pinMode(11,OUTPUT);
  //wdt_enable(WDTO_1S);
  setSyncProvider(RTC.get);   // the function to get the time from the RTC
  setSyncInterval(SECS_PER_HOUR);  // Changed to sync every hour.
  
  now();//why are you here?
  CloudToday=false;
  trigger=true; 
  StrikeNow=false;
  Cloud=false;
  lastmillis=millis();//start our millis timer now
  counter=0;
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop()
{
  wdt_reset();
  //Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255){
    ProcessCMD(cmdnum,datanum);    
    cmdnum=255;
    datanum=255;
  }
//Use millis to enable tracking of time interval
  if ((millis()-lastmillis)>1000) {
    StormAdvance=true;
    lastmillis=millis();
    counter++;//use this to see if time has moved ahead 2, or, 3 seconds from first check which is 1 second advance
      switch (counter){
        case (2):
          CloudAdvance=true;
          break;
        case (3):
          InsolationAdvance=true;
          counter=0;
          break;
      }
  }     
  
// now run through rise/set calculation and then intensity set and finally weather overlay
  CalSun();
  Insolation();
  Weather();
    if (StrikeNow==false){
      StrikeStart=millis();
      StrikeCount=0;
    }
    if (StrikeNow==true){
      elapsedMillis=(millis()-StrikeStart);
        
      if (StrikeCount>StrikeNumber){
        StrikeNow=false;
        StrikeCount=0;
        elapsedMillis=0;
      }  
      int additiveDelay;//value combining dealys across array to the stike number were at
      for (byte a=0; a=StrikeCount;a++){//StrikeCount goes by 2 and starts at zero... so just add it up
          additiveDelay+=StrikeMaster[a];//if were at zero we get 1 val... if were at 2 we get first two delays
      }
      if (elapsedMillis>=additiveDelay){
        byte intensity;
        intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
        for (byte a=0; a<6; a++){
          if (Wchannel[a]==1) analogWrite(PWMports[a],intensity);  
          }  
        delay(StrikeMaster[(strikeCount+1)]);
        StrikeCount+=2;
       }
    }
    
  //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.
  if (isDST==true){
    elapsedTime=(((now()-946684800)+3600)-newDay);
  }
  else{
    elapsedTime=((now()-946684800)-newDay);
  }
   
  //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],ChannelValue[a]);
  }
}
//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();
  // Individual Channel
  if (cmd>=0 && cmd<=5){
    ChannelValue[cmd]=data;
    analogWrite(PWMports[cmd],data);
  }
  if (cmd==6){
    Storm=true;
  }
}
//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()
{  
//this entire function runs only based upon the first if being true... so its a LONG ways down to the else if trigger is false statement to finish this if 
// Every day at midnight, we calculate rise, set, time of highest sun, moon phase, or we do this if system has reset
  if ((hour()==0 && minute()==0 && second()==0) || (trigger==true)){  
    Serial.println("trigger 1");
    Serial.println(trigger);
    
    unsigned long SecInput;
    
        //void CalcDST(day(),month(),weekday()) modifies isDST memory to appropriate true/false based upon USA rules
     if (ApplyDST==true){
        CalcDST(day(),month(),weekday());
     }
        //Using time library from Arduino.time_t now(); returns seconds since 1970
        //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
      
      long hours;//possible to be reset at 23 hours*3600=long required
      long minutes;// int is enough as 60*60 is max val but math is conducted with possibility of roll over in intermediate so to be sure.. use long
      time_t t=now();
      hours=hour(t);
      minutes=minute(t);
      
       if ((ApplyDST==true) && (isDST==true)){
           if ((hours!=0) || (minutes!=0)){
               hours=hours+1;
               newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your rise being off by <60 seconds on a day of a power failure... your more anal than I
               Serial.print("DST Hours,Min");
               Serial.print(hours);
               Serial.print(",");
               Serial.println(minutes);
           }  
           else{
             newDay=(now()-946684800+3600);
           }
       }
       else if ((isDST==false) || (ApplyDST==false)){  
            if (hours!=0 || minutes!=0){
                newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your rise being off by <60 seconds on a day of a power failure... your more anal than I
                Serial.print("Hours,Min");
                Serial.print(hours);
                Serial.print(",");
                Serial.println(minutes);
            }  
       } 
       
     SecInput=((newDay)+25200);//modify to GMT time (i.e. MST + 7 hours = GMT)
     
         //Calculate Latitude and Longitude converting from Degree, Minute, Seconds to seconds
     latitude=dmsToSeconds(40,45,39); //Set to about the latitude of the great barrier reef rotated into the morthern hemisphere
     longitude=dmsToSeconds(-111,53,25); //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.
         
         //Calculate rise time and set time using Epherma Library   
      
     rise=SecInput;
     set=SecInput;
     
     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
       
     rise-=25200; //Correct back to MST from GMT- DST still accounted for
     set-=25200; 
     Serial.print("newDay=");
     Serial.println(newDay);
     Serial.print("Local uncorrected rise, set=");
     Serial.print(rise);
     Serial.print(",");
     Serial.print(set);

     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);
//*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
//offsets for rise/set all values in seconds offset from calculated rise or set value
//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,-3600,5400,0,600,-3600,5400,-4500,7200,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);
      Serial.print("MidDay");
      Serial.println(midDay);
  
     for (byte b=0;b<12;b++){//working as of April 5 2012 serial tested
        if (b%2==0){
          ChRiseSet[b]=rise+(Choffset[b]);
          ChSlope[b]=(deltaY/(float)(midDay-(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)(midDay+(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=70;//% 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
  randomSeed(now()-(random(30000,1000000001))); //so that we are now more trully random
  byte RainMaker=random(1,101); 
  if (RainMaker<CloudChance){
    CloudToday=true;
  }
  else if (RainMaker>=CloudChance){
    CloudToday=false;
    trigger=false;
    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);
  byte OvercastMin=10;//% as fraction (0-1 is 0-100%) of daylight hours that would minimially be cloudy on a cloud day
  byte OvercastMax=31;//% of daylight hours that would maximally be cloudy on a cloud day (then add 1 to that #)
  float Overcast=random(OvercastMin,OvercastMax);
  Overcast=(Overcast/100);
  Serial.println("Overcast");
  Serial.println(Overcast);
  // number of clouds possible for the day, max and min
  byte CloudsMax=9;
  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);

  static long CloudMaster[15];// 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
  pCloudPoint=&CloudMaster[0];//use address of pointer to find cloud data in weather, array is declared static to persist throughout day.
  
  // 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*/

     long CloudLength;
     CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
     float fraction=random(20,181);
     fraction=(fraction/100);//vary each cloud from 20%-180% of an equal fraction of total cloud for day
     Serial.print("fraction ");
     Serial.println(fraction);
     Serial.print("CloudLength ");
     Serial.println(CloudLength);
   
     //using fraction to vary cloud length throughout day fill array CloudMaster[] at positions 0,2,4,6 to full with durations in seconds
     //this way the MAX cloud duration is 180% of the average cloud druation while the min is 20%
     byte b=0;//set up counter to alter cloud length and initialize to zero outside of loop
     for (byte a=0; a<CloudsTotal; a++){
       int CurrentCloudLength;
          // if were having an odd # of clouds make sure last one is full length
        if ((CloudsTotal%2!=0) && (a==(CloudsTotal-1))){
           CloudMaster[a*2]=CloudLength;
           Serial.print("last odd cloud Cloudmaster[a]=");
           Serial.println(a*2);
           Serial.print("fraction=");
           Serial.println(fraction);
           Serial.print("CloudMaster=");
           Serial.println(CloudMaster[a*2]);
           break;
        }
           // else we make clouds variable in length short then long repeated with every two cloud sets being different in short and long but adding up equally by sets
        else {
          if (fraction<1){
            CurrentCloudLength=abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
            CloudMaster[a*2]=CurrentCloudLength;
             Serial.print("Cloudmaster[a]=");
         Serial.println(a*2);
         Serial.print("fraction=");
         Serial.println(fraction);
         Serial.print("CloudMaster=");
         Serial.println(CloudMaster[a*2]);
         Serial.print("b=");
         Serial.println(b);
          }
          if (fraction==1){
             CloudMaster[a*2]=CloudLength;
             Serial.print("Cloudmaster[a]=");
         Serial.println(a*2);
         Serial.print("fraction=");
         Serial.println(fraction);
         Serial.print("CloudMaster=");
         Serial.println(CloudMaster[a*2]);
         Serial.print("b=");
         Serial.println(b);
          }
          if (fraction>1){
            CurrentCloudLength=((CloudLength*fraction)-(b*CloudLength*fraction))+(b*((2-fraction)*CloudLength));
            CloudMaster[a*2]=CurrentCloudLength;
            Serial.print("Cloudmaster[a]=");
         Serial.println(a*2);
         Serial.print("fraction=");
         Serial.println(fraction);
         Serial.print("CloudMaster=");
         Serial.println(CloudMaster[a*2]);
         Serial.print("b=");
         Serial.println(b);
          } 
        }
       b=b+1;
         if (b==2){
           b=0;
           fraction=random(20,181);//every other cloud recalculate a new fractional distribution for the next set
           fraction=(fraction/100);
        } 
     }
     
 randomSeed(now());

// Now space the clouds out during the day using random fractionation of day segments as per cloud calculation
    b=0;//reset counter used for fraction reset
    byte c=0;//counter for indexing # of itterations in loop
    long StartTime;//Used in loop to assign cloud spacing, i.e. these loop values determine daylight interval between clouds
    fraction=random(10,181);
    fraction=(fraction/100);
      for (byte a=1; a<(CloudsTotal*2); a=(a+2)){
          //calculate amount of sun during day (i.e. total day lenth - portion cloudy), divide into equal parts then randomly initiate cloud within sun segment for each part of the day.
          // must index segment time by +1 segment for each cloud completed
        long SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));
        
        if (fraction<1){
          abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
          StartTime=rise+((SunSegment*b+abs(b*SunSegment-(SunSegment*fraction)))+(c*SunSegment));
          CloudMaster[a]=StartTime;
          StartTime=0;
          b=b+1;
        }
        else if (fraction==1){
          StartTime=rise+(SunSegment+(SunSegment*c));
          CloudMaster[a]=StartTime;
          StartTime=0;
          b=2;//by doing this I ensure that new fraction will be calculated so next interval will not be 1 (or rarely so)
        }
        else if (fraction>1){
          StartTime=rise+(((SunSegment*fraction)-(b*SunSegment*fraction))+(b*((2-fraction)*SunSegment))+(c*SunSegment));
          CloudMaster[a]=StartTime;
          StartTime=0;
          b=b+1;
        }  
        
        if (b==2){
           b=0;
           fraction=random(20,181);//every other cloud recalculate a new fractional distribution for the next set
           fraction=(fraction/100);
        }
        c++;   //index loop counter by one now  
        Serial.print("CloudMaster position number, ");
        Serial.print(a);
        Serial.print(" is= ");
       
        Serial.println(CloudMaster[a]);
    }  
    Serial.println("now we print start and end times for clouds");
//just reframe CloudMaster into array of start and end times for clouds (CLoudMaster[]={start,end,start,end,start,end} as progression through day in elapsed seconds from midnight.
    for (byte a=0; a<(CloudsTotal*2);a=(a+2)){
        long endT, startT;
        startT=CloudMaster[(a+1)];
        endT=CloudMaster[a]+startT;
        CloudMaster[a]=startT;
        CloudMaster[a+1]=endT;
        Serial.println(CloudMaster[a]);
        Serial.println(CloudMaster[a+1]);
     }
     trigger=false;
    Serial.println("Trigger 2");
    Serial.println(trigger);
    
    //As the LAST thing we need to do today and only once... frame ChInt array as max value-flicker point... so it can be correctly set in Insolation
    for (byte a=0; a<6;a++){
      ChInt[a]=(ChMax[a]-flicker[a]);
    }
  }//Finally end loop if trigger is true of hr=0 min=0 sec=0 .... i.e. basically entire function runs only 1x per day or on restart of controller
  else if (trigger==false){
    return;
   }
}//END FUNCTION

void Insolation()
{
 
  if (InsolationAdvance==true){//only change lights every 3 seconds
    Serial.println("Insolation 2 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){
   for (byte b=0;b<12;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]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b])+flicker[b];
             Serial.print("ch0 setting first 1/2 day=");
             Serial.println((byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b])+flicker[b],6);
             break;
            case 2:
              ChannelValue[1]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b-1])+flicker[b-1];   
              break;
            case 4:
              ChannelValue[2]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b-2])+flicker[b-2];
              break;
            case 6:
              ChannelValue[3]=(byte)(-cos(PiHalf+(ChSlope[b])*secSoFar)*ChInt[b-3])+flicker[b-3];
              break;
            case 8:
              ChannelValue[4]=(byte)(-cos(PiHalf+(ChSlope[b])*secSoFar)*ChInt[b-4])+flicker[b-4];
              break;
            case 10:
              ChannelValue[5]=(byte)(-cos(PiHalf+(ChSlope[b])*secSoFar)*ChInt[b-5])+flicker[b-5];
              break;
          }   
      }
      else if (elapsedTime<ChRiseSet[b]){
        switch (b){
          case 0:
            ChannelValue[0]=0;
            break;
          case 2:
            ChannelValue[1]=0;
            break;
          case 4:
            ChannelValue[2]=0;
            break;
          case 6:
            ChannelValue[3]=0;
            break;
          case 8:
            ChannelValue[4]=0;
            break;
          case 10:
            ChannelValue[5]=0;
            break;
         }
      } 
    }
  }  
   if (elapsedTime>=midDay){
     for (byte b=1;b<12;b=b+2){
    
       if (elapsedTime<=ChRiseSet[b]){
         secSoFar=(elapsedTime-midDay);
           switch (b){
             case 1:
               ChannelValue[0]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-1])+flicker[b-1];
               Serial.print("ch0 setting second 1/2 day=");
               Serial.println((-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-1]),6);
               break;
              case 3:
                ChannelValue[1]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-2])+flicker[b-2];    
                break;
              case 5:
                ChannelValue[2]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-3])+flicker[b-3];
                break;
              case 7:
                ChannelValue[3]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-4])+flicker[b-4]; 
                break;
              case 9:
                 ChannelValue[4]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-5])+flicker[b-5];
                 break;
              case 11:
                 ChannelValue[5]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-6])+flicker[b-6];
                 break;
           }
        }
        else if (elapsedTime>ChRiseSet[b]){
           switch (b){
          case 1:
            ChannelValue[0]=0;
            break;
          case 3:
            ChannelValue[1]=0;
            break;
          case 5:
            ChannelValue[2]=0;
            break;
          case 7:
            ChannelValue[3]=0;
            break;
          case 9:
            ChannelValue[4]=0;
            break;
          case 11:
            ChannelValue[5]=0;
            break;
         }
       }
     }
   }
 
  InsolationAdvance=false;
   Serial.print("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]);
 }
}//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 byte  CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static byte 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
// see insolation elapsed time function for assigmnent of Cloud start times and set of Cloud=true
//check to see if were having a scheduled cloud
 if (Cloud==false){
      for (byte a=0; a<=(CloudsTotal-1); a=(a+2)){
         if ((elapsedTime>=*(pCloudPoint+a)) && (elapsedTime<=*(pCloudPoint+a+1))){//time needs to be within a cloud segment, not just greater than any start
             CloudEnd=*(pCloudPoint+(a+1));
             CloudEnd=CloudEnd-120;
             Cloud=true;
          }
          else{
             Cloud=false;
             CloudCover=0;
             PriorCloudCover=0; 
          }
       }
   }  
 else if ((Cloud==true) && (Storm==false)){
     if (CloudAdvance==false){//use millis tracker to run this loop every 2 seconds
       return;
     }
    CloudAdvance=false;//if were going to run this, stop it from happening again until time advance
    boolean skipthis=false;
    Serial.println("in a clouddoing stuff every 2 seconds");
       //now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times u
       //seems backwards since CloudCover has not been calculated if you read linearly but it will always be set prior to this=true
      if (elapsedTime>=CloudEnd){
        skipthis=true; 
      }
          
       /*Use fractional intensity to set minimum value for any channel its WAY to confusing to try to set up otherwise.  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. (CloudCover/100)*Insolation SetPoint 
      is how the current cloud intensity is set*/
        if (skipthis==false){
          randomSeed(millis());
          byte stepsize=random(-10,11);            
          PriorCloudCover=CloudCover;
            CloudCover = CloudCover + stepsize;
               if (CloudCover>=80){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms change this value to a lower number
                  Storm=true;
                  wtrigger=true;
                  StormStart=elapsedTime;
                }
                else if (CloudCover<=0){
                  CloudCover-=(stepsize*1.5);
                }
        }
        for (int a=0;a<6;a++){
           if (DimOrder[a]==0){
              ChannelValue[a]=(byte)((ChannelValue[a]-flicker[a])*(float)(1-((float)(PriorCloudCover/100))))+flicker[a]; ;   
           }
           if (DimOrder[a]==1){
              ChannelValue[a]=(byte)(((ChannelValue[a]-flicker[a])*(float)(1-((float)CloudCover/100)))+flicker[a]);  
           }
        }
        if (skipthis==true){ 
           ClearSky(CloudCover, CloudEnd);
        }
  }  
//enable a flag sent from controller to triger a storm, i.e. Storm=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) && (Storm=true)) || ((Cloud=false) && (Storm=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){
         int LongestStorm=((CloudEnd-StormStart)-120);//remove the last 2 minutes I use to clear the sky from the longest possible storm duration
         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
         StormStart=elapsedTime;
         StormEnd=((StormStart+StormDuration)+elapsedTime);
         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.
     
 
     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
       Storm=false;
       wtrigger=false;//allow next strom to be randomized correctly
       return;
     }
     byte stepsize;
     if (StrikeNow==false){
         // step through intensity values
         randomSeed(millis());
         stepsize=random(-15,16);
           if ((stepsize<-12) || (stepsize>12)){ //it is convient to use this random call to generate a strike during a storm for more frequent strikes adjust down
             randomSeed(lastmillis); //start random with a new value to avoid repetitive stike generations
             StrikeNumber=(random(2,10)); //random high =x-1 so max strike =9 and array=17 for StrikeMaster
             int StartStrike=0;
               //Rather than bother with Vector and dynamic allocation with push/pull use an array big enough to accomadate the largest strike pattern and then
               //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 Strike Start millis, strike end millis, etc... total of StrikeNumber pairs.
               for (byte a=0;a<18;a++){
                 if (a>=(StrikeNumber*2)){
                     StrikeMaster[a]=0;
                 }
                 else if (a%2==0){
                     StartStrike+=random(100,701);//position 0,2,4,6,8.. is strike delay
                     StrikeMaster[a]=StartStrike;
                     StartStrike=0;
                 }
                 else if (a%2!=0){
                     StartStrike+=random(30,81);//position 1,3,5,7,9... is strike duration- added to StartStrike=StrikeEnd  
                     StrikeMaster[a]=StartStrike;
                     StartStrike=0;
                 } 
               }
             StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
           }
      strikeCount=0;
     }
         PriorCloudCover=CloudCover;
         CloudCover = CloudCover + stepsize;
       
           if (CloudCover>=100){//will never set intensity below flicker point... so no issue with this.
              CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
           }
           else if (CloudCover<=0){
             CloudCover-=(stepsize*1.5);//if were 100% intensity of light in a storm reflect random path to less intensity
           }

         for (int a=0;a<6;a++) {
             if (DimOrder[a]==0){
                  ChannelValue[a]=(byte)((ChannelValue[a]-flicker[a])*(float)(1-((float)(PriorCloudCover/100))))+flicker[a];  
             }
             else if (DimOrder[a]==1){
                   ChannelValue[a]=(byte)((ChannelValue[a]-flicker[a])*(float)(1-((float)(CloudCover/100))))+flicker[a];  
             }
             if (Wchannel[a]==1){
                 ChannelValue[a]=0;//turn off whites during a storm
             }
         }
   }//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(byte CloudCover, int CloudEnd)
{
 
    // no need to restrict time to avoid resource hogging, its only called every x seconds from weather.
    //I AM ASSUMING THAT everything calling this does so with 120 seconds left in the cloud/storm etc to clear the sky.
    //if you do this incorrectly (i.e. change this and dont change it everywhere I am not sure what would happen for certain, but it may cause issues... almost certainly will
    int elapsed=(120-((CloudEnd+120)-elapsedTime));//For ease of all other calculations previousl in weather... CloudEnd is set as ArrayEndpoint-120... so I need to add it back here... but only here
    
    int LightNeeded=CloudCover;//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.
 
    int slope=((LightNeeded*100)/120);//trying to convert float to decimal math... 70*100=7,000/120=58 as int which is close enough i.e. every second increase by 58 then correct to int with /100
   
    if (elapsed<=120){
      Storm=false;
      Cloud=false;
      return;
    }
    if (InsolationAdvance==true){//using Int converted decimal math we should typically see an advance in intensity as integer every 3 seconds, not much sooner so limit to this interval
      int LightAdvance;
      LightAdvance=(CloudCover-((elapsed*slope)/100));//were reducing CloudCover from start to zero over 120 seconds every 3 seconds... so this goes from 0-cloud cover over 120 seconds (approximately) and value trends to zero
      for (byte a=0; a<6; a++){ 
         ChannelValue[a]=(ChannelValue[a]*(float)(1-(LightAdvance)));//finally need to convert to float to get setpoint- dont think you can avoid the float conversion... but only here is it required
      }
    }
}//End Clear Sky function
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

A few observations-

There remain a few things that need looking into, seems like it gets sunrise and sunset right some times but also seems to have a bug related to exiting storms or something like that, somehow its setting correctly but the next morning I am sometimes not getting lights on but when I restart the system the lights come on normally... so its probably something stupid with the new day calculation at midnight, as I never did test that until now. Have not seen any lightning, and I am not entirely sure if the cloud effect is running as intended, I need to sit down with a volt meter to make sure during what I KNOW is a cloud...so its working but only mostly, I would not yet put this on a tank and rely upon it to produce the required photo periods. I am guessing this will be easily solved and is a minor issue, just need to look at the weather loop a bit more closely. Basically, I am going to speed up the cloud, make bigger changes in intensity, and look the code over closely and then let it run for the weekend and note issues that appear (if more arise).

Still not sure I have ever seen a cloud, as I am home only at the beginning and end of the light cycles... The weekend will tell more. At least its baseline (with obvious bugs) functional, all the offsets are working, sun rises right to left blues first, sets left to right whites first, then blues go off then violets finally set for lights out etc etc...
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok... I guess I get beaten by the stupid stick again.
I found (the final I hope) if statement that I used VBasic syntax in (i.e. if (x=y) rather than x==y).

This should fix the issue I was having in getting stuck in a storm... I believe

But now I am getting a error on compile that I have not seen, I looked it up and to be honest I don't see where its coming from.... I think I know why (read to end) but an unsure how to check for this, I blame the library update (or perhaps my choosing a variable that is used in a newer library, chicken and egg I guess).

I am declaring a global variable

boolean Storm;

which is being set to false in the setup (as a just to be sure step) and then is triggered variously to true or false depending on if the storm is starting or ending etc etc....

I only declare it ONCE, and I have checked.

But on compile it gives me this error

expected un-qualified id before numeric constant

and pulls up every instance of a toggle to the boolean variable in the code



LatLong_PMW_Backup031612:58: error: expected unqualified-id before numeric constant
LatLong_PMW_Backup031612.cpp: In function 'void setup()':
LatLong_PMW_Backup031612:98: error: lvalue required as left operand of assignment
LatLong_PMW_Backup031612.cpp: In function 'void Weather()':
LatLong_PMW_Backup031612:724: error: lvalue required as left operand of assignment
LatLong_PMW_Backup031612:761: error: lvalue required as left operand of assignment
LatLong_PMW_Backup031612.cpp: In function 'void ClearSky(byte, int)':
LatLong_PMW_Backup031612:834: error: lvalue required as left operand of assignment

and the basics are that the code always looks like this

if (elapsed<=120){
Storm=false;
Cloud=false;
return;
}

of some variation of it... this specific line is in the error (it is line 834 if you care). NOTHING changed with this, so I don't get the error, it was compiling previously just fine and I was running the program... what gives?

Do I need a bigger stupid stick??

seems a waste to post the entire thousand lines of code as none of this part has been changed... but let me know and I will put it up.

My only guess is that this is defined somewhere in one of the library files I have referenced... but thats impossible since it was working for the last few months of testing... aha....

I UPDATED MY LIBRARIES.... is this why?????????????

10 bucks says it is.... and that Storm is now some numeric constant specified in one of the #include statements I make to the RA libraries.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

guessing this was the correct thinking, changing it to boolean IsStorm; and changing out all other instances fixes compile error...
WOuld appreciate confirmation to be sure I did not just gloss over another problem I am unaware of.
binder
Posts: 2865
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: C library for Sun/Moon effects

Post by binder »

rufessor wrote:guessing this was the correct thinking, changing it to boolean IsStorm; and changing out all other instances fixes compile error...
WOuld appreciate confirmation to be sure I did not just gloss over another problem I am unaware of.
Open up Globals.h. Around line 612....
here's the exert:

Code: Select all

/*
Vortech Modes

Used by the RF Expansion Module
 */
#define Constant      0
#define Random1       1 // Lagoonal
#define Random2       2 // Reef Crest
#define ShortWave     3
#define LongWave      4
#define Smart_NTM     5 // Nutrient Transport Mode
#define Smart_TSM     6 // Tidal Swell Mode
#define Feeding_Start 7
#define Feeding_Stop  8
#define Night_Stop	  8
#define Night         9
#define Storm         10
#define Custom        11
#define Slave_Start   97
#define Slave_Stop    98
#define None          99
#define Radion        100
Look and you will see "#define Storm 10". I'm pretty sure that's been in there a while too but I don't recall. I haven't looked through the history yet.

As a side note, I personally try to use this as a convention for my variable names:
All Caps for #define statements or constants
Lower case letter for variable type followed by variable name for all variables in a mixture of upper & lower case letters.
Sometimes I will put a m_ for a class member variable. Sometimes I will put a g_ for a global variable.
I "try" to follow this convention. It's a little tricky to do and since there are several people contributing code to the libraries, it's tough to keep things straight. I haven't just gone through and changed everything because when I do that, lots of peoples code will break and often times there's not a major benefit from it.
Anyways, that's just a side note on variable naming conventions.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Thanks-

I am not completely sure when I last had updated... but it was working with the last major update some months ago and then as of a few days ago I updated to whatever is the current version (using the mac updater) which then led to the error. I am pretty sure that
#define Storm

line did it.

I changed mine to IsStorm and it now compiles fine....

I *Think* I am kinda sorta following convention, I do not however use any #define statements in my code. I was under the impression that this was for a CONSTANT, i.e. the compiler simply replaces and instance of the keyword XX after the #define XXX in the code with whatever XXX was set to in the define statement. Since I have very few instances of constants in my code I use the header portion of the code to set up any variables (of any type, array, int, boolean, float) that are required within more than one function within my code. So since I have many instances in which this is the case, my code is "bloated" with tons of variable declarations but no #define statements.

I am (obviously) doing this for my own "fun" but also based upon some comments from Roberto, trying to make it portable by for instance enabling the DST calculation to be flagged OFF for individuals who do not use DST corrections for time. Should anyone end up using this they will need to play with a few (not many) mostly arrays that specify which lights are blue/white, and then with the "order" physically of the arrays over the tank to get the effects in terms of sunrise and sunset to work "right" as well as the could effect. Who knows if this will ever occur... for now I just want it to work on my tank. I *think* that its got to be really close with the changes I made and errors I caught so I will post back when I reload tonight and let it run for a few days.

Curious, thats what I am. I once was able to find video of someone who had something (I THINK) similar where it appeared that their LED would rapidly fade in and out for a cloud, and it looked super cool... so I am hoping this is what I get.

THanks.

If you have any plans for this *should* it work, maybe PM me with a list of stuff that you would like to see. I am totally willing to work on it to get it out for wider use but don't have any background on this- at all. I taught myself C++ to write this and it may or may not be user friendly, its always hard to read other peoples code (for me) so I kinda tried to format it according to some conventions I found on line in developer forums and follow rules or guide lines when possible so that it would at least be consistent. Changing variable names is not too difficult... but I HATE that the find and replace finds instances of say... Cloud in all these statements.

I.e.

int Cloud;// which is what I want to find

but also in

CloudEnd etc etc... everywhere there is a Cloud inside a phrase... anyway to get it to ignore that stuff... else changing is going to be more painful. Probably need to go to XCode or something, which I am currently playing with...
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: C library for Sun/Moon effects

Post by rimai »

Sorry.. :(
I think it was me... :oops:
Roberto.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

LOL!!!

Hardly worth an apology. Its not like Storm was some original call on my part! And its really my mess... I am working in YOUR environment and loving it!

I reloaded fixed code after self administered beatings with the stupid stick... at least its appearing to work for now but the sun was in the process of setting just as I loaded code so I saw white light for about 30 seconds on one side of the tank then blues... will probably have to wait for the weekend again to catch a storm/cloud effect. But I SHOULD know if I fixed something by tomorrow evening, it was getting stuck in storms with whites off... so tomorrow (unless its not a cloudy day.. which is random) if its not stuck on BLUE when I get home, there is about a 70% chance I fixed it (which is the random cloud chance I have currently). Weekend will tell for sure. Probably other issues will crop up... but at least its kinda sorta working.

I was just happy that I had the right idea on the compile error :idea: I hate to say this, but once this works I am going to have to actually use the controller and learn how to really program that! For now its monitoring my temperature and doing a BANG UP JOB displaying that on the LED :lol:

I am going to go back to square one figuring out how to set up the relays for different modes etc... but I think I should be able to move a bit more quickly given this experience. Probably should have done it in reverse- but this part seemed more interesting :geek:
cranemaster
Posts: 3
Joined: Sun Apr 22, 2012 6:48 pm

Re: C library for Sun/Moon effects

Post by cranemaster »

Rufessor, do you have any updated data for your system? I am currently assembling an 8 channel LED lighting system using 3 PWM expansion outputs for Cree cool white LED's (78), 3 PWM expansion channels for Cree Royal Blue LED's 75), and the 2 PWM channels on the controller for Cree blue LED's (30). I would love to try your programming for my system. This system is over a 180 tank that is 72" in length, and if my calculations are correct should have more than adequate PAR at below full output for the deepest tanks.

I had the same idea to travel the lighting intensity across the lighting array to simulate the traverse of the sun (as well as the moon). I have been working on coding to do the same but have approached the problem from a different viewpoint.

I have uploaded year long sunrise.sunset table from the US Astronomical Society as well as their moonrise/moonset table. I then import this to Excel which I then import using C commands into a 2 dimensional array data file. My program then pulls the data from the array.

This is much simpler than the method you are using, as I don't need all the calculations necessary for determing the data in the tables. Several tables can be stored for multible years just renaming the tables so the program draws data from the proper table for a particular year which increments each year (I have 10 years stored).

Though not nearly as elegant as yours, it should function and take less memory.

I have not tried to write the code for clouds and storms, and your program seems to accomplish all of this.

Please let me know how your beta testing is looking.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Hi-

Thanks for the interest-

It is currently still a "beta" thats for sure, I have not completely fixed the issues I was and still am having, but I have spent in total about 10 minutes on it in the last 10 days or so as the last few weeks or more have been consumed with other responsibilities that must be placed higher in the que of things to do. I hope to actually get time to work on this this week as things are easing up a bit for the moment. I think you said you had 8 light strings... that might be somewhat of an issue, as I wrote this to work predominantly with a single PWM output module and did not account for the possibility to use the two on the controller as well, but its just a bit of looking to be sure the arrays will work. Let me get it working to my satisfaction, then we can PM about moving to your system, very possible and once I have it trouble free, probably only a very little bit of modification.


Check back here, I will post when its working to my satisfaction, or with additional questions if I run into issues I dont fully grasp.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Found a "*small*" math error, adding anywhere from 0-~75,000 seconds to the length of a storm, depending upon where in the day you were. I guess this would make it appear that you were stuck in a storm! :mrgreen:

We shall see if this was the issue.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok-

So although I am not yet prepared to declare it working, I am feeling like its down to a very few issues and have it running on my tank now. So- time to get back into my completely clueless hat (damn, did I ever take that off?)

So I left some serial comm stuff in the program because I knew I would want to communicate with the main controller. I am now at the point where it would be useful to have this going so that I could tell what the PWM output values were without a voltmeter, see if it was supposed to be cloudy, stormy, and check elapsed time and sunrise sunset without using the serial monitor, cause I need my lap top and everytime I start up the serial monitor (I.E. plug it back in) it basically resets the controller and then I end up having to wait another day or two to see if its working correctly as I am now checking that the overnight calculations occur but if I pulg and unplug the laptop I loose all that as it resets.

SO. 1st- my controller is NOT doing anything currently really, its plugged in mounted and the temp probe is in the tank, but I dont have anything plugged into it, not even the pH probe since I knew I wanted to get the lights done FIRST.

Therefore-

IF I want it to do the following,

Run the Refugium lighting, main pump, skimmer, and both heaters as well as both in tank powerheads (these may not be good for wavemaker functionality but at least would like to be able see that they are ON) based upon power to one of the outlets on the strip connected to the main controller, monitor temp, pH, and then allow for say a feeding/water change mode where the main pump and skimmer shut down... and then display on the main unit the following,

Sunrise
Sunset
Current time (I presuppose they will be using the same, do I need to enable this somehow?)
CLoud (only when its cloudy)
Storm (only when its stormy)
and Channel 0-5 current intensity output (0-255)
pH
Temp,
Powerstrip ON/Off as say green or red dots (whatever)

Whats the best way to start, i.e. run a RAgen to create a basic script, then put in the comm, and display? Or is there a somewhat pre-built code I could grab.

Wish I felt confident about this part, but its starting from zero as I have focused on the coding part, so now... I need to learn more about the controller. Not starting from zero but have played MUCH less here.

Basically, if I can get a handle on a prebuilt set of code I am fairly confident I can follow it and modify it where needed based upon my experience writing this... I feel like I can now read and write code but I am sure I will be tripped up a bit here and there.

I think If I get it to do this much, should people have say an RF module with a Vortech pump, if I have it bringing in information to say that its a storm, they would then easily be able to use that to change modes on the pump etc etc.

For me, its the final stage of both trouble shooting as well as functionality. Reason I ask, is if I run RAGen I will end up with a ton of adds... so if anyone knows some .ino file that has comm to the PWM board built in, and a little display mod built in, I could probably modify this faster than building it- maybe.

ADVICE?
binder
Posts: 2865
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: C library for Sun/Moon effects

Post by binder »

Have you looked at the Example Code from the Installed inside Arduino? That contains a lot of good examples. It's under File->Sketchbook->Examples Codes.

I would suggest you just generate a quick "default" base code from RAGen and add the following features: Custom Main Screen, Custom Menu (have it mimic the Simple Menu), PWM Expansion.
Then obviously set your devices on the proper ports and what ports you want toggled on/off during the modes. Don't get carried away....keep it simple.

This will give you a nice and clean base code to start. Once you have this base set, use some of the examples to tweak a main screen and then tweak some of your menu entries to trigger modes and such.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Thanks-

I will do EXACTLY as you say with RAGen and move ahead from there. Was trying to decide what was best way forward, thanks for the advice.
binder
Posts: 2865
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: C library for Sun/Moon effects

Post by binder »

rufessor wrote:Thanks-

I will do EXACTLY as you say with RAGen and move ahead from there. Was trying to decide what was best way forward, thanks for the advice.
No problem at all. Let me know if you need help with things.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

OK-

I am prepared to say that I *think* that the code is now working with a few unknowns.

I am fairly sure I am getting correct sunrise and sunset values and the lights are coming on over my tank according to the offsets I have programmed. I caught a 1 hr problem so the actual out the window sunrise and sunset should now match perfectly to a few seconds the tank rise and set.

I am less sure that the cloud/storm affects are entirely working, but if others load and run this it will actually help with trouble shooting this. I do think that at least it will no longer get stuck in storms.

IF your interested in trying this on your tank, I think I am confident enough to say lets give it a try. Problems if any should be fairly easy to fix and I will try to be responsive to issues and trouble shooting.

Two things-

It may NOT be visually apparent that the lights are dimming or brightening over the tank during a cloud as you WATCH the tank in real time, but it should become OBVIOUS as they dim to very low levels. It would help to figure out what type of ramp/sec values I need to use to get a visual effect - the current values are what I think should be almost TOO much light variation but what I think and what is visually apparent are not probably perfectly correlated. Feed back if its used would be very welcome

When you hit a storm (which may or may not happen often enough... so let me know) the tank will go to ALL blues but STILL ramp from the flicker point to the 100% intensity value you set and should ramp more quickly, changes in intensity every 1 second. I would anticipate that this should be immediately apparent as I have it set to maximally ramp by 10% in intensity every second... but its a random number so I MAY need to move this to 20 or even 25 % as the random ramp point will vary from +/- the max value, and thus would be expected to kinda center on zero... so I might need to re-evaluate this but other examples of random walks using similar algorithms do show random walks. Let me know what you see.

You will or at least SHOULD get lightning at some point in a storm. It should FLASH the whites some random number of times in a row with random spacing between the flashes of up to about 1.5 seconds with the flash duration randomly centered on the duration of a real lightning strike (like 40-80 msec or something)

I have not yet been home to see any of the storm/cloud stuff but its RUNNING over my tank and I am getting lights on and off as scheduled and thus am ASSUMING that this part is either working, or at least not killing the program. IT IS POSSIBLE that I have not hit a cloudy day and that there is a serious error that will result in your lights going to blue and not coming back or something like that (thats about the worst case).


The lights will NEVER be set above your max values (0-255 output on PWM Channel0-5)

For now, I guess I will post the code.

Anyone trying this, grab the code- LOOK through it more or less line by line with some emphasis on the header then PM me and I will try to get you all the information on what NEEDs to be customized. Once I get this into a list that works for one person I will reproduce that list here as a how to guide.

Please be aware you will be helping to develop this and thus it may not work 100% at this point, but I am confident enough to put it up for real use now hoping that any additional errors will be caught MORE quickly with other users trying this out.

KEEP Your old PWM code that your using and be ready to load it if you run into trouble. I am going to try to be responsive but this is NOT in any way part of my normal job/schedule and I may or may not see PM or posts here as quickly as the true developers do so be patient and simply move back to what was working for you if things go wrong, BUT LET ME KNOW.

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 <Globals.h>
#include <ReefAngel_Features.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <avr/wdt.h>
#include <SWFLTEK_EPHERMA.h> 


//*********************************************************************************************************************************
//IF YOU DO NOT WISH TO USE the U.S.A. Day Light Savings Time rules to modify the time stamp for rise/set calculations...set to false- i.e. if you dont live in the USA etc
boolean ApplyDST=true;
long elapsedTime;//used multiple places as elapsed since new day.
unsigned long elapsedMillis;//used in strike timing
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long rise;
unsigned long set;
unsigned long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long ChRiseSet[12];
float ChSlope[12];
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[6];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)


//set up channel max intensity arrays. comment is just illustrating value range possible
byte ChMax[]={220,220,220,220,220,0};//max values you want to hit at solar noon scaled as byte values for LED output PWM write

byte flicker[]={26,26,26,26,10,0};//need to input actual values here
byte ChInt[5];
//YOU MUST CHANGE THIS- designate channels of white light  (channels you want off during Storm and used for lightning strikes)
//use 1 to designate white channel.  Array corresponds to PWM channel 0-5 in order
boolean Wchannel[]={1,0,1,0,0,0,0}; 
unsigned long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[17];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte StrikeCount;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop

byte cmdnum=255;
byte datanum=255;

boolean trigger, trigger2; //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, Cloud, CloudToday, StrikeNow;
boolean IsStorm;// what they seem, is it DST?, Are we in a cloud?
byte CloudsTotal; 
byte strikeCount;//required to know how large the array for CloudMaster will be- I need to use Vectors.... I know.
long *pCloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
unsigned long lastmillis;// variable to track millis to enable cloud and insolation loop restriction by time
boolean CloudAdvance=false;//cloud timer for light effect advance
boolean StormAdvance=false;//storm timer for light effect advance
boolean InsolationAdvance=false;//when true we recalculate light intensity during clear sky
byte counter;//used to track millis advance for insolation,cloud trigger

//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,};
//****************************************
//END HEADER/Global Variable declaration//
//****************************************
//Setup
void setup()
{
    Serial.begin(57600);
    // Wire.begin(8);
    // Wire.onReceive(receiveEvent);
    // Wire.onRequest(requestEvent);
    randomSeed(analogRead(0));
    pinMode(3,OUTPUT);
    pinMode(5,OUTPUT);
    pinMode(6,OUTPUT);
    pinMode(9,OUTPUT);
    pinMode(10,OUTPUT);
    pinMode(11,OUTPUT);
    //wdt_enable(WDTO_1S);
    setSyncProvider(RTC.get);   // the function to get the time from the RTC
    setSyncInterval(SECS_PER_HOUR);  // Changed to sync every hour.
    
    now();//why are you here?
    CloudToday=false;
    trigger=true; 
    StrikeNow=false;
    Cloud=false;
    IsStorm=false;
    lastmillis=millis();//start our millis timer now
    counter=0;
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop()
{
    wdt_reset();
    //Serial.println(ChannelValue[0],DEC);
    if (cmdnum!=255){
        ProcessCMD(cmdnum,datanum);    
        cmdnum=255;
        datanum=255;
    }
    if ((hour())==0 && (minute())==0 && (second())==0){
      trigger2=true;//decided to put this in loop to avoid continued redundant calls to CalSun()- note trigger is set to true on resart
    }
    //Use millis to enable tracking of time interval
    if ((millis()-lastmillis)>=1000){
        StormAdvance=true;
        lastmillis=millis();
        counter++;//use this to see if time has moved ahead 2, or, 3 seconds from first check which is 1 second advance
        switch (counter){
            case (2):
                CloudAdvance=true;
                break;
            case (3):
                InsolationAdvance=true;
                counter=0;
                break;
        }
    }     
    
    // now run through rise/set calculation and then intensity set and finally weather overlay
    
   if ((trigger==true) || (trigger2==true)){
    CalSun();
   }
    Insolation();
    Weather();
  //check to see if were need to have a lightning strike
    if (StrikeNow==false){
        StrikeStart=millis();
        StrikeCount=0;
    }
    if (StrikeNow==true){
        elapsedMillis=(millis()-StrikeStart);
        
        if (StrikeCount>StrikeNumber){
            StrikeNow=false;
            StrikeCount=0;
            elapsedMillis=0;
        }  
        int additiveDelay;//value combining dealys across array to the stike number were at
        for (byte a=0; a=StrikeCount;a++){//StrikeCount goes by 2 and starts at zero... so just add it up
            additiveDelay+=StrikeMaster[a];//if were at zero we get 1 val... if were at 2 we get first two delays
        }
        if (elapsedMillis>=additiveDelay){
            byte intensity;
            intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
            for (byte a=0; a<6; a++){
                if (Wchannel[a]==1) analogWrite(PWMports[a],intensity);  
            }  
            delay(StrikeMaster[(strikeCount+1)]);
            StrikeCount+=2;
        }
    }
  
    //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.
    if (isDST==true){
        elapsedTime=(((now()-946684800)+3600)-newDay);
    }
    else if (isDST==false){
        elapsedTime=((now()-946684800)-newDay);
    }
    
    //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],ChannelValue[a]);
    }
}
//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();
    // Individual Channel
    if (cmd>=0 && cmd<=5){
        ChannelValue[cmd]=data;
        analogWrite(PWMports[cmd],data);
    }
    if (cmd==6){
        //Storm=true;
    }
}
//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()
{  
   
    // Every day at midnight, we calculate rise, set, time of highest sun, moon phase, or we do this if system has reset
    
      //  Serial.println("trigger 1");
      //  Serial.println(trigger);
        
        unsigned long SecInput;
        
        //void CalcDST(day(),month(),weekday()) modifies isDST memory to appropriate true/false based upon USA rules
        if (ApplyDST==true){
            CalcDST(day(),month(),weekday());
        }
        //Using time library from Arduino.time_t now(); returns seconds since 1970
        //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
        
        
        if (trigger==true){//ON RESET need to calculate how far into day we are to feed correct val to sunrise sunset for midnight
          long hours;//possible to be reset at 23 hours*3600=long required
          long minutes;// int is enough as 60*60 is max val but math is conducted with possibility of roll over in intermediate so to be sure.. use long
          time_t t=now();
          hours=hour(t);
          minutes=minute(t);
        
          if ((ApplyDST==true) && (isDST==true)){
            hours=hours+1;//DST correction
            newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your rise being off by <60 seconds on a day of a power failure... your more anal than I
               /* Serial.print("DST Hours,Min");
                Serial.print(hours);
                Serial.print(",");
                Serial.println(minutes);*/
           } 
           else if ((isDST==false) || (ApplyDST==false)){  
            
                newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your rise being off by <60 seconds on a day of a power failure... your more anal than I
               /* Serial.print("Hours,Min");
                Serial.print(hours);
                Serial.print(",");
                Serial.println(minutes);*/
           }
        }
        if (trigger2==true){// When we are at midnight we just use simple correction of unix time to year 2000 time keeping
            if ((ApplyDST==true) && (isDST==true)){
                newDay=(now()-946684800+3600);
            }
       
            else if ((isDST==false) || (ApplyDST==false)){ 
                newDay=(now()-946684800);
            }
        } 
           
         
        
        SecInput=((newDay)+25200);//modify to GMT time (i.e. MST + 7 hours = GMT)
        
        //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.
        
        //Calculate rise time and set time using Epherma Library   
        
        rise=SecInput;
        set=SecInput;
        
        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
        
        rise-=25200; //Correct back to MST from GMT- DST still accounted for
        set-=25200; 
        /*Serial.print("newDay=");
        Serial.println(newDay);
        Serial.print("Local uncorrected rise, set=");
        Serial.print(rise);
        Serial.print(",");
        Serial.print(set);*/
        
        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);
        //*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
        //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};
        //**********************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<12;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=70;//% 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
        randomSeed(now()-(random(30000,1000000001))); //so that we are now more trully random
        byte RainMaker=random(1,101); 
        if (RainMaker<CloudChance){
            CloudToday=true;
        }
        else if (RainMaker>=CloudChance){
            CloudToday=false;
            trigger=false;
            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);
        byte OvercastMin=10;//% as fraction (0-1 is 0-100%) of daylight hours that would minimially be cloudy on a cloud day
        byte OvercastMax=31;//% of daylight hours that would maximally be cloudy on a cloud day (then add 1 to that #)
        float Overcast=random(OvercastMin,OvercastMax);
        Overcast=(Overcast/100);
        //Serial.println("Overcast");
        //Serial.println(Overcast);
        // number of clouds possible for the day, max and min
        byte CloudsMax=9;
        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);
        
        static long CloudMaster[15];// 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
        pCloudPoint=&CloudMaster[0];//use address of pointer to find cloud data in weather, array is declared static to persist throughout day.
        
        // 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*/
        
        long CloudLength;
        CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
        float fraction=random(20,181);
        fraction=(fraction/100);//vary each cloud from 20%-180% of an equal fraction of total cloud for day
        //Serial.print("fraction ");
        //Serial.println(fraction);
        //Serial.print("CloudLength ");
       // Serial.println(CloudLength);
        
        //using fraction to vary cloud length throughout day fill array CloudMaster[] at positions 0,2,4,6 to full with durations in seconds
        //this way the MAX cloud duration is 180% of the average cloud druation while the min is 20%
        byte b=0;//set up counter to alter cloud length and initialize to zero outside of loop
        for (byte a=0; a<CloudsTotal; a++){
            int CurrentCloudLength;
            // if were having an odd # of clouds make sure last one is full length
            if ((CloudsTotal%2!=0) && (a==(CloudsTotal-1))){
                CloudMaster[a*2]=CloudLength;
                /*Serial.print("last odd cloud Cloudmaster[a]=");
                Serial.println(a*2);
                Serial.print("fraction=");
                Serial.println(fraction);
                Serial.print("CloudMaster=");
                Serial.println(CloudMaster[a*2]);*/
            }
            // else we make clouds variable in length short then long repeated with every two cloud sets being different in short and long but adding up equally by sets
            else {
                if (fraction<1){
                    CurrentCloudLength=abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
                    CloudMaster[a*2]=CurrentCloudLength;
                    /*Serial.print("Cloudmaster[a]=");
                    Serial.println(a*2);
                    Serial.print("fraction=");
                    Serial.println(fraction);
                    Serial.print("CloudMaster=");
                    Serial.println(CloudMaster[a*2]);
                    Serial.print("b=");
                    Serial.println(b);*/
                }
                if (fraction==1){
                    CloudMaster[a*2]=CloudLength;
                    /*Serial.print("Cloudmaster[a]=");
                    Serial.println(a*2);
                    Serial.print("fraction=");
                    Serial.println(fraction);
                    Serial.print("CloudMaster=");
                    Serial.println(CloudMaster[a*2]);
                    Serial.print("b=");
                    Serial.println(b);*/
                }
                if (fraction>1){
                    CurrentCloudLength=((CloudLength*fraction)-(b*CloudLength*fraction))+(b*((2-fraction)*CloudLength));
                    CloudMaster[a*2]=CurrentCloudLength;
                    /*Serial.print("Cloudmaster[a]=");
                    Serial.println(a*2);
                    Serial.print("fraction=");
                    Serial.println(fraction);
                    Serial.print("CloudMaster=");
                    Serial.println(CloudMaster[a*2]);
                    Serial.print("b=");
                    Serial.println(b);*/
                } 
            }
            b=b+1;
            if (b==2){
                b=0;
                fraction=random(20,181);//every other cloud recalculate a new fractional distribution for the next set
                fraction=(fraction/100);
            } 
        }
        
        randomSeed(now());
        
        // Now space the clouds out during the day using random fractionation of day segments as per cloud calculation
        b=0;//reset counter used for fraction reset
        byte c=0;//counter for indexing # of itterations in loop
        long StartTime;//Used in loop to assign cloud spacing, i.e. these loop values determine daylight interval between clouds
        fraction=random(10,181);
        fraction=(fraction/100);
        for (byte a=1; a<(CloudsTotal*2); a=(a+2)){
            //calculate amount of sun during day (i.e. total day lenth - portion cloudy), divide into equal parts then randomly initiate cloud within sun segment for each part of the day.
            // must index segment time by +1 segment for each cloud completed
            long SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));
            
            if (fraction<1){
                abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
                StartTime=rise+((SunSegment*b+abs(b*SunSegment-(SunSegment*fraction)))+(c*SunSegment));
                CloudMaster[a]=StartTime;
                StartTime=0;
                b=b+1;
            }
            else if (fraction==1){
                StartTime=rise+(SunSegment+(SunSegment*c));
                CloudMaster[a]=StartTime;
                StartTime=0;
                b=2;//by doing this I ensure that new fraction will be calculated so next interval will not be 1 (or rarely so)
            }
            else if (fraction>1){
                StartTime=rise+(((SunSegment*fraction)-(b*SunSegment*fraction))+(b*((2-fraction)*SunSegment))+(c*SunSegment));
                CloudMaster[a]=StartTime;
                StartTime=0;
                b=b+1;
            }  
            
            if (b==2){
                b=0;
                fraction=random(20,181);//every other cloud recalculate a new fractional distribution for the next set
                fraction=(fraction/100);
            }
            c++;   //index loop counter by one now  
            //Serial.print("CloudMaster position number, ");
            //Serial.print(a);
            //Serial.print(" is= ");
            //Serial.println(CloudMaster[a]);
        }  
      //  Serial.println("now we print start and end times for clouds");
        //just reframe CloudMaster into array of start and end times for clouds (CLoudMaster[]={start,end,start,end,start,end} as progression through day in elapsed seconds from midnight.
        for (byte a=0; a<(CloudsTotal*2);a=(a+2)){
            long endT, startT;
            startT=CloudMaster[(a+1)];
            endT=CloudMaster[a]+startT;
            CloudMaster[a]=startT;
            CloudMaster[a+1]=endT;
            //Serial.println(CloudMaster[a]);
            //Serial.println(CloudMaster[a+1]);
        }
        
        //As the LAST thing we need to do today and only once... frame ChInt array as max value-flicker point... so it can be correctly set in Insolation
        for (byte a=0; a<6;a++){
            ChInt[a]=(ChMax[a]-flicker[a]);
        }
   
    
}//END FUNCTION

void Insolation()
{
    
    if (InsolationAdvance==true){//only change lights every 3 seconds
        //Serial.println("Insolation 2 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){
            for (byte b=0;b<12;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]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b])+flicker[b];
                            //Serial.print("ch0 setting first 1/2 day=");
                            //Serial.println((byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b])+flicker[b],6);
                            break;
                        case 2:
                            ChannelValue[1]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b-1])+flicker[b-1];   
                            break;
                        case 4:
                            ChannelValue[2]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b-2])+flicker[b-2];
                            break;
                        case 6:
                            ChannelValue[3]=(byte)(-cos(PiHalf+(ChSlope[b])*secSoFar)*ChInt[b-3])+flicker[b-3];
                            break;
                        case 8:
                            ChannelValue[4]=(byte)(-cos(PiHalf+(ChSlope[b])*secSoFar)*ChInt[b-4])+flicker[b-4];
                            break;
                        case 10:
                            ChannelValue[5]=(byte)(-cos(PiHalf+(ChSlope[b])*secSoFar)*ChInt[b-5])+flicker[b-5];
                            break;
                    }   
                }
                else if (elapsedTime<ChRiseSet[b]){
                    switch (b){
                        case 0:
                            ChannelValue[0]=0;
                            break;
                        case 2:
                            ChannelValue[1]=0;
                            break;
                        case 4:
                            ChannelValue[2]=0;
                            break;
                        case 6:
                            ChannelValue[3]=0;
                            break;
                        case 8:
                            ChannelValue[4]=0;
                            break;
                        case 10:
                            ChannelValue[5]=0;
                            break;
                    }
                } 
            }
        }  
        if (elapsedTime>=midDay){
            for (byte b=1;b<12;b=b+2){
                
                if (elapsedTime<=ChRiseSet[b]){
                    secSoFar=(elapsedTime-midDay);
                    switch (b){
                        case 1:
                            ChannelValue[0]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-1])+flicker[b-1];
                            //Serial.print("ch0 setting second 1/2 day=");
                            //Serial.println((-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-1]),6);
                            break;
                        case 3:
                            ChannelValue[1]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-2])+flicker[b-2];    
                            break;
                        case 5:
                            ChannelValue[2]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-3])+flicker[b-3];
                            break;
                        case 7:
                            ChannelValue[3]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-4])+flicker[b-4]; 
                            break;
                        case 9:
                            ChannelValue[4]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-5])+flicker[b-5];
                            break;
                        case 11:
                            ChannelValue[5]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-6])+flicker[b-6];
                            break;
                    }
                }
                else if (elapsedTime>ChRiseSet[b]){
                    switch (b){
                        case 1:
                            ChannelValue[0]=0;
                            break;
                        case 3:
                            ChannelValue[1]=0;
                            break;
                        case 5:
                            ChannelValue[2]=0;
                            break;
                        case 7:
                            ChannelValue[3]=0;
                            break;
                        case 9:
                            ChannelValue[4]=0;
                            break;
                        case 11:
                            ChannelValue[5]=0;
                            break;
                    }
                }
            }
        }
        
        InsolationAdvance=false;
       /* Serial.print("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]);*/
    }
}//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 byte  CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
    static byte 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
    // see insolation elapsed time function for assigmnent of Cloud start times and set of Cloud=true
    //check to see if were having a scheduled cloud
    if (Cloud==false){
        for (byte a=0; a<CloudsTotal; a=(a+2)){
            if ((elapsedTime>=*(pCloudPoint+a)) && (elapsedTime<=*(pCloudPoint+(a+1)))){//time needs to be within a cloud segment, not just greater than any start
                CloudEnd=*(pCloudPoint+(a+1));
                CloudEnd=CloudEnd-120;
                Cloud=true;
            }
            else{
                CloudCover=0;
                PriorCloudCover=0; 
                return;
            }
        }
    }  
    else if ((Cloud==true) && (IsStorm==false)){
        if (CloudAdvance==false){//use millis tracker to run this loop every 2 seconds
            return;
        }
        CloudAdvance=false;//reset to false when true so we run this once, until time advance is true again
        //Serial.println("in a cloud doing stuff every 2 seconds");
        if (elapsedTime>=CloudEnd){
            ClearSky(CloudCover, CloudEnd); 
            return;
        }
        
        /*Use fractional intensity to set minimum value for any channel its WAY to confusing to try to set up otherwise.  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. (CloudCover/100)*Insolation SetPoint 
         is how the current cloud intensity is set*/
        randomSeed(millis());
        byte stepsize=random(-20,21);            
        PriorCloudCover=CloudCover;
        CloudCover = CloudCover + stepsize;
        if (CloudCover>=90){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms change this value to a lower number
            long CloudLeft=(CloudEnd-elapsedTime);//Check to be sure we have enough cloud left to fit a storm in if not skip it)
              if (CloudLeft>300){
                IsStorm=true;
                wtrigger=true;
              }
              if (CloudLeft<=300){
                CloudCover-=(stepsize*1.5);//reflect to clearer sky (i.e. we had to be adding to get here so subtract val)
              }
              
        }
        else if (CloudCover<=0){//i.e if were bright sky in a cloud.. uhh.. we need a cloud
            CloudCover-=(stepsize*1.5);
        }
        
        for (int a=0;a<6;a++){
            if (DimOrder[a]==0){
                ChannelValue[a]=(byte)((ChannelValue[a]-flicker[a])*(float)(1-((float)(PriorCloudCover/100))))+flicker[a]; ;   
            }
            if (DimOrder[a]==1){
                ChannelValue[a]=(byte)(((ChannelValue[a]-flicker[a])*(float)(1-((float)CloudCover/100)))+flicker[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;
            int LongestStorm=(CloudEnd-StormStart);
            LongestStorm-=120;//remove 2 mins from longest storm so that we end up with 2 minutes of cloud after the storm
            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.
        
        
        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;
            wtrigger=false;//allow next strom to be randomized correctly, redundant call but just to be safe.
            return;
        }
        byte stepsize;
        randomSeed(millis());
        stepsize=random(-20,21);
        if (StrikeNow==false){
            // check to see if we are going to have a lightning strike now
            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
                randomSeed(lastmillis); //start random with a new value to avoid repetitive stike generations
                StrikeNumber=(random(2,10)); //random high =x-1 so max strike =9 and array=17 for StrikeMaster
                int StartStrike=0;
                //Rather than bother with Vector and dynamic allocation with push/pull use an array big enough to accomadate the largest strike pattern and then
                //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 Strike Start millis, strike end millis, etc... total of StrikeNumber pairs.
                for (byte a=0;a<18;a++){
                    if (a>=(StrikeNumber*2)){
                        StrikeMaster[a]=0;
                    }
                    else if (a%2==0){
                        StartStrike+=random(100,1500);//position 0,2,4,6,8.. is strike delay
                        StrikeMaster[a]=StartStrike;
                        StartStrike=0;
                    }
                    else if (a%2!=0){
                        StartStrike+=random(30,81);//position 1,3,5,7,9... is strike duration- added to StartStrike=StrikeEnd  
                        StrikeMaster[a]=StartStrike;
                        StartStrike=0;
                    } 
                }
                StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
            }
          strikeCount=0;
        }
        PriorCloudCover=CloudCover;
        CloudCover = CloudCover + stepsize;
        
        if (CloudCover>=100){//if were too dark reflect random path to light
            CloudCover-=(stepsize*2);
        }
        else if (CloudCover<=0){
            CloudCover-=(stepsize*2);//if were 100% intensity of light in a storm reflect random path to less intensity
        }
        
        for (int a=0;a<6;a++) {
            if (Wchannel[a]==1){
              ChannelValue[a]=0;
            }
            else if (DimOrder[a]==0){
                ChannelValue[a]=(byte)((ChannelValue[a]-flicker[a])*(float)(1-((float)(PriorCloudCover/100))))+flicker[a];  
            }
            else if (DimOrder[a]==1){
                ChannelValue[a]=(byte)((ChannelValue[a]-flicker[a])*(float)(1-((float)(CloudCover/100))))+flicker[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(byte CloudCover, int CloudEnd)
{
    
    // no need to restrict time to avoid resource hogging, its only called every x seconds from weather.
    //I AM ASSUMING THAT everything calling this does so with 120 seconds left in the cloud/storm etc to clear the sky.
    //if you do this incorrectly (i.e. change this and dont change it everywhere I am not sure what would happen for certain, but it may cause issues... almost certainly will
    int elapsed=(120-((CloudEnd+120)-elapsedTime));//For ease of all other calculations previousl in weather... CloudEnd is set as ArrayEndpoint-120... so I need to add it back here... but only here
    
    int LightNeeded=CloudCover;//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.
    
    int slope=((LightNeeded*100)/120);//trying to convert float to decimal math... 70*100=7,000/120=58 as int which is close enough i.e. every second increase by 58 then correct to int with /100
    
    if (elapsed<=120){
        IsStorm=false;
        Cloud=false;
        return;
    }
    if (InsolationAdvance==true){//using Int converted decimal math we should typically see an advance in intensity as integer every 3 seconds, not much sooner so limit to this interval
        int LightAdvance;
        LightAdvance=(CloudCover-((elapsed*slope)/100));//were reducing CloudCover from start to zero over 120 seconds every 3 seconds... so this goes from 0-cloud cover over 120 seconds (approximately) and value trends to zero
        for (byte a=0; a<6; a++){ 
            ChannelValue[a]=(ChannelValue[a]*(float)(1-(LightAdvance)));//finally need to convert to float to get setpoint- dont think you can avoid the float conversion... but only here is it required
        }
    }
}//End Clear Sky function
cranemaster
Posts: 3
Joined: Sun Apr 22, 2012 6:48 pm

Re: C library for Sun/Moon effects

Post by cranemaster »

Thanks for the heads-up! However, I have been frequently checking the forum and saw your post this morning. I have already downloaded and printed out your program, and will hopefully learn how you are doing everything. My light should be completed this week.

It's taking me a while since this is my first LED fixture and I've done extensive research with optics, and have spent a good deal of time on the phone with Cree and LuxDrive. Hopefully it will work as well as anticipated.

I will have the 3 white channels and the 3 Royal Blue channels on the expansion, and the 2 channels for the blues on the controller.

I'll update you further on what I'm changing after I fully comprehend your program.

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

Re: C library for Sun/Moon effects

Post by rufessor »

Just a quick note. I discovered a fault in how I have assigned the triggers to get the clouds to update intensity every 2 seconds. It just takes longer than 2 the way I have it written. These are the types of errors I suspect will arrise with more testing, not deal breakers but subtle errors or at least deviations from what I want to happen. I will post a correction to the few lines of code involved.

I also note that if you restart the program late at night, i.e. before midnight but such that correcting to GMT pushes the sunrise sunset calculation into the next day (i.e. I have it set to ADD 7 hours- you will need to correct this if your not in my time zone), even though its late, when it reboots it will light up the tank. Probably explained by edit I just added

EDIT-

I JUST figured out that I have been wrongly correcting the Unix time stamp to GMT... its IN GMT.


Working on this now.

I plan to implement a new variable to indicate the number of seconds of offset to GMT for YOUR time zone... for now you will need to find that correction (its 25200... in my code) if your looking for it.

Will figure out how to do this to make it more portable, fix the time advance problem (does not effect function, just timing) and anything else I spot and continue to post updates. If they are EASY to copy and paste I will just post new code with old code that needs to be pasted over, if its a distributed fix over the entire code I will simply post a new .ino in entirety as I find that its really hard for me to figure out what people want to remove/fix when they post incremental fixes to lines that are not in a block.
cranemaster
Posts: 3
Joined: Sun Apr 22, 2012 6:48 pm

Re: C library for Sun/Moon effects

Post by cranemaster »

When trying to compile your program, I get errors in CalSun of undeclared latitude, longitude, dmsToSeconds, SunRise and Sunset. Am I missing something? Also, where does dmsToSec come from?
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok... first things first. I made some changes to make it easier for others to use this, so new code will be posted after I test it (it compiles but I want to be sure it also mostly works, i.e. didn't break it further than it perhaps was).

Then

I assume you have the .h file and named it exactly as I did in the #include statement and that its in your library folder in arduino wherever that exits on your system.

Then, if you look for dmsToSec you will find it in the Void CalSun() function

It converts a latitude and longitude specified in degrees minutes and seconds to a the format required for the library (which I did not write), that is a call to the library.

SunRise and SunSet are also calls to the library- which uses pointers to modify the memory location for the variables rise and set to the values calculated by the library.

latitude and longitude are the variables that hold the dmsToSeconds and are declared in the header but initialized with a value in CalSun using the dmsToSeconds library call..

I would guess you don't have the library correctly installed. Look back a few pages... the initial description I posted at the beginning of the thread is WRONG as I was assuming things that I thought the original author wanted to be done, which in fact were NOT supposed to be done. It SHOULD be a SINGLE file.

I posted the entire file a bit ago with basic instructions to make it. Super easy.

But WAIT for the update, I had some weird crap dealing with DST corrections and was getting wrong sunrise and sunset values, I made it MUCH less complex now and I think it should work, but gotta try it 1st. Will post when I know its working or at least that its still being worked on.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok-

I think I was causing myself some strange issues with the system clock by having some old serial comm code in which was not being used but removing it fixed things... at least I cannot find the time error anymore.

I also re-wrote a few parts that were difficult for me to follow/I didn't like because I was not sure I was getting everything just right.

Now, I am confident that at least testing it tonight, its getting absolutely correct rise and set as well as elapsed times of the day that match my LOCAL time zone.

I have implemented a number of changes to the code.

Please look at the header and change everything that is on top grouped together, these are MOST but not all of the variables or arrays that are or may be different on your system.

Then in the CODE.

byte CloudChance=100; this is the percent chance of cloudy day change it if you desire
byte CloudsMax=8 is the MAXIMUM NUMBER ALLOWED DO NOT GO OVER THIS
byte CLoudsMin=4 is where I have it set, you could use 2-8 maybe not 1 (didn't fully work this out, might be ok)
byte OvercastMin=10 is where I have it, its the minimum % of daylight that will be cloudy (distributed over 4-8 clouds)
byte OvercastMax=31 is the MAX% of daylight hours that might be cloudy (distributed in4-8 clouds)
LongestStorm- LOOK in the weather function in the if loop with Storm==true you can modify the max duration of a storm, I used to let it run for a random duration of the remaining cloud, but this can get too long... so now you can limit it to XXX seconds (or XXXX or XX or whatever you want)

CHANGE all these if you want.

I think thats about it (probably missed a few but its more details like how to mod storm and cloud intensity changes but really, read the code that part is well annotated)

Then finally

THIS IS DESIGNED TO WORK WITH THE PWM EXPANSION BOX ONLY. It is not designed to run the PWM channels on the controller, easy to do probably but I would need help and someone who knows how to comm BACK to the controller to send it the set points, and a test system since mine DOES NOT HAVE THESE channels.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

OK... So I fixed a lot of stuff that was sorta not working, and managed to at one point overwrite an array by a fairly large amount (say double its size) which somehow ran into the memory spot on the PWM board for the time so that my board briefly thought it was about 1 billion seconds prior to today... which was interesting to trouble shoot.

But. Past this and I am convinced that much is working, but somehow I have managed to screw up the sunrise and sunset which was working PERFECTLY the last few days, literally I could look out my window and watch the sun hit the horizon and the lights in the tank would set (whites dim and then off)... I have my blues on extended and that worked as well.
But now its telling me sunrise is like 11 am or some screwed up value and I swear I did not F*$% with that at all.... so this is why I removed the prior posted code.

When its fixed I will put it back up, but with people wanting to try this I would rather make you wait and use something that works than figure its a POS and never try it after working on it... so please be patient. I am beginning to appreciate that this is a fairly complex bit of code for me and lots of pieces interact so since I taught myself how to code and the last computer course I took was in junior high on an apple II or something... well... bare (or is it bear) with me~!
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

I apologize for a VERY long post..... but I am stuck and its not a normal behavior and the error is complex... so lets start with the good part. I corrected the overflow of the array so I don't think this is the issue... but am confused to say the least.

Ok... as an aid to people wanting to try this, and to illustrate my problem. Here is the serial output from a debugging session after I got everything working in the CalcSun() function which does the majority of the work in terms of sunrise sunset, correcting individual channels to the offset values you want (i.e. blues on before whites and off later), calculates the number of clouds (0,4,5,6,7,8) i.e. none or 4-8, calculates the % of the day that will be cloudy, then divides it up into random length clouds and spaces them randomly throughout the day such that days with less clouds will be correspondingly less overcast as a % total of the day length, i.e. the AVERAGE cloud length will be about the same every day, so regardless of Total cloud # you will get about the same length of clouds, i.e. low cloud days will not simply have REALLY long clouds, they instead will be correspondingly shorter. It then calculates how much daylight to stuff between the clouds (randomly but so that the clouds stay within daylight hours with daylight prior to the first cloud and after the last).

The MAJOR problem I am now seeing is that somehow the processor is changing when it thinks now() is! Which is I thought something that would be impossible to achieve given that NOTHING in my code plays with this. I wonder if I am somehow getting an update from the main controller which is corrupted because of all the serial debugging going on? Would this be possible? I do HAVE the getRTC statements in the setup and loop so it I think it synching but this is beyond me. So I am going to post the code but Its CURRENTLY BROKEN> do not load it until I can figure this out.

What happens is when I first reboot the PWMBoard it executes it perfectly (see serial debugging immediately below) then it gets stuck for a few seconds, and seems to change now() by about 42000 seconds (see serial debugging posted below the code but this came off serial about 5-10 seconds after I capture the first and NOTHING else came off serial... so it running, getting stuck.. then seemingly rebooting and then has the wrong system clock time.

I have no idea if the change in time is causing the reboot or is a symptom of a reboot- seems to be a symptom because the change is NOT enough to have pushed the controller into a new day which would trigger the recalculation of CalcSun and result in serial debugging showing this running again (which it does). Since the change in now() is not pushing us to a new day.. the only other way CalcSun() would rerun (it obviously does) , is a reboot, so I think its getting corrupted somehow and rebooting, then its further screwed because now() becomes somehow WRONG> The results of the serial debugging after the pause and reboot is posted below the code, keep in mind this comes off serial about 5 seconds after the first execution.... but the time changes by 11.75 hours!

Serial debugging out put followed by code followed by symptom of problem in second serial output

Please note that the CODE is SUBSTANTIALLY ALTERED from prior versions, I fixed some things that were causing me problems and vastly improved some of the code from both a readability and simplicity standpoint. CalcSun() is VERY different than prior versions. The rest has small distributed changes.


One thing to see, the timing loop is working, if you look at the bottom of the second serial output you will see insolation advance occurs every third time that storm advance occurs (i.e. storm advance is every second, cloud is every two, and insolation is every three) so the program is actually working but for the weird issue with the "reboot" and the change in now() which also corrupts the sunrise and sunset values which is REALLY weird, because they should NEVER be the values they become for my lat lon coordinates, and its really NOT important what time of the day you feed sunrise and sunset the time, it just figures out what day it is and when sunrise and sunset are for THAT day, NO DAY IN HISTORY should have the values I get after the restart!!

Hope I can figure this out.

Here is serial debugging.

CalSun Run Now

Hours:Mins

7

15

CalSun now()=1337238940 second value on restart is CalSun now()=1337196544


newDay=390528040

Local Elapsed Seconds from NewDay--rise=

22104

Local Elapsed Seconds from NewDay--set=

74296

Rise/Set a=0

Value=21504Slope0.000058840141

Rise/Set a=1

Value=74296Slope0.000060192995

Rise/Set a=2

Value=17904Slope0.000051848311

Rise/Set a=3

Value=79696Slope0.000049872879

Rise/Set a=4

Value=22104Slope0.000060192995

Rise/Set a=5

Value=74896Slope0.000058840141

Rise/Set a=6

Value=18504Slope0.000052895889

Rise/Set a=7

Value=80296Slope0.000048940563

Rise/Set a=8

Value=17604Slope0.000051339921

Rise/Set a=9

Value=81496Slope0.000047176733

Rise/Set a=10

Value=22104Slope0.000060192995

Rise/Set a=11

Value=74296Slope0.000060192995

DayLength

52192

CloudsTotal

4

Overcast

0.10

CloudLength 1304

a is even fraction=0.41

Cloudmaster position=0

CloudMaster=534

a is odd fraction=0.41

Cloudmaster position=2

Cloud Master=2073

a is even fraction=0.56

Cloudmaster position=4

CloudMaster=730

a is odd fraction=0.56

Cloudmaster position=6

Cloud Master=1877

SunSegment=9394

even a; CloudMaster position number, 1

is= 34128

odd a; CloudMaster position number, 3

is= 40892

even a; CloudMaster position number, 5

is= 54795

odd a; CloudMaster position number, 7

is= 59680

here is cloud master in is entirety

534

34128

2073

40892

730

54795

1877

59680

0

0

0

0

0

0

0

0

now we print start and end times for clouds

34128

34662

40892

42965

54795

55525

59680

61557

0

0

0

0

0

0

0

0

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[]={220,220,220,220,220,0};//max values you want to hit at solar noon scaled as byte values for LED output PWM write
byte flicker[]={30,30,30,30,10,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}; //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};



//*******************GLOBAL VARIABLE DECLERATIONS*************************************
//Unless your planning on editing the program DO NOT CHANGE ANYTHING HERE
long elapsedTime;//used multiple places as elapsed since new day.
unsigned long elapsedMillis;//used in strike timing
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long rise;
unsigned long set;
unsigned long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long ChRiseSet[12];
float ChSlope[12];
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[5];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)
byte ChInt[5];
unsigned long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[17];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte StrikeCount;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop
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;
boolean Cloud;
boolean CloudToday;
boolean StrikeNow;
boolean IsStorm;// what they seem, is it DST?, Are we in a cloud?
byte CloudsTotal; 
byte strikeCount;//required to know how large the array for CloudMaster will be- I need to use Vectors.... I know.
long *pCloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
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);
    randomSeed(analogRead(0));
    pinMode(3,OUTPUT);
    pinMode(5,OUTPUT);
    pinMode(6,OUTPUT);
    pinMode(9,OUTPUT);
    pinMode(10,OUTPUT);
    pinMode(11,OUTPUT);
    //wdt_enable(WDTO_1S);
    setSyncProvider(RTC.get);   // the function to get the time from the RTC
    setSyncInterval(SECS_PER_HOUR);  // Changed to sync every hour.
    
    isDST=false;
    trigger=false; 
    dow=0;
    StrikeNow=false;
    Cloud=false;
    IsStorm=false;
    lastmillis=millis();//start our millis timer now
    counter=0;
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop()
{
        
    wdt_reset();
    //Serial.println(ChannelValue[0],DEC);
    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();
    }
  
    //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)>=1000){
        lastmillis=millis();
        counter+=1;
        StormAdvance=true;
        Serial.println("StormAdvance=true");
       
        if (counter%2==0){
          CloudAdvance=true;
          Serial.println("CloudAdvane=true");
        } 
        if (counter%3==0){
          InsolationAdvance=true;
          Serial.println("InsolationAdvance=true");
        }
        if (counter==255) {
          counter=0;
          Serial.println("Counter reset");
        }  
    }     
    
    // 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==false){
        StrikeStart=millis();
        StrikeCount=0;
    }
    else if (StrikeNow==true){
        elapsedMillis=(millis()-StrikeStart);
        
        if (StrikeCount>StrikeNumber){
            StrikeNow=false;
            StrikeCount=0;
            elapsedMillis=0;
        }  
        int additiveDelay;//value combining dealys across array to the stike number were at
        for (byte a=0; a=StrikeCount;a++){//StrikeCount goes by 2 and starts at zero... so just add it up
            additiveDelay+=StrikeMaster[a];//if were at zero we get 1 val... if were at 2 we get first two delays
        }
        if (elapsedMillis>=additiveDelay){
            byte intensity;
            intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
            for (byte a=0; a<6; a++){
                if (Wchannel[a]==1) analogWrite(PWMports[a],intensity);  
            }  
            delay(StrikeMaster[(strikeCount+1)]);
            StrikeCount+=2;
        }
    }
  
    //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],ChannelValue[a]);
    }
}
//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());
        }
        //Using time library from Arduino.time_t now(); returns seconds since 1970
        //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
        
       
        byte hours;//
        byte minutes;// 
        
        hours=hour();
        minutes=minute();
        Serial.println("Hours:Mins");
        Serial.println(hours);
        Serial.println(minutes);
        
         //Seems weird, but actually DO NOT correct new Day for DST 
        newDay=(now()-(946684800+(3600*hours)+(60*minutes)));//local time uncorrected for DST
        rise=(newDay+GMToffset);// Dont screw with DST in sunrise sunset input its NOT part of GMT  
        set=rise;//were not converted yet- library uses POINTERS.. to modify these values    
        Serial.print("CalSun now()=");
        Serial.println(now());
        
        //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.
        
        //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-=(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-=(GMToffset-3600); 
        }
        else if (isDST==false){
          rise-=GMToffset;
          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***************************
        //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};
        //**********************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<12;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
        randomSeed(now()-(random(30000,1000000001))); //so that we are now more trully random
        byte RainMaker=random(1,101); 
        if (RainMaker<=CloudChance){
            CloudToday=true;//this is a holding variable to trigger display on the Main currently not implemented
        }
        else if (RainMaker>CloudChance){
            CloudToday=false;//see above comment on CloudToday
            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=8;//DONT INCREASE BEYOND 8 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);
        
        static long CloudMaster[15];// 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
        pCloudPoint=&CloudMaster[0];//use address of pointer to find cloud data in weather, array is declared static to persist throughout day.
        
        // 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*/
        randomSeed(analogRead(0));
        int CloudLength;
        CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
        float fraction;
        Serial.print("CloudLength ");
        Serial.println(CloudLength);
        byte b=0;
        //using fraction to vary cloud length throughout day fill array CloudMaster[] at positions 0,2,4,6 etc with durations in seconds
        for (byte a=0; a<8; a++){
            if (a>=CloudsTotal){
               for (byte b=CloudsTotal*2; b<16;b++){//zero fill unused array positions on days with less than 8 clouds
                 CloudMaster[b]=0;
               }
               break;
            }    
                // if were having an odd # of clouds make sure last one is full length
            if ((CloudsTotal%2==1) && (a==(CloudsTotal-1))){
                    CloudMaster[(a+b)]=CloudLength;
                    Serial.print("last odd cloud Cloudmaster[a]=");
                    Serial.println(a+b);
                    Serial.println(CloudMaster[(a+b)]);
            }
                // else we make clouds variable in length with every two cloud sets being different in short and long but adding up equally by sets
            else if (a%2==0){
                  fraction=random(20,181);//vary each cloud from 20%-180% of an equal fraction of total cloud for day
                  fraction=(fraction/100);
                  CloudMaster[(a+b)]=(CloudLength*fraction);
                  Serial.print("a is even fraction=");
                  Serial.println(fraction);
                  Serial.print("Cloudmaster position=");
                  Serial.println(a+b);
                  Serial.print("CloudMaster=");
                  Serial.println(CloudMaster[(a+b)]);
                
             }
             else if (a%2==1){
                  CloudMaster[(a+b)]=(CloudLength*(2-fraction));
                  Serial.print("a is odd fraction=");
                  Serial.println(fraction);
                  Serial.print("Cloudmaster position=");
                  Serial.println(a+b);
                  Serial.print("Cloud Master=");
                  Serial.println(CloudMaster[(a+b)]);
             }
             b++;
          }  
        
        // Now space the clouds out during the day using random fractionation of day segments as per cloud calculation    
        long SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));//yields amount of sun on either side of a cloud that exactly center the clouds, next part randomizes the centering part
        Serial.print("SunSegment=");
        Serial.println(SunSegment);
        b=0;//counter for indexing # of itterations in loop
        byte c=1;
        for (byte a=0; a<(CloudsTotal); a++){
              // array has been zero filled in unused positions in last routine so dont worry here as were limited to needed space in this for loop
              //calculate amount of sun during day (i.e. total day lenth - portion cloudy), divide into equal parts then randomly initiate cloud within sun segment for each part of the day.      
              if (a%2==0){
                fraction=random(10,181);
                fraction=(fraction/100);
                CloudMaster[c]=(rise+(fraction*(SunSegment))+(SunSegment*b)); 
                Serial.print("even a; CloudMaster position number, ");
                Serial.println(c);
                Serial.print(" is= ");
                Serial.println(CloudMaster[c]);  
                c=c+2;
                       
              }
              else if (a%2==1){
                b=b+2;
                CloudMaster[c]=(rise+(SunSegment*b)); 
                Serial.print(" odd a; CloudMaster position number, ");
                Serial.println(c);
                Serial.print(" is= ");
                Serial.println(CloudMaster[c]);  
                c=c+2;
                          
              }
        } 
        Serial.println("here is cloud master in is entirety");
        for (byte a=0;a<16;a++){
          Serial.println(CloudMaster[a]);
        }
        Serial.println("now we print start and end times for clouds");
     
        //just reframe CloudMaster into array of start and end times for clouds (CLoudMaster[]={start,end,start,end,start,end} as progression through day in elapsed seconds from midnight.
        for (byte a=0; a<16;a=(a+2)){
            long endT, startT;
            startT=CloudMaster[(a+1)];
            endT=(CloudMaster[a]+startT);
            CloudMaster[a]=startT;
            CloudMaster[a+1]=endT;
            Serial.println(CloudMaster[a]);
            Serial.println(CloudMaster[a+1]);
            b=b+2;
        }
        
        //As the LAST thing we need to do today and only once... frame ChInt array as max value-flicker point... so it can be correctly set in Insolation
        for (byte a=0; a<6;a++){
            ChInt[a]=(ChMax[a]-flicker[a]);
            Serial.println("ChMax[] setpoints in order 0-5 are=")
            Serial.println(ChInt[a]);
            if (ChMax[a]==0){
              ChInt[a]=0;//you should not have a flicker point for an unsued PWM port but just to be sure
              Serial.println("zero setting for channels");
              Serial.println(a);
        }
   
    
}//END 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<12;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]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b])+flicker[b];
                            Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            break;
                        case 2:
                            ChannelValue[1]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b-1])+flicker[b-1];   
                            Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            break;
                        case 4:
                            ChannelValue[2]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b-2])+flicker[b-2];
                            Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            break;
                        case 6:
                            ChannelValue[3]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b-3])+flicker[b-3];
                            Serial.println(-cos(PiHalf+(ChSlope[b-3]*secSoFar)),4);
                            break;
                        case 8:
                            ChannelValue[4]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b-4])+flicker[b-4];
                            Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            break;
                        case 10:
                            ChannelValue[5]=(byte)(-cos(PiHalf+(ChSlope[b]*secSoFar))*ChInt[b-5])+flicker[b-5];
                            Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            break;
                    }   
                }
                else if (elapsedTime<ChRiseSet[b]){
                    switch (b){
                        case 0:
                            ChannelValue[0]=0;
                            break;
                        case 2:
                            ChannelValue[1]=0;
                            break;
                        case 4:
                            ChannelValue[2]=0;
                            break;
                        case 6:
                            ChannelValue[3]=0;
                            break;
                        case 8:
                            ChannelValue[4]=0;
                            break;
                        case 10:
                            ChannelValue[5]=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]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-1])+flicker[b-1];
                            Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            break;
                        case 3:
                            ChannelValue[1]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-2])+flicker[b-2];    
                            Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            break;
                        case 5:
                            ChannelValue[2]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-3])+flicker[b-3];
                            Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            break;
                        case 7:
                            ChannelValue[3]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-4])+flicker[b-4];
                           Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4); 
                            break;
                        case 9:
                            ChannelValue[4]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-5])+flicker[b-5];
                            Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            break;
                        case 11:
                            ChannelValue[5]=(byte)(-cos(Pi+(ChSlope[b]*secSoFar))*ChInt[b-6])+flicker[b-6];
                            Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            break;
                    }
                }
                else if (elapsedTime>ChRiseSet[b]){
                    switch (b){
                        case 1:
                            ChannelValue[0]=0;
                            break;
                        case 3:
                            ChannelValue[1]=0;
                            break;
                        case 5:
                            ChannelValue[2]=0;
                            break;
                        case 7:
                            ChannelValue[3]=0;
                            break;
                        case 9:
                            ChannelValue[4]=0;
                            break;
                        case 11:
                            ChannelValue[5]=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]);
}//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 byte CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
    static byte 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
    // see insolation elapsed time function for assigmnent of Cloud start times and set of Cloud=true
    //check to see if were having a scheduled cloud
    int LongestStorm;//used to pass max possible time to storm if loop from cloud within weather function
    if (Cloud==false){
        for (byte a=0; a<CloudsTotal; a=(a+2)){
            if ((elapsedTime>=*(pCloudPoint+a)) && ((elapsedTime+125)<=*(pCloudPoint+(a+1)))){//time needs to be within a cloud segment, not just greater than any start, add 125 to elased  to ensure 
                                                                                              //that clear sky function can finish before it sets clud to true again.
                CloudEnd=*(pCloudPoint+(a+1));
                CloudEnd-=120;
                Cloud=true;
                CloudCover=0;
                PriorCloudCover=0; 
            }
            else{
                return;
            }
        }
    }  
    else if ((Cloud==true) && (IsStorm==false)){
        if (CloudAdvance==false){//use millis tracker to run this loop every 2 seconds
            return;
        }
        
        CloudAdvance=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*/
        randomSeed(random(-500,500));
        
        byte stepsize;
        stepsize=random(-10,11);  //this works out to about a percent value change in lighting intensity 0-100 per step...based upon random bounds  (+1 to upper bounds if using equal numbers)         
        PriorCloudCover=CloudCover;
        CloudCover+=stepsize;
       
        if (CloudCover>=90){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms change this value to a lower number
            if ((CloudEnd-elapsedTime)>=300){//if storm can last 5 minutes before cloud would end anyways.. do it
               IsStorm=true;
               wtrigger=true;
               LongestStorm=(CloudEnd-elapsedTime);
            }
            else CloudCover-=(stepsize*2.0);//if not lets get a bit lighter out.
            
              
        }
        else if (CloudCover<=0){//i.e if were bright sky in a cloud.. uhh.. we need a cloud
            CloudCover-=(stepsize*1.5);//reflect to less positive value, i.e. we had to be adding a negative so subtract it instead (i.e. add a positive)
        }
        
        for (int a=0;a<6;a++){
            if (DimOrder[a]==0){
                ChannelValue[a]=(byte)(((ChannelValue[a]-flicker[a])*(float)(1-((float)(CloudCover/100))))+flicker[a]); 
                Serial.print("DimOrder 0 setting is"); 
                Serial.println(ChannelValue[a]);
            }
            if (DimOrder[a]==1){
                ChannelValue[a]=(byte)(((ChannelValue[a]-flicker[a])*(float)(1-((float)(PriorCloudCover/100))))+flicker[a]);  
                Serial.print("DimOrder 1 setting is"); 
                Serial.println(ChannelValue[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>900) LongestStorm=900;//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;
        }
        
        byte stepsize;
        stepsize=random(-20,21);
        
        if (StrikeNow==false){
            // check to see if we are going to have a lightning strike now
            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
                randomSeed(lastmillis); //start random with a new value to avoid repetitive stike generations
                StrikeNumber=(random(2,10)); //random high =x-1 so max strike =9 and array=17 for StrikeMaster
                //Rather than bother with Vector and dynamic allocation with push/pull use an array big enough to accomadate the largest strike pattern and then
                //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 Strike Start millis, strike end millis, etc... total of StrikeNumber pairs.
                for (byte a=0;a<18;a++){
                    if (a>=(StrikeNumber*2)){
                        StrikeMaster[a]=0;
                    }
                    else if (a%2==0){
                        StrikeMaster[a]=random(100,1500);//position 0,2,4,6,8.. is strike delay
                    }
                    else if (a%2!=0){
                        StrikeMaster[a]=random(30,81);//position 1,3,5,7,9... is strike duration- added to StartStrike=StrikeEnd  
                    } 
                }
                StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
            }
          strikeCount=0;
        }
        //get back to setting cloud intensity for storm
        PriorCloudCover=CloudCover;
        CloudCover+=stepsize;
        
        if (CloudCover>=100){//if were too dark reflect random path to light
            CloudCover-=(stepsize);
        }
        else if (CloudCover<=0){
            CloudCover-=(stepsize);//if were 100% intensity of light in a storm reflect random path to less intensity
        }
        
        for (int a=0;a<6;a++) {
            if (Wchannel[a]==1){
              ChannelValue[a]=0;
            }
            else if (DimOrder[a]==0){
                ChannelValue[a]=(byte)((ChannelValue[a]-flicker[a])*(float)(1-((float)(CloudCover/100))))+flicker[a]; 
                Serial.print("In a storm and DimOrder0 Int are as follows no whites here");
                Serial.println(ChannelValue[a]); 
            }
            else if (DimOrder[a]==1){
                ChannelValue[a]=(byte)((ChannelValue[a]-flicker[a])*(float)(1-((float)(PriorCloudCover/100))))+flicker[a];  
                Serial.print("In a storm and DimOrder1 Int are as follows no whites here");
                Serial.println(ChannelValue[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(byte CloudCover, int CloudEnd)
{
    
    // no need to restrict time to avoid resource hogging, its only called every x seconds from weather.
    //I AM ASSUMING THAT everything calling this does so with 120 seconds left in the cloud/storm etc to clear the sky.
    //if you do this incorrectly (i.e. change this and dont change it everywhere I am not sure what would happen for certain, but it may cause issues... almost certainly will
    int elapsed=(120-((CloudEnd+120)-elapsedTime));//For ease of all other calculations previousl in weather... CloudEnd is set as ArrayEndpoint-120... so I need to add it back here... but only here
    
    int LightNeeded=CloudCover;//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.
    
    int slope=((LightNeeded*100)/120);//trying to convert float to decimal math... 70*100=7,000/120=58 as int which is close enough i.e. every second increase by 58 then correct to int with /100
    
    if (elapsed<=120){
        IsStorm=false;
        Cloud=false;
        return;
    }
    if (InsolationAdvance==true){//using Int converted decimal math we should typically see an advance in intensity as integer every 3 seconds, not much sooner so limit to this interval
        int LightAdvance;
        LightAdvance=(CloudCover-((elapsed*slope)/100));//were reducing CloudCover from start to zero over 120 seconds every 3 seconds... so this goes from 0-cloud cover over 120 seconds (approximately) and value trends to zero
        for (byte a=0; a<6; a++){ 
            ChannelValue[a]=(ChannelValue[a]*(float)(1-(LightAdvance)));//finally need to convert to float to get setpoint- dont think you can avoid the float conversion... but only here is it required
        }
    }
}//End Clear Sky function
here is the second serial out put some 5-10 seconds after the first


We set trigger to true

CalSun Run Now

Hours:Mins

19

29

CalSun now()=1337196544

newDay=390507140/first value was 390528040 delta is 20,900 seconds (DST correction is 25,200 so its not this and its also not DST which would reduce it to 21600- not 20,900 and it should not be doing that anyway)


Local Elapsed Seconds from NewDay--rise=

43036

Local Elapsed Seconds from NewDay--set=

95196

Rise/Set a=0

Value=42436Slope0.000058875422

Rise/Set a=1

Value=95196Slope0.000060229926

Rise/Set a=2

Value=38836Slope0.000051875705

Rise/Set a=3

Value=100596Slope0.000049898233

Rise/Set a=4

Value=43036Slope0.000060229926

Rise/Set a=5

Value=95796Slope0.000058875422

Rise/Set a=6

Value=39436Slope0.000052924404

Rise/Set a=7

Value=101196Slope0.000048964977

Rise/Set a=8

Value=38536Slope0.000051366796

Rise/Set a=9

Value=102396Slope0.000047199411

Rise/Set a=10

Value=43036Slope0.000060229926

Rise/Set a=11

Value=95196Slope0.000060229926

DayLength

52160

CloudsTotal

8

Overcast

0.20

CloudLength 1304

a is even fraction=0.48

Cloudmaster position=0

CloudMaster=625

a is odd fraction=0.48

Cloudmaster position=2

Cloud Master=1982

a is even fraction=1.34

Cloudmaster position=4

CloudMaster=1747

a is odd fraction=1.34

Cloudmaster position=6

Cloud Master=860

a is even fraction=0.21

Cloudmaster position=8

CloudMaster=273

a is odd fraction=0.21

Cloudmaster position=10

Cloud Master=2334

a is even fraction=0.98

Cloudmaster position=12

CloudMaster=1277

a is odd fraction=0.98

Cloudmaster position=14

Cloud Master=1330

SunSegment=4636

even a; CloudMaster position number, 1

is= 46281

odd a; CloudMaster position number, 3

is= 52308

even a; CloudMaster position number, 5

is= 53420

odd a; CloudMaster position number, 7

is= 61580

even a; CloudMaster position number, 9

is= 63666

odd a; CloudMaster position number, 11

is= 70852

even a; CloudMaster position number, 13

is= 78918

odd a; CloudMaster position number, 15

is= 80124

here is cloud master in is entirety

625

46281

1982

52308

1747

53420

860

61580

273

63666

2334

70852

1277

78918

1330

80124

now we print start and end times for clouds

46281

46906

52308

54290

53420

55167

61580

62440

63666

63939

70852

73186

78918

80195

80124

81454

StormAdvance=true

StormAdvance=true

CloudAdvane=true

StormAdvance=true

InsolationAdvance=true

Insolation 3 sec elapsed

Elapsed Time is=8208

in first half of day

Insolation settings=

0

0

0

0

0

0

StormAdvance=true

CloudAdvane=true

StormAdvance=true

StormAdvance=true

CloudAdvane=true

InsolationAdvance=true

Insolation 3 sec elapsed

Elapsed Time is=8211

in first half of day

Insolation settings=

0

0

0

0

0

0

StormAdvance=true

StormAdvance=true

CloudAdvane=true

StormAdvance=true

InsolationAdvance=true

Insolation 3 sec elapsed

Elapsed Time is=8214

in first half of day

Insolation settings=

0

0

0

0

0

0

StormAdvance=true

CloudAdvane=true

StormAdvance=true

StormAdvance=true

CloudAdvane=true

InsolationAdvance=true

Insolation 3 sec elapsed

Elapsed Time is=8217

in first half of day

Insolation settings=

0

0

0

0

0

0
Post Reply