Progressive sunphase

Do you have a question on how to do something.
Ask in here.
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

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
//*********************************************************************************************************************************
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: Progressive sunphase

Post by rimai »

I was able to see them jumping around too, but will need more time to figure out why.
Roberto.
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

No problem, Im trying to weed through it too, just hadn't had luck so figured i'd see if another set of eyes helped
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

Also, I've been trying to figure this out - with sunrise and sunset I'm getting the right readout for times. But the pwmslope makes it rise at night - I think its due to the single digit integers in the morning. I'm thinking I need to create a function that reads the ints to see if they are greater then 9, if they are less then nine convert them to a string to put a 0 infront of it for pwm slope to read properly...any ideas how to acomplish this?
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: Progressive sunphase

Post by rimai »

PWMSlope can be used with single digits.
In fact it won't work if you pass it a string.
Roberto.
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

Hmmmm I lol I may have something backwards in the pwm slope then. You think of anything for the cloud function? Its copied directly from yours and bhmiars code
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: Progressive sunphase

Post by rimai »

Didn't find time yet, but I'll look into it.
I think it's got to do with the % chance per day and/or the every x days.
My tests were always 100% every day.
Roberto.
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

K, no problem. I did notice one thing about it - I have it set to only do clouds between 11am and 4pm now. If its within the 11-4 range I get a solid number, outside of that range it just randomizes. From what I can tell anyway - I may just write an if statement to only have it on screen during 11-4 so I don't gotta watch the randomized characters lol
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: Progressive sunphase

Post by rimai »

That's very good debugging info. Thanks.
Roberto.
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: Progressive sunphase

Post by rimai »

Ok, so for those that want to implement this and take advantage of the PWM Expansion Module processing capability, here is how to upload new code to the module:
1. Connect the cable according to the picture:
PWM-Connector.png
PWM-Connector.png (207.23 KiB) Viewed 6686 times
2. Use Arduino to upload code just like you do with RA.
Roberto.
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

Yea I was gonna post that, I've gotten a couple PM's about that haha - Also still sorting out some things with the code ill update again soon
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

Latest Version of the code

I beleive most of the bugs are worked out of it if you find any let me know.
I dunno about everyone else but one of the main reasons I wrote this was because it was a feature another controller(neptune) had but only worked for bulbed fixtures - not pwm fixtures or PWM LEDs. But we all bought and wanted this controller(RA) because of its abilities to do exactly what WE want the way WE want it to. I wanted to be able to bring another feature to the table for the RA(mainly for myself haha)- The hardware was here but not necessarily the code. Hopefully everyone can enjoy it as much as I have writing it. Hope it works for you guys if you decide to use it. Thanks Roberto for the help, and also creating this amazing peice of hardware to make my life simple :D and Binder for all the wonderful precoded libraries full of goodness.

Global Variable in RA PDE

Code: Select all

byte SeasonsVar[]={
  0,0,0,0,0,0,0,0,0,0,0,0};
Placed in custom Main() to pull variables from PWM Module

Code: Select all

 Wire.requestFrom(8,8);
  for (int a=0;a<12;a++)
  {
    if (Wire.available()) SeasonsVar[a]=Wire.receive();
  }
Example to reference them for display numbers 0-11
This is the order from 0
PWM0, sunrisehour, sunriseminute, sunsethour, sunsetminute, cloudstarthout, cloudstartminute, cloudduration,lightningchance, PWM1,PWM2,PWM3

Code: Select all

ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,15,98,SeasonsVar[0]);
PWM PDE

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 Vavriables
long calcSec(long,long);
long calcTime(long,long);
short Ndays;
static byte lightningchance=0;
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};
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;
//*********************************************************************************************************************************

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

