Re: C library for Sun/Moon effects
Posted: Tue Feb 28, 2012 12:32 pm
Yeap. You got the idea.
That's what the CheckCloud() function does.
That's what the CheckCloud() function does.
Community discussion about Reef Angel Controllers and reefing related subjects
https://forum.reefangel.com/
Code: Select all
// Reef Angel Includes
#include <Time.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <Globals.h>
#include <ReefAngel_Features.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <avr/wdt.h>
#include <SWFLTEK_EPHERMA.h>
//Defines for ReefAngel PWM module
#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5
//*********************************************************************************************************************************
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
// long latitude, longitude;
unsigned long sunrise;
unsigned long sunset;
long newDay;
int midDay;
int ChRiseSet[12];
int ChSlope[12];
byte dst;
byte PWMports[] ={
3,5,6,9,10,11};
byte ChannelValue[6];
byte ChInt[6];
byte SeasonsVar[]={
0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, sunrisehour, sunriseminute, sunsethour, sunsetminute, cloudstarthout, cloudstartminute, cloudduration,lightningchance, PWM1,PWM2,PWM3
byte cmdnum=255;
byte datanum=255;
byte ForceCloud=false;
boolean trigger;
byte hours, minutes;
boolean isDST, cloud, storm;
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Setup
void setup()
{
Serial.begin(57600);
Wire.begin(8);
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
randomSeed(analogRead(0));
pinMode(3,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
//wdt_enable(WDTO_1S);
setSyncProvider(RTC.get); // the function to get the time from the RTC
setSyncInterval(SECS_PER_HOUR); // Changed to sync every hour.
now();
trigger=true;
}
//End Setup
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Loop
void loop()
{
wdt_reset();
//Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255)
{
ProcessCMD(cmdnum,datanum);
cmdnum=255;
datanum=255;
}
// functions called from loop
//CheckCloud();
CalSun();
Insolation();
//Weather(cloudtoken, stormtoken);
//need to write function somewhere that does random generation of cloud time (10 min-60 min)and indexes # for day
for (byte a=0;a<6;a++)
{
analogWrite(PWMports[a],ChannelValue[a]);
}
/* Serial.println("DST");
Serial.println(isDST);
delay(500);
Serial.println("trigger");
Serial.println(trigger);
delay(500);
Serial.println("newDay");
Serial.println(newDay,DEC);
delay(500);
Serial.println("sunrise");
Serial.println(sunrise,DEC);
delay(500);
Serial.println("sunset");
Serial.println(sunset,DEC);
Serial.println("now()");
long secon;
secon=now();
Serial.println(secon,DEC);
delay(1000);
*/
}
//End Loop
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
void receiveEvent(int howMany) {
wdt_reset();
if (howMany==5)
{
byte cmd1, cmd2, cmd3, cmd4, cmd5;
cmd1=Wire.read();
cmd2=Wire.read();
cmd3=Wire.read();
cmd4=Wire.read();
cmd5=Wire.read();
if (cmd1=='$' && cmd2=='$' && cmd3=='$')
{
cmdnum=cmd4;
datanum=cmd5;
//Serial.println(cmd4,DEC);
//Serial.println(cmd5,DEC);
}
}
else
{
for (int a=0;a<howMany;a++)
{
Wire.read();
}
}
}
void ProcessCMD(byte cmd, byte data)
{
wdt_reset();
// Individual Channel
if (cmd>=0 && cmd<=5)
{
ChannelValue[cmd]=data;
analogWrite(PWMports[cmd],data);
}
if (cmd==6) ForceCloud=true;
}
//End Standard Functions
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
SeasonsVar[0]=ChannelValue[0];
SeasonsVar[1];
SeasonsVar[2];
SeasonsVar[3];
SeasonsVar[4];
SeasonsVar[5];
SeasonsVar[6];
SeasonsVar[7];
SeasonsVar[8];
SeasonsVar[9];
SeasonsVar[10];
SeasonsVar[11];
Wire.write(SeasonsVar,12);
}
//End Send Data
//*********************************************************************************************************************************
// Function to run Epherma Calc for sunrise/sunset/noon/moon phase
boolean CalcDST(byte D, byte M, byte dow)
{
//January, february, and december are out.
if (M < 3 || M > 11)
{
dst=false;
}
//April to October are in
else if (M > 4 && M < 11)
{
dst=true;
}
else
{
int previousSunday = D - dow; // Sunday=1 Sat=7
if (M==3 && previousSunday > 7)
{
dst=true;
}
else if (M==11 && previousSunday<=0)
{
dst=false;
}
}
return (dst);
}
void CalSun()
{
if (trigger==false)
{
return;
}
// Every day at midnight, we calculate sunrise, sunset, time of highest sun, moon phase, or we do this if system has reset
if ((hour()==0 && minute()==0 && second()==0) || (trigger=true))
{
trigger=false;
// Arduino uses Unix time- seconds since Jan 1 12:00 am UTC 1970
//Must convert to MST by adding 7 hrs =7*3600=25,200 seconds MST=GMT-7
//Must get daylight savings rules into effect or sunrise set will change by 1 hour
// In D, M, dow;
// boolean isDST=CalcDST(day(),month(),weekday());
isDST=CalcDST(day(),month(),weekday());
//Using time library from Arduino.time_t now(); returns seconds since 1970
//Library states #seconds to subtrace to bring to Jan 1 2000 as origin for time is
//#define SECS_YR_2000 (946684800) the time at the start of y2k
//DST rules are march second sunday until november 1st sunday + 1 hr starting at 2 am which I dont care about so that part is neglected
long SecInput;
if (isDST==true)
{
time_t t=(now()+3600);
hours=hour(t), minutes=minute(t);
if (hours!=0 || minutes!=0)
{
newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
SecInput=newDay+25200;
}
else
{
newDay=(now()-(946684800+3600));
SecInput=newDay+25200;
}
}
else if (isDST==false)
{
time_t t=now();
hours=hour(t), minutes=minute(t);
if (hours!=0 || minutes!=0)
{
newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
SecInput=newDay+25200;
}
else
{
newDay=(now()-946684800);
SecInput=newDay+25200;
}
}
//Calculate Latitude and Longitude converting from Degree, Minute, Seconds to seconds
latitude=dmsToSeconds(40,45,39); //Set to about the latitude of the great barrier reef rotated into the morthern hemisphere
longitude=dmsToSeconds(-111,53,25); //Set to Salt lake City, or MST zone, USA or there abouts- actually the AT921 remote weather station in Salt Lake City UT. Random web grab.
//Calculate sunrise time and sunset time
sunrise=sunset=SecInput;
SunRise(&sunrise);
SunSet(&sunset);
// Correct sunrise data to reflect elapsed time from midnight (newDay)
// DST correction is all ready in both variables so no need to deal with it
sunrise-=25200; //Correct back to MST from GMT- DST still accounted for
sunset-=25200;
sunrise-=newDay;//set to elapsed seconds today
sunset-=newDay;
/*offsets for sunrise/sunset with colors all values in seconds offset from calculated sunrise or sunset value
set up array with order ch0 am offset, pm offset, ch1 am offset, pm offset etc (negative to rise earlier/set earlier*/
int Choffset[]={
-600,0,-3600,5400,0,600,-3600,5400,-4500,7200,0,0};
//Calculate rise and set times for all channels in equivlants to elapsed seconds from midnight today
//populate array for chRise and Set as well as chSlope for 0.5pi/ half day lenght for each channel from midday (asymmetric days are allowed)
unsigned int deltaY=2/3.141592653589793238462643383279502884197169399;
midDay=(sunset-sunrise)/2;
for (byte b=0;b<12;b++)
{
if (b%2==0)
{
ChRiseSet[b]=sunrise+Choffset[b];
ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
}
else if (b%2==1)
{
ChRiseSet[b]=sunset+Choffset[b];
ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
}
}
}
}//END FUNCTION
void Insolation()
{
// Calculate time since day started correcting for DST
int elapsedtime;
if (dst==true)
{
elapsedtime=(now()+3600-newDay);
}
else
{
elapsedtime=(now()-newDay);
}
// Channels for me are 0=RW 1=RB 2=LW 3=LB 4=Whole tank V use in order to assign flicker points
int flicker[]={
10,10,10,10,5,10};
//What are the max values for ch intensity we want to reach use 0-255 scale for PWM board byte output
ChInt[210,225,210,225,220,0];
unsigned int deltaY=3.141592653589793238462643383279502884197169399;
/* using -cos(pi/2+elapsedtime/slope)*((ChMaxVal-flicker)+flicker) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light. Turn off lights after sunset or before sunrise etc.
by splitting into half days centered on midday (1/2 ofsunset-sunrise) we center exactly the cos function for every channel so color blends are maintained
throughout intensity ramp... more or less ... change intensity every 30 seconds throughout the day*/
if ((elapsedtime%30)==0)
{
for (byte b=0;b<12;b++)
{
if ((b%2==0) && (elapsedtime>=ChRiseSet[b]) && (elapsedtime<midDay))
{
if (b==0)ChannelValue[0]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b])+flicker[b]));
else if (b==2) ChannelValue[1]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-1]-flicker[b-1])+flicker[b-1]));
else if (b==4) ChannelValue[2]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-2]-flicker[b-2])+flicker[b-2]));
else if (b==6) ChannelValue[3]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-3]-flicker[b-3])+flicker[b-3]));
else if (b==8) ChannelValue[4]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-4]-flicker[b-4])+flicker[b-4]));
else if (b==10) ChannelValue[5]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-5]-flicker[b-5])+flicker[b-5]));
}
else if ((b%2==1) && (elapsedtime<=ChRiseSet[b]) && (elapsedtime>=midDay))
{
if (b==1)ChannelValue[0]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-1])+flicker[b-1]));
else if (b==3) ChannelValue[1]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-2])+flicker[b-2]));
else if (b==5) ChannelValue[2]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-3])+flicker[b-3]));
else if (b==7) ChannelValue[3]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-4])+flicker[b-4]));
else if (b==9) ChannelValue[4]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-5])+flicker[b-5]));
else if (b==11) ChannelValue[5]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-6])+flicker[b-6]));
}
else if (((b%2==0) && (elapsedtime<ChRiseSet[b])) || ((b%2==1) && (elapsedtime>ChRiseSet[b])))
{
if ((b==0) || (b==1)) ChannelValue[0]=0;
else if ((b==2) || (b=3)) ChannelValue[1]=0;
else if ((b==4) || (b=5)) ChannelValue[2]=0;
else if ((b==6) || (b=7)) ChannelValue[3]=0;
else if ((b==8) || (b=9)) ChannelValue[4]=0;
else if ((b==10) || (b=11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
}
}
}
}
// cloud function allowing for cloud passing over tank and random walk dimming in all channels
//also include storm, dimming whites with lightning as random chance during day
//set up void function to accept array input of current channel intensity set and to set actual channel intensity and to accept cloud or storm toggles from menu as boolean true to start them
void weather (boolean cloud, boolean storm)
{
//for storm shut off white use 1 to designate channel as white or a channel you want off during storm array is channel (0,1,2,3,4,5)
boolean Wchannel[]={1,0,1,0,0,0,0};
if ((cloud=false) && (storm==false))
{
return;
}
else if ((cloud=true) && (storm=false))
{
static int level; // variable to store value in random walk - declared static to accumulate cloud effect
byte low[]={30,75,30,75,125,0}; //lowest allowed channel intensity during a cloud, triggers a storm if channel is whites.
//use ChannelValue[] a=0-5 for high point of random walk constrain
//Cloud function
level = level + (random(-4, 4));
for (int a=0;a<6;a++)
{
if ((ChannelValue[a]-level)>ChInt[a])
{
level=abs(level);
ChannelValue[a]=(ChannelValue[a]-level);
}
else if ((ChannelValue[a]-level)<(low[a]))
{
if (Wchannel[a]=true)
{
storm=true;
}
int sign=-1;
level=(level*sign);
ChannelValue[a]=(ChannelValue[a]+level);
}
}
}
//as written it will never be first case, but might be nice to have menu on controller allowing for storm selection
else if (((cloud=false) && (storm=true)) || ((cloud=true) && (storm=true)))
{
for (int a=0;a<6;a++)
{
if (Wchannel[a]=true)
{
ChannelValue[a]=0;
}
}
//now I need to adopt something like a random number strike generator for lighting about 30-100 ms long
// unsure where to put this as ch intensity is written to PWM board in loop.. so I dont know if
//delay executed here would work...
}
}//End Function
Code: Select all
//CheckCloud();
CalSun();
Insolation();
//Weather(cloudtoken, stormtoken);
Code: Select all
if ((cloud=false) && (storm==false))
{
return;
}
Code: Select all
void Weather ()
{
boolean Wchannel[]={1,0,1,0,0,0,0}; //for storm shut off white use 1 to designate channel as white or a channel you want off during storm array is channel (0,1,2,3,4,5)
static int cloudCover; // variable to store value in random walk - declared static to accumulate cloud effect
static long timeStart;
byte maxDuration; //(max duration of a cloud in minutes- SET THIS TO YOUR DESIRED VALUE THERE ARE NO CONSTRAINTS but if you go over 255 switch type to int also change elapsedTime to int)
static byte cloudsTotal;
byte cloudsMax=3;//Total number of clouds allowed for a day as MAXIMUM
static boolean cloudStart;
// see insolation elapsed time function for assigmnent of cloud start times and set of cloud=true
if ((cloud==false) && (storm==false) || (cloudsTotal>=cloudsMax))
{
timeStart=now();
cloudStart=false;
return;
}
else if ((cloud==true) && (storm==false) && (cloudsTotal<=cloudsMax))
{
byte elapsedTime;
elapsedTime=((now()-timeStart)/60);
// check to see if we have started this cloud yet if not pick a random duration and initalize cloud else keep it going until times up
if (cloudStart==false)
{
maxDuration=random(10,40);//set (min,max) minutes any single cloud can exist. The longer it exists the more likely a storm will occur\
cloudStart=true;
}
if (elapsedTime>=maxDuration)
{
cloud=false;
storm=false;
cloudsTotal+=1;
cloudStart=false;
return;
}
/*Use fractional intensity to set minimum value for any channel its WAY to confusing to try to set up otherwise. Dimming is proportional to actual intensity output
and constrained by flicker point. Random walk uses static variable "CloudCover" constrained to 0-100 to represent fractional intensity (i.e. (CloudCover/100)*Insolation SetPoint
is how the current cloud intensity is set*/
byte stepsize=random(-10,10);
cloudCover = cloudCover + stepsize;
if (cloudCover>=80) //dont dim below 20% max value as this will almost certainly hit flicker point on most lights especially given insolation pattern
//although it is checked and reset, this will keep cloud in a "visual" range of values and not bottom it out and trigger a storm
{
storm=true;
cloudStart=false;
timeStart=now();
cloudsTotal+=1;
}
else if (cloudCover<=0)
{
cloudCover-=(stepsize*1.5);
}
for (int a=0;a<6;a++)
{
ChannelValue[a]=(ChannelValue[a]*(1-(cloudCover/100)));
if (ChannelValue[a]<=flicker[a])
{
ChannelValue[a]=flicker[a];
storm=true;
cloudsTotal+=1;
cloudStart=false;
timeStart=now();
}
}
}
//as written it will never be first case, but might be nice to have menu on controller allowing for storm selection
// set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
else if (((cloud=false) && (storm=true)) || ((cloud=true) && (storm=true)))
{
byte elapsedTime;
elapsedTime=((now()-timeStart)/60);
// check to see if we have started this storm yet if not pick a random duration and initalize storm else keep it going until times up
if (cloudStart==false)
{
maxDuration=random(5,15);//set (min,max) minutes any single storm can exist. Remember this is on top of what might have been a long lived cloud and is additive
cloudStart=true;
}
if (elapsedTime>=maxDuration)
{
cloud=false;
storm=false;
cloudStart=false;
return;
}
// step through intensity values
byte stepsize=random(-15,15); //bigger changes during a storm for more chaos... play with this but careful you can make a strobe
cloudCover = cloudCover + stepsize;
for (int a=0;a<6;a++)
{
if (Wchannel[a]=true)
{
ChannelValue[a]=0;
}
else if (ChannelValue[a]<=flicker[a])
{
ChannelValue[a]=flicker[a];
}
//*******************
//THIS IS WHERE I WANT TO PUT THE STRIKE CODE
}
}
}//End Function
Code: Select all
void loop()
{
CalSun();
if ((second()%15)==0)
{
Insolation();
if (storm==true || cloud==true)
{
Weather();//need to add 2 way comm to force cloud/storm from controller
}
}
if (strike) ProcessStrike();
Code: Select all
declare as global..
int *CloudPoint;
Then in function calcSun().
static int CloudMaster[15];// Set up array to hold start and end times for clouds for the CloudPoint=&CloudMaster[0];//use address of pointer to find cloud data from other function
and then in function Weather() using a for loop jump through the start and end times in the array using the pointer
for (byte a=0; a<=(CloudsTotal-1); a+2){
if (((elapsedTime>=CloudPoint[a]) && (elapsedTime<=CloudPoint[a+1])) || ForceCloud==true){
CloudEnd=(CloudPoint[a+1]-120);
Cloud=true;
Code: Select all
// Reef Angel Includes
#include <Time.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <Globals.h>
#include <ReefAngel_Features.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <avr/wdt.h>
#include <SWFLTEK_EPHERMA.h>
//Defines for ReefAngel PWM module
#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5
//*********************************************************************************************************************************
//IF YOU DO NOT WISH TO USE the U.S.A. Day Light Savings Time rules to modify the time stamp for sunrise/sunset calculations...set to false- i.e. if you dont live in the USA etc
boolean ApplyDST=true;
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long sunrise;
unsigned long sunset;
long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
int ChRiseSet[12];
int ChSlope[12];
int midDay;// exactly 1/2 way between sunrise and sunset, i.e. my take on solar noon.
byte PWMports[] ={
3,5,6,9,10,11};
byte ChannelValue[6];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)
//What are the max values for ch intensity we want to reach, i.e. at and around solar noon- use 0-255 scale for PWM board byte output (ch0,1,2,3,4,5)
//i.e. 100% channel 0, ~10% channel 1, 40% channel 3 would be {255, 25, 102, ...etc}
byte ChInt[]={
210,225,210,225,220,0};
// At what point in PWM dimming do your individual channels flicker, input this as 0-100% PWM output value into the array (ch0,1,2,3,4,5)
byte flicker[]={
10,10,10,10,5,0};
byte SeasonsVar[]={
0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, sunrisehour, sunriseminute, sunsethour, sunsetminute, Cloudstarthout, Cloudstartminute, Cloudduration,lightningchance, PWM1,PWM2,PWM3
byte cmdnum=255;
byte datanum=255;
boolean trigger; //used a few places as a switch to ensure things only run 1x when needed
boolean isDST, Cloud, Storm, CloudToday, ForceCloud; // what they seem, is it DST?, Are we in a cloud?
byte CloudsTotal;//required to know how large the array for CloudMaster will be- I need to use Vectors.... I know.
int *CloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Setup
void setup()
{
Serial.begin(57600);
Wire.begin(8);
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
randomSeed(analogRead(0));
pinMode(3,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
//wdt_enable(WDTO_1S);
setSyncProvider(RTC.get); // the function to get the time from the RTC
setSyncInterval(SECS_PER_HOUR); // Changed to sync every hour.
now();//why are you here?
CloudToday=false;
trigger=true;
ForceCloud=false;
}
//End Setup
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Loop
void loop()
{
wdt_reset();
//Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255){
ProcessCMD(cmdnum,datanum);
cmdnum=255;
datanum=255;
}
// now run through sunrise/sunset calculation and then intensity set and finally weather overlay
CalSun();
Insolation();
Weather();
Strike();//need to add 2 way comm to force Cloud/Storm from controller
// write final ChannelValue[a] setpoints from Insolation or as modified by Weather overlay
for (byte a=0;a<6;a++){
analogWrite(PWMports[a],ChannelValue[a]);
}
/* Serial.println("DST");
Serial.println(isDST);
delay(500);
Serial.println("trigger");
Serial.println(trigger);
delay(500);
Serial.println("newDay");
Serial.println(newDay,DEC);
delay(500);
Serial.println("sunrise");
Serial.println(sunrise,DEC);
delay(500);
Serial.println("sunset");
Serial.println(sunset,DEC);
Serial.println("now()");
long secon;
secon=now();
Serial.println(secon,DEC);
delay(1000);
*/
}
//End Loop
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
void receiveEvent(int howMany) {
wdt_reset();
if (howMany==5){
byte cmd1, cmd2, cmd3, cmd4, cmd5;
cmd1=Wire.read();
cmd2=Wire.read();
cmd3=Wire.read();
cmd4=Wire.read();
cmd5=Wire.read();
if (cmd1=='$' && cmd2=='$' && cmd3=='$'){
cmdnum=cmd4;
datanum=cmd5;
//Serial.println(cmd4,DEC);
//Serial.println(cmd5,DEC);
}
}
else{
for (int a=0;a<howMany;a++){
Wire.read();
}
}
}
void ProcessCMD(byte cmd, byte data)
{
wdt_reset();
// Individual Channel
if (cmd>=0 && cmd<=5){
ChannelValue[cmd]=data;
analogWrite(PWMports[cmd],data);
}
if (cmd==6){
Storm=true;
}
}
//End Standard Functions
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
SeasonsVar[0]=ChannelValue[0];
SeasonsVar[1];
SeasonsVar[2];
SeasonsVar[3];
SeasonsVar[4];
SeasonsVar[5];
SeasonsVar[6];
SeasonsVar[7];
SeasonsVar[8];
SeasonsVar[9];
SeasonsVar[10];
SeasonsVar[11];
Wire.write(SeasonsVar,12);
}
//End Send Data
//*********************************************************************************************************************************
// Function to run Epherma Calc for sunrise/sunset/noon/moon phase
void CalcDST(byte D, byte M, byte dow)
{
//January, february, and december are out.
if (M < 3 || M > 11){
isDST=false;
}
//April to October are in
else if (M > 4 && M < 11){
isDST=true;
}
else{
int previousSunday = D - dow; // Sunday=1 Sat=7
if (M==3 && previousSunday > 7){
isDST=true;
}
else if (M==11 && previousSunday<=0){
isDST=false;
}
}
}
void CalSun()
{
if (trigger==false){
return;
}
// Every day at midnight, we calculate sunrise, sunset, time of highest sun, moon phase, or we do this if system has reset
if ((hour()==0 && minute()==0 && second()==0) || (trigger=true)){
trigger=false;
// Arduino uses Unix time- seconds since Jan 1 12:00 am UTC 1970
//Must convert to MST by adding 7 hrs =7*3600=25,200 seconds MST=GMT-7
//void CalcDST(day(),month(),weekday()) modifies isDST memory to appropriate true/false based upon USA rules
if (ApplyDST==true){
CalcDST(day(),month(),weekday());
}
//Using time library from Arduino.time_t now(); returns seconds since 1970
//Library states #seconds to subtrace to bring to Jan 1 2000 as origin for time is
//#define SECS_YR_2000 (946684800) the time at the start of y2k
//DST rules are march second sunday until november 1st sunday + 1 hr starting at 2 am which I dont care about so that part is neglected
long SecInput;
byte hours=hour(), minutes=minute();
if (isDST==true){
time_t t=(now()+3600);
if (hours!=0 || minutes!=0){
newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
SecInput=newDay+25200;
}
else{
newDay=(now()-(946684800+3600));
SecInput=newDay+25200;
}
}
else if (isDST==false){
time_t t=now();
hours=hour(t), minutes=minute(t);
if (hours!=0 || minutes!=0){
newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
SecInput=newDay+25200;
}
else{
newDay=(now()-946684800);
SecInput=newDay+25200;
}
}
//Calculate Latitude and Longitude converting from Degree, Minute, Seconds to seconds
latitude=dmsToSeconds(40,45,39); //Set to about the latitude of the great barrier reef rotated into the morthern hemisphere
longitude=dmsToSeconds(-111,53,25); //Set to Salt lake City, or MST zone, USA or there abouts- actually the AT921 remote weather station in Salt Lake City UT. Random web grab.
//Calculate sunrise time and sunset time using Epherma Library
sunrise=sunset=SecInput;
SunRise(&sunrise);
SunSet(&sunset);
// Correct sunrise data to reflect elapsed time from midnight (newDay)
// DST correction is all ready in both variables so no need to deal with it
sunrise-=25200; //Correct back to MST from GMT- DST still accounted for
sunset-=25200;
sunrise-=newDay;//set to elapsed seconds today
sunset-=newDay;
//*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
//offsets for sunrise/sunset all values in seconds offset from calculated sunrise or sunset value
//array order is ch0 am offset, pm offset, ch1 am offset, pm offset etc..
//THESE values are the number of seconds that a particular channel will be offset from the sunrise/sunset time, i.e. negative to rise earlier/set earlier
int Choffset[]={
-600,0,-3600,5400,0,600,-3600,5400,-4500,7200,0,0};
//**********************ok now were done changing things here********************************************
//Calculate rise and set times for all channels in equivlants to elapsed seconds from midnight today
//populate array for chRise and Set as well as chSlope for 0.5pi/ half day lenght for each channel from midday (asymmetric days are allowed)
unsigned int deltaY=2/3.141592653589793238462643383279502884197169399;
midDay=(sunset-sunrise)/2;
for (byte b=0;b<12;b++){
if (b%2==0){
ChRiseSet[b]=sunrise+Choffset[b];
ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
}
else if (b%2==1){
ChRiseSet[b]=sunset+Choffset[b];
ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
}
}
//***************** to CHANGE THE chance of Clouds actually occuring on a given day************************
byte CloudChance=70;//% Chance of a Cloud every day
//****************************now were done- did you use a value from 0-100 without a decimal?*****************
//once a day, after sunrise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
randomSeed((now()-(random(300000000,1000000000); //so that we are now more trully random
byte RainMaker=random(1,100);
byte CloudChance=70;//% Chance of a Cloud every day
RainMaker<CloudChance ? CloudToday=true: return; // to "simplify" things and avoid redundant calculation loops all cloud times are in fraction of day lengths
//*********** this is only executed 1x /day unless power outage and ONLY if last statement is true****************
///ALl cloud set up is done here... weather subroutine just implements it
/*The general strategy for this algorithim is as follows. Calculate random cloud cover as percent of day,
then randomly generate the # of discreet cloud instances for the day,
then determine cloud length by (daylight seconds * percent overcast)/#clouds
then randomize cloud lengths by splitting into groups of twos correcting for if we have odd # of clouds today*/
int dayLength=(sunset-sunrise);
byte PercentOvercastMin=10;//% as fraction (0-1 is 0-100%) of daylight hours that would minimially be cloudy on a cloud day
byte PercentOvercastMax=61;//% of daylight hours that would maximally be cloudy on a cloud day (then add 1 to that #)
byte Overcast=random(PercentOvercastMin,PercentOvercastMax);
// number of clouds possible for the day, max and min
byte CloudsMax=8;
byte CloudsMin=3;
CloudsTotal=random(CloudsMin,CloudsMax);
static int CloudMaster[15];// Set up array to hold start and end times for clouds for the day- dont size it
CloudPoint=&CloudMaster[0];//use address of pointer to find cloud data in weather, array is declared static to persist throughout day.
// set up start time for clouds
/*The way this is done is to split the total lenght of time for clouds into equal segments and then to randomly chop or add time to the
segments such that cloud length is variable. Then distribute into random parts of the day and fill array with start,duration pairs for clouds*/
int cloudLength;
cloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
byte fraction=(random(20,101)/100);//vary each cloud from 20%-180% of an equal fraction of total cloud for day
//using fraction to vary cloud length throughout day fill array CloudMaster[] at positions 0,2,4,6 to full with durations in seconds
//this way the MAX cloud duration is 180% of the average cloud druation while the min is 20%
for (byte a=0; (a=(CloudsTotal*2)-1); a=a+2){
byte b=0;
// if were having an odd # of clouds make sure last one is full length
if ((cloudsTotal%2!=0) && (a==(CloudsTotal*2)-2)) {
cloudMaster[a]=cloudLength;
b=b+1;
}
// else we make clouds variable in length short then long repeated with every two cloud sets being different in short and long but adding up equally by sets
else if ((CloudsTotal%2!=0)){
cloudLength=abs(cloudLength*b+(b*cloudLength-(cloudLength*fraction)));
cloudMaster[a]=cloudLength;
b=b+1;
if (b==2){
b=0;
fraction=(random(20,101)/100);//every other cloud recalculate a new fractional distribution for the next set
}
}
else if ((CloudsTotal%2==0){
cloudLength=abs(cloudLength*b+(b*cloudLength-(cloudLength*fraction)));
cloudMaster[a]=cloudLength;
b=b+1;
if (b==2){
b=0;
fraction=(random(20,101)/100);//every other cloud recalculate a new fractional distribution for the next set
}
}
}
// Now space the clouds out during the day using random fractionation of day segments
for (byte a=0; a<((CloudsTotal*2)-1); a++){
if (a%2==1){
byte b=0;
//calculate amount of sun during day (i.e. total day lenth - portion cloudy), divide into equal parts then randomly initiate cloud within sun segment for each part of the day.
// must index segment time by +1 segment for each cloud completed
int SunSegment=((dayLength-(dayLength*Overcast))/cloudNumber);
StartTime=(sunSegment*(random(1,101)/100));
StartTime=StartTime+((SunSegment*b)+sunrise)
cloudMaster[a]=StartTime;
b++;
}
}
//just reframe CloudMaster into array of start and end times for clouds (CLoudMaster[]={start,end,start,end,start,end} as progression through day in elapsed seconds from midnight.
for (byte a=0; a<((CloudsTotal*2)-1);a+2){
int endTime;
endTime=CloudMaster[a]+CloudMaster[a+1];
CloudMaster[a]=CloudMaster[a+1];
CloudMaster[a+1]=endTime;
}
}//Finally end time loop.. i.e. basically entire function runs only 1x per day or on restart of controller
}//END FUNCTION
void Insolation()
{
// Calculate time since day started correcting for DST
int elapsedtime;
if (isDST==true){
elapsedtime=(now()+3600-newDay);
}
else{
elapsedtime=(now()-newDay);
}
//define Pi as delta Y for slope since cos 0.5-1.5 Pi goes 0-1-0 in 0.5 pI increments slope of 1/2 day (0-1 intensity) delta Y is 1/2 Pi
unsigned int deltaY=3.141592653589793238462643383279502884197169399;
/* using -cos(pi/2+elapsedtime/slope)*((ChMaxVal-flicker)+flicker) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light. Turn off lights after sunset or before sunrise etc.
by splitting into half days centered on midday (1/2 ofsunset-sunrise) we center exactly the cos function for every channel so color blends are maintained
throughout intensity ramp... more or less ... change intensity every 30 seconds throughout the day*/
for (byte b=0;b<12;b++){
if ((b%2==0) && (elapsedtime>=ChRiseSet[b]) && (elapsedtime<midDay)){
if (b==0)ChannelValue[0]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b])+flicker[b]));
else if (b==2) ChannelValue[1]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-1]-flicker[b-1])+flicker[b-1]));
else if (b==4) ChannelValue[2]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-2]-flicker[b-2])+flicker[b-2]));
else if (b==6) ChannelValue[3]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-3]-flicker[b-3])+flicker[b-3]));
else if (b==8) ChannelValue[4]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-4]-flicker[b-4])+flicker[b-4]));
else if (b==10) ChannelValue[5]=(-cos((deltaY/2)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b-5]-flicker[b-5])+flicker[b-5]));
}
else if ((b%2==1) && (elapsedtime<=ChRiseSet[b]) && (elapsedtime>=midDay)){
if (b==1)ChannelValue[0]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-1])+flicker[b-1]));
else if (b==3) ChannelValue[1]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-2])+flicker[b-2]));
else if (b==5) ChannelValue[2]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-3])+flicker[b-3]));
else if (b==7) ChannelValue[3]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-4])+flicker[b-4]));
else if (b==9) ChannelValue[4]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-5])+flicker[b-5]));
else if (b==11) ChannelValue[5]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-6])+flicker[b-6]));
}
else if (((b%2==0) && (elapsedtime<ChRiseSet[b])) || ((b%2==1) && (elapsedtime>ChRiseSet[b]))){
Cloud=false;//no lights = no Cloud
Storm=false;
if ((b==0) || (b==1)) ChannelValue[0]=0;
else if ((b==2) || (b=3)) ChannelValue[1]=0;
else if ((b==4) || (b=5)) ChannelValue[2]=0;
else if ((b==6) || (b=7)) ChannelValue[3]=0;
else if ((b==8) || (b=9)) ChannelValue[4]=0;
else if ((b==10) || (b=11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
}
}
}//END function
//This is where modification of insolation by clouds or a storm occurs
//Its worth noting that the clouds simply modify (depending on how much cloud cover there is) the existing light set by insolation as a fractional value
void Weather ()
{
boolean Wchannel[]={1,0,1,0,0,0,0}; //YOU MUST CHANGE THIS- designate channels of white or channels you want off during Storm as 1 in array position all else as zero (channel's {0,1,2,3,4,5} )
static int CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static int PriorCloudCover; //used to "delay" one side of the tank from the other in cloud passing effects
int elapsedTime=(now()-newDay);
static int StormStart;
static int StormEnd;
static byte CloudEnd;
static boolean trigger;//use to track the first run of functions to calculate random times that become fixed, see change from cloud to storm for useage
// see insolation elapsed time function for assigmnent of Cloud start times and set of Cloud=true
//check to see if were having a scheduled cloud
if ((Cloud==true) && (Storm==false)){
if ((elapsedTime%2)!=0){
return;
}
else if ((elapsedTime%2)==0){// EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
//now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times up
//seems backwards since CloudCover has not been calculated if you read linearly but it will always be set prior to this=true
if ((elapsedTime%2)>=CloudEnd){
ClearSky(CloudCover, CloudEnd);
return;
}
/*Use fractional intensity to set minimum value for any channel its WAY to confusing to try to set up otherwise. Dimming is proportional to actual intensity output
and constrained by flicker point. Random walk uses static variable "CloudCover" constrained to 0-100 to represent fractional intensity (i.e. (CloudCover/100)*Insolation SetPoint
is how the current cloud intensity is set*/
ranodomSeed(millis());
byte stepsize=random(-10,10);
PriorCloudCover=CloudCover;
CloudCover = CloudCover + stepsize;
if (CloudCover>=80){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms change this value to a lower number
Storm=true;
Trigger=true;
StormStart=now()
}
else if (CloudCover<=0){
CloudCover-=(stepsize*1.5);
}
//Array to give direction to dimming. e.g. DimOrder[]={0,0,1,1,0,0} would dim PWM channels 0-1,4-5 as FIRST dim value, then channels 2-3 would take on FIRST dime Value
//when channels 0-1,4-5 take on SECOND dim value, so the values chase eachother across the PWM output light array according to your set up. I have 0-1 as RIGHT side lights,
//2-3 as LEFT side LED, and 4 as whole tank while I do not use 5
byte DimOrder[]={0,0,1,1,0,0,};
for (int a=0;a<6;a++){
if (DimOrder[a]==0){
ChannelValue[a]=(ChannelValue[a]*(1-(CloudCover/100)));
if (ChannelValue[a]<=flicker[a]){
ChannelValue[a]=flicker[a];
Storm=true;
trigger=true;
StormStart=now();
}
}
if (DimOrder[a]==1){
ChannelValue[a]=(ChannelValue[a]*(1-(PriorCloudCover/100)));
if (ChannelValue[a]<=flicker[a]){
ChannelValue[a]=flicker[a];
Storm=true;
trigger=true;
StormStart=now()
}
}
}
}
}
//enable a flag sent from controller to triger a storm, i.e. Storm=true
// set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
else if (((Cloud=false) && (Storm=true)) || ((Cloud=true) && (Storm=true))){
//do this next part exactly once per storm then loop past
if (trigger==true){
int LongestStorm=((CloudEnd-StormStart)-120);//remove the last 2 minutes I use to clear the sky from the longest possible storm duration
int StormDuration=random((LongestStorm/4),(LongestStorm));//set (min,max) seconds any single Storm can exist.
StormStart=(now()-newDay);
StormEnd=((StormStart+StormDuration)+(now()-newDay);
trigger=false;
}
//Every 1 second duing a storm change intensity, clouds are movin fast baby
if ((elapsedTime%1)!=0){
return;
}
else if ((elapsedTime%1)==0){// EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
if (elapsedTime>=StormEnd){
ClearSky(CloudCover, CloudEnd);
return;
}
// step through intensity values
ranodomSeed(millis());
byte stepsize=random(-10,10);
PriorCloudCover=CloudCover;
CloudCover = CloudCover + stepsize;
if (CloudCover>=100){//conflicted here, since were dimming by fractional intenstiy its likely we will hit flicker point or lower... might change how this works
CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
}
else if (CloudCover<=0){
CloudCover-=(stepsize*1.5);//if were clear sky path reflect random path to cloud
}
//*****************IF You want the light to "chase" across the tank****************************
//you need to specify the left or right side (or front back whatever, depends on how your lights are set up)
//Use either 0 or 1 to specify groups of channels that dim together (arbitrary but if you need to know direction the cloud will travel you will need to read the next bit of code)
//assign the same 0 or 1 value to all left side (or front back or whatever) channels and the other value to all right side channels
byte DimOrder[]={0,0,1,1,0,0,};
//****************** ok were done changing things*****************************
for (int a=0;a<6;a++) {
if (DimOrder[a]==0){
ChannelValue[a]=(ChannelValue[a]*(1-(CloudCover/100)));
if (ChannelValue[a]<=flicker[a]){
ChannelValue[a]=flicker[a];
Storm=true;
trigger=true;
StormStart=now();
}
}
else if (DimOrder[a]==1){
ChannelValue[a]=(ChannelValue[a]*(1-(PriorCloudCover/100)));
if (ChannelValue[a]<=flicker[a]){
ChannelValue[a]=flicker[a];
Storm=true;
trigger=true;
StormStart=now();
}
}
}
}
}//end of storm if loop
// Place this loop last as it will only run then cloud is over, and trigger next cloud start and end, this way its not computing during a cloud.
else {
for (byte a=0; a<=(CloudsTotal-1); a+2){
if (((elapsedTime>=CloudPoint[a]) && (elapsedTime<=CloudPoint[a+1])) || ForceCloud==true){
CloudEnd=(CloudPoint[a+1]-120);
Cloud=true;
}
else{
Cloud=false;
CloudCover=0;
PriorCloudCover=0;
return;
}
}
}
}//End Function
//THE LAST 2 mins of ANY Storm, or cloud, are used to ramp linearlly to full daylight setting, whatever that is at the given time of day
void ClearSky(byte CloudCover, int CloudEnd)
{
int elapsedTime=(now()-newDay);
// no need to restrict time to avoid resource hogging, its only called every x seconds from weather.
//I AM ASSUMING THAT everything calling this does so with 120 seconds left in the cloud/storm etc to clear the sky.
//if you do this incorrectly (i.e. change this and dont change it everywhere I am not sure what would happen for certain, but it may cause issues... almost certainly will
int remaining=(CloudEnd-elapsedTime);
int Range=(CloudCover/100);
int slope=(Range/120);
if (remaining<=0){
Storm=false;
Cloud=false;
return;
}
for (byte a=0; a<6; a++){
ChannelValue[a]=(ChannelValue[a]*((120-remaining)*slope));
}
}//End Function
//I HAVE NOT FINISHED THIS.. nor even put in a call to it... figuring out how/what I want to do still.
void Strike()
{
int msecNow, msecStart, msecElapsed;
byte strikePattern;
if (Storm==false)
{
msecStart=millis();
return;
}
msecNow=millis();
msecElapsed=(msecNow-msecStart);
strikePattern=(random(3,9);
strikeTime=
}
Ok. You can't do what you are trying to do with pointers. They don't work like that. They simply "point" to a memory location and you can reference the data stored at that location. You can't do an array index with the brackets because the pointer doesn't know that it's an array. The pointer simply knows that it is a certain size (int in this case) and it refers to a single int storage location in memory.rufessor wrote:Still working on this...
I post the entire code but really have one question
regarding pointers and arrays.
If I create a static array (15 values all int)
and then make a pointer to get the value from another function (cannot pass arrays)
is this the correct syntax.
Code: Select all
declare as global.. int *CloudPoint; Then in function calcSun(). static int CloudMaster[15];// Set up array to hold start and end times for clouds for the CloudPoint=&CloudMaster[0];//use address of pointer to find cloud data from other function and then in function Weather() using a for loop jump through the start and end times in the array using the pointer for (byte a=0; a<=(CloudsTotal-1); a+2){ if (((elapsedTime>=CloudPoint[a]) && (elapsedTime<=CloudPoint[a+1])) || ForceCloud==true){ CloudEnd=(CloudPoint[a+1]-120); Cloud=true;
Code: Select all
b = *(pCloudPoint+1);
Code: Select all
a = *pCloudPoint;
// increments the location of the pointer by one, just like (pCloudPoint+1)
// however, pCloudPoint is always going to point to the latest and
// you can't go to the beginning without a pCloudPoint-- and then that gets tricky
pCloudPoint++;
b = *pCloudPoint;
Code: Select all
pCloudPoint = &CloudMaster[0];
// this is the same thing but can be confusing to understand
pCloudPoint = CloudMaster;
Code: Select all
//AS OF 3/15/12 this code is completely UNTESTED beyond compile error checking- and will likely NOT RUN.
#include <Time.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <Globals.h>
#include <ReefAngel_Features.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <avr/wdt.h>
#include <SWFLTEK_EPHERMA.h>
//Defines for ReefAngel PWM module
#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5
//*********************************************************************************************************************************
//IF YOU DO NOT WISH TO USE the U.S.A. Day Light Savings Time rules to modify the time stamp for sunrise/sunset calculations...set to false- i.e. if you dont live in the USA etc
boolean ApplyDST=true;
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long sunrise;
unsigned long sunset;
long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
int ChRiseSet[12];
float ChSlope[12];
int midDay;// exactly 1/2 way between sunrise and sunset, i.e. my take on solar noon.
byte PWMports[] ={
3,5,6,9,10,11};
byte ChannelValue[6];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)
//What are the max values for ch intensity we want to reach, i.e. at and around solar noon- use 0-255 scale for PWM board byte output (ch0,1,2,3,4,5)
//i.e. 100% channel 0, ~10% channel 1, 40% channel 3 would be {255, 25, 102, ...etc}
byte ChInt[]={
210,225,210,225,220,0};
// At what point in PWM dimming do your individual channels flicker, input this as PWM output value into the array -NOT % intensity, use actual PWM output(ch0,1,2,3,4,5)
byte flicker[]={
25,25,25,25,15,0};
//YOU MUST CHANGE THIS- designate channels of white light (channels you want off during Storm and used for lightning strikes)
//use 1 to designate white channel. Array corresponds to PWM channel 0-5 in order
boolean Wchannel[]={1,0,1,0,0,0,0};
long int StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[18];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte count;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop
byte SeasonsVar[]={
0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, sunrisehour, sunriseminute, sunsethour, sunsetminute, Cloudstarthout, Cloudstartminute, Cloudduration,lightningchance, PWM1,PWM2,PWM3
byte cmdnum=255;
byte datanum=255;
boolean trigger; //used a few places as a switch to ensure things only run 1x when needed
byte strikePattern, strikeTime;//used in Lightning() for timing of particular instance of strike
boolean isDST, Cloud, Storm, CloudToday, StrikeNow; // what they seem, is it DST?, Are we in a cloud?
byte CloudsTotal;//required to know how large the array for CloudMaster will be- I need to use Vectors.... I know.
int *pCloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Setup
void setup()
{
Serial.begin(57600);
Wire.begin(8);
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
randomSeed(analogRead(0));
pinMode(3,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
//wdt_enable(WDTO_1S);
setSyncProvider(RTC.get); // the function to get the time from the RTC
setSyncInterval(SECS_PER_HOUR); // Changed to sync every hour.
now();//why are you here?
CloudToday=false;
trigger=true;
}
//End Setup
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Loop
void loop()
{
wdt_reset();
//Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255){
ProcessCMD(cmdnum,datanum);
cmdnum=255;
datanum=255;
}
// now run through sunrise/sunset calculation and then intensity set and finally weather overlay
CalSun();
Insolation();
Weather();
if (StrikeNow==false){
StrikeStart=millis();
count=0;
}
else if (StrikeNow==true){
int intensity;//strike intensity value for each individual strike calculated once for each strike when index of strike changes i.e. a in for loop
int elapsed=millis()-StrikeStart;
if (elapsed>StrikeMaster[(StrikeNumber*2-1)]){
StrikeNow=false;
}
for (byte a=0;a<(StrikeNumber*2);a=(a+2)){ //StrikeNumber is the number of strikes generated this sequence so only look this far into array or we just hit zeros if its not a full stike seq
if ((elapsed>StrikeMaster[a])&&(elapsed<StrikeMaster[a+1])){
if (count!=(a+1)){
intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
}
count=(a+1);
for (byte a=0; a<6; a++){
if (Wchannel[a]==1) ChannelValue[a]=intensity;
}
}
else {
for (byte a=0; a<6; a++){
if (Wchannel[a]==1) ChannelValue[a]=0;
}
}
}
}
//Now that we have generated our sun patter, Weather as clouds, and possibly a storm, and possibly lightning each of which override the prior channel setting
//lets actually make some light.
for (byte a=0;a<6;a++){
analogWrite(PWMports[a],ChannelValue[a]);
}
// DEBUGGING serial comm... you can delete this if you dont want to use it.
/* Serial.println("DST");
Serial.println(isDST);
delay(500);
Serial.println("trigger");
Serial.println(trigger);
delay(500);
Serial.println("newDay");
Serial.println(newDay,DEC);
delay(500);
Serial.println("sunrise");
Serial.println(sunrise,DEC);
delay(500);
Serial.println("sunset");
Serial.println(sunset,DEC);
Serial.println("now()");
long secon;
secon=now();
Serial.println(secon,DEC);
delay(1000);
*/
}
//End Loop
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
void receiveEvent(int howMany) {
wdt_reset();
if (howMany==5){
byte cmd1, cmd2, cmd3, cmd4, cmd5;
cmd1=Wire.read();
cmd2=Wire.read();
cmd3=Wire.read();
cmd4=Wire.read();
cmd5=Wire.read();
if (cmd1=='$' && cmd2=='$' && cmd3=='$'){
cmdnum=cmd4;
datanum=cmd5;
//Serial.println(cmd4,DEC);
//Serial.println(cmd5,DEC);
}
}
else{
for (int a=0;a<howMany;a++){
Wire.read();
}
}
}
void ProcessCMD(byte cmd, byte data)
{
wdt_reset();
// Individual Channel
if (cmd>=0 && cmd<=5){
ChannelValue[cmd]=data;
analogWrite(PWMports[cmd],data);
}
if (cmd==6){
Storm=true;
}
}
//End Standard Functions
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
/*SeasonsVar[0]=ChannelValue[0];
SeasonsVar[1];
SeasonsVar[2];
SeasonsVar[3];
SeasonsVar[4];
SeasonsVar[5];
SeasonsVar[6];
SeasonsVar[7];
SeasonsVar[8];
SeasonsVar[9];
SeasonsVar[10];
SeasonsVar[11];
Wire.write(SeasonsVar,12);
*/
}
//End Send Data
//*********************************************************************************************************************************
// Function to run Epherma Calc for sunrise/sunset/noon/moon phase
void CalcDST(byte D, byte M, byte dow)
{
//January, february, and december are out.
if (M < 3 || M > 11){
isDST=false;
}
//April to October are in
else if (M > 4 && M < 11){
isDST=true;
}
else{
int previousSunday = D - dow; // Sunday=1 Sat=7
if (M==3 && previousSunday > 7){
isDST=true;
}
else if (M==11 && previousSunday<=0){
isDST=false;
}
}
}
void CalSun()
{ //this entire function runs only based upon the first if being true... so its a LONG ways down to the else if trigger is false statement to finish this if
// Every day at midnight, we calculate sunrise, sunset, time of highest sun, moon phase, or we do this if system has reset
if ((hour()==0 && minute()==0 && second()==0) || (trigger=true)){
trigger=false;
long int SecInput;
// Arduino uses Unix time- seconds since Jan 1 12:00 am UTC 1970
//Must convert to MST by adding 7 hrs =7*3600=25,200 seconds MST=GMT-7
//void CalcDST(day(),month(),weekday()) modifies isDST memory to appropriate true/false based upon USA rules
if (ApplyDST==true){
CalcDST(day(),month(),weekday());
}
//Using time library from Arduino.time_t now(); returns seconds since 1970
//Library states #seconds to subtrace to bring to Jan 1 2000 as origin for time is
//#define SECS_YR_2000 (946684800) the time at the start of y2k
//DST rules are march second sunday until november 1st sunday + 1 hr starting at 2 am which I dont care about so that part is neglected
byte hours, minutes;
if ((ApplyDST==true)&&(isDST==true)){
time_t t=now()+3600;
hours=hour(t), minutes=minute(t);
if (hours!=0 || minutes!=0){
newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
SecInput=newDay+25200;
}
else{
newDay=(now()-(946684800+3600));
SecInput=newDay+25200;
}
}
else {
time_t t=now();
hours=hour(t), minutes=minute(t);
if (hours!=0 || minutes!=0){
newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
SecInput=newDay+25200;
}
else{
newDay=(now()-946684800);
SecInput=newDay+25200;
}
}
//Calculate Latitude and Longitude converting from Degree, Minute, Seconds to seconds
latitude=dmsToSeconds(40,45,39); //Set to about the latitude of the great barrier reef rotated into the morthern hemisphere
longitude=dmsToSeconds(-111,53,25); //Set to Salt lake City, or MST zone, USA or there abouts- actually the AT921 remote weather station in Salt Lake City UT. Random web grab.
//Calculate sunrise time and sunset time using Epherma Library
sunrise=sunset=SecInput;
SunRise(&sunrise);
SunSet(&sunset);
// Correct sunrise data to reflect elapsed time from midnight (newDay)
// DST correction is all ready in both variables so no need to deal with it
sunrise-=25200; //Correct back to MST from GMT- DST still accounted for
sunset-=25200;
sunrise-=newDay;//set to elapsed seconds today
sunset-=newDay;
//*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
//offsets for sunrise/sunset all values in seconds offset from calculated sunrise or sunset value
//array order is ch0 am offset, pm offset, ch1 am offset, pm offset etc..
//THESE values are the number of seconds that a particular channel will be offset from the sunrise/sunset time, i.e. negative to rise earlier/set earlier
int Choffset[]={
-600,0,-3600,5400,0,600,-3600,5400,-4500,7200,0,0};
//**********************ok now were done changing things here********************************************
//Calculate rise and set times for all channels in equivlants to elapsed seconds from midnight today
//populate array for chRise and Set as well as chSlope for 0.5pi/ half day lenght for each channel from midday (asymmetric days are allowed)
float deltaY=2/3.141592653589793238462643383279502884197169399;
midDay=(sunset-sunrise)/2;
for (byte b=0;b<12;b++){
if (b%2==0){
ChRiseSet[b]=sunrise+Choffset[b];
ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
}
else if (b%2==1){
ChRiseSet[b]=sunset+Choffset[b];
ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
}
}
//***************** to CHANGE THE chance of Clouds actually occuring on a given day************************
byte CloudChance=70;//% Chance of a Cloud every day
//****************************now were done- did you use a value from 0-100 without a decimal?*****************
//once a day, after sunrise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
randomSeed(now()-(random(300000000,1000000000))); //so that we are now more trully random
byte RainMaker=random(1,101);
if (RainMaker<CloudChance){
CloudToday=true;
}
else if (RainMaker>=CloudChance){
CloudToday=false;
return;
}
// to "simplify" things and avoid redundant calculation loops all cloud times are in fraction of day lengths
//*********** this is only executed 1x /day unless power outage and ONLY if last statement is true****************
///ALl cloud set up is done here... weather subroutine just implements it
/*The general strategy for this algorithim is as follows. Calculate random cloud cover as percent of day,
then randomly generate the # of discreet cloud instances for the day,
then determine cloud length by (daylight seconds * percent overcast)/#clouds
then randomize cloud lengths by splitting into groups of twos correcting for if we have odd # of clouds today*/
int dayLength=(sunset-sunrise);
byte PercentOvercastMin=10;//% as fraction (0-1 is 0-100%) of daylight hours that would minimially be cloudy on a cloud day
byte PercentOvercastMax=61;//% of daylight hours that would maximally be cloudy on a cloud day (then add 1 to that #)
byte Overcast=random(PercentOvercastMin,(PercentOvercastMax+1));
// number of clouds possible for the day, max and min
byte CloudsMax=8;
byte CloudsMin=3;
CloudsTotal=random(CloudsMin,(CloudsMax+1));
static int CloudMaster[15];// Set up array to hold start and end times for clouds for the day- dont size it
pCloudPoint=&CloudMaster[0];//use address of pointer to find cloud data in weather, array is declared static to persist throughout day.
// set up start time for clouds
/*The way this is done is to split the total lenght of time for clouds into equal segments and then to randomly chop or add time to the
segments such that cloud length is variable. Then distribute into random parts of the day and fill array with start,duration pairs for clouds*/
int CloudLength;
CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
byte fraction=(random(20,101)/100);//vary each cloud from 20%-180% of an equal fraction of total cloud for day
//using fraction to vary cloud length throughout day fill array CloudMaster[] at positions 0,2,4,6 to full with durations in seconds
//this way the MAX cloud duration is 180% of the average cloud druation while the min is 20%
for (byte a=0; (a=(CloudsTotal*2)-1); a=(a+2)){
byte b=0;
// if were having an odd # of clouds make sure last one is full length
if ((CloudsTotal%2!=0) && (a==(CloudsTotal*2)-2)){
CloudMaster[a]=CloudLength;
b=b+1;
}
// else we make clouds variable in length short then long repeated with every two cloud sets being different in short and long but adding up equally by sets
else if ((CloudsTotal%2!=0)){
CloudLength=abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
CloudMaster[a]=CloudLength;
b=b+1;
if (b==2){
b=0;
fraction=(random(20,101)/100);//every other cloud recalculate a new fractional distribution for the next set
}
}
else if (CloudsTotal%2==0){
CloudLength=abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
CloudMaster[a]=CloudLength;
b=b+1;
if (b==2){
b=0;
fraction=(random(20,101)/100);//every other cloud recalculate a new fractional distribution for the next set
}
}
}
// Now space the clouds out during the day using random fractionation of day segments
for (byte a=0; a<((CloudsTotal*2)-1); a++){
if (a%2==1){
byte b=0;
//calculate amount of sun during day (i.e. total day lenth - portion cloudy), divide into equal parts then randomly initiate cloud within sun segment for each part of the day.
// must index segment time by +1 segment for each cloud completed
int SunSegment=((dayLength-(dayLength*Overcast))/CloudsTotal);
int StartTime=(SunSegment*(random(1,101)/100));
StartTime=StartTime+((SunSegment*b)+sunrise);
CloudMaster[a]=StartTime;
b++;
}
}
//just reframe CloudMaster into array of start and end times for clouds (CLoudMaster[]={start,end,start,end,start,end} as progression through day in elapsed seconds from midnight.
for (byte a=0; a<((CloudsTotal*2)-1);a=(a+2)){
int endTime;
endTime=CloudMaster[a]+CloudMaster[a+1];
CloudMaster[a]=CloudMaster[a+1];
CloudMaster[a+1]=endTime;
}
}//Finally end loop if trigger is true of hr=0 min=0 sec=0 .... i.e. basically entire function runs only 1x per day or on restart of controller
else if (trigger==false){
return;
}
}//END FUNCTION
void Insolation()
{
// Calculate time since day started correcting for DST
int elapsedtime;
if (isDST==true){
elapsedtime=(now()+3600-newDay);
}
else{
elapsedtime=(now()-newDay);
}
//define Pi as delta Y for slope since cos 0.5-1.5 Pi goes 0-1-0 in 0.5 pI increments slope of 1/2 day (0-1 intensity) delta Y is 1/2 Pi
float deltaY=3.141592653589793238462643383279502884197169399;
/* using -cos(pi/2+elapsedtime/slope)*((ChMaxVal-flicker)+flicker) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light. Turn off lights after sunset or before sunrise etc.
by splitting into half days centered on midday (1/2 ofsunset-sunrise) we center exactly the cos function for every channel so color blends are maintained
throughout intensity ramp... more or less ... change intensity every 30 seconds throughout the day*/
for (byte b=0;b<12;b++){
if ((b%2==0) && (elapsedtime>=ChRiseSet[b]) && (elapsedtime<midDay)){
if (b==0)ChannelValue[0]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b])+flicker[b]));
else if (b==2) ChannelValue[1]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b-1]-flicker[b-1])+flicker[b-1]));
else if (b==4) ChannelValue[2]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b-2]-flicker[b-2])+flicker[b-2]));
else if (b==6) ChannelValue[3]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b-3]-flicker[b-3])+flicker[b-3]));
else if (b==8) ChannelValue[4]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b-4]-flicker[b-4])+flicker[b-4]));
else if (b==10) ChannelValue[5]=(-cos((deltaY/2)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b-5]-flicker[b-5])+flicker[b-5]));
}
else if ((b%2==1) && (elapsedtime<=ChRiseSet[b]) && (elapsedtime>=midDay)){
if (b==1)ChannelValue[0]=(-cos((deltaY)+(elapsedtime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-1])+flicker[b-1]));
else if (b==3) ChannelValue[1]=(-cos((deltaY)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-2])+flicker[b-2]));
else if (b==5) ChannelValue[2]=(-cos((deltaY)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-3])+flicker[b-3]));
else if (b==7) ChannelValue[3]=(-cos((deltaY)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-4])+flicker[b-4]));
else if (b==9) ChannelValue[4]=(-cos((deltaY)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-5])+flicker[b-5]));
else if (b==11) ChannelValue[5]=(-cos((deltaY)+((elapsedtime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-6])+flicker[b-6]));
}
else if (((b%2==0) && (elapsedtime<ChRiseSet[b])) || ((b%2==1) && (elapsedtime>ChRiseSet[b]))){
Cloud=false;//no lights = no Cloud
Storm=false;
if ((b==0) || (b==1)) ChannelValue[0]=0;
else if ((b==2) || (b=3)) ChannelValue[1]=0;
else if ((b==4) || (b=5)) ChannelValue[2]=0;
else if ((b==6) || (b=7)) ChannelValue[3]=0;
else if ((b==8) || (b=9)) ChannelValue[4]=0;
else if ((b==10) || (b=11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
}
}
}//END function
//This is where modification of insolation by clouds or a storm occurs
//Its worth noting that the clouds simply modify (depending on how much cloud cover there is) the existing light set by insolation as a fractional value
void Weather ()
{
static int CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static int PriorCloudCover; //used to "delay" one side of the tank from the other in cloud passing effects
int elapsedTime=(now()-newDay);
static int StormStart;
static int StormEnd;
static byte CloudEnd;
static boolean trigger;//use to track the first run of functions to calculate random times that become fixed, see change from cloud to storm for useage
// see insolation elapsed time function for assigmnent of Cloud start times and set of Cloud=true
//check to see if were having a scheduled cloud
if ((Cloud==true) && (Storm==false)){
if ((elapsedTime%2)!=0){
return;
}
else if ((elapsedTime%2)==0){// EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
//now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times up
//seems backwards since CloudCover has not been calculated if you read linearly but it will always be set prior to this=true
if ((elapsedTime%2)>=CloudEnd){
ClearSky(CloudCover, CloudEnd);
return;
}
/*Use fractional intensity to set minimum value for any channel its WAY to confusing to try to set up otherwise. Dimming is proportional to actual intensity output
and constrained by flicker point. Random walk uses static variable "CloudCover" constrained to 0-100 to represent fractional intensity (i.e. (CloudCover/100)*Insolation SetPoint
is how the current cloud intensity is set*/
randomSeed(millis());
byte stepsize=random(-10,11);
PriorCloudCover=CloudCover;
CloudCover = CloudCover + stepsize;
if (CloudCover>=80){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms change this value to a lower number
Storm=true;
trigger=true;
StormStart=now();
}
else if (CloudCover<=0){
CloudCover-=(stepsize*1.5);
}
//Array to give direction to dimming. e.g. DimOrder[]={0,0,1,1,0,0} would dim PWM channels 0-1,4-5 as FIRST dim value, then channels 2-3 would take on FIRST dime Value
//when channels 0-1,4-5 take on SECOND dim value, so the values chase eachother across the PWM output light array according to your set up. I have 0-1 as RIGHT side lights,
//2-3 as LEFT side LED, and 4 as whole tank while I do not use 5
byte DimOrder[]={0,0,1,1,0,0,};
for (int a=0;a<6;a++){
if (DimOrder[a]==0){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);
}
if (DimOrder[a]==1){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);
}
}
}
}
//enable a flag sent from controller to triger a storm, i.e. Storm=true
// set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
else if (((Cloud=false) && (Storm=true)) || ((Cloud=true) && (Storm=true))){
//do this next part exactly once per storm then loop past
if (trigger==true){
int LongestStorm=((CloudEnd-StormStart)-120);//remove the last 2 minutes I use to clear the sky from the longest possible storm duration
int StormDuration=random((LongestStorm/4),(LongestStorm+1));//set (min,max) seconds any single Storm can exist.
StormStart=(now()-newDay);
StormEnd=((StormStart+StormDuration)+(now()-newDay));
trigger=false;
}
//Every 1 second duing a storm change intensity, clouds are movin fast baby
if ((elapsedTime%1)!=0){
return;
}
else if ((elapsedTime%1)==0){// EVERY one second change the PWM output for a storm
if (elapsedTime>=CloudEnd){ //if we randomly had a storm go to cloud end (which is possible but will be rare)
ClearSky(CloudCover, CloudEnd);
return;
}
else if (elapsedTime>=StormEnd){ //if were done with the storm but still cloudy
Storm=false;
return;
// step through intensity values
randomSeed(millis());
byte stepsize=random(-15,16);
if ((stepsize<-12) || (stepsize>12)){ //it is convient to use this random call to generate a strike during a storm for more frequent strikes adjust down
randomSeed(now()-newDay); //start random with a new value to avoid repetitive stike generations
StrikeNumber=(random(2,10)); //random high =x-1 so max strike =9 and array=17 for StrikeMaster
int StartStrike=0;
//Rather than bother with Vector and dynamic allocation with push/pull use an array big enough to accomadate the largest strike pattern and then
//ensure the array is zeroed out past the last position required for this strike pattern so each pattern is only as long as generated
//Array is Strike Start millis, strike end millis, etc... total of StrikeNumber pairs.
for (byte a=0;a<18;a++){
if (a>=(StrikeNumber*2)){
StrikeMaster[a]=0;
}
else if (a%2==0){
StartStrike+=random(100,701);//position 0,2,4,6,8.. is strike delay
StrikeMaster[a]=StartStrike;
}
else if (a%2!=0){
StartStrike+=random(30,81);//position 1,3,5,7,9... is strike duration- added to StartStrike=StrikeEnd
StrikeMaster[a]=StartStrike;
}
}
StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
}
PriorCloudCover=CloudCover;
CloudCover = CloudCover + stepsize;
if (CloudCover>=100){//conflicted here, since were dimming by fractional intenstiy its likely we will hit flicker point or lower... might change how this works
CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
}
else if (CloudCover<=0){
CloudCover-=(stepsize*1.5);//if were clear sky path reflect random path to cloud
}
//*****************IF You want the light to "chase" across the tank****************************
//you need to specify the left or right side (or front back whatever, depends on how your lights are set up)
//Use either 0 or 1 to specify groups of channels that dim together (arbitrary but if you need to know direction the cloud will travel you will need to read the next bit of code)
//assign the same 0 or 1 value to all left side (or front back or whatever) channels and the other value to all right side channels
byte DimOrder[]={0,0,1,1,0,0,};
//****************** ok were done changing things*****************************
for (int a=0;a<6;a++) {
if (Wchannel[a]==1){
ChannelValue[a]=0;
continue;
}
if (DimOrder[a]==0){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);
}
else if (DimOrder[a]==1){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);
}
}
}
}
}//end of storm if loop
// Place this loop last as it will only run then cloud is over, and trigger next cloud start and end, this way its not computing during a cloud.
else {
for (byte a=0; a<=(CloudsTotal-1); a=(a+2)){
if ((elapsedTime>=*(pCloudPoint+a)) && (elapsedTime<=*(pCloudPoint+(a+1)))){
CloudEnd=*(pCloudPoint+(a+1));
CloudEnd+=120;
Cloud=true;
}
else{
Cloud=false;
CloudCover=0;
PriorCloudCover=0;
}
}
}
}//End Weather function
//THE LAST 2 mins of ANY Storm, or cloud, are used to ramp linearlly to full daylight setting, whatever that is at the given time of day
void ClearSky(byte CloudCover, int CloudEnd)
{
int elapsedTime=(now()-newDay);
// no need to restrict time to avoid resource hogging, its only called every x seconds from weather.
//I AM ASSUMING THAT everything calling this does so with 120 seconds left in the cloud/storm etc to clear the sky.
//if you do this incorrectly (i.e. change this and dont change it everywhere I am not sure what would happen for certain, but it may cause issues... almost certainly will
int remaining=(CloudEnd-elapsedTime);
int Range=(CloudCover/100);
int slope=(Range/120);
if (remaining<=0){
Storm=false;
Cloud=false;
return;
}
for (byte a=0; a<6; a++){
ChannelValue[a]=(ChannelValue[a]*((120-remaining)*slope));
}
}//End Clear Sky function
Code: Select all
for (byte a=1; a<(CloudsTotal*2); a=(a+2)){
//calculate amount of sun during day (i.e. total day lenth - portion cloudy), divide into equal parts then randomly initiate cloud within sun segment for each part of the day.
// must index segment time by +1 segment for each cloud completed
int SunSegment=((dayLength-(dayLength*Overcast))/CloudsTotal);
Serial.print("SunSegment=");
Serial.println(SunSegment);
float randomSplit=random(10,101);//clouds will not start until at least 10% of the daylight (no cloud time) fraction of the sunlight period for the day segment has passed
randomSplit=(randomSplit/100);
Serial.println(randomSplit);
StartTime=(sunrise+(SunSegment*randomSplit)+(SunSegment*b));
Serial.println(StartTime);
CloudMaster[a]=StartTime;
StartTime=0;
Serial.print("b=");
Serial.println(b);
Serial.print("Cloudmaster position");
Serial.println(a);
Serial.println("is value");
Serial.println(CloudMaster[a]);
b=b+1;
}
Code: Select all
StartTime=(sunrise+(SunSegment*randomSplit)+(SunSegment*b));
Code: Select all
//AS OF 4/6/12 this code is marginally functional in sections but not yet working, do not load to PWM board-non functional -
#include <Time.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <Globals.h>
#include <ReefAngel_Features.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <avr/wdt.h>
#include <SWFLTEK_EPHERMA.h>
//Defines for ReefAngel PWM module
#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5
//*********************************************************************************************************************************
//IF YOU DO NOT WISH TO USE the U.S.A. Day Light Savings Time rules to modify the time stamp for sunrise/sunset calculations...set to false- i.e. if you dont live in the USA etc
boolean ApplyDST=true;
long int elapsedTime;//used multiple places as elapsed since new day.
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long sunrise;
unsigned long sunset;
long int newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long int ChRiseSet[12];
float ChSlope[12];
long int midDay;// exactly 1/2 way between sunrise and sunset, i.e. my take on solar noon.
byte PWMports[] ={
3,5,6,9,10,11};
byte ChannelValue[6];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)
//What are the max values for ch intensity we want to reach, i.e. at and around solar noon- use 0-255 scale for PWM board byte output (ch0,1,2,3,4,5)
//i.e. 100% channel 0, ~10% channel 1, 40% channel 3 would be {255, 25, 102, ...etc}
byte ChInt[]={
250,235,250,235,230,0};
// At what point in PWM dimming do your individual channels flicker, input this as PWM output value into the array -NOT % intensity, use actual PWM output(ch0,1,2,3,4,5)
byte flicker[]={
25,25,25,25,15,0};
//YOU MUST CHANGE THIS- designate channels of white light (channels you want off during Storm and used for lightning strikes)
//use 1 to designate white channel. Array corresponds to PWM channel 0-5 in order
boolean Wchannel[]={1,0,1,0,0,0,0};
long int StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[18];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte count;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop
byte SeasonsVar[]={
0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, sunrisehour, sunriseminute, sunsethour, sunsetminute, Cloudstarthout, Cloudstartminute, Cloudduration,lightningchance, PWM1,PWM2,PWM3
byte cmdnum=255;
byte datanum=255;
boolean trigger; //used a few places as a switch to ensure things only run 1x when needed
byte strikePattern, strikeTime;//used in Lightning() for timing of particular instance of strike
boolean isDST, Cloud, Storm, CloudToday, StrikeNow; // what they seem, is it DST?, Are we in a cloud?
byte CloudsTotal;//required to know how large the array for CloudMaster will be- I need to use Vectors.... I know.
long int *pCloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Setup
void setup()
{
Serial.begin(57600);
Wire.begin(8);
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
randomSeed(analogRead(0));
pinMode(3,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
//wdt_enable(WDTO_1S);
setSyncProvider(RTC.get); // the function to get the time from the RTC
setSyncInterval(SECS_PER_HOUR); // Changed to sync every hour.
now();//why are you here?
CloudToday=false;
trigger=true;
}
//End Setup
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Loop
void loop()
{
wdt_reset();
//Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255){
ProcessCMD(cmdnum,datanum);
cmdnum=255;
datanum=255;
}
// now run through sunrise/sunset calculation and then intensity set and finally weather overlay
CalSun();
Insolation();
Weather();
if (StrikeNow==false){
StrikeStart=millis();
count=0;
}
else if (StrikeNow==true){
int intensity=0;//strike intensity value for each individual strike calculated once for each strike when index of strike changes i.e. a in for loop
int elapsed=millis()-StrikeStart;
if (elapsed>StrikeMaster[(StrikeNumber*2-1)]){
StrikeNow=false;
}
for (byte a=0;a<(StrikeNumber*2);a=(a+2)){ //StrikeNumber is the number of strikes generated this sequence so only look this far into array or we just hit zeros if its not a full stike seq
if ((elapsed>StrikeMaster[a])&&(elapsed<StrikeMaster[a+1])){
if (count!=(a+1)){
intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
}
count=(a+1);
for (byte a=0; a<6; a++){
if (Wchannel[a]==1) ChannelValue[a]=intensity;
}
}
else {
for (byte a=0; a<6; a++){
if (Wchannel[a]==1) ChannelValue[a]=0;
}
}
}
}
//Now that we have generated our sun patter, Weather as clouds, and possibly a storm, and possibly lightning each of which override the prior channel setting
//lets actually make some light.
for (byte a=0;a<6;a++){
analogWrite(PWMports[a],ChannelValue[a]);
}
// DEBUGGING serial comm... you can delete this if you dont want to use it.
/* Serial.println("DST");
Serial.println(isDST);
delay(500);
Serial.println("trigger");
Serial.println(trigger);
delay(500);
Serial.println("newDay");
Serial.println(newDay,DEC);
delay(500);
Serial.println("sunrise");
Serial.println(sunrise,DEC);
delay(500);
Serial.println("sunset");
Serial.println(sunset,DEC);
Serial.println("now()");
long secon;
secon=now();
Serial.println(secon,DEC);
delay(1000);
*/
}
//End Loop
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
void receiveEvent(int howMany) {
wdt_reset();
if (howMany==5){
byte cmd1, cmd2, cmd3, cmd4, cmd5;
cmd1=Wire.read();
cmd2=Wire.read();
cmd3=Wire.read();
cmd4=Wire.read();
cmd5=Wire.read();
if (cmd1=='$' && cmd2=='$' && cmd3=='$'){
cmdnum=cmd4;
datanum=cmd5;
//Serial.println(cmd4,DEC);
//Serial.println(cmd5,DEC);
}
}
else{
for (int a=0;a<howMany;a++){
Wire.read();
}
}
}
void ProcessCMD(byte cmd, byte data)
{
wdt_reset();
// Individual Channel
if (cmd>=0 && cmd<=5){
ChannelValue[cmd]=data;
analogWrite(PWMports[cmd],data);
}
if (cmd==6){
Storm=true;
}
}
//End Standard Functions
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
/*SeasonsVar[0]=ChannelValue[0];
SeasonsVar[1];
SeasonsVar[2];
SeasonsVar[3];
SeasonsVar[4];
SeasonsVar[5];
SeasonsVar[6];
SeasonsVar[7];
SeasonsVar[8];
SeasonsVar[9];
SeasonsVar[10];
SeasonsVar[11];
Wire.write(SeasonsVar,12);
*/
}
//End Send Data
//*********************************************************************************************************************************
// Function to run Epherma Calc for sunrise/sunset/noon/moon phase
void CalcDST(byte D, byte M, byte dow)
{
//January, february, and december are out.
if (M < 3 || M > 11){
isDST=false;
}
//April to October are in
else if (M > 4 && M < 11){
isDST=true;
}
else{
int previousSunday = D - dow; // Sunday=1 Sat=7
if (M==3 && previousSunday > 7){
isDST=true;
}
else if (M==11 && previousSunday<=0){
isDST=false;
}
}
}
void CalSun()
{
//this entire function runs only based upon the first if being true... so its a LONG ways down to the else if trigger is false statement to finish this if
// Every day at midnight, we calculate sunrise, sunset, time of highest sun, moon phase, or we do this if system has reset
if ((hour()==0 && minute()==0 && second()==0) || (trigger==true)){
Serial.println("trigger 1");
Serial.println(trigger);
long int SecInput;
// Arduino uses Unix time- seconds since Jan 1 12:00 am UTC 1970
//Must convert to MST by adding 7 hrs =7*3600=25,200 seconds MST=GMT-7
//void CalcDST(day(),month(),weekday()) modifies isDST memory to appropriate true/false based upon USA rules
if (ApplyDST==true){
CalcDST(day(),month(),weekday());
}
//Using time library from Arduino.time_t now(); returns seconds since 1970
//Library states #seconds to subtrace to bring to Jan 1 2000 as origin for time is
//#define SECS_YR_2000 (946684800) the time at the start of y2k
//DST rules are march second sunday until november 1st sunday + 1 hr starting at 2 am which I dont care about so that part is neglected
byte hours, minutes;
if ((ApplyDST==true)&&(isDST==true)){
time_t t=now()+3600;
hours=hour(t), minutes=minute(t);
if (hours!=0 || minutes!=0){
newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
SecInput=newDay+25200;
}
else{
newDay=(now()-(946684800+3600));
SecInput=newDay+25200;
}
}
else {
time_t t=now();
hours=hour(t), minutes=minute(t);
if (hours!=0 || minutes!=0){
newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your sunrise being off by <60 seconds on a day of a power failure... your more anal than I
SecInput=newDay+25200;
}
else{
newDay=(now()-946684800);
SecInput=newDay+25200;
}
}
//Calculate Latitude and Longitude converting from Degree, Minute, Seconds to seconds
latitude=dmsToSeconds(40,45,39); //Set to about the latitude of the great barrier reef rotated into the morthern hemisphere
longitude=dmsToSeconds(-111,53,25); //Set to Salt lake City, or MST zone, USA or there abouts- actually the AT921 remote weather station in Salt Lake City UT. Random web grab.
//Calculate sunrise time and sunset time using Epherma Library
sunrise=SecInput;
sunset=SecInput;
SunRise(&sunrise);
SunSet(&sunset);
// Correct sunrise data to reflect elapsed time from midnight (newDay)
// DST correction is all ready in both variables so no need to deal with it
sunrise-=25200; //Correct back to MST from GMT- DST still accounted for
sunset-=25200;
sunrise-=newDay;//set to elapsed seconds today
sunset-=newDay;
Serial.println("sunrise");
Serial.println(sunrise);
Serial.println("sunset");
Serial.println(sunset);
//*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
//offsets for sunrise/sunset all values in seconds offset from calculated sunrise or sunset value
//array order is ch0 am offset, pm offset, ch1 am offset, pm offset etc..
//THESE values are the number of seconds that a particular channel will be offset from the sunrise/sunset time, i.e. negative to rise earlier/set earlier
int Choffset[]={
-600,0,-3600,5400,0,600,-3600,5400,-4500,7200,0,0};
//**********************ok now were done changing things here********************************************
//Calculate rise and set times for all channels in equivlants to elapsed seconds from midnight today
//populate array for chRise and Set as well as chSlope for 0.5pi/ half day lenght for each channel from midday (asymmetric days are allowed)
float deltaY=2/3.141592653589793238462643383279502884197169399;
midDay=(sunset-sunrise)/2;
for (byte b=0;b<12;b++){//working as of April 5 2012 serial tested
if (b%2==0){
ChRiseSet[b]=sunrise+Choffset[b];
ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
/*Serial.print("Rise/Set a=");
Serial.println(b);
Serial.print("Value=");
Serial.print(ChRiseSet[b]);
Serial.print("Slope");
Serial.println(ChSlope[b]);*/
}
else if (b%2==1){
ChRiseSet[b]=sunset+Choffset[b];
ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
/*Serial.print("Rise/Set a=");
Serial.println(b);
Serial.print("Value=");
Serial.print(ChRiseSet[b]);
Serial.print("Slope");
Serial.println(ChSlope[b]);*/
}
}
//***************** to CHANGE THE chance of Clouds actually occuring on a given day************************
byte CloudChance=70;//% Chance of a Cloud every day
//****************************now were done- did you use a value from 0-100 without a decimal?*****************
//once a day, after sunrise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
randomSeed(now()-(random(300000000,1000000000))); //so that we are now more trully random
byte RainMaker=random(1,101);
if (RainMaker<CloudChance){
CloudToday=true;
}
else if (RainMaker>=CloudChance){
CloudToday=false;
trigger=false;
return;
}
// to "simplify" things and avoid redundant calculation loops all cloud times are in fraction of day lengths
//*********** this is only executed 1x /day unless power outage and ONLY if last statement is true****************
///ALl cloud set up is done here... weather subroutine just implements it
/*The general strategy for this algorithim is as follows. Calculate random cloud cover as percent of day,
then randomly generate the # of discreet cloud instances for the day,
then determine cloud length by (daylight seconds * percent overcast)/#clouds
then randomize cloud lengths by splitting into groups of twos correcting for if we have odd # of clouds today*/
long int dayLength=(sunset-sunrise);
Serial.println("DayLength");
Serial.println(dayLength);
byte OvercastMin=10;//% as fraction (0-1 is 0-100%) of daylight hours that would minimially be cloudy on a cloud day
byte OvercastMax=31;//% of daylight hours that would maximally be cloudy on a cloud day (then add 1 to that #)
float Overcast=random(OvercastMin,OvercastMax);
Overcast=(Overcast/100);
Serial.println("Overcast");
Serial.println(Overcast);
// number of clouds possible for the day, max and min
byte CloudsMax=9;
byte CloudsMin=4;//dont use 1 it may or may not work in the next loops and the cloud will likely be very long, i.e. daylength*overcast fraction
CloudsTotal=random(CloudsMin,(CloudsMax+1));
Serial.println("CloudsTotal");
Serial.println(CloudsTotal);
static long int CloudMaster[15];// Set up array to hold start and end times for clouds for the day- dont size it dynamically make it big enough for any day
pCloudPoint=&CloudMaster[0];//use address of pointer to find cloud data in weather, array is declared static to persist throughout day.
// set up start time for clouds
/*The way this is done is to split the total lenght of time for clouds into equal segments and then to randomly chop or add time to the
segments such that cloud length is variable. Then distribute into random parts of the day and fill array with start,duration pairs for clouds*/
long int CloudLength;
CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
float fraction=random(20,181);
fraction=(fraction/100);//vary each cloud from 20%-180% of an equal fraction of total cloud for day
Serial.print("fraction ");
Serial.println(fraction);
Serial.print("CloudLength ");
Serial.println(CloudLength);
//using fraction to vary cloud length throughout day fill array CloudMaster[] at positions 0,2,4,6 to full with durations in seconds
//this way the MAX cloud duration is 180% of the average cloud druation while the min is 20%
byte b=0;//set up counter to alter cloud length and initialize to zero outside of loop
for (byte a=0; a<CloudsTotal; a++){
int CurrentCloudLength;
// if were having an odd # of clouds make sure last one is full length
if ((CloudsTotal%2!=0) && (a==(CloudsTotal-1))){
CloudMaster[a*2]=CloudLength;
Serial.print("last odd cloud Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
break;
}
// else we make clouds variable in length short then long repeated with every two cloud sets being different in short and long but adding up equally by sets
else {
if (fraction<1){
CurrentCloudLength=abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
CloudMaster[a*2]=CurrentCloudLength;
Serial.print("Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
Serial.print("b=");
Serial.println(b);
}
if (fraction==1){
CloudMaster[a*2]=CloudLength;
Serial.print("Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
Serial.print("b=");
Serial.println(b);
}
if (fraction>1){
CurrentCloudLength=((CloudLength*fraction)-(b*CloudLength*fraction))+(b*((2-fraction)*CloudLength));
CloudMaster[a*2]=CurrentCloudLength;
Serial.print("Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
Serial.print("b=");
Serial.println(b);
}
}
b=b+1;
if (b==2){
b=0;
fraction=random(20,181);//every other cloud recalculate a new fractional distribution for the next set
fraction=(fraction/100);
}
}
randomSeed(now());
// Now space the clouds out during the day using random fractionation of day segments as per cloud calculation
b=0;//reset counter used for fraction reset
byte c=0;//counter for indexing # of itterations in loop
long int StartTime;//Used in loop to assign cloud spacing, i.e. these loop values determine daylight interval between clouds
fraction=random(10,181);
fraction=(fraction/100);
for (byte a=1; a<CloudsTotal; a=(a+2){
//calculate amount of sun during day (i.e. total day lenth - portion cloudy), divide into equal parts then randomly initiate cloud within sun segment for each part of the day.
// must index segment time by +1 segment for each cloud completed
long int SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));
if (fraction<1){
abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)))
StartTime=sunrise+((SunSegment*b+abs(b*SunSegment-(SunSegment*fraction)))+(c*SunSegment));
CloudMaster[a]=StartTime;
StartTime=0;
b=b+1;
}
else if (fraction==1){
StartTime=sunrise+(SunSegment+(SunSegment*c));
CloudMaster[a]=StartTime;
StartTime=0;
b=2;//by doing this I ensure that new fraction will be calculated so next interval will not be 1 (or rarely so)
}
else if (fraction>1){
StartTime=sunrise+(((SunSegment*fraction)-(b*SunSegment*fraction))+(b*((2-fraction)*SunSegment))+(c*SunSegment));
CloudMaster[a]=StartTime;
StartTime=0;
b=b+1;
}
if (b==2){
b=0;
fraction=random(20,181);//every other cloud recalculate a new fractional distribution for the next set
fraction=(fraction/100);
}
c++; //index loop counter by one now
}
Serial.println("now we print start and end times for clouds");
//just reframe CloudMaster into array of start and end times for clouds (CLoudMaster[]={start,end,start,end,start,end} as progression through day in elapsed seconds from midnight.
for (byte a=0; a<(CloudsTotal*2);a=(a+2)){
long int endT, startT;
startT=CloudMaster[(a+1)];
endT=CloudMaster[a]+startT;
CloudMaster[a]=startT;
CloudMaster[a+1]=endT;
Serial.println(CloudMaster[a]);
Serial.println(CloudMaster[a+1]);
}
trigger=false;
Serial.println("Trigger 2");
Serial.println(trigger);
}//Finally end loop if trigger is true of hr=0 min=0 sec=0 .... i.e. basically entire function runs only 1x per day or on restart of controller
else if (trigger==false){
return;
}
}//END FUNCTION
void Insolation()
{
// Calculate time since day started correcting for DST
if (isDST==true){
elapsedTime=((now()-946684800)+3600)-newDay;
}
else{
elapsedTime=(now()-946684800)-newDay;
}
if (elapsedTime%2==0){//only change lights every 2 seconds
Serial.println("Insolation 2 sec elapsed");
//Serial.print("Elapsed Time is=");
Serial.println(elapsedTime);
//Serial.print(and now converted to new day should be");
//long int blha= (now()-946684800+3600);
//Serial.println(blha);
//define Pi as delta Y for slope since cos 0.5-1.5 Pi goes 0-1-0 in 0.5 pI increments slope of 1/2 day (0-1 intensity) delta Y is 1/2 Pi
float deltaY=3.141592653589793238462643383279502884197169399;
/* using -cos(pi/2+elapsedTime/slope)*((ChMaxVal-flicker)+flicker) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light. Turn off lights after sunset or before sunrise etc.
by splitting into half days centered on midday (1/2 ofsunset-sunrise) we center exactly the cos function for every channel so color blends are maintained
throughout intensity ramp... more or less ... change intensity every 30 seconds throughout the day*/
for (byte b=0;b<12;b++){
if ((b%2==0) && (elapsedTime>=ChRiseSet[b]) && (elapsedTime<midDay)){
if (b==0){ ChannelValue[0]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b])+flicker[b]));
Serial.print("ch0 setting first 1/2 day=");
Serial.println(ChannelValue[0]);}
else if (b==2) ChannelValue[1]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b-1]-flicker[b-1])+flicker[b-1]));
else if (b==4) ChannelValue[2]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b-2]-flicker[b-2])+flicker[b-2]));
else if (b==6) ChannelValue[3]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b-3]-flicker[b-3])+flicker[b-3]));
else if (b==8) ChannelValue[4]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b-4]-flicker[b-4])+flicker[b-4]));
else if (b==10) ChannelValue[5]=(-cos((deltaY/2)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b-5]-flicker[b-5])+flicker[b-5]));
}
else if ((b%2==1) && (elapsedTime<=ChRiseSet[b]) && (elapsedTime>=midDay)){
if (b==1){ChannelValue[0]=(-cos((deltaY)+(elapsedTime-sunrise/ChSlope[b]))*((ChInt[b]-flicker[b-1])+flicker[b-1]));
Serial.print("ch0 setting second 1/2 day=");
Serial.println(ChannelValue[0]);}
else if (b==3) ChannelValue[1]=(-cos((deltaY)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-2])+flicker[b-2]));
else if (b==5) ChannelValue[2]=(-cos((deltaY)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-3])+flicker[b-3]));
else if (b==7) ChannelValue[3]=(-cos((deltaY)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-4])+flicker[b-4]));
else if (b==9) ChannelValue[4]=(-cos((deltaY)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-5])+flicker[b-5]));
else if (b==11) ChannelValue[5]=(-cos((deltaY)+((elapsedTime-sunrise)/ChSlope[b]))*((ChInt[b]-flicker[b-6])+flicker[b-6]));
}
else if (((b%2==0) && (elapsedTime<ChRiseSet[b])) || ((b%2==1) && (elapsedTime>ChRiseSet[b]))){
Cloud=false;//no lights = no Cloud
Storm=false;
Serial.println("were not in daylight");
if ((b==0) || (b==1)) ChannelValue[0]=0;
else if ((b==2) || (b=3)) ChannelValue[1]=0;
else if ((b==4) || (b=5)) ChannelValue[2]=0;
else if ((b==6) || (b=7)) ChannelValue[3]=0;
else if ((b==8) || (b=9)) ChannelValue[4]=0;
else if ((b==10) || (b=11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
}
}
/* Serial.println(ChannelValue[0]);
Serial.println(ChannelValue[1]);
Serial.println(ChannelValue[2]);
Serial.println(ChannelValue[3]);
Serial.println(ChannelValue[4]);
Serial.println(ChannelValue[5]);*/
}
}//END function
//This is where modification of insolation by clouds or a storm occurs
//Its worth noting that the clouds simply modify (depending on how much cloud cover there is) the existing light set by insolation as a fractional value
void Weather ()
{
static int CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static int PriorCloudCover; //used to "delay" one side of the tank from the other in cloud passing effects
static long int StormStart;
static long int StormEnd;
static long int CloudEnd;
static boolean wtrigger;//use to track the first run of functions to calculate random times that become fixed, see change from cloud to storm for useage
// see insolation elapsed time function for assigmnent of Cloud start times and set of Cloud=true
//check to see if were having a scheduled cloud
if ((Cloud==true) && (Storm==false)){
if ((elapsedTime%2)!=0){
return;
}
else if ((elapsedTime%2)==0){// EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
Serial.println("in a cloud");
//now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times up
//seems backwards since CloudCover has not been calculated if you read linearly but it will always be set prior to this=true
if ((elapsedTime)>=CloudEnd){
ClearSky(CloudCover, CloudEnd);
return;
}
/*Use fractional intensity to set minimum value for any channel its WAY to confusing to try to set up otherwise. Dimming is proportional to actual intensity output
and constrained by flicker point. Random walk uses static variable "CloudCover" constrained to 0-100 to represent fractional intensity (i.e. (CloudCover/100)*Insolation SetPoint
is how the current cloud intensity is set*/
randomSeed(millis());
byte stepsize=random(-10,11);
PriorCloudCover=CloudCover;
CloudCover = CloudCover + stepsize;
if (CloudCover>=80){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms change this value to a lower number
Storm=true;
wtrigger=true;
StormStart=now();
}
else if (CloudCover<=0){
CloudCover-=(stepsize*1.5);
}
//Array to give direction to dimming. e.g. DimOrder[]={0,0,1,1,0,0} would dim PWM channels 0-1,4-5 as FIRST dim value, then channels 2-3 would take on FIRST dime Value
//when channels 0-1,4-5 take on SECOND dim value, so the values chase eachother across the PWM output light array according to your set up. I have 0-1 as RIGHT side lights,
//2-3 as LEFT side LED, and 4 as whole tank while I do not use 5
byte DimOrder[]={0,0,1,1,0,0,};
for (int a=0;a<6;a++){
if (DimOrder[a]==0){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);
}
if (DimOrder[a]==1){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);
}
}
}
}
//enable a flag sent from controller to triger a storm, i.e. Storm=true
// set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
else if (((Cloud=false) && (Storm=true)) || ((Cloud=true) && (Storm=true))){
//do this next part exactly once per storm then loop past
if (wtrigger==true){
int LongestStorm=((CloudEnd-StormStart)-120);//remove the last 2 minutes I use to clear the sky from the longest possible storm duration
int StormDuration=random((LongestStorm/4),(LongestStorm+1));//set (min,max) seconds any single Storm can exist.
StormStart=(now()-newDay);
StormEnd=((StormStart+StormDuration)+(now()-newDay));
wtrigger=false;
}
//Every 1 second duing a storm change intensity, clouds are movin fast baby
if ((elapsedTime%1)!=0){
return;
}
else if ((elapsedTime%1)==0){// EVERY one second change the PWM output for a storm
if (elapsedTime>=CloudEnd){ //if we randomly had a storm go to cloud end (which is possible but will be rare)
ClearSky(CloudCover, CloudEnd);
return;
}
else if (elapsedTime>=StormEnd){ //if were done with the storm but still cloudy
Storm=false;
return;
// step through intensity values
randomSeed(millis());
byte stepsize=random(-15,16);
if ((stepsize<-12) || (stepsize>12)){ //it is convient to use this random call to generate a strike during a storm for more frequent strikes adjust down
randomSeed(now()-newDay); //start random with a new value to avoid repetitive stike generations
StrikeNumber=(random(2,10)); //random high =x-1 so max strike =9 and array=17 for StrikeMaster
int StartStrike=0;
//Rather than bother with Vector and dynamic allocation with push/pull use an array big enough to accomadate the largest strike pattern and then
//ensure the array is zeroed out past the last position required for this strike pattern so each pattern is only as long as generated
//Array is Strike Start millis, strike end millis, etc... total of StrikeNumber pairs.
for (byte a=0;a<18;a++){
if (a>=(StrikeNumber*2)){
StrikeMaster[a]=0;
}
else if (a%2==0){
StartStrike+=random(100,701);//position 0,2,4,6,8.. is strike delay
StrikeMaster[a]=StartStrike;
}
else if (a%2!=0){
StartStrike+=random(30,81);//position 1,3,5,7,9... is strike duration- added to StartStrike=StrikeEnd
StrikeMaster[a]=StartStrike;
}
}
StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
}
PriorCloudCover=CloudCover;
CloudCover = CloudCover + stepsize;
if (CloudCover>=100){//conflicted here, since were dimming by fractional intenstiy its likely we will hit flicker point or lower... might change how this works
CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
}
else if (CloudCover<=0){
CloudCover-=(stepsize*1.5);//if were clear sky path reflect random path to cloud
}
//*****************IF You want the light to "chase" across the tank****************************
//you need to specify the left or right side (or front back whatever, depends on how your lights are set up)
//Use either 0 or 1 to specify groups of channels that dim together (arbitrary but if you need to know direction the cloud will travel you will need to read the next bit of code)
//assign the same 0 or 1 value to all left side (or front back or whatever) channels and the other value to all right side channels
byte DimOrder[]={0,0,1,1,0,0,};
//****************** ok were done changing things*****************************
for (int a=0;a<6;a++) {
if (Wchannel[a]==1){
ChannelValue[a]=0;
continue;
}
if (DimOrder[a]==0){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);
}
else if (DimOrder[a]==1){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);
}
}
}
}
}//end of storm if loop
// Place this loop last as it will only run then cloud is over, and trigger next cloud start and end, this way its not computing during a cloud.
else {
for (byte a=0; a<=(CloudsTotal-1); a=(a+2)){
if ((elapsedTime>=*(pCloudPoint+a)) && (elapsedTime<=*(pCloudPoint+(a+1)))){
CloudEnd=*(pCloudPoint+(a+1));
CloudEnd=CloudEnd+120;
Cloud=true;
}
else{
Cloud=false;
CloudCover=0;
PriorCloudCover=0;
}
}
}
}//End Weather function
//THE LAST 2 mins of ANY Storm, or cloud, are used to ramp linearlly to full daylight setting, whatever that is at the given time of day
void ClearSky(byte CloudCover, int CloudEnd)
{
int elapsedTime=(now()-newDay);
// no need to restrict time to avoid resource hogging, its only called every x seconds from weather.
//I AM ASSUMING THAT everything calling this does so with 120 seconds left in the cloud/storm etc to clear the sky.
//if you do this incorrectly (i.e. change this and dont change it everywhere I am not sure what would happen for certain, but it may cause issues... almost certainly will
int remaining=(CloudEnd-elapsedTime);
int Range=(CloudCover/100);
int slope=(Range/120);
if (remaining<=0){
Storm=false;
Cloud=false;
return;
}
for (byte a=0; a<6; a++){
ChannelValue[a]=(ChannelValue[a]*((120-remaining)*slope));
}
}//End Clear Sky function
Upload to youtube and then when you create a post, use the "youtube" button to place the link inside it.rufessor wrote:Ironic thought.... I may find that I seriously dislike my cloud effect, or that I cannot really see it... that would be so awesome after all this work. At least I accomplished my goal of learning c++ a bit. If I can get it to run cleanly I will be happy, even happier if I like it!
Whats the best way to post a video?
Code: Select all
//AS OF 3/15/12 this code is completely UNTESTED beyond compile error checking- and will likely NOT RUN.
#include <Time.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <Globals.h>
#include <ReefAngel_Features.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <avr/wdt.h>
#include <SWFLTEK_EPHERMA.h>
//Defines for ReefAngel PWM module
#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5
//*********************************************************************************************************************************
//IF YOU DO NOT WISH TO USE the U.S.A. Day Light Savings Time rules to modify the time stamp for rise/set calculations...set to false- i.e. if you dont live in the USA etc
boolean ApplyDST=true;
long elapsedTime;//used multiple places as elapsed since new day.
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long rise;
unsigned long set;
unsigned long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long ChRiseSet[12];
float ChSlope[12];
unsigned long midDay;// exactly 1/2 way between rise and set, i.e. my take on solar noon- which its not...
byte PWMports[] ={
3,5,6,9,10,11};
byte ChannelValue[6];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)
//What are the max values for ch intensity we want to reach, i.e. at and around solar noon- use 0-255 scale for PWM board byte output (ch0,1,2,3,4,5)
//i.e. 100% channel 0, ~10% channel 1, 40% channel 3 would be {255, 25, 102, ...etc}
byte ChInt[]={
250,250,250,250,250,0};
// At what point in PWM dimming do your individual channels flicker, input this as PWM output value into the array -NOT % intensity, use actual PWM output(ch0,1,2,3,4,5)
byte flicker[]={
25,25,25,25,15,0};
//YOU MUST CHANGE THIS- designate channels of white light (channels you want off during Storm and used for lightning strikes)
//use 1 to designate white channel. Array corresponds to PWM channel 0-5 in order
boolean Wchannel[]={1,0,1,0,0,0,0};
long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[18];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte count;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop
byte SeasonsVar[]={
0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, risehour, riseminute, sethour, setminute, Cloudstarthout, Cloudstartminute, Cloudduration,lightningchance, PWM1,PWM2,PWM3
byte cmdnum=255;
byte datanum=255;
boolean trigger; //used a few places as a switch to ensure things only run 1x when needed
byte strikePattern, strikeTime;//used in Lightning() for timing of particular instance of strike
boolean isDST, Cloud, Storm, CloudToday, StrikeNow; // what they seem, is it DST?, Are we in a cloud?
byte CloudsTotal;//required to know how large the array for CloudMaster will be- I need to use Vectors.... I know.
long *pCloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Setup
void setup()
{
Serial.begin(57600);
Wire.begin(8);
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
randomSeed(analogRead(0));
pinMode(3,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
//wdt_enable(WDTO_1S);
setSyncProvider(RTC.get); // the function to get the time from the RTC
setSyncInterval(SECS_PER_HOUR); // Changed to sync every hour.
now();//why are you here?
CloudToday=false;
trigger=true;
}
//End Setup
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Loop
void loop()
{
wdt_reset();
//Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255){
ProcessCMD(cmdnum,datanum);
cmdnum=255;
datanum=255;
}
// now run through rise/set calculation and then intensity set and finally weather overlay
CalSun();
Insolation();
Weather();
if (StrikeNow==false){
StrikeStart=millis();
count=0;
}
else if (StrikeNow==true){
int intensity=0;//strike intensity value for each individual strike calculated once for each strike when index of strike changes i.e. a in for loop
int elapsed=millis()-StrikeStart;
if (elapsed>StrikeMaster[(StrikeNumber*2-1)]){
StrikeNow=false;
}
for (byte a=0;a<(StrikeNumber*2);a=(a+2)){ //StrikeNumber is the number of strikes generated this sequence so only look this far into array or we just hit zeros if its not a full stike seq
if ((elapsed>StrikeMaster[a])&&(elapsed<StrikeMaster[a+1])){
if (count!=(a+1)){
intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
}
count=(a+1);
for (byte a=0; a<6; a++){
if (Wchannel[a]==1) ChannelValue[a]=intensity;
}
}
else {
for (byte a=0; a<6; a++){
if (Wchannel[a]==1) ChannelValue[a]=0;
}
}
}
}
//Now that we have generated our sun patter, Weather as clouds, and possibly a storm, and possibly lightning each of which override the prior channel setting
//lets actually make some light.
for (byte a=0;a<6;a++){
analogWrite(PWMports[a],ChannelValue[a]);
}
}
//End Loop
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
void receiveEvent(int howMany) {
wdt_reset();
if (howMany==5){
byte cmd1, cmd2, cmd3, cmd4, cmd5;
cmd1=Wire.read();
cmd2=Wire.read();
cmd3=Wire.read();
cmd4=Wire.read();
cmd5=Wire.read();
if (cmd1=='$' && cmd2=='$' && cmd3=='$'){
cmdnum=cmd4;
datanum=cmd5;
//Serial.println(cmd4,DEC);
//Serial.println(cmd5,DEC);
}
}
else{
for (int a=0;a<howMany;a++){
Wire.read();
}
}
}
void ProcessCMD(byte cmd, byte data)
{
wdt_reset();
// Individual Channel
if (cmd>=0 && cmd<=5){
ChannelValue[cmd]=data;
analogWrite(PWMports[cmd],data);
}
if (cmd==6){
Storm=true;
}
}
//End Standard Functions
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
/*SeasonsVar[0]=ChannelValue[0];
SeasonsVar[1];
SeasonsVar[2];
SeasonsVar[3];
SeasonsVar[4];
SeasonsVar[5];
SeasonsVar[6];
SeasonsVar[7];
SeasonsVar[8];
SeasonsVar[9];
SeasonsVar[10];
SeasonsVar[11];
Wire.write(SeasonsVar,12);
*/
}
//End Send Data
//*********************************************************************************************************************************
// Function to run Epherma Calc for rise/set/noon/moon phase
void CalcDST(byte D, byte M, byte dow)
{
//January, february, and december are out.
if (M < 3 || M > 11){
isDST=false;
}
//April to October are in
else if (M > 4 && M < 11){
isDST=true;
}
else{
int previousSunday = D - dow; // Sunday=1 Sat=7
if (M==3 && previousSunday > 7){
isDST=true;
}
else if (M==11 && previousSunday<=0){
isDST=false;
}
}
}
void CalSun()
{
//this entire function runs only based upon the first if being true... so its a LONG ways down to the else if trigger is false statement to finish this if
// Every day at midnight, we calculate rise, set, time of highest sun, moon phase, or we do this if system has reset
if ((hour()==0 && minute()==0 && second()==0) || (trigger==true)){
Serial.println("trigger 1");
Serial.println(trigger);
unsigned long SecInput;
//void CalcDST(day(),month(),weekday()) modifies isDST memory to appropriate true/false based upon USA rules
if (ApplyDST==true){
CalcDST(day(),month(),weekday());
}
//Using time library from Arduino.time_t now(); returns seconds since 1970
//Library states #seconds to subtract to bring to Jan 1 2000 as origin for time is
//#define SECS_YR_2000 (946684800) the time at the start of y2k
long hours;//possible to be reset at 23 hours*3600=long required
long minutes;// int is enough as 60*60 is max val but math is conducted with possibility of roll over in intermediate so to be sure.. use long
time_t t=now();
hours=hour(t);
minutes=minute(t);
if ((ApplyDST==true)&&(isDST==true)){
if ((hours!=0) || (minutes!=0)){
hours=hours+1;
newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your rise being off by <60 seconds on a day of a power failure... your more anal than I
Serial.print("DST Hours,Min");
Serial.print(hours);
Serial.print(",");
Serial.println(minutes);
}
else{
newDay=(now()-946684800+3600);
}
}
else if ((isDST==false) || (ApplyDST==false)){
if (hours!=0 || minutes!=0){
newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your rise being off by <60 seconds on a day of a power failure... your more anal than I
Serial.print("Hours,Min");
Serial.print(hours);
Serial.print(",");
Serial.println(minutes);
}
}
SecInput=((newDay)+25200);//modify to GMT time (i.e. MST + 7 hours = GMT)
//Calculate Latitude and Longitude converting from Degree, Minute, Seconds to seconds
latitude=dmsToSeconds(40,45,39); //Set to about the latitude of the great barrier reef rotated into the morthern hemisphere
longitude=dmsToSeconds(-111,53,25); //Set to Salt lake City, or MST zone, USA or there abouts- actually the AT921 remote weather station in Salt Lake City UT. Random web grab.
//Calculate rise time and set time using Epherma Library
rise=SecInput;
set=SecInput;
SunRise(&rise);//call to Epherma Library using reference- memory is modified by Epherma
SunSet(&set);//Call to Epherma library using reference- memory position is modified by Epherma
rise-=25200; //Correct back to MST from GMT- DST still accounted for
set-=25200;
Serial.print("newDay=");
Serial.println(newDay);
Serial.print("Local uncorrected rise, set=");
Serial.print(rise);
Serial.print(",");
Serial.print(set);
rise=(rise-newDay);//set to elapsed seconds today
set=(set-newDay);
Serial.println("Local Elapsed Seconds from NewDay--rise=");
Serial.println(rise);
Serial.println("Local Elapsed Seconds from NewDay--set=");
Serial.println(set);
//*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
//offsets for rise/set all values in seconds offset from calculated rise or set value
//array order is ch0 am offset, pm offset, ch1 am offset, pm offset etc..
//THESE values are the number of seconds that a particular channel will be offset from the rise/set time, i.e. negative to rise earlier/set earlier
int Choffset[]={
-600,0,-3600,5400,0,600,-3600,5400,-4500,7200,0,0};
//**********************ok now were done changing things here********************************************
//Calculate rise and set times for all channels in equivlants to elapsed seconds from midnight today
//populate array for chRise and Set as well as chSlope for 0.5pi/ half day lenght for each channel from midday (asymmetric days are allowed)
float deltaY=1.57080;//1/2 * pi
midDay=(((set-rise)/2)+rise);
Serial.print("MidDay");
Serial.println(midDay);
for (byte b=0;b<12;b++){//working as of April 5 2012 serial tested
if (b%2==0){
ChRiseSet[b]=rise+(Choffset[b]);
ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
Serial.print("Rise/Set a=");
Serial.println(b);
Serial.print("Value=");
Serial.print(ChRiseSet[b]);
Serial.print("Slope");
Serial.println(ChSlope[b]);
}
else if (b%2==1){
ChRiseSet[b]=set+
(Choffset[b]);
ChSlope[b]=deltaY/(midDay+abs(Choffset[b]));
Serial.print("Rise/Set a=");
Serial.println(b);
Serial.print("Value=");
Serial.print(ChRiseSet[b]);
Serial.print("Slope");
Serial.println(ChSlope[b]);
}
}
//***************** to CHANGE THE chance of Clouds actually occuring on a given day************************
byte CloudChance=70;//% Chance of a Cloud every day
//****************************now were done- did you use a value from 0-100 without a decimal?*****************
//once a day, after rise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
randomSeed(now()-(random(300000000,1000000000))); //so that we are now more trully random
byte RainMaker=random(1,101);
if (RainMaker<CloudChance){
CloudToday=true;
}
else if (RainMaker>=CloudChance){
CloudToday=false;
trigger=false;
return;
}
// to "simplify" things and avoid redundant calculation loops all cloud times are in fraction of day lengths
//*********** this is only executed 1x /day unless power outage and ONLY if last statement is true****************
///ALl cloud set up is done here... weather subroutine just implements it
/*The general strategy for this algorithim is as follows. Calculate random cloud cover as percent of day,
then randomly generate the # of discreet cloud instances for the day,
then determine cloud length by (daylight seconds * percent overcast)/#clouds
then randomize cloud lengths by splitting into groups of twos correcting for if we have odd # of clouds today*/
long dayLength=(set-rise);
Serial.println("DayLength");
Serial.println(dayLength);
byte OvercastMin=10;//% as fraction (0-1 is 0-100%) of daylight hours that would minimially be cloudy on a cloud day
byte OvercastMax=31;//% of daylight hours that would maximally be cloudy on a cloud day (then add 1 to that #)
float Overcast=random(OvercastMin,OvercastMax);
Overcast=(Overcast/100);
Serial.println("Overcast");
Serial.println(Overcast);
// number of clouds possible for the day, max and min
byte CloudsMax=9;
byte CloudsMin=4;//dont use 1 it may or may not work in the next loops and the cloud will likely be very long, i.e. daylength*overcast fraction
CloudsTotal=random(CloudsMin,(CloudsMax+1));
Serial.println("CloudsTotal");
Serial.println(CloudsTotal);
static long CloudMaster[15];// Set up array to hold start and end times for clouds for the day- dont size it dynamically make it big enough for any day
pCloudPoint=&CloudMaster[0];//use address of pointer to find cloud data in weather, array is declared static to persist throughout day.
// set up start time for clouds
/*The way this is done is to split the total lenght of time for clouds into equal segments and then to randomly chop or add time to the
segments such that cloud length is variable. Then distribute into random parts of the day and fill array with start,duration pairs for clouds*/
long CloudLength;
CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
float fraction=random(20,181);
fraction=(fraction/100);//vary each cloud from 20%-180% of an equal fraction of total cloud for day
Serial.print("fraction ");
Serial.println(fraction);
Serial.print("CloudLength ");
Serial.println(CloudLength);
//using fraction to vary cloud length throughout day fill array CloudMaster[] at positions 0,2,4,6 to full with durations in seconds
//this way the MAX cloud duration is 180% of the average cloud druation while the min is 20%
byte b=0;//set up counter to alter cloud length and initialize to zero outside of loop
for (byte a=0; a<CloudsTotal; a++){
int CurrentCloudLength;
// if were having an odd # of clouds make sure last one is full length
if ((CloudsTotal%2!=0) && (a==(CloudsTotal-1))){
CloudMaster[a*2]=CloudLength;
Serial.print("last odd cloud Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
break;
}
// else we make clouds variable in length short then long repeated with every two cloud sets being different in short and long but adding up equally by sets
else {
if (fraction<1){
CurrentCloudLength=abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
CloudMaster[a*2]=CurrentCloudLength;
Serial.print("Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
Serial.print("b=");
Serial.println(b);
}
if (fraction==1){
CloudMaster[a*2]=CloudLength;
Serial.print("Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
Serial.print("b=");
Serial.println(b);
}
if (fraction>1){
CurrentCloudLength=((CloudLength*fraction)-(b*CloudLength*fraction))+(b*((2-fraction)*CloudLength));
CloudMaster[a*2]=CurrentCloudLength;
Serial.print("Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
Serial.print("b=");
Serial.println(b);
}
}
b=b+1;
if (b==2){
b=0;
fraction=random(20,181);//every other cloud recalculate a new fractional distribution for the next set
fraction=(fraction/100);
}
}
randomSeed(now());
// Now space the clouds out during the day using random fractionation of day segments as per cloud calculation
b=0;//reset counter used for fraction reset
byte c=0;//counter for indexing # of itterations in loop
long StartTime;//Used in loop to assign cloud spacing, i.e. these loop values determine daylight interval between clouds
fraction=random(10,181);
fraction=(fraction/100);
for (byte a=1; a<(CloudsTotal*2); a=(a+2)){
//calculate amount of sun during day (i.e. total day lenth - portion cloudy), divide into equal parts then randomly initiate cloud within sun segment for each part of the day.
// must index segment time by +1 segment for each cloud completed
long SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));
if (fraction<1){
abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
StartTime=rise+((SunSegment*b+abs(b*SunSegment-(SunSegment*fraction)))+(c*SunSegment));
CloudMaster[a]=StartTime;
StartTime=0;
b=b+1;
}
else if (fraction==1){
StartTime=rise+(SunSegment+(SunSegment*c));
CloudMaster[a]=StartTime;
StartTime=0;
b=2;//by doing this I ensure that new fraction will be calculated so next interval will not be 1 (or rarely so)
}
else if (fraction>1){
StartTime=rise+(((SunSegment*fraction)-(b*SunSegment*fraction))+(b*((2-fraction)*SunSegment))+(c*SunSegment));
CloudMaster[a]=StartTime;
StartTime=0;
b=b+1;
}
if (b==2){
b=0;
fraction=random(20,181);//every other cloud recalculate a new fractional distribution for the next set
fraction=(fraction/100);
}
c++; //index loop counter by one now
Serial.print("CloudMaster position number, ");
Serial.print(a);
Serial.print(" is= ");
Serial.println(CloudMaster[a]);
}
Serial.println("now we print start and end times for clouds");
//just reframe CloudMaster into array of start and end times for clouds (CLoudMaster[]={start,end,start,end,start,end} as progression through day in elapsed seconds from midnight.
for (byte a=0; a<(CloudsTotal*2);a=(a+2)){
long endT, startT;
startT=CloudMaster[(a+1)];
endT=CloudMaster[a]+startT;
CloudMaster[a]=startT;
CloudMaster[a+1]=endT;
Serial.println(CloudMaster[a]);
Serial.println(CloudMaster[a+1]);
}
trigger=false;
Serial.println("Trigger 2");
Serial.println(trigger);
}//Finally end loop if trigger is true of hr=0 min=0 sec=0 .... i.e. basically entire function runs only 1x per day or on restart of controller
else if (trigger==false){
return;
}
}//END FUNCTION
void Insolation()
{
// Calculate time since day started correcting for DST
if (isDST==true){
elapsedTime=((now()-946684800)+3600)-newDay;
}
else{
elapsedTime=(now()-946684800)-newDay;
}
if (elapsedTime%2==0){//only change lights every 2 seconds
Serial.println("Insolation 2 sec elapsed");
Serial.print("Elapsed Time is=");
Serial.println(elapsedTime);
//define Pi as delta Y for slope since cos 0.5-1.5 Pi goes 0-1-0 in 0.5 pI increments slope of 1/2 day (0-1 intensity) delta Y is 1/2 Pi
float deltaY=3.141592653589793238462643383279502884197169399;
/* using -cos(pi/2+elapsedTime/slope)*((ChMaxVal-flicker)+flicker) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light. Turn off lights after set or before rise etc.
by splitting into half days centered on midday (1/2 ofset-rise) we center exactly the cos function for every channel so color blends are maintained
throughout intensity ramp... more or less ... change intensity every 30 seconds throughout the day*/
for (byte b=0;b<12;b++){
if ((b%2==0) && (elapsedTime>=ChRiseSet[b]) && (elapsedTime<midDay)){
if (b==0){ ChannelValue[0]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b])+flicker[b]));
Serial.print("ch0 setting first 1/2 day=");
Serial.println(ChannelValue[0]);}
else if (b==2) ChannelValue[1]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b-1]-flicker[b-1])+flicker[b-1]));
else if (b==4) ChannelValue[2]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b-2]-flicker[b-2])+flicker[b-2]));
else if (b==6) ChannelValue[3]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b-3]-flicker[b-3])+flicker[b-3]));
else if (b==8) ChannelValue[4]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b-4]-flicker[b-4])+flicker[b-4]));
else if (b==10) ChannelValue[5]=(-cos((deltaY/2)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b-5]-flicker[b-5])+flicker[b-5]));
}
else if ((b%2==1) && (elapsedTime<=ChRiseSet[b]) && (elapsedTime>=midDay)){
if (b==1){ChannelValue[0]=(-cos((deltaY)+(elapsedTime-rise/ChSlope[b]))*((ChInt[b]-flicker[b-1])+flicker[b-1]));
Serial.print("ch0 setting second 1/2 day=");
Serial.println(ChannelValue[0]);}
else if (b==3) ChannelValue[1]=(-cos((deltaY)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b-2])+flicker[b-2]));
else if (b==5) ChannelValue[2]=(-cos((deltaY)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b-3])+flicker[b-3]));
else if (b==7) ChannelValue[3]=(-cos((deltaY)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b-4])+flicker[b-4]));
else if (b==9) ChannelValue[4]=(-cos((deltaY)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b-5])+flicker[b-5]));
else if (b==11) ChannelValue[5]=(-cos((deltaY)+((elapsedTime-rise)/ChSlope[b]))*((ChInt[b]-flicker[b-6])+flicker[b-6]));
}
else if (((b%2==0) && (elapsedTime<ChRiseSet[b])) || ((b%2==1) && (elapsedTime>ChRiseSet[b]))){
Cloud=false;//no lights = no Cloud
Storm=false;
Serial.println("were not in daylight");
if ((b==0) || (b==1)) ChannelValue[0]=0;
else if ((b==2) || (b=3)) ChannelValue[1]=0;
else if ((b==4) || (b=5)) ChannelValue[2]=0;
else if ((b==6) || (b=7)) ChannelValue[3]=0;
else if ((b==8) || (b=9)) ChannelValue[4]=0;
else if ((b==10) || (b=11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
}
}
/* Serial.println(ChannelValue[0]);
Serial.println(ChannelValue[1]);
Serial.println(ChannelValue[2]);
Serial.println(ChannelValue[3]);
Serial.println(ChannelValue[4]);
Serial.println(ChannelValue[5]);*/
Serial.print("Insolation elapsed time=");
Serial.println(elapsedTime);
}
}//END function
//This is where modification of insolation by clouds or a storm occurs
//Its worth noting that the clouds simply modify (depending on how much cloud cover there is) the existing light set by insolation as a fractional value
void Weather ()
{
static int CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static int PriorCloudCover; //used to "delay" one side of the tank from the other in cloud passing effects
static long StormStart;
static long StormEnd;
static long CloudEnd;
static boolean wtrigger;//use to track the first run of functions to calculate random times that become fixed, see change from cloud to storm for useage
// see insolation elapsed time function for assigmnent of Cloud start times and set of Cloud=true
//check to see if were having a scheduled cloud
if ((Cloud==true) && (Storm==false)){
if ((elapsedTime%2)!=0){
return;
}
// EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
Serial.println("in a cloud");
//now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times up
//seems backwards since CloudCover has not been calculated if you read linearly but it will always be set prior to this=true
if ((elapsedTime)>=CloudEnd){
ClearSky(CloudCover, CloudEnd);
return;
}
/*Use fractional intensity to set minimum value for any channel its WAY to confusing to try to set up otherwise. Dimming is proportional to actual intensity output
and constrained by flicker point. Random walk uses static variable "CloudCover" constrained to 0-100 to represent fractional intensity (i.e. (CloudCover/100)*Insolation SetPoint
is how the current cloud intensity is set*/
randomSeed(millis());
byte stepsize=random(-10,11);
PriorCloudCover=CloudCover;
CloudCover = CloudCover + stepsize;
if (CloudCover>=80){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms change this value to a lower number
Storm=true;
wtrigger=true;
StormStart=elapsedTime;
}
else if (CloudCover<=0){
CloudCover-=(stepsize*1.5);
}
//Array to give direction to dimming. e.g. DimOrder[]={0,0,1,1,0,0} would dim PWM channels 0-1,4-5 as FIRST dim value, then channels 2-3 would take on FIRST dime Value
//when channels 0-1,4-5 take on SECOND dim value, so the values chase eachother across the PWM output light array according to your set up. I have 0-1 as RIGHT side lights,
//2-3 as LEFT side LED, and 4 as whole tank while I do not use 5
byte DimOrder[]={0,0,1,1,0,0,};
for (int a=0;a<6;a++){
if (DimOrder[a]==0){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);
}
if (DimOrder[a]==1){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);
}
}
}
//enable a flag sent from controller to triger a storm, i.e. Storm=true
// set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
else if (((Cloud=false) && (Storm=true)) || ((Cloud=true) && (Storm=true))){
//do this next part exactly once per storm then loop past
if (wtrigger==true){
int LongestStorm=((CloudEnd-StormStart)-120);//remove the last 2 minutes I use to clear the sky from the longest possible storm duration
int StormDuration=random((LongestStorm/4),(LongestStorm+1));//set (min,max) seconds any single Storm can exist.
StormStart=elapsedTime;
StormEnd=((StormStart+StormDuration)+elapsedTime);
wtrigger=false;
}
//Every 1 second duing a storm change intensity, clouds are movin fast baby
if ((elapsedTime%1)!=0){
return;
}
if (elapsedTime>=CloudEnd){ //if we randomly had a storm go to cloud end (which is possible but will be rare)
ClearSky(CloudCover, CloudEnd);
return;
}
else if (elapsedTime>=StormEnd){ //if were done with the storm but still cloudy
Storm=false;
return;
// step through intensity values
randomSeed(millis());
byte stepsize=random(-15,16);
if ((stepsize<-12) || (stepsize>12)){ //it is convient to use this random call to generate a strike during a storm for more frequent strikes adjust down
randomSeed(now()-newDay); //start random with a new value to avoid repetitive stike generations
StrikeNumber=(random(2,10)); //random high =x-1 so max strike =9 and array=17 for StrikeMaster
int StartStrike=0;
//Rather than bother with Vector and dynamic allocation with push/pull use an array big enough to accomadate the largest strike pattern and then
//ensure the array is zeroed out past the last position required for this strike pattern so each pattern is only as long as generated
//Array is Strike Start millis, strike end millis, etc... total of StrikeNumber pairs.
for (byte a=0;a<18;a++){
if (a>=(StrikeNumber*2)){
StrikeMaster[a]=0;
}
else if (a%2==0){
StartStrike+=random(100,701);//position 0,2,4,6,8.. is strike delay
StrikeMaster[a]=StartStrike;
}
else if (a%2!=0){
StartStrike+=random(30,81);//position 1,3,5,7,9... is strike duration- added to StartStrike=StrikeEnd
StrikeMaster[a]=StartStrike;
}
}
StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
}
PriorCloudCover=CloudCover;
CloudCover = CloudCover + stepsize;
if (CloudCover>=100){//conflicted here, since were dimming by fractional intenstiy its likely we will hit flicker point or lower... might change how this works
CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
}
else if (CloudCover<=0){
CloudCover-=(stepsize*1.5);//if were clear sky path reflect random path to cloud
}
//*****************IF You want the light to "chase" across the tank****************************
//you need to specify the left or right side (or front back whatever, depends on how your lights are set up)
//Use either 0 or 1 to specify groups of channels that dim together (arbitrary but if you need to know direction the cloud will travel you will need to read the next bit of code)
//assign the same 0 or 1 value to all left side (or front back or whatever) channels and the other value to all right side channels
byte DimOrder[]={0,0,1,1,0,0,};
//****************** ok were done changing things*****************************
for (int a=0;a<6;a++) {
if (Wchannel[a]==1){
ChannelValue[a]=0;
continue;
}
if (DimOrder[a]==0){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);
}
else if (DimOrder[a]==1){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);
}
}
}
}//end of storm if loop
// Place this loop last as it will only run then cloud is over, and trigger next cloud start and end, this way its not computing during a cloud.
else {
for (byte a=0; a<=(CloudsTotal-1); a=(a+2)){
if ((elapsedTime>=*(pCloudPoint+a)) && (elapsedTime<=*(pCloudPoint+(a+1)))){
CloudEnd=*(pCloudPoint+(a+1));
CloudEnd=CloudEnd-120;
Cloud=true;
}
else{
Cloud=false;
CloudCover=0;
PriorCloudCover=0;
}
}
}
}//End Weather function
//THE LAST 2 mins of ANY Storm, or cloud, are used to ramp linearlly to full daylight setting, whatever that is at the given time of day
void ClearSky(byte CloudCover, int CloudEnd)
{
// no need to restrict time to avoid resource hogging, its only called every x seconds from weather.
//I AM ASSUMING THAT everything calling this does so with 120 seconds left in the cloud/storm etc to clear the sky.
//if you do this incorrectly (i.e. change this and dont change it everywhere I am not sure what would happen for certain, but it may cause issues... almost certainly will
int remaining=(CloudEnd-elapsedTime);
int Range=(CloudCover/100);
int slope=(Range/120);
if (remaining<=0){
Storm=false;
Cloud=false;
return;
}
for (byte a=0; a<6; a++){
ChannelValue[a]=(ChannelValue[a]*((120-remaining)*slope));
}
}//End Clear Sky function
Code: Select all
//this is not working code, its very close but if you run it currently, your lights will not come on and other bad things may happen... just so you know.
#include <Time.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <Globals.h>
#include <ReefAngel_Features.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <avr/wdt.h>
#include <SWFLTEK_EPHERMA.h>
//Defines for ReefAngel PWM module
#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5
//*********************************************************************************************************************************
//IF YOU DO NOT WISH TO USE the U.S.A. Day Light Savings Time rules to modify the time stamp for rise/set calculations...set to false- i.e. if you dont live in the USA etc
boolean ApplyDST=true;
long elapsedTime;//used multiple places as elapsed since new day.
//Globals Vavriables for ReefAngel PWM Cloud, PWM Settings, Epherma Lat/Long Sunrise/Sunset
unsigned long rise;
unsigned long set;
unsigned long newDay;// time in seconds since 2000 adjusted for DST (if used) at a new day (12:00:00am)
long ChRiseSet[12];
float ChSlope[12];
long midDay;// exactly 1/2 way between rise and set, i.e. my take on solar noon- which its not...
byte PWMports[] ={
3,5,6,9,10,11};
byte ChannelValue[6];// Array to store current setpoint for PMW control of output to ch (0,1,2,3,4,5)
//set up channel max intensity arrays. comment is just illustrating value range possible
byte ChMax[]={255,255,255,255,255,0};//max values you want to hit at solar noon scaled as byte values for LED output PWM write
byte flicker[]={25,25,25,25,10,0};//need to input actual values here
byte ChInt[5];
//YOU MUST CHANGE THIS- designate channels of white light (channels you want off during Storm and used for lightning strikes)
//use 1 to designate white channel. Array corresponds to PWM channel 0-5 in order
boolean Wchannel[]={1,0,1,0,0,0,0};
long StrikeStart;//timer to keep track of strike sequence
int StrikeMaster[18];//Array to hold random strike pattern generated by weather array is sized to MAX needed given strike patter generator
byte StrikeNumber;//place to hold total number of strikes this sequence
byte count;//used in loop for strike intensity varaition when StrikeNow is triggered true within the Weather function starting a strike sequence in the loop
byte SeasonsVar[]={
0,0,0,0,0,0,0,0,0,0,0,0}; // PWM0, risehour, riseminute, sethour, setminute, Cloudstarthout, Cloudstartminute, Cloudduration,lightningchance, PWM1,PWM2,PWM3
byte cmdnum=255;
byte datanum=255;
boolean trigger; //used a few places as a switch to ensure things only run 1x when needed
byte strikePattern, strikeTime;//used in Lightning() for timing of particular instance of strike
boolean isDST, Cloud, Storm, CloudToday, StrikeNow; // what they seem, is it DST?, Are we in a cloud?
byte CloudsTotal;//required to know how large the array for CloudMaster will be- I need to use Vectors.... I know.
long *pCloudPoint;//Use global to point to static memory location of array CloudMaster created 1/x day in calcSun.
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Setup
void setup()
{
Serial.begin(57600);
Wire.begin(8);
Wire.onReceive(receiveEvent);
Wire.onRequest(requestEvent);
randomSeed(analogRead(0));
pinMode(3,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
pinMode(9,OUTPUT);
pinMode(10,OUTPUT);
pinMode(11,OUTPUT);
//wdt_enable(WDTO_1S);
setSyncProvider(RTC.get); // the function to get the time from the RTC
setSyncInterval(SECS_PER_HOUR); // Changed to sync every hour.
now();//why are you here?
CloudToday=false;
trigger=true;
StrikeNow=false;
}
//End Setup
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Loop
void loop()
{
wdt_reset();
//Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255){
ProcessCMD(cmdnum,datanum);
cmdnum=255;
datanum=255;
}
// now run through rise/set calculation and then intensity set and finally weather overlay
CalSun();
Insolation();
/* Weather();
if (StrikeNow==false){
StrikeStart=millis();
count=0;
}
else if (StrikeNow==true){
int intensity=0;//strike intensity value for each individual strike calculated once for each strike when index of strike changes i.e. a in for loop
int elapsed=millis()-StrikeStart;
if (elapsed>StrikeMaster[(StrikeNumber*2-1)]){
StrikeNow=false;
}
for (byte a=0;a<(StrikeNumber*2);a=(a+2)){ //StrikeNumber is the number of strikes generated this sequence so only look this far into array or we just hit zeros if its not a full stike seq
if ((elapsed>StrikeMaster[a])&&(elapsed<StrikeMaster[a+1])){
if (count!=(a+1)){
intensity=random(155-255);// this little bit should generate a randomly bright flash variation between the series of flashes in StrikeMaster
}
count=(a+1);
for (byte a=0; a<6; a++){
if (Wchannel[a]==1) ChannelValue[a]=intensity;
}
}
else {
for (byte a=0; a<6; a++){
if (Wchannel[a]==1) ChannelValue[a]=0;
}
}
}
}
//Now that we have generated our sun patter, Weather as clouds, and possibly a storm, and possibly lightning each of which override the prior channel setting
//lets actually make some light.
for (byte a=0;a<6;a++){
analogWrite(PWMports[a],ChannelValue[a]);
}
*/
}
//End Loop
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
void receiveEvent(int howMany) {
wdt_reset();
if (howMany==5){
byte cmd1, cmd2, cmd3, cmd4, cmd5;
cmd1=Wire.read();
cmd2=Wire.read();
cmd3=Wire.read();
cmd4=Wire.read();
cmd5=Wire.read();
if (cmd1=='$' && cmd2=='$' && cmd3=='$'){
cmdnum=cmd4;
datanum=cmd5;
//Serial.println(cmd4,DEC);
//Serial.println(cmd5,DEC);
}
}
else{
for (int a=0;a<howMany;a++){
Wire.read();
}
}
}
void ProcessCMD(byte cmd, byte data)
{
wdt_reset();
// Individual Channel
if (cmd>=0 && cmd<=5){
ChannelValue[cmd]=data;
analogWrite(PWMports[cmd],data);
}
if (cmd==6){
Storm=true;
}
}
//End Standard Functions
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Send Data Function
void requestEvent() {
/*SeasonsVar[0]=ChannelValue[0];
SeasonsVar[1];
SeasonsVar[2];
SeasonsVar[3];
SeasonsVar[4];
SeasonsVar[5];
SeasonsVar[6];
SeasonsVar[7];
SeasonsVar[8];
SeasonsVar[9];
SeasonsVar[10];
SeasonsVar[11];
Wire.write(SeasonsVar,12);
*/
}
//End Send Data
//*********************************************************************************************************************************
// Function to run Epherma Calc for rise/set/noon/moon phase
void CalcDST(byte D, byte M, byte dow)
{
//January, february, and december are out.
if (M < 3 || M > 11){
isDST=false;
}
//April to October are in
else if (M > 4 && M < 11){
isDST=true;
}
else{
int previousSunday = D - dow; // Sunday=1 Sat=7
if (M==3 && previousSunday > 7){
isDST=true;
}
else if (M==11 && previousSunday<=0){
isDST=false;
}
}
}
void CalSun()
{
//this entire function runs only based upon the first if being true... so its a LONG ways down to the else if trigger is false statement to finish this if
// Every day at midnight, we calculate rise, set, time of highest sun, moon phase, or we do this if system has reset
if ((hour()==0 && minute()==0 && second()==0) || (trigger==true)){
Serial.println("trigger 1");
Serial.println(trigger);
unsigned long SecInput;
//void CalcDST(day(),month(),weekday()) modifies isDST memory to appropriate true/false based upon USA rules
if (ApplyDST==true){
CalcDST(day(),month(),weekday());
}
//Using time library from Arduino.time_t now(); returns seconds since 1970
//Library states #seconds to subtract to bring to Jan 1 2000 as origin for time is
//#define SECS_YR_2000 (946684800) the time at the start of y2k
long hours;//possible to be reset at 23 hours*3600=long required
long minutes;// int is enough as 60*60 is max val but math is conducted with possibility of roll over in intermediate so to be sure.. use long
time_t t=now();
hours=hour(t);
minutes=minute(t);
if ((ApplyDST==true) && (isDST==true)){
if ((hours!=0) || (minutes!=0)){
hours=hours+1;
newDay=(now()-(946684800+3600+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your rise being off by <60 seconds on a day of a power failure... your more anal than I
Serial.print("DST Hours,Min");
Serial.print(hours);
Serial.print(",");
Serial.println(minutes);
}
else{
newDay=(now()-946684800+3600);
}
}
else if ((isDST==false) || (ApplyDST==false)){
if (hours!=0 || minutes!=0){
newDay=(now()-(946684800+((hours*3600)+(minutes*60)))); //yes I am ignoring seconds... if your worried about your rise being off by <60 seconds on a day of a power failure... your more anal than I
Serial.print("Hours,Min");
Serial.print(hours);
Serial.print(",");
Serial.println(minutes);
}
}
SecInput=((newDay)+25200);//modify to GMT time (i.e. MST + 7 hours = GMT)
//Calculate Latitude and Longitude converting from Degree, Minute, Seconds to seconds
latitude=dmsToSeconds(40,45,39); //Set to about the latitude of the great barrier reef rotated into the morthern hemisphere
longitude=dmsToSeconds(-111,53,25); //Set to Salt lake City, or MST zone, USA or there abouts- actually the AT921 remote weather station in Salt Lake City UT. Random web grab.
//Calculate rise time and set time using Epherma Library
rise=SecInput;
set=SecInput;
SunRise(&rise);//call to Epherma Library using reference- memory is modified by Epherma
SunSet(&set);//Call to Epherma library using reference- memory position is modified by Epherma
rise-=25200; //Correct back to MST from GMT- DST still accounted for
set-=25200;
Serial.print("newDay=");
Serial.println(newDay);
Serial.print("Local uncorrected rise, set=");
Serial.print(rise);
Serial.print(",");
Serial.print(set);
rise=(rise-newDay);//set to elapsed seconds today
set=(set-newDay);
Serial.println("Local Elapsed Seconds from NewDay--rise=");
Serial.println(rise);
Serial.println("Local Elapsed Seconds from NewDay--set=");
Serial.println(set);
//*********************UNLESS YOUR TANK LIGHTING IS IDENTICAL IN EVERY WAY TO MINE----YOU NEED TO CHANGE THESE VALUES***************************
//offsets for rise/set all values in seconds offset from calculated rise or set value
//array order is ch0 am offset, pm offset, ch1 am offset, pm offset etc..
//THESE values are the number of seconds that a particular channel will be offset from the rise/set time, i.e. negative to rise earlier/set earlier
int Choffset[]={
-600,0,-3600,5400,0,600,-3600,5400,-4500,7200,0,0};
//**********************ok now were done changing things here********************************************
//Calculate rise and set times for all channels in equivlants to elapsed seconds from midnight today
//populate array for chRise and Set as well as chSlope for 0.5pi/ half day lenght for each channel from midday (asymmetric days are allowed)
float deltaY=1.570796327;//1/2 * pi as integer by scaling* 10^9 to fill UL
midDay=(((set-rise)/2)+rise);
Serial.print("MidDay");
Serial.println(midDay);
for (byte b=0;b<12;b++){//working as of April 5 2012 serial tested
if (b%2==0){
ChRiseSet[b]=rise+(Choffset[b]);
ChSlope[b]=(deltaY/(float)(midDay-(Choffset[b])));
Serial.print("Rise/Set a=");
Serial.println(b);
Serial.print("Value=");
Serial.print(ChRiseSet[b]);
Serial.print("Slope");
Serial.println(ChSlope[b], 12);
}
else if (b%2==1){
ChRiseSet[b]=set+(Choffset[b]);
ChSlope[b]=deltaY/(float)(midDay+(Choffset[b]));
Serial.print("Rise/Set a=");
Serial.println(b);
Serial.print("Value=");
Serial.print(ChRiseSet[b]);
Serial.print("Slope");
Serial.println(ChSlope[b], 12);
}
}
//***************** to CHANGE THE chance of Clouds actually occuring on a given day************************
byte CloudChance=70;//% Chance of a Cloud every day
//****************************now were done- did you use a value from 0-100 without a decimal?*****************
//once a day, after rise set has been calculated and populated we need to trigger a reset to the Cloud time calculation loop
randomSeed(now()-(random(30000,1000000001))); //so that we are now more trully random
byte RainMaker=random(1,101);
if (RainMaker<CloudChance){
CloudToday=true;
}
else if (RainMaker>=CloudChance){
CloudToday=false;
trigger=false;
return;
}
// to "simplify" things and avoid redundant calculation loops all cloud times are in fraction of day lengths
//*********** this is only executed 1x /day unless power outage and ONLY if last statement is true****************
///ALl cloud set up is done here... weather subroutine just implements it
/*The general strategy for this algorithim is as follows. Calculate random cloud cover as percent of day,
then randomly generate the # of discreet cloud instances for the day,
then determine cloud length by (daylight seconds * percent overcast)/#clouds
then randomize cloud lengths by splitting into groups of twos correcting for if we have odd # of clouds today*/
long dayLength=(set-rise);
Serial.println("DayLength");
Serial.println(dayLength);
byte OvercastMin=10;//% as fraction (0-1 is 0-100%) of daylight hours that would minimially be cloudy on a cloud day
byte OvercastMax=31;//% of daylight hours that would maximally be cloudy on a cloud day (then add 1 to that #)
float Overcast=random(OvercastMin,OvercastMax);
Overcast=(Overcast/100);
Serial.println("Overcast");
Serial.println(Overcast);
// number of clouds possible for the day, max and min
byte CloudsMax=9;
byte CloudsMin=4;//dont use 1 it may or may not work in the next loops and the cloud will likely be very long, i.e. daylength*overcast fraction
CloudsTotal=random(CloudsMin,(CloudsMax+1));
Serial.println("CloudsTotal");
Serial.println(CloudsTotal);
static long CloudMaster[15];// Set up array to hold start and end times for clouds for the day- dont size it dynamically make it big enough for any day
pCloudPoint=&CloudMaster[0];//use address of pointer to find cloud data in weather, array is declared static to persist throughout day.
// set up start time for clouds
/*The way this is done is to split the total lenght of time for clouds into equal segments and then to randomly chop or add time to the
segments such that cloud length is variable. Then distribute into random parts of the day and fill array with start,duration pairs for clouds*/
long CloudLength;
CloudLength=((dayLength*Overcast)/CloudsTotal);//average cloud length
float fraction=random(20,181);
fraction=(fraction/100);//vary each cloud from 20%-180% of an equal fraction of total cloud for day
Serial.print("fraction ");
Serial.println(fraction);
Serial.print("CloudLength ");
Serial.println(CloudLength);
//using fraction to vary cloud length throughout day fill array CloudMaster[] at positions 0,2,4,6 to full with durations in seconds
//this way the MAX cloud duration is 180% of the average cloud druation while the min is 20%
byte b=0;//set up counter to alter cloud length and initialize to zero outside of loop
for (byte a=0; a<CloudsTotal; a++){
int CurrentCloudLength;
// if were having an odd # of clouds make sure last one is full length
if ((CloudsTotal%2!=0) && (a==(CloudsTotal-1))){
CloudMaster[a*2]=CloudLength;
Serial.print("last odd cloud Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
break;
}
// else we make clouds variable in length short then long repeated with every two cloud sets being different in short and long but adding up equally by sets
else {
if (fraction<1){
CurrentCloudLength=abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
CloudMaster[a*2]=CurrentCloudLength;
Serial.print("Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
Serial.print("b=");
Serial.println(b);
}
if (fraction==1){
CloudMaster[a*2]=CloudLength;
Serial.print("Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
Serial.print("b=");
Serial.println(b);
}
if (fraction>1){
CurrentCloudLength=((CloudLength*fraction)-(b*CloudLength*fraction))+(b*((2-fraction)*CloudLength));
CloudMaster[a*2]=CurrentCloudLength;
Serial.print("Cloudmaster[a]=");
Serial.println(a*2);
Serial.print("fraction=");
Serial.println(fraction);
Serial.print("CloudMaster=");
Serial.println(CloudMaster[a*2]);
Serial.print("b=");
Serial.println(b);
}
}
b=b+1;
if (b==2){
b=0;
fraction=random(20,181);//every other cloud recalculate a new fractional distribution for the next set
fraction=(fraction/100);
}
}
randomSeed(now());
// Now space the clouds out during the day using random fractionation of day segments as per cloud calculation
b=0;//reset counter used for fraction reset
byte c=0;//counter for indexing # of itterations in loop
long StartTime;//Used in loop to assign cloud spacing, i.e. these loop values determine daylight interval between clouds
fraction=random(10,181);
fraction=(fraction/100);
for (byte a=1; a<(CloudsTotal*2); a=(a+2)){
//calculate amount of sun during day (i.e. total day lenth - portion cloudy), divide into equal parts then randomly initiate cloud within sun segment for each part of the day.
// must index segment time by +1 segment for each cloud completed
long SunSegment=((dayLength-(dayLength*Overcast))/(CloudsTotal+1));
if (fraction<1){
abs(CloudLength*b+(b*CloudLength-(CloudLength*fraction)));
StartTime=rise+((SunSegment*b+abs(b*SunSegment-(SunSegment*fraction)))+(c*SunSegment));
CloudMaster[a]=StartTime;
StartTime=0;
b=b+1;
}
else if (fraction==1){
StartTime=rise+(SunSegment+(SunSegment*c));
CloudMaster[a]=StartTime;
StartTime=0;
b=2;//by doing this I ensure that new fraction will be calculated so next interval will not be 1 (or rarely so)
}
else if (fraction>1){
StartTime=rise+(((SunSegment*fraction)-(b*SunSegment*fraction))+(b*((2-fraction)*SunSegment))+(c*SunSegment));
CloudMaster[a]=StartTime;
StartTime=0;
b=b+1;
}
if (b==2){
b=0;
fraction=random(20,181);//every other cloud recalculate a new fractional distribution for the next set
fraction=(fraction/100);
}
c++; //index loop counter by one now
Serial.print("CloudMaster position number, ");
Serial.print(a);
Serial.print(" is= ");
Serial.println(CloudMaster[a]);
}
Serial.println("now we print start and end times for clouds");
//just reframe CloudMaster into array of start and end times for clouds (CLoudMaster[]={start,end,start,end,start,end} as progression through day in elapsed seconds from midnight.
for (byte a=0; a<(CloudsTotal*2);a=(a+2)){
long endT, startT;
startT=CloudMaster[(a+1)];
endT=CloudMaster[a]+startT;
CloudMaster[a]=startT;
CloudMaster[a+1]=endT;
Serial.println(CloudMaster[a]);
Serial.println(CloudMaster[a+1]);
}
trigger=false;
Serial.println("Trigger 2");
Serial.println(trigger);
//As the LAST thing we need to do today and only once... frame ChInt array as max value-flicker point... so it can be correctly set in Insolation
for (byte a=0; a<6;a++){
ChInt[a]=(ChMax[a]-flicker[a]);
}
}//Finally end loop if trigger is true of hr=0 min=0 sec=0 .... i.e. basically entire function runs only 1x per day or on restart of controller
else if (trigger==false){
return;
}
}//END FUNCTION
void Insolation()
{
//static long TimeModulo;
// Calculate time since day started correcting for DST
if (isDST==true){
elapsedTime=(((now()-946684800)+3600)-newDay);
}
else{
elapsedTime=((now()-946684800)-newDay);
}
//if (elapsedTime%2==0) TimeModulo=elapsedTime;
if (elapsedTime%2==0){//only change lights every 2 seconds
Serial.println("Insolation 2 sec elapsed");
Serial.print("Elapsed Time is=");
Serial.println(elapsedTime);
//define Pi as delta Y for slope since cos 0.5-1.5 Pi goes 0-1-0 in 0.5 pI increments slope of 1/2 day (0-1 intensity) delta Y is 1/2 Pi
float Pi=3.1415926;//scale to 10^8
float PiHalf=1.5707963;//scale to 10^8
long secSoFar;//variable to account for seconds elapsed for each channel 1/2 day period from rise-->midDay and midDay-->set
/* using -cos(pi/2+elapsedTime/slope) calculate fractional intensity of each channel throughout the day
use flicker points to adjust minimum intensity to stable light. Turn off lights after set or before rise etc.
by splitting into half days centered on midday (1/2 ofset-rise) we center exactly the cos function for every channel so color blends are maintained
throughout intensity ramp... more or less ... change intensity every 120 seconds throughout the day*/
for (byte b=0;b<12;b++){
if ((b%2==0) && (elapsedTime>=ChRiseSet[b]) && (elapsedTime<midDay)){
secSoFar=(elapsedTime-ChRiseSet[b]);//just account for length of every channel 1/2 day and switch at midDay
switch (b){
case 0:
ChannelValue[0]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*ChInt[b]+flicker[b];
Serial.print("ch0 setting first 1/2 day=");
Serial.println(ChannelValue[0]);
break;
case 2:
ChannelValue[1]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-1]+flicker[b-1];
break;
case 4:
ChannelValue[2]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-2]+flicker[b-2];
break;
case 6:
ChannelValue[3]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-3]+flicker[b-3];
break;
case 8:
ChannelValue[4]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-4]+flicker[b-4];
break;
case 10:
ChannelValue[5]=(byte)(-cos((PiHalf)+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-5]+flicker[b-5];
break;
}
}
else if ((b%2==1) && (elapsedTime<=ChRiseSet[b]) && (elapsedTime>=midDay)){
secSoFar=(elapsedTime-ChRiseSet[b]);
switch (b){
case 1:
ChannelValue[0]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-1]+flicker[b-1];
Serial.print("ch0 setting second 1/2 day=");
Serial.println((-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-1]+flicker[b-1],6);
Serial.println((-cos(Pi+((float)secSoFar*ChSlope[b])))*ChInt[b-1]+flicker[b-1],6);
break;
case 3:
ChannelValue[1]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-2]+flicker[b-2];
break;
case 5:
ChannelValue[2]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-3]+flicker[b-3];
break;
case 7:
ChannelValue[3]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-4]+flicker[b-4];
break;
case 9:
ChannelValue[4]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-5]+flicker[b-5];
break;
case 11:
ChannelValue[5]=(byte)(-cos(Pi+((float)secSoFar*ChSlope[b])))*(float)ChInt[b-6]+flicker[b-6];
break;
}
}
else if (((b%2==0) && (elapsedTime<ChRiseSet[b])) || ((b%2==1) && (elapsedTime>ChRiseSet[b]))){
Cloud=false;//no lights = no Cloud
Storm=false;
Serial.println("were not in daylight");
if ((b==0) || (b==1)) ChannelValue[0]=0;
else if ((b==2) || (b==3)) ChannelValue[1]=0;
else if ((b==4) || (b==5)) ChannelValue[2]=0;
else if ((b==6) || (b==7)) ChannelValue[3]=0;
else if ((b==8) || (b==9)) ChannelValue[4]=0;
else if ((b==10) || (b==11)) ChannelValue[5]=0;//if using for moon lights ChannelValue[5]=MoonPhase or something like that using global variable for MoonPhase
}
}
/* Serial.println(ChannelValue[0]);
Serial.println(ChannelValue[1]);
Serial.println(ChannelValue[2]);
Serial.println(ChannelValue[3]);
Serial.println(ChannelValue[4]);
Serial.println(ChannelValue[5]);*/
Serial.print("Insolation elapsed time=");
Serial.println(elapsedTime);
WeDidThis=true;
}
}//END function
//This is where modification of insolation by clouds or a storm occurs
//Its worth noting that the clouds simply modify (depending on how much cloud cover there is) the existing light set by insolation as a fractional value
void Weather ()
{
static int CloudCover; // variable to store value in random walk - declared static to accumulate Cloud effect
static int PriorCloudCover; //used to "delay" one side of the tank from the other in cloud passing effects
static long StormStart;
static long StormEnd;
static long CloudEnd;
static boolean wtrigger;//use to track the first run of functions to calculate random times that become fixed, see change from cloud to storm for useage
// see insolation elapsed time function for assigmnent of Cloud start times and set of Cloud=true
//check to see if were having a scheduled cloud
if ((Cloud==true) && (Storm==false)){
if ((elapsedTime%2)!=0){
return;
}
// EVERY two seconds change the PWM output for a cloud- just change to %# to number of secs you want to use for cloud intensity varaition
Serial.println("in a cloud");
//now is a good time to check if our cloud is ending, prior to setting more PWM outputs send to ClearSky if times up
//seems backwards since CloudCover has not been calculated if you read linearly but it will always be set prior to this=true
if ((elapsedTime)>=CloudEnd){
ClearSky(CloudCover, CloudEnd);
return;
}
/*Use fractional intensity to set minimum value for any channel its WAY to confusing to try to set up otherwise. Dimming is proportional to actual intensity output
and constrained by flicker point. Random walk uses static variable "CloudCover" constrained to 0-100 to represent fractional intensity (i.e. (CloudCover/100)*Insolation SetPoint
is how the current cloud intensity is set*/
randomSeed(millis());
byte stepsize=random(-10,11);
PriorCloudCover=CloudCover;
CloudCover = CloudCover + stepsize;
if (CloudCover>=80){ //arbitrary "STORM" cutoff... i.e. if the sky gets dark enough we must be having a storm- if you dont get enough storms change this value to a lower number
Storm=true;
wtrigger=true;
StormStart=elapsedTime;
}
else if (CloudCover<=0){
CloudCover-=(stepsize*1.5);
}
//Array to give direction to dimming. e.g. DimOrder[]={0,0,1,1,0,0} would dim PWM channels 0-1,4-5 as FIRST dim value, then channels 2-3 would take on FIRST dime Value
//when channels 0-1,4-5 take on SECOND dim value, so the values chase eachother across the PWM output light array according to your set up. I have 0-1 as RIGHT side lights,
//2-3 as LEFT side LED, and 4 as whole tank while I do not use 5
byte DimOrder[]={0,0,1,1,0,0,};
for (int a=0;a<6;a++){
if (DimOrder[a]==0){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);
}
if (DimOrder[a]==1){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);
}
}
}
//enable a flag sent from controller to triger a storm, i.e. Storm=true
// set channel intensities for all but white with random walk continuing from above using static variable so should be seamless
else if (((Cloud=false) && (Storm=true)) || ((Cloud=true) && (Storm=true))){
//do this next part exactly once per storm then loop past
if (wtrigger==true){
int LongestStorm=((CloudEnd-StormStart)-120);//remove the last 2 minutes I use to clear the sky from the longest possible storm duration
int StormDuration=random((LongestStorm/4),(LongestStorm+1));//set (min,max) seconds any single Storm can exist.
StormStart=elapsedTime;
StormEnd=((StormStart+StormDuration)+elapsedTime);
wtrigger=false;
}
//Every 1 second duing a storm change intensity, clouds are movin fast baby
if ((elapsedTime%1)!=0){
return;
}
if (elapsedTime>=CloudEnd){ //if we randomly had a storm go to cloud end (which is possible but will be rare)
ClearSky(CloudCover, CloudEnd);
return;
}
else if (elapsedTime>=StormEnd){ //if were done with the storm but still cloudy
Storm=false;
return;
// step through intensity values
randomSeed(millis());
byte stepsize=random(-15,16);
if ((stepsize<-12) || (stepsize>12)){ //it is convient to use this random call to generate a strike during a storm for more frequent strikes adjust down
randomSeed(now()-newDay); //start random with a new value to avoid repetitive stike generations
StrikeNumber=(random(2,10)); //random high =x-1 so max strike =9 and array=17 for StrikeMaster
int StartStrike=0;
//Rather than bother with Vector and dynamic allocation with push/pull use an array big enough to accomadate the largest strike pattern and then
//ensure the array is zeroed out past the last position required for this strike pattern so each pattern is only as long as generated
//Array is Strike Start millis, strike end millis, etc... total of StrikeNumber pairs.
for (byte a=0;a<18;a++){
if (a>=(StrikeNumber*2)){
StrikeMaster[a]=0;
}
else if (a%2==0){
StartStrike+=random(100,701);//position 0,2,4,6,8.. is strike delay
StrikeMaster[a]=StartStrike;
}
else if (a%2!=0){
StartStrike+=random(30,81);//position 1,3,5,7,9... is strike duration- added to StartStrike=StrikeEnd
StrikeMaster[a]=StartStrike;
}
}
StrikeNow=true; //Trigger start of timming loop in "loop" to initiate Strike pattern do this last so were all good on settings
}
PriorCloudCover=CloudCover;
CloudCover = CloudCover + stepsize;
if (CloudCover>=100){//conflicted here, since were dimming by fractional intenstiy its likely we will hit flicker point or lower... might change how this works
CloudCover-=(stepsize*1.5);//if were too dark reflect random path to light
}
else if (CloudCover<=0){
CloudCover-=(stepsize*1.5);//if were clear sky path reflect random path to cloud
}
//*****************IF You want the light to "chase" across the tank****************************
//you need to specify the left or right side (or front back whatever, depends on how your lights are set up)
//Use either 0 or 1 to specify groups of channels that dim together (arbitrary but if you need to know direction the cloud will travel you will need to read the next bit of code)
//assign the same 0 or 1 value to all left side (or front back or whatever) channels and the other value to all right side channels
byte DimOrder[]={0,0,1,1,0,0,};
//****************** ok were done changing things*****************************
for (int a=0;a<6;a++) {
if (Wchannel[a]==1){
ChannelValue[a]=0;
continue;
}
if (DimOrder[a]==0){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(PriorCloudCover/100)))+flicker[a]);
}
else if (DimOrder[a]==1){
ChannelValue[a]=(((ChannelValue[a]-flicker[a])*(1-(CloudCover/100)))+flicker[a]);
}
}
}
}//end of storm if loop
// Place this loop last as it will only run between clouds and trigger next cloud start and end, this way its not computing during a cloud.
else {
for (byte a=0; a<=(CloudsTotal-1); a=(a+2)){
if ((elapsedTime>=*(pCloudPoint+a)) && (elapsedTime<=*(pCloudPoint+(a+1)))){
CloudEnd=*(pCloudPoint+(a+1));
CloudEnd=CloudEnd-120;
Cloud=true;
}
else{
Cloud=false;
CloudCover=0;
PriorCloudCover=0;
}
}
}
}//End Weather function
//THE LAST 2 mins of ANY Storm, or cloud, are used to ramp linearlly to full daylight setting, whatever that is at the given time of day
void ClearSky(byte CloudCover, int CloudEnd)
{
// no need to restrict time to avoid resource hogging, its only called every x seconds from weather.
//I AM ASSUMING THAT everything calling this does so with 120 seconds left in the cloud/storm etc to clear the sky.
//if you do this incorrectly (i.e. change this and dont change it everywhere I am not sure what would happen for certain, but it may cause issues... almost certainly will
int remaining=(CloudEnd-elapsedTime);
int Range=(CloudCover/100);
int slope=(Range/120);
if (remaining<=0){
Storm=false;
Cloud=false;
return;
}
for (byte a=0; a<6; a++){
ChannelValue[a]=(ChannelValue[a]*((120-remaining)*slope));
}
}//End Clear Sky function
Code: Select all
unsigned long lastmillis=0;
Code: Select all
if ( (millis()-lastmillis> 2000 )
{
//Do whatever
lastmillis=millis();
}