C library for Sun/Moon effects

Do you have a question on how to do something.
Ask in here.
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

//***********************************ARRAYS YOU MUST MODIFY TO MAKE YOUR TANK SET UP WORK*********************
//CHANGE this to the number of seconds your time zone is offset from GMT (WITHOUT REGARD TO DAYLIGHT SAVINGS TIME)
int GMToffset=-19800;//USA MST time zone is 25,200 if that helps you
//GLOBAL Variables YOU NEED TO CHANGE
boolean ApplyDST=false;//CHANGE THIS IF YOUR NOT USING DST

//Calculate Latitude and Longitude converting from Degree, Minute, Seconds to decimal
latitude=dmsToSeconds(26,46,0); //Set to kanpur, or GMT+5.30 zone,India
longitude=dmsToSeconds(80,33,0);//Set to kanpur, or GMT+5.30 zone,India

int Choffset[]={
-600,0,-4200,5400,0,600,-3600,6000,-4500,7200,0,0,0,0,0,0};

I Live in India, State is Uttar Pradesh, City is Kanpur.
Image
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Abhi-

Looks right so your lights should be on at rise and off at set of sun but you have a 1 hour 30 minute early rise for ch 4 and a 2 hour late set for ch 4. So your first light would be ch 4 about 1.5 hours before official sunrise (not light in sky but son on horizon) and then the tank should go dark a few hours after the sun has left the horizon.

Is this now happening? You last post was encouraging, i.e. 100% at noon and you were saying still 90% some 1 hours later... this is about what the cosine function should do... let me know if its now working.

You also should have the daylight savings time set to false which I believe is true in your case based upon past posts.
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

JNieuwenhuizen
You GMT offset should be -7200 according to my google searches... i.e. your +2 from GMT. Your lat lon look like south africa to me :)

Your offsets are normal and you would get first light 15 minutes after sunrise in channel 0 and then 30 minutes after sunrise in ch 1 then 45 minute after sunrise in ch 2 and finally 1 hour late in ch3. Set is 1 hour early for ch 0, 45 minutes early, 30 minutes and 10 minutes early for ch 1-3. So your tank should mostly rise late and set early in phases.

I just looked back at your earlier post and you had 0 offset and if I read it right... sunset at like 1 pm local... which would indicate that its actually ahead by some large number of hours... Your channel offset is going to set some channels (like ch 3 I think) will actually set an hour early. Remember- with respect to the ChOffset array negative (-) values in the morning rise early negative (-) values in the evening set early.... Unsure if this is what you wanted... its your set up. Just a reminder.


The actual code for setting rise and set times (I am posting only the two relevant lines)

ChRiseSet=rise+(Choffset);// for rise time of individual channels- rise is calculated sunrise time
ChRiseSet=set+(Choffset);// for set time of individual channels - set is calculated sunset time

