Page 1 of 7

Weather Simulation for Dimming expansion module

Posted: Mon Jun 25, 2012 10:01 am
by rufessor
Hi everyone-

Over the last few months I have been developing a bit of code that attempts to more or less comprehensively simulate natural weather patterns over our tanks using PWM dimming of LED lights controlled by the PWM expansion module. This package includes things like clouds, storms, severe storms, lightning strikes, Moon Phase lighting, etc and utilizes your (or your chosen point on the globe) latitude and longitude coordinates to calculate the daily sunrise and sunset times so that your tank lights will turn on and off according to the natural rhythm of the seasons for that location. The code also models the natural solar insolation intensity and changes your light output throughout the day (akin to but not exactly like the Parabola Function). In effort to make this code "easy" to implement I have spent a fair amount of time working in the development thread (http://forum.reefangel.com/viewtopic.php?f=12&t=667) with a few users at different points on the globe to test this. I believe that its now fully functional as a PWM expansion ONLY code. Currently there is no way to control PWM channels installed on the main controller, there is no way to communicate from the controller to the PWM module or vice versa- These features are being added... if you want to help or are interested in this check the development thread as any new features being added will show up there first. I will NOT be posting a lot of incremental revisions here. This will be the place for tested working code. As MAJOR new features are added I will post the complete code here.

This thread is being started to greatly simplify the use and distribution of this .INO file. Any questions about how to do this should, I hope, be answered in the tutorial thread (basically a small manual) here http://forum.reefangel.com/viewtopic.php?f=14&t=1449.

A few generic provisions- If your just learning your way with Arduino, ReefAngel, and coding in general, you should be able to get this running pretty easily on you system- if you follow the tutorial- please go to that thread and read the tutorial carefully. There are a few blocks inside the .INO file below that contain set up information that you will NEED to edit so that my program knows how many channels of lights you have, where you live (or where you want you tank to live), and a few other things. IT WILL NOT WORK IF YOU JUST INSTALL THIS CODE- YOU NEED TO CONFIGURE A FEW THINGS IN THE .INO

If I may make a request- PLEASE post questions on implementation or problems HERE. If you have gained a bit of experience and have something to offer that you feel would help others set this up please post additions, revisions, tips, tricks, etc to the tutorial thread... But lets try to keep the tutorial thread simply a tutorial.
If you follow the tutorial I am hoping that things will go smoothly- when or if you run into problems and ask questions here (NOT on the tutorial) this thread will evolve so that as users are added and they read this thread it will serve as a Q and A and eventually most if not all of their questions will be answered here.

The tutorial is rather long, there are MANY things this code does for you... and to explain them requires some background... so start there.

TUTORIAL LINK
http://forum.reefangel.com/viewtopic.php?f=14&t=1449

So- to reiterate---- download the code once I get it posted here... then- open it in Arduino, go to the tutorial and walk through the set up. If its NOT working, post your problem here. Since the code is REALLY LONG.. if you have questions just copy and paste the SET UP information here (not the whole code).

And finally.... a small word of caution/warning- editing OUTSIDE of the set up blocks may cause major problems. This code is fairly complex and has many interdependent functions- please feel free (DUH its open source thats the point) to play but use caution if your new to coding... its not any more complicated than anything the controller is doing, but because I did not distribute functions into libraries.... well... its all there to mess with and that means its easy to break. If you like it but think you can make it better... jump into the development thread. I would love it!

Re: Weather Simulation for PWM expansion module

Posted: Mon Jun 25, 2012 12:42 pm
by rufessor
OK, here is the entire code. Copy paste and try it out.

You should not need any additional library functions, it does require some of the standard arduino library files but they should be installed for you if you used the package Roberto posts for installation of ReefAngel. I had at one point been playing with and using some custom library files but these were removed and all functions moved to this file to ease implementation. This is the ENTIRE code, it should compile and once you edit it according to the tutorial... it should run.

Lets see how its goes... I would like to say that this a guaranteed to work... but I keep working on it and we have really only had a handful of people use this, so far as I know they are all enjoying it and still using it... so it seems to be doing its job and making our tanks look just that much nicer.

As more people try this I would not be shocked to discover a bug or oddity. It was working for me until we tired it using East Latitudes.. that bug is now fixed apparently and its working to calculate day lengths and rise and set values in Utah and South Africa... that would seem to pretty much validate this... so please try it and let me know how its doing for you. I made a few changes to the code to greatly simplify the moon lighting use... I tested it and its working - so this is much easier to configure now. The tutorial has been updated.

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).

#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*****************************
//YOU MUST READ EVERY WORD IN THIS SECTION IN ORDER TO APPROPRIATELY CONFIGURE THIS PROGERAM- READ EVERY LINE BELOW///
byte ChMax[]={200,220,200,220,225,0,0,0};//Incremental value (Max-flicker) above flicker you want as max intensity (!!!!!!! Light Set Point is ChMax PLUS Flicker !!!!!!) 
byte flicker[]={31,31,31,31,40,0,0,0};//need to input actual values here for flicker point on all channels in PWM expansion box
boolean Wchannel[]={1,0,1,0,0,0,0,0}; //use 1 to designate white channel (i.e. off during storm and used for lightning).  Array corresponds to PWM channel 0-5 in order
//Array to give direction to dimming.  e.g. DimOrder[]={0,0,1,1,0,0} (cloud chase effects, just group channels you want to dim together during a cloud or storm 
//as either a 0 or a 1, i.e. all left side channels are 0 all right are 1 or all front are 0 all back are 1 or whatever
//(which is zero or 1 will change who dims first).  set them all to 0 if your tank has no left/right or front/back lights.
byte DimOrder[]={0,0,1,1,1,0,0,0};
//set all channel positions that you would like to use for the lightning strike effect to 1 (0-5 are PWM channels 6,7 are Main PWM outs)- and channels with a 0 are not used in strike
byte StrikeChannel[]={1,1,1,1,1,0,0,0};
byte MoonCh[]={0,0,0,0,1,0,0,0};//place a 1 in the array position of all lighting channels you would like to use a moon lighting (this does not preclude their use in other phases (day, storm etc)
//**********************************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 final write values for PWM intensity setting
long elapsedTime;//used multiple places as elapsed since midnight
long newDay;
unsigned long rise;//time in seconds from the year 2000 (GMT) for sunrise
unsigned long set;//time in seconds from the year 2000 (GMT) for sunrise
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-
long midDay;// exactly 1/2 way between rise and set, i.e. solar noon for latitudes <60 close enough for us... 
byte PWMports[] ={
    3,5,6,9,10,11};
