Code: Select all
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include "ReefAngel_Globals.h"
#include "ReefAngel_Features.h"
#include <ReefAngel_Colors.h>
#include <ReefAngel_CustomColors.h>
#include <Wire.h>
#include <avr/wdt.h>
#define LEDPWM0 0
#define LEDPWM1 1
#define LEDPWM2 2
#define LEDPWM3 3
#define LEDPWM4 4
#define LEDPWM5 5
//*********************************************************************************************************************************
//Globals integers
long calcSec(long,long);
long calcTime(long,long);
short Ndays; //Returns the number of day in the year
//*********************************************************************************************************************************
byte cloudduration=0;
int cloudstart=0;
int rhour = 0, rmin = 0, shour = 0, smin = 0;
byte PWMports[] ={
3,5,6,9,10,11};
byte ChannelValue[] = {
0,0,0,0,0,0};
// PWM, sunrisehour, sunriseminute, sunsethour, sunsetminute, cloudstarthout, cloudstartminute, cloudduration
byte SeasonsVar[]={
0,0,0,0,0,0,0,0};
byte cmdnum=255;
byte datanum=255;
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();
}
void loop()
{
wdt_reset();
//Serial.println(ChannelValue[0],DEC);
if (cmdnum!=255)
{
ProcessCMD(cmdnum,datanum);
cmdnum=255;
datanum=255;
}
Seasons();
CheckCloud();
//Serial.println("Start");
//Serial.println(ChannelValue[0],DEC);
//Serial.println(rhour,DEC);
//Serial.println(rmin,DEC);
//Serial.println(shour,DEC);
//Serial.println(smin,DEC);
//Serial.println(cloudstart/60,DEC);
//Serial.println(cloudstart%60,DEC);
//Serial.println(cloudduration,DEC);
for (int a=0;a<6;a++)
{
analogWrite(PWMports[a],ChannelValue[a]);
}
}
void receiveEvent(int howMany) {
wdt_reset();
if (howMany==5)
{
byte cmd1, cmd2, cmd3, cmd4, cmd5;
cmd1=Wire.receive();
cmd2=Wire.receive();
cmd3=Wire.receive();
cmd4=Wire.receive();
cmd5=Wire.receive();
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.receive();
}
}
}
void ProcessCMD(byte cmd, byte data)
{
wdt_reset();
// Individual Channel
if (cmd>=0 && cmd<=5)
{
ChannelValue[cmd]=data;
analogWrite(PWMports[cmd],data);
}
}
void requestEvent() {
SeasonsVar[0]=ChannelValue[0];
SeasonsVar[1]=rhour;
SeasonsVar[2]=rmin;
SeasonsVar[3]=shour;
SeasonsVar[4]=smin;
SeasonsVar[5]=cloudstart/60;
SeasonsVar[6]=cloudstart%60;
SeasonsVar[7]=cloudduration;
Wire.send(SeasonsVar,8);
}
//*********************************************************************************************************************************
// Random Cloud/Thunderstorm effects function
void CheckCloud()
{
// ------------------------------------------------------------
// Change the values below to customize your cloud/storm effect
// Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
// For testing purposes, you can use 1 and cause the cloud to occur everyday
#define Clouds_Every_X_Days 2
// Percentage chance of a cloud happening today
// For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 30
// Minimum number of minutes for cloud duration. Don't use max duration of less than 6
#define Min_Cloud_Duration 6
// Maximum number of minutes for the cloud duration. Don't use max duration of more than 255
#define Max_Cloud_Duration 30
// Minimum number of clouds that can happen per day
#define Min_Clouds_per_Day 2
// Maximum number of clouds that can happen per day
#define Max_Clouds_per_Day 5
// Only start the cloud effect after this setting
// In this example, start could after 11:30am
#define Start_Cloud_After NumMins(11,00)
// Always end the cloud effect before this setting
// In this example, end could before 8:00pm
#define End_Cloud_Before NumMins(16,00)
// Percentage chance of a lightning happen for every cloud
// For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Change_per_Cloud 40
// Channels used by the actinic LEDs on the PWM Expansion module
// These channels will not be dimmed when the cloud effect is triggered
// Number is a binary form. B001100 means channel 2 and 3 are used for actinics
#define Actinic_Channels B001101
// Channels used by the daylight LEDs on the PWM Expansion module
// These channels will be used for the spike when lightning effect is triggered
// Number is a binary form. B000011 means channel 0 and 1 are used for daylights
#define Daylight_Channels B000010
// Note: Make sure to choose correct values that will work within your PWMSLope settings.
// For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
// Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen
// results could happen.
// Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
// In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can
//fit in that 510 minutes window.
// It's a tight fit, but it did.
//#define printdebug // Uncomment this for debug print on Serial Monitor window
#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.
// Change the values above to customize your cloud/storm effect
// ------------------------------------------------------------
// Do not change anything below here
static byte cloudchance=255;
static byte numclouds=0;
static byte lightningchance=0;
static byte cloudindex=0;
static byte lightningstatus=0;
static int LastNumMins=0;
// Every day at midnight, we check for chance of cloud happening today
if (hour()==0 && minute()==0 && second()==0) cloudchance=255;
#ifdef forcecloudcalculation
if (cloudchance==255)
#else
if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
{
//Pick a random number between 0 and 99
cloudchance=random(100);
// if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
// Check if today is day for clouds.
if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
// If we have cloud today
if (cloudchance)
{
// pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
// pick the time that the first cloud will start
// the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
// pick a random number for the cloud duration of first cloud.
cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//Pick a random number between 0 and 99
lightningchance=random(100);
// if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
if (lightningchance>Lightning_Change_per_Cloud) lightningchance=0;
}
}
// Now that we have all the parameters for the cloud, let's create the effect
if (cloudchance)
{
//is it time for cloud yet?
if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
{
// let's go through all channels to pick which ones will be dimmed
for (int a=0;a<6;a++)
{
if (bitRead(Actinic_Channels,a)==0)
{
// this will slope down the channel from the current PWM to 0 within 3minutes.
// then it will stay at 0 for the duration of the cycle
// and finally slope up from 0 to PWM value within 3 minutes
// it is basically an inversed slope
ChannelValue[a]=ReversePWMSlope(cloudstart,cloudstart+cloudduration,ChannelValue[a],0,180);
}
}
if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
{
for (int b=0;b<6;b++)
{
if (bitRead(Daylight_Channels,b)==1)
{
if (random(100)<20) lightningstatus=1;
else lightningstatus=0;
if (lightningstatus) ChannelValue[b]=100;
else ChannelValue[b]=0;
//delay(10);
}
else
{
ChannelValue[b]=20;
}
}
}
}
if (NumMins(hour(),minute())>(cloudstart+cloudduration))
{
cloudindex++;
if (cloudindex < numclouds)
{
cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
// pick a random number for the cloud duration of first cloud.
cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//Pick a random number between 0 and 99
lightningchance=random(100);
// if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
if (lightningchance>Lightning_Change_per_Cloud) lightningchance=0;
}
}
}
}
//End Cloud Function
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Reverse PWM slope from cloud function
byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
long n=elapsedSecsToday(now());
cstart*=60;
cend*=60;
if (n<cstart) return PWMStart;
if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
if (n>cend) return PWMStart;
}
//End Reverse PWM function
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Calculate Sunrise/set by date & predefined season & rise/set times
void Seasons()
{
#define forceseasoncalculation
static byte ssn , ssnp , ssnpt ;
long stime, wstime, vstime, wrtime, rtime, vrtime;
int wrhour,wrmin,wrsec,wshour,wsmin,wssec,rsec,ssec,vrhour,vrmin,vrsec,vshour,vsmin,vssec;
int iDiffrise = 0;
int iDiffset = 0;
int risediffperday = 0;
int setdiffperday = 0;
int totalrise = 0;
int totalset = 0;
byte s=0;
int DaysPerYear;
//rise and set times set by hour and minute. there are 4 seasons however there are 8 highs & lows in rise and set throughout the year
//first spot is second half of winter starting jan 1st
int risehour[8]= {
7,7,7,6,6,5,6,6 };
int riseminute[8]={
00,30,00,30,00,30,00,30 };
int sethour[8] = {
17,18,18,19,19,19,19,18 };
int setminute[8] = {
30,00,30,00,00,30,00,00 };
//
if (hour()==0 && minute()==0 && second()==0) ssnp=0;
#ifdef forceseasoncalculation
if (ssnp==0)
#else
if (hour()==0 && minute()==0 && second()==1 && ssnp==0)
#endif
{
//leapyear or not - Define days per year
if (year()%4 == 0 && !(year()%100 == 0 && year()%400 != 0)) {
DaysPerYear=366;
}
//every other year
else {
DaysPerYear = 365;
}
//Call Day Number Calc
DayNumber(year(),month(),day());
//define days between begin and middle season
int seasons[9] ={
0,45,96,135,187,238,283,328,DaysPerYear };
//define season and array pulling variable
for (s=0; seasons[s] < Ndays; s++) ssn = s+1, ssnpt = s+1, ssnp = s;
//set loop on array time pulling variable to go back to beginning instead of increasing array size
if (ssn >= 7) ssn = 0;
//differece in seconds between season array times
long rise1 = calcSec(risehour[ssn],riseminute[ssn]);
long rise2 = calcSec(risehour[ssnp],riseminute[ssnp]);
iDiffrise = calcTime(rise1, rise2);
long set1 = calcSec(sethour[ssn],setminute[ssn]);
long set2 = calcSec(sethour[ssnp],setminute[ssnp]);
iDiffset = calcTime(set1,set2);
//calculate new sunrise/set difference from array value
//here we take the difference in day and get a difference in seconds per day. So if the time difference is 30 minutes from the last e
//uation we divide that by the start day of season subtracted by the end day of season to get the number of days in the season
//example: 30 minutes/(day 45 – day 0) = 30/45 = 0.66666
//0.66666 is the difference of minutes each day throughout the season for sunrise
risediffperday = iDiffrise/(seasons[ssnpt]-seasons[ssnp]);
totalrise = risediffperday*(Ndays - seasons[ssnp]);
setdiffperday = iDiffset/(seasons[ssnpt]-seasons[ssnp]);
totalset = setdiffperday*(Ndays - seasons[ssnp]);
//creating time in seconds
rtime=calcSec(risehour[ssnp],riseminute[ssnp]);
if (ssnp == 0 || ssnp == 2 || ssnp == 4 || ssnp == 6){
rtime -= totalrise;
}
else {
rtime += totalrise;
}
stime=calcSec(sethour[ssnp],setminute[ssnp]);
if (ssnp == 1 || ssnp == 3 || ssnp == 5 || ssnp == 7){
stime -= totalset;
}
else {
stime += totalset;
}
wrtime = rtime + 1200;
wstime = stime - 1200;
vrtime = rtime - 1200;
vstime = stime + 1200;
//turning seconds back to h m s
//here are three different times offset by half hour to have different colors rise and set at different times
//standard times
rhour=rtime/3600;
rtime=rtime%3600;
rmin=rtime/60;
rtime=rtime%60;
rsec=rtime;
if(rsec > 30) rmin++;
shour=stime/3600;
stime=stime%3600;
smin=stime/60;
stime=stime%60;
ssec=stime;
if(ssec > 30) smin++;
//to change the offset change the number 1200 in seconds added or subtracted to rtime
//half hour shorter
wrhour = wrtime/3600;
wrtime=wrtime%3600;
wrmin=wrtime/60;
wrtime=wrtime%60;
wrsec=wrtime;
if(wrsec>30) wrmin++;
wshour = wstime/3600;
wstime=wstime%3600;
wsmin=wstime/60;
wstime=wstime%60;
wssec=wstime;
if(wssec>30) wsmin++;
//half hour longer
vrhour = vrtime/3600;
vrtime=vrtime%3600;
vrmin=vrtime/60;
vrtime=vrtime%60;
vrsec=vrtime;
if(vrsec>30) vrmin++;
vshour = vstime/3600;
vstime=vstime%3600;
vsmin=vstime/60;
vstime=vstime%60;
vssec=vstime;
if(vssec>30) vsmin++;
//time for each active led channel to pull for sunrise
int Sunrise[2]={
rhour,rmin };
int Sunset[2]={
shour,smin };
int whSunrise[2]={
wrhour,wrmin };
int whSunset [2]={
wshour,wsmin };
int vSunrise[2]={
vrhour,vrmin };
int vSunset [2]={
vshour,vsmin };
ChannelValue[LEDPWM0]=PWMSlope(vSunrise[1],vSunrise[2],vSunset[1],vSunset[2],0,100,180,ChannelValue[LEDPWM1]);
ChannelValue[LEDPWM1]=PWMSlope(whSunrise[1],whSunrise[2],whSunset[1],whSunset[2],0,100,180,ChannelValue[LEDPWM0]);
ChannelValue[LEDPWM2]=PWMSlope(Sunrise[1],Sunrise[2],Sunset[1],Sunset[2],0,100,180,ChannelValue[LEDPWM2]);
ChannelValue[LEDPWM3]=PWMSlope(Sunrise[1],Sunrise[2],whSunset[1],whSunset[2],0,100,180,ChannelValue[LEDPWM3]);
//return Sunrise, Sunset, whSunrise,whSunset,vSunrise,vSunset;
}
}
//End Seasons Calculation
//*********************************************************************************************************************************
//*********************************************************************************************************************************
//Calculators for Seasons function
long calcSec(long hr, long minu)
{
long totalseconds;
totalseconds=(hr*3600)+(minu*60);
return totalseconds;
}
long calcTime(long seconds1, long seconds2)
{
long timediff=abs(seconds1-seconds2);
return timediff;
}
void DayNumber(unsigned int y, unsigned int m, unsigned int d)
{
int days[]={
0,31,59,90,120,151,181,212,243,273,304,334 }; // Number of days at the beginning of the month in a not leap year.
//Start to calculate the number of day
if (m==1 || m==2){
Ndays = days[(m-1)]+d; //for any type of year, it calculate the number of days for January or february
} // Now, try to calculate for the other months
else if ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0){ //those are the conditions to have a leap year
Ndays = days[(m-1)]+d+1; // if leap year, calculate in the same way but increasing one day
}
else { //if not a leap year, calculate in the normal way, such as January or February
Ndays = days[(m-1)]+d;
}
}
//End calculators
//*********************************************************************************************************************************