I am actually confused with your behavior. Please set your GMT offset to -7200. Set your ChOffset array to all zeros and let it run though to a sunset or rise and tell me how far off reality it was. Also .. please use this next bit of code (see next post), just copy and paste your relevant settings (most but for ChOffset are al grouped together, its acceptable to just copy and paste the entire group of arrays you have customized for your tank.
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok-

So this is the current complete code that I am running. It has a few changes from even the most recent post that I made of just the weather segment and clear sky.

Although there are no changes to the sunrise and sunset calculation, there are significant changes to cloud allocation in terms of time start and end pairs. Its a lot more random now.

Storms are limited to 600 sec (MAX) by a new function called
byte StormStart()

its fairly easy to understand and editable but I would ask questions if your unsure because you can do bad things, the function returns a value that is important for determining the severity of a storm (i.e. blue lights only and very frequent lightning, or very dim white lights that ramp (but are constrained to flicker point-->20% intensity setting) with the blue and less frequent lightning).

Clouds now start and finish with about 20 seconds of time for the light in the tank to ramp down to 50% of its initial intensity (cloud start) and ramp up from its final intensity (whatever it is, its random) to whatever the Insolation set point for that moment is (cloud end). I also changed the loop for cloud length and start time calculation and randomization. Without going into great detail.... I used to do this in pairs... so if cloud 1 was long cloud 2 was short but the addition of the two was the same across every pair of clouds for a day (if odd the last one was unpaired). The problem with this, I realized after playing with it a lot, is that your kinda hard coding in a time interval where there will be parts of the day that will be sunny, period.. So to fix this. I allow the first cloud of the day to be randomly between 0.2 - 1.8 the length of an average cloud (i.e. 10 clouds on a 50% overcast 50,000 second day will average to (50% of 50,000=25,000 seconds----- 25,000/10 =2500 seconds each)... so the first cloud could be 0.2*2500 or as much as 1.8*2500 seconds long. Then the second and third clouds are paired, so if the second is 0.2 long the third is 1.8 long (adding to 2 for every pair) and so on... if there are an even number of clouds, the last one is paired with the first. By uncoupling the first cloud from the second, even though the rest pair into segments =2*avg length, because the first interval is 0.2-1.8 the start and end times of the clouds on different days will be really different due to the spacing effect of the first. They otherwise end up kind close to the same.... if the day had the exact same # of clouds and same % overcast.. so its not common... but was bugging me---- now its not- and it appears to work robustly. I serial debugged and ran it through about 20 iterations.. nice and random. Its now almost irritating, I have to keep rebooting to hit a cloud because its random :)

I also played further with the storm appearance rate. Its hard to get this really random... but it should take it about 5-10 minutes of cloud before a storm can even appear (the statistics of accumulation of a marker I track that is related to cloud intensity). Then each cloud now is randomly allocated a MAX # of storms that can occur, from 0-2. So some clouds will never get a storm.. period. Some can have 2- only if they are long enough and the randomness works out to trigger two.. they are not scheduled but rather allowed or disallowed. Storms are now of two types. Severe (more frequent in the second half of the day) and "normal". A Severe storm shuts off the WChannel array positions designated as 1, has the possibility for more intense lightning strikes that chain together more frequently. A "normal" storm now turns down the WChannel array positions with 1 to 20% of max intensity set point, so the tank gets kinda dark, the remaining WChannel positions with 0 are ramped between 0-100% of their Insolation set point- the WChannel position 1 lights are still ramped like the position 0 arrays, but are constrained to be between the flicker point and 20% of their current Insolation set point.


I think that sums it up... For People still having issues with light on/off times. Please load this .INO cant just copy and paste your WChannel, DimOrder, IsDST, GMToffset etc etc setups over the ones here. Then, please set the ChOffset array to ALL zeros. and the GMToffset to the value (negative if your time zone is GMT +x hours) in seconds of the difference between your clock and a clock running on Grenwich Mean Time. Then please post your actual local rise and set times. Compared with the true sunrise and sunset times for your locality. Remember that you need to use the appropriate signs (- or positive (you don't need a + to indicate positive)) on your latitude and longitude settings to reflect accurately your position with respect to the equator and meridian (North/South and East/West). Instead of trying to explain this... if your unsure (and I still cannot remember without looking it up) just LOOK IT UP- or Google map your house and click on the whats here (left click I think) box and it will post the Latitude and Longitude values with appropriate sign.

Here is the code. At this point, I am pretty pleased and its running I think without error. I have watched the arrays and storms and seems good. But... I am not waiting weeks to test run this, so something still may come up but its getting less and less likely. If we can figure out the time issue I am going to turn to Serial comm. If time is a problem. I need to figure that out. So.. please, load, configure and try. Its really beautiful with the new update timing and the smoothed intensity variation I implemented for the cloud and storm effects, MUCH better in my mind.

Code is NEXT post stand alone for NO confusion.
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

So much for that... my code is now longer than the text limit for a single post. We should probably move to email in the future to avoid clogging up the server here.. but I split it. Just copy this part and then the next post together. If I knew the light timing issue was solved I would say this is pretty stable code... so if you have been waiting to try it. Now is a good time to jump in. Copy paste and learn from what we have been discussing for a few days and get all your values set up correctly and lets see what happens.

This is the code up to the end of the loop.. just append the next post code to the end of this in your new .INO file

Code: Select all

//By Matthew Hockin 2012.  
//SWFLTEK library functions, #includes, and #define were copied directly from the Epherma library
//Epherma (SWFLTEK.com) was written by Michael Rice- thanks Michael it works great. 
//If you copy from this (its all open source) please 
//acknowledge Michael for SWFLTEK library code (obviously labeled) or Matthew Hockin (for the rest).

//You MUST have installed the SWFLTEK Ephmerma library- this library was written by Michael Rice (swfltek.com) 
//its source code and distribution rights are stated in the .h file -  Thanks to Michael for doing such a great job 
#include <Time.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <avr/wdt.h>
//includes for SWFLTEK functions
#include <stdlib.h>
#include <math.h>


//***********************************ARRAYS YOU MUST MODIFY TO MAKE YOUR TANK SET UP WORK*********************
//CHANGE this to the number of seconds your time zone is offset from GMT (WITHOUT REGARD TO DAYLIGHT SAVINGS TIME)
int GMToffset=25200;//USA MST time zone is 25,200 if that helps you
//GLOBAL Variables YOU NEED TO CHANGE
boolean ApplyDST=true;//CHANGE THIS IF YOUR NOT USING DST
byte ChMax[]={200,220,200,220,225,0,0,0};//Incremental value (Max-flicker) above flicker you want as max intensity- 
byte flicker[]={31,30,31,30,28,0,0,0};//need to input actual values here for flicker point on all channels in PWM expansion box
boolean Wchannel[]={1,0,1,0,0,0,0,0}; //use 1 to designate white channel (i.e. off during storm and used for lightning).  Array corresponds to PWM channel 0-5 in order
//Array to give direction to dimming.  e.g. DimOrder[]={0,0,1,1,0,0} (cloud chase effects, just group channels you want to dim together during a cloud or storm 
//as either a 0 or a 1, i.e. all left side channels are 0 all right are 1 or all front are 0 all back are 1 or whatever
//(which is zero or 1 will change who dims first).  set them all to 0 if your tank has no left/right or front/back lights.
byte DimOrder[]={0,0,1,1,1,0,0,0};
//**********************************DONE CHANGING THINGS HERE BUT YOU MUST CHANGE ChOffset array IN CalcSUN function******


//defines for SWFLTEK functions
// arc-seconds per radian
#define _sec_rad 206264.806247096370813

// axial tilt of earth at epoch, in radians
#define _tilt 0.409092804222329

// tropical year in seconds... rounding error accumulates to 26 seconds by the year 2136
#define _tropical_year 31556925

// 'zenith' of rising (setting) sun in radians (360 - 2 * 90.833 degrees)
#define _zenith 3.11250383272322

//*******************GLOBAL VARIABLE DECLERATIONS FOR MHOCKIN Weather package*************************************
//Unless your planning on editing the program DO NOT CHANGE ANYTHING HERE
long latitude, longitude;
byte TrueIntensity[8];//array used to place hold values calculated by insolation
long elapsedTime;//used multiple places as elapsed since new day.
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long rise;//time in seconds from midnight for sunrise today
unsigned long set;//time in seconds from midnithgt for sunset today
unsigned long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long ChRiseSet[16];//times of rise and set for all 8 channels based upon offsets from calc rise and set values
float ChSlope[16];//slopes for 1/2 day calculations based upon time from offset to midday for channel 1-8
long CloudMaster[20];// Set up array to hold start and end times for clouds for the day- dont size it dynamically make it big enough for any day
long midDay;// exactly 1/2 way between rise and set, i.e. my take on solar noon- which its not... 
byte PWMports[] ={
    3,5,6,9,10,11};
byte ChannelValue[8];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)
unsigned long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[20];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator (8 strikes=16 positions)
byte StrikeNumber;//place to hold total number of strikes this sequence
boolean StrikeNow;//starts lightning strike sequence in loop state change made in weather/storm loop
byte StrikeCount;//Used to properly sequence strike sequence for delay between strikes
byte cmdnum=255;
byte datanum=255;
byte dow=0;//day of week
boolean trigger; //used a few places as a switch to ensure things only run 1x when needed trigger 2 is for daily reset of sunrise sunset
byte strikePattern, strikeTime;//used in Lightning() for timing of particular instance of strike 
boolean isDST;//are we in DST 
boolean Cloud;// are we in a cloud interval on days that have clouds
boolean CloudToday;//set in CalcSun if randomization yields a day with clouds.
boolean IsStorm;// are we in a storm
byte CloudsTotal;// how many clouds today
long lastmillis;// variable to track millis to enable cloud and insolation loop restriction by time
boolean StormAdvance;//storm timer for light effect advance
boolean InsolationAdvance;//when true we recalculate light intensity during clear sky
byte counter;//used to track millis advance for insolation,cloud trigger
//****************************************
//END HEADER/Global Variable declaration//
//****************************************

//Setup
void setup(){
    Serial.begin(57600);
    Wire.begin(8);
    //Wire.onReceive(receiveEvent);
    //Wire.onRequest(requestEvent);

    pinMode(3,OUTPUT);
    pinMode(5,OUTPUT);
    pinMode(6,OUTPUT);
    pinMode(9,OUTPUT);
    pinMode(10,OUTPUT);
    pinMode(11,OUTPUT);
    wdt_enable(WDTO_1S);
    unsigned long seed=0, count=32;
    while (--count){
      seed = (seed<<1) | (analogRead(5)&1);
    }
      randomSeed(seed);//start random generator at a different point each time (not perfect but whatever its gonna be pretty damn random)
    setSyncProvider(RTC.get);   // the function to get the time from the RTC
    setSyncInterval(SECS_PER_HOUR);  // Changed to sync every hour.
    
    isDST=false;//set day light savings time correction to false, is overridden by DST function if you choose to use it 
    dow=0;//set Day Of Week (dow) to a 0 value which is impossible (day()=1-7)... so we trigger calcSun on restart 
    StrikeNow=false;//no lightning strike yet
    CloudToday=false;//set to no clouds so CalcSun can set correctly if should be true
    Cloud=false;//set cloud to false
    IsStorm=false;//set storm to false
    lastmillis=millis();//start our millis timer now
    counter=0;//used in weather for triggering a storm, triggering lightning in a storm.
    StrikeCount=0;//Number of lightning strikes in the sequence.. set to zero until initialized in sequence
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop(){ 
    wdt_reset();
    if (cmdnum!=255){
        ProcessCMD(cmdnum,datanum);    
        cmdnum=255;
        datanum=255;
    }
    
    if (dow!=day()){ //be aware that during DST times the new day calculation will occur 1 hour early....but its corrected out
      trigger=true;//on reset this will also be true as numbering is 1-31 max
      //Serial.println("We set trigger to true");
      dow=day();//now set dow to actual day numbering so that at midnight/new day we again calc sunrise sunset
    }
  
    //Use millis to enable tracking of time interval
    //this will ensure at least minimally 1 sec intervals but may be longer if program is slow during a long calculation but at least never shorter.
    if ((millis()-lastmillis)>=100){
        lastmillis=millis();
        counter+=1;
        StormAdvance=true;
        //now a bunch of stuff that may or may not be true at the same time but that all needs to happen when its true
        if (counter==0){
          InsolationAdvance=true;//so that it runs on start up to provide light immediately 
        }  
        if (counter%30==0){
          InsolationAdvance=true;
          /*Serial.print("elapsed time in seconds from midnight is =");
          Serial.println(elapsedTime);
          Serial.print("cloud=");
          Serial.println(Cloud);
          Serial.print("IsStorm=");
          Serial.println(IsStorm);*/
        }
        if (counter==210) counter=0; 
    }     
    
    // now run through rise/set calculation and then intensity set and finally weather overlay when required

   if (trigger==true) CalSun();
   if (InsolationAdvance==true) Insolation();
   Weather();//due to variable storm vs cloud update timing this must be run from loop without condition
  //check to see if were need to have a lightning strike
    
    if (StrikeNow==true){
       if ((millis()-StrikeStart)>=StrikeMaster[(StrikeCount*2)]){//check if time has passed the delay (position 0,2,4,6,8 etc in StrikeMaster)-StrikeCount is indexed up by 1 after each strike so we see positions 0,2,4,6,etc in sequence
          byte intensity;
          intensity=random(175,256);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
              for (byte b=0; b<6; b++){
                  if (Wchannel[b]==1) analogWrite(PWMports[b],intensity);// set all whites to whatever random intensity was generated
              }
          delay(StrikeMaster[((StrikeCount*2)+1)]);//index to +1 position in array from 0,2,4, etc to 1,3,5 etc
          StrikeCount++;//so that the next time we look at elapsed time were looking at the right array position
          if (StrikeCount==(StrikeNumber-1)){
            StrikeNow=false;
            StrikeCount=0;
          }
       }
    }

    //track time in seconds-dont move this part up in the loop it really should stay below the rest as DST is not calculated until CalSun is run 1 time.
    elapsedTime=((now()-946684800)-newDay);//this is uncorrected for DST, the ONLY thing that is, since its all relative... is the actual sunrise and set seconds
    //Now that we have generated our sun pattern, Weather as clouds, and possibly a storm,  and possibly lightning each of which override the prior channel setting
    //lets actually make some light.
    for (byte a=0;a<6;a++){
      analogWrite(PWMports[a],TrueIntensity[a]);//dont change this to 8 to refelct array for channels.. we only have 6 here!
    }
}
//End Loop
//*********************************************************************************************************************************
Last edited by rufessor on Sun Jun 10, 2012 9:17 pm, edited 1 time in total.
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Code: Select all

//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
void receiveEvent(int howMany) {
    wdt_reset();
    if (howMany==5){
        byte cmd1, cmd2, cmd3, cmd4, cmd5;
        cmd1=Wire.read();
        cmd2=Wire.read();
        cmd3=Wire.read();
        cmd4=Wire.read();
        cmd5=Wire.read();
        if (cmd1=='$' && cmd2=='$' && cmd3=='$'){
            cmdnum=cmd4;
            datanum=cmd5;
            //Serial.println(cmd4,DEC);
            //Serial.println(cmd5,DEC);
        }
    }
    else{
        for (int a=0;a<howMany;a++){
            Wire.read();
        }
    }  
}

void ProcessCMD(byte cmd, byte data){
    wdt_reset(); 
}

//End Standard Functions
//*********************************************************************************************************************************

void CalSun(){
   //Serial.println("CalSun Run Now");
   trigger=false;
    // Every day at midnight, we calculate rise, set, time of highest sun, moon phase, or we do this if system has reset
        if (ApplyDST==true){
            CalcDST(day(),month(),weekday());
        }
        
        //Calculate Latitude and Longitude converting from Degree, Minute, Seconds to decimal
        latitude=dmsToSeconds(40,44,32); //Set to about the latitude of Salt Lake City
        longitude=dmsToSeconds(-111,48,11); //Set to Salt lake City, or MST zone, USA or there abouts- actually the AT921 remote weather station in Salt Lake City UT.  Random web grab.
        
       unsigned long SecInput;//intermediate variable to set rise and set input times
       unsigned long hours;//avoid casting and use UL
       unsigned long minutes;//avoid casting and use UL
          time_t t=now();
          hours=hour(t);
          minutes=minute(t);
        
        //Serial.println("Hours:Mins");
        //Serial.println(hours);
        //Serial.println(minutes);
        //Serial.print("CalSun now()=");
        //Serial.println(now());
        
        //Using time library from Arduino.time_t now(); returns seconds since 1970 in local time with NO DST correction
        //Arduino Time.h Library states #seconds to subtract to bring to Jan 1 2000 as origin for time is 
        //#define SECS_YR_2000  (946684800) the time at the start of y2k
        
         //Seems weird, but actually DO NOT correct new Day for DST 
        hours=(hours*3600);//seconds conversion saving a variable
        minutes=(minutes*60);//seconds conversion saving a variable
        newDay=(now()-(946684800+hours+minutes));//local time uncorrected for DST- hours and minutes were just converted to seconds in prior step
        SecInput=(newDay+GMToffset);
        rise=SecInput;// Dont screw with DST in sunrise sunset input its NOT part of GMT  
        set=SecInput;//were not converted yet- library uses POINTERS.. to modify these values  
        
        //Calculate rise time and set time using Epherma Library   
        SunRise(&rise);//call to Epherma Library using reference- memory is modified by Epherma
        SunSet(&set);//Call to Epherma library using reference- memory position is modified by Epherma
        
        if (isDST==true){//must correct DST NOW by subtracting 1 hour from the offset (i.e. clocks are 1 hr ahead and thus closer to GMT)
          rise=(rise-(GMToffset-3600));//during DST rise is 1 hour earlier than calculated i.e. clocks ahead 1 hour 6 am occurs "at 5am" if that makes sense
          set=(set-(GMToffset-3600)); 
        }
        else if (isDST==false){
          rise=(rise-GMToffset);
          set=(set-GMToffset);
        }
        
        //Serial.print("newDay=");
        //Serial.println(newDay);
        
        rise=(rise-newDay);//set to elapsed seconds today
        set=(set-newDay);
       /*Serial.println("Local Elapsed Seconds from NewDay--rise=");
        Serial.println(rise);
        Serial.println("Local Elapsed Seconds from NewDay--set=");
        Serial.println(set);*/
        
        //DONT MOVE THIS LINE ABOVE FINAL rise and set decleration it will SCREW up your rise and set during DST by 1 hour
        if (isDST==true) newDay-=3600;//subtract an hour from the time that will be  subtracted from GMT i.e. were one hour closer during DST
        
        
        
        //*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
        //channels 0-5 are PWM expansion board lights 6,7 are ReefAngel Controller PWM outputs
        //offsets for rise/set all values in seconds offset from calculated rise or set value (-) am offset=longer day****** (-)pm offset=shorter day)
        //array order is ch0 am offset, pm offset, ch1 am offset, pm offset etc..
        //THESE values are the number of seconds that a particular channel will be offset from the rise/set time, i.e. negative to rise earlier/set earlier
        int Choffset[]={
            -600,0,-4200,5400,0,600,-3600,6000,-4500,7200,0,0,0,0,0,0};
        //**********************ok now were done changing things here********************************************   
        
        
        
        //Calculate rise and set times for all channels in equivlants to elapsed seconds from midnight today
        //populate array for chRise and Set as well as chSlope for 0.5pi/ half day lenght for each channel from midday (asymmetric days are allowed)
        float deltaY=1.570796327;//1/2 * pi as integer by scaling* 10^9 to fill UL
        midDay=(((set-rise)/2)+rise);
        long HalfDayLength=((set-rise)/2);
        //Serial.print("MidDay");
        //Serial.println(midDay);
        
        for (byte b=0;b<16;b++){//working as of April 5 2012 serial tested
            if (b%2==0){
                ChRiseSet[b]=rise+(Choffset[b]);
                ChSlope[b]=(deltaY/(float)(HalfDayLength-(Choffset[b])));
                //Serial.print("Rise/Set a=");
                //Serial.println(b);
                //Serial.print("Value=");
                //Serial.print(ChRiseSet[b]);
                //Serial.print("Slope");
                //Serial.println(ChSlope[b], 12);
            }
            else if (b%2==1){
                ChRiseSet[b]=set+(Choffset[b]);
                ChSlope[b]=(deltaY/(float)(HalfDayLength+(Choffset[b])));
                //Serial.print("Rise/Set a=");
                //Serial.println(b);
                //Serial.print("Value=");          
                //Serial.print(ChRiseSet[b]);
                //Serial.print("Slope");
                //Serial.println(ChSlope[b], 12);
            }
        }  
        
        //***************** to CHANGE THE chance of Clouds actually occuring on a given day************************
        byte CloudChance=100;//% Chance of a Cloud every day
        //****************************now were done- did you use a value from 0-100 without a decimal?****************
        
        //once a day, after rise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
        byte RainMaker=random(1,101); 
        if (RainMaker<=CloudChance){
            CloudToday=true;//used to trigger weather function, can also be used to send flag to controller
        }
        else if (RainMaker>CloudChance){
            CloudToday=false;//see above comment on CloudToday
            //Serial.print("no cloud today");
            return;
        }
        // to "simplify" things and avoid redundant calculation loops all cloud times are in fraction of day lengths 
        
        //*********** this is only executed 1x /day unless power outage and ONLY if last statement is true****************
        ///ALl cloud set up is done here... weather subroutine just implements it
        /*The general strategy for this algorithim is as follows.  Calculate random cloud cover as percent of day,
         then randomly generate the # of discreet cloud instances for the day,
         then determine cloud length by (daylight seconds * percent overcast)/#clouds
         then randomize cloud lengths by splitting into groups of twos correcting for if we have odd # of clouds today*/
        
        long dayLength=(set-rise);
        //Serial.println("DayLength");
        //Serial.println(dayLength);
        
        // number of clouds possible for the day, max and min
        byte CloudsMax=10;//DONT INCREASE BEYOND 10 or it will DIE, or increase array size to handle it
        byte CloudsMin=4;//dont use 1 it may or may not work in the next loops and the cloud will likely be very long, i.e. daylength*overcast fraction
        CloudsTotal=random(CloudsMin,(CloudsMax+1));
        //Serial.print("CloudsTotal=");
        //Serial.println(CloudsTotal);
        
        
        // Average day is 50,000 secs so if 4 clouds and 10% that gets you 5,000 seconds of clouds (about 1800 seconds length for each of the 4 clouds in independent segments (if 4 is # clouds)
        byte OvercastMin=((CloudsTotal*10)/5);//Min cloud length will be about 1000 seconds (15 mins)- 1 hour min of clouds if you have 4, 2 hours if you have 8
        byte OvercastMax=((CloudsTotal*10)/2);//max cloud length will be about 2500 seconds (45 mins)- 6 hours max of clouds if you have 8, 3 hours max if you have 4
        float Overcast=random(OvercastMin,OvercastMax);
        Overcast=(Overcast/100);
        //Serial.println("Overcast");
        //Serial.println(Overcast);
 
        // set up start time for clouds
        //The way this is done is to split the total lenght of time for clouds into equal segments and then to randomly chop or add time to the 
        //segments such that cloud length is variable.  Then distribute into random parts of the day and fill array with start,duration pairs for clouds
        int CloudLength;
        CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
        long SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));//average sun length between clouds
        float CloudFraction=0;
        float SunFraction=0;
        /*Serial.print("CloudLength=");
        Serial.println(CloudLength);
        Serial.print("SunSegment=");
        Serial.println(SunSegment);*/
        
        //start by zero filling CloudMaster array
        for (byte a=0; a<20; a++){
          CloudMaster[a]=0;
        }
        byte b=0;//used to get pairs of fraction for SunFraction in for loop
        byte c=0;//used to get pairs of fraction for CloudFraction in for loop
        //now randomize cloud length and sunsegment length as pairs to get different looking days- 
        
        for (byte a=0; a<(CloudsTotal*2); a++){
          if (a%2==0){
            if (b==0){
              if (a==0){
                SunFraction=random(20,181);//vary each pair of SunSegments from 20%-180% of possible length such that every pair =2*SunSegment in length
                SunFraction=(SunFraction/100);
                CloudMaster[a]=(SunFraction*SunSegment);
              }
              else if (a<((CloudsTotal*2)-2)){
                SunFraction=random(20,181);//vary each pair of SunSegments from 20%-180% of possible length such that every pair =2*SunSegment in length
                SunFraction=(SunFraction/100);
                CloudMaster[a]=(SunFraction*SunSegment);
                b++;
              }
              else if (a==((CloudsTotal*2)-2)){
                SunFraction=(2-((float)CloudMaster[0]/(float)SunSegment));
                //Serial.print("Last SunSegment Fraction=");
                //Serial.println(SunFraction);
                CloudMaster[a]=(SunFraction*SunSegment);
              }
                
            }
            else if (b==1){
              if (a<((CloudsTotal*2)-2)){
                SunFraction=(2-SunFraction);//were on the second part of a pair
                CloudMaster[a]=(SunFraction*SunSegment);
                b=0;//reset so next time we start a new fraction
              }
              else if (a==((CloudsTotal*2)-2)){
                SunFraction=(2-((float)CloudMaster[0]/(float)SunSegment));
                //Serial.print("Last SunSegment Fraction=");
                //Serial.println(SunFraction);
                CloudMaster[a]=(SunFraction*SunSegment); 
              }
            }
          }
          else if (a%2==1){//if were in odd positions we need to determine cloud lengths in random pairs such that each pair =2*CloudLength in length
            if (c==0){
              CloudFraction=random(20,181);//vary each pair of SunSegments from 20%-180% of possible length such that every pair =2*SunSegment in length
              CloudFraction=(CloudFraction/100);
              CloudMaster[a]=(CloudFraction*CloudLength);
              c++;       
            }
            else if (c==1){
              CloudFraction=(2-CloudFraction);
              CloudMaster[a]=(CloudFraction*CloudLength);
              c=0;//reset so next loop finds a new fraction
            }
         }
      }
      //serial debugging to examine array
        /*Serial.println("here is cloud master in is entirety prior to forming start and end pairs");
        for (byte a=0;a<20;a++){
          Serial.println(CloudMaster[a]);
        }*/
      
         // now we have CloudMaster constructed as SunSegment,CloudLength,SunSegment,CloudLength.... 
         //reframe it to generate cloud start, cloud end, cloud start, cloud end
      for (byte a=0; a<(CloudsTotal*2); a++){
        if (a==0){// if were starting our first cloud we need to add to rise value to first sun segment
          CloudMaster[a]=rise+CloudMaster[a];
        }
        else {
          CloudMaster[a]=(CloudMaster[a-1]+CloudMaster[a]);//just add prior values together e.g. (second position is cloud end so to find end add rise corrected start time with duration)
                                                           // subsequent start would be end of 1st cloud + next sunsegment fraction
        }
      }
        //serial debugging to examine array
       /* Serial.println("here is cloud master in is entirety as start and end pairs");
        for (byte a=0;a<20;a++){
          if (a%2==0){
            Serial.print("Start time=");
            Serial.println(CloudMaster[a]);
          }
          else {
            Serial.print("End time=");
            Serial.println(CloudMaster[a]);
          }
        }*/
}//END SunCalc FUNCTION

void Insolation()
{
  InsolationAdvance=false;//reset this flag now that we have entered function
        /*Serial.println("Insolation 3 sec elapsed");
        Serial.print("Elapsed Time is=");
        Serial.println(elapsedTime);*/
        
        //define Pi as delta Y for slope since cos 0.5-1.5 Pi goes 0-1-0 in 0.5 pI increments slope of 1/2 day (0-1 intensity) delta Y is 1/2 Pi 
        float Pi=3.1415926;//scale to 10^8
        float PiHalf=1.5707963;//scale to 10^8
        
        float secSoFar;//variable to account for seconds elapsed for each channel 1/2 day period from rise-->midDay and midDay-->set
        
        /* using -cos(pi/2+elapsedTime/slope) calculate fractional intensity of each channel throughout the day
         use flicker points to adjust minimum intensity to stable light.  Turn off lights after set or before rise etc.
         by splitting into half days centered on midday (1/2 ofset-rise) we center exactly the cos function for every channel so color blends are maintained 
         throughout intensity ramp... more or less ... change intensity every 120 seconds throughout the day*/
        
        if (elapsedTime<=midDay){
          //Serial.println("in first half of day");
            for (byte b=0;b<16;b=(b+2)){
                if (elapsedTime>=ChRiseSet[b]){
                    secSoFar=(elapsedTime-ChRiseSet[b]);//just account for length of every channel 1/2 day and switch at midDay
                    switch (b){
                        case 0:
                            ChannelValue[0]=flicker[b]+ChMax[b]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println((float)ChMax[b],4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[0]);
                            //Serial.println((float)ChMax[b],4);
                            break;
                        case 2:
                            ChannelValue[1]=flicker[(b-1)]+ChMax[(b-1)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));   
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[1]);
                            break;
                        case 4:
                            ChannelValue[2]=flicker[(b-2)]+ChMax[(b-2)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[2]);
                            break;
                        case 6:
                            ChannelValue[3]=flicker[(b-3)]+ChMax[(b-3)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[3]);
                            break;
                        case 8:
                            ChannelValue[4]=flicker[(b-4)]+ChMax[(b-4)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[4]);
                            break;
                        case 10:
                            ChannelValue[5]=flicker[(b-5)]+ChMax[(b-5)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[5]);
                            break;
                       case 12:
                            ChannelValue[6]=flicker[(b-6)]+ChMax[(b-6)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[6]);
                            break;
                        case 14:
                            ChannelValue[7]=flicker[(b-7)]+ChMax[(b-7)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[7]);
                            break;
                    }   
                }
                else if (elapsedTime<ChRiseSet[b]){
                    switch (b){
                        case 0:
                            ChannelValue[0]=0;                //to enable moon phase lighting for any channel comment out this part and uncomment the next 2 lines
                            //ChannelValue[0]=MoonPhase();    //this is the after sunset part I told you to look for when you set the before sunrise moon phase channels
                            //if (ChannelValue[0]<flicker[0]) ChannelValue[0]=0;
                            break;
                        case 2:
                            ChannelValue[1]=0;
                            //ChannelValue[1]=MoonPhase();    
                            //if (ChannelValue[1]<flicker[1]) ChannelValue[1]=0;
                            break;
                        case 4:
                            ChannelValue[2]=0;
                            //ChannelValue[2]=MoonPhase();    
                            //if (ChannelValue[2]<flicker[2]) ChannelValue[2]=0;
                            break;
                        case 6:
                            ChannelValue[3]=0;
                            //ChannelValue[3]=MoonPhase();    
                            //if (ChannelValue[3]<flicker[3]) ChannelValue[3]=0;
                            break;
                        case 8:
                            //ChannelValue[4]=0;
                            ChannelValue[4]=(MoonPhase()/2.2);
                            if (ChannelValue[4]<flicker[4]) ChannelValue[4]=0;
                            break;
                        case 10:
                            ChannelValue[5]=0;
                            //ChannelValue[5]=MoonPhase();    
                            //if (ChannelValue[5]<flicker[5]) ChannelValue[5]=0;
                            break;
                       case 12:
                            ChannelValue[6]=0;
                            //ChannelValue[6]=MoonPhase();    
                            //if (ChannelValue[6]<flicker[6]) ChannelValue[6]=0;
                            break;
                        case 14:
                            ChannelValue[7]=0;
                            //ChannelValue[7]=MoonPhase();    
                            //if (ChannelValue[7]<flicker[7]) ChannelValue[7]=0;
                            break;
                    }
                } 
            }
        }  
        else if (elapsedTime>midDay){
          //Serial.println("were in the second half of the day");
            for (byte b=1;b<12;b=b+2){
                
                if (elapsedTime<=ChRiseSet[b]){
                    secSoFar=(elapsedTime-midDay);
                    switch (b){
                        case 1:
                            ChannelValue[0]=flicker[(b-1)]+ChMax[(b-1)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[0]);
                            break;
                        case 3:
                            ChannelValue[1]=flicker[(b-2)]+ChMax[(b-2)]*(-cos(Pi+(ChSlope[b]*secSoFar)));    
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[1]);
                            break;
                        case 5:
                            ChannelValue[2]=flicker[(b-3)]+ChMax[(b-3)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[2]);
                            break;
                        case 7:
                            ChannelValue[3]=flicker[(b-4)]+ChMax[(b-4)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                           //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4); 
                           //Serial.println(ChannelValue[3]);
                            break;
                        case 9:
                            ChannelValue[4]=flicker[(b-5)]+ChMax[(b-5)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[4]);
                            break;
                        case 11:
                            ChannelValue[5]=flicker[(b-6)]+ChMax[(b-6)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[5]);
                            break;
                       case 13:
                            ChannelValue[6]=flicker[(b-7)]+ChMax[(b-7)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[6]);
                            break;
                        case 15:
                            ChannelValue[7]=flicker[(b-8)]+ChMax[(b-8)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[7]);
                            break;
                    }
                }
                else if (elapsedTime>ChRiseSet[b]){
                    switch (b){
                        case 1:
                            ChannelValue[0]=0;
                            //ChannelValue[0]=MoonPhase();    
                            //if (ChannelValue[0]<flicker[0]) ChannelValue[0]=0;
                            break;
                        case 3:
                            ChannelValue[1]=0;
                            //ChannelValue[1]=MoonPhase();    
                            //if (ChannelValue[1]<flicker[1]) ChannelValue[1]=0;
                            break;
                        case 5:
                            ChannelValue[2]=0;
                            //ChannelValue[2]=MoonPhase();    
                            //if (ChannelValue[2]<flicker[2]) ChannelValue[2]=0;
                            break;
                        case 7:
                            ChannelValue[3]=0;
                            //ChannelValue[3]=MoonPhase();    
                            //if (ChannelValue[3]<flicker[3]) ChannelValue[3]=0;
                            break;
                        case 9:
                            //ChannelValue[4]=0;
                            ChannelValue[4]=(MoonPhase()/2.2);
                            if (ChannelValue[4]<flicker[4]) ChannelValue[4]=0;
                            break;
                        case 11:
                            ChannelValue[5]=0;
                            //ChannelValue[5]=MoonPhase();    
                            //if (ChannelValue[5]<flicker[5]) ChannelValue[5]=0;
                            break;
                       case 13:
                            ChannelValue[6]=0;
                            //ChannelValue[6]=MoonPhase();    
                            //if (ChannelValue[6]<flicker[6]) ChannelValue[6]=0;
                            break;
                        case 15:
                            ChannelValue[7]=0;
                            //ChannelValue[7]=MoonPhase();    
                            //if (ChannelValue[7]<flicker[7]) ChannelValue[7]=0;
                            break;
                    }
                }
            }
        }
        /*Serial.println("Insolation settings=");
        Serial.println(ChannelValue[0]);
        Serial.println(ChannelValue[1]);
        Serial.println(ChannelValue[2]);
        Serial.println(ChannelValue[3]);
        Serial.println(ChannelValue[4]);
        Serial.println(ChannelValue[5]);
        Serial.println(ChannelValue[6]);
        Serial.println(ChannelValue[7]);
        Serial.println("ChMax Settings");
        Serial.println(ChMax[0]);
        Serial.println(ChMax[1]);
        Serial.println(ChMax[2]);
        Serial.println(ChMax[3]);
        Serial.println(ChMax[4]);
        Serial.println(ChMax[5]);
        Serial.println(ChMax[6]);
        Serial.println(ChMax[7]);*/
        
}//END function