byte ChannelValue[8];// Array to store output of insolaiton which may be modified and stored in TrueIntensity which is used to write to the PWM channels
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
byte strikePattern, strikeTime;//used in Lightning() for timing of particular instance of strike 
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 (every 3 seconds seems more than often enough)
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.
    
    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(){ 
    elapsedTime=(now()-newDay);//Elapsed time is seconds from midnight of today- local processor time.
    wdt_reset();
    if (cmdnum!=255){
        ProcessCMD(cmdnum,datanum);    
        cmdnum=255;
        datanum=255;
    }
    
    if (dow!=day()){ //used to see that were in a new day and need to recalculate sunrise and sunset
      CalSun();
      dow=day();
    }
  
    //Use millis to enable tracking of time interval
    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;
        }
        if (counter==210) counter=0; 
    }     

   if (InsolationAdvance==true) Insolation();//calculate clear sky solar intensity as the day advances
   Weather();//run the weather overlay (cloud, storm)
   //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(180,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 (StrikeChannel[b]==1) analogWrite(PWMports[b],intensity);// set all strike channels to intensity of strike
              }
          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;
          }
       }
    }
    for (byte a=0;a<6;a++){//using all prior mods to light intensity (Insolation-->Cloud-->Storm) lets make some light
      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
//*********************************************************************************************************************************
//Start of sunrise, sunset and cloud calculations- runs on reset and once a day thereafter.
void CalSun(){
   //Serial.println("CalSun Run Now");

        //*********************YOU NEED TO CHANGE THESE VALUES Read instructions in their ENTIRETY and CAREFULLY change to values for your tank and geographical region***************************
        //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,-1800,6000,0,600,-1200,6600,-2200,6600,0,0,0,0,0,0};
        // NOW SET YOUR LATIDTUDE AND LONGITUDE COORDINATES as Degrees, Minutes, Seconds of Lat and Lon
        //If Your NORTH of the equator your LONGITUDE must START with a NEGATIVE number (the rest are positive) e.g. All of North America, Europe, Russia etc are negative
        //If Your EAST of the Prime Meridian your LATITUDE must START with a NEGATIVE number (the rest are positive), e.g. Most of Europe, All of China, India, Austraila, Russia etc are negative
       latitude=dmsToSeconds(40,44,00);//United States of America- Salt Lake City, local time is -7 hours GMT 
       longitude=dmsToSeconds(-111,47,00);
        //**********************ok now were done changing things IF YOU CHANGED the Top part of the GLOBAL variable decleration AND this... your FULLY configured and ready to load******************************************** 
   
    if (dow==0){//if the controller has resarted we need to find midnight
      long hours, minutes;//store current elapsed local hours as total seconds from midnight
      time_t t=now();//store current clock time to parse
      hours=hour(t);
      hours=(hours*3600);//current hour number 0-23 as seconds
      minutes=minute(t);
      minutes=(minutes*60);//minutes of current hour as seconds
      newDay=now();
      newDay-=(hours+minutes);//Convert current local unix epoch time to local unix epoch time of midnight
    }
    else if (dow!=0){//if we did not restart but the day is new then it is midnight and were good to go..
      newDay=now();
    }
      
    //#define SECS_YR_2000 (946684800) the time at the start of y2k (need to subtract from unix epoch time to bring to Y2K origin
    newDay-=946684800;//convert GMT unix Epoch to seconds elasped since 2000 for GMT midnight of today
    
    rise=newDay;//set value to send to SunRise as midnight GMT in seconds from Y2K
    set=newDay;//
    //Calculate rise time and set time using Epherma Library functions (see end of code) 
    SunRise(&rise);//call to Epherma function
    SunSet(&set);//Call to Epherma functionunsigned long newDay;
  
   /*Serial.print("rise and set=  ");
   Serial.println(rise);
   Serial.println(set);
   Serial.print("newDay as seconds since 2000 to todays midnight=  ");
   Serial.println(newDay);*/
    rise=(rise-newDay);// set to elapsed seconds of day
    set=(set-newDay);
   /*Serial.print("rise and set as elapsed seconds of day=  ");
   Serial.println(rise);
   Serial.println(set);*/
    newDay+=946684800;//Convert newDay back to unix epoch GMT midnight today (used in loop to determine how far we are into the day) 
   /*Serial.print("newDay as seconds since since 1970 to todays midnight=  ");
   Serial.println(newDay);
   Serial.print("elapsed is");
   long elapsed=now()-newDay;
   Serial.println(elapsed);*/
   
        //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);
       
        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])));
            }
            else if (b%2==1){
                ChRiseSet[b]=set+(Choffset[b]);
                ChSlope[b]=(deltaY/(float)(HalfDayLength+(Choffset[b])));
            }
        }  
        
        //***************** 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?****************
        
  
        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
            return;
        }
        long dayLength=0;
        for (byte a=1;a<16;a=(a+2)){//determine maximum day length given light on tank that is not moon light, this will yield night clouds and storms (and a storm after dark is severe... always
          if (a==0){
            if (((set+Choffset[a])-rise)>(set-rise)){
              dayLength=((set+Choffset[a])-rise);  
            }
            else dayLength=(set-rise);
          }
          else if (a!=0){
            if (dayLength<((set+Choffset[a])-rise)){
              dayLength=((set+Choffset[a])-rise);
            }
          }
        }
        
        // 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 (among other things)
        byte CloudsMin=4;//use 2 as a minimum
        CloudsTotal=random(CloudsMin,(CloudsMax+1));
        
        // 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);
        
        // 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;
       
        //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));
                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));
                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.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]);
        }*/
      //reframe array 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.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

        //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){
           byte c=0;//loop counter
            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
                    ChannelValue[c]=flicker[c]+ChMax[c]*(-cos(PiHalf+(ChSlope[b]*secSoFar)));
                 }
                 else if (elapsedTime<ChRiseSet[b]){
                   if (MoonCh[c]==1){  
                      byte MoonToday=MoonPhase()*0.5;//SCALE FACTOR to DIM moon setting for use with HIGH power LED as moon light
                      if (MoonToday==0) ChannelValue[c]=0;
                      else if (MoonToday<flicker[c]) ChannelValue[c]=flicker[c];
                      else ChannelValue[c]=MoonToday;
                   }
                   else if (MoonCh[c]==0){
                     ChannelValue[c]=0;//its dark and this is not a moon phase channel
                   }
                 }
              c++;//index by one so we count 0-7 as b goes 0-14 by twos
            }
        }  
        else if (elapsedTime>midDay){
            byte c=0;//loop counter
            for (byte b=1;b<16;b=b+2){
              if (elapsedTime<=ChRiseSet[b]){
                    secSoFar=(elapsedTime-midDay);
                    ChannelValue[c]=flicker[c]+ChMax[c]*(-cos(Pi+(ChSlope[b]*secSoFar)));          
               }
               else if (elapsedTime>ChRiseSet[b]){
                   if (MoonCh[c]==1){  
                      byte MoonToday=MoonPhase()*0.5;//SCALE FACTOR to DIM moon setting for use with HIGH power LED as moon light
                      if (MoonToday==0) ChannelValue[c]=0;
                      else if (MoonToday<flicker[c]) ChannelValue[c]=flicker[c];
                      else ChannelValue[c]=MoonToday;
                   }
                   else if (MoonCh[c]==0){
                     ChannelValue[c]=0;//its dark and this is not a moon phase channel
                   }
                }
              c++;//index to count 0-7 as b counts 1-15 by twos.
            }
        }   
}//END function
//WEATHER FUNCTION BEGIN
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
         }
       } 
     }
         
    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
        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++;
          }
          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  
          }
          CloudCover=CloudCover+StepSize;
        }

        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
               } 
           }
        }
        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 (ChannelValue[a]==0) TrueIntensity[a]=0;// if were in an evening storm dont reset intensity (it would go to flicker point and possibly flicker)
            else if (DimOrder[a]==0){
                TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-((CloudCover-(StepSize-(StepSize/4)*loopCount))/100))));
            }
            else if (DimOrder[a]==1){
                TrueIntensity[a]=(flicker[a]+(((float)(ChannelValue[a]-flicker[a]))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/4)*loopCount))/100))));
            }     
         }
         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.
        
       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++;
          }
          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    
          }
          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 
          CloudCover=CloudCover+StepSize;
        }
   
        if ((Counter>=(Severity+random(-1,4))) && (StrikeNow==false)) {//this is where a storm is triggered.  Counter indexes when cloud cover reaches 100 on the random walk
        //to change the frequency of lightning strikes increase or decrease the number comparison for counter in the if statement above (larger #== less storms).
          byte RandomStriker;
          RandomStriker=random(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(200,1401));//position 0,2,4,6,8.. is strike delay
                    } 
                 }
                 else if(a%2!=0){
                    StrikeMaster[a]=random(50,110);//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 (ChannelValue[a]==0) TrueIntensity[a]=0;
              else 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){
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])/4))*(1-((CloudCover-(StepSize-(StepSize/3)*loopCount))/100)))); 
                  }
                  else if (DimOrder[a]==1){
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])/4))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/3)*loopCount))/100))));
                  }
              }
              else if (Wchannel[a]==0){//if were blue, we chase as for a cloud
                  if (DimOrder[a]==0){
                      TrueIntensity[a]=(flicker[a]+(((float)((ChannelValue[a]-flicker[a])))*(1-((CloudCover-(StepSize-(StepSize/3)*loopCount))/100)))); 
                  }
                  else if (DimOrder[a]==1){
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/3)*loopCount))/100))));
                  }
              }    
          }
        }
        else if (Severity<=5){// severe storms occur throughout the day, but EVERY storm after sunset is severe...
          for (int a=0;a<8;a++) {
              if (ChannelValue[a]==0) TrueIntensity[a]=0;
              else if (Wchannel[a]==1){//if were white we need to be off in a storm
                TrueIntensity[a]=0;
              }
              else if (ChannelValue[a]==0){//if this light channel is dark... e.g. after sunset for this channel- it produces no cloud effect
                  TrueIntensity[a]=0;
              }
              else if (Wchannel[a]==0){//if were not shut off in a strom and not after our daylight period (this channel) then we produce storm light sequences.
                  if (DimOrder[a]==0){//if we dim first... do it
                      TrueIntensity[a]=(flicker[a]+(((float)((ChannelValue[a]-flicker[a])))*(1-((CloudCover-(StepSize-(StepSize/3)*loopCount))/100)))); 
                  }
                  else if (DimOrder[a]==1){//else we dim second
                      TrueIntensity[a]=(flicker[a]+((((float)(ChannelValue[a]-flicker[a])))*(1-((PriorCloudCover-(LastStepSize-(LastStepSize/3)*loopCount))/100))));
                  }
              }//end of CWHchannel==0 being true
           }//end of for loop in severity <5 == true loop
        }//end severity compairson loop no more else statements
       
        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>720){
         MaxLength=720;//modify local variable
         StormDuration=random((MaxLength/3),(MaxLength+1));
         EndTime=(elapsedTime+StormDuration);//Set by reference StormEnd static variable in weather
      }
      else {
         StormDuration=random((MaxLength/2),(MaxLength+1));
         EndTime=(elapsedTime+StormDuration);//Set by reference StormEnd static variable in weather
      }
      if (elapsedTime<midDay){
         LightningIntensity=random(3,11);//morning storms are generally less severe
      }
      else if (elapsedTime>midDay){//afternoon storms are more likely to be severe (every 10-15 sec or less) to about once in a minute or maybe less
         if (elapsedTime>(set-rise)){//Storms after sunset are always severe... it just looks too cool!
           LightningIntensity=3;
         }
         else {
         LightningIntensity=random(3,8);
         }
      }
      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 
}

