Page 2 of 8

Re: C library for Sun/Moon effects

Posted: Tue Feb 28, 2012 12:32 pm
by rimai
Yeap. You got the idea.
That's what the CheckCloud() function does.

Re: C library for Sun/Moon effects

Posted: Tue Feb 28, 2012 12:56 pm
by rufessor
Thanks... never thought to look at that for an idea!

Re: C library for Sun/Moon effects

Posted: Wed Feb 29, 2012 10:49 pm
by rufessor
Ok.. I am pretty sure this is working, but its too late to really test it. I was going to do so tonight, but started in on coding my cloud function... which is why I am posting this.

Would really like someone to look this over and help me with the general idea.

So, I have been wanting a "better" (IMHO) cloud function, that allows for random drift and dimming/intensification of the light over the tank. Obviously you need to cap intensity at X% PWM output and would probably like to maintain some PAR on the tank, so you need to cap the low end % PWM output as well as restrict the time this happens (want to do this random between say 10-and 60 mins) and the # times per day... these last two might be modeled based on existing cloud function depending on if it fits into my structure.

What I have is a program that compiles, uses Lat Lon for sunrise sunset, calculates 1/2 day lengths for every channel from the midpoint of sunrise/sunset and uses am and pm offsets from the calc sunrise and sunset value for every channel... so anyone can basically sex up their lighting to suit their tank designs without changing anything other than a few constants in the array designations. I.E. in my case left right sunrise sunset fades and more evening blue light than morning etc etc.

I also have a cloud function started that uses a random walk function to vary intensity when cloud is true, then it sets an array of minimum intensity value for all channels, and asks which are white. If the random walk in intensity for any channel tries to exceed set point its reflected back, if the random walk tries to exceed low point its reflected up... the COOL thing (I think) is if the random walk hits the LOW point for the WHITE channel then a storm is triggered and the white channels will go to ZERO, the blues will still random walk in intensity..

and I need to write in a lighting strike generator.

So.

I need to figure out if this is structured correctly to work..

I.E. in loop I call in this order.

Calc sunrise and sunset = call CalSun()
Set up Insolation intensity variation centered on midday following cosine function =Insolation()
Insolation also sets first value for ChannelValue[] which is used in loop for PWM board set points for output to lights
then I call weather()

weather does the random walk and resets ChannelValue[] based upon that and is where *I think* I would put the
strike function...

but if I call a function weather() from loop...

and set a strike by say putting white channel Intensity to 100% and delay 100 ms... will it ever actually happen...
i.e. the strike will occur within the weather function but never actually work...

or is it that, because ChannelValue is a Global array that the loop will run in the background and the strike will occur (I think this).

I probably should know this but its late and somehow I am confused...

I KNOW that the sunrise and sunset functions are working, I am almost sure they are correct values but need to check this more. Insolation is also probably working but I need to check this a LOT more.

weather has never been loaded to a board but the whole thing compiles so no formatting errors :roll:

Here is the code so far

Code: Select all

// Reef Angel Includes


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

//Defines for ReefAngel PWM module

#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5


//*********************************************************************************************************************************
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
// long latitude, longitude;
unsigned long sunrise;
unsigned long sunset;
long newDay;
int midDay;
int ChRiseSet[12];
int ChSlope[12];
byte dst;
byte PWMports[] ={
  3,5,6,9,10,11};
byte ChannelValue[6];
byte ChInt[6];
byte SeasonsVar[]={
  0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, sunrisehour, sunriseminute, sunsethour, sunsetminute, cloudstarthout, cloudstartminute, cloudduration,lightningchance, PWM1,PWM2,PWM3
byte cmdnum=255;
byte datanum=255;
byte ForceCloud=false;
boolean trigger;
byte hours, minutes;
boolean isDST, cloud, storm;
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//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();
  trigger=true;
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop()
{
  wdt_reset();
  //Serial.println(ChannelValue[0],DEC);
 if (cmdnum!=255)
  {
    ProcessCMD(cmdnum,datanum);    
    cmdnum=255;
    datanum=255;
   }
 // functions called from loop
 //CheckCloud();
  CalSun();
  Insolation();
  //Weather(cloudtoken, stormtoken);
  //need to write function somewhere that does random generation of cloud time (10 min-60 min)and indexes # for day
  
            
  
  for (byte a=0;a<6;a++)
   {
     analogWrite(PWMports[a],ChannelValue[a]);
    }
 /* Serial.println("DST");
  Serial.println(isDST);
  delay(500);
  Serial.println("trigger");
  Serial.println(trigger);
  delay(500);
  Serial.println("newDay");
  Serial.println(newDay,DEC);
  delay(500);
  Serial.println("sunrise");
  Serial.println(sunrise,DEC);
  delay(500);
  Serial.println("sunset");
  Serial.println(sunset,DEC);
  Serial.println("now()");
 long secon;
  secon=now();
  Serial.println(secon,DEC);
  delay(1000);
*/

}
//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) ForceCloud=true;
}
//End Standard Functions
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
  SeasonsVar[0]=ChannelValue[0];
  SeasonsVar[1];
  SeasonsVar[2];
  SeasonsVar[3];
  SeasonsVar[4];
  SeasonsVar[5];
  SeasonsVar[6];
  SeasonsVar[7];
  SeasonsVar[8];
  SeasonsVar[9];
  SeasonsVar[10];
  SeasonsVar[11];
  Wire.write(SeasonsVar,12);
}
//End Send Data
//*********************************************************************************************************************************
// Function to run Epherma Calc for sunrise/sunset/noon/moon phase 
boolean CalcDST(byte D, byte M, byte dow)
      { 
        //January, february, and december are out.
        if (M < 3 || M > 11)
          {
            dst=false; 
          }
        //April to October are in
        else if (M > 4 && M < 11)
          {
            dst=true; 
          }
        else
          {  
            int previousSunday = D - dow; // Sunday=1 Sat=7
            if (M==3 && previousSunday > 7)
             { 
               dst=true;
             }
            else if (M==11 && previousSunday<=0)
              {
                dst=false;
              }
          }
          return (dst);
    }
void CalSun()
{
   if (trigger==false)
   {
    return;
   }
     
     // Every day at midnight, we calculate sunrise, sunset, time of highest sun, moon phase, or we do this if system has reset
  if ((hour()==0 && minute()==0 && second()==0) || (trigger=true))
   {  
     trigger=false;
        // Arduino uses Unix time- seconds since Jan 1 12:00 am UTC 1970
        //Must convert to MST by adding 7 hrs =7*3600=25,200 seconds MST=GMT-7
        //Must get daylight savings rules into effect or sunrise set will change by 1 hour
        // In D, M, dow;
        //  boolean isDST=CalcDST(day(),month(),weekday());
      isDST=CalcDST(day(),month(),weekday());
        //Using time library from Arduino.time_t now(); returns seconds since 1970
        //Library states #seconds to subtrace to bring to Jan 1 2000 as origin for time is 
        //#define SECS_YR_2000  (946684800) the time at the start of y2k
        //DST rules are march second sunday until november 1st sunday + 1 hr starting at 2 am which I dont care about so that part is neglected 
      long SecInput;
       if (isDST==true) 
         {
         time_t t=(now()+3600);
         hours=hour(t), minutes=minute(t);
           if (hours!=0 || minutes!=0)
             {
               newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
               SecInput=newDay+25200;
              }  
            else
              {  
               newDay=(now()-(946684800+3600));
               SecInput=newDay+25200; 
             }
           }    
         else if (isDST==false)
           {
             time_t t=now();
             hours=hour(t), minutes=minute(t);
               if (hours!=0 || minutes!=0)
                 {
                   newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
                   SecInput=newDay+25200;
                  }  
                else
                 {  
                 newDay=(now()-946684800);
                 SecInput=newDay+25200;
                 }
             } 
     
         //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 sunrise time and sunset time   
     sunrise=sunset=SecInput;
     SunRise(&sunrise);
     SunSet(&sunset);
     
       // Correct sunrise data to reflect elapsed time from midnight (newDay)
       // DST correction is all ready in both variables so no need to deal with it
     sunrise-=25200; //Correct back to MST from GMT- DST still accounted for
     sunset-=25200; 
     sunrise-=newDay;//set to elapsed seconds today
     sunset-=newDay;
    /*offsets for sunrise/sunset with colors all values in seconds offset from calculated sunrise or sunset value
    set up array with order ch0 am offset, pm offset, ch1 am offset, pm offset etc (negative to rise earlier/set earlier*/
     int Choffset[]={
       -600,0,-3600,5400,0,600,-3600,5400,-4500,7200,0,0};
   
  //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)
      unsigned int deltaY=2/3.141592653589793238462643383279502884197169399;
      midDay=(sunset-sunrise)/2;
      for (byte b=0;b<12;b++)
        {
        if (b%2==0)
          {
          ChRiseSet[b]=sunrise+Choffset[b];
          ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
           }
        else if (b%2==1)
          {
          ChRiseSet[b]=sunset+Choffset[b];
          ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
           }
         } 
    }  
}//END FUNCTION

void Insolation()
{
    // Calculate time since day started correcting for DST
   int elapsedtime;
  if (dst==true)
   {
     elapsedtime=(now()+3600-newDay);
   }
  else
   {
     elapsedtime=(now()-newDay);
   }
  
   // Channels for me are 0=RW 1=RB 2=LW 3=LB 4=Whole tank V  use in order to assign flicker points 
  int flicker[]={
  10,10,10,10,5,10};
  //What are the max values for ch intensity we want to reach use 0-255 scale for PWM board byte output 
  ChInt[210,225,210,225,220,0];
  unsigned int deltaY=3.141592653589793238462643383279502884197169399;
    


/* using -cos(pi/2+elapsedtime/slope)*((ChMaxVal-flicker)+flicker) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light.  Turn off lights after sunset or before sunrise etc.
by splitting into half days centered on midday (1/2 ofsunset-sunrise) we center exactly the cos function for every channel so color blends are maintained 
throughout intensity ramp... more or less ... change intensity every 30 seconds throughout the day*/
    if ((elapsedtime%30)==0)
      {
      for (byte b=0;b<12;b++)
        {
          if ((b%2==0) && (elapsedtime>=ChRiseSet[b]) && (elapsedtime<midDay))
            {
              if (b==0)ChannelValue[0]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b])+flicker[b]));
              else if (b==2) ChannelValue[1]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-1]-flicker[b-1])+flicker[b-1]));    
              else if (b==4) ChannelValue[2]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-2]-flicker[b-2])+flicker[b-2]));
              else if (b==6) ChannelValue[3]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-3]-flicker[b-3])+flicker[b-3]));
              else if (b==8) ChannelValue[4]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-4]-flicker[b-4])+flicker[b-4]));
              else if (b==10) ChannelValue[5]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-5]-flicker[b-5])+flicker[b-5]));
             }
          else if ((b%2==1) && (elapsedtime<=ChRiseSet[b]) && (elapsedtime>=midDay))
            {
              if (b==1)ChannelValue[0]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-1])+flicker[b-1]));
              else if (b==3) ChannelValue[1]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-2])+flicker[b-2]));    
              else if (b==5) ChannelValue[2]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-3])+flicker[b-3]));
              else if (b==7) ChannelValue[3]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-4])+flicker[b-4])); 
              else if (b==9) ChannelValue[4]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-5])+flicker[b-5]));
              else if (b==11) ChannelValue[5]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-6])+flicker[b-6]));
             }
          else if (((b%2==0) && (elapsedtime<ChRiseSet[b])) || ((b%2==1) && (elapsedtime>ChRiseSet[b])))
            {
              if ((b==0) || (b==1)) ChannelValue[0]=0;
              else if ((b==2) || (b=3)) ChannelValue[1]=0;     
              else if ((b==4) || (b=5)) ChannelValue[2]=0;
              else if ((b==6) || (b=7)) ChannelValue[3]=0; 
              else if ((b==8) || (b=9)) ChannelValue[4]=0;
              else if ((b==10) || (b=11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
            }  
          }
        
   } 
}
// cloud function allowing for cloud passing over tank and random walk dimming in all channels 
//also include storm, dimming whites with lightning as random chance during day
//set up void function to accept array input of current channel intensity set and to set actual channel intensity and to accept cloud or storm toggles from menu as boolean true to start them
void weather (boolean cloud, boolean storm)  
{
 //for storm shut off white use 1 to designate channel as white or a channel you want off during storm array is channel (0,1,2,3,4,5)
 boolean Wchannel[]={1,0,1,0,0,0,0};
 if ((cloud=false) && (storm==false))
   {
     return;
   }
 else if ((cloud=true) && (storm=false))
   {
     static int  level;     // variable to store value in random walk - declared static to accumulate cloud effect
     byte low[]={30,75,30,75,125,0};  //lowest allowed channel intensity during a cloud, triggers a storm if channel is whites.  
      
      //use ChannelValue[] a=0-5 for high point of random walk constrain
      //Cloud function
        level = level + (random(-4, 4));
          for (int a=0;a<6;a++)
            {
              
              if ((ChannelValue[a]-level)>ChInt[a])
                {
                   level=abs(level);
                   ChannelValue[a]=(ChannelValue[a]-level);
                }
              else if ((ChannelValue[a]-level)<(low[a]))
                {
                  if (Wchannel[a]=true)
                      {
                        storm=true;
                      }
                  int sign=-1;
                  level=(level*sign);
                  ChannelValue[a]=(ChannelValue[a]+level); 
                }
             }
   }
 //as written it will never be first case, but might be nice to have menu on controller allowing for storm selection
 else if (((cloud=false) && (storm=true)) || ((cloud=true) && (storm=true)))
   {
     for (int a=0;a<6;a++)
       {
         if (Wchannel[a]=true)
           {
             ChannelValue[a]=0;
           }
       }
     //now I need to adopt something like a random number strike generator for lighting about 30-100 ms long
    // unsure where to put this as ch intensity is written to PWM board in loop.. so I dont know if 
     //delay executed here would work...    
    
   }
}//End Function

 

Re: C library for Sun/Moon effects

Posted: Wed Feb 29, 2012 10:56 pm
by rufessor
Sorry... I know that I am kinda out there with this... not trying to be a pain. But I think that when this is done, which should be soon in terms of an outline working code (interface with controller might be a bit) it would be pretty cool. I also hope others would use it. I am trying hard to code this so it works on ANY system. I.E. all arrays are set up to use ALL PWM channels etc etc etc. So if anyone wants to give this a try when its working it should be pretty easy to load and go. Wish I was faster... I really do... my lights are running currently based on my unplugging them every night *grin*

Re: C library for Sun/Moon effects

Posted: Thu Mar 01, 2012 9:37 am
by rimai
I think the Weather() function would work.
ChannelValue is global, so whichever function changes it, will influence the output of the channel.
One remark though.