//This is where modification of insolation by clouds or a storm occurs
//Its worth noting that the clouds simply modify (depending on how much cloud cover there is) the existing light set by insolation as a fractional value
void Weather ()  
{
    static byte loopCount;
    static float CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
    static float PriorCloudCover;  //used to "delay" one side of the tank from the other in cloud passing effects
    static long StormStart;
    static long StormEnd;
    static long CloudEnd;
    static boolean wtrigger;//use to track the first run of functions to calculate random times that become fixed, see change from cloud to storm for useage
    static byte Counter;//used to trigger storms from cloud you can change its if loop comparison to decrease or increase storm rate see below in cloud==true if loop
    static byte Severity;
    static byte StormCount;// used to limit X storms per cloud and to choose which cloud can have a storm
    static int StepSize;
    static int LastStepSize;
    //check to see if were having a scheduled cloud
    if (Cloud==false){
      //Write Insolation values to TrueIntensity so the loop will pick them up and the cloud/storm will get the right data (since intensity changes during the day)
      for (byte a=0; a<8; a++){//this must be above the next loop
            TrueIntensity[a]=ChannelValue[a];//this is where intensity is set for the PWM channel analog write in the loop... don't mess with this.
        }   
      for (byte a=0; a<(CloudsTotal*2); a=(a+2)){//if its time for a cloud, run it
         if ((elapsedTime>=CloudMaster[a]) && (elapsedTime<=CloudMaster[(a+1)])) {
             CloudEnd=CloudMaster[(a+1)];//to avoid this loop running true during the compute cycles at the end of the cloud and before elapsedTime advances a second, actual cloud does not
             //Serial.print("We started a cloud and its end is=");
             //Serial.println(CloudEnd);
             CloudCover=CloudStart(CloudMaster[a]);//CloudStart modifies TrueIntensity to get us to 50% intensity at the start of the cloud effect and also sets cloud=true to bypass this
             Counter=0;
             StormCount=random(0,3);//the number of storms MAX that may occur in this cloud (remember Random is range= -1 on high end)
             loopCount=1;
             LastStepSize=0;//zero out cloud random walk variables
             StepSize=0;//zero out cloud ranodm walk variables
             return;//exit having started a cloud in CLoudStart routine called above
         }
       } 
     //just write ChannelVales to TrueIntensity without modification so that cloud/storm can later modify them
     
     }
         
    else if ((Cloud==true) && (IsStorm==false)){
        if (StormAdvance==false){//use millis tracker to run this loop every 2 seconds
            return;
        }
        StormAdvance=false;//reset to false when true so we run this once, until time advance is true again
        //Serial.println("in a cloud doing stuff");
        if (elapsedTime>=CloudEnd){
            ClearSky(CloudCover, CloudEnd); 
            return;
        }
        
        /*Use fractional intensity to set minimum value for any channel.  Dimming is proportional to actual intensity output 
         and constrained by flicker point.  Random walk uses static variable "CloudCover" constrained to 0-100 to represent fractional intensity (i.e. (1-(CloudCover/100))*Insolation SetPoint 
         is how the current cloud intensity is set, i.e. cloud cover of 90 gives 10% insolation setpoint unless below flicker in which case = flicker*/
        if (loopCount==1){
          PriorCloudCover=CloudCover; //e.g. PriorCloudCover=CloudCover with no float math to screw things up
          LastStepSize=StepSize;
          StepSize=(random(5,21));// in Percent% (0-100) This is how much light intensity will change over the loop count interval (this actual time is dependent upon the call frequency of StromAdvance as set in the loop)
          if ((random(0,2)!=1)) StepSize=-(StepSize);
          if ((CloudCover+StepSize)>=100){//cannot shut off lights more than 100% so limit here
            StepSize=(100-CloudCover);//
            Counter++;
            //Serial.print("Counter for cloud to storm transition just hit 100 cloud cover and went up by 1=");
            //Serial.println(Counter);  
          }
          else if ((CloudCover+StepSize)<=0){//cannot be brighter than 100% so since were in a cloud dont "limit" it but reflect it back down
            StepSize=-(StepSize);
            if (Counter>=50) Counter-=random(-1,2);//since we got bright... lets further delay and randomize storm occurence 
            //Serial.print("Counter for cloud to stormtransition just hit 0% cloud and it now is =");
            //Serial.println(Counter);  
          }
          CloudCover=CloudCover+StepSize;
        }
       // float math for light intensity setting is PriorCloudCover (dim order 1)=(PriorCLoudCover+(LastStepSize/4)*loopCount);//this way its going to change every 250 msec  
       //float math for light intensity setting is CloudCover (dim order 0)=(CloudCover+(StepSize/4)*loopCount);//Now add the % increment or decremement to intensity
        
        /*Serial.print("PriorCloudCover=");
          Serial.println(PriorCloudCover);
          Serial.print("Stepsize=");
          Serial.println(StepSize);
          Serial.print("CloudCover=");
          Serial.println(CloudCover);*/
        
        if ((Counter>=60) && ((CloudEnd-elapsedTime)>=300)) {//if Counter (indexes when cloud cover reaches 100) has accumulated and we still have time lets make a storm
        //to change the frequency of storms increase or decrease the number comparison for counter in the if statement above (larger #== less storms).
        //if you change counter comparison here change it in the next loop as well
           if (StormCount>=1){//if we can have storms in this cloud (random- statisticly 1/3 clouds = no storm, 1/3 = 1 possible storm, 1/3 = 2 possible storms)
             byte RandomStorm;
             RandomStorm=random(0,11);//this randomizes for longer clouds without storm, avg cloud is much longer prior to storm occuring- thus short clouds will not generally have a storm
               if (RandomStorm>=4){
                 StormCount-=1;//count down by 1 the number of storms in this cloud- this will not roll the byte since the loop requires it to be at least 1 to ever subtract here. 
                 Counter=0;//reset this variable since Storm loop uses it as well.
                 int LongestStorm;//used to pass max possible time to storm if loop from cloud loop within weather function
                 LongestStorm=(CloudEnd-elapsedTime);
                 Severity=StartStorm(LongestStorm, IsStorm, StormEnd);
                 loopCount=1;//reset counting loop for the storm
                 //Serial.print("we started a storm"); 
               }
               //else Serial.print("Random call was missed- not a storm");  
           }
        }
        else if ((Counter>=60) && ((CloudEnd-elapsedTime)<300)){
           Counter=0;//just reset the counter (does not really matter in this case but its clean)
        }
        
        for (int a=0;a<8;a++){
            if (DimOrder[a]==0){
                //Serial.print("Initial Channel setting is= ");
                //Serial.println(a);
                //Serial.println(FullSun);
                TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-((CloudCover-(StepSize-(StepSize/4)*loopCount))/100))));//max intensity is 65% of initial for better visual dimming performance
                //Serial.print("DimOrder 0 setting is"); 
                //Serial.println(TrueIntensity[a]);
            }
            else if (DimOrder[a]==1){
                //Serial.print("Initial Channel setting is= ");
                //Serial.println(a);
                //Serial.println(FullSun);
                TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/4)*loopCount))/100))));//max intensity is 65% of initial for better visual dimming performance
                //Serial.print("DimOrder 1 setting is"); 
                //Serial.println(TrueIntensity[a]);
            }     
         }
         loopCount++;
         if (loopCount>4) loopCount=1;
    }  
    //enable a flag sent from controller to triger a storm, i.e. IsStorm=true
    // set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
    else if (((Cloud==true) && (IsStorm==true)) || ((Cloud==false) && (IsStorm==true))){
        //current else statement covers possibility of triggering storm from controller (i.e. not coming out of a cloud) but remember you need to flag wtrigger as TRUE when you do this
        if (StormAdvance==false){//Every 1 second duing a storm change intensity, clouds are movin fast baby
           return;
        }
        
        StormAdvance=false;//reset so we run again in 1 second.
        //Serial.print("in a storm now");
        
       if (elapsedTime>=StormEnd){ //if were done with the storm we need to stop this loop, but were probably still cloudy so dont mess with that here
            IsStorm=false;
            Counter=0;
            return;
        }
        if (loopCount==1){
          PriorCloudCover=CloudCover; //e.g. PriorCloudCover=CloudCover with no float math to screw things up
          LastStepSize=StepSize;
          StepSize=(random(5,21));// in Percent% (0-100) This is how much light intensity will change over the loop count interval (this actual time is dependent upon the call frequency of StromAdvance as set in the loop)
          if ((random(0,2)!=1)) StepSize=-(StepSize);
          if ((CloudCover+StepSize)>=100){//cannot shut off lights more than 100% so limit here
            StepSize=(100-CloudCover);
            Counter++;
            if (Counter>(Severity+2)) Counter=0;//allow if to accumulate on ocassion to train strike sequences 2-3 in a row but then dump it 
          }
          else if ((CloudCover+StepSize)<=0){//cannot be brighter than 100% so since were in a cloud dont "limit" it but reflect it back down
            StepSize=-(StepSize);
            if (Counter>=2) Counter-=random(0,2);//since we got bright... lets further delay and randomize strike occurence    
          }
          CloudCover=CloudCover+StepSize;
        }
   
       // float math to get intensity changes in 3 steps  PriorCloudCover=(PriorCloudCover+(LastStepSize/3)*loopCount);//this way its going to change every 250 msec  
       //float math to get intensity changes in 3 steps in intensity modeling CloudCover=(CloudCover+(StepSize/3)*loopCount);//Now add the % increment or decremement to intensity
        
        if ((Counter>=(Severity+random(-1,6))) && (StrikeNow==false)) {//this is where a storm is triggered.  Counter indexes when cloud cover reaches 100 on the random walk
        //to change the frequency of lightning strikes increase or decrease the number comparison for counter in the if statement above (larger #== less storms).
          byte RandomStriker;
          RandomStriker=random(1,11);
          if (RandomStriker>4){
            StrikeNumber=(random(2,11)); //random high =x-1 so max strike =12 each strike requires a duration and a delay thus StrikeMaster is 18 positions
            //ensure the array is zeroed out past the last position required for this strike pattern so each pattern is only as long as generated
            //Array is in pairs, position in arry of (0,1) (2,3) etc as strike (delay,duration)
            for (byte a=0;a<20;a++){
                if (a>=(StrikeNumber*2)){
                    StrikeMaster[a]=0;
                }
                if (a%2==0){
                   if (a==0){
                      StrikeMaster[a]=random(300,1601);//no need for random here but I am leaving it since I wrote it that way.  This must be independent from a=2,4,6 etc...
                    }
                    else {
                       StrikeMaster[a]=(StrikeMaster[(a-2)]+random(160,1201));//position 0,2,4,6,8.. is strike delay
                    } 
                 }
                 else if(a%2!=0){
                    StrikeMaster[a]=random(45,100);//position 1,3,5,7,9... is strike duration (I tried real lightning strike durations and its too short this is adjusted for visual effect
                 }
            }
            StrikeNow=true; //Trigger to start strike sequence in loop
            StrikeStart=millis();//set timer to "zero" now- sequence will start in loop after this function
            StrikeCount=0;
          }
          Counter=0;
        }
        if (Severity>=5){
          for (int a=0;a<8;a++) {
              if (Wchannel[a]==1){//if were in a storm but not a severe storm constrain whites to 50% of Insolation intensity
                if (DimOrder[a]==0){
                      //Serial.print("In a storm and DimOrder0 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])*0.2))*(1-((CloudCover-(StepSize-(StepSize/3)*loopCount))/100)))); 
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
                  else if (DimOrder[a]==1){
                      //Serial.print("In a storm and DimOrder1 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])*0.2))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/3)*loopCount))/100))));
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
              }
              else if (Wchannel[a]==0){//if were blue, we chase as for a cloud
                  if (DimOrder[a]==0){
                      //Serial.print("In a storm and DimOrder0 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+(((float)((ChannelValue[a]-flicker[a])))*(1-((CloudCover-(StepSize-(StepSize/3)*loopCount))/100)))); 
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
                  else if (DimOrder[a]==1){
                      //Serial.print("In a storm and DimOrder1 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/3)*loopCount))/100))));
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
              }    
          }
        }
        else if (Severity<5){
          for (int a=0;a<8;a++) {
              if (Wchannel[a]==1){//if were white we need to be off in a storm
                TrueIntensity[a]=0;
              }
              else if (Wchannel[a]==0){//if were blue, we chase as for a cloud
                  if (DimOrder[a]==0){
                      //Serial.print("In a storm and DimOrder0 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+(((float)((ChannelValue[a]-flicker[a])))*(1-((CloudCover-(StepSize-(StepSize/3)*loopCount))/100)))); 
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
                  else if (DimOrder[a]==1){
                      //Serial.print("In a storm and DimOrder1 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/3)*loopCount))/100))));
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
              }    
          }
        }
        loopCount++;
        if (loopCount>3) loopCount=1;
    }//end of storm if loop
}//End Weather function

//CloudStart drops light intensity to 50% of whatever daylight setting is to start the cloud at 50
int CloudStart(long StartTime){
    byte elapsed;
    elapsed=(elapsedTime-StartTime);//counts up since we start this at elapsedTime=StartTime and StartTime is fixed
    for (byte a=0; a<8; a++){ 
      TrueIntensity[a]=(flicker[a]+(((float)((ChannelValue[a]-flicker[a])))*(1-(elapsed*2.5)/100))); 
    if (elapsed>=20);
      Cloud=true;//start the cloud
    }
    return 50;//set CloudCover to 50 
}//end CloudStart function

//StartStorm sets up duration and severity of storm. Its currently limited to 90-600 sec in length- it will rarely be lower than 3 minutes
byte StartStorm(int MaxLength, boolean& trigger, long& EndTime){
      byte LightningIntensity;
      int StormDuration;
      MaxLength-=120;//remove 2 mins from longest storm so that we end up with 2 minutes of cloud after the storm before the sky clears to daylight
      if (MaxLength>600){
         MaxLength=600;//modify local variable
         StormDuration=random((MaxLength/3),(MaxLength+1));
         EndTime=(elapsedTime+StormDuration);//Set by reference StormEnd static variable in weather
         //Serial.print("We started a max length storm it should last=");
         //Serial.println(StormDuration);
      }
      else {
         StormDuration=random((MaxLength/2),(MaxLength+1));
         EndTime=(elapsedTime+StormDuration);//Set by reference StormEnd static variable in weather
         //Serial.print("We staarted a cloud time limited length storm it should last=");
         //Serial.println(StormDuration);
      }
      if (elapsedTime<midDay){
         LightningIntensity=random(3,7);//afternoon storms are more likely to be severe (every 10-15 sec or less) to about once in a minute or maybe less
      }
      else if (elapsedTime>midDay){
         LightningIntensity=random(3,11);//morning storms are generally less severe.
      }
      trigger=true;
      return (LightningIntensity);
}
//End Storm Start Function
        
//Similar to Cloud start but in reverse... now ramp intensity from wherever we were at the end of the cloud to the value set by Insolation
void ClearSky(int CloudPercent, long TerminationTime)
{
    byte elapsed=(elapsedTime-TerminationTime);//Counts up from the scheduled end of the cloud in seconds
    float slope=(CloudPercent/30);//Just calculate how much to increment every second to go from CloudCover to clear sky (CloudCover of zero)
    float LightAdvance;
    LightAdvance=(CloudPercent-(slope*elapsed));//were reducing CloudCover from start to zero over 10 seconds.
    for (byte a=0; a<8; a++){ 
    TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-(LightAdvance/100)))); 
    }
    if (elapsed>=30){//at this point lights are back to full Insolation setting so cancel the cloud and start waiting for the next one
        Cloud=false;//stop the cloud we are now outside of a true condition in the if loop so it will now stay false and lights are back on
        IsStorm=false;//just to be redundant this is not called from a storm... 
    }
}//End Clear Sky function

byte MoonPhase()
{
   int m,d,y;
   int yy,mm;
   long K1,K2,K3,J,V;
   byte PWMvalue;
   m = month();
   d = day();
   y = year();
   yy = y-((12-m)/10);
   mm = m+9;
   if (mm>=12) mm -= 12;
   K1 = 365.25*(yy+4712);
   K2 = 30.6*mm+.5;
   K3 = int(int((yy/100)+49)*.75)-38;
   J = K1+K2+d+59-K3;
   V = (J-2451550.1)/0.29530588853;
   V -= int(V/100)*100;
   V = abs(V-50);
   PWMvalue = 4*abs(50-V);  // 5.12=100%    4=~80%
   //pinMode(lowATOPin,OUTPUT);
   //return (PWMvalue*100)/255; //output is 0-100
   return PWMvalue;//output is 0-255 
}

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 > 3 && M < 11){
        isDST=true; 
    }
    else{  
        int previousSunday=(D-dow); // Sunday=1 Sat=7
        if (M==3 && previousSunday>7){ 
            isDST=true;
        }
        else if (M==11 && previousSunday<=0){
            isDST=false;
        }
    }
}