//********************** 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);
}
DONT forget to post a link to your video of the cloud and storm effect here! I would love to see it on different set ups and see if its working well - I only had my tank to test it on!

Re: Weather Simulation for PWM expansion module

Posted: Tue Jun 26, 2012 5:46 pm
by DrewPalmer04
Video of yours? Once I get the expansion PWM I'll try this as well.

Re: Weather Simulation for PWM expansion module

Posted: Tue Jun 26, 2012 8:45 pm
by rufessor
Here is a short you tube video of a storm, its taken with an iPhone and obviously hand held..

http://youtu.be/jEOpuVZ__Ik

One note-- the current code is a bit more refined than the one I was running to take this video (3 weeks ago.. much has changed). I think now it looks even better. This would be a "Severe Storm" as I have termed it given the current capabilities of the code in that my whites are off, but its still not the same as a current severe storm.

Anyhow, to me the best part is the strike sequencing for lightning- I think it looks pretty realistic. One thing that I changed, because I did NOT like it... if you watch very carefully (focus on a dark part of the tank in the back on the wall.... you can see the intensity step down or up as an incremental flash. I kinda didn't notice this until I had the video... then I started to see it with my eyes and it was driving me nuts- it was not good enough.

The current code uses the same step size, and update frequency (300 msec for a storm 400 msec for a cloud) but to eliminate the "flicker" I wrote in subroutines that take the steps in the random walk of light intensity change and update it incrementally every 100 msec---its so much better. Now you CANNOT see the intensity steps, its completely natural looking no flicker no matter how faint. Look for this in the video and then see if you can see incremental intensity steps in the new program (the current code is posted). If you can see it I can make it a little faster but there is a limit to this as the Arduino processors are not noted for their floating point operations and ALL of this at some level requires floating point.

I have this video of a cloud, but somewhat to my surprise the iPhone video is apparently having trouble with these lights and the exposure just isn't working well... so it RUINS the effect - the video looks more or less like very little is happening in intensity variation in the tank, towards the end when I zoom in a bit you can kinda make out the effect a little better (a little bit)... but trust me- ITS REALLY COOL when your in the room. I will try to get a better video.. don't want to discourage any one from trying this because it looks so lame in this one :mrgreen:

http://youtu.be/XpmG90fF7Jc
If you watch, of all things, the WALL between the light canopy and the tank... you can see the intensity change and the incremental steps (this is now much better)...

I have a lot more fish... but they were all pissed at me and hiding because I had been testing this late night and turning the lights on and off... The tang is obviously pissed... he keeps hiding and being weird! Normally, the fish don't respond at ALL to the clouds or even the storms (I was surprised but they really don't do anything different... so long as I don't turn on the lights after bed TIME!) :twisted:

Here is a video of someone else took of a tank in South Africa running this code... again its an earlier version but this tank does not have an all blue channel... so its kinda cool, they configured it to shut down some of their lights during storms... their camera is doing a much better job of exposure compensation and its looking much better... they posted this on the development thread so I am sure its fine to link it here. Thanks again goes out to Jean for suffering through the debugging to get the rise and set algorithm to function correctly (my errors) in regions with East of the prime meridian!!! With their help running a few too many serial debugging sessions I was able to confirm that I have much to learn about C++ (but I do eventually come around).

http://youtu.be/1QwJo3AhRAQ

Re: Weather Simulation for PWM expansion module

Posted: Wed Jun 27, 2012 3:05 am
by phrusher
Looks really nice! Once I finish up my DIY LEDs I'll give it a try. Would it be possible to also integrate this with (analog tunze) streamers later on?

Re: Weather Simulation for PWM expansion module

Posted: Wed Jun 27, 2012 6:16 am
by DrewPalmer04
I think a break-down of what you are running would help. (Number, type, and drive method of LEDs) People will see different effects based on how many channels they are using on the PWM module. For example, two meanwell drivers versus eight, etc. I'm not sure about everyone else, but I'd like to know what/how many of which components you are running with the PWM module.

Re: Weather Simulation for PWM expansion module

Posted: Wed Jun 27, 2012 9:06 am
by rufessor
I have 5 channels with MeanWell ELN-p series drivers on channel 0-4 of the PWM expansion slot. My set up, and I posted a picture of the lights here at the end of this short thread.

http://forum.reefangel.com/viewtopic.php?f=15&t=576

Basically I built the LED's in what I am calling "PAR" groups. Each group and there are two on the left and two on the right.... are groups of 12 leds... so the PAR groups which are 4 clusters - 2 on the right and 2 on the left contain in total 48 leds. I am calling them PAR groups because I clustered the LED's AS CLOSE together as I could without actually touching the stars, there is like 2 mm of space between them. This puts a serious heat load on the head sinks so my heat sinks are black anodized and have CNC machined grooves along the fins on the back and I have speed controlled Stealth fans mounted in the hood blowing down onto the top of each heat sink. But because of the very tight clustering the tank kinda looks like its lit by 4 point sources and the shimmer is really sweet and the color blends are awesome.. but I would probably mix in some cool whites (maybe possibly) if I did it again... maybe...

Each group of 12 is made up of 7 Royal Blue 405 nm (I was VERY Careful to get this color bin) XP-E Cree, 4 Cree XPG Neutral Whites, and 1 XP-E neutral blue (kinda more to the green side).

These are then wired such that the right side group has an independent string of 8 neutral whites, and an independent string of 14 royal blues and the 2 neutral blues. Same for the left. Then I have a dispersed array of 8 True Violets on their own dimmer that cover the entire tank.

So..... Thats the set up, I guess if you read this and really focus on the tutorial you would be able to figure out pretty much EXACTLY how many LED's are being used for every single channel, strike, storm, and severe storm. My tank is a 57 gallon Oceanic Illuminata rimless and the canopy is suspended about 18 inches off the tank.. but the canopy is a custom built ARC so the lights actually are angled into the tank along the arc so the middle of the tank is a bit brighter than the edges... look at the pics in the thread I posted it will become clear why this is so.

OH..... I just looked.. .those pictures were before I even had the canopy suspended over the tank... so in them its just resting on some boards I wrapped in towels on the tank.. Totally Sweet Looking :D :D :D

Its WAY nicer now... I guess I should update that...

Re: Weather Simulation for PWM expansion module

Posted: Wed Jun 27, 2012 9:59 am
by rufessor
With respect to the Tznue... maybe Roberto or Kurt or someone with knowledge about this and integration to the main will reply.... but that would be controlled by the MAIN unless its some kinda PWM device... in which case I would still put it on the main. The PWM board is working fairly hard with this code, and once I get comm built in I suspect we will be getting up to the 60-70% capacity in terms of lines of code it can even handle... so for me I was basically going to use the PWM processor and work it hard to run these effects and comm but there is not going to be much overhead in terms of processor compute cycles or memory- so squeezing more in will be possible but severely limited especially if you end up needing to import larger library functions- thats not going to fit. Kurt or Roberto are your guys... I am guessing. If they don't catch this query post it elsewhere and you will get response.

But... if your asking... could the PWM module comm to the controller to tell it there is a storm... causing the controller to tirgger a storm mode for wavemaker functionality.... thats EXACTLY what I am working on now. So if thats the Q... Absolutely yes 100% will work. BUT ITS NOT ENABLED NOW. I will post new code here with comm when its fully fleshed out on the development thread where I have a few people sacrificing their systems to help me test this code on multiple builds... when we get it all figured out I will update the tutorial and post the code here.

One thing- once the comm is enabled its going to be entirely up to the end user to configure the Main to use the information my program will feed it. I am NOT the guy to help here, I will be starting this up from ground zero myself and posting questions to learn... if I have any experience its simply in learning to code this, the Main is a whole other animal and I will be learning with everyone else. BUT... There are some pretty smart people who can code at an entirely different level that have produced some great stuff there... so I intend to milk those tools and their expertise and copy paste code to get my main working, it should be relatively (relatively) easier to integrate on that end since so much work has all ready been done doing more or less exactly this... the only difference is the comm from the PMW board to the main... and a few other users are all ready doing this so were not building anything new just frankenstein coding.

Re: Weather Simulation for PWM expansion module

Posted: Wed Jun 27, 2012 10:08 am
by rimai
Tunze uses the channels just like lights :)
If you set the channel to 75%, it will put the pump at 75% :)
Pretty simple!!!
You will need the Dimming expansion module with Analog signal though and not PWM, but as far as code, it is treated just like lights.