Code: Select all

  //CheckCloud();
  CalSun();
  Insolation();
  //Weather(cloudtoken, stormtoken);
Because it goes through a loop, the last function will always have priority over the previous ones.
In your case, Weather() has priority over Insolation, which in turn has priority over CalSun.
So, Weather() function would always have the final say no matter what the other functions are doing.
If you don't want weather influencing the other functions, you have to make sure to return out of the function when not needed, which I believe you already coded that.

Code: Select all

 if ((cloud=false) && (storm==false))
   {
     return;
   }
So, I think you are in track with your idea and I'd like to see a youtube of the strike :)
The only comment I have about DST is that you are using it for those states in the USA that uses DST.
Arizona for example doesn't observe DST and if you head out to anywhere outside the USA, they are all different depending on the country you are in. Some countries even observe 1/2 hour DST and even 1/4 hour DST. It's crazy, but they do.
That's why it is very hard to implement DST and I decide it wasn't worth to do so.
Maybe you should have a define on wether to use your USA DST or not. Just a thought.

Re: C library for Sun/Moon effects

Posted: Thu Mar 01, 2012 11:50 am
by rufessor
THANKS!!!

RE DST... yeah. I will work on porting that to a universal code... ie. maybe make a global variable for the offset value, in terms of the dates... thats going to be REALLY hard to make universal unless I change the code entirely and simply have people enter the month, day, time for the start of the offest and use the time.h library to query these values. Which would be a total rewrite... let me think about this and see if it would end up being painful... my overwhelming feeling currently is that if you gave up I should bail with out further consideration.

In any case, I want to get back to my lights running based upon the controller... so I will try to get the strike code to work and I need to address a time loop to the random walk cloud feature.

My only "complaint" with the current cloud is that to my eye all I ever really see is the tank go blue. I know that white PWM dimming is hard to discern, but I also know that if I turn my lights on to 90% and then rapidly switch to 25% its OBVIOUS. So, if I can get the random walk variable tuned to a large enough step size and a rapid enough (but not too fast) cycle update I think it would have the potential to be truly beautiful with rapid cloud cover appearing to pass over the tank giving observable dim and brightness, then the idea that if it gets dark enough a storm happens, just seems natural.

I think its obvious that I am trying very hard to make this really "natural" we shall see how it looks.

In any case, I am SUPER psyched to be finally past the initial c++ garbage and able to code something without copy paste and having no idea why the copy paste worked/failed/whatever. Still want to work into pointers with some of the array manipulation... but I am currently using globals (bad form I guess) but its not like were going to end up with 10,000 lines of code given memory so perhaps I will just keep it simple.

I really want to thank you for being so responsive- very very fun to work on this. I love working out the math functions and loop structures to get control going... its like a fun logic puzzle.

Re: C library for Sun/Moon effects

Posted: Thu Mar 01, 2012 10:47 pm
by rufessor
SOOO Close- but a question

entire code compiles but is untested----still coding final bit of storm before trying it on a PWM board and going through what I am sure will be a night of troubleshooting (this requires BEER... friday seems well suited).

I have set up a "time loop" in the "loop"