//********************** DO NOT MESS WITH THIS UNLESS YOU KNOW WHAT YOUR DOING****************************
//THE CODE BELOW THIS copied directly from the SWFLTEK Epherma library constructed by Michael Rice.  
//this code is being used freely with attribution to Micahel Rice in accord with his request
//  A big thank you for these library functions.  Its great! 


//convert degrees to seconds of arc


// decimal degrees
long ddToSeconds(float dd){
	return dd * 3600.0;
}

//Degrees, minutes, seconds
long dmsToSeconds(int d, unsigned char m, unsigned char s){
long ret;

	ret = labs((long)d);
	ret = ret * 3600L + 60L * m + s;
	ret = (d<0L) ? -ret : ret;
	return ret;
}
/* ------------------------------------------------------------------------------------------------
	'Equation of Time'
	We use the 'short form equation, which has a theoretical accuracy of about 40 seconds.
	The returned value is in seconds.
*/
int equation_of_time(unsigned long dt){
double t;

	dt -= 192540UL; // refer to Jan 3 2000 05:29 (first periapsis)
	dt %= _tropical_year;
	t = dt;
	t /= _tropical_year;
	t *= 6.283185307179586;
	t = -459.27672 * sin(t) + 575.333472 * sin(2.0 * t + 3.588414);
	return t;
}

/*
	'Solar Noon' adjusts the passed time stamp to the time (GMT) of local solar noon.
	The accuracy is about 40 seconds (set by the equation of time).
*/
void SolarNoon(unsigned long * dt){
long r;

	// Set stamp to noon GMT
	*dt /= 86400UL;
	*dt *= 86400UL;
	*dt += 43200UL;

	// adjust for equation of time, at noon GMT
	*dt -= equation_of_time(*dt);

	// rotate to our longitude
	r = longitude / 15L;
	*dt -= r;
}

/* -----------------------------------------------------------------------------------------------
	'Solar Declination'
	Returns declination in radians
	Accurate to within 50 arc-seconds
*/

double SolarDeclination(unsigned long dt){
double y;

	dt %= _tropical_year;
	y = dt;
	y /= _tropical_year; // fractional year
	y *= 6.283185307179586;
	y=0.006918-0.399912*cos(y)+0.070257*sin(y)-0.006758*cos(y*2)+0.000907*sin(y*2)-0.002697*cos(y*3)+0.00148*sin(y*3);
	return y;
}

/* ------------------------------------------------------------------------------------------------
	Return the period between sunrise and sunset, in seconds.
	At high latitudes around the time of the solstices, this could be zero, or all day.
*/
unsigned long daylightseconds(unsigned long dt){
float l, d, e;
long n;

	d = -SolarDeclination(dt); // will be positive in Northern winter
	l = latitude / _sec_rad; // latitude in radians

	e += 60.0 * l * tan(l + d); // latitudinal error
	d = tan(l) * tan(d); //

	if(d>1.0) return 86400UL;
	if(d < -1.0) return 0UL;

	d = acos(d);
	d /= _zenith;

	n = 86400UL * d;
	n += e;
	return n;
}


/* ------------------------------------------------------------------------------------------------
	Modify the passed time stamp to the time of sunrise (or sunset if 'set' is non-zero).
	Returns 0 to signal 'normal' completion. If the position is in a polar circle, 1 will be
	returned if the sun is above the horizon all day, and -1 if the sun is below the horizon
	all day.

*/
char SunRiseSet(unsigned long * dt, char set){
unsigned long daylen;

	daylen = daylightseconds(*dt);
	if(daylen == 86400UL) return 1;	// there is no 'night' today (midnight sun)
	if(daylen == 0UL) return -1; // there is no 'day' today

	*dt /= 86400UL;
	*dt *= 86400UL;
	*dt += 43200UL; // set the time stamp to 12:00:00 GMT

	*dt -= daylen / 2; //  		sunrise at the prime meridian
	if(set) *dt += daylen; //  	sunset at the prime meridian

	*dt -= equation_of_time(*dt);

	*dt -= longitude / 15.0; // rotate to our own meridian

	return 0;
}
 // 'short' forms of SunRiseSet
char SunRise(unsigned long* when){
    return SunRiseSet(when, 0);
}
char SunSet(unsigned long* when){
    return SunRiseSet(when, 1);
}
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

Hey i think we have to put GMT offset to zero & Lat/long as per GMT to have sunrise/sunset. i used the values of lat/long of my place without any success & when i am using the same with GMT lat/long its working great.
Image
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

please post your settings for GT offset and lat lon.... curious whats up. BUT Awesome.!
And a question... your banner always shows you water is like 96 degrees.... is that working???

What do you keep at that temp!?
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

Well that a secret.lol actually right now i am just testing all my stuff will be setting the tank once temp of my place came down.

here is the copy of the code which m using now.
//***********************************ARRAYS YOU MUST MODIFY TO MAKE YOUR TANK SET UP WORK*********************
//CHANGE this to the number of seconds your time zone is offset from GMT (WITHOUT REGARD TO DAYLIGHT SAVINGS TIME)
int GMToffset=-0;//USA MST time zone is 25,200 if that helps you
//GLOBAL Variables YOU NEED TO CHANGE
boolean ApplyDST=false;//CHANGE THIS IF YOUR NOT USING DST
        //Calculate Latitude and Longitude converting from Degree, Minute, Seconds to decimal
        latitude=dmsToSeconds(51,28,38); //Set to kanpur,or GMT+5:30 zone,India
        longitude=dmsToSeconds(0,0,0); //Set to kanpur,or GMT+5:30 zone,India
Image
User avatar
JNieuwenhuizen
Posts: 96
Joined: Thu Feb 16, 2012 12:39 am
Location: South Africa

Re: C library for Sun/Moon effects

Post by JNieuwenhuizen »

I have mooncycle all the way, no lights on for 2 days now

Code: Select all

//CHANGE this to the number of seconds your time zone is offset from GMT (WITHOUT REGARD TO DAYLIGHT SAVINGS TIME)
int GMToffset=-7200;//USA MST time zone is 25,200 if that helps you
//GLOBAL Variables YOU NEED TO CHANGE
boolean ApplyDST=false;//CHANGE THIS IF YOUR NOT USING DST
byte ChMax[]={225,225,225,225,225,0,0,0};//Incremental value (Max-flicker) above flicker you want as max intensity- 
byte flicker[]={30,30,30,30,30,30,0,0};//need to input actual values here for flicker point on all channels in PWM expansion box
boolean Wchannel[]={1,1,1,1,0,0,0,0}; //use 1 to designate white channel (i.e. off during storm and used for lightning).  Array corresponds to PWM channel 0-5 in order
//Array to give direction to dimming.  e.g. DimOrder[]={0,0,1,1,0,0} (cloud chase effects, just group channels you want to dim together during a cloud or storm 
//as either a 0 or a 1, i.e. all left side channels are 0 all right are 1 or all front are 0 all back are 1 or whatever
//(which is zero or 1 will change who dims first).  set them all to 0 if your tank has no left/right or front/back lights.
byte DimOrder[]={0,1,0,1,0,0,1,1};

Code: Select all

latitude=dmsToSeconds(-25,87,13); //Set to Witbank - South Africa
longitude=dmsToSeconds(29,23,32); //Set to Witbank - South Africa
and

Code: Select all

int Choffset[]={
            900,3000,1800,2700,2700,1800,3600,900,-900,3600,0,0,0,0,0,0,0};
How did you do the GMT in the lat/long? i see you used 3 different numbers?
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

Hi,

The number which i recently posted are the lat/long of greenwich. I think that this program works with the time & value set on the head value(although its my wild guess still have to prove it) but looks like its automatically adjusting the sun timing with the local timing.
Image
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

What is the intensity of light @ moonphase?
Image
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Using the latitude and longitude of greenwich england would produce light at local times but according to the rise and set times of the sun in Grenwich england... not your part of the globe... so in your winter the sun would be rising and setting according to Greenwich summer patterns etc... Its obviously OK to run your tank this way for now. I am going to run a bunch of tests tonight to see what the heck is going on. To be honest, I am completely unsure what's wrong. I am betting I did something that is working for my half of the globe (or maybe quarter) but that I need to change to get it to work universally. The GMT correction is almost unnecessary but according to the library author (I did not write the few functions at the bottom of the code that actually calcualte the sunrise and sunset times... this should be clear... just read the first post on this thread) using a GMT offset improves accuracy by a few minutes a day. I will sit down this evening and try to get my tank to rise and set according to local time in both SA and India.

TO JNeu....

If you have no light... something else went wrong. Please copy and paste the new code I posted and then put your offsets etc and use the solution of abhi_123 for now using 0 GMT offset and Lat Lon coords he posted (those are GreenWich England). That should get you light.
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

rufessor wrote:Using the latitude and longitude of greenwich england would produce light at local times but according to the rise and set times of the sun in Grenwich england... not your part of the globe... so in your winter the sun would be rising and setting according to Greenwich summer patterns etc... Its obviously OK to run your tank this way for now. I am going to run a bunch of tests tonight to see what the heck is going on. To be honest, I am completely unsure what's wrong. I am betting I did something that is working for my half of the globe (or maybe quarter) but that I need to change to get it to work universally. The GMT correction is almost unnecessary but according to the library author (I did not write the few functions at the bottom of the code that actually calcualte the sunrise and sunset times... this should be clear... just read the first post on this thread) using a GMT offset improves accuracy by a few minutes a day. I will sit down this evening and try to get my tank to rise and set according to local time in both SA and India.