Re: Weather Simulation for PWM expansion module

Posted: Wed Jun 27, 2012 11:23 am
by rufessor
Roberto-

How trivial would it be to convert this code to the analog dimming module? I think you would just have to change two lines... both are actually in the loop...


this for the strike...
if (StrikeChannel==1) analogWrite(PWMports,intensity);

and this for everything else...
analogWrite(PWMports[a],TrueIntensity[a]);

What would the new lines look like for analog dimming set ups?.... or is it more complex since your probably not scaling to a byte output...? If its using 0-100 as a output its a little weird but possibly we could just do this and have people still use the 0-255 settings for the ChMax and flicker arrays... just scale the end output to 0-100

TrueIntensity=TrueIntensity/25 and scale it to 0-100...

Re: Weather Simulation for PWM expansion module

Posted: Wed Jun 27, 2012 11:26 am
by rimai
No need to change anything in the code.
The convertion is done by hardware components.

Re: Weather Simulation for PWM expansion module

Posted: Wed Jun 27, 2012 11:43 am
by rufessor
AWESOME! Thanks!

Re: Weather Simulation for PWM expansion module

Posted: Thu Jun 28, 2012 1:03 am
by phrusher
rufessor wrote:But... if your asking... could the PWM module comm to the controller to tell it there is a storm... causing the controller to tirgger a storm mode for wavemaker functionality.... thats EXACTLY what I am working on now. So if thats the Q... Absolutely yes 100% will work. BUT ITS NOT ENABLED NOW. I will post new code here with comm when its fully fleshed out on the development thread where I have a few people sacrificing their systems to help me test this code on multiple builds... when we get it all figured out I will update the tutorial and post the code here.
Yes that is EXACTLY what I'm looking for :) I have analog dimmers on my main so I intend to plug that into Tunze streamers. Would be sweet if the weather could trigger different states of the pumps (ofcourse without overriding feed mode, night mode etc). I'm gonna head over to the dev thread and read up some more on your Weather sim.