void loop()
{
CalSun();
if ((second()%15)==0)
{
Insolation();
if (storm==true || cloud==true)
{
Weather();//need to add 2 way comm to force cloud/storm from controller
}
}

First question, and I can get this through serial EASILY... so if you KNOW it instantly great else no worries.

does second() ever take on a value of 60, or zero, it must (I presume) take either one or the other but I doubt both... if either is true I am ok... and will get the function calls evenly 4 times a minute


Setting intensity via insolation every 15 seconds is more than enough to provide insolation modeling that will appear completely smooth to the eye (I was going to use 30 but for a cloud I want things to happen faster and it seemed like timing would be weird if I ran them on different "if time loops" as priority NEEDS to be insolation-->weather --> set to PWM board intensity so if they are called in that order from a single loop.. no issues.

Question is my lighting strike.

If I put in a bit of code that sets white channel intensity to 100% for say 50 msec and then reverts to zero and then waits a bit and does it again some random # of times (less than 15 seconds in duration for the pulse chain), but this bit resides in the weather function, which is only called every 15 seconds... will it ever actually happen... i.e. will the analogWrite ever occur using the strike values... somehow this seems like an incredibly STUPID question as the answer must be yes since I am modifying a global memory value but I am a bit unsure because I am going to put in a delay statement to get the white =100% value to "stick" for the time I want it to... so please help here.

I think the answer is yes... and I also don't think I care if the random strike chain ends up being longer than the duration between function calls.. because its terminated by definition when the function is called again (correct???) or do I need to worry about that as well... if you get what I mean.

Code is just in case you need it to figure out what in the H-e-c-k am talking about. I only posted the weather function as nothing else is terribly relevant at this point given my Q's. I started this completely differently then changed it back then changed it again and am now here. I was going to do this incredibly convoluted work dimming each channel independently of the other with independent minimum values by using a random walk value to add to each channel... but since they all start and end at different values (from insolation) it got WAY TOO complicated to keep them above flicker point, not over 100% and to keep them in sync with each other as if you add a random value which is negative and one channel starts at 70% and the other at 90% and keep doing this, one ends up off before the other is even below 20% and that would look REALLY STUPID... so instead I am using a simple 0.1-1.0 variation of all channel intensity.. and check if we hit flicker points and hold channels at that value as a minimum.

Else storm is triggered when cloud cover exceeds a threshold, I have it set to 80% currently,i.e. intensity is 20% of start. or storm is triggered if any single channel hits a flicker point (lowest set point possible). The storm shuts off whites, keeps track of time, and intensity ramps are more violent (random -15,15) which equates to max +/-0.15 *current value during a storm....

I think I have all the timers set up correctly so the cloud only goes on for the value picked between the min and max possible, so that the cloud count cannot exceed max/day and so that the storm occurs on top of a cloud and does not count as a "cloud" for the daily event... I am wondering if I could have the storm end with a ramp up as the sky clears... probably add that.

Code: Select all

void Weather ()  
{
 boolean Wchannel[]={1,0,1,0,0,0,0};  //for storm shut off white use 1 to designate channel as white or a channel you want off during storm array is channel (0,1,2,3,4,5)
 static int  cloudCover;     // variable to store value in random walk - declared static to accumulate cloud effect
 static long timeStart;
 byte maxDuration; //(max duration of a cloud in minutes- SET THIS TO YOUR DESIRED VALUE THERE ARE NO CONSTRAINTS but if you go over 255 switch type to int also change elapsedTime to int)
 static byte cloudsTotal;
 byte cloudsMax=3;//Total number of clouds allowed for a day as MAXIMUM
 static boolean cloudStart;
// see insolation elapsed time function for assigmnent of cloud start times and set of cloud=true
 
 if ((cloud==false) && (storm==false) || (cloudsTotal>=cloudsMax))
   {
     timeStart=now();
     cloudStart=false;
     return;
   }
 else if ((cloud==true) && (storm==false) && (cloudsTotal<=cloudsMax))
   {
     byte elapsedTime;
     elapsedTime=((now()-timeStart)/60);
     // check to see if we have started this cloud yet if not pick a random duration and initalize cloud else keep it going until times up
       if (cloudStart==false)
         {
           maxDuration=random(10,40);//set (min,max) minutes any single cloud can exist.  The longer it exists the more likely a storm will occur\
           cloudStart=true;
         }
        if (elapsedTime>=maxDuration)
         {
           cloud=false;
           storm=false;
           cloudsTotal+=1;
           cloudStart=false;
           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*/
    
      byte stepsize=random(-10,10);
      cloudCover = cloudCover + stepsize;
        
        if (cloudCover>=80) //dont dim below 20% max value as this will almost certainly hit flicker point on most lights especially given insolation pattern
                            //although it is checked and reset, this will keep cloud in a "visual" range of values and not bottom it out and trigger a storm
         {
          storm=true;
          cloudStart=false;
          timeStart=now();
          cloudsTotal+=1;
         }
        else if (cloudCover<=0) 
         {
          cloudCover-=(stepsize*1.5);
         }
           
      for (int a=0;a<6;a++)
       {
         ChannelValue[a]=(ChannelValue[a]*(1-(cloudCover/100)));
           if (ChannelValue[a]<=flicker[a])
                {
                   ChannelValue[a]=flicker[a];
                   storm=true;
                   cloudsTotal+=1;
                   cloudStart=false;
                   timeStart=now();
                }
       }    
   }
 //as written it will never be first case, but might be nice to have menu on controller allowing for storm selection
 // set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
 else if (((cloud=false) && (storm=true)) || ((cloud=true) && (storm=true)))
   {
     byte elapsedTime;
     elapsedTime=((now()-timeStart)/60);
     // check to see if we have started this storm yet if not pick a random duration and initalize storm else keep it going until times up
       if (cloudStart==false)
         {
           maxDuration=random(5,15);//set (min,max) minutes any single storm can exist. Remember this is on top of what might have been a long lived cloud and is additive
           cloudStart=true;
         }
        if (elapsedTime>=maxDuration)
         {
           cloud=false;
           storm=false;
           cloudStart=false;
           return;
         }
     // step through intensity values
     byte stepsize=random(-15,15); //bigger changes during a storm for more chaos... play with this but careful you can make a strobe
     cloudCover = cloudCover + stepsize;
     for (int a=0;a<6;a++)
       {
         if (Wchannel[a]=true)
           {
             ChannelValue[a]=0;
           }
          else if (ChannelValue[a]<=flicker[a])
                {
                   ChannelValue[a]=flicker[a];
                }
          //*******************
          //THIS IS WHERE I WANT TO PUT THE STRIKE CODE
          
        }
       
    
   }
}//End Function

Re: C library for Sun/Moon effects

Posted: Fri Mar 02, 2012 9:16 am
by rimai
seconds() will return 0-59
Your function will be executed 4 times evenly.
AnalogWrite() will always be execute, but for your strike to work, you also have to place the strike function outside the Weather() function. Otherwise, it won't strike. The code only executes what is inside the weather function every 15s. So, you may need to add a new global variable and do something like this:

Code: Select all

void loop()
{
  CalSun();
  if ((second()%15)==0)
  {
    Insolation();
    if (storm==true || cloud==true) 
    {
      Weather();//need to add 2 way comm to force cloud/storm from controller
    }
  }
  if (strike) ProcessStrike();

Re: C library for Sun/Moon effects

Posted: Sun Mar 11, 2012 9:32 pm
by rufessor
Still working on this...

I post the entire code but really have one question

regarding pointers and arrays.

If I create a static array (15 values all int)
and then make a pointer to get the value from another function (cannot pass arrays)

is this the correct syntax.

Code: Select all

declare as global..
int *CloudPoint;

Then in function calcSun().

static int CloudMaster[15];// Set up array to hold start and end times for clouds for the CloudPoint=&CloudMaster[0];//use address of pointer to find cloud data from other function


and then in function Weather() using a for loop jump through the start and end times in the array using the pointer


for (byte a=0; a<=(CloudsTotal-1); a+2){
         if (((elapsedTime>=CloudPoint[a]) && (elapsedTime<=CloudPoint[a+1])) || ForceCloud==true){
             CloudEnd=(CloudPoint[a+1]-120);
             Cloud=true;

and finally... its not yet complete, but basically I need to just work in the strike code from a storm and think about a few things but here is the code. Might be worth glancing at if your curious but I will post back something either working or that I have Q's about soon. It took so long to get here because I bailed on how I was modeling clouds previously -trashed it all and re did it all... but its much cooler now. Details later. or look, code is well commented.

Also, added DST flag to use or NOT use DST correction... its global.

Code: Select all

// Reef Angel Includes

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

//Defines for ReefAngel PWM module

#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5


//*********************************************************************************************************************************
//IF YOU DO NOT WISH TO USE the U.S.A. Day Light Savings Time rules to modify the time stamp for sunrise/sunset calculations...set to false- i.e. if you dont live in the USA etc
boolean ApplyDST=true;

//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long sunrise;
unsigned long sunset;
long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
int ChRiseSet[12];
int ChSlope[12];
int midDay;// exactly 1/2 way between sunrise and sunset, i.e. my take on solar noon.
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)


//What are the max values for ch intensity we want to reach, i.e. at and around solar noon- use 0-255 scale for PWM board byte output (ch0,1,2,3,4,5)
//i.e. 100% channel 0, ~10% channel 1, 40% channel 3 would be {255, 25, 102, ...etc}
byte ChInt[]={
  210,225,210,225,220,0};
// At what point in PWM dimming do your individual channels flicker, input this as 0-100% PWM output value into the array (ch0,1,2,3,4,5)
byte flicker[]={
  10,10,10,10,5,0};
  
  
byte SeasonsVar[]={
  0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, sunrisehour, sunriseminute, sunsethour, sunsetminute, Cloudstarthout, Cloudstartminute, Cloudduration,lightningchance, PWM1,PWM2,PWM3
byte cmdnum=255;
byte datanum=255;

boolean trigger; //used a few places as a switch to ensure things only run 1x when needed

boolean isDST, Cloud, Storm, CloudToday, ForceCloud; // what they seem, is it DST?, Are we in a cloud?
byte CloudsTotal;//required to know how large the array for CloudMaster will be- I need to use Vectors.... I know.
int *CloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//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;
  ForceCloud=false;
  
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop()
{
  wdt_reset();
  //Serial.println(ChannelValue[0],DEC);
 if (cmdnum!=255){
    ProcessCMD(cmdnum,datanum);    
    cmdnum=255;
    datanum=255;
  }
// now run through sunrise/sunset calculation and then intensity set and finally weather overlay
  CalSun();
  Insolation();
  Weather();
  Strike();//need to add 2 way comm to force Cloud/Storm from controller
 
  // write final ChannelValue[a] setpoints from Insolation or as modified by Weather overlay
  for (byte a=0;a<6;a++){
    analogWrite(PWMports[a],ChannelValue[a]);
  }
 /* Serial.println("DST");
  Serial.println(isDST);
  delay(500);
  Serial.println("trigger");
  Serial.println(trigger);
  delay(500);
  Serial.println("newDay");
  Serial.println(newDay,DEC);
  delay(500);
  Serial.println("sunrise");
  Serial.println(sunrise,DEC);
  delay(500);
  Serial.println("sunset");
  Serial.println(sunset,DEC);
  Serial.println("now()");
 long secon;
  secon=now();
  Serial.println(secon,DEC);
  delay(1000);
*/

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

//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
  SeasonsVar[0]=ChannelValue[0];
  SeasonsVar[1];
  SeasonsVar[2];
  SeasonsVar[3];
  SeasonsVar[4];
  SeasonsVar[5];
  SeasonsVar[6];
  SeasonsVar[7];
  SeasonsVar[8];
  SeasonsVar[9];
  SeasonsVar[10];
  SeasonsVar[11];
  Wire.write(SeasonsVar,12);
}
//End Send Data
//*********************************************************************************************************************************
// Function to run Epherma Calc for sunrise/sunset/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()
{
   if (trigger==false){
    return;
   }
     
     // Every day at midnight, we calculate sunrise, sunset, time of highest sun, moon phase, or we do this if system has reset
  if ((hour()==0 && minute()==0 && second()==0) || (trigger=true)){  
    trigger=false;
        // Arduino uses Unix time- seconds since Jan 1 12:00 am UTC 1970
        //Must convert to MST by adding 7 hrs =7*3600=25,200 seconds MST=GMT-7
        //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 subtrace to bring to Jan 1 2000 as origin for time is 
        //#define SECS_YR_2000  (946684800) the time at the start of y2k
        //DST rules are march second sunday until november 1st sunday + 1 hr starting at 2 am which I dont care about so that part is neglected 
      long SecInput;
      byte hours=hour(), minutes=minute();
       if (isDST==true){
         time_t t=(now()+3600);
           if (hours!=0 || minutes!=0){
               newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
               SecInput=newDay+25200;
           }  
           else{  
               newDay=(now()-(946684800+3600));
               SecInput=newDay+25200; 
           }
        }    
        else if (isDST==false){
          time_t t=now();
          hours=hour(t), minutes=minute(t);
            if (hours!=0 || minutes!=0){
                newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
                SecInput=newDay+25200;
            }  
            else{  
                newDay=(now()-946684800);
                SecInput=newDay+25200;
            }
       } 
     
         //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 sunrise time and sunset time using Epherma Library   
     sunrise=sunset=SecInput;
     SunRise(&sunrise);
     SunSet(&sunset);
       // Correct sunrise data to reflect elapsed time from midnight (newDay)
       // DST correction is all ready in both variables so no need to deal with it
     sunrise-=25200; //Correct back to MST from GMT- DST still accounted for
     sunset-=25200; 
     sunrise-=newDay;//set to elapsed seconds today
     sunset-=newDay;
     
 //*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
 //offsets for sunrise/sunset all values in seconds offset from calculated sunrise or sunset 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 sunrise/sunset 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)
      unsigned int deltaY=2/3.141592653589793238462643383279502884197169399;
      midDay=(sunset-sunrise)/2;
  
     for (byte b=0;b<12;b++){
        if (b%2==0){
          ChRiseSet[b]=sunrise+Choffset[b];
          ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
         }
        else if (b%2==1){
          ChRiseSet[b]=sunset+Choffset[b];
          ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
        }
     }  

//***************** 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 sunrise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
  randomSeed((now()-(random(300000000,1000000000); //so that we are now more trully random
  byte RainMaker=random(1,100); 
  byte CloudChance=70;//% Chance of a Cloud every day
  RainMaker<CloudChance ? CloudToday=true: 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*/

  int dayLength=(sunset-sunrise);
  byte PercentOvercastMin=10;//% as fraction (0-1 is 0-100%) of daylight hours that would minimially be cloudy on a cloud day
  byte PercentOvercastMax=61;//% of daylight hours that would maximally be cloudy on a cloud day (then add 1 to that #)
  byte Overcast=random(PercentOvercastMin,PercentOvercastMax);
 
  // number of clouds possible for the day, max and min
  byte CloudsMax=8;
  byte CloudsMin=3;
  CloudsTotal=random(CloudsMin,CloudsMax);
  
 
  static int CloudMaster[15];// Set up array to hold start and end times for clouds for the day- dont size it
  CloudPoint=&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*/
 
     int cloudLength;
     cloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
     byte fraction=(random(20,101)/100);//vary each cloud from 20%-180% of an equal fraction of total cloud for day
   
     //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%
     for (byte a=0; (a=(CloudsTotal*2)-1); a=a+2){
        byte b=0;
          // if were having an odd # of clouds make sure last one is full length
        if ((cloudsTotal%2!=0) && (a==(CloudsTotal*2)-2)) {
           cloudMaster[a]=cloudLength;
           b=b+1;    
        }
           // 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 ((CloudsTotal%2!=0)){
           cloudLength=abs(cloudLength*b+(b*cloudLength-(cloudLength*fraction)));
           cloudMaster[a]=cloudLength;
           b=b+1;
             if (b==2){
                b=0;
                fraction=(random(20,101)/100);//every other cloud recalculate a new fractional distribution for the next set
             }
        } 
        else if ((CloudsTotal%2==0){
            cloudLength=abs(cloudLength*b+(b*cloudLength-(cloudLength*fraction)));
            cloudMaster[a]=cloudLength;
            b=b+1;
              if (b==2){
                b=0;
                fraction=(random(20,101)/100);//every other cloud recalculate a new fractional distribution for the next set
              }
        }
     }
       
 
// Now space the clouds out during the day using random fractionation of day segments
      for (byte a=0; a<((CloudsTotal*2)-1); a++){
        if (a%2==1){
          byte b=0;
          //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
          int SunSegment=((dayLength-(dayLength*Overcast))/cloudNumber);
          StartTime=(sunSegment*(random(1,101)/100));
          StartTime=StartTime+((SunSegment*b)+sunrise)
          cloudMaster[a]=StartTime;
          b++;
       }
    }  
    
//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)-1);a+2){
        int endTime;
        endTime=CloudMaster[a]+CloudMaster[a+1];
        CloudMaster[a]=CloudMaster[a+1];
        CloudMaster[a+1]=endTime;
     }
  }//Finally end time loop.. i.e. basically entire function runs only 1x per day or on restart of controller
}//END FUNCTION

void Insolation()
{
    // Calculate time since day started correcting for DST
  int elapsedtime;
  if (isDST==true){
    elapsedtime=(now()+3600-newDay);
  }
  else{
    elapsedtime=(now()-newDay);
  }

//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  
  unsigned int deltaY=3.141592653589793238462643383279502884197169399;
 

/* using -cos(pi/2+elapsedtime/slope)*((ChMaxVal-flicker)+flicker) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light.  Turn off lights after sunset or before sunrise etc.
by splitting into half days centered on midday (1/2 ofsunset-sunrise) we center exactly the cos function for every channel so color blends are maintained 
throughout intensity ramp... more or less ... change intensity every 30 seconds throughout the day*/
   for (byte b=0;b<12;b++){
      if ((b%2==0) && (elapsedtime>=ChRiseSet[b]) && (elapsedtime<midDay)){
          if (b==0)ChannelValue[0]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b])+flicker[b]));
          else if (b==2) ChannelValue[1]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-1]-flicker[b-1])+flicker[b-1]));    
          else if (b==4) ChannelValue[2]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-2]-flicker[b-2])+flicker[b-2]));
          else if (b==6) ChannelValue[3]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-3]-flicker[b-3])+flicker[b-3]));
          else if (b==8) ChannelValue[4]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-4]-flicker[b-4])+flicker[b-4]));
          else if (b==10) ChannelValue[5]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-5]-flicker[b-5])+flicker[b-5]));
      }
      else if ((b%2==1) && (elapsedtime<=ChRiseSet[b]) && (elapsedtime>=midDay)){
          if (b==1)ChannelValue[0]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-1])+flicker[b-1]));
          else if (b==3) ChannelValue[1]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-2])+flicker[b-2]));    
          else if (b==5) ChannelValue[2]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-3])+flicker[b-3]));
          else if (b==7) ChannelValue[3]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-4])+flicker[b-4])); 
          else if (b==9) ChannelValue[4]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-5])+flicker[b-5]));
          else if (b==11) ChannelValue[5]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-6])+flicker[b-6]));
      }
       else if (((b%2==0) && (elapsedtime<ChRiseSet[b])) || ((b%2==1) && (elapsedtime>ChRiseSet[b]))){
          Cloud=false;//no lights = no Cloud
          Storm=false;
          if ((b==0) || (b==1)) ChannelValue[0]=0;
          else if ((b==2) || (b=3)) ChannelValue[1]=0;     
          else if ((b==4) || (b=5)) ChannelValue[2]=0;
          else if ((b==6) || (b=7)) ChannelValue[3]=0; 
          else if ((b==8) || (b=9)) ChannelValue[4]=0;
          else if ((b==10) || (b=11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
      }  
   }
}//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 ()  
{
 boolean Wchannel[]={1,0,1,0,0,0,0};  //YOU MUST CHANGE THIS- designate channels of white or channels you want off during Storm as 1 in array position all else as zero (channel's {0,1,2,3,4,5} )
 static int  CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
 static int PriorCloudCover;  //used to "delay" one side of the tank from the other in cloud passing effects
 int elapsedTime=(now()-newDay);
 static int StormStart;
 static int StormEnd;
 static byte CloudEnd;
 static boolean trigger;//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==true) && (Storm==false)){
     if ((elapsedTime%2)!=0){
       return;
     }
     else if ((elapsedTime%2)==0){// EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
        
          //now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times up
          //seems backwards since CloudCover has not been calculated if you read linearly but it will always be set prior to this=true
          if ((elapsedTime%2)>=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*/
          ranodomSeed(millis());
          byte stepsize=random(-10,10);
     
          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;
                  Trigger=true;
                  StormStart=now()
                }
                else if (CloudCover<=0){
                  CloudCover-=(stepsize*1.5);
                }
              
              //Array to give direction to dimming.  e.g. DimOrder[]={0,0,1,1,0,0} would dim PWM channels 0-1,4-5 as FIRST dim value, then channels 2-3 would take on FIRST dime Value 
              //when channels 0-1,4-5 take on SECOND dim value, so the values chase eachother across the PWM output light array according to your set up.  I have 0-1 as RIGHT side lights, 
              //2-3 as LEFT side LED, and 4 as whole tank while I do not use 5
              
              byte DimOrder[]={0,0,1,1,0,0,};
              for (int a=0;a<6;a++){
                 if (DimOrder[a]==0){
                    ChannelValue[a]=(ChannelValue[a]*(1-(CloudCover/100)));
                       if (ChannelValue[a]<=flicker[a]){
                               ChannelValue[a]=flicker[a];
                               Storm=true;
                               trigger=true;
                               StormStart=now();
                       }
                  }
                  if (DimOrder[a]==1){
                       ChannelValue[a]=(ChannelValue[a]*(1-(PriorCloudCover/100)));
                       if (ChannelValue[a]<=flicker[a]){
                               ChannelValue[a]=flicker[a];
                               Storm=true;
                               trigger=true;
                               StormStart=now()
                        }
                  }
             }
         } 
   }
 //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=false) && (Storm=true)) || ((Cloud=true) && (Storm=true))){
   //do this next part exactly once per storm then loop past
     if (trigger==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));//set (min,max) seconds any single Storm can exist. 
         StormStart=(now()-newDay);
         StormEnd=((StormStart+StormDuration)+(now()-newDay);
         trigger=false;
     }
     //Every 1 second duing a storm change intensity, clouds are movin fast baby
     if ((elapsedTime%1)!=0){
       return;
     }
     else if ((elapsedTime%1)==0){// EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
          if (elapsedTime>=StormEnd){
             ClearSky(CloudCover, CloudEnd);
             return;
           }
         // step through intensity values
         ranodomSeed(millis());
         byte stepsize=random(-10,10);
     
         PriorCloudCover=CloudCover;
         CloudCover = CloudCover + stepsize;
       
         if (CloudCover>=100){//conflicted here, since were dimming by fractional intenstiy its likely we will hit flicker point or lower... might change  how this works
            CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
         }
         else if (CloudCover<=0){
           CloudCover-=(stepsize*1.5);//if were clear sky path reflect random path to cloud
         }
 //*****************IF You want the light to "chase" across the tank****************************
 //you need to specify the left or right side (or front back whatever, depends on how your lights are set up)
 //Use either 0 or 1 to specify groups of channels that dim together (arbitrary but if you need to know direction the cloud will travel you will need to read the next bit of code)
 //assign the same 0 or 1 value to all left side (or front back or whatever) channels and the other value to all right side channels
         byte DimOrder[]={0,0,1,1,0,0,};
 //****************** ok were done changing things*****************************
 
         for (int a=0;a<6;a++) {
             if (DimOrder[a]==0){
               ChannelValue[a]=(ChannelValue[a]*(1-(CloudCover/100)));
               if (ChannelValue[a]<=flicker[a]){
                 ChannelValue[a]=flicker[a];
                 Storm=true;
                 trigger=true;
                 StormStart=now();
               }
            }
            else if (DimOrder[a]==1){
                ChannelValue[a]=(ChannelValue[a]*(1-(PriorCloudCover/100)));
                if (ChannelValue[a]<=flicker[a]){
                   ChannelValue[a]=flicker[a];
                   Storm=true;
                   trigger=true;
                   StormStart=now();
                 }
            }
          }
     }       
 }//end of storm if loop
 
 // Place this loop last as it will only run then cloud is over, and trigger next cloud start and end, this way its not computing during a cloud.
  else {
      for (byte a=0; a<=(CloudsTotal-1); a+2){
         if (((elapsedTime>=CloudPoint[a]) && (elapsedTime<=CloudPoint[a+1])) || ForceCloud==true){
             CloudEnd=(CloudPoint[a+1]-120);
             Cloud=true;
          }
          else{
             Cloud=false;
             CloudCover=0;
             PriorCloudCover=0; 
             return;
          }
       }
   }
}//End 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)
{
    int elapsedTime=(now()-newDay);
    // 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 remaining=(CloudEnd-elapsedTime);
    
    int Range=(CloudCover/100);
    int slope=(Range/120);
   
    if (remaining<=0){
          Storm=false;
          Cloud=false;
          return;
    }
    for (byte a=0; a<6; a++){
         ChannelValue[a]=(ChannelValue[a]*((120-remaining)*slope));
    }
}//End Function