TO JNeu....

If you have no light... something else went wrong. Please copy and paste the new code I posted and then put your offsets etc and use the solution of abhi_123 for now using 0 GMT offset and Lat Lon coords he posted (those are GreenWich England). That should get you light.
Well @ noon according to local timing my light was shining just like a sun @noon where as at that time it is 6:30 am @ greenwich. Don't know why this is happening though.
Image
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

The intensity model for the moon phase (Riami can chime in here he wrote that code I used it) is output as 0-255 as a byte value that is simply use to set the Channel Intensity for whichever channels in Insolation your used for moon phase. My understanding is that its basically scaling the rough percentage of the moons surface that is illuminated so a Full Moon would output 255 from the MoonPhase calculation and no moon is a zero. IF your using (like I am) a high power LED array for moon lighting, your going to want to do what I did... which is to cut down the output (just look at my code, in Insolation to find that part). Otherwise, since I have 8 high power violet LEDs as my moon lighting, during a full moon there would be very significant amounts of PAR on the tank which is not what I want... so I limit my intensity. If your using a purpose built MoonLight, then just leave it 0-255 should be fine.

Is this what you wanted to know? Or were you asking prior post a questions.

In answer to your question about time... your local clock is local not on GMT... sunrise and sunset just determine seconds since midnight (or I should say the way my code works is this way) that the sun will rise or set. So. Since your Arduino processor clock thinks midnight is midnight LOCAL... your getting the day length of Greenwich England, but at your local times. Does this make sense?
User avatar
JNieuwenhuizen
Posts: 96
Joined: Thu Feb 16, 2012 12:39 am
Location: South Africa

Re: C library for Sun/Moon effects

Post by JNieuwenhuizen »

Code: Select all

//By Matthew Hockin 2012.  
//SWFLTEK library functions, #includes, and #define were copied directly from the Epherma library
//Epherma (SWFLTEK.com) was written by Michael Rice- thanks Michael it works great. 
//If you copy from this (its all open source) please 
//acknowledge Michael for SWFLTEK library code (obviously labeled) or Matthew Hockin (for the rest).

//You MUST have installed the SWFLTEK Ephmerma library- this library was written by Michael Rice (swfltek.com) 
//its source code and distribution rights are stated in the .h file -  Thanks to Michael for doing such a great job 
#include <Time.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <avr/wdt.h>
//includes for SWFLTEK functions
#include <stdlib.h>
#include <math.h>


//***********************************ARRAYS YOU MUST MODIFY TO MAKE YOUR TANK SET UP WORK*********************
//CHANGE this to the number of seconds your time zone is offset from GMT (WITHOUT REGARD TO DAYLIGHT SAVINGS TIME)
int GMToffset=-7200;//USA MST time zone is 25,200 if that helps you
//GLOBAL Variables YOU NEED TO CHANGE
boolean ApplyDST=false;//CHANGE THIS IF YOUR NOT USING DST
byte ChMax[]={225,225,225,225,225,0,0,0};//Incremental value (Max-flicker) above flicker you want as max intensity- 
byte flicker[]={30,30,30,30,30,30,0,0};//need to input actual values here for flicker point on all channels in PWM expansion box
boolean Wchannel[]={1,1,1,1,0,0,0,0}; //use 1 to designate white channel (i.e. off during storm and used for lightning).  Array corresponds to PWM channel 0-5 in order
//Array to give direction to dimming.  e.g. DimOrder[]={0,0,1,1,0,0} (cloud chase effects, just group channels you want to dim together during a cloud or storm 
//as either a 0 or a 1, i.e. all left side channels are 0 all right are 1 or all front are 0 all back are 1 or whatever
//(which is zero or 1 will change who dims first).  set them all to 0 if your tank has no left/right or front/back lights.
byte DimOrder[]={0,1,0,1,0,0,1,1};
//**********************************DONE CHANGING THINGS HERE BUT YOU MUST CHANGE ChOffset array IN CalcSUN function******


//defines for SWFLTEK functions
// arc-seconds per radian
#define _sec_rad 206264.806247096370813

// axial tilt of earth at epoch, in radians
#define _tilt 0.409092804222329

// tropical year in seconds... rounding error accumulates to 26 seconds by the year 2136
#define _tropical_year 31556925

// 'zenith' of rising (setting) sun in radians (360 - 2 * 90.833 degrees)
#define _zenith 3.11250383272322

//*******************GLOBAL VARIABLE DECLERATIONS FOR MHOCKIN Weather package*************************************
//Unless your planning on editing the program DO NOT CHANGE ANYTHING HERE
long latitude, longitude;
byte TrueIntensity[8];//array used to place hold values calculated by insolation
long elapsedTime;//used multiple places as elapsed since new day.
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long rise;//time in seconds from midnight for sunrise today
unsigned long set;//time in seconds from midnithgt for sunset today
unsigned long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long ChRiseSet[16];//times of rise and set for all 8 channels based upon offsets from calc rise and set values
float ChSlope[16];//slopes for 1/2 day calculations based upon time from offset to midday for channel 1-8
long CloudMaster[20];// Set up array to hold start and end times for clouds for the day- dont size it dynamically make it big enough for any day
long midDay;// exactly 1/2 way between rise and set, i.e. my take on solar noon- which its not... 
byte PWMports[] ={
    3,5,6,9,10,11};
byte ChannelValue[8];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)
unsigned long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[20];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator (8 strikes=16 positions)
byte StrikeNumber;//place to hold total number of strikes this sequence
boolean StrikeNow;//starts lightning strike sequence in loop state change made in weather/storm loop
byte StrikeCount;//Used to properly sequence strike sequence for delay between strikes
byte cmdnum=255;
byte datanum=255;
byte dow=0;//day of week
boolean trigger; //used a few places as a switch to ensure things only run 1x when needed trigger 2 is for daily reset of sunrise sunset
byte strikePattern, strikeTime;//used in Lightning() for timing of particular instance of strike 
boolean isDST;//are we in DST 
boolean Cloud;// are we in a cloud interval on days that have clouds
boolean CloudToday;//set in CalcSun if randomization yields a day with clouds.
boolean IsStorm;// are we in a storm
byte CloudsTotal;// how many clouds today
long lastmillis;// variable to track millis to enable cloud and insolation loop restriction by time
boolean StormAdvance;//storm timer for light effect advance
boolean InsolationAdvance;//when true we recalculate light intensity during clear sky
byte counter;//used to track millis advance for insolation,cloud trigger
//****************************************
//END HEADER/Global Variable declaration//
//****************************************

//Setup
void setup(){
    Serial.begin(57600);
    Wire.begin(8);
    //Wire.onReceive(receiveEvent);
    //Wire.onRequest(requestEvent);

    pinMode(3,OUTPUT);
    pinMode(5,OUTPUT);
    pinMode(6,OUTPUT);
    pinMode(9,OUTPUT);
    pinMode(10,OUTPUT);
    pinMode(11,OUTPUT);
    wdt_enable(WDTO_1S);
    unsigned long seed=0, count=32;
    while (--count){
      seed = (seed<<1) | (analogRead(5)&1);
    }
      randomSeed(seed);//start random generator at a different point each time (not perfect but whatever its gonna be pretty damn random)
    setSyncProvider(RTC.get);   // the function to get the time from the RTC
    setSyncInterval(SECS_PER_HOUR);  // Changed to sync every hour.
    
    isDST=false;//set day light savings time correction to false, is overridden by DST function if you choose to use it 
    dow=0;//set Day Of Week (dow) to a 0 value which is impossible (day()=1-7)... so we trigger calcSun on restart 
    StrikeNow=false;//no lightning strike yet
    CloudToday=false;//set to no clouds so CalcSun can set correctly if should be true
    Cloud=false;//set cloud to false
    IsStorm=false;//set storm to false
    lastmillis=millis();//start our millis timer now
    counter=0;//used in weather for triggering a storm, triggering lightning in a storm.
    StrikeCount=0;//Number of lightning strikes in the sequence.. set to zero until initialized in sequence
}
//End Setup
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Loop
void loop(){ 
    wdt_reset();
    if (cmdnum!=255){
        ProcessCMD(cmdnum,datanum);    
        cmdnum=255;
        datanum=255;
    }
    
    if (dow!=day()){ //be aware that during DST times the new day calculation will occur 1 hour early....but its corrected out
      trigger=true;//on reset this will also be true as numbering is 1-31 max
      //Serial.println("We set trigger to true");
      dow=day();//now set dow to actual day numbering so that at midnight/new day we again calc sunrise sunset
    }
  
    //Use millis to enable tracking of time interval
    //this will ensure at least minimally 1 sec intervals but may be longer if program is slow during a long calculation but at least never shorter.
    if ((millis()-lastmillis)>=100){
        lastmillis=millis();
        counter+=1;
        StormAdvance=true;
        //now a bunch of stuff that may or may not be true at the same time but that all needs to happen when its true
        if (counter==0){
          InsolationAdvance=true;//so that it runs on start up to provide light immediately 
        }  
        if (counter%30==0){
          InsolationAdvance=true;
          /*Serial.print("elapsed time in seconds from midnight is =");
          Serial.println(elapsedTime);
          Serial.print("cloud=");
          Serial.println(Cloud);
          Serial.print("IsStorm=");
          Serial.println(IsStorm);*/
        }
        if (counter==210) counter=0; 
    }     
    
    // now run through rise/set calculation and then intensity set and finally weather overlay when required

   if (trigger==true) CalSun();
   if (InsolationAdvance==true) Insolation();
   Weather();//due to variable storm vs cloud update timing this must be run from loop without condition
  //check to see if were need to have a lightning strike
    
    if (StrikeNow==true){
       if ((millis()-StrikeStart)>=StrikeMaster[(StrikeCount*2)]){//check if time has passed the delay (position 0,2,4,6,8 etc in StrikeMaster)-StrikeCount is indexed up by 1 after each strike so we see positions 0,2,4,6,etc in sequence
          byte intensity;
          intensity=random(175,256);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
              for (byte b=0; b<6; b++){
                  if (Wchannel[b]==1) analogWrite(PWMports[b],intensity);// set all whites to whatever random intensity was generated
              }
          delay(StrikeMaster[((StrikeCount*2)+1)]);//index to +1 position in array from 0,2,4, etc to 1,3,5 etc
          StrikeCount++;//so that the next time we look at elapsed time were looking at the right array position
          if (StrikeCount==(StrikeNumber-1)){
            StrikeNow=false;
            StrikeCount=0;
          }
       }
    }

    //track time in seconds-dont move this part up in the loop it really should stay below the rest as DST is not calculated until CalSun is run 1 time.
    elapsedTime=((now()-946684800)-newDay);//this is uncorrected for DST, the ONLY thing that is, since its all relative... is the actual sunrise and set seconds
    //Now that we have generated our sun pattern, Weather as clouds, and possibly a storm,  and possibly lightning each of which override the prior channel setting
    //lets actually make some light.
    for (byte a=0;a<6;a++){
      analogWrite(PWMports[a],TrueIntensity[a]);//dont change this to 8 to refelct array for channels.. we only have 6 here!
    }
}
//End Loop
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
void receiveEvent(int howMany) {
    wdt_reset();
    if (howMany==5){
        byte cmd1, cmd2, cmd3, cmd4, cmd5;
        cmd1=Wire.read();
        cmd2=Wire.read();
        cmd3=Wire.read();
        cmd4=Wire.read();
        cmd5=Wire.read();
        if (cmd1=='$' && cmd2=='$' && cmd3=='$'){
            cmdnum=cmd4;
            datanum=cmd5;
            //Serial.println(cmd4,DEC);
            //Serial.println(cmd5,DEC);
        }
    }
    else{
        for (int a=0;a<howMany;a++){
            Wire.read();
        }
    }  
}

void ProcessCMD(byte cmd, byte data){
    wdt_reset(); 
}

//End Standard Functions
//*********************************************************************************************************************************