Re: Weather Simulation for Dimming expansion module

Posted: Thu Jun 28, 2012 7:14 am
by rufessor
One word of caution on the development thread.... I started that with a vague idea that I wanted to use latitude and longitude to calculate sunrise but I had never coded in c++ before so its FILLED with starts, stops, errors, mis-statements, and most of all.... BAD code :lol: If your want to know what its doing in terms of where its at today the tutorial is *much* better and informative. If you want to watch, in slow motion, the train wreck of learning C++ and working on an increasingly complex project spread over 20 pages- its all there. Just beware that much of the first 1/2 of that thread is just me finding my way with no real clear idea what I was working on other than to learn C++ and get a library working that someone else wrote and that we ended up deciding was very good but not needed as a "library" so I just stuck the required code in the .INO rather than having to distribute .h and .cpp files... so IGNORE completely that part of the discussion.

Finally- to everyone... can you let me know how close your tank lights (those with a ZERO ChOffset) rise and set to the real sun. My tank was running within literally a minute or two and lately its more like 5-10 minutes... I know the functions error can be somewhat significant and this error increases with latitude and day length... so for me were at the ABSOLUTE MAX day length of the year and I suspect its just the same 1-2% error but instead of a 40,000 second day its so much longer that it just is more obvious.

Re: Weather Simulation for Dimming expansion module