//I HAVE NOT FINISHED THIS.. nor even put in a call to it... figuring out how/what I want to do still.
void Strike()
{
  int msecNow, msecStart, msecElapsed;
  byte strikePattern;
  
  if (Storm==false)
   {
     msecStart=millis();
     return;
   }
   
  msecNow=millis();
  msecElapsed=(msecNow-msecStart);
  
  strikePattern=(random(3,9);
  strikeTime=
}

Re: C library for Sun/Moon effects

Posted: Tue Mar 13, 2012 10:09 am
by rufessor
Another question, still curious about call using pointers but will get it via debug if nothing else...

so almost done and just writing in the actual strike during a lightning storm. I decided to call a strike function
that itself will actually write to the PWM board independent of the loop so that I can time the strikes perfectly and then when that function finishes the loop will simply take over and write the PWM values to the board that either Insolation or Weather (functions) are providing it with.

My question is one of timing, I think this will be fine,
but say the following is the structure

void Weather()
if a strike is going to happen then global variable "strikeStart"=millis();
call to strike function ()
etc...
end Weather function

void Strike()
millisNow=millis();
millisElapsed=millisNow-StrikeStart;

if millisElapsed=0 then initialize a bunch of stuff I want to do only 1 time per strike

My question is.

Since I get a millisecond clock read in one function, then call another, then compare current milliseconds with prior from earlier function, will the subtraction be ZERO EVERY SINGLE TIME... or will some fraction of the time will the mills clock advance between function calls (which should be running a clock speed... so very very fast comparatively). I am guessing that this might happen once in a million times or something, which would simply void the strike so who cares... but curious.

Re: C library for Sun/Moon effects

Posted: Tue Mar 13, 2012 4:29 pm
by rimai
Curt is the master of pointers :)
I always get confused too.
Now, the millis() function could be zero, but the subtraction needs to be really tight when you are sampling millis().
You would have to acquire and store the 1st millis() and acquire and store the 2nd millis() and calculate the result of the subtraction within 1 millisecond or the result would be more than zero.

Re: C library for Sun/Moon effects

Posted: Tue Mar 13, 2012 4:56 pm
by binder
rufessor wrote:Still working on this...

I post the entire code but really have one question

regarding pointers and arrays.

If I create a static array (15 values all int)
and then make a pointer to get the value from another function (cannot pass arrays)

is this the correct syntax.

Code: Select all

declare as global..
int *CloudPoint;

Then in function calcSun().

static int CloudMaster[15];// Set up array to hold start and end times for clouds for the CloudPoint=&CloudMaster[0];//use address of pointer to find cloud data from other function


and then in function Weather() using a for loop jump through the start and end times in the array using the pointer


for (byte a=0; a<=(CloudsTotal-1); a+2){
         if (((elapsedTime>=CloudPoint[a]) && (elapsedTime<=CloudPoint[a+1])) || ForceCloud==true){
             CloudEnd=(CloudPoint[a+1]-120);
             Cloud=true;

Ok. You can't do what you are trying to do with pointers. They don't work like that. They simply "point" to a memory location and you can reference the data stored at that location. You can't do an array index with the brackets because the pointer doesn't know that it's an array. The pointer simply knows that it is a certain size (int in this case) and it refers to a single int storage location in memory.

I do have a question. Based on your pseudo code, it appears that you are creating an array inside a function and trying to use the array in another function. Is that correct?

If so, that will not work because it violates coding and compiler logics. You cannot refer to local variables outside of their context (ie, in another function). If you did what you have coded, your pointer will refer to invalid memory locations and can return "garbage" (unknown and undesired values).

So a better suggestion would be to make the cloud array a global array. Then you can access it everywhere and not need to worry about pointers. ALSO, another important one is that it's only created once and kept in memory. If you create it inside the function, every time the function calcSun is called the array gets created and then when the function ends the array is "destroyed" (so-to-speak).

Hopefully that makes sense.

Re: C library for Sun/Moon effects

Posted: Wed Mar 14, 2012 10:13 am
by rufessor
Hi Roberto and Curt-

THanks... I will try to answer and correct my syntax (you found a big error thanks)

The millis thing may just be converted to passing a global boolean and switching it to get variable initialization to occur only once, as it seems sketchy, thansk.

Now, a bit more elaborate reply to figure out if what I want to do (which is ABSOLUTELY un-necessary as a Global will fix all this).. .but just one more try to expand my brain a bit.

globally I do this

int *pCloudPoint;

void loop
{
A();
B();
}//end loop

I declare a static array here in "A"

void A()
{
static int CloudMaster[]={1,10,100,1000};
pCLoudPoint=&CloudMaster[]; //I dont think I need the dereference " * " here
}

//then in function"X" use it to get a value

void F{}
{
int a, b, c, d;
a=*pCLoudPoint; //a=1 correct?
b=*(pCLoudPoint+1); //b=10 correct? and I had this WRONG previously
etc... to index through the array...
}

Let me know the error of my ways... but with a global pointer decleration and a static array I think this *should* work...

I assume I can use vectors with #include <vector>

Re: C library for Sun/Moon effects

Posted: Wed Mar 14, 2012 11:22 am
by binder
You've got the right idea. The way you are referencing the pointer will work properly.

Code: Select all

b = *(pCloudPoint+1);
You could also do something like this:

Code: Select all

a = *pCloudPoint;
// increments the location of the pointer by one, just like (pCloudPoint+1)
// however, pCloudPoint is always going to point to the latest and 
// you can't go to the beginning without a pCloudPoint-- and then that gets tricky
pCloudPoint++;
b = *pCloudPoint;
You had the pointer initialization correct the first time:

Code: Select all

pCloudPoint = &CloudMaster[0];
// this is the same thing but can be confusing to understand
pCloudPoint = CloudMaster;
I'm still a little uneasy about using a pointer to a statically defined array inside a function. I know that static keeps it in memory and does not get rid of it and I know that the pointer just points to a memory location and the location shouldn't change with the memory since it is static. It just doesn't look right to me and it makes it a little harder to follow.
I did some searching online to look things up and doing exactly that is done in practice. It works for the reasons that I stated before. So despite my thoughts of it not looking right, it is perfectly legal and possible according to the compilers and standards.

So, you've got the right idea. Have fun now. :)

Re: C library for Sun/Moon effects

Posted: Wed Mar 14, 2012 12:00 pm
by rufessor
Cool...Thanks.

So if this is bad form... or seemingly "feels weird" then I will probably go to a global array and leave it at that, perhaps I can find somewhere else to test my pointer "skills"- if that word can be taken to mean absolutely the opposite of its true meaning.

I was going to use a perhaps more std declaration of

new int CloudMaster

and then later, at the new day or reset

delete CloudMaster

to start over again.... Perhaps this is a better way that seemingly looks more correct or is easier to understand?

ANyhow, will not bother you anymore on this topic as its really outside of what is necessary to accomplish the end goal which is the whole point anyway. I will probably end up checking back here for Q's on vector as I am going to move a few arrays that change size day to day, over to vectors. Its going to work the way I have it, as the array never changes size during its use, just upon a new instance being created, but my for loops look ugly as I am constantly having to include variables to size the for loop to the array as I don't know how big it will be until its created with some random generators I have built. If I use vectors in these instances I think I will be much happier and it will be easier to read but does complicate it a tiny bit as its a new container type people will have to deal with if they want to play with this...



Incidently, except for swapping in a vector or two and finishing cleaning up my mess in compile (blah blah expected ; before } yadda yadda) the code is done, so expect me to post something like... ahhh crap... and a question... or better yet... a video of the thing in action...

Re: C library for Sun/Moon effects

Posted: Wed Mar 14, 2012 3:49 pm
by binder
the new / delete stuff will most likely give you problems. it doesn't work well on arduino. arduino is more geared towards C. i got into some really bad problems with using the new / delete stuff with the menu system. i had totally random stuff happening that i could not explain at all and could not track down.

i'd suggest sticking with what you posted about the pointer references or using a global array.

i can't comment on the vector stuff because i haven't used it on arduino at all.

Re: C library for Sun/Moon effects

Posted: Thu Mar 15, 2012 7:29 am
by rufessor
Very good information, will bail on that one. Vector seems interesting, will look into stability with Arduino. Perhaps I will just finish up compile and test as is... not sure I need to be pushing the envelope in terms of my ability (all ready doing that, obviously) as well as in terms of attempting stuff that Arduino is not really designed around.

Thanks again.

Re: C library for Sun/Moon effects

Posted: Thu Mar 15, 2012 4:19 pm
by rufessor
Ok... I think I am done and will begin testing this tonight...

Code compiles cleanly but for one unused variable in the library which I am ignoring.

Entire code for PWM upload is attached. Couple questions.

What happens if you multiply a float by and int and put the result into a channel value which is a byte.

Presumably the multiplication is carried out as a float operation (forced by one being float) and the result then truncated to fit into the byte. Is this correct?

And finally, if anyone has the courage to really look at this can you tell me if you think the strike will occur from the loop as I have it written.

To help out, StrikeMaster is an array with the start and end of each strike for that sequence, it is populated in the Weather function whenever we get an unusually large random shift in cloud intensity during a storm this sets strike to true and repopulates StrikeMaster with a new set of random strike sequences..

SO I am curious if you think the strike sequence will work.
I am trying to set white channel PWM intensity per strike randomly, then set PWM output during strike duration to random value, then recalculate next strike intensity, wait for time to hit strike start and write PWM channel (whites) to zero during interval, then write to new intensity value during next strike duration.
The strike time pattern is randomly generated by weather to create an array StrikeMaster which holds two positions for each strike, the start and end time, and these are cumulative.

I.e. array should look like this for strikes 200 ms apart and 50 msec long
{200,250,450,500,700,750..etc} but it will never be even as each increment is randomized but you can see how if you set up a msec timer and waited for time to be > array 0 and < array 1 and then wrote whites to some random high intensity value while this was true, then reset to zero and indexed to a new intensity value for the next strike, you might get a really cool totally randomized lightning storm. I allow flashes to occur in groups of 2-9 (random in weather) and vary from 100-700 ms between flashed with flash durations to vary from 30-80 msec (or something like that)... so adding it all up I am hoping this will be uber cool and kinda natural looking.

Advice on if the code is doing what I just described would be appreciated. At least this part really only requires inspection of a few lines from the loop.

Here is code that compiles cleanly but will only be on a PWM board later tonight so it might not "run" for many reasons I would not advise loading and expecting it to work JUST yet... give me a little time to run through serial debugging but its gotta be close.

Code: Select all


//AS OF 3/15/12 this code is completely UNTESTED beyond compile error checking- and will likely NOT RUN.

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

//Defines for ReefAngel PWM module

#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5


//*********************************************************************************************************************************
//IF YOU DO NOT WISH TO USE the U.S.A. Day Light Savings Time rules to modify the time stamp for sunrise/sunset calculations...set to false- i.e. if you dont live in the USA etc
boolean ApplyDST=true;

//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long sunrise;
unsigned long sunset;
long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
int ChRiseSet[12];
float ChSlope[12];
int midDay;// exactly 1/2 way between sunrise and sunset, i.e. my take on solar noon.
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)


//What are the max values for ch intensity we want to reach, i.e. at and around solar noon- use 0-255 scale for PWM board byte output (ch0,1,2,3,4,5)
//i.e. 100% channel 0, ~10% channel 1, 40% channel 3 would be {255, 25, 102, ...etc}
byte ChInt[]={
  210,225,210,225,220,0};
// At what point in PWM dimming do your individual channels flicker, input this as PWM output value into the array -NOT % intensity, use actual PWM output(ch0,1,2,3,4,5)
byte flicker[]={
  25,25,25,25,15,0};
  
//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}; 
long int StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[18];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte count;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop

byte SeasonsVar[]={
  0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, sunrisehour, sunriseminute, sunsethour, sunsetminute, Cloudstarthout, Cloudstartminute, Cloudduration,lightningchance, PWM1,PWM2,PWM3
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;//required to know how large the array for CloudMaster will be- I need to use Vectors.... I know.
int *pCloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//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; 
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop()
{
  wdt_reset();
  //Serial.println(ChannelValue[0],DEC);
 if (cmdnum!=255){
    ProcessCMD(cmdnum,datanum);    
    cmdnum=255;
    datanum=255;
  }
// now run through sunrise/sunset calculation and then intensity set and finally weather overlay
  CalSun();
  Insolation();
  Weather();
    if (StrikeNow==false){
      StrikeStart=millis();
      count=0;
    }
    else if (StrikeNow==true){
       int intensity;//strike intensity value for each individual strike calculated once for each strike when index of strike changes i.e. a in for loop
       int elapsed=millis()-StrikeStart;
       if (elapsed>StrikeMaster[(StrikeNumber*2-1)]){
            StrikeNow=false;
       }
       for (byte a=0;a<(StrikeNumber*2);a=(a+2)){ //StrikeNumber is the number of strikes generated this sequence so only look this far into array or we just hit zeros if its not a full stike seq
          if ((elapsed>StrikeMaster[a])&&(elapsed<StrikeMaster[a+1])){
               if (count!=(a+1)){
                  intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
               }
             count=(a+1);
             for (byte a=0; a<6; a++){
                if (Wchannel[a]==1) ChannelValue[a]=intensity;
             }
          
          }  
          else {
            for (byte a=0; a<6; a++){
               if (Wchannel[a]==1) ChannelValue[a]=0;
            }
         }
       }  
    }
    
 
  //Now that we have generated our sun patter, 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]);
  }
  
// DEBUGGING serial comm... you can delete this if you dont want to use it.
 /* Serial.println("DST");
  Serial.println(isDST);
  delay(500);
  Serial.println("trigger");
  Serial.println(trigger);
  delay(500);
  Serial.println("newDay");
  Serial.println(newDay,DEC);
  delay(500);
  Serial.println("sunrise");
  Serial.println(sunrise,DEC);
  delay(500);
  Serial.println("sunset");
  Serial.println(sunset,DEC);
  Serial.println("now()");
 long secon;
  secon=now();
  Serial.println(secon,DEC);
  delay(1000);
*/

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

//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
  /*SeasonsVar[0]=ChannelValue[0];
  SeasonsVar[1];
  SeasonsVar[2];
  SeasonsVar[3];
  SeasonsVar[4];
  SeasonsVar[5];
  SeasonsVar[6];
  SeasonsVar[7];
  SeasonsVar[8];
  SeasonsVar[9];
  SeasonsVar[10];
  SeasonsVar[11];
  Wire.write(SeasonsVar,12);
  */
}
//End Send Data
//*********************************************************************************************************************************
// Function to run Epherma Calc for sunrise/sunset/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 sunrise, sunset, time of highest sun, moon phase, or we do this if system has reset
  if ((hour()==0 && minute()==0 && second()==0) || (trigger=true)){  
    trigger=false;
    long int SecInput;
        // Arduino uses Unix time- seconds since Jan 1 12:00 am UTC 1970
        //Must convert to MST by adding 7 hrs =7*3600=25,200 seconds MST=GMT-7
        //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 subtrace to bring to Jan 1 2000 as origin for time is 
        //#define SECS_YR_2000  (946684800) the time at the start of y2k
        //DST rules are march second sunday until november 1st sunday + 1 hr starting at 2 am which I dont care about so that part is neglected 
      byte hours, minutes;
       if ((ApplyDST==true)&&(isDST==true)){
         time_t t=now()+3600;
         hours=hour(t), minutes=minute(t);
           if (hours!=0 || minutes!=0){
               newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
               SecInput=newDay+25200;
           }  
           else{  
               newDay=(now()-(946684800+3600));
               SecInput=newDay+25200; 
           }
        }    
        else {
          time_t t=now();
          hours=hour(t), minutes=minute(t);
            if (hours!=0 || minutes!=0){
                newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
                SecInput=newDay+25200;
            }  
            else{  
                newDay=(now()-946684800);
                SecInput=newDay+25200;
            }
       } 
     
         //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 sunrise time and sunset time using Epherma Library   
     sunrise=sunset=SecInput;
     SunRise(&sunrise);
     SunSet(&sunset);
       // Correct sunrise data to reflect elapsed time from midnight (newDay)
       // DST correction is all ready in both variables so no need to deal with it
     sunrise-=25200; //Correct back to MST from GMT- DST still accounted for
     sunset-=25200; 
     sunrise-=newDay;//set to elapsed seconds today
     sunset-=newDay;
     
 //*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
 //offsets for sunrise/sunset all values in seconds offset from calculated sunrise or sunset 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 sunrise/sunset 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=2/3.141592653589793238462643383279502884197169399;
      midDay=(sunset-sunrise)/2;
  
     for (byte b=0;b<12;b++){
        if (b%2==0){
          ChRiseSet[b]=sunrise+Choffset[b];
          ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
         }
        else if (b%2==1){
          ChRiseSet[b]=sunset+Choffset[b];
          ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
        }
     }  

//***************** 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 sunrise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
  randomSeed(now()-(random(300000000,1000000000))); //so that we are now more trully random
  byte RainMaker=random(1,101); 
  if (RainMaker<CloudChance){
    CloudToday=true;
  }
 else if (RainMaker>=CloudChance){
    CloudToday=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*/

  int dayLength=(sunset-sunrise);
  byte PercentOvercastMin=10;//% as fraction (0-1 is 0-100%) of daylight hours that would minimially be cloudy on a cloud day
  byte PercentOvercastMax=61;//% of daylight hours that would maximally be cloudy on a cloud day (then add 1 to that #)
  byte Overcast=random(PercentOvercastMin,(PercentOvercastMax+1));
 
  // number of clouds possible for the day, max and min
  byte CloudsMax=8;
  byte CloudsMin=3;
  CloudsTotal=random(CloudsMin,(CloudsMax+1));
  
 
  static int CloudMaster[15];// Set up array to hold start and end times for clouds for the day- dont size it
  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*/
 
     int CloudLength;
     CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
     byte fraction=(random(20,101)/100);//vary each cloud from 20%-180% of an equal fraction of total cloud for day
   
     //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%
     for (byte a=0; (a=(CloudsTotal*2)-1); a=(a+2)){
        byte b=0;
          // if were having an odd # of clouds make sure last one is full length
        if ((CloudsTotal%2!=0) && (a==(CloudsTotal*2)-2)){
           CloudMaster[a]=CloudLength;
           b=b+1;    
        }
           // 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 ((CloudsTotal%2!=0)){
           CloudLength=abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
           CloudMaster[a]=CloudLength;
           b=b+1;
             if (b==2){
                b=0;
                fraction=(random(20,101)/100);//every other cloud recalculate a new fractional distribution for the next set
             }
        } 
        else if (CloudsTotal%2==0){
            CloudLength=abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
            CloudMaster[a]=CloudLength;
            b=b+1;
              if (b==2){
                b=0;
                fraction=(random(20,101)/100);//every other cloud recalculate a new fractional distribution for the next set
              }
        }
     }
       
 
// Now space the clouds out during the day using random fractionation of day segments
      for (byte a=0; a<((CloudsTotal*2)-1); a++){
        if (a%2==1){
          byte b=0;
          //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
          int SunSegment=((dayLength-(dayLength*Overcast))/CloudsTotal);
          int StartTime=(SunSegment*(random(1,101)/100));
          StartTime=StartTime+((SunSegment*b)+sunrise);
          CloudMaster[a]=StartTime;
          b++;
       }
    }  
    
//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)-1);a=(a+2)){
        int endTime;
        endTime=CloudMaster[a]+CloudMaster[a+1];
        CloudMaster[a]=CloudMaster[a+1];
        CloudMaster[a+1]=endTime;
     }
  }//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()
{
    // Calculate time since day started correcting for DST
  int elapsedtime;
  if (isDST==true){
    elapsedtime=(now()+3600-newDay);
  }
  else{
    elapsedtime=(now()-newDay);
  }

//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 deltaY=3.141592653589793238462643383279502884197169399;
 

/* using -cos(pi/2+elapsedtime/slope)*((ChMaxVal-flicker)+flicker) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light.  Turn off lights after sunset or before sunrise etc.
by splitting into half days centered on midday (1/2 ofsunset-sunrise) we center exactly the cos function for every channel so color blends are maintained 
throughout intensity ramp... more or less ... change intensity every 30 seconds throughout the day*/
   for (byte b=0;b<12;b++){
      if ((b%2==0) && (elapsedtime>=ChRiseSet[b]) && (elapsedtime<midDay)){
          if (b==0)ChannelValue[0]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b])+flicker[b]));
          else if (b==2) ChannelValue[1]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b-1]-flicker[b-1])+flicker[b-1]));    
          else if (b==4) ChannelValue[2]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b-2]-flicker[b-2])+flicker[b-2]));
          else if (b==6) ChannelValue[3]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b-3]-flicker[b-3])+flicker[b-3]));
          else if (b==8) ChannelValue[4]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b-4]-flicker[b-4])+flicker[b-4]));
          else if (b==10) ChannelValue[5]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b-5]-flicker[b-5])+flicker[b-5]));
      }
      else if ((b%2==1) && (elapsedtime<=ChRiseSet[b]) && (elapsedtime>=midDay)){
          if (b==1)ChannelValue[0]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-1])+flicker[b-1]));
          else if (b==3) ChannelValue[1]=(-cos((deltaY)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-2])+flicker[b-2]));    
          else if (b==5) ChannelValue[2]=(-cos((deltaY)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-3])+flicker[b-3]));
          else if (b==7) ChannelValue[3]=(-cos((deltaY)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-4])+flicker[b-4])); 
          else if (b==9) ChannelValue[4]=(-cos((deltaY)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-5])+flicker[b-5]));
          else if (b==11) ChannelValue[5]=(-cos((deltaY)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-6])+flicker[b-6]));
      }
       else if (((b%2==0) && (elapsedtime<ChRiseSet[b])) || ((b%2==1) && (elapsedtime>ChRiseSet[b]))){
          Cloud=false;//no lights = no Cloud
          Storm=false;
          if ((b==0) || (b==1)) ChannelValue[0]=0;
          else if ((b==2) || (b=3)) ChannelValue[1]=0;     
          else if ((b==4) || (b=5)) ChannelValue[2]=0;
          else if ((b==6) || (b=7)) ChannelValue[3]=0; 
          else if ((b==8) || (b=9)) ChannelValue[4]=0;
          else if ((b==10) || (b=11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
      }  
   }
}//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 int  CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
 static int PriorCloudCover;  //used to "delay" one side of the tank from the other in cloud passing effects
 int elapsedTime=(now()-newDay);
 static int StormStart;
 static int StormEnd;
 static byte CloudEnd;
 static boolean trigger;//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==true) && (Storm==false)){
     if ((elapsedTime%2)!=0){
       return;
     }
     else if ((elapsedTime%2)==0){// EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
        
          //now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times up
          //seems backwards since CloudCover has not been calculated if you read linearly but it will always be set prior to this=true
          if ((elapsedTime%2)>=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(-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;
                  trigger=true;
                  StormStart=now();
                }
                else if (CloudCover<=0){
                  CloudCover-=(stepsize*1.5);
                }
         
              //Array to give direction to dimming.  e.g. DimOrder[]={0,0,1,1,0,0} would dim PWM channels 0-1,4-5 as FIRST dim value, then channels 2-3 would take on FIRST dime Value 
              //when channels 0-1,4-5 take on SECOND dim value, so the values chase eachother across the PWM output light array according to your set up.  I have 0-1 as RIGHT side lights, 
              //2-3 as LEFT side LED, and 4 as whole tank while I do not use 5
              
              byte DimOrder[]={0,0,1,1,0,0,};
              for (int a=0;a<6;a++){
                 if (DimOrder[a]==0){
                    ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);  
                  }
                  if (DimOrder[a]==1){
                       ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);  
                  }
             }
         } 
   }
 //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=false) && (Storm=true)) || ((Cloud=true) && (Storm=true))){
   //do this next part exactly once per storm then loop past
     if (trigger==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. 
         StormStart=(now()-newDay);
         StormEnd=((StormStart+StormDuration)+(now()-newDay));
         trigger=false;
     }
     //Every 1 second duing a storm change intensity, clouds are movin fast baby
     if ((elapsedTime%1)!=0){
       return;
     }
     else if ((elapsedTime%1)==0){// EVERY one second change the PWM output for a storm 
          if (elapsedTime>=CloudEnd){ //if we randomly had a storm go to cloud end (which is possible but will be rare)
             ClearSky(CloudCover, CloudEnd);
             return;
           }
           else if (elapsedTime>=StormEnd){ //if were done with the storm but still cloudy
             Storm=false;
             return;
         // step through intensity values
         randomSeed(millis());
         byte 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(now()-newDay); //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;
                 }
                 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;
                 } 
               }
             StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
           }
         PriorCloudCover=CloudCover;
         CloudCover = CloudCover + stepsize;
       
           if (CloudCover>=100){//conflicted here, since were dimming by fractional intenstiy its likely we will hit flicker point or lower... might change  how this works
              CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
           }
           else if (CloudCover<=0){
             CloudCover-=(stepsize*1.5);//if were clear sky path reflect random path to cloud
           }
 //*****************IF You want the light to "chase" across the tank****************************
 //you need to specify the left or right side (or front back whatever, depends on how your lights are set up)
 //Use either 0 or 1 to specify groups of channels that dim together (arbitrary but if you need to know direction the cloud will travel you will need to read the next bit of code)
 //assign the same 0 or 1 value to all left side (or front back or whatever) channels and the other value to all right side channels
         byte DimOrder[]={0,0,1,1,0,0,};
 //****************** ok were done changing things*****************************

         for (int a=0;a<6;a++) {
             if (Wchannel[a]==1){
                 ChannelValue[a]=0;
                 continue;
             }
             if (DimOrder[a]==0){
                  ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);  
             }
             else if (DimOrder[a]==1){
                   ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);  
             }
         }
       }  
     }     
 }//end of storm if loop
 
 // Place this loop last as it will only run then cloud is over, and trigger next cloud start and end, this way its not computing during a cloud.
  else {
      for (byte a=0; a<=(CloudsTotal-1); a=(a+2)){
         if ((elapsedTime>=*(pCloudPoint+a)) && (elapsedTime<=*(pCloudPoint+(a+1)))){
             CloudEnd=*(pCloudPoint+(a+1));
             CloudEnd+=120;
             Cloud=true;
          }
          else{
             Cloud=false;
             CloudCover=0;
             PriorCloudCover=0; 
          }
       }
   }
}//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)
{
    int elapsedTime=(now()-newDay);
    // 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 remaining=(CloudEnd-elapsedTime);
    
    int Range=(CloudCover/100);
    int slope=(Range/120);
   
    if (remaining<=0){
      Storm=false;
      Cloud=false;
      return;
    }
    for (byte a=0; a<6; a++){
         ChannelValue[a]=(ChannelValue[a]*((120-remaining)*slope));
    }
}//End Clear Sky function

Re: C library for Sun/Moon effects

Posted: Wed Apr 04, 2012 10:44 pm
by rufessor
Ok... finally back to being able to work on this. I have it working pretty far along...

Sunrise sunset calculations appear to be working.
Cloud number, %overcast and calculation of random duration of cloud times and writing these values to CloudMaster array appears to be robust

Calculation of cloud start times is working perfectly until it hits the last itteration in this for loop.

Code: Select all

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
          int SunSegment=((dayLength-(dayLength*Overcast))/CloudsTotal);
          Serial.print("SunSegment=");
          Serial.println(SunSegment);
          float randomSplit=random(10,101);//clouds will not start until at least 10% of the daylight (no cloud time) fraction of the sunlight period for the day segment has passed
          randomSplit=(randomSplit/100);
          Serial.println(randomSplit);
          StartTime=(sunrise+(SunSegment*randomSplit)+(SunSegment*b));
          Serial.println(StartTime);
          CloudMaster[a]=StartTime;
          StartTime=0;
          Serial.print("b=");
          Serial.println(b);
          Serial.print("Cloudmaster position");
          Serial.println(a);
          Serial.println("is value");
          Serial.println(CloudMaster[a]);
          b=b+1;
    }  
which ouputs this to serial...

SunSegment=6936
0.80
48305
b=0
Cloudmaster position1
is value
48305
SunSegment=6936
0.33
51981
b=1
Cloudmaster position3
is value
51981
SunSegment=6936
0.33
58917
b=2
Cloudmaster position5
is value
58917
SunSegment=6936
0.94
70084
b=3
Cloudmaster position7
is value
70084
SunSegment=6936
0.77
75841
b=4
Cloudmaster position9
is value
75841
SunSegment=6936
0.79
17380
b=5
Cloudmaster position11
is value
17380

What its is supposed to be doing, and is in fact doing for the first 5 iterations through the loop is the following.... which is fairly obvious from code...

but to be exact.
SunSegment is an int (local variable)
CloudMaster is a long int array that is retained in memory (i.e. static)
StartTime is a long int (local variable)
random split is a float which takes on a value between 0.1-1 (local variable)

So why is the last iteration coming up with what appears to be a random number- I don't see how it can do this. Its not overflowing a variable, or writing past the end of an array (Cloud Master has 18 positions) and everything going into it looks to have the correct float/long int etc...

Re: C library for Sun/Moon effects

Posted: Thu Apr 05, 2012 8:52 am
by rimai
Make sure you declare Cloudmaster with at least one more of what you are using.
So, if you use Cloudmaster[6]=0; for example, you need to declare it as int Cloudmaster[7];

Re: C library for Sun/Moon effects

Posted: Thu Apr 05, 2012 8:59 am
by rufessor
Hi-
Thats for sure not a problem I understand the zero based numbering...

any case, the serial print which is giving the wrong value, is from an intermediate calculation that has NOTHING whatsoever to do with the array.

StartTime=(sunrise+(SunSegment*randomSplit)+(SunSegment*b));
Serial.println(StartTime);

THis is WRONG... which I do not understand.

and none of the values used in its calculation have any relationship to any array at all...

Re: C library for Sun/Moon effects

Posted: Thu Apr 05, 2012 9:15 am
by rimai
I think you are overflowing the variable.

Code: Select all