void CalSun(){
   //Serial.println("CalSun Run Now");
   trigger=false;
    // Every day at midnight, we calculate rise, set, time of highest sun, moon phase, or we do this if system has reset
        if (ApplyDST==true){
            CalcDST(day(),month(),weekday());
        }
        
        //Calculate Latitude and Longitude converting from Degree, Minute, Seconds to decimal
        latitude=dmsToSeconds(-25,87,13); //Set to Witbank - South Africa
        longitude=dmsToSeconds(29,23,32); //Set to Witbank - South Africa
       unsigned long SecInput;//intermediate variable to set rise and set input times
       unsigned long hours;//avoid casting and use UL
       unsigned long minutes;//avoid casting and use UL
          time_t t=now();
          hours=hour(t);
          minutes=minute(t);
        
        //Serial.println("Hours:Mins");
        //Serial.println(hours);
        //Serial.println(minutes);
        //Serial.print("CalSun now()=");
        //Serial.println(now());
        
        //Using time library from Arduino.time_t now(); returns seconds since 1970 in local time with NO DST correction
        //Arduino Time.h Library states #seconds to subtract to bring to Jan 1 2000 as origin for time is 
        //#define SECS_YR_2000  (946684800) the time at the start of y2k
        
         //Seems weird, but actually DO NOT correct new Day for DST 
        hours=(hours*3600);//seconds conversion saving a variable
        minutes=(minutes*60);//seconds conversion saving a variable
        newDay=(now()-(946684800+hours+minutes));//local time uncorrected for DST- hours and minutes were just converted to seconds in prior step
        SecInput=(newDay+GMToffset);
        rise=SecInput;// Dont screw with DST in sunrise sunset input its NOT part of GMT  
        set=SecInput;//were not converted yet- library uses POINTERS.. to modify these values  
        
        //Calculate rise time and set time using Epherma Library   
        SunRise(&rise);//call to Epherma Library using reference- memory is modified by Epherma
        SunSet(&set);//Call to Epherma library using reference- memory position is modified by Epherma
        
        if (isDST==true){//must correct DST NOW by subtracting 1 hour from the offset (i.e. clocks are 1 hr ahead and thus closer to GMT)
          rise=(rise-(GMToffset-3600));//during DST rise is 1 hour earlier than calculated i.e. clocks ahead 1 hour 6 am occurs "at 5am" if that makes sense
          set=(set-(GMToffset-3600)); 
        }
        else if (isDST==false){
          rise=(rise-GMToffset);
          set=(set-GMToffset);
        }
        
        //Serial.print("newDay=");
        //Serial.println(newDay);
        
        rise=(rise-newDay);//set to elapsed seconds today
        set=(set-newDay);
       /*Serial.println("Local Elapsed Seconds from NewDay--rise=");
        Serial.println(rise);
        Serial.println("Local Elapsed Seconds from NewDay--set=");
        Serial.println(set);*/
        
        //DONT MOVE THIS LINE ABOVE FINAL rise and set decleration it will SCREW up your rise and set during DST by 1 hour
        if (isDST==true) newDay-=3600;//subtract an hour from the time that will be  subtracted from GMT i.e. were one hour closer during DST
        
        
        
        //*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
        //channels 0-5 are PWM expansion board lights 6,7 are ReefAngel Controller PWM outputs
        //offsets for rise/set all values in seconds offset from calculated rise or set value (-) am offset=longer day****** (-)pm offset=shorter day)
        //array order is ch0 am offset, pm offset, ch1 am offset, pm offset etc..
        //THESE values are the number of seconds that a particular channel will be offset from the rise/set time, i.e. negative to rise earlier/set earlier
        int Choffset[]={
            900,3000,1800,2700,2700,1800,3600,900,-900,3600,0,0,0,0,0,0,0};
        //**********************ok now were done changing things here********************************************   
        
        
        
        //Calculate rise and set times for all channels in equivlants to elapsed seconds from midnight today
        //populate array for chRise and Set as well as chSlope for 0.5pi/ half day lenght for each channel from midday (asymmetric days are allowed)
        float deltaY=1.570796327;//1/2 * pi as integer by scaling* 10^9 to fill UL
        midDay=(((set-rise)/2)+rise);
        long HalfDayLength=((set-rise)/2);
        //Serial.print("MidDay");
        //Serial.println(midDay);
        
        for (byte b=0;b<16;b++){//working as of April 5 2012 serial tested
            if (b%2==0){
                ChRiseSet[b]=rise+(Choffset[b]);
                ChSlope[b]=(deltaY/(float)(HalfDayLength-(Choffset[b])));
                //Serial.print("Rise/Set a=");
                //Serial.println(b);
                //Serial.print("Value=");
                //Serial.print(ChRiseSet[b]);
                //Serial.print("Slope");
                //Serial.println(ChSlope[b], 12);
            }
            else if (b%2==1){
                ChRiseSet[b]=set+(Choffset[b]);
                ChSlope[b]=(deltaY/(float)(HalfDayLength+(Choffset[b])));
                //Serial.print("Rise/Set a=");
                //Serial.println(b);
                //Serial.print("Value=");          
                //Serial.print(ChRiseSet[b]);
                //Serial.print("Slope");
                //Serial.println(ChSlope[b], 12);
            }
        }  
        
        //***************** to CHANGE THE chance of Clouds actually occuring on a given day************************
        byte CloudChance=44;//% Chance of a Cloud every day
        //****************************now were done- did you use a value from 0-100 without a decimal?****************
        
        //once a day, after rise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
        byte RainMaker=random(1,101); 
        if (RainMaker<=CloudChance){
            CloudToday=true;//used to trigger weather function, can also be used to send flag to controller
        }
        else if (RainMaker>CloudChance){
            CloudToday=false;//see above comment on CloudToday
            //Serial.print("no cloud today");
            return;
        }
        // to "simplify" things and avoid redundant calculation loops all cloud times are in fraction of day lengths 
        
        //*********** this is only executed 1x /day unless power outage and ONLY if last statement is true****************
        ///ALl cloud set up is done here... weather subroutine just implements it
        /*The general strategy for this algorithim is as follows.  Calculate random cloud cover as percent of day,
         then randomly generate the # of discreet cloud instances for the day,
         then determine cloud length by (daylight seconds * percent overcast)/#clouds
         then randomize cloud lengths by splitting into groups of twos correcting for if we have odd # of clouds today*/
        
        long dayLength=(set-rise);
        //Serial.println("DayLength");
        //Serial.println(dayLength);
        
        // number of clouds possible for the day, max and min
        byte CloudsMax=10;//DONT INCREASE BEYOND 10 or it will DIE, or increase array size to handle it
        byte CloudsMin=4;//dont use 1 it may or may not work in the next loops and the cloud will likely be very long, i.e. daylength*overcast fraction
        CloudsTotal=random(CloudsMin,(CloudsMax+1));
        //Serial.print("CloudsTotal=");
        //Serial.println(CloudsTotal);
        
        
        // Average day is 50,000 secs so if 4 clouds and 10% that gets you 5,000 seconds of clouds (about 1800 seconds length for each of the 4 clouds in independent segments (if 4 is # clouds)
        byte OvercastMin=((CloudsTotal*10)/5);//Min cloud length will be about 1000 seconds (15 mins)- 1 hour min of clouds if you have 4, 2 hours if you have 8
        byte OvercastMax=((CloudsTotal*10)/2);//max cloud length will be about 2500 seconds (45 mins)- 6 hours max of clouds if you have 8, 3 hours max if you have 4
        float Overcast=random(OvercastMin,OvercastMax);
        Overcast=(Overcast/100);
        //Serial.println("Overcast");
        //Serial.println(Overcast);
 
        // set up start time for clouds
        //The way this is done is to split the total lenght of time for clouds into equal segments and then to randomly chop or add time to the 
        //segments such that cloud length is variable.  Then distribute into random parts of the day and fill array with start,duration pairs for clouds
        int CloudLength;
        CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
        long SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));//average sun length between clouds
        float CloudFraction=0;
        float SunFraction=0;
        /*Serial.print("CloudLength=");
        Serial.println(CloudLength);
        Serial.print("SunSegment=");
        Serial.println(SunSegment);*/
        
        //start by zero filling CloudMaster array
        for (byte a=0; a<20; a++){
          CloudMaster[a]=0;
        }
        byte b=0;//used to get pairs of fraction for SunFraction in for loop
        byte c=0;//used to get pairs of fraction for CloudFraction in for loop
        //now randomize cloud length and sunsegment length as pairs to get different looking days- 
        
        for (byte a=0; a<(CloudsTotal*2); a++){
          if (a%2==0){
            if (b==0){
              if (a==0){
                SunFraction=random(20,181);//vary each pair of SunSegments from 20%-180% of possible length such that every pair =2*SunSegment in length
                SunFraction=(SunFraction/100);
                CloudMaster[a]=(SunFraction*SunSegment);
              }
              else if (a<((CloudsTotal*2)-2)){
                SunFraction=random(20,181);//vary each pair of SunSegments from 20%-180% of possible length such that every pair =2*SunSegment in length
                SunFraction=(SunFraction/100);
                CloudMaster[a]=(SunFraction*SunSegment);
                b++;
              }
              else if (a==((CloudsTotal*2)-2)){
                SunFraction=(2-((float)CloudMaster[0]/(float)SunSegment));
                //Serial.print("Last SunSegment Fraction=");
                //Serial.println(SunFraction);
                CloudMaster[a]=(SunFraction*SunSegment);
              }
                
            }
            else if (b==1){
              if (a<((CloudsTotal*2)-2)){
                SunFraction=(2-SunFraction);//were on the second part of a pair
                CloudMaster[a]=(SunFraction*SunSegment);
                b=0;//reset so next time we start a new fraction
              }
              else if (a==((CloudsTotal*2)-2)){
                SunFraction=(2-((float)CloudMaster[0]/(float)SunSegment));
                //Serial.print("Last SunSegment Fraction=");
                //Serial.println(SunFraction);
                CloudMaster[a]=(SunFraction*SunSegment); 
              }
            }
          }
          else if (a%2==1){//if were in odd positions we need to determine cloud lengths in random pairs such that each pair =2*CloudLength in length
            if (c==0){
              CloudFraction=random(20,181);//vary each pair of SunSegments from 20%-180% of possible length such that every pair =2*SunSegment in length
              CloudFraction=(CloudFraction/100);
              CloudMaster[a]=(CloudFraction*CloudLength);
              c++;       
            }
            else if (c==1){
              CloudFraction=(2-CloudFraction);
              CloudMaster[a]=(CloudFraction*CloudLength);
              c=0;//reset so next loop finds a new fraction
            }
         }
      }
      //serial debugging to examine array
        /*Serial.println("here is cloud master in is entirety prior to forming start and end pairs");
        for (byte a=0;a<20;a++){
          Serial.println(CloudMaster[a]);
        }*/
      
         // now we have CloudMaster constructed as SunSegment,CloudLength,SunSegment,CloudLength.... 
         //reframe it to generate cloud start, cloud end, cloud start, cloud end
      for (byte a=0; a<(CloudsTotal*2); a++){
        if (a==0){// if were starting our first cloud we need to add to rise value to first sun segment
          CloudMaster[a]=rise+CloudMaster[a];
        }
        else {
          CloudMaster[a]=(CloudMaster[a-1]+CloudMaster[a]);//just add prior values together e.g. (second position is cloud end so to find end add rise corrected start time with duration)
                                                           // subsequent start would be end of 1st cloud + next sunsegment fraction
        }
      }
        //serial debugging to examine array
       /* Serial.println("here is cloud master in is entirety as start and end pairs");
        for (byte a=0;a<20;a++){
          if (a%2==0){
            Serial.print("Start time=");
            Serial.println(CloudMaster[a]);
          }
          else {
            Serial.print("End time=");
            Serial.println(CloudMaster[a]);
          }
        }*/
}//END SunCalc FUNCTION

void Insolation()
{
  InsolationAdvance=false;//reset this flag now that we have entered function
        /*Serial.println("Insolation 3 sec elapsed");
        Serial.print("Elapsed Time is=");
        Serial.println(elapsedTime);*/
        
        //define Pi as delta Y for slope since cos 0.5-1.5 Pi goes 0-1-0 in 0.5 pI increments slope of 1/2 day (0-1 intensity) delta Y is 1/2 Pi 
        float Pi=3.1415926;//scale to 10^8
        float PiHalf=1.5707963;//scale to 10^8
        
        float secSoFar;//variable to account for seconds elapsed for each channel 1/2 day period from rise-->midDay and midDay-->set
        
        /* using -cos(pi/2+elapsedTime/slope) calculate fractional intensity of each channel throughout the day
         use flicker points to adjust minimum intensity to stable light.  Turn off lights after set or before rise etc.
         by splitting into half days centered on midday (1/2 ofset-rise) we center exactly the cos function for every channel so color blends are maintained 
         throughout intensity ramp... more or less ... change intensity every 120 seconds throughout the day*/
        
        if (elapsedTime<=midDay){
          //Serial.println("in first half of day");
            for (byte b=0;b<16;b=(b+2)){
                if (elapsedTime>=ChRiseSet[b]){
                    secSoFar=(elapsedTime-ChRiseSet[b]);//just account for length of every channel 1/2 day and switch at midDay
                    switch (b){
                        case 0:
                            ChannelValue[0]=flicker[b]+ChMax[b]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println((float)ChMax[b],4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[0]);
                            //Serial.println((float)ChMax[b],4);
                            break;
                        case 2:
                            ChannelValue[1]=flicker[(b-1)]+ChMax[(b-1)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));   
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[1]);
                            break;
                        case 4:
                            ChannelValue[2]=flicker[(b-2)]+ChMax[(b-2)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[2]);
                            break;
                        case 6:
                            ChannelValue[3]=flicker[(b-3)]+ChMax[(b-3)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[3]);
                            break;
                        case 8:
                            ChannelValue[4]=flicker[(b-4)]+ChMax[(b-4)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[4]);
                            break;
                        case 10:
                            ChannelValue[5]=flicker[(b-5)]+ChMax[(b-5)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[5]);
                            break;
                       case 12:
                            ChannelValue[6]=flicker[(b-6)]+ChMax[(b-6)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[6]);
                            break;
                        case 14:
                            ChannelValue[7]=flicker[(b-7)]+ChMax[(b-7)]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(PiHalf+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(b);
                            //Serial.println(ChannelValue[7]);
                            break;
                    }   
                }
                else if (elapsedTime<ChRiseSet[b]){
                    switch (b){
                        case 0:
                            ChannelValue[0]=0;                //to enable moon phase lighting for any channel comment out this part and uncomment the next 2 lines
                            //ChannelValue[0]=MoonPhase();    //this is the after sunset part I told you to look for when you set the before sunrise moon phase channels
                            //if (ChannelValue[0]<flicker[0]) ChannelValue[0]=0;
                            break;
                        case 2:
                            ChannelValue[1]=0;
                            //ChannelValue[1]=MoonPhase();    
                            //if (ChannelValue[1]<flicker[1]) ChannelValue[1]=0;
                            break;
                        case 4:
                            ChannelValue[2]=0;
                            //ChannelValue[2]=MoonPhase();    
                            //if (ChannelValue[2]<flicker[2]) ChannelValue[2]=0;
                            break;
                        case 6:
                            ChannelValue[3]=0;
                            //ChannelValue[3]=MoonPhase();    
                            //if (ChannelValue[3]<flicker[3]) ChannelValue[3]=0;
                            break;
                        case 8:
                            ChannelValue[4]=0;
                            //ChannelValue[4]=(MoonPhase()/2.2);
                            //if (ChannelValue[4]<flicker[4]) ChannelValue[4]=0;
                            break;
                        case 10:
                            //ChannelValue[5]=0;
                            ChannelValue[5]=(MoonPhase()/2.2);    
                            if (ChannelValue[5]<flicker[5]) ChannelValue[5]=0;
                            break;
                       case 12:
                            ChannelValue[6]=0;
                            //ChannelValue[6]=MoonPhase();    
                            //if (ChannelValue[6]<flicker[6]) ChannelValue[6]=0;
                            break;
                        case 14:
                            ChannelValue[7]=0;
                            //ChannelValue[7]=MoonPhase();    
                            //if (ChannelValue[7]<flicker[7]) ChannelValue[7]=0;
                            break;
                    }
                } 
            }
        }  
        else if (elapsedTime>midDay){
          //Serial.println("were in the second half of the day");
            for (byte b=1;b<12;b=b+2){
                
                if (elapsedTime<=ChRiseSet[b]){
                    secSoFar=(elapsedTime-midDay);
                    switch (b){
                        case 1:
                            ChannelValue[0]=flicker[(b-1)]+ChMax[(b-1)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[0]);
                            break;
                        case 3:
                            ChannelValue[1]=flicker[(b-2)]+ChMax[(b-2)]*(-cos(Pi+(ChSlope[b]*secSoFar)));    
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[1]);
                            break;
                        case 5:
                            ChannelValue[2]=flicker[(b-3)]+ChMax[(b-3)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[2]);
                            break;
                        case 7:
                            ChannelValue[3]=flicker[(b-4)]+ChMax[(b-4)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                           //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4); 
                           //Serial.println(ChannelValue[3]);
                            break;
                        case 9:
                            ChannelValue[4]=flicker[(b-5)]+ChMax[(b-5)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[4]);
                            break;
                        case 11:
                            ChannelValue[5]=flicker[(b-6)]+ChMax[(b-6)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[5]);
                            break;
                       case 13:
                            ChannelValue[6]=flicker[(b-7)]+ChMax[(b-7)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[6]);
                            break;
                        case 15:
                            ChannelValue[7]=flicker[(b-8)]+ChMax[(b-8)]*(-cos(Pi+(ChSlope[b]*secSoFar)));
                            //Serial.println(-cos(Pi+(ChSlope[b]*secSoFar)),4);
                            //Serial.println(ChannelValue[7]);
                            break;
                    }
                }
                else if (elapsedTime>ChRiseSet[b]){
                    switch (b){
                        case 1:
                            ChannelValue[0]=0;
                            //ChannelValue[0]=MoonPhase();    
                            //if (ChannelValue[0]<flicker[0]) ChannelValue[0]=0;
                            break;
                        case 3:
                            ChannelValue[1]=0;
                            //ChannelValue[1]=MoonPhase();    
                            //if (ChannelValue[1]<flicker[1]) ChannelValue[1]=0;
                            break;
                        case 5:
                            ChannelValue[2]=0;
                            //ChannelValue[2]=MoonPhase();    
                            //if (ChannelValue[2]<flicker[2]) ChannelValue[2]=0;
                            break;
                        case 7:
                            ChannelValue[3]=0;
                            //ChannelValue[3]=MoonPhase();    
                            //if (ChannelValue[3]<flicker[3]) ChannelValue[3]=0;
                            break;
                        case 9:
                            ChannelValue[4]=0;
                            //ChannelValue[4]=(MoonPhase()/2.2);
                            //if (ChannelValue[4]<flicker[4]) ChannelValue[4]=0;
                            break;
                        case 11:
                            //ChannelValue[5]=0;
                            ChannelValue[5]=(MoonPhase()/2.2);    
                            if (ChannelValue[5]<flicker[5]) ChannelValue[5]=0;
                            break;
                       case 13:
                            ChannelValue[6]=0;
                            //ChannelValue[6]=MoonPhase();    
                            //if (ChannelValue[6]<flicker[6]) ChannelValue[6]=0;
                            break;
                        case 15:
                            ChannelValue[7]=0;
                            //ChannelValue[7]=MoonPhase();    
                            //if (ChannelValue[7]<flicker[7]) ChannelValue[7]=0;
                            break;
                    }
                }
            }
        }
        /*Serial.println("Insolation settings=");
        Serial.println(ChannelValue[0]);
        Serial.println(ChannelValue[1]);
        Serial.println(ChannelValue[2]);
        Serial.println(ChannelValue[3]);
        Serial.println(ChannelValue[4]);
        Serial.println(ChannelValue[5]);
        Serial.println(ChannelValue[6]);
        Serial.println(ChannelValue[7]);
        Serial.println("ChMax Settings");
        Serial.println(ChMax[0]);
        Serial.println(ChMax[1]);
        Serial.println(ChMax[2]);
        Serial.println(ChMax[3]);
        Serial.println(ChMax[4]);
        Serial.println(ChMax[5]);
        Serial.println(ChMax[6]);
        Serial.println(ChMax[7]);*/
        
}//END function