Posted: Thu Jun 28, 2012 8:55 pm
by chort55
Ok I have my code for the lights ready to go, but I have not yet implemented it because I am not sure on how to set up the fans and drivers? Currently I have them set to come on when my lights come on through the "standard lights" channel on my 8 outlet RA box, but since with this code I am guessing that time will change daily is there a way to program the outlet to come on/off when it is "Sunrise"/ "Sunset" as I don't really want/ need my drivers or fans running 24/7

Re: Weather Simulation for Dimming expansion module

Posted: Fri Jun 29, 2012 7:32 am
by rufessor
I am going to assume your North of the equator and thus your day length is now at or very near its maximum. Until I get the comm module up, which would then allow your head unit to see that the lights are on and automatically turn on the fans (this should be like 3 lines of code on the Head unit).... what I would do is just instruct the head unit to turn on the fans at your local sunrise corrected to the earliest offset you have running and turn off after sunset + the latest offset you have running. Then... as we move to fall your fans will come on increasingly earlier than the lights but its no big deal... and really- the Comm should be fairly easy to implement and I was hoping to get some time on that this weekend (doubt its that fast... but it really should not take very long all the code to do this exists and I have expert help).

This will get your lights up on the new code and ensure they stay cool.. I need to get this working for myself as my fans have been on 24/7 for a few months now (but really... they are case fans for a computer and this is pretty normal for them). The other option is just to leave them on (if this is easier) I doubt it will matter one iota as we should have comm up very soon.


Curious... did you use the tutorial and did it have enough information to do this configuration easily? Does it make sense?

Re: Weather Simulation for Dimming expansion module

Posted: Sat Jun 30, 2012 9:19 pm
by chort55
rufessor wrote:I am going to assume your North of the equator and thus your day length is now at or very near its maximum. Until I get the comm module up, which would then allow your head unit to see that the lights are on and automatically turn on the fans (this should be like 3 lines of code on the Head unit).... what I would do is just instruct the head unit to turn on the fans at your local sunrise corrected to the earliest offset you have running and turn off after sunset + the latest offset you have running. Then... as we move to fall your fans will come on increasingly earlier than the lights but its no big deal... and really- the Comm should be fairly easy to implement and I was hoping to get some time on that this weekend (doubt its that fast... but it really should not take very long all the code to do this exists and I have expert help).

This will get your lights up on the new code and ensure they stay cool.. I need to get this working for myself as my fans have been on 24/7 for a few months now (but really... they are case fans for a computer and this is pretty normal for them). The other option is just to leave them on (if this is easier) I doubt it will matter one iota as we should have comm up very soon.