StartTime=(sunrise+(SunSegment*randomSplit)+(SunSegment*b));
SunSegment*b is overflowing int capacity. It only goes up to about 32K because SunSegment is declared as int.
The way the math is calculated is always on the current variable and not the end resut (StartTime).
So the SunSegment*b calculation will be done in whatever type you declared SunSegment, which was int.
Try increasing it to long.

Re: C library for Sun/Moon effects

Posted: Thu Apr 05, 2012 9:26 am
by rufessor
THANK YOU!!!!!!!

I am SURE you are right... I had thought of this but only with respect to the int*float and figured I was fine, I did NOT know what you just explained. Guarantee you fixed it. AWESOME. Thanks.... I knew it was going to be something I was not getting as its pretty much impossible that simple math is wrong... and trust me I tried a few different ways of stating that in terms of order of operations... so I am sure your correct. Also makes perfect sense that it overflows only at the end.

THANK YOU>>>>>>>>>>> I owe you a beer.

Seriously hoping I have this running soon. Its turning out that I have some trouble shooting to do, but for the most part I have been fixing a few instances of variable overflows- you caught this one, I found another prior... and a couple stupid logic errors like using if (x=y) instead of (x==y) which both compile but do quite different things :)

So, the lights are actually coming on, but I have no idea yet how much of an issue I have in that code segment, but hoping things will go quickly now as thats the next segment to debug but at least is working to some degree, perhaps fully but I would be surprised. Then I need to see if the execution of the clouds works correctly, thats a bit complex so I would be amazed if it goes without a few bugs showing up... after that. Its VIDEO TIME!

Re: C library for Sun/Moon effects

Posted: Thu Apr 05, 2012 9:40 am
by rufessor
Ironic thought.... I may find that I seriously dislike my cloud effect, or that I cannot really see it... that would be so awesome after all this work. At least I accomplished my goal of learning c++ a bit. If I can get it to run cleanly I will be happy, even happier if I like it!

Whats the best way to post a video?

Re: C library for Sun/Moon effects

Posted: Thu Apr 05, 2012 3:05 pm
by JNieuwenhuizen
Is there any progress on this? It would be way cool to see this working.

Re: C library for Sun/Moon effects

Posted: Fri Apr 06, 2012 7:28 am
by rufessor
Hi-


Added as edit-

If there are a few guinea pigs that would like to try this out, I would be willing to help you by directing you to the relevant pieces that would need (most likely) to be edited to fit your configuration--- to see how it works.

This would of course occur after I have it working at least somewhat reliably on my tank, but if you interested just post here with interest and I will PM when its working for me.


Thanks for the interest. Its quite close to working, the code is fully formed and I am going through a rather extensive serial debugging/verification process to ensure that its doing what its supposed to be doing. I wish I could tell you that this will be finished in x days, as its quite close to working now.... but my day job and other commitments preclude me from spending much serious time on this (Darn it). I have been trying to finish it as its close enough now that I am quite excited to see if it works as intended and in fact looks good. One good thing, since its a PWM board based program, its going to be super easy to download and try out as it will not effect the main controller much. At this point its a function only- no comm module. I intend to slowly add in the ability for the main controller to communicate with this program to say cancel a storm, trigger a storm, and display intensity values and mode (day, night, moon, cloud, storm, etc). But this will be after I get it running stand alone.

There is a SMALL chance it might be finished this weekend, but I doubt it. I would not advise doing anything with the code I have posted thus far, there are simply too many bugs, mostly little things like the prior discussion, but unless your a true sucker for punishment, going through this non-working code as a third person and trying to figure out whats not working (especially in the formatting and structure of the many arrays) would be seriously seriously painful, it sucks for me.

Here is the latest version of the code, currently I am trying to figure out where I screwed up the elapsed time, or at least how two different values are not agreeing so although the lights do come on, and stay on for the correct times, its not at the correct time- if that makes any sense. (not asking for help here... just as an example). Once this is fixed all thats left to test is the actual execution of the cloud effect and the lightning strike so its CLOSE.

This code is much much less buggy than the last version but still NOT functional... but if your curious where its at... Also... its FILLED with serial Debugging. I now wish I had finished my serial debugging macro and actually used it... oh well.

Code: Select all


//AS OF 4/6/12 this code is marginally functional in sections but not yet working, do not load to PWM board-non functional - 

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

//Defines for ReefAngel PWM module

#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5


//*********************************************************************************************************************************
//IF YOU DO NOT WISH TO USE the U.S.A. Day Light Savings Time rules to modify the time stamp for sunrise/sunset calculations...set to false- i.e. if you dont live in the USA etc
boolean ApplyDST=true;
long int elapsedTime;//used multiple places as elapsed since new day.
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long sunrise;
unsigned long sunset;
long int newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long int ChRiseSet[12];
float ChSlope[12];
long int midDay;// exactly 1/2 way between sunrise and sunset, i.e. my take on solar noon.
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)


//What are the max values for ch intensity we want to reach, i.e. at and around solar noon- use 0-255 scale for PWM board byte output (ch0,1,2,3,4,5)
//i.e. 100% channel 0, ~10% channel 1, 40% channel 3 would be {255, 25, 102, ...etc}
byte ChInt[]={
  250,235,250,235,230,0};
// At what point in PWM dimming do your individual channels flicker, input this as PWM output value into the array -NOT % intensity, use actual PWM output(ch0,1,2,3,4,5)
byte flicker[]={
  25,25,25,25,15,0};
  
//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}; 
long int StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[18];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte count;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop

byte SeasonsVar[]={
  0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, sunrisehour, sunriseminute, sunsethour, sunsetminute, Cloudstarthout, Cloudstartminute, Cloudduration,lightningchance, PWM1,PWM2,PWM3
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;//required to know how large the array for CloudMaster will be- I need to use Vectors.... I know.
long int *pCloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//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; 
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop()
{
  wdt_reset();
  //Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255){
    ProcessCMD(cmdnum,datanum);    
    cmdnum=255;
    datanum=255;
  }
// now run through sunrise/sunset calculation and then intensity set and finally weather overlay
  CalSun();
  Insolation();
  Weather();
    if (StrikeNow==false){
      StrikeStart=millis();
      count=0;
    }
    else if (StrikeNow==true){
       int intensity=0;//strike intensity value for each individual strike calculated once for each strike when index of strike changes i.e. a in for loop
       int elapsed=millis()-StrikeStart;
       if (elapsed>StrikeMaster[(StrikeNumber*2-1)]){
            StrikeNow=false;
       }
       for (byte a=0;a<(StrikeNumber*2);a=(a+2)){ //StrikeNumber is the number of strikes generated this sequence so only look this far into array or we just hit zeros if its not a full stike seq
          if ((elapsed>StrikeMaster[a])&&(elapsed<StrikeMaster[a+1])){
               if (count!=(a+1)){
                  intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
               }
             count=(a+1);
             for (byte a=0; a<6; a++){
                if (Wchannel[a]==1) ChannelValue[a]=intensity;
             }
          
          }  
          else {
            for (byte a=0; a<6; a++){
               if (Wchannel[a]==1) ChannelValue[a]=0;
            }
         }
       }  
    }
    

  //Now that we have generated our sun patter, 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]);
  }
  
// DEBUGGING serial comm... you can delete this if you dont want to use it.
/* Serial.println("DST");
  Serial.println(isDST);
  delay(500);
  Serial.println("trigger");
  Serial.println(trigger);
  delay(500);
  Serial.println("newDay");
  Serial.println(newDay,DEC);
  delay(500);
  Serial.println("sunrise");
  Serial.println(sunrise,DEC);
  delay(500);
  Serial.println("sunset");
  Serial.println(sunset,DEC);
  Serial.println("now()");
long secon;
  secon=now();
  Serial.println(secon,DEC);
  delay(1000);
*/

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

//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
  /*SeasonsVar[0]=ChannelValue[0];
  SeasonsVar[1];
  SeasonsVar[2];
  SeasonsVar[3];
  SeasonsVar[4];
  SeasonsVar[5];
  SeasonsVar[6];
  SeasonsVar[7];
  SeasonsVar[8];
  SeasonsVar[9];
  SeasonsVar[10];
  SeasonsVar[11];
  Wire.write(SeasonsVar,12);
  */
}
//End Send Data
//*********************************************************************************************************************************
// Function to run Epherma Calc for sunrise/sunset/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 sunrise, sunset, 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);
    
    long int SecInput;
        // Arduino uses Unix time- seconds since Jan 1 12:00 am UTC 1970
        //Must convert to MST by adding 7 hrs =7*3600=25,200 seconds MST=GMT-7
        //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 subtrace to bring to Jan 1 2000 as origin for time is 
        //#define SECS_YR_2000  (946684800) the time at the start of y2k
        //DST rules are march second sunday until november 1st sunday + 1 hr starting at 2 am which I dont care about so that part is neglected 
      byte hours, minutes;
       if ((ApplyDST==true)&&(isDST==true)){
         time_t t=now()+3600;
         hours=hour(t), minutes=minute(t);
           if (hours!=0 || minutes!=0){
               newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
               SecInput=newDay+25200;
           }  
           else{  
               newDay=(now()-(946684800+3600));
               SecInput=newDay+25200; 
           }
        }    
        else {
          time_t t=now();
          hours=hour(t), minutes=minute(t);
            if (hours!=0 || minutes!=0){
                newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
                SecInput=newDay+25200;
            }  
            else{  
                newDay=(now()-946684800);
                SecInput=newDay+25200;
            }
       } 
     
         //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 sunrise time and sunset time using Epherma Library   
     sunrise=SecInput;
     sunset=SecInput;
     SunRise(&sunrise);
     SunSet(&sunset);
       // Correct sunrise data to reflect elapsed time from midnight (newDay)
       // DST correction is all ready in both variables so no need to deal with it
     sunrise-=25200; //Correct back to MST from GMT- DST still accounted for
     sunset-=25200; 
     sunrise-=newDay;//set to elapsed seconds today
     sunset-=newDay;
     Serial.println("sunrise");
     Serial.println(sunrise);
     Serial.println("sunset");
     Serial.println(sunset);
//*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
//offsets for sunrise/sunset all values in seconds offset from calculated sunrise or sunset 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 sunrise/sunset 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=2/3.141592653589793238462643383279502884197169399;
      midDay=(sunset-sunrise)/2;
  
     for (byte b=0;b<12;b++){//working as of April 5 2012 serial tested
        if (b%2==0){
          ChRiseSet[b]=sunrise+Choffset[b];
          ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
          /*Serial.print("Rise/Set a=");
          Serial.println(b);
          Serial.print("Value=");
          Serial.print(ChRiseSet[b]);
          Serial.print("Slope");
          Serial.println(ChSlope[b]);*/
         }
        else if (b%2==1){
          ChRiseSet[b]=sunset+Choffset[b];
          ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
          /*Serial.print("Rise/Set a=");
          Serial.println(b);
          Serial.print("Value=");
          Serial.print(ChRiseSet[b]);
          Serial.print("Slope");
          Serial.println(ChSlope[b]);*/
        }
     }  