//This is where modification of insolation by clouds or a storm occurs
//Its worth noting that the clouds simply modify (depending on how much cloud cover there is) the existing light set by insolation as a fractional value
void Weather ()  
{
    static byte loopCount;
    static float CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
    static float PriorCloudCover;  //used to "delay" one side of the tank from the other in cloud passing effects
    static long StormStart;
    static long StormEnd;
    static long CloudEnd;
    static boolean wtrigger;//use to track the first run of functions to calculate random times that become fixed, see change from cloud to storm for useage
    static byte Counter;//used to trigger storms from cloud you can change its if loop comparison to decrease or increase storm rate see below in cloud==true if loop
    static byte Severity;
    static byte StormCount;// used to limit X storms per cloud and to choose which cloud can have a storm
    static int StepSize;
    static int LastStepSize;
    //check to see if were having a scheduled cloud
    if (Cloud==false){
      //Write Insolation values to TrueIntensity so the loop will pick them up and the cloud/storm will get the right data (since intensity changes during the day)
      for (byte a=0; a<8; a++){//this must be above the next loop
            TrueIntensity[a]=ChannelValue[a];//this is where intensity is set for the PWM channel analog write in the loop... don't mess with this.
        }   
      for (byte a=0; a<(CloudsTotal*2); a=(a+2)){//if its time for a cloud, run it
         if ((elapsedTime>=CloudMaster[a]) && (elapsedTime<=CloudMaster[(a+1)])) {
             CloudEnd=CloudMaster[(a+1)];//to avoid this loop running true during the compute cycles at the end of the cloud and before elapsedTime advances a second, actual cloud does not
             //Serial.print("We started a cloud and its end is=");
             //Serial.println(CloudEnd);
             CloudCover=CloudStart(CloudMaster[a]);//CloudStart modifies TrueIntensity to get us to 50% intensity at the start of the cloud effect and also sets cloud=true to bypass this
             Counter=0;
             StormCount=random(0,3);//the number of storms MAX that may occur in this cloud (remember Random is range= -1 on high end)
             loopCount=1;
             LastStepSize=0;//zero out cloud random walk variables
             StepSize=0;//zero out cloud ranodm walk variables
             return;//exit having started a cloud in CLoudStart routine called above
         }
       } 
     //just write ChannelVales to TrueIntensity without modification so that cloud/storm can later modify them
     
     }
         
    else if ((Cloud==true) && (IsStorm==false)){
        if (StormAdvance==false){//use millis tracker to run this loop every 2 seconds
            return;
        }
        StormAdvance=false;//reset to false when true so we run this once, until time advance is true again
        //Serial.println("in a cloud doing stuff");
        if (elapsedTime>=CloudEnd){
            ClearSky(CloudCover, CloudEnd); 
            return;
        }
        
        /*Use fractional intensity to set minimum value for any channel.  Dimming is proportional to actual intensity output 
         and constrained by flicker point.  Random walk uses static variable "CloudCover" constrained to 0-100 to represent fractional intensity (i.e. (1-(CloudCover/100))*Insolation SetPoint 
         is how the current cloud intensity is set, i.e. cloud cover of 90 gives 10% insolation setpoint unless below flicker in which case = flicker*/
        if (loopCount==1){
          PriorCloudCover=CloudCover; //e.g. PriorCloudCover=CloudCover with no float math to screw things up
          LastStepSize=StepSize;
          StepSize=(random(5,21));// in Percent% (0-100) This is how much light intensity will change over the loop count interval (this actual time is dependent upon the call frequency of StromAdvance as set in the loop)
          if ((random(0,2)!=1)) StepSize=-(StepSize);
          if ((CloudCover+StepSize)>=100){//cannot shut off lights more than 100% so limit here
            StepSize=(100-CloudCover);//
            Counter++;
            //Serial.print("Counter for cloud to storm transition just hit 100 cloud cover and went up by 1=");
            //Serial.println(Counter);  
          }
          else if ((CloudCover+StepSize)<=0){//cannot be brighter than 100% so since were in a cloud dont "limit" it but reflect it back down
            StepSize=-(StepSize);
            if (Counter>=50) Counter-=random(-1,2);//since we got bright... lets further delay and randomize storm occurence 
            //Serial.print("Counter for cloud to stormtransition just hit 0% cloud and it now is =");
            //Serial.println(Counter);  
          }
          CloudCover=CloudCover+StepSize;
        }
       // float math for light intensity setting is PriorCloudCover (dim order 1)=(PriorCLoudCover+(LastStepSize/4)*loopCount);//this way its going to change every 250 msec  
       //float math for light intensity setting is CloudCover (dim order 0)=(CloudCover+(StepSize/4)*loopCount);//Now add the % increment or decremement to intensity
        
        /*Serial.print("PriorCloudCover=");
          Serial.println(PriorCloudCover);
          Serial.print("Stepsize=");
          Serial.println(StepSize);
          Serial.print("CloudCover=");
          Serial.println(CloudCover);*/
        
        if ((Counter>=60) && ((CloudEnd-elapsedTime)>=300)) {//if Counter (indexes when cloud cover reaches 100) has accumulated and we still have time lets make a storm
        //to change the frequency of storms increase or decrease the number comparison for counter in the if statement above (larger #== less storms).
        //if you change counter comparison here change it in the next loop as well
           if (StormCount>=1){//if we can have storms in this cloud (random- statisticly 1/3 clouds = no storm, 1/3 = 1 possible storm, 1/3 = 2 possible storms)
             byte RandomStorm;
             RandomStorm=random(0,11);//this randomizes for longer clouds without storm, avg cloud is much longer prior to storm occuring- thus short clouds will not generally have a storm
               if (RandomStorm>=4){
                 StormCount-=1;//count down by 1 the number of storms in this cloud- this will not roll the byte since the loop requires it to be at least 1 to ever subtract here. 
                 Counter=0;//reset this variable since Storm loop uses it as well.
                 int LongestStorm;//used to pass max possible time to storm if loop from cloud loop within weather function
                 LongestStorm=(CloudEnd-elapsedTime);
                 Severity=StartStorm(LongestStorm, IsStorm, StormEnd);
                 loopCount=1;//reset counting loop for the storm
                 //Serial.print("we started a storm"); 
               }
               //else Serial.print("Random call was missed- not a storm");  
           }
        }
        else if ((Counter>=60) && ((CloudEnd-elapsedTime)<300)){
           Counter=0;//just reset the counter (does not really matter in this case but its clean)
        }
        
        for (int a=0;a<8;a++){
            if (DimOrder[a]==0){
                //Serial.print("Initial Channel setting is= ");
                //Serial.println(a);
                //Serial.println(FullSun);
                TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-((CloudCover-(StepSize-(StepSize/4)*loopCount))/100))));//max intensity is 65% of initial for better visual dimming performance
                //Serial.print("DimOrder 0 setting is"); 
                //Serial.println(TrueIntensity[a]);
            }
            else if (DimOrder[a]==1){
                //Serial.print("Initial Channel setting is= ");
                //Serial.println(a);
                //Serial.println(FullSun);
                TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/4)*loopCount))/100))));//max intensity is 65% of initial for better visual dimming performance
                //Serial.print("DimOrder 1 setting is"); 
                //Serial.println(TrueIntensity[a]);
            }     
         }
         loopCount++;
         if (loopCount>4) loopCount=1;
    }  
    //enable a flag sent from controller to triger a storm, i.e. IsStorm=true
    // set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
    else if (((Cloud==true) && (IsStorm==true)) || ((Cloud==false) && (IsStorm==true))){
        //current else statement covers possibility of triggering storm from controller (i.e. not coming out of a cloud) but remember you need to flag wtrigger as TRUE when you do this
        if (StormAdvance==false){//Every 1 second duing a storm change intensity, clouds are movin fast baby
           return;
        }
        
        StormAdvance=false;//reset so we run again in 1 second.
        //Serial.print("in a storm now");
        
       if (elapsedTime>=StormEnd){ //if were done with the storm we need to stop this loop, but were probably still cloudy so dont mess with that here
            IsStorm=false;
            Counter=0;
            return;
        }
        if (loopCount==1){
          PriorCloudCover=CloudCover; //e.g. PriorCloudCover=CloudCover with no float math to screw things up
          LastStepSize=StepSize;
          StepSize=(random(5,21));// in Percent% (0-100) This is how much light intensity will change over the loop count interval (this actual time is dependent upon the call frequency of StromAdvance as set in the loop)
          if ((random(0,2)!=1)) StepSize=-(StepSize);
          if ((CloudCover+StepSize)>=100){//cannot shut off lights more than 100% so limit here
            StepSize=(100-CloudCover);
            Counter++;
            if (Counter>(Severity+2)) Counter=0;//allow if to accumulate on ocassion to train strike sequences 2-3 in a row but then dump it 
          }
          else if ((CloudCover+StepSize)<=0){//cannot be brighter than 100% so since were in a cloud dont "limit" it but reflect it back down
            StepSize=-(StepSize);
            if (Counter>=2) Counter-=random(0,2);//since we got bright... lets further delay and randomize strike occurence    
          }
          CloudCover=CloudCover+StepSize;
        }
   
       // float math to get intensity changes in 3 steps  PriorCloudCover=(PriorCloudCover+(LastStepSize/3)*loopCount);//this way its going to change every 250 msec  
       //float math to get intensity changes in 3 steps in intensity modeling CloudCover=(CloudCover+(StepSize/3)*loopCount);//Now add the % increment or decremement to intensity
        
        if ((Counter>=(Severity+random(-1,6))) && (StrikeNow==false)) {//this is where a storm is triggered.  Counter indexes when cloud cover reaches 100 on the random walk
        //to change the frequency of lightning strikes increase or decrease the number comparison for counter in the if statement above (larger #== less storms).
          byte RandomStriker;
          RandomStriker=random(1,11);
          if (RandomStriker>4){
            StrikeNumber=(random(2,11)); //random high =x-1 so max strike =12 each strike requires a duration and a delay thus StrikeMaster is 18 positions
            //ensure the array is zeroed out past the last position required for this strike pattern so each pattern is only as long as generated
            //Array is in pairs, position in arry of (0,1) (2,3) etc as strike (delay,duration)
            for (byte a=0;a<20;a++){
                if (a>=(StrikeNumber*2)){
                    StrikeMaster[a]=0;
                }
                if (a%2==0){
                   if (a==0){
                      StrikeMaster[a]=random(300,1601);//no need for random here but I am leaving it since I wrote it that way.  This must be independent from a=2,4,6 etc...
                    }
                    else {
                       StrikeMaster[a]=(StrikeMaster[(a-2)]+random(160,1201));//position 0,2,4,6,8.. is strike delay
                    } 
                 }
                 else if(a%2!=0){
                    StrikeMaster[a]=random(45,100);//position 1,3,5,7,9... is strike duration (I tried real lightning strike durations and its too short this is adjusted for visual effect
                 }
            }
            StrikeNow=true; //Trigger to start strike sequence in loop
            StrikeStart=millis();//set timer to "zero" now- sequence will start in loop after this function
            StrikeCount=0;
          }
          Counter=0;
        }
        if (Severity>=5){
          for (int a=0;a<8;a++) {
              if (Wchannel[a]==1){//if were in a storm but not a severe storm constrain whites to 50% of Insolation intensity
                if (DimOrder[a]==0){
                      //Serial.print("In a storm and DimOrder0 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])*0.2))*(1-((CloudCover-(StepSize-(StepSize/3)*loopCount))/100)))); 
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
                  else if (DimOrder[a]==1){
                      //Serial.print("In a storm and DimOrder1 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])*0.2))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/3)*loopCount))/100))));
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
              }
              else if (Wchannel[a]==0){//if were blue, we chase as for a cloud
                  if (DimOrder[a]==0){
                      //Serial.print("In a storm and DimOrder0 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+(((float)((ChannelValue[a]-flicker[a])))*(1-((CloudCover-(StepSize-(StepSize/3)*loopCount))/100)))); 
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
                  else if (DimOrder[a]==1){
                      //Serial.print("In a storm and DimOrder1 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/3)*loopCount))/100))));
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
              }    
          }
        }
        else if (Severity<5){
          for (int a=0;a<8;a++) {
              if (Wchannel[a]==1){//if were white we need to be off in a storm
                TrueIntensity[a]=0;
              }
              else if (Wchannel[a]==0){//if were blue, we chase as for a cloud
                  if (DimOrder[a]==0){
                      //Serial.print("In a storm and DimOrder0 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+(((float)((ChannelValue[a]-flicker[a])))*(1-((CloudCover-(StepSize-(StepSize/3)*loopCount))/100)))); 
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
                  else if (DimOrder[a]==1){
                      //Serial.print("In a storm and DimOrder1 channel values were");
                      //Serial.println(ChannelValue[a]);
                      //Serial.println(a);
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/3)*loopCount))/100))));
                      //Serial.print("and now the channel is set to");
                      //Serial.println(TrueIntensity[a]); 
                  }
              }    
          }
        }
        loopCount++;
        if (loopCount>3) loopCount=1;
    }//end of storm if loop
}//End Weather function

//CloudStart drops light intensity to 50% of whatever daylight setting is to start the cloud at 50
int CloudStart(long StartTime){
    byte elapsed;
    elapsed=(elapsedTime-StartTime);//counts up since we start this at elapsedTime=StartTime and StartTime is fixed
    for (byte a=0; a<8; a++){ 
      TrueIntensity[a]=(flicker[a]+(((float)((ChannelValue[a]-flicker[a])))*(1-(elapsed*2.5)/100))); 
    if (elapsed>=20);
      Cloud=true;//start the cloud
    }
    return 50;//set CloudCover to 50 
}//end CloudStart function

//StartStorm sets up duration and severity of storm. Its currently limited to 90-600 sec in length- it will rarely be lower than 3 minutes
byte StartStorm(int MaxLength, boolean& trigger, long& EndTime){
      byte LightningIntensity;
      int StormDuration;
      MaxLength-=120;//remove 2 mins from longest storm so that we end up with 2 minutes of cloud after the storm before the sky clears to daylight
      if (MaxLength>600){
         MaxLength=600;//modify local variable
         StormDuration=random((MaxLength/3),(MaxLength+1));
         EndTime=(elapsedTime+StormDuration);//Set by reference StormEnd static variable in weather
         //Serial.print("We started a max length storm it should last=");
         //Serial.println(StormDuration);
      }
      else {
         StormDuration=random((MaxLength/2),(MaxLength+1));
         EndTime=(elapsedTime+StormDuration);//Set by reference StormEnd static variable in weather
         //Serial.print("We staarted a cloud time limited length storm it should last=");
         //Serial.println(StormDuration);
      }
      if (elapsedTime<midDay){
         LightningIntensity=random(3,7);//afternoon storms are more likely to be severe (every 10-15 sec or less) to about once in a minute or maybe less
      }
      else if (elapsedTime>midDay){
         LightningIntensity=random(3,11);//morning storms are generally less severe.
      }
      trigger=true;
      return (LightningIntensity);
}
//End Storm Start Function
        
//Similar to Cloud start but in reverse... now ramp intensity from wherever we were at the end of the cloud to the value set by Insolation
void ClearSky(int CloudPercent, long TerminationTime)
{
    byte elapsed=(elapsedTime-TerminationTime);//Counts up from the scheduled end of the cloud in seconds
    float slope=(CloudPercent/30);//Just calculate how much to increment every second to go from CloudCover to clear sky (CloudCover of zero)
    float LightAdvance;
    LightAdvance=(CloudPercent-(slope*elapsed));//were reducing CloudCover from start to zero over 10 seconds.
    for (byte a=0; a<8; a++){ 
    TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-(LightAdvance/100)))); 
    }
    if (elapsed>=30){//at this point lights are back to full Insolation setting so cancel the cloud and start waiting for the next one
        Cloud=false;//stop the cloud we are now outside of a true condition in the if loop so it will now stay false and lights are back on
        IsStorm=false;//just to be redundant this is not called from a storm... 
    }
}//End Clear Sky function

User avatar
JNieuwenhuizen
Posts: 96
Joined: Thu Feb 16, 2012 12:39 am
Location: South Africa

Re: C library for Sun/Moon effects

Post by JNieuwenhuizen »

Code: Select all