Curious... did you use the tutorial and did it have enough information to do this configuration easily? Does it make sense?
Well, no rush for me on the comm, I managed to knock out my blues on 2 of 3 panels (the two I haven't already replaced lol), so I am waiting on uploading this code and making any other changes anyways until I can get the new ones and replace them... however I did follow the tutorial and get the code set (before my mishap) and ready to be implemented and it was straightforward and easy to follow! And thats coming from a guy who has 0 coding knowledge! I left the code at the Utah location as I figure it will give me a later rise/set and I like to run my lights late (they currently shut off at 11:59 pm ;) ).

Thanks for taking the time to not only get this put together but for taking the time to explain it as well! I can't wait to actually get to see it in action lol

Re: Weather Simulation for Dimming expansion module

Posted: Sun Jul 01, 2012 10:17 am
by JNieuwenhuizen
This code has nw been runningin my tank for over a month, and I must say its the best natural light simulation I have seen to date.

The YouTube video woth the lightning and clouds is on my tank, and even my crals look healtier with the natural sunrise/sunset.

I must agree, taking the time to set this up is no small ting, if there were award buttons or something i'd click it a few times!

Re: Weather Simulation for Dimming expansion module

Posted: Mon Jul 02, 2012 10:29 am
by rufessor
Thanks! Glad its working out and your liking the effects.


With respect to using a different latitude and longitude to achieve a day length that is offset from your local times. Lets say you live in New York City and your time zone is 4 hours ahead of someone living off the coast of california.... so you input their latitude and longitude and expect that the sun would then rise at 11 am local to you (because that would be the correct time for it to rise at 7 am over the eastern pacific using their local time)... seems like this is how it would work. But ITS NOT.

I correct all times such that the local rise and set time of any latitude and longitude on the planet is converted to your local time. E.G. If its supposed to rise at 8 am and set at 10 pm (in local time for the latitude and longitude you entered) its going to rise and set at 8 am and 10 pm according to YOUR clock. This makes it VERY EASY to get the yearly day length variation you desire for your tank without having to convert time zones. It makes it impossible to do what you had asked. Trust me when I say that you do NOT want to try messing with all the conversions to get it to do what you want... its just too hard and then we could only realistically use locations very close to our house. This way, we can use anywhere on the planet and its like you actually MOVED there and are living at that point using your local times as their local times.

To make it rise or set later in the day, just use the ChOffset array and add 3 hours (or whatever to all the values)... this is the only way to shift the day... but its also very easy.

ONE THING. The DAY ends or starts (depending on how you look at it) at MIDNIGHT. So... if you use ChOffset values to push sunset past midnight... it aint gonna work. At MIDNIGHT, a new day worth of rise and set times are calculated and everything starts again. So, at 12:01 am, its going to see that the sun is not yet risen and your tank will go dark regardless of the fact that you may have told it to set 5 hours late which should have been 1 am... its a new day and all calculations are reset. I keep track of time ONLY as elapsed seconds since the new day... so if your set value is greater than 84600 (24 hours) the counter will RESET to ZERO at midnight and the tank will go immediately dark (or to MoonPhase lighting) and wait for the elapsed seconds to count up to the rise time... and then rise... and then when midnight hits again... rinse and repeat.

The ONLY way you could know that your set value was greater than 84600 is to put in some serial debugging and figure it out... or (much easier) simply look up what the local rise and set times are for the latitude and longitude you choose, check what the LATEST set time is for the year and make sure that your ChOffset does not push that value to greater than 24 hours for ANY channel.

The even easier way to know this is when I get the serial comm up and you can display rise and set for every channel given the offset you used, on the main... but I am kinda trying to figure out if we have enough room to do this... cause with 8 channels that pretty much maxes out the portal in terms of custom display... and if we use all 8 for individual channel values we cannot do cool stuff like tell us if its currently cloudy, stormy, a severe storm, tell us how many clouds are left, etc etc... so I am working on kinda coming up with the optimal 8 things to show. Probably we will end up with Rise (actual calculated), Set, #Clouds Remaining, is Storm (yes/no), is SevereStorm (yes/no), MoonPhase, and then probably some way of MAKING a cloud or a storm on demand.

I think people will probably want different things... so I will get together the stuff I WANT... and release the code when its working for me. I will release the .INO for my controller (relevant display and comm parts) and the .INO for the dimming module. My guess is that you will want it to be different than mine... so that part will be up to you, but I should be able to provide a working frame work. When we get there... I am going to have to back off a tiny bit in terms of helping people customize this as its also new to me... so if your going the custom route just realize that your in fact going to have to work it out. I am sure that help will come if your post questions but once we get there, people with far more experience than I in terms of integrating wave makers with a storm etc will have to chime in cause I will just cause confusion and delay at that point. It appears that we will have a decent number of users some assuredly have experience here that I lack.

Re: Weather Simulation for Dimming expansion module

Posted: Mon Jul 02, 2012 2:15 pm
by rufessor
One quick proviso...

I *think* this is working as intended.. the whole part about converting everything to local times... but please LET ME KNOW if things are acting weird. I got it working and heard from others in very different locations that it was working... so I would bet its 100% robust but I would not bet much :mrgreen:

Finally- I edited the tutorial to include a more condensed (and perhaps even easier to understand) summary of the above discussion. Please re-read that if your *at all* confused about how the Lat Lon calculations are set up. Its actually easy, everything gets converted to YOUR TIME. But its all there in the tutorial now.

Re: Weather Simulation for Dimming expansion module

Posted: Tue Jul 31, 2012 8:56 am
by rufessor
All right... its been a while with lots of views here and the tutorial-

-Would be curious- if you have this up and running can you reply here to let me know. I suppose its either working so well no one has issues and were good... or perhaps there is little interest. My tank has been stable for over a month now, I am waiting for the sun to start changing again so I can be sure its tracking it correctly so if your running it a few comments with regard to the timing of the tank sunrise and set v.s. your window would help me assess this.

THanks!

Re: Weather Simulation for Dimming expansion module

Posted: Thu Aug 02, 2012 9:14 am
by adrynapoletano
Hello I would like to have your help for the setup program.
I understood very little. I built an LED lamp white and royal-blue. There are 8 blocks of LEDs.
Only one block is composed of white LEDs (W) and blue (B) distributed in this way [3W-2B-2B-3W-3W].
The white LEDs are operated by drivers 8
The blue LEDs are operated by 4 drivers.
I do not have any expansion pwm, but check the lamp with the two channels of PWM reefangel. So I can say that I have two channels?
If so how and what should I set in the file. Ino
thanks

Re: Weather Simulation for Dimming expansion module

Posted: Thu Aug 02, 2012 3:04 pm
by rufessor
Unfortunately you cannot currently use the program as I have been neglecting putting in the code to enable it to control dimming channels on the reef angel controller itself. Another reason you cannot use the code, is that it ABSOLUTELY requires you have a dimming expansion module, period. I doubt you could fit this code on your controller and have room enough for anything else really. Sorry!

Re: Weather Simulation for Dimming expansion module

Posted: Sat Aug 04, 2012 1:33 pm
by TanksNStuff
Forgive my ignorance, but I have a couple quick questions that need to be answered before I can attempt to test this code.

1. Are we supposed to be uploading this code directly to the Dimming Module or just including it in our main controllers coding?

2. If we're uploading directly to the Dimming Module... do we need any special cables? Do we just hook up a USB cable to it?

2. If we are including it in the main controllers code... does it matter where we add our own personal function coding (ie., custom main screen, custom menus, feeding schedules, RF Module control, etc) in relation to your weather simulation coding? Is there anything that needs to be first (or last) thing in the overall coding?

Thanks in advance. I read this entire thread and the entire "Tutorial" thread you linked and neither one explained anything about where the code gets uploaded to or how to combine it with our own stuff.

The only reason I thought of this question is this paragraph in your tutorial:
As it exists today, the code runs as a stand alone software for the PWM OR ANALOG dimming expansion module. Although I use PWM, Roberto tells me that there is no difference between them aside from the output signal type so the code will work identically on either. By stand alone I mean that it completely takes over your PMW expansion module- it runs independently of the Main controller- it doesn't even acknowledge the fact that the main controller exists. This will evolve as we work to enable some comm etc but its not working now. It simply runs your lights, thats it- period. Sounds like its not doing much? Read on...
Anyway, I'm hoping to get this running this weekend so hopefully you or someone who has used this code can answer me.

Re: Weather Simulation for Dimming expansion module

Posted: Mon Aug 06, 2012 1:00 pm
by rimai
Yes, you need to load into the dimming module. You can use the same cable you use to program RA.
http://i761.photobucket.com/albums/xx25 ... 191505.jpg

Attached is the original code if you want to revert back.

Re: Weather Simulation for Dimming expansion module

Posted: Mon Aug 06, 2012 1:40 pm
by TanksNStuff
Oh, I see. I need to take it apart and connect it inside? Gotcha.

Thanks Roberto!

Re: Weather Simulation for Dimming expansion module

Posted: Tue Aug 07, 2012 7:23 am
by TanksNStuff
Well, I uploaded this code (after editing all the required lines as per the tutorial) last night and it didn't appear to work as intended. Granted, I only tested it by changing the time on the controller as a means of testing when the lights would come on/off.

I'm going to try it again next weekend when I can be home to watch it in real time after a natural reset of time on the clock.

Here are a couple observations I made while trying it:

1. After you upload the code (if it's not already disconnected) unplug and then reconnect the USB cable going into the Dimming Module. Until you do that, it appears that the Dimming Module remains the same as it was before changing the code on it. I'm guess that this sort of reboots the board on the module?

2. Here's the test I did and my results:

Since I set the lat / long for my house, I looked up the sunrise/sunset times for my house and found that sunrise should have been 6:04 AM and sunset was 8:09 PM. While testing this by changing the clock time on my controller, I first set it to a time that I knew it should have lights - 11:00 AM.

No lights came on at all. After taking a quick peak at the android app, I noticed that since it was after my normal lighting period, the relay ports for my LED's were turned off. I manually turned them on with the Android App and all the lights went on. At this point, I thought everything should be working great!...

So, my next test idea was to change the clock time to an earlier time when I knew they should be off (4:00 AM). My plan was to find the sunrise point that the code was calculating to see if it was close to my actual time per the table I looked up. I was going to first increase in 1 hr intervals until lights popped on, then go back 1 hr and then increase the minutes until I got it to pop on. Sounds like a solid plan right?

To my surpise, the lights actually stayed on at 4:00 AM (whites and blues) and didn't go off at all. I think this is when I discovered that I had to unplug/replug the USB cable to reset the dimming module and then all lights went off after that. I found this by just dumb luck while staring at the module, lol.

Then I increased the clock 1 hour to 5:00 AM and lights came on again (even though they shouldn't have for at least another hour). They also appeared to be on 100% (or very bright at least, not at the 10% start intensity I was expecting.) I was very confused at this point and thought that maybe I had just used the wrong lat/long and it was just working on another places settings.

At this point, I opted to increase the clock time to see if it would get even brighter (even though I thought it was already at or close to 100%). No change was noticed after going up in 1 hour intervals until 11 AM.

Now, I decided to change the clock back to 5 AM, lights stayed the same intensity. Then went back to 4 AM, still the same. At this point, I unplugged/reconnected the USB again and lights went out again.

I began ramping up the clock 1 hour at a time again and at one point I let the clock sit idle and watched for about 5-10 minutes. (I think it was around the 9 AM clock change, but I'm not positive.) There was sort of a random dimming going on and I could see what appeared to be the "cloud" feature. It didn't appear to flow from my left fixture to my right (as I had set in the variables to be changed). It was more like each light was randomly dimming/brightening/dimming/brightening/etc.... This went on for several minutes repeatedly. I should have watched longer to see if it would develop into a storm and possibly see lightning, but it was getting late at this point and I needed to get to bed.

3. I then reverted my dimming module back to the original code that Roberto provided. This morning, everything was back to normal as per my slope settings in my controller's .ino so I'm happy this test was no harm, no fowl.

While writing this post, I just had a thought that the last observation of the lights dimming/brightening repeatedly could possibly have been lightning, but maybe the "flash" interval was just set really slow? Or, perhaps my analog drivers just don't react as quickly as a PWM driver would? That's something I'll play with this weekend too.

Another thought I had was maybe my controller's slope coding had interfered with the weather sim code on the dimmer module all the while? Next time I test, I'll just comment out my dimming code on the controller and reupload before changing the code back on the dimmer module.

Anyway, hope that helps anyone else trying this that may have similar results.

Anyway, that's all I have for now. I'll report back over the weekend after I try this again.

Re: Weather Simulation for Dimming expansion module

Posted: Tue Aug 07, 2012 8:01 am
by rimai
Did you reboot the dimming module everytime you changed the time?
I think the dimming only syncs with the time on reboot.

Re: Weather Simulation for Dimming expansion module

Posted: Tue Aug 07, 2012 9:03 am
by TanksNStuff
Not every time, no. After a while, I kind of figured that was the issue... but I wasn't sure if the module just checked for the time read from the controller on every cycle of the loop?

If it only syncs time on reboot then that could explain a lot of the issues I was having.

On a side note, is there a way to see what the sunrise / sunset output values the dimming module would currently be using? I'd like to know what the unit is using after calculating the formula using my lat/long values and the current date/time. Any way I can simulate running the code in Arduino (before I upload it) and have it give me the sunrise / sunset values that the module would be using at the current time?

Re: Weather Simulation for Dimming expansion module

Posted: Tue Aug 07, 2012 9:31 am
by rimai
Yeah, you can load it in a regular arduino board and place some Serial.println() calls.
Rufessor would be best to tell where to put the calls though.