//***************** 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 sunrise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
  randomSeed(now()-(random(300000000,1000000000))); //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 int dayLength=(sunset-sunrise);
  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 int 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 int 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 int 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; 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 int SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));
        
        if (fraction<1){
          abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)))
          StartTime=sunrise+((SunSegment*b+abs(b*SunSegment-(SunSegment*fraction)))+(c*SunSegment));
          CloudMaster[a]=StartTime;
          StartTime=0;
          b=b+1;
        }
        else if (fraction==1){
          StartTime=sunrise+(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=sunrise+(((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.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 int 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);
  }//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()
{
    // Calculate time since day started correcting for DST
  if (isDST==true){
    elapsedTime=((now()-946684800)+3600)-newDay;
  }
  else{
    elapsedTime=(now()-946684800)-newDay;
  }
if (elapsedTime%2==0){//only change lights every 2 seconds
  Serial.println("Insolation 2 sec elapsed");
  //Serial.print("Elapsed Time is=");
  Serial.println(elapsedTime);
  //Serial.print(and now converted to new day should be");
  //long int blha= (now()-946684800+3600);
   //Serial.println(blha);
//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 deltaY=3.141592653589793238462643383279502884197169399;

/* using -cos(pi/2+elapsedTime/slope)*((ChMaxVal-flicker)+flicker) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light.  Turn off lights after sunset or before sunrise etc.
by splitting into half days centered on midday (1/2 ofsunset-sunrise) we center exactly the cos function for every channel so color blends are maintained 
throughout intensity ramp... more or less ... change intensity every 30 seconds throughout the day*/
   for (byte b=0;b<12;b++){
      if ((b%2==0) && (elapsedTime>=ChRiseSet[b]) && (elapsedTime<midDay)){
          if (b==0){ ChannelValue[0]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b])+flicker[b]));
          Serial.print("ch0 setting first 1/2 day=");
          Serial.println(ChannelValue[0]);}
          else if (b==2) ChannelValue[1]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b-1]-flicker[b-1])+flicker[b-1]));    
          else if (b==4) ChannelValue[2]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b-2]-flicker[b-2])+flicker[b-2]));
          else if (b==6) ChannelValue[3]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b-3]-flicker[b-3])+flicker[b-3]));
          else if (b==8) ChannelValue[4]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b-4]-flicker[b-4])+flicker[b-4]));
          else if (b==10) ChannelValue[5]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b-5]-flicker[b-5])+flicker[b-5]));
      }
      else if ((b%2==1) && (elapsedTime<=ChRiseSet[b]) && (elapsedTime>=midDay)){
          if (b==1){ChannelValue[0]=(-cos((deltaY)+(elapsedTime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-1])+flicker[b-1]));
          Serial.print("ch0 setting second 1/2 day=");
          Serial.println(ChannelValue[0]);}
          else if (b==3) ChannelValue[1]=(-cos((deltaY)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-2])+flicker[b-2]));    
          else if (b==5) ChannelValue[2]=(-cos((deltaY)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-3])+flicker[b-3]));
          else if (b==7) ChannelValue[3]=(-cos((deltaY)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-4])+flicker[b-4])); 
          else if (b==9) ChannelValue[4]=(-cos((deltaY)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-5])+flicker[b-5]));
          else if (b==11) ChannelValue[5]=(-cos((deltaY)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-6])+flicker[b-6]));
      }
       else if (((b%2==0) && (elapsedTime<ChRiseSet[b])) || ((b%2==1) && (elapsedTime>ChRiseSet[b]))){
          Cloud=false;//no lights = no Cloud
          Storm=false;
          Serial.println("were not in daylight");
          if ((b==0) || (b==1)) ChannelValue[0]=0;
          else if ((b==2) || (b=3)) ChannelValue[1]=0;     
          else if ((b==4) || (b=5)) ChannelValue[2]=0;
          else if ((b==6) || (b=7)) ChannelValue[3]=0; 
          else if ((b==8) || (b=9)) ChannelValue[4]=0;
          else if ((b==10) || (b=11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
      }  
   }
  /* 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 int  CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static int PriorCloudCover;  //used to "delay" one side of the tank from the other in cloud passing effects
static long int StormStart;
static long int StormEnd;
static long int 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==true) && (Storm==false)){
     if ((elapsedTime%2)!=0){
       return;
     }
     else if ((elapsedTime%2)==0){// EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
        Serial.println("in a cloud");
          //now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times up
          //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){
               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(-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=now();
                }
                else if (CloudCover<=0){
                  CloudCover-=(stepsize*1.5);
                }
         
              //Array to give direction to dimming.  e.g. DimOrder[]={0,0,1,1,0,0} would dim PWM channels 0-1,4-5 as FIRST dim value, then channels 2-3 would take on FIRST dime Value 
              //when channels 0-1,4-5 take on SECOND dim value, so the values chase eachother across the PWM output light array according to your set up.  I have 0-1 as RIGHT side lights, 
              //2-3 as LEFT side LED, and 4 as whole tank while I do not use 5
              
              byte DimOrder[]={0,0,1,1,0,0,};
              for (int a=0;a<6;a++){
                 if (DimOrder[a]==0){
                    ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);  
                  }
                  if (DimOrder[a]==1){
                       ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);  
                  }
             }
         } 
   }
//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=false) && (Storm=true)) || ((Cloud=true) && (Storm=true))){
   //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. 
         StormStart=(now()-newDay);
         StormEnd=((StormStart+StormDuration)+(now()-newDay));
         wtrigger=false;
     }
     //Every 1 second duing a storm change intensity, clouds are movin fast baby
     if ((elapsedTime%1)!=0){
       return;
     }
     else if ((elapsedTime%1)==0){// EVERY one second change the PWM output for a storm 
          if (elapsedTime>=CloudEnd){ //if we randomly had a storm go to cloud end (which is possible but will be rare)
             ClearSky(CloudCover, CloudEnd);
             return;
           }
           else if (elapsedTime>=StormEnd){ //if were done with the storm but still cloudy
             Storm=false;
             return;
         // step through intensity values
         randomSeed(millis());
         byte 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(now()-newDay); //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;
                 }
                 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;
                 } 
               }
             StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
           }
         PriorCloudCover=CloudCover;
         CloudCover = CloudCover + stepsize;
       
           if (CloudCover>=100){//conflicted here, since were dimming by fractional intenstiy its likely we will hit flicker point or lower... might change  how this works
              CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
           }
           else if (CloudCover<=0){
             CloudCover-=(stepsize*1.5);//if were clear sky path reflect random path to cloud
           }
//*****************IF You want the light to "chase" across the tank****************************
//you need to specify the left or right side (or front back whatever, depends on how your lights are set up)
//Use either 0 or 1 to specify groups of channels that dim together (arbitrary but if you need to know direction the cloud will travel you will need to read the next bit of code)
//assign the same 0 or 1 value to all left side (or front back or whatever) channels and the other value to all right side channels
         byte DimOrder[]={0,0,1,1,0,0,};
//****************** ok were done changing things*****************************

         for (int a=0;a<6;a++) {
             if (Wchannel[a]==1){
                 ChannelValue[a]=0;
                 continue;
             }
             if (DimOrder[a]==0){
                  ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);  
             }
             else if (DimOrder[a]==1){
                   ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);  
             }
         }
       }  
     }     
}//end of storm if loop

// Place this loop last as it will only run then cloud is over, and trigger next cloud start and end, this way its not computing during a cloud.
  else {
      for (byte a=0; a<=(CloudsTotal-1); a=(a+2)){
         if ((elapsedTime>=*(pCloudPoint+a)) && (elapsedTime<=*(pCloudPoint+(a+1)))){
             CloudEnd=*(pCloudPoint+(a+1));
             CloudEnd=CloudEnd+120;
             Cloud=true;
          }
          else{
             Cloud=false;
             CloudCover=0;
             PriorCloudCover=0; 
          }
       }
   }
}//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)
{
    int elapsedTime=(now()-newDay);
    // 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 remaining=(CloudEnd-elapsedTime);
    
    int Range=(CloudCover/100);
    int slope=(Range/120);
   
    if (remaining<=0){
      Storm=false;
      Cloud=false;
      return;
    }
    for (byte a=0; a<6; a++){
         ChannelValue[a]=(ChannelValue[a]*((120-remaining)*slope));
    }
}//End Clear Sky function

Re: C library for Sun/Moon effects

Posted: Sat Apr 07, 2012 7:15 am
by binder
rufessor wrote:Ironic thought.... I may find that I seriously dislike my cloud effect, or that I cannot really see it... that would be so awesome after all this work. At least I accomplished my goal of learning c++ a bit. If I can get it to run cleanly I will be happy, even happier if I like it!

Whats the best way to post a video?
Upload to youtube and then when you create a post, use the "youtube" button to place the link inside it.

Re: C library for Sun/Moon effects

Posted: Mon Apr 16, 2012 2:06 pm
by rufessor
I need to get to the aquarium to test this, but I think I found an annoying bug that I ahd overlooked (i.e. midDay is NOT (set-rise)/2 but instead has to be ((set-rise)/2)+rise since i am using elapsed seconds of daylight. THis combined with some likely variable overflow was corrupting things so I think I may finally have time accounting working. Again, would not upload this yet but these bugs were annoying me and I think I have it close, updating to use mac instead of parallels on Mac so also want a nice spot I can get the latest code back if my upgrade kills things (which it should NOT)

Code: Select all



//AS OF 3/15/12 this code is completely UNTESTED beyond compile error checking- and will likely NOT RUN.

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

//Defines for ReefAngel PWM module

#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5


//*********************************************************************************************************************************
//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.
//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];
unsigned 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)


//What are the max values for ch intensity we want to reach, i.e. at and around solar noon- use 0-255 scale for PWM board byte output (ch0,1,2,3,4,5)
//i.e. 100% channel 0, ~10% channel 1, 40% channel 3 would be {255, 25, 102, ...etc}
byte ChInt[]={
  250,250,250,250,250,0};
// At what point in PWM dimming do your individual channels flicker, input this as PWM output value into the array -NOT % intensity, use actual PWM output(ch0,1,2,3,4,5)
byte flicker[]={
  25,25,25,25,15,0};
  
//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}; 
long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[18];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte count;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop

byte SeasonsVar[]={
  0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, risehour, riseminute, sethour, setminute, Cloudstarthout, Cloudstartminute, Cloudduration,lightningchance, PWM1,PWM2,PWM3
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;//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.
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//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; 
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop()
{
  wdt_reset();
  //Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255){
    ProcessCMD(cmdnum,datanum);    
    cmdnum=255;
    datanum=255;
  }
// now run through rise/set calculation and then intensity set and finally weather overlay
  CalSun();
  Insolation();
  Weather();
    if (StrikeNow==false){
      StrikeStart=millis();
      count=0;
    }
    else if (StrikeNow==true){
       int intensity=0;//strike intensity value for each individual strike calculated once for each strike when index of strike changes i.e. a in for loop
       int elapsed=millis()-StrikeStart;
       if (elapsed>StrikeMaster[(StrikeNumber*2-1)]){
            StrikeNow=false;
       }
       for (byte a=0;a<(StrikeNumber*2);a=(a+2)){ //StrikeNumber is the number of strikes generated this sequence so only look this far into array or we just hit zeros if its not a full stike seq
          if ((elapsed>StrikeMaster[a])&&(elapsed<StrikeMaster[a+1])){
               if (count!=(a+1)){
                  intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
               }
             count=(a+1);
             for (byte a=0; a<6; a++){
                if (Wchannel[a]==1) ChannelValue[a]=intensity;
             }
          
          }  
          else {
            for (byte a=0; a<6; a++){
               if (Wchannel[a]==1) ChannelValue[a]=0;
            }
         }
       }  
    }
   
 

  //Now that we have generated our sun patter, 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
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
  /*SeasonsVar[0]=ChannelValue[0];
  SeasonsVar[1];
  SeasonsVar[2];
  SeasonsVar[3];
  SeasonsVar[4];
  SeasonsVar[5];
  SeasonsVar[6];
  SeasonsVar[7];
  SeasonsVar[8];
  SeasonsVar[9];
  SeasonsVar[10];
  SeasonsVar[11];
  Wire.write(SeasonsVar,12);
  */
}
//End Send Data
//*********************************************************************************************************************************
// 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.57080;//1/2 * pi
      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/(midDay+abs(Choffset[b]));
          Serial.print("Rise/Set a=");
          Serial.println(b);
          Serial.print("Value=");
          Serial.print(ChRiseSet[b]);
          Serial.print("Slope");
          Serial.println(ChSlope[b]);
         }
        else if (b%2==1){
          ChRiseSet[b]=set+
          
          (Choffset[b]);
          ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
          Serial.print("Rise/Set a=");
          Serial.println(b);
          Serial.print("Value=");
          
          
          
          
          Serial.print(ChRiseSet[b]);
          Serial.print("Slope");
          Serial.println(ChSlope[b]);
        }
     }  

//***************** 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(300000000,1000000000))); //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);
  }//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()
{
    // Calculate time since day started correcting for DST
  if (isDST==true){
    elapsedTime=((now()-946684800)+3600)-newDay;
  }
  else{
    elapsedTime=(now()-946684800)-newDay;
  }
if (elapsedTime%2==0){//only change lights every 2 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 deltaY=3.141592653589793238462643383279502884197169399;

/* using -cos(pi/2+elapsedTime/slope)*((ChMaxVal-flicker)+flicker) 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 30 seconds throughout the day*/
   for (byte b=0;b<12;b++){
      if ((b%2==0) && (elapsedTime>=ChRiseSet[b]) && (elapsedTime<midDay)){
          if (b==0){ ChannelValue[0]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b])+flicker[b]));
          Serial.print("ch0 setting first 1/2 day=");
          Serial.println(ChannelValue[0]);}
          else if (b==2) ChannelValue[1]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b-1]-flicker[b-1])+flicker[b-1]));    
          else if (b==4) ChannelValue[2]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b-2]-flicker[b-2])+flicker[b-2]));
          else if (b==6) ChannelValue[3]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b-3]-flicker[b-3])+flicker[b-3]));
          else if (b==8) ChannelValue[4]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b-4]-flicker[b-4])+flicker[b-4]));
          else if (b==10) ChannelValue[5]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b-5]-flicker[b-5])+flicker[b-5]));
      }
      else if ((b%2==1) && (elapsedTime<=ChRiseSet[b]) && (elapsedTime>=midDay)){
          if (b==1){ChannelValue[0]=(-cos((deltaY)+(elapsedTime-rise/ChSlope[b]))*((ChInt[b]-flicker[b-1])+flicker[b-1]));
          Serial.print("ch0 setting second 1/2 day=");
          Serial.println(ChannelValue[0]);}
          else if (b==3) ChannelValue[1]=(-cos((deltaY)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b-2])+flicker[b-2]));    
          else if (b==5) ChannelValue[2]=(-cos((deltaY)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b-3])+flicker[b-3]));
          else if (b==7) ChannelValue[3]=(-cos((deltaY)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b-4])+flicker[b-4])); 
          else if (b==9) ChannelValue[4]=(-cos((deltaY)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b-5])+flicker[b-5]));
          else if (b==11) ChannelValue[5]=(-cos((deltaY)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b-6])+flicker[b-6]));
      }
       else if (((b%2==0) && (elapsedTime<ChRiseSet[b])) || ((b%2==1) && (elapsedTime>ChRiseSet[b]))){
          Cloud=false;//no lights = no Cloud
          Storm=false;
          Serial.println("were not in daylight");
          if ((b==0) || (b==1)) ChannelValue[0]=0;
          else if ((b==2) || (b=3)) ChannelValue[1]=0;     
          else if ((b==4) || (b=5)) ChannelValue[2]=0;
          else if ((b==6) || (b=7)) ChannelValue[3]=0; 
          else if ((b==8) || (b=9)) ChannelValue[4]=0;
          else if ((b==10) || (b=11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
      }  
   }
  /* Serial.println(ChannelValue[0]);
   Serial.println(ChannelValue[1]);
   Serial.println(ChannelValue[2]);
   Serial.println(ChannelValue[3]);
   Serial.println(ChannelValue[4]);
   Serial.println(ChannelValue[5]);*/
   Serial.print("Insolation elapsed time=");
   Serial.println(elapsedTime);
 }
}//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 int  CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static int 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==true) && (Storm==false)){
     if ((elapsedTime%2)!=0){
       return;
     }
     // EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
        Serial.println("in a cloud");
          //now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times up
          //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){
               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(-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);
                }
         
              //Array to give direction to dimming.  e.g. DimOrder[]={0,0,1,1,0,0} would dim PWM channels 0-1,4-5 as FIRST dim value, then channels 2-3 would take on FIRST dime Value 
              //when channels 0-1,4-5 take on SECOND dim value, so the values chase eachother across the PWM output light array according to your set up.  I have 0-1 as RIGHT side lights, 
              //2-3 as LEFT side LED, and 4 as whole tank while I do not use 5
              
              byte DimOrder[]={0,0,1,1,0,0,};
              for (int a=0;a<6;a++){
                 if (DimOrder[a]==0){
                    ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);  
                  }
                  if (DimOrder[a]==1){
                       ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);  
                  }
             } 
   }
//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=false) && (Storm=true)) || ((Cloud=true) && (Storm=true))){
   //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. 
         StormStart=elapsedTime;
         StormEnd=((StormStart+StormDuration)+elapsedTime);
         wtrigger=false;
     }
     //Every 1 second duing a storm change intensity, clouds are movin fast baby
     if ((elapsedTime%1)!=0){
       return;
     }
          if (elapsedTime>=CloudEnd){ //if we randomly had a storm go to cloud end (which is possible but will be rare)
             ClearSky(CloudCover, CloudEnd);
             return;
           }
           else if (elapsedTime>=StormEnd){ //if were done with the storm but still cloudy
             Storm=false;
             return;
         // step through intensity values
         randomSeed(millis());
         byte 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(now()-newDay); //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;
                 }
                 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;
                 } 
               }
             StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
           }
         PriorCloudCover=CloudCover;
         CloudCover = CloudCover + stepsize;
       
           if (CloudCover>=100){//conflicted here, since were dimming by fractional intenstiy its likely we will hit flicker point or lower... might change  how this works
              CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
           }
           else if (CloudCover<=0){
             CloudCover-=(stepsize*1.5);//if were clear sky path reflect random path to cloud
           }