byte MoonPhase()
{
   int m,d,y;
   int yy,mm;
   long K1,K2,K3,J,V;
   byte PWMvalue;
   m = month();
   d = day();
   y = year();
   yy = y-((12-m)/10);
   mm = m+9;
   if (mm>=12) mm -= 12;
   K1 = 365.25*(yy+4712);
   K2 = 30.6*mm+.5;
   K3 = int(int((yy/100)+49)*.75)-38;
   J = K1+K2+d+59-K3;
   V = (J-2451550.1)/0.29530588853;
   V -= int(V/100)*100;
   V = abs(V-50);
   PWMvalue = 4*abs(50-V);  // 5.12=100%    4=~80%
   //pinMode(lowATOPin,OUTPUT);
   //return (PWMvalue*100)/255; //output is 0-100
   return PWMvalue;//output is 0-255 
}

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 > 3 && M < 11){
        isDST=true; 
    }
    else{  
        int previousSunday=(D-dow); // Sunday=1 Sat=7
        if (M==3 && previousSunday>7){ 
            isDST=true;
        }
        else if (M==11 && previousSunday<=0){
            isDST=false;
        }
    }
}

//********************** DO NOT MESS WITH THIS UNLESS YOU KNOW WHAT YOUR DOING****************************
//THE CODE BELOW THIS copied directly from the SWFLTEK Epherma library constructed by Michael Rice.  
//this code is being used freely with attribution to Micahel Rice in accord with his request
//  A big thank you for these library functions.  Its great! 


//convert degrees to seconds of arc


// decimal degrees
long ddToSeconds(float dd){
   return dd * 3600.0;
}

//Degrees, minutes, seconds
long dmsToSeconds(int d, unsigned char m, unsigned char s){
long ret;

   ret = labs((long)d);
   ret = ret * 3600L + 60L * m + s;
   ret = (d<0L) ? -ret : ret;
   return ret;
}
/* ------------------------------------------------------------------------------------------------
   'Equation of Time'
   We use the 'short form equation, which has a theoretical accuracy of about 40 seconds.
   The returned value is in seconds.
*/
int equation_of_time(unsigned long dt){
double t;

   dt -= 192540UL; // refer to Jan 3 2000 05:29 (first periapsis)
   dt %= _tropical_year;
   t = dt;
   t /= _tropical_year;
   t *= 6.283185307179586;
   t = -459.27672 * sin(t) + 575.333472 * sin(2.0 * t + 3.588414);
   return t;
}

/*
   'Solar Noon' adjusts the passed time stamp to the time (GMT) of local solar noon.
   The accuracy is about 40 seconds (set by the equation of time).
*/
void SolarNoon(unsigned long * dt){
long r;

   // Set stamp to noon GMT
   *dt /= 86400UL;
   *dt *= 86400UL;
   *dt += 43200UL;

   // adjust for equation of time, at noon GMT
   *dt -= equation_of_time(*dt);

   // rotate to our longitude
   r = longitude / 15L;
   *dt -= r;
}

/* -----------------------------------------------------------------------------------------------
   'Solar Declination'
   Returns declination in radians
   Accurate to within 50 arc-seconds
*/

double SolarDeclination(unsigned long dt){
double y;

   dt %= _tropical_year;
   y = dt;
   y /= _tropical_year; // fractional year
   y *= 6.283185307179586;
   y=0.006918-0.399912*cos(y)+0.070257*sin(y)-0.006758*cos(y*2)+0.000907*sin(y*2)-0.002697*cos(y*3)+0.00148*sin(y*3);
   return y;
}

/* ------------------------------------------------------------------------------------------------
   Return the period between sunrise and sunset, in seconds.
   At high latitudes around the time of the solstices, this could be zero, or all day.
*/
unsigned long daylightseconds(unsigned long dt){
float l, d, e;
long n;

   d = -SolarDeclination(dt); // will be positive in Northern winter
   l = latitude / _sec_rad; // latitude in radians

   e += 60.0 * l * tan(l + d); // latitudinal error
   d = tan(l) * tan(d); //

   if(d>1.0) return 86400UL;
   if(d < -1.0) return 0UL;

   d = acos(d);
   d /= _zenith;

   n = 86400UL * d;
   n += e;
   return n;
}


/* ------------------------------------------------------------------------------------------------
   Modify the passed time stamp to the time of sunrise (or sunset if 'set' is non-zero).
   Returns 0 to signal 'normal' completion. If the position is in a polar circle, 1 will be
   returned if the sun is above the horizon all day, and -1 if the sun is below the horizon
   all day.

*/
char SunRiseSet(unsigned long * dt, char set){
unsigned long daylen;

   daylen = daylightseconds(*dt);
   if(daylen == 86400UL) return 1;   // there is no 'night' today (midnight sun)
   if(daylen == 0UL) return -1; // there is no 'day' today

   *dt /= 86400UL;
   *dt *= 86400UL;
   *dt += 43200UL; // set the time stamp to 12:00:00 GMT

   *dt -= daylen / 2; //        sunrise at the prime meridian
   if(set) *dt += daylen; //     sunset at the prime meridian

   *dt -= equation_of_time(*dt);

   *dt -= longitude / 15.0; // rotate to our own meridian

   return 0;
}
 // 'short' forms of SunRiseSet
char SunRise(unsigned long* when){
    return SunRiseSet(when, 0);
}
char SunSet(unsigned long* when){
    return SunRiseSet(when, 1);
}

rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: C library for Sun/Moon effects

Post by rimai »

Correct. It just represents the full scale 8bit resolution of the moon cycle (0-255)
If you are using high power LEDs, you can simply use MoonPhase()/10 so the range would go from 0-255 to 0-25 and still preserve the correct cycle.
Roberto.
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

Well is there is any way to make this code only use two channels instead of all? If we can able to do that than i think we will quickly figure it out the issue related to sunrise & sunset.
Image
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

Roberto but the prob is that you cant tell which channel of your module will work on moon phase.
Image
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

Alright. All my modules channels are now on zero except channel no. 5.its on 21%. Looks like this is the one which is installed for moonlight. What say experts?
Image
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

This is the segment from the Insolation function that tells the PWM expansion board what to do with the lights when the elapsed time of the day is greater than the set (that part is not totally clear but the ChRiseSet array uses 0,2,4,6 positions for rise times and 1,3,5,7 positions for set times in pairs starting with CH0 is specified by position 0,1 in ChRiseSet (0= rise time, 1 = set time) so you can see in the switch case statements (case 1: etc)

that we are selecting the set times... and if its later than that... we can tell it what to do with ChanneValue[0], through 5... you NEED to edit this to make one (or more) of the channels a moon light channel. This is a code with NO moon lighting enabled for after the sunsets... note that ChannelValue[X]=MoonPhase is COMMENTED OUT AT ALL POSITIONS... and instead its just set to [0]= no light.

Code: Select all

  else if (elapsedTime>ChRiseSet[b]){
                    switch (b){
                        case 1:
                            ChannelValue[0]=0;
                            //ChannelValue[0]=MoonPhase();    
                            //if (ChannelValue[0]<flicker[0]) ChannelValue[0]=0;
                            break;
                        case 3:
                            ChannelValue[1]=0;
                            //ChannelValue[1]=MoonPhase();    
                            //if (ChannelValue[1]<flicker[1]) ChannelValue[1]=0;
                            break;
                        case 5:
                            ChannelValue[2]=0;
                            //ChannelValue[2]=MoonPhase();    
                            //if (ChannelValue[2]<flicker[2]) ChannelValue[2]=0;
                            break;
                        case 7:
                            ChannelValue[3]=0;
                            //ChannelValue[3]=MoonPhase();    
                            //if (ChannelValue[3]<flicker[3]) ChannelValue[3]=0;
                            break;
                        case 9:
                            ChannelValue[4]=0;
                            //ChannelValue[4]=(MoonPhase()/2.2);
                            //if (ChannelValue[4]<flicker[4]) ChannelValue[4]=0;
                            break;
                        case 11:
                            //ChannelValue[5]=0;
                            ChannelValue[5]=(MoonPhase()/2.2);    
                            if (ChannelValue[5]<flicker[5]) ChannelValue[5]=0;
                            break;
                       case 13:
                            ChannelValue[6]=0;
                            //ChannelValue[6]=MoonPhase();    
                            //if (ChannelValue[6]<flicker[6]) ChannelValue[6]=0;
                            break;
                        case 15:
                            ChannelValue[7]=0;
                            //ChannelValue[7]=MoonPhase();    
                            //if (ChannelValue[7]<flicker[7]) ChannelValue[7]=0;
                            break;
                    }
                }
            }
        }
Here is my code for this segment where I am using Ch4 for moon lights and scaling the intensity.

Code: Select all

 else if (elapsedTime>ChRiseSet[b]){
                    switch (b){
                        case 1:
                            ChannelValue[0]=0;
                            //ChannelValue[0]=MoonPhase();    
                            //if (ChannelValue[0]<flicker[0]) ChannelValue[0]=0;
                            break;
                        case 3:
                            ChannelValue[1]=0;
                            //ChannelValue[1]=MoonPhase();    
                            //if (ChannelValue[1]<flicker[1]) ChannelValue[1]=0;
                            break;
                        case 5:
                            ChannelValue[2]=0;
                            //ChannelValue[2]=MoonPhase();    
                            //if (ChannelValue[2]<flicker[2]) ChannelValue[2]=0;
                            break;
                        case 7:
                            ChannelValue[3]=0;
                            //ChannelValue[3]=MoonPhase();    
                            //if (ChannelValue[3]<flicker[3]) ChannelValue[3]=0;
                            break;
                        case 9:
                            //ChannelValue[4]=0;
                            ChannelValue[4]=(MoonPhase()/2.5);// I scale it by reducing intensity by 2.5 fold (divide by 2.5)
                            if (ChannelValue[4]<flicker[4]) ChannelValue[4]=0;
                            break;
Please beware that this ONLY sets the moon phase for AFTER the sunsets. Since the tank is ALSO dark before it rises you need to FIND the loop that looks exactly like this, but uses Case 0: Case 2: etc (even numbers) and make a corresponding change there... that way your moon lights will be on before and after sunset. That loop is a little further up the program (i.e. maybe 50 lines up the page).

I know its weird to have to set moonphase for the time from midnight to rise in a different part than the time from set-midnight.. but the day is split into 2 parts to get the cosine functions to map exactly at midday as 100% intensity while also allowing you to have offsets for the morning and evening that are different from each other. I will look but I think we might need to just require you to set your MoonPhase as I instructed... in two places for every channel you want to use.

It may be the case that your Ch5 offset is large and its still on due to it not having set yet... I dont recall ever releasing a version with moonlights on Ch5.. I only have channels 0-4 enabled on my tank since I only have 5 lights strings... so it would not have come from me... you either edited the parts above and enabled it... or your Ch5 offset is larger than the rest for the evening and its still daylight on that channel.. but would be at very low intensity as its getting ready to set. (i.e. Cos function is now near 0)
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

any way to use moonphase on relay box channel.
Image
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: C library for Sun/Moon effects

Post by rimai »

Use this in your main code instead of using on the pwm module.

Code: Select all

ReefAngel.PWM.SetDaylight(MoonPhase());
ReefAngel.PWM.SetActinic(MoonPhase());
Roberto.
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

And be sure its all off on the PWM like in the first example I posted... but check the before sunrise segment too... to be sure its all ChannelValue[X]=0;//
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

Rufessor i m using that. what m trying to say that instead of all channels of module used for lighting cant we use 2 channels only so that we can utilize other channels for other use.
Image
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

I don't get your question. What do you mean by instead of using for lighting (or did you mean lightning as in a thunderstorm)... if you meant lighting... what else would you use the PWM channels for... its designed to control lights. Please try to ask clear questions, its confusing enough with all the issues with rise and set times currently. Trying to work on that actually right now. Maybe I will figure it out and have a fix in the next hours... maybe not
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

I am using only 2 Drivers so i dont think that it is necessary to have led working out on each channel. I will be using pwm fans on other channels to control the temp ofheatsink & temp of water ofmy tank.
Image
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: C library for Sun/Moon effects

Post by rufessor »

Ok... sorry I cannot help with the fans!

However. Maybe the latitude and longitude will work now. Please copy paste the code below over the corresponding part of the code your using. Its the first half (roughly) of the CalSun() function. Copy up to the ChOffset array declaration.

Then change your GMT offset to a POSITIVE value if your in India or South Africa (its now the number of seconds you need to ADD to GMT to get local time (so its a negative value for me)). Then, please use your actual latitude and longitude and set the day light savings flag to false.

When you load this code... its got active serial debugging comments in, on purpose. After you load it, go to your Arduino program tools menu and choose serial monitor and then 57,600 baud. You may need to close and reopen the serial monitor.. but it should spit out some text and numbers. Please paste them back to this thread. its the raw value in seconds since 2K for your local sunrise and sunset as well as (I hope) the number of seconds since midnight for the rise and set... which if this is working will be in the range of 27,381 and 72,013 for Kanpur (when I had it working it was this for todays sun which is more or less EXACTLY what it should be) and about 24,478 and 62,174 for WitBank South Africa. If you get these values... just comment out the two parts of the code with

Serial.print etc.... (there are two groups close together) then reload and you should be good. if those values are wrong and your lights are not on then please post them here to help me debug (also post what time of day you did this locally) and reload your codes with the fix using 0 GMT offset and Greenwich lat and long values.

if its not working and you want to try a single troubleshooting step.. if the values are wrong or really different than what I posted (they should be within a hundred seconds or less about) try this... and then post both sets of results if you can..
change this line

SecInput=(newDay-GMToffset);
to this
SecInput=newDay;

Thanks

Code: Select all

void CalSun(){
   //Serial.println("CalSun Run Now");
   trigger=false;
    // Every day at midnight, we calculate rise, set, time of highest sun, moon phase, or we do this if system has reset
        if (ApplyDST==true){
            CalcDST(day(),month(),weekday());
        }

       unsigned long SecInput;//intermediate variable to set rise and set input times
       unsigned long hours;//avoid casting and use UL
       unsigned long minutes;//avoid casting and use UL
       time_t t=now();
       hours=hour(t);
       minutes=minute(t);
        
       //Using time library from Arduino.time_t now(); returns seconds since 1970 in local time with NO DST correction
       //Arduino Time.h Library states #seconds to subtract to bring to Jan 1 2000 as origin for time is 
       //#define SECS_YR_2000  (946684800) the time at the start of y2k
        hours=(hours*3600);//convert to seconds
        minutes=(minutes*60);//
        newDay=(now()-(946684800+hours+minutes));//local time at midnight in seconds from year 2K

        SecInput=(newDay-GMToffset);//Here we want to convert our seconds to GMT so change sign of offset
        rise=SecInput;// 
        set=SecInput;//
        
       //convert degree minute sec to decimal latitude longitude into required units for rise/set calculations
       latitude=dmsToSeconds(40,44,15);//United States of America- Salt Lake City, local time is -7 hours GMT (DST enabled is OK) 
       longitude=dmsToSeconds(-111,49,25);
      
       //Calculate rise time and set time using Epherma Library   
       SunRise(&rise);//call to Epherma Library using reference- memory is modified by Epherma
       SunSet(&set);//Call to Epherma library using reference- memory position is modified by Epherma
       Serial.print("raw rise value=");
       Serial.println(rise);
       Serial.print("raw set value is=");
       Serial.println(set);
       
       rise=(rise-newDay)+GMToffset;//to move from the time of sunrise/sunset as calculated
       set=(set-newDay)+GMToffset;
        
       if (isDST==true){
         rise+=3600;//during DST we move our clocks ahead so rise is actually 1 hour later than strict GMT offset correction
         set+=3600;
       }
       Serial.println("Final calculatd elasped seconds from midnight local time for sunrise=");
       Serial.println(rise);      
       Serial.println("Final calculated elapsed seconds from midnight local time for sunset=");
       Serial.println(set);
        
        //*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
abhi_123
Posts: 217
Joined: Tue Mar 20, 2012 8:34 am

Re: C library for Sun/Moon effects

Post by abhi_123 »

raw rise value 1089330668
raw set value 1089380096

final calculated elasped seconds from midnight local time for sunrise 4294900175
final calculated elasped seconds from midnight local time for sunset 4294949583.

this is what i am getting.
Image
Post Reply