//*********************************************************************************************************************************
//Loop
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]);
    
  }
}
//End Loop
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
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);
  }
}
//End Standard Functions
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Send Data Function
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;
  SeasonsVar[8]=lightningchance;
  SeasonsVar[9]=ChannelValue[1];
  SeasonsVar[10]=ChannelValue[2];
  SeasonsVar[11]=ChannelValue[3];
  Wire.send(SeasonsVar,12);
}
//End Send Data
//*********************************************************************************************************************************

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

  // 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(15,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 B011101

  // 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 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()
{
  //Set the hour you want the calculations of rise an set to be based on
  int UserRiseHour = 6;
  int UserSetHour =  19;
  
  #define forceseasoncalculation
  static byte ssn , ssnp = 0 , 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 - DO NOT CHANGE
    int risehour[8]= {UserRiseHour+1,UserRiseHour+1,UserRiseHour+1,UserRiseHour,UserRiseHour,UserRiseHour-1,UserRiseHour,UserRiseHour};/*{
    7,7,7,6,6,5,6,6      };*/
  int riseminute[8]={
    00,30,00,30,00,30,00,30      };
  int sethour[8] = {UserSetHour-2,UserSetHour-1,UserSetHour-1,UserSetHour,UserSetHour,UserSetHour,UserSetHour,UserSetHour-1};/*{
    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 to define DaysPerYear - DO NOT CHANGE
      if (year()%4 == 0 && !(year()%100 == 0 && year()%400 != 0)) {
        DaysPerYear=366;
      }
      else {
        DaysPerYear = 365;
      }
      //Call Day Number Calc to determin day ie december 31st on a non leap year is day 365 - DO NOT CHANGE
      DayNumber(year(),month(),day());
      //define days between beginning, middle and end of seasons high peaks -  DO NOT CHANGE
      int seasons[9] ={
        0,45,96,135,187,238,283,328,DaysPerYear                  };
      //define season and array pulling variable - DO NOT CHANGE
      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 - DO NOT CHANGE
      if (ssn >= 7) ssn = 0;

      //differece in seconds between two rise/set array times pulled - DO NOT CHANGE
      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 & last group of code - DO NOT CHANGE
      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 for main sun rise/set number - DO NOT CHANGE
      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;
      }
      
      //These are the off set times, standard rtime and stime are for Royal Blues & Blues
      // DO NOT CHANGE the operators in these equations ie +- 
      // The number is in seconds (1200) change this number to change the offset for each color
      wrtime = rtime + 1200;//w r/stime is for Whites - shorter time span then blues
      wstime = stime - 1200;
      vrtime = rtime - 1200;//v r/stime is for Violets - Longer time then blues
      vstime = stime + 1200;
      
      //turning seconds back to Hours:Minutes:Seconds
      //Blues
      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++;
      //White
      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++;
      //Violet
      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 - DO NOT CHANGE
      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                  };
        
      //This is the PWM Slope for each channel, each channel pulls an array value from above(hour,minute) to use, how you set them is up to you. 
      //Just always use a Rise hour in a Rise spot, always a set hour in a set spot ect ect
      ChannelValue[LEDPWM0]=PWMSlope(vSunrise[1],vSunrise[2],vSunset[1],vSunset[2],0,100,1,ChannelValue[LEDPWM0]);
      ChannelValue[LEDPWM1]=PWMSlope(whSunrise[1],whSunrise[2],whSunset[1],whSunset[2],0,100,1,ChannelValue[LEDPWM1]);
      ChannelValue[LEDPWM2]=PWMSlope(Sunrise[1],Sunrise[2],Sunset[1],Sunset[2],0,100,1,ChannelValue[LEDPWM2]);
      ChannelValue[LEDPWM3]=PWMSlope(Sunrise[1],Sunrise[2],whSunset[1],whSunset[2],0,100,1,ChannelValue[LEDPWM3]);

      //ChannelValue[LEDPWM4]=PWMSlope(Sunrise[1],Sunrise[2],whSunset[1],whSunset[2],0,100,180,ChannelValue[LEDPWM3]);
      //ChannelValue[LEDPWM5]=PWMSlope(Sunrise[1],Sunrise[2],whSunset[1],whSunset[2],0,100,180,ChannelValue[LEDPWM3]);
    }
}
//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
//*********************************************************************************************************************************

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

Re: Progressive sunphase

Post by rimai »

Did you find the bug with the CheckCloud() function?
Roberto.
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

Yes - I had an accidental key stroke which set one of the user set variables outside of the 0 -100 range (cloudchanceperday) and it was set on 140. Forgot or guess I missed deleting the 1 seemed to cause a loop
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: Progressive sunphase

Post by rimai »

Cool!!! :)
Roberto.
rimai
Posts: 12857
Joined: Fri Mar 18, 2011 6:47 pm

Re: Progressive sunphase

Post by rimai »

Ok, so I think we are ready to force some clouds, shall we?
The 2 way communication is already implemented in this code, so all we need to do is expand the protocol so both RA and PWM module understand each other.
In the protocol that is already being used, we have this:
This list of commands:
0 - Sets PWM % to channel 0
1 - Sets PWM % to channel 1
2 - Sets PWM % to channel 2
3 - Sets PWM % to channel 3
4 - Sets PWM % to channel 4
5 - Sets PWM % to channel 5

What we want to do is add a new command.
6 - Force cloud

Then we want to implement something similar to this thread:
http://forum.reefangel.com/viewtopic.php?f=12&t=425

We don't really care what the data is going to be, so we only check for the cmd.
This is the part that would trigger the force clouds, instead of the menu entry on RA.

Code: Select all

  if (cmd==6) ForceCloud=true;
So the final code is:

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 Vavriables
long calcSec(long,long);
long calcTime(long,long);
short Ndays;
static byte lightningchance=0;
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};
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 ForceCloud=false;

//*********************************************************************************************************************************

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

//*********************************************************************************************************************************
//Loop
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]);
    
  }
}
//End Loop
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
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);
  }
  if (cmd==6) ForceCloud=true;
}
//End Standard Functions
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Send Data Function
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;
  SeasonsVar[8]=lightningchance;
  SeasonsVar[9]=ChannelValue[1];
  SeasonsVar[10]=ChannelValue[2];
  SeasonsVar[11]=ChannelValue[3];
  Wire.send(SeasonsVar,12);
}
//End Send Data
//*********************************************************************************************************************************

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

  // 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(15,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 B011101

  // 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 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 (ForceCloud)
  {
  ForceCloud=false;
  cloudchance=1;
  cloudduration=10;
  lightningchance=1;
  cloudstart=NumMins(hour(),minute())+1;
  }

  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()
{
  //Set the hour you want the calculations of rise an set to be based on
  int UserRiseHour = 6;
  int UserSetHour =  19;
  
  #define forceseasoncalculation
  static byte ssn , ssnp = 0 , 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 - DO NOT CHANGE
    int risehour[8]= {UserRiseHour+1,UserRiseHour+1,UserRiseHour+1,UserRiseHour,UserRiseHour,UserRiseHour-1,UserRiseHour,UserRiseHour};/*{
    7,7,7,6,6,5,6,6      };*/
  int riseminute[8]={
    00,30,00,30,00,30,00,30      };
  int sethour[8] = {UserSetHour-2,UserSetHour-1,UserSetHour-1,UserSetHour,UserSetHour,UserSetHour,UserSetHour,UserSetHour-1};/*{
    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 to define DaysPerYear - DO NOT CHANGE
      if (year()%4 == 0 && !(year()%100 == 0 && year()%400 != 0)) {
        DaysPerYear=366;
      }
      else {
        DaysPerYear = 365;
      }
      //Call Day Number Calc to determin day ie december 31st on a non leap year is day 365 - DO NOT CHANGE
      DayNumber(year(),month(),day());
      //define days between beginning, middle and end of seasons high peaks -  DO NOT CHANGE
      int seasons[9] ={
        0,45,96,135,187,238,283,328,DaysPerYear                  };
      //define season and array pulling variable - DO NOT CHANGE
      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 - DO NOT CHANGE
      if (ssn >= 7) ssn = 0;

      //differece in seconds between two rise/set array times pulled - DO NOT CHANGE
      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 & last group of code - DO NOT CHANGE
      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 for main sun rise/set number - DO NOT CHANGE
      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;
      }
      
      //These are the off set times, standard rtime and stime are for Royal Blues & Blues
      // DO NOT CHANGE the operators in these equations ie +- 
      // The number is in seconds (1200) change this number to change the offset for each color
      wrtime = rtime + 1200;//w r/stime is for Whites - shorter time span then blues
      wstime = stime - 1200;
      vrtime = rtime - 1200;//v r/stime is for Violets - Longer time then blues
      vstime = stime + 1200;
      
      //turning seconds back to Hours:Minutes:Seconds
      //Blues
      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++;
      //White
      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++;
      //Violet
      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 - DO NOT CHANGE
      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                  };
        
      //This is the PWM Slope for each channel, each channel pulls an array value from above(hour,minute) to use, how you set them is up to you. 
      //Just always use a Rise hour in a Rise spot, always a set hour in a set spot ect ect
      ChannelValue[LEDPWM0]=PWMSlope(vSunrise[1],vSunrise[2],vSunset[1],vSunset[2],0,100,1,ChannelValue[LEDPWM0]);
      ChannelValue[LEDPWM1]=PWMSlope(whSunrise[1],whSunrise[2],whSunset[1],whSunset[2],0,100,1,ChannelValue[LEDPWM1]);
      ChannelValue[LEDPWM2]=PWMSlope(Sunrise[1],Sunrise[2],Sunset[1],Sunset[2],0,100,1,ChannelValue[LEDPWM2]);
      ChannelValue[LEDPWM3]=PWMSlope(Sunrise[1],Sunrise[2],whSunset[1],whSunset[2],0,100,1,ChannelValue[LEDPWM3]);

      //ChannelValue[LEDPWM4]=PWMSlope(Sunrise[1],Sunrise[2],whSunset[1],whSunset[2],0,100,180,ChannelValue[LEDPWM3]);
      //ChannelValue[LEDPWM5]=PWMSlope(Sunrise[1],Sunrise[2],whSunset[1],whSunset[2],0,100,180,ChannelValue[LEDPWM3]);
    }
}
//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
//*********************************************************************************************************************************

You still need to create the menu entry to your custom menu to send this new command to the PWM module.
So, on your custom menu, you can add this:

Code: Select all

void MenuEntry1()
{
  ReefAngel.PWM.Expansion(6,0);
}
Give it a try and let me know if this works.
Roberto.
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

Interesting - I will give it a try :D Thanks for the input roberto. I just did wire up the PWM module to my drivers - was using a Dim4 analog controller. Thus far I am only getting 0% across the board, but all the right numbers for rise/set times. Im about to go test with a multimeter to find out whats up. I've changed the slope to ramp up over 1 minute temporarily so hopefully I can figure this out by tomorrow morning.
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

Still getting 0...nothing rising ugh

ok I had to change ChannelValue to an int not a byte - as all the values being input to it were integers. But now my lights are at 100%pwm....and my leds are only 30% lit up :(

ok - fixed it here's yet another updated code lol

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 Vavriables
long calcSec(long,long);
long calcTime(long,long);
short Ndays;
static byte lightningchance=0;
byte cloudduration=0;
int cloudstart=0;
int rhour = 0, rmin = 0, shour = 0, smin = 0;
byte PWMports[] ={
  3,5,6,9,10,11};
int ChannelValue[] = {
  0,0,0,0,0,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 ForceCloud=false;

//*********************************************************************************************************************************

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

//*********************************************************************************************************************************
//Loop
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]);
    
  }
}
//End Loop
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
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);
  }
  if (cmd==6) ForceCloud=true;
}
//End Standard Functions
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Send Data Function
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;
  SeasonsVar[8]=lightningchance;
  SeasonsVar[9]=ChannelValue[1];
  SeasonsVar[10]=ChannelValue[2];
  SeasonsVar[11]=ChannelValue[3];
  Wire.send(SeasonsVar,12);
}
//End Send Data
//*********************************************************************************************************************************

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

  // 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(15,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 B00011

  // 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 B001100

  // 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 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 (ForceCloud)
  {
  ForceCloud=false;
  cloudchance=1;
  cloudduration=10;
  lightningchance=1;
  cloudstart=NumMins(hour(),minute())+1;
  }

  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()
{
  //Set the hour you want the calculations of rise an set to be based on
  int UserRiseHour = 6;
  int UserSetHour =  19;
  
  #define forceseasoncalculation
  static byte ssn , ssnp = 0 , 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 - DO NOT CHANGE
    int risehour[8]= {UserRiseHour+1,UserRiseHour+1,UserRiseHour+1,UserRiseHour,UserRiseHour,UserRiseHour-1,UserRiseHour,UserRiseHour};/*{
    7,7,7,6,6,5,6,6      };*/
  int riseminute[8]={
    00,30,00,30,00,30,00,30      };
  int sethour[8] = {UserSetHour-2,UserSetHour-1,UserSetHour-1,UserSetHour,UserSetHour,UserSetHour,UserSetHour,UserSetHour-1};/*{
    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 to define DaysPerYear - DO NOT CHANGE
      if (year()%4 == 0 && !(year()%100 == 0 && year()%400 != 0)) {
        DaysPerYear=366;
      }
      else {
        DaysPerYear = 365;
      }
      //Call Day Number Calc to determin day ie december 31st on a non leap year is day 365 - DO NOT CHANGE
      DayNumber(year(),month(),day());
      //define days between beginning, middle and end of seasons high peaks -  DO NOT CHANGE
      int seasons[9] ={
        0,45,96,135,187,238,283,328,DaysPerYear                  };
      //define season and array pulling variable - DO NOT CHANGE
      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 - DO NOT CHANGE
      if (ssn >= 7) ssn = 0;

      //differece in seconds between two rise/set array times pulled - DO NOT CHANGE
      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 & last group of code - DO NOT CHANGE
      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 for main sun rise/set number - DO NOT CHANGE
      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;
      }
      
      //These are the off set times, standard rtime and stime are for Royal Blues & Blues
      // DO NOT CHANGE the operators in these equations ie +- 
      // The number is in seconds (1200) change this number to change the offset for each color
      wrtime = rtime + 1200;//w r/stime is for Whites - shorter time span then blues
      wstime = stime - 1200;
      vrtime = rtime - 1200;//v r/stime is for Violets - Longer time then blues
      vstime = stime + 1200;
      
      //turning seconds back to Hours:Minutes:Seconds
      //Blues
      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++;
      //White
      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++;
      //Violet
      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++;

        
      //This is the PWM Slope for each channel, each channel pulls an array value from above(hour,minute) to use, how you set them is up to you. 
      //Just always use a Rise hour in a Rise spot, always a set hour in a set spot ect ect
      ChannelValue[LEDPWM0]=PWMSlope(vrhour,vrmin,vshour,vsmin,0,2.55*100,180,ChannelValue[LEDPWM0]);
      ChannelValue[LEDPWM1]=PWMSlope(rhour,rmin,shour,smin,0,2.55*100,180,ChannelValue[LEDPWM1]);
      ChannelValue[LEDPWM2]=PWMSlope(wrhour,wrmin,wshour,wsmin,0,2.55*100,180,ChannelValue[LEDPWM2]);
      ChannelValue[LEDPWM3]=PWMSlope(rhour,rmin,wshour,wsmin,0,2.55*100,180,ChannelValue[LEDPWM3]);


      //ChannelValue[LEDPWM4]=PWMSlope(Sunrise[1],Sunrise[2],whSunset[1],whSunset[2],0,100,180,ChannelValue[LEDPWM3]);
      //ChannelValue[LEDPWM5]=PWMSlope(Sunrise[1],Sunrise[2],whSunset[1],whSunset[2],0,100,180,ChannelValue[LEDPWM3]);
    }
}
//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
//*********************************************************************************************************************************
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: Progressive sunphase

Post by rufessor »

Hi All-

So I am hesitant but thinking I might be able to attempt to implement this on my system as I have the PWM expansion running 4 LED modules (with a 5th to be installed this week).

What I am wondering, is the following.

Since I am currently still in the learning phase... I am thinking that if I just load your PWM PDE (which is the code posted in the last reply to this thread) onto my PWM controller, and change around the actinic/daylight positions on the PWM part to accurately reflect how mine is wired... this would basically run.

I would like to avoid anything with custom menu and display of any of the data for now as I simply have not even looked at that stuff and it would swamp me in terms of getting anything to work correct.

Can anyone help me by providing a fairly detailed explanation of what exactly needs to be added to my controller PDE for this to work, It seems like very little.

Then I would be able to remove the cloud function and any light stuff from my controller PDE and begin to play with custom menu/display to get things to report back, but for now I would be happy just to off load all this to the PWM board and let it run in "stealth" mode, just do its thing without telling me what its doing just now.

I am thinking that all this.... does the following (see my // comments)

// Initiate an 12 member array to accept data from the PWM PDE)
byte SeasonsVar[]={
0,0,0,0,0,0,0,0,0,0,0,0};

// Ask the PWM controller for the data it has generated within SeasonsVar stepping through [a] as a++ i.e. 0, then 1 etc until we hit 11 when it stops.

Wire.requestFrom(8,8);
for (int a=0;a<12;a++)
{
if (Wire.available()) SeasonsVar[a]=Wire.receive();
}

//THen, display each individual SeasonsVar[a] on the LCD with each one requiring a line like the following.
//But I would need to add 10 more lines like this to display each data point or else they would overwrite each other.
ReefAngel.LCD.DrawText(COLOR_CORNFLOWERBLUE,255,15,98,SeasonsVar[0]);



So if I just want to have the PWM PDE working... I am thinking none of that is required... until I want to get a display going...


Thoughts? Help?
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

You are correct, You do not need any of that on the RA unless you want to pull values, the code can be loaded as is however - here is the latest

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 Vavriables
long calcSec(long,long);
long calcTime(long,long);
short Ndays;
static byte lightningchance=0;
byte cloudduration=0;
int cloudstart=0;
int rhour = 0, rmin = 0, shour = 0, smin = 0;
byte PWMports[] ={
  3,5,6,9,10,11};
int ChannelValue[] = {
  0,0,0,0,0,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 ForceCloud=false;

//*********************************************************************************************************************************

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

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

  }
}
//End Loop
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Standard PWM Functions Receive/Process
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);
  }
  if (cmd==6) ForceCloud=true;
}
//End Standard Functions
//*********************************************************************************************************************************

//*********************************************************************************************************************************
//Send Data Function
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;
  SeasonsVar[8]=lightningchance;
  SeasonsVar[9]=ChannelValue[1];
  SeasonsVar[10]=ChannelValue[2];
  SeasonsVar[11]=ChannelValue[3];
  Wire.send(SeasonsVar,12);
}
//End Send Data
//*********************************************************************************************************************************

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

  // 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(18,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 B011011

  // 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 B000100

  // 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 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 (ForceCloud)
  {
    ForceCloud=false;
    cloudchance=1;
    cloudduration=10;
    lightningchance=1;
    cloudstart=NumMins(hour(),minute())+1;
  }

  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* 0.5))) && second()<1.5) 
      {
        int strikes = random(6);
        for (int b=0;b<strikes;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;
            if (b==random(strikes)) delay(2);
          }
          else
          {
            ChannelValue[b]=20;
          }
        }
      }
      if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration * 0.25))) && second()<1.5) 
      {
        int strikes = random(6);
        for (int b=0;b<strikes;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;
            if (b==random(strikes)) delay(2);
          }
          else
          {
            ChannelValue[b]=20;
          }
        }
      }
      if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration * 0.75))) && second()<1.5) 
      {
        int strikes = random(6);
        for (int b=0;b<strikes;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;
            if (b==random(strikes)) delay(2);
          }
          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()
{
  //Set the hour you want the calculations of rise an set to be based on
  int UserRiseHour = 9;
  int UserSetHour =  22;

#define forceseasoncalculation
  static byte ssn , ssnp = 0 , 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 - DO NOT CHANGE
  int risehour[8]= {
    UserRiseHour+1,UserRiseHour+1,UserRiseHour+1,UserRiseHour,UserRiseHour,UserRiseHour-1,UserRiseHour,UserRiseHour  };/*{
   7,7,7,6,6,5,6,6      };*/
  int riseminute[8]={
    00,30,00,30,00,30,00,30        };
  int sethour[8] = {
    UserSetHour-2,UserSetHour-1,UserSetHour-1,UserSetHour,UserSetHour,UserSetHour,UserSetHour,UserSetHour-1  };/*{
   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 to define DaysPerYear - DO NOT CHANGE
      if (year()%4 == 0 && !(year()%100 == 0 && year()%400 != 0)) {
        DaysPerYear=366;
      }
      else {
        DaysPerYear = 365;
      }
      //Call Day Number Calc to determin day ie december 31st on a non leap year is day 365 - DO NOT CHANGE
      DayNumber(year(),month(),day());
      //define days between beginning, middle and end of seasons high peaks -  DO NOT CHANGE
      int seasons[9] ={
        0,45,96,135,187,238,283,328,DaysPerYear                        };
      //define season and array pulling variable - DO NOT CHANGE
      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 - DO NOT CHANGE
      if (ssn >= 7) ssn = 0;

      //differece in seconds between two rise/set array times pulled - DO NOT CHANGE
      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 & last group of code - DO NOT CHANGE
      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 for main sun rise/set number - DO NOT CHANGE
      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;
      }

      //These are the off set times, standard rtime and stime are for Royal Blues & Blues
      // DO NOT CHANGE the operators in these equations ie +- 
      // The number is in seconds (1200) change this number to change the offset for each color
      wrtime = rtime + 1200;//w r/stime is for Whites - shorter time span then blues
      wstime = stime - 1200;
      vrtime = rtime - 1200;//v r/stime is for Violets - Longer time then blues
      vstime = stime + 1200;

      //turning seconds back to Hours:Minutes:Seconds
      //Blues
      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++;
      //White
      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++;
      //Violet
      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++;


      //This is the PWM Slope for each channel, each channel pulls an array value from above(hour,minute) to use, how you set them is up to you. 
        //Just always use a Rise hour in a Rise spot, always a set hour in a set spot ect ect
      ChannelValue[LEDPWM0]=PWMSlope(vrhour,vrmin,vshour,vsmin,0,2.55*85,90,ChannelValue[LEDPWM0]);
      ChannelValue[LEDPWM1]=PWMSlope(vrhour,vrmin,vshour,vsmin,0,2.55*85,90,ChannelValue[LEDPWM1]);
      ChannelValue[LEDPWM2]=PWMSlope(wrhour,wrmin,wshour,wsmin,0,2.55*95,90,ChannelValue[LEDPWM2]);
      ChannelValue[LEDPWM3]=PWMSlope(rhour,rmin,wshour,wsmin,0,2.55*85,90,ChannelValue[LEDPWM3]);
      ChannelValue[LEDPWM4]=PWMSlope(rhour,rmin,shour,smin,0,2.55*85,90,ChannelValue[LEDPWM4]);
    }
}
//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
//*********************************************************************************************************************************


look for these lines in above code and change the hour you wish for it to rise/set

Code: Select all

  int UserRiseHour = 9;
  int UserSetHour =  22;
You had mentioned you wanted one side of the tank to rise sooner then the other. On my tank I have it set as different colors, but for you you could use the V variables as for the fifth channel your adding of violets, and the standard variables as left, and then the W variables as right. here the W variables are a half hour shorter then standard variables, and V variables are half hour longer, change the seconds but not the operator unless you are comfortable and understand that you will be doing the opposite of what I just said

Code: Select all

wrtime = rtime + 1200;//w r/stime is for Whites - shorter time span then blues
      wstime = stime - 1200;
      vrtime = rtime - 1200;//v r/stime is for Violets - Longer time then blues
      vstime = stime + 1200
After this you need to assign how your PWM channels are used here is how I am using them

Code: Select all

ChannelValue[LEDPWM0]=PWMSlope(vrhour,vrmin,vshour,vsmin,0,2.55*85,90,ChannelValue[LEDPWM0]);
      ChannelValue[LEDPWM1]=PWMSlope(vrhour,vrmin,vshour,vsmin,0,2.55*85,90,ChannelValue[LEDPWM1]);
      ChannelValue[LEDPWM2]=PWMSlope(wrhour,wrmin,wshour,wsmin,0,2.55*95,90,ChannelValue[LEDPWM2]);
      ChannelValue[LEDPWM3]=PWMSlope(rhour,rmin,wshour,wsmin,0,2.55*85,90,ChannelValue[LEDPWM3]);
      ChannelValue[LEDPWM4]=PWMSlope(rhour,rmin,shour,smin,0,2.55*85,90,ChannelValue[LEDPWM4]);
the values you can use to fill the positions are below. The variables with an R are the rise variables, the variables with an S are the set variables.

Code: Select all

rhour, rmin
wrhour,wrmin
vrhour,vrmin
shour,smin
wshour,wsmin
vshour,vsmin
the channelValue code is as such so you know where and what each thing means

Code: Select all

ChannelValue[LEDPWM#]=PWMSlope(Start Hour, Start Minute, End Hour, End Minute, Start Slope Value %, End Slope Value %(max255), Ramp Time, ChannelValue[LEDPWM#]);
Hope that helps and makes some sense, what you need to load is the first block of code I posted - but you need to edit the references I posted above within the code to meet your needs. Also - Don't forget to edit the cloud functions variables to meet your wants. If you want all channels to rise and set at the same time and not have left to right like you spoke to me about you could simply just declare your channelvalue's and use the standard time variables
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: Progressive sunphase

Post by rufessor »

Sweet. That is all looking very easy- having not yet tried anything :)

I will I think be running a bit closer to your configuration as I have a left and right bank of white LEDs (Cree XP-G NW) and a left and right bank of "Blue" LED's (Cree XP-E RB and Cree XP-E Blue) as well as a universal full tank coverage bank of Violets (which have shipped but are not yet on my tank). So I can run the sunrise start with the blues, then the whites running right to left (or whatever) and then hit the full tank with the UV after everything else is on and then run sunset opposite (whites dim first, then blues) and leave the UV string on later for max fluorescent viewing at night. I will try this probably tonight, so my understanding would be that I need to convert my RA controller PDE to something basic, I would like to control my fuge lights from the controller but I think I could just write a simple time based script to turn on that socket and thus be able to run the controller with a RAgen script PDE that deletes all lights saving even more space for future code on the controller. see any issues with that, i.e. I will probably eventually go with something like a moon phase calculator and I am not sure if that will run on the PWM board or the controller...

Thanks for the very clear response.... will post back success or failure as needed.
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

you could do something like this if you use the code on RA to pull the values

add this to top of RA PDE

Code: Select all

byte SeasonsVar[]={
  0,0,0,0,0,0,0,0,0,0,0,0};

Placed in Loop() to pull variables from PWM Module

Code: Select all

Wire.requestFrom(8,12);
  for (int a=0;a<12;a++)
  {
    if (Wire.available()) SeasonsVar[a]=Wire.receive();
  }


if (SeasonsVar[0] == 0) ReefAngel.Relay.On(Refugium);
else ReefAngel.Relay.Off(Refugium);
Basically - if PWM channel 0 has a value of 0 the refugium light will be on.

just make sure you define the port like so

Code: Select all

#define Refugium            3
also you can run the moonphase on the pwm module if you want or on the RA. Im not sure you can completely remove lights from the RA unless you don't plan on defining pwm module in feature file, and plan on running the PWM as a complete standalone - but using RA for power & time sync
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: Progressive sunphase

Post by rufessor »

Thanks... I was looking at the code, you have three different lightning strike "patterns" that each use essentially the same strike generation code, so that part is repeated 3 x ... see below

its not a big savings, but could we move this to a function such as... "Strike" which could be placed above the loop statement in the main code since it needs to be declared prior to the If statements which call it (I think this is correct, I am being painfully exact because I want to be sure I get this correct)

Int Strike() //Since it passes the value for b channels I think we need an int?? OR could we use Void which I think would just auto set to pass the value as assigned?
{
int strikes = random(6);
for (int b=0;b<strikes;b++)
{
if (bitRead(Daylight_Channels,b)==1)
{
if (random(100)<20) lightningstatus=1;
else lightningstatus=0;
if (lightningstatus) ChannelValue=100;
else ChannelValue=0;
if (b==random(strikes)) delay(2);
}
else
{
ChannelValue=20;
}
}
}

And then in the lighting strike calc use something like this as a nested if statement to parse when lightning will occur referencing the strike function (Void because its not returning a data type correct? Or am I getting in over my head)..... else its reads as three nested if statements each reiterating the strike code within the if statement... I think this will take less memory (not much) and would make the code easier to read... if what I am proposing actually turns out to be correct.

{
if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration* 0.5))) && second()<1.5) strike;
Else if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration * 0.25))) && second()<1.5) strike;
Else if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration * 0.75))) && second()<1.5) strike;
Else ChannelValue=20;
}
Last edited by rufessor on Wed Jan 18, 2012 1:08 pm, edited 1 time in total.
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: Progressive sunphase

Post by rufessor »

Oh... and can you tell me why your resseting ChannelValue=20 when in the PWMSlope calc it ramps to zero... during a cloud??\\Unsure a bit but it seems this value gets passed to your white PWM channels but since the loop is always running, does it actually ever trully pass as PWMSlope would be continually feeding it another value, the Lighning strike has a delay so the program should stop and pass the strike value (100%) to the daylight channels which obviously overrides PWMSlope but I am not sure what the last call does... not comenting on how this is written, its still over my head a bit, just trying to understand why that setting is made and what it does in reality.

THANKS!
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

Yes we could do that for lightning - the three sets were test runs as Im trying to implement more then one lightning strike during a storm.

also you could but void strike() after the function, and you would call it after the if statement like so

Code: Select all

if (blahblahblah) strike();
The reason ChannelValue=20 is how bhmiar and roberto coded it, but basically its setting the channels to 100% then down to 20% then back to 100% ect ect for each flash of the lightning strike - if you look at lightning in between flashes it never goes completely black, my guess is thats why they used a value of 20
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: Progressive sunphase

Post by rufessor »

Ahhh... I could see that you were allowing lighting to occur during different phases of the cloud... I will try to rearrange the code just to see if it works and to get more familiar with this whole thing. I think I am going to change that to end value to 0, lightning goes dark pretty damn quick... I may have to change the delay to get a longer strike.. it will be fun to play.

Thanks for providing this code... lots to play with!
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: Progressive sunphase

Post by rufessor »

Forget about turning this into a function to save a few lines of code, I don't know enough to make it work and I think it would be problematic due to the delay statement...

Working on implementing this one as is :)
I think I might be getting a better understanding of the whole thing by trying to work on parts of it THanks for the help!
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: Progressive sunphase

Post by rufessor »

Oh... getting ready to load this... can you post the features.h file required to get this to work on the PWM board.
Deckoz2302
Posts: 149
Joined: Tue Nov 01, 2011 11:05 am

Re: Progressive sunphase

Post by Deckoz2302 »

My features file

Code: Select all

/*
 * Copyright 2010 Curt Binder
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef __REEFANGEL_FEATURES_H__
#define __REEFANGEL_FEATURES_H__

/*
This contains all the defines for enabling/disabling features with the controller software.

This file can be AutoGenerated by using the RAGenFeatures program.
*/

/*
If your sketch/compile size is getting too big or if you are running out of RAM and having bizarre events
happen with the controller, you may want to not display the graphics on screen which can save some memory.
Just comment out the next line to prevent any graphics from being displayed, you will have text only screens
during water changes and feeding modes.

Approximately 346 bytes to have this feature
*/
//#define DisplayImages  // do we display the graphics for feeding or water change mode

/*
The next line is for displaying the setup screens to configure the values for the Feeding Mode timer and
the LCD shutoff timer.  The defaults are fine, BUT if you would like to have the ability to change them
from the setup screen, uncomment the next line.  This will increase the file size.  If you do not plan
to change these values often (or at all), keep the next line commented out.

Approximately 362 bytes to have this feature
*/
//#define SetupExtras  // feeding mode & screensaver timeout setup. ACTIVATE WITH CAUTION

/*
Since we may or may not need to always configure the Wavemakers, give the option to
turn off the setup screens to keep the compile size down.  You can still use the Wavemakers,
you just will not have the setup screen available to configure the values.  You will have to manually set
the intervals inside the Sketch (hardcode or have it read from memory if the memory contains the correct values).

Approximately 378 bytes to have WavemakerSetup
*/
//#define WavemakerSetup

/*
These next two options are for the Dosing Pumps.  They operate differently, so read carefully to determine
what option you want.

DosingPumpSetup
  Allows for specifying a specific time in which you want the dosing pump to turn on and run for.
  It only runs once per day and for the specified duration/run time.
  This feature will allow you to configure the start time and run time from the setup screens.

DosingPumpIntervalSetup
  Allows for specifying a minute interval that you would like the pump to turn on and run for.
  It runs every minute interval for the entire day.  It's start time is based off of midnight of the current day.
  If you specified a 60 minute interval, it will run for the specified duration every hour.
  This feature will allow you to configure the minute interval and run time from the setup screens.

Both options use the same Run Time that is stored in memory, so you will only be able to use one or the other
if you plan on configuring the run time from the setup screens.  If you use the hard coded values, you won't
need to use these options and you can use separate run times.

You can still use the DosingPump and DosingPumpInterval functions without these options,
you will just need to have the memory already be set or specifically set the values in the PDE file.
You just will not be able to change the values from the controller's setup screen.

Approximately 2000 bytes to have DosingPumpSetup
Approximately 368 bytes to have DosingPumpIntervalSetup
*/
//#define DosingPumpSetup
//#define DosingPumpIntervalSetup


/*
Overheat Temperature is fairly constant.  This value will most likely not get changed very often (if ever).
The default value is set to 150.0F.  Once this value is reached, all the lights will get shutoff.  If you
would like the ability to change this value from the menus, uncomment the next line.  Otherwise you will have
to hardcode the value in the ShowInterface Function

Approximately 156 bytes to have this feature
*/
//#define OverheatSetup

/*
The ability to set the Date & Time on the controller is controlled by this next line.  This line will add
in a Date / Time Setup menu entry which will allow you to set the date & time on the controller easily.
Comment the next line to remove this ability.

Approximately 1984 bytes to have this feature
*/
#define DateTimeSetup

/*
If you do not want to have a Version menu entry to see what version of the software is on the controller,
then you will want to comment out the next line

Approximately 144 bytes to have this feature
*/
//#define VersionMenu

/*
If you do not use any of the ATO features in your setup, you can comment out this next line to remove
the ATO set and clear menu items.

Approximately 900 bytes to have this feature (and without SetupExtras)

When this or SetupExtras are defined, the Timeouts menu is included.
Timeouts menu requires approximately 710 bytes
This feature requires approximately 190 bytes if SetupExtras is defined
*/
//#define ATOSetup

/*
This item will remove all lighting functionality from the controller.  It is the equivalent to
commenting out MetalHalideSetup, StandardLightSetup, DisplayLEDPWM
This will OVERRIDE any of the other defines.  So use caution when enabling this feature.

Approximately 2796 bytes to have this feature
*/
//#define RemoveAllLights

/*
If you do not use metal halides and do not wish to have any of the setup screens on your controller,
you can comment out the next line to remove the Metal Halide Setup and Metal Halide Delay

Approximately 258 bytes to have this feature
*/
//#define MetalHalideSetup

/*
If you do not use standard lights and do not wish to have the setup screens on your controller,
you can comment out the next line to remove the Standard Lights Setup

Approximately 90 bytes to have this feature
*/
//#define StandardLightSetup

/*
If you want to use the old way of reading the temp sensor which is always reading the value in
and not performing any sanity check, then you will want to uncomment this next line.

Otherwise, you will use the new way to handle the temperatures.  The value is read in and then
compared to the existing value.  If the difference between the 2 values is less than MAX_TEMP_SWING
or the temperature is 0 then the temperature is allowed to be updated, otherwise it is not updated.
MAX_TEMP_SWING is currently set to 50, which is 5.0 F.  This prevents any temporary large fluctations
in temperatures.  Also, there should not be more than a 5.0 F degree fluctation in 1 second.
*/
//#define DirectTempSensor

/*
Do we save the relay state before we enter Feeding Mode or Water Change Mode?

Comment out the next line to not save the state and always force specific ports to be turned off and then
back on again after we exit the mode.  This can turn on some ports that were not already on.  This
is also how the controller originally works.

Currently untested.
*/
#define SaveRelayState

/*
If you have the wifi module for your controller and wish to use it, you must uncomment this next line
to utilize the built-in webserver for the controller.

If enabled, you may want to consider enabling SIMPLE_MENU and disabling DateTimeSetup to save space.
This is advisable since all settings can be updated via the wifi interface.

Approximately 5000+ bytes to have this feature. This size can vary.
*/
//#define wifi

/*
This next line will control the displaying of all LED PWM related items.  The items it controls are:

DP & AP displaying on main screen
LED PWM Setup screen

So, if you do not use LED PWM's at all and do not wish to display anything related to it, comment out
the next line and all that stuff will be removed.

Approximately 720 bytes to have this feature
*/
#define DisplayLEDPWM

/*
PWM Expansion Device

This next line will allow the enabling of the PWM Expansion Device.  It will add a menu entry that displays
"PWM ->".  It will give the preset defaults for the expansion device.
The menu entries will be:

Slow Cloud
Fast Cloud
T-storm 1
T-storm 2
0%
50%
100%
Custom

Approximately 630 bytes to have this feature
*/
#define PWMEXPANSION

/*
This next line will allow you to use the ATO switches independently from each other.  You can specify
separate ports to control, separate timeouts and separate hourly intervals.
If you have this defined, you cannot use both switches together like originally intended.
The setup screens will show configurations for both switches.

Approximately 454 bytes to have this feature
*/
#define SingleATOSetup

/*
If this next line is uncommented, you must include the following lines in
the PDE file at the top above all other include statements:
#include <ReefAngel_Colors.h>
#include <ReefAngel_CustomColors.h>
*/
#define COLORS_PDE

/*
This will enable code for multiple expansion modules.  If you have expasion modules this must
be enabled to make use of them.

If you have more than 1 expansion module, you will need to uncomment and adjust the number of
expansion modules.  If it's left commented out, it is assumed to have 1 expansion module.  This
is only necessary when 2 or more modules are enabled so the web banner data gets sent
appropriately and possibly other enhancments in the future (in an attempt to keep code size
down and make the controller more customized for the user)

Approximately 530 bytes to have RelayExp
Approximately 94, 176 or 270 additional bytes needed when increasing InstalledRelayExpansionModules
*/
//#define RelayExp
//#define InstalledRelayExpansionModules	1

/*
This will enable the ability for people to create a custom main screen with the complete menu system
and without having to modify the libraries.
Once this is defined/enabled, you MUST create the following functions inside your PDE file.  If you do
not, then you will receive errors about missing functions.  If you create them and leave them blank,
you will have a blank main screen.  :)

void DrawCustomMain()
{
}
void DrawCustomGraph()
{
}

Just copy and paste the framework of the functions above into your PDE file.

If you do not want a graph to be displayed, you can leave it blank/empty.
*/
#define CUSTOM_MAIN


/*
This is for the simplified menu.  All the additional / extra menus have been stripped down.
The bare basics are only left for operating on the controller.  This is useful for those who want
all the extra memory they can get on the controller.  Also for those who hardcode their values or
use the Client Suite (or other app or SetInternalMemory PDE) to update their memory values.

The menu system only contains:
	- Feeding Mode
	- Water Change Mode
	- ATO Clear
	- Overheat Clear
	- PH Calibration
	- Date / Time Setup (ability to be removed)
	- Version Menu (ability to be removed)

When this is enabled, the following other features are ignored, you can still use the functions,
you just won't have a setup menu for them:
	- StandardLightsSetup
	- MetalHalideSetup
	- ATOSetup
	- DosingPumpSetup
	- DosingPumpIntervalSetup
	- WavemakerSetup
	- SingleATOSetup

The following features are available for use with this feature:
	- DisplayLEDPWM / RemoveAllLights - shows or hides the LED PWM values on the main screen, no setup menu
	- wifi
	- SaveRelayState
	- DirectTempSensor
	- VersionMenu
	- DateTimeSetup
	- DisplayImages
	- CUSTOM_MAIN
	- COLORS_PDE
	- RelayExp
	- InstalledRelayExpansionModules

The following features partially work.  There is no setup menu for them, BUT if you want to use
the Internal Memory values for them you must enable the features:
	- SetupExtras (Feeding Timer and LCD Timeout Timer)
	- OverheatSetup (Change the overheat temperature)

Approximately 4566 bytes removed when using the Simplified Menu
*/
#define SIMPLE_MENU

/*
This option allows for the user to handle the menu in their PDE file

This option OVERRIDES SIMPLE_MENU

To use, you must enable CUSTOM_MENU AND define how many entries you want in your menu.
To define how many entries, you must change the number after CUSTOM_MENU_ENTRIES below.

When you enable this, you have to add several items to your PDE file.  You will be creating your menu
at the top of the file just like in the Standard Libraries.  You will also need to create the menu entry
functions associated with all of your menu entries.

To create the menu entries, use this code:

#include <avr/pgmspace.h>
prog_char menu0_label[] PROGMEM = "Item 1";
prog_char menu1_label[] PROGMEM = "Item 2";
prog_char menu2_label[] PROGMEM = "Item 3";
prog_char menu3_label[] PROGMEM = "Item 4";
prog_char menu4_label[] PROGMEM = "Item 5";
prog_char menu5_label[] PROGMEM = "Item 6";
prog_char menu6_label[] PROGMEM = "Item 7";
prog_char menu7_label[] PROGMEM = "Item 8";
prog_char menu8_label[] PROGMEM = "Item 9";
PROGMEM const char *menu_items[] = {
menu0_label, menu1_label, menu2_label,
menu3_label, menu4_label, menu5_label,
menu6_label, menu7_label, menu8_label
};

Only include the number of menu items that you are going to use.  If you add more than needed, it can cause problems.
So just copy and paste, remove the labels (starting from 8 and going down) until you get the total number you need.
Then change the string to be something you want.  There is a max of 20 characters per item.

Next, we must create the menu entry functions for use.  Use this code:

void MenuEntry1()
{
  ReefAngel.DisplayMenuEntry("Item 1");
}
void MenuEntry2()
{
  ReefAngel.DisplayMenuEntry("Item 2");
}
void MenuEntry3()
{
  ReefAngel.DisplayMenuEntry("Item 3");
}
void MenuEntry4()
{
  ReefAngel.DisplayMenuEntry("Item 4");
}
void MenuEntry5()
{
  ReefAngel.DisplayMenuEntry("Item 5");
}
void MenuEntry6()
{
  ReefAngel.DisplayMenuEntry("Item 6");
}
void MenuEntry7()
{
  ReefAngel.DisplayMenuEntry("Item 7");
}
void MenuEntry8()
{
  ReefAngel.DisplayMenuEntry("Item 8");
}
void MenuEntry9()
{
  ReefAngel.DisplayMenuEntry("Item 9");
}

Remove the functions that you do not need.  The above code just displays the text in the string on the display.
You can remove that line and put whatever you want in the function.
*/
#define CUSTOM_MENU
// max of 9 entries
#define CUSTOM_MENU_ENTRIES		6


/*
Alternate Fonts

To use the alternate fonts, you must uncomment the appropriate define statement to make the font available.
Once the font is available, it is not automatically used everywhere.  It's not used with the default main screen
and default menus.  You must enable the CUSTOM_MAIN so you can make use of the font when drawing your own main
screen.

There are full fonts for the sizes:  5x8 (built in), 8x8, 8x16, 12x16
There are numbers only for sizes:  8x8, 8x16, 12x16, 16x16

How to use:

Font 8x8:
ReefAngel.NokiaLCD.DrawLargeText(..., Font8x8);

Numbers 8x8:
ReefAngel.NokiaLCD.DrawLargeText(..., Num8x8);

Font 8x16:
ReefAngel.NokiaLCD.DrawLargeText(..., Font8x16);

Numbers 8x16:
ReefAngel.NokiaLCD.DrawLargeText(..., Num8x16);

Font 12x16:
ReefAngel.NokiaLCD.DrawHugeText(..., Font12x16);

Numbers 12x16:
ReefAngel.NokiaLCD.DrawHugeText(..., Num12x16);

Numbers 16x16:
ReefAngel.NokiaLCD.DrawHugeNumbers(...);

The ... means that the default parameters for the functions should be used.  It is just a place holder.
The DrawHugeNumbers needs to have the numbers drawn in a string, so it cannot take numeric values,
they must be converted prior to calling the function.

Enabling fonts requires more memory usage.  The more fonts you enable, the more memory is used.  It is advisable
to only enable the fonts that you plan on using.  Try to not use several at once to keep memory usage down.

ENABLE AT YOUR OWN RISK
*/
#define FONT_8x8
//#define FONT_8x16
//#define FONT_12x16
#define NUMBERS_8x8
//#define NUMBERS_8x16
//#define NUMBERS_12x16
//#define NUMBERS_16x16

/*
Watchdog Timers

With the optiboot bootloader, enabling the watchdog timer is possible.  You must have version 4 of the
optiboot booloader installed on the controller for this to work.  Once enabled, the watchdog timer must
be reset/tickled/touched periodically or else the controller will reboot.

If enabled, there is a check on controller initialization that verifies the proper bootloader is installed
before enabling the watchdog timer.
This option is called WDT.  This option also overrides WDT_FORCE (mentioned below).

There is also another option for people who have the optiboot bootloader prior to version 4.  There is only
a couple people who fall into this category.  This option should not be enabled unless instructed to do so.
Enabling this option otherwise could potentially cause problems with your controller.
This option is called WDT_FORCE.

Approximately 82 bytes to have Watchdog Enabled (WDT).
*/
// Watchdog Timer
//#define WDT
// Force Watchdog Timer - DO NOT ENABLE UNLESS TOLD TO DO SO
//#define WDT_FORCE

/*
Enable Exceeded flags

The red LED on the controller gets turned on when either the Overheat temp is reached OR if the ATO timeout is exceeded.
You do not know which event is the the one that caused the LED to turn on.  When you clear either event the LED is turned
off and you do not the other event was triggered.

Enabling this feature will keep track of which event caused the LED to be turned on.  The event gets stored in the internal
memory.  To know what event triggered the LED, you have to add in a display on your CUSTOM_MAIN screen to perform a check
or query the controller in the specified memory spaces to check.  When you clear the event (either ATO or Overheat), the event
is cleared from memory.  The time the event occurs is not logged only that the event did occur.

The memory locations are as follows:

ATO_Exceed_Flag
ATO_Single_Exceed_Flag
Overheat_Exceed_Flag

The ATO Clear will clear both the Standard ATO and Single ATO events.  Currently, there is no distinction between ATO High and
Low events.  This may be added in the future though.

This should only be enabled if you are running a custom main screen because you have to code it manually to display.

Approximately 68 bytes to enable the storing of the exceeded flags (not counting displaying them on the screen)
*/
//#define ENABLE_EXCEED_FLAGS

/*
ATO Event Logging

Enabling will log the ATO events to the internal memory for later querying from another application
such as Client Suite.  This will help catch the ATO events that occur between the logging intervals.
Only useful to enable if you have WIFI enabled as well.

Request ato events with /sa
Events logged with a max of 4 events (4 low / 4 high)
	Standard ATO events are logged with the atolow
	SingleATO events are logged either high or low
Logs are cleared when request for log information is sent

Structure of XML data being sent to requestor is:
	<AL#ON>VALUE</AL#ON>
	<AL#OFF>VALUE</AL#OFF>
	<AH#ON>VALUE</AH#ON>
    <AH#OFF>VALUE</AH#OFF>
Where # is the event number from 0 - 3
VALUE is the number of seconds since the epoc (jan 1, 1970)
*/
//#define ENABLE_ATO_LOGGING

/*
Special prototype PLUS board with built-in WIFI module.  Only 1 exists and Curt Binder has it.
Do NOT enable.  Code will not compile with this enabled.
*/
//#define __PLUS_SPECIAL_WIFI__

/*
Salinity Module

Uncomment this next line if you have a Salinity Module that you would like to use.  You will want to
use a custom main screen as well to display the Salinity values for you.  You must also put this line
in your PDE file above the ReefAngel.h include line:

#include <ReefAngel_Salinity.h>  // <-- Add this line
#include <ReefAngel.h>  // <-- Add above this line

If you do not, you will get an error compiling.

Approximately 208 bytes to enable this feature
*/
#define SALINITYEXPANSION

/*
RF Module

This module is used to control Vortech pumps.  Enable this feature if you want that control.
You will have to add in this line above the ReefAngel.h include:

#include <ReefAngel_RF.h>  // <-- add this line
#include <ReefAngel.h>  // add above this line

Approximately 346 bytes to enable this feature
*/
#define RFEXPANSION

/*
AI LED

This features the ability to control the AI LED's.
You will have to add in this line above the ReefAngel.h include:

#include <ReefAngel_AI.h>  // <-- add this line
#include <ReefAngel.h>  // add above this line

Approximately 382 bytes to enable this feature
*/
//#define AI_LED

#endif  // __REEFANGEL_FEATURES_H__
rufessor
Posts: 291
Joined: Tue Oct 25, 2011 7:39 am

Re: Progressive sunphase

Post by rufessor »

Happy to report its up and running very successfully. I made a few minor changes here and there and now that I have watched most of a sunset cycle I will tune it a little bit more but its working fantastically. Thanks for all your work. I will be using my UVs as Moon lights since they dim to 1%!! So working on coding that now. Probably will grab you moon phase calculation from the complete feature hog PDE you have up.... if I can neuter it cleanly.
Thanks again
Post Reply