//*****************IF You want the light to "chase" across the tank****************************
//you need to specify the left or right side (or front back whatever, depends on how your lights are set up)
//Use either 0 or 1 to specify groups of channels that dim together (arbitrary but if you need to know direction the cloud will travel you will need to read the next bit of code)
//assign the same 0 or 1 value to all left side (or front back or whatever) channels and the other value to all right side channels
         byte DimOrder[]={0,0,1,1,0,0,};
//****************** ok were done changing things*****************************

         for (int a=0;a<6;a++) {
             if (Wchannel[a]==1){
                 ChannelValue[a]=0;
                 continue;
             }
             if (DimOrder[a]==0){
                  ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);  
             }
             else if (DimOrder[a]==1){
                   ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);  
             }
         }
       }  
     }//end of storm if loop

// Place this loop last as it will only run then cloud is over, and trigger next cloud start and end, this way its not computing during a cloud.
  else {
      for (byte a=0; a<=(CloudsTotal-1); a=(a+2)){
         if ((elapsedTime>=*(pCloudPoint+a)) && (elapsedTime<=*(pCloudPoint+(a+1)))){
             CloudEnd=*(pCloudPoint+(a+1));
             CloudEnd=CloudEnd-120;
             Cloud=true;
          }
          else{
             Cloud=false;
             CloudCover=0;
             PriorCloudCover=0; 
          }
       }
   }
}//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 remaining=(CloudEnd-elapsedTime);
    
    int Range=(CloudCover/100);
    int slope=(Range/120);
   
    if (remaining<=0){
      Storm=false;
      Cloud=false;
      return;
    }
    for (byte a=0; a<6; a++){
         ChannelValue[a]=(ChannelValue[a]*((120-remaining)*slope));
    }
}//End Clear Sky function

Re: C library for Sun/Moon effects

Posted: Tue Apr 17, 2012 9:38 pm
by rufessor
SOOOO close-
Ohhh.. and I switched to ALL MAC- arduino etc.. went well and my serial comm is MUCH better, using parallels for Mac it worked but god help you if your computer went to sleep with arduino running a serial comm session... UGH. Still not so great but MUCH better running pure mac instead of the combo of parallels emulating windows and then the serial USB driver installed in the windows environment emulating a serial port (not surprising)

I need a little help, at least at 10:30 at night I cannot figure this out... but I have come a very long way.

IT would appear that all time accounting is working, all cloud calculations, start, end, etc etc are working, and that the sorta complicated cosine function I implemented to calculate intensity for each channel based upon the 1/2 day length is working and all the worry about floating point calc screwing up is now in the past...

here is the funny part...
If you look at this statement... and I tell you that elapsedTime is a long value that counts seconds

if (elapsedTime%2==0){
calculate all channel intensity and set them...
using a lot of math and floating point math...
}

I had (STOOPID) though about this as a way to get that function to run every two seconds...

uhhhh...... yeah... not quite. The computer is MUCH faster than the second as an Int value... so it actually loops through the calculations a good number of times (didn't count but 10-20 or something) before that becomes FALSE when the int value for seconds actually changes.

For some reason, my brain is not working this out... whats the dirt simple stupid way to get it to see that the integer value for elapsedTime has changed to get a elapsedTime%2==0 to become true, then loop through the calculations ONE TIME, and wait for the value to become false, and then true again. I KNOW I know how to do this.. but its pissing me off currently as its the last thing NOT working that should be easy to fix and my first few attempts failed
Was trying stuff like this

if elapsedTime%2==0
set a flag to true

if flag is true run calculation and set it to false..

ummm... then it just RESETS IT TO TRUE on the next cycle because the clock didn't move yet!!!!


Then I thought I would track elapsedTime-Some other stored time... but the same crap happens... I guess I just cannot get my head away from using TIME to do this... its the whole problem in the first place.

HELP! Please

This is getting pretty seriously long and complicated, but its commented as well as I can... but here is the code with the strike function commented out (this is likely seriously wrong, wrote it, debugged to get it to compile-have yet to look at it prior to today, just did and was seriously confused by myself... it will get better soon)

I went with a selective switch in insolation, which seems to be working and cleans up the code a TON for that part.
the actual if statement is near the top of the insolation function if you want to see it for real

TONS of serial debugging into it now, but I will post the serial output once its working well enough to illustrate everything- its entertaining to see it in action. Then I suppose I will comment it all out but leave it otherwise intact as its worth it and if people want to try this, it is very very useful.

Posting code now because most of it is working,

Finally, for some reason (I have not looked at this yet, but I may ask for help soon) although my insolation function is setting the correct value for the channel output, the lights are not coming on... so I post the entire code as the lights should be written to in the loop, and the ChannelValue[] set in Insolation ( also modified in Weather and by strike but unsure if these are screwing it up or not as I didn't see them running) Anyhow, if you see something in the loop thats just plain wrong in terms of writing to the PWM output, let me know, else I will try to work on this (have not really addressed it yet so don't spend much time on this part).

Code: Select all

//this is not working code, its very close but if you run it currently, your lights will not come on and other bad things may happen... just so you know.
#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> 

//Defines for ReefAngel PWM module

#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5


//*********************************************************************************************************************************
//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.
//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[]={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
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}; 
long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[18];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte count;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop

byte SeasonsVar[]={
  0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, risehour, riseminute, sethour, setminute, Cloudstarthout, Cloudstartminute, Cloudduration,lightningchance, PWM1,PWM2,PWM3
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;//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.
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//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;
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop()
{
  wdt_reset();
  //Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255){
    ProcessCMD(cmdnum,datanum);    
    cmdnum=255;
    datanum=255;
  }
// now run through rise/set calculation and then intensity set and finally weather overlay
  CalSun();
  Insolation();
/*  Weather();
    if (StrikeNow==false){
      StrikeStart=millis();
      count=0;
    }
    else if (StrikeNow==true){
       int intensity=0;//strike intensity value for each individual strike calculated once for each strike when index of strike changes i.e. a in for loop
       int elapsed=millis()-StrikeStart;
       if (elapsed>StrikeMaster[(StrikeNumber*2-1)]){
            StrikeNow=false;
       }
       for (byte a=0;a<(StrikeNumber*2);a=(a+2)){ //StrikeNumber is the number of strikes generated this sequence so only look this far into array or we just hit zeros if its not a full stike seq
          if ((elapsed>StrikeMaster[a])&&(elapsed<StrikeMaster[a+1])){
               if (count!=(a+1)){
                  intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
               }
             count=(a+1);
             for (byte a=0; a<6; a++){
                if (Wchannel[a]==1) ChannelValue[a]=intensity;
             }
          
          }  
          else {
            for (byte a=0; a<6; a++){
               if (Wchannel[a]==1) ChannelValue[a]=0;
            }
         }
       }  
    }
   
 

  //Now that we have generated our sun patter, 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
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
  /*SeasonsVar[0]=ChannelValue[0];
  SeasonsVar[1];
  SeasonsVar[2];
  SeasonsVar[3];
  SeasonsVar[4];
  SeasonsVar[5];
  SeasonsVar[6];
  SeasonsVar[7];
  SeasonsVar[8];
  SeasonsVar[9];
  SeasonsVar[10];
  SeasonsVar[11];
  Wire.write(SeasonsVar,12);
  */
}
//End Send Data
//*********************************************************************************************************************************
// 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()
{
  //static long TimeModulo;
    // Calculate time since day started correcting for DST
  if (isDST==true){
    elapsedTime=(((now()-946684800)+3600)-newDay);
  }
  else{
    elapsedTime=((now()-946684800)-newDay);
  }
  //if (elapsedTime%2==0) TimeModulo=elapsedTime;
  
if (elapsedTime%2==0){//only change lights every 2 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
  
  long 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*/


   for (byte b=0;b<12;b++){
      if ((b%2==0) && (elapsedTime>=ChRiseSet[b]) && (elapsedTime<midDay)){
       
        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)+((float)secSoFar*ChSlope[b])))*ChInt[b]+flicker[b];
             Serial.print("ch0 setting first 1/2 day=");
             Serial.println(ChannelValue[0]);
             break;
            case 2:
              ChannelValue[1]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-1]+flicker[b-1];   
              break;
            case 4:
              ChannelValue[2]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-2]+flicker[b-2];
              break;
            case 6:
              ChannelValue[3]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-3]+flicker[b-3];
              break;
            case 8:
              ChannelValue[4]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-4]+flicker[b-4];
              break;
            case 10:
              ChannelValue[5]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-5]+flicker[b-5];
              break;
          }   
      }
      else if ((b%2==1) && (elapsedTime<=ChRiseSet[b]) && (elapsedTime>=midDay)){
         secSoFar=(elapsedTime-ChRiseSet[b]);
           switch (b){
             case 1:
               ChannelValue[0]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-1]+flicker[b-1];
               Serial.print("ch0 setting second 1/2 day=");
               Serial.println((-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-1]+flicker[b-1],6);
               Serial.println((-cos(Pi+((float)secSoFar*ChSlope[b])))*ChInt[b-1]+flicker[b-1],6);
               break;
              case 3:
                ChannelValue[1]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-2]+flicker[b-2];    
                break;
              case 5:
                ChannelValue[2]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-3]+flicker[b-3];
                break;
              case 7:
                ChannelValue[3]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-4]+flicker[b-4]; 
                break;
              case 9:
                 ChannelValue[4]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-5]+flicker[b-5];
                 break;
              case 11:
                 ChannelValue[5]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-6]+flicker[b-6];
                 break;
           }
      }
       else if (((b%2==0) && (elapsedTime<ChRiseSet[b])) || ((b%2==1) && (elapsedTime>ChRiseSet[b]))){
          Cloud=false;//no lights = no Cloud
          Storm=false;
          Serial.println("were not in daylight");
          if ((b==0) || (b==1)) ChannelValue[0]=0;
          else if ((b==2) || (b==3)) ChannelValue[1]=0;     
          else if ((b==4) || (b==5)) ChannelValue[2]=0;
          else if ((b==6) || (b==7)) ChannelValue[3]=0; 
          else if ((b==8) || (b==9)) ChannelValue[4]=0;
          else if ((b==10) || (b==11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
      }  
   }
  /* Serial.println(ChannelValue[0]);
   Serial.println(ChannelValue[1]);
   Serial.println(ChannelValue[2]);
   Serial.println(ChannelValue[3]);
   Serial.println(ChannelValue[4]);
   Serial.println(ChannelValue[5]);*/
   Serial.print("Insolation elapsed time=");
   Serial.println(elapsedTime);
   WeDidThis=true;
 }
}//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 int  CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static int 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==true) && (Storm==false)){
     if ((elapsedTime%2)!=0){
       return;
     }
     // EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
        Serial.println("in a cloud");
          //now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times up
          //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){
               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(-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);
                }
         
              //Array to give direction to dimming.  e.g. DimOrder[]={0,0,1,1,0,0} would dim PWM channels 0-1,4-5 as FIRST dim value, then channels 2-3 would take on FIRST dime Value 
              //when channels 0-1,4-5 take on SECOND dim value, so the values chase eachother across the PWM output light array according to your set up.  I have 0-1 as RIGHT side lights, 
              //2-3 as LEFT side LED, and 4 as whole tank while I do not use 5
              
              byte DimOrder[]={0,0,1,1,0,0,};
              for (int a=0;a<6;a++){
                 if (DimOrder[a]==0){
                    ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);  
                  }
                  if (DimOrder[a]==1){
                       ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);  
                  }
             } 
   }
//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=false) && (Storm=true)) || ((Cloud=true) && (Storm=true))){
   //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. 
         StormStart=elapsedTime;
         StormEnd=((StormStart+StormDuration)+elapsedTime);
         wtrigger=false;
     }
     //Every 1 second duing a storm change intensity, clouds are movin fast baby
     if ((elapsedTime%1)!=0){
       return;
     }
          if (elapsedTime>=CloudEnd){ //if we randomly had a storm go to cloud end (which is possible but will be rare)
             ClearSky(CloudCover, CloudEnd);
             return;
           }
           else if (elapsedTime>=StormEnd){ //if were done with the storm but still cloudy
             Storm=false;
             return;
         // step through intensity values
         randomSeed(millis());
         byte 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(now()-newDay); //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;
                 }
                 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;
                 } 
               }
             StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
           }
         PriorCloudCover=CloudCover;
         CloudCover = CloudCover + stepsize;
       
           if (CloudCover>=100){//conflicted here, since were dimming by fractional intenstiy its likely we will hit flicker point or lower... might change  how this works
              CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
           }
           else if (CloudCover<=0){
             CloudCover-=(stepsize*1.5);//if were clear sky path reflect random path to cloud
           }
//*****************IF You want the light to "chase" across the tank****************************
//you need to specify the left or right side (or front back whatever, depends on how your lights are set up)
//Use either 0 or 1 to specify groups of channels that dim together (arbitrary but if you need to know direction the cloud will travel you will need to read the next bit of code)
//assign the same 0 or 1 value to all left side (or front back or whatever) channels and the other value to all right side channels
         byte DimOrder[]={0,0,1,1,0,0,};
//****************** ok were done changing things*****************************

         for (int a=0;a<6;a++) {
             if (Wchannel[a]==1){
                 ChannelValue[a]=0;
                 continue;
             }
             if (DimOrder[a]==0){
                  ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);  
             }
             else if (DimOrder[a]==1){
                   ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);  
             }
         }
       }  
     }//end of storm if loop

// Place this loop last as it will only run between clouds and trigger next cloud start and end, this way its not computing during a cloud.
  else {
      for (byte a=0; a<=(CloudsTotal-1); a=(a+2)){
         if ((elapsedTime>=*(pCloudPoint+a)) && (elapsedTime<=*(pCloudPoint+(a+1)))){
             CloudEnd=*(pCloudPoint+(a+1));
             CloudEnd=CloudEnd-120;
             Cloud=true;
          }
          else{
             Cloud=false;
             CloudCover=0;
             PriorCloudCover=0; 
          }
       }
   }
}//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 remaining=(CloudEnd-elapsedTime);
    
    int Range=(CloudCover/100);
    int slope=(Range/120);
   
    if (remaining<=0){
      Storm=false;
      Cloud=false;
      return;
    }
    for (byte a=0; a<6; a++){
         ChannelValue[a]=(ChannelValue[a]*((120-remaining)*slope));
    }
}//End Clear Sky function

Re: C library for Sun/Moon effects

Posted: Tue Apr 17, 2012 10:00 pm
by rimai
You need to use millis()
Declare a global variable above setup()

Code: Select all

unsigned long lastmillis=0;
Then on loop()

Code: Select all

if ( (millis()-lastmillis> 2000 ) 
{
  //Do whatever
  lastmillis=millis();
}