Cloud and Lightning Code for Dimming Expansion

Do you have a question on how to do something.
Ask in here.
User avatar
Posts: 1426
Joined: Fri Mar 29, 2013 3:51 pm
Location: Oklahoma City
PostPosted: Sun Aug 14, 2016 6:50 am
Try changing this line:

Code: Select all
      DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,DaylightPWMValue/40.95,0,180)*40.95;


Change the 0 to whatever value you want for the bottom.

Posts: 81
Joined: Mon Aug 11, 2014 6:45 am
Location: Clermont, Florida
PostPosted: Thu Oct 20, 2016 3:26 pm
I successfully made adjustments to the "Mega" storm mode, and have it broken out across all 3 lights. These are 3 black boxes (SB Reef Lights) on the 6 channel dimming expansion. Let me know what you think. I still have stuff I want to do, but am making good progress. :P

Yes, I know the music sucks, but needed to have something.

https://www.youtube.com/watch?v=Bp5k5U6q4hc
Image

Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm
PostPosted: Sun Dec 18, 2016 8:08 pm
Is there a spot for minimum and maximum dimming of cloud and storm i have a issue with my relays tied to my dimming and my fuge on opposite on,off of day lights so i need to make the cloud and flash need to not go below 2% or above 90%

Storm is at the bottom

Code: Select all
include <ReefAngel_Features.h>
    #include <Globals.h>
    #include <RA_Wifi.h>
    #include <Wire.h>
    #include <OneWire.h>
    #include <Time.h>
    #include <DS1307RTC.h>
    #include <InternalEEPROM.h>
    #include <RA_NokiaLCD.h>
    #include <RA_ATO.h>
    #include <RA_Joystick.h>
    #include <LED.h>
    #include <RA_TempSensor.h>
    #include <Relay.h>
    #include <RA_PWM.h>
    #include <Timer.h>
    #include <Memory.h>
    #include <RA_Colors.h>
    #include <RA_CustomColors.h>
    #include <ReefAngel.h>
    #include <SunLocation.h>
    #include <Tide.h>
    #include <Moon.h>
    #include <WiFiAlert.h>
    #include <DCPump.h>
   
   
        // Won't compile without this...
        // ReefAngel.DCPump.UseMemory=true;
        // Custom menus
        #include <avr/pgmspace.h>
        const char menu1_label[] PROGMEM = "Feeding Mode";
        const char menu2_label[] PROGMEM = "Water Change";
        const char menu3_label[] PROGMEM = "ATO Clear";
        const char menu4_label[] PROGMEM = "DC Pump Mode";
        const char menu5_label[] PROGMEM = "Overheat Clear";
        const char menu6_label[] PROGMEM = "PH Calibration";
        const char menu7_label[] PROGMEM = "Date / Time";
        const char menu8_label[] PROGMEM = "Refugium Light";

        // Group the menu entries together
        PROGMEM const char * const menu_items[] = {
        menu1_label, menu2_label, menu3_label,
        menu4_label, menu5_label, menu6_label, menu7_label, menu8_label
        };

        // Define Custom Memory Locations
        #define Mem_B_MoonOffset          100
        #define Mem_B_AtoHourInterval     101
        #define Mem_I_Latitude            108
        #define Mem_I_Longitude           110
        #define Mem_B_AcclRiseOffset      112
        #define Mem_B_AcclSetOffset       113
        #define Mem_B_AcclDay             114
        #define Mem_B_TideMin             117
        #define Mem_B_TideMax             118
        #define Mem_B_PumpOffset          119
        #define Mem_B_FeedingDCPump       120
        #define Mem_B_NightDCPump         121
        #define Mem_B_NightSpeed          122
        #define Mem_B_NightDuration       123
        #define Mem_B_NTMSpeed            124
        #define Mem_B_NTMDuration         125
        #define Mem_B_NTMDelay            126
        #define Mem_B_NTMTime             127
        #define Mem_B_TideMode            143
        #define Mem_B_CloudsEveryXDays    149
        #define Mem_B_CloudChancePerDay   150
        #define Mem_B_MinCloudDuration    151
        #define Mem_B_MaxCloudDuration    152
        #define Mem_B_MinCloudsPerDay     153
        #define Mem_B_MaxCloudsPerDay     154
        #define Mem_B_StartCloudAfterHour 155
        #define Mem_B_StartCloudAfterMin  156
        #define Mem_B_EndCloudBeforeHour  157
        #define Mem_B_EndCloudBeforeMin   158
        #define Mem_B_LightningChance     159
        #define Mem_B_LightMode           160
        #define Mem_B_LightOffset         161
        #define Mem_I_RiseOffset          162
        #define Mem_I_SetOffset           164
        #define Mem_B_AcclActinicOffset   166
        #define Mem_B_AcclDaylightOffset  167
        #define Mem_B_RandomMode          168
        #define Mem_B_GyreOffset          169
        #define Mem_B_MoonMode            170
        #define Mem_B_LightsOffPerc       171
        #define Mem_B_FeedingSpeed        172
        #define Mem_B_WCSpeed             173
        #define Mem_B_EnableStorm         178
        #define Mem_B_ForceRandomTide     179
        #define Mem_B_ResetMemory         199

        void init_memory() {
          // Initialize Custom Memory Locations
         
         
          InternalMemory.write(Mem_B_MoonOffset,15);           //mb100
          InternalMemory.write(Mem_B_AtoHourInterval,1);       //mb101
          InternalMemory.write_int(Mem_I_Latitude,-21);        //mi108
          InternalMemory.write_int(Mem_I_Longitude,-147);      //mi110
          InternalMemory.write(Mem_B_AcclRiseOffset,1);        //mb112
          InternalMemory.write(Mem_B_AcclSetOffset,1);         //mb113
          InternalMemory.write(Mem_B_AcclDay,0);               //mb114
          InternalMemory.write(Mem_B_TideMin,10);              //mb117
          InternalMemory.write(Mem_B_TideMax,20);              //mb118
          InternalMemory.write(Mem_B_PumpOffset,80);           //mb119
          InternalMemory.write(Mem_B_FeedingDCPump,true);      //mb120
          InternalMemory.write(Mem_B_NightDCPump,false);       //mb121
          InternalMemory.write(Mem_B_NightSpeed,35);           //mb122
          InternalMemory.write(Mem_B_NightDuration,16);        //mb123
          InternalMemory.write(Mem_B_NTMSpeed,100);            //mb124
          InternalMemory.write(Mem_B_NTMDuration,50);          //mb125
          InternalMemory.write(Mem_B_NTMDelay,0);              //mb126
          InternalMemory.write(Mem_B_NTMTime,5);               //mb127
          InternalMemory.write(Mem_B_TideMode,0);              //mb143
          InternalMemory.write(Mem_B_CloudsEveryXDays,1);      //mb149
          InternalMemory.write(Mem_B_CloudChancePerDay,40);    //mb150
          InternalMemory.write(Mem_B_MinCloudDuration,5);      //mb151
          InternalMemory.write(Mem_B_MaxCloudDuration,10);     //mb152
          InternalMemory.write(Mem_B_MinCloudsPerDay,2);       //mb153
          InternalMemory.write(Mem_B_MaxCloudsPerDay,20);      //mb154
          InternalMemory.write(Mem_B_StartCloudAfterHour,12);  //mb155
          InternalMemory.write(Mem_B_StartCloudAfterMin,1);    //mb156
          InternalMemory.write(Mem_B_EndCloudBeforeHour,19);   //mb157
          InternalMemory.write(Mem_B_EndCloudBeforeMin,1);     //mb158
          InternalMemory.write(Mem_B_LightningChance,25);      //mb159
          InternalMemory.write(Mem_B_LightMode,1);             //mb160
          InternalMemory.write(Mem_B_LightOffset,10);          //mb161
          InternalMemory.write_int(Mem_I_RiseOffset,20);       //mi162
          InternalMemory.write_int(Mem_I_SetOffset,16);        //mi164
          InternalMemory.write(Mem_B_AcclActinicOffset,214);   //mb166
          InternalMemory.write(Mem_B_AcclDaylightOffset,214);  //mb167
          InternalMemory.write(Mem_B_RandomMode,true);         //mb168
          InternalMemory.write(Mem_B_GyreOffset,10);           //mb169
          InternalMemory.write(Mem_B_MoonMode,1);              //mb170
          InternalMemory.write(Mem_B_LightsOffPerc,1);         //mb171
          InternalMemory.write(Mem_B_FeedingSpeed,0);          //mb172
          InternalMemory.write(Mem_B_WCSpeed,0);               //mb173

          InternalMemory.write(Mem_B_ResetMemory,false);       //mb199
        }
 

        #define NUMBERS_8x16

        #define Var_Tide         4
        #define Var_TideMode     5
       
        // Define Relay Ports by Name
        #define Return             1
        #define Heater             2
        #define Refugium           3
        #define MediaPump          4
        #define WhiteLeft          5
        #define BlueLeft           6
        #define Autotopoff         7
        #define Skimmer            8

        ////// Place global variable code below here

        // Custom classes
        SunLocation sun;
        Tide tide;

        // Jebao Variables
        byte DCPumpMode, DCPumpSpeed, DCPumpDuration;

        // For Cloud and preset code
        int DaylightPWMValue=0;
        int ActinicPWMValue=0;
        int DaylightPWMValue0=0;        // For cloud code, channel 0
        int DaylightPWMValue2=0;        // For cloud code, chennel 2
        int ActinicPWMValue1=0;        // For cloud code, channel 0
        int ActinicPWMValue3=0;        // For cloud code, chennel 2

        // Needs to be global for DrawCustomGraph()
        int ScreenID=1;
        ////// Place global variable code above here

        // Setup on controller startup/reset
        void setup()
        {
          // This must be the first line
          ReefAngel.Init();  //Initialize controller
          for (int a=0;a<SIZE(menu_items);a++)
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[a])),a); // Initialize Menu
         
          // Ports toggled in Feeding Mode
          ReefAngel.FeedingModePorts = Port1Bit | Port2Bit ;
          // Ports toggled in Water Change Mode
          ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port4Bit | Port7Bit  | Port8Bit;
          // Ports toggled when Lights On / Off menu entry selected
          ReefAngel.LightsOnPorts = Port3Bit | Port5Bit | Port6Bit;
          // Ports turned off when Overheat temperature exceeded
          ReefAngel.OverheatShutoffPorts =  Port2Bit | Port4Bit;
          // Use T1 probe as temperature and overheat functions
          ReefAngel.TempProbe = T1_PROBE;
          ReefAngel.OverheatProbe = T1_PROBE;
         
          // Feeeding and Water Change mode speed
         
           // Ports that are always on
        ReefAngel.Relay.On( Return ); // Return Pump
         
           
          ////// Place additional initialization code below here
       ReefAngel.DCPump.UseMemory=false;
       randomSeed(now()/SECS_PER_DAY);
           
          if (InternalMemory.read(Mem_B_ResetMemory))
            init_memory();
         ////// Place additional initialization code above here
        }

        void loop()
        {
        DelayedOnFeedMode(Return); // DelayedOn after feed mode change only
        ReefAngel.Relay.Set(Refugium,!ReefAngel.Relay.Status(WhiteLeft));
        ReefAngel.SingleATO(true,Autotopoff, InternalMemory.ATOExtendedTimeout_read(), InternalMemory.read(Mem_B_AtoHourInterval));
        ReefAngel.Relay.Set(Skimmer, ReefAngel.HighATO.IsActive());
        ReefAngel.DCPump.ExpansionChannel[4] = AntiSync; // Left Jebao RW4
        ReefAngel.DCPump.ExpansionChannel[5] = Sync; // Right jebao rw4
        ReefAngel.StandardHeater(Heater);
       
         
          ////// Place your custom code below here
         
         // Added New features
          SetSun();               // Setup Sun rise/set lighting
          AcclimateLED();         // Apply acclimation dimming
          SetMoon();              // Setup Moon rise/set lighting
          FillInMoon();           // Fill in 5% to 0% gap in main LEDs
          LEDPresets();           // Set preset light levels
          CheckCloud();           // Check for cloud and lightning.
          UpdateLED();            // Set Lights on and off in sync with dimming
          SetTide();              // Set High/Low tide properties
          SetDCPump();            // Set DCPump modes     
   

         
         
          ////// Place your custom code above here

          // This should always be the last line
           ReefAngel.Portal( "addyourown" );
        ReefAngel.DDNS( "1" ); // Your DDNS is addyourown
        ReefAngel.ShowInterface();
        }


       
        void SetSun() {
          // Start acclimation routine
          int acclRiseOffset=InternalMemory.read(Mem_B_AcclRiseOffset)*60;
          int acclSetOffset=InternalMemory.read(Mem_B_AcclSetOffset)*60;
          byte acclDay=InternalMemory.read(Mem_B_AcclDay);
         
          // See if we are acclimating corals and decrease the countdown each day
          static boolean acclCounterReady=false;
          if (now()%SECS_PER_DAY!=0) acclCounterReady=true;
          if (now()%SECS_PER_DAY==0 && acclCounterReady && acclDay>0) {
            acclDay--;
            acclCounterReady=false;
            InternalMemory.write(Mem_B_AcclDay,acclDay);
          }
          // End acclimation

          // Add some customizable offsets
          sun.Init(InternalMemory.read_int(Mem_I_Latitude), InternalMemory.read_int(Mem_I_Longitude));
          int riseOffset=InternalMemory.read_int(Mem_I_RiseOffset);
          int setOffset=InternalMemory.read_int(Mem_I_SetOffset);
         
          sun.SetOffset(riseOffset,(acclDay*acclRiseOffset),setOffset,(-acclDay*acclSetOffset)); // Bahamas
          sun.CheckAndUpdate(); // Calculate today's Sunrise / Sunset

          byte lightOffset=InternalMemory.read(Mem_B_LightOffset); // left right separation
          byte actinicOffset=InternalMemory.ActinicOffset_read();
         
          // Make sure light resets to zero at night.
          for(int i=0;i<4;i++) { ReefAngel.PWM.SetChannel(i,0); }
         
          switch(InternalMemory.read(Mem_B_LightMode)) {   
            case 0: {
              // Daylights
              ReefAngel.PWM.Channel0PWMSlope(lightOffset,0);
              ReefAngel.PWM.Channel2PWMSlope(0,lightOffset);
              // Actinics
              ReefAngel.PWM.Channel1PWMSlope(actinicOffset+lightOffset,actinicOffset);
              ReefAngel.PWM.Channel3PWMSlope(actinicOffset,actinicOffset+lightOffset);
              break;
            }
            case 1: {
              // Daylights
              ReefAngel.PWM.Channel0PWMParabola(lightOffset,0);
              ReefAngel.PWM.Channel2PWMParabola(0,lightOffset);
              // Actinics
              ReefAngel.PWM.Channel1PWMParabola(actinicOffset+lightOffset,actinicOffset);
              ReefAngel.PWM.Channel3PWMParabola(actinicOffset,actinicOffset+lightOffset);
              break;
            }
          case 2: {
              // Daylights
              ReefAngel.PWM.Channel0PWMSmoothRamp(lightOffset,0);
              ReefAngel.PWM.Channel2PWMSmoothRamp(0,lightOffset);
              // Actinics
              ReefAngel.PWM.Channel1PWMSmoothRamp(actinicOffset+lightOffset,actinicOffset);
              ReefAngel.PWM.Channel3PWMSmoothRamp(actinicOffset,actinicOffset+lightOffset);
              break;
            }
          case 3: {
              // Daylights
              ReefAngel.PWM.Channel0PWMSigmoid(lightOffset,0);
              ReefAngel.PWM.Channel2PWMSigmoid(0,lightOffset);
              // Actinics
              ReefAngel.PWM.Channel1PWMSigmoid(actinicOffset+lightOffset,actinicOffset);
              ReefAngel.PWM.Channel3PWMSigmoid(actinicOffset,actinicOffset+lightOffset);
              break;
            }
          case 4: { // Reverse the actinics in the morning
          // Daylights
          ReefAngel.PWM.Channel0PWMParabola(lightOffset+actinicOffset,0);
          ReefAngel.PWM.Channel2PWMParabola(actinicOffset,lightOffset);
          // Actinics
          ReefAngel.PWM.Channel1PWMParabola(lightOffset,actinicOffset);
          ReefAngel.PWM.Channel3PWMParabola(0,actinicOffset+lightOffset);
          break;
        }
      }
    }

    void SetMoon() {
      byte offset=InternalMemory.read(Mem_B_MoonOffset);
     
      byte startD=InternalMemory.read(Mem_B_PWMSlopeStartD);
      byte endD=InternalMemory.read(Mem_B_PWMSlopeEndD);
      byte timeD=InternalMemory.read(Mem_B_PWMSlopeDurationD);

      byte startA=InternalMemory.read(Mem_B_PWMSlopeStartA);
      byte endA=InternalMemory.read(Mem_B_PWMSlopeEndA);
      byte timeA=InternalMemory.read(Mem_B_PWMSlopeDurationA);

      time_t onTime=ScheduleTime(Moon.riseH, Moon.riseM,0);
      time_t offTime=ScheduleTime(Moon.setH, Moon.setM,0);
      time_t offsetOnTime=ScheduleTime(Moon.riseH, Moon.riseM,0)-(offset*60);
      time_t offsetOffTime=ScheduleTime(Moon.setH, Moon.setM,0)-(offset*60);

      byte actRiseH=(offsetOnTime%SECS_PER_DAY)/SECS_PER_HOUR;
      byte actRiseM=((offsetOnTime%SECS_PER_DAY)%SECS_PER_HOUR)/60;
      byte actSetH=(offsetOffTime%SECS_PER_DAY)/SECS_PER_HOUR;
      byte actSetM=((offsetOffTime%SECS_PER_DAY)%SECS_PER_HOUR)/60;
     
      static byte mp=MoonPhase();
     
      if (mp!=MoonPhase()) {
        InternalMemory.write(Mem_B_PWMSlopeEndD,mp);
        InternalMemory.write(Mem_B_PWMSlopeEndA,mp);
        mp=MoonPhase();
      }
     
      moon_init(InternalMemory.read_int(Mem_I_Latitude), InternalMemory.read_int(Mem_I_Longitude));
     
      // Make sure light resets to zero at night.
      ReefAngel.PWM.SetDaylight(0);
      ReefAngel.PWM.SetActinic(0);
     
      switch(InternalMemory.read(Mem_B_MoonMode)) {   
        case 0: {
          // Daylights
          ReefAngel.PWM.SetDaylightRaw(PWMSlopeHighRes(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM,startA,endA,timeA,0));
          ReefAngel.PWM.SetActinicRaw(PWMSlopeHighRes(actRiseH,actRiseM,actSetH,actSetM,startD,endD,timeD,0));
          break;
        }
        case 1: {
          ReefAngel.PWM.SetDaylightRaw(PWMParabolaHighRes(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM, startA,endA,0));
          ReefAngel.PWM.SetActinicRaw(PWMParabolaHighRes(actRiseH,actRiseM,actSetH,actSetM, startD,endD,0));
          break;
        }
      case 2: {
          ReefAngel.PWM.SetDaylightRaw(PWMSmoothRampHighRes(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM,startA,endA,timeA,0));
          ReefAngel.PWM.SetActinicRaw(PWMSmoothRampHighRes(actRiseH,actRiseM,actSetH,actSetM,startD,endD,timeD,0));
          break;
        }
      case 3: {
          ReefAngel.PWM.SetDaylightRaw(PWMSigmoidHighRes(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM,startA,endA,0));
          ReefAngel.PWM.SetActinicRaw(PWMSigmoidHighRes(actRiseH,actRiseM,actSetH,actSetM,startD,endD,0));
          break;
        }
     
      }
    }

    void FillInMoon() {
      // Extend the sunrise/sunset to fill in gaps when fixtures shut off.
      byte actinicOffset=InternalMemory.ActinicOffset_read();
      byte lightOffset=InternalMemory.read(Mem_B_LightOffset); // left right separation
      int LightsOffPerc=40.95*InternalMemory.read(Mem_B_LightsOffPerc);
      int onTime=NumMins(InternalMemory.StdLightsOnHour_read(),InternalMemory.StdLightsOnMinute_read())-(actinicOffset+(2*lightOffset));
      int offTime=NumMins(InternalMemory.StdLightsOffHour_read(),InternalMemory.StdLightsOffMinute_read())+(actinicOffset+(2*lightOffset));

      int moonVal=ReefAngel.PWM.GetDaylightValueRaw();
      int channelVal=PWMSlopeHighRes(onTime/60,onTime%60,offTime/60,offTime%60,0,100,lightOffset,0);

      if (ReefAngel.PWM.GetChannelValueRaw(1)<=LightsOffPerc && channelVal>ReefAngel.PWM.GetDaylightValueRaw())
        ReefAngel.PWM.SetDaylightRaw(channelVal);
      if (ReefAngel.PWM.GetChannelValueRaw(3)<=LightsOffPerc && channelVal>ReefAngel.PWM.GetActinicValueRaw())
        ReefAngel.PWM.SetActinicRaw(channelVal);
       
      DaylightPWMValue=ReefAngel.PWM.GetDaylightValueRaw();
      ActinicPWMValue=ReefAngel.PWM.GetActinicValueRaw();
    }

    void AcclimateLED() {
          byte acclDay=InternalMemory.read(Mem_B_AcclDay);
         
          if (acclDay > 0) {
            float acclActinicOffset=acclDay*(40.95*(((float)InternalMemory.read(Mem_B_AcclActinicOffset)/100)));
            float acclDaylightOffset=acclDay*(40.95*((float)InternalMemory.read(Mem_B_AcclDaylightOffset)/100));
            float endPerc;
         
            endPerc=40.95*InternalMemory.PWMSlopeEnd1_read();
            ReefAngel.PWM.SetChannelRaw(1,map(ReefAngel.PWM.GetChannelValueRaw(1),0,endPerc,0,endPerc-acclActinicOffset));
            endPerc=40.95*InternalMemory.PWMSlopeEnd3_read();
            ReefAngel.PWM.SetChannelRaw(3,map(ReefAngel.PWM.GetChannelValueRaw(3),0,endPerc,0,endPerc-acclActinicOffset));
            endPerc=40.95*InternalMemory.PWMSlopeEnd0_read();
            ReefAngel.PWM.SetChannelRaw(0,map(ReefAngel.PWM.GetChannelValueRaw(0),0,endPerc,0,endPerc-acclDaylightOffset));
            endPerc=40.95*InternalMemory.PWMSlopeEnd2_read();
            ReefAngel.PWM.SetChannelRaw(2,map(ReefAngel.PWM.GetChannelValueRaw(2),0,endPerc,0,endPerc-acclDaylightOffset));
          }
        }

#define LED_1to1      Box2_Port1
#define LED_2to1      Box2_Port2
#define LED_3to1      Box2_Port3
#define LED_BLUE      Box2_Port4
#define LED_WHITE     Box2_Port5
#define LED_MOON      Box2_Port6
#define LED_STORM     Box2_Port7
#define TRIGGER_STORM Box2_Port8

        void resetRelayBox(byte ID) {
  // toggle all relays except for the one selected
  for (int i=Box2_Port1;i<=Box2_Port4;i++) {
    if (i!=ID) ReefAngel.Relay.Auto(i);
  }
}

void LEDPresets() {
  static byte lastPreset=0;
 
  DaylightPWMValue0=ReefAngel.PWM.GetChannelValueRaw(0);
  ActinicPWMValue1=ReefAngel.PWM.GetChannelValueRaw(1);
  DaylightPWMValue2=ReefAngel.PWM.GetChannelValueRaw(2);
  ActinicPWMValue3=ReefAngel.PWM.GetChannelValueRaw(3);
  DaylightPWMValue=ReefAngel.PWM.GetDaylightValueRaw();
  ActinicPWMValue=ReefAngel.PWM.GetActinicValueRaw();

  if (ReefAngel.Relay.isMaskOn(LED_1to1)) {
    if (lastPreset!=1) resetRelayBox(LED_1to1);
    DaylightPWMValue0=90*40.95;
    ActinicPWMValue1=10*40.95;
    DaylightPWMValue2=90*40.95;
    ActinicPWMValue3=10*40.95;
    lastPreset=1;
  }
 
  if (ReefAngel.Relay.isMaskOff(LED_1to1)) {
    if (lastPreset!=2) resetRelayBox(LED_1to1);
    DaylightPWMValue0=10*40.95;
    ActinicPWMValue1=90*40.95;
    DaylightPWMValue2=10*40.95;
    ActinicPWMValue3=90*40.95;
    lastPreset=2;
  }
 
  if (ReefAngel.Relay.isMaskOn(LED_2to1)) {
    if (lastPreset!=3) resetRelayBox(LED_2to1);
    DaylightPWMValue0=60*40.95;
    ActinicPWMValue1=40*40.95;
    DaylightPWMValue2=60*40.95;
    ActinicPWMValue3=40*40.95;
    lastPreset=3;
  }

  if (ReefAngel.Relay.isMaskOff(LED_2to1)) {
    if (lastPreset!=4) resetRelayBox(LED_2to1);
    DaylightPWMValue0=40*40.95;
    ActinicPWMValue1=60*40.95;
    DaylightPWMValue2=40*40.95;
    ActinicPWMValue3=60*40.95;
    lastPreset=4;
  }

  if (ReefAngel.Relay.isMaskOn(LED_3to1)) {
    if (lastPreset!=5) resetRelayBox(LED_3to1);
    DaylightPWMValue0=75*40.95;
    ActinicPWMValue1=25*40.95;
    DaylightPWMValue2=75*40.95;
    ActinicPWMValue3=25*40.95;
    lastPreset=5;
  }

  if (ReefAngel.Relay.isMaskOff(LED_3to1)) {
    if (lastPreset!=6) resetRelayBox(LED_3to1);
    DaylightPWMValue0=25*40.95;
    ActinicPWMValue1=75*40.95;
    DaylightPWMValue2=25*40.95;
    ActinicPWMValue3=75*40.95;
    lastPreset=6;
  }


  if (ReefAngel.Relay.isMaskOn(LED_BLUE)) {
    if (lastPreset!=9) resetRelayBox(LED_BLUE);
    DaylightPWMValue0=0;
    ActinicPWMValue1=80*40.95;
    DaylightPWMValue2=0;
    ActinicPWMValue3=80*40.95;
    lastPreset=9;
  }

  if (ReefAngel.Relay.isMaskOff(LED_BLUE)) {
    if (lastPreset!=10) resetRelayBox(LED_BLUE);
    ActinicPWMValue1=0;
    ActinicPWMValue3=0;
    lastPreset=10;
  }   
 
  if (ReefAngel.Relay.isMaskOn(LED_WHITE)) {
    if (lastPreset!=11) resetRelayBox(LED_WHITE);
    DaylightPWMValue0=80*40.95;
    ActinicPWMValue1=0;
    DaylightPWMValue2=80*40.95;
    ActinicPWMValue3=0;
    lastPreset=11;
  }

  if (ReefAngel.Relay.isMaskOff(LED_WHITE)) {
    if (lastPreset!=12) resetRelayBox(LED_WHITE);
    DaylightPWMValue0=0;
    DaylightPWMValue2=0;
    lastPreset=12;
  }   
 
  if (ReefAngel.Relay.isMaskOn(LED_MOON)) {
    if (lastPreset!=13) resetRelayBox(LED_MOON);
    DaylightPWMValue=4095;
    ActinicPWMValue=4095;
    lastPreset=13;
  }

  if (ReefAngel.Relay.isMaskOff(LED_MOON)) {
    if (lastPreset!=14) resetRelayBox(LED_MOON);
    DaylightPWMValue=0;
    ActinicPWMValue=0;
    lastPreset=14;
  }
}

// Write updated values to the channels
void UpdateLED() {
  ReefAngel.PWM.SetChannelRaw(0,DaylightPWMValue0);
  ReefAngel.PWM.SetChannelRaw(1,ActinicPWMValue1);
  ReefAngel.PWM.SetChannelRaw(2,DaylightPWMValue2);
  ReefAngel.PWM.SetChannelRaw(3,ActinicPWMValue3);
  ReefAngel.PWM.SetDaylightRaw(DaylightPWMValue);   
  ReefAngel.PWM.SetActinicRaw(ActinicPWMValue);

  byte LightsOffPerc=40.95*InternalMemory.read(Mem_B_LightsOffPerc);
 
  if (ReefAngel.PWM.GetChannelValueRaw(0)>=LightsOffPerc) ReefAngel.Relay.On(WhiteLeft); else ReefAngel.Relay.Off(WhiteLeft);
  if (ReefAngel.PWM.GetChannelValueRaw(1)>=LightsOffPerc) ReefAngel.Relay.On(BlueLeft); else ReefAngel.Relay.Off(BlueLeft);
}


void SetTide() {
  byte nightSpeed=InternalMemory.read(Mem_B_NightSpeed);
  byte tideMin=InternalMemory.read(Mem_B_TideMin);
  byte tideMax=InternalMemory.read(Mem_B_TideMax);

  // Set tide offsets
  tide.SetOffset(tideMin, tideMax);     
  // Set tide speed. Slope in/out of Night Mode
  tide.SetSpeed(PWMSlope(sun.GetRiseHour()-1,sun.GetRiseMinute(),
    sun.GetSetHour(),sun.GetSetMinute(),nightSpeed+tideMin,DCPumpSpeed,120,nightSpeed+tideMin));

  // Show tide info on portal
  ReefAngel.CustomVar[Var_Tide]=tide.CalcTide();
}

void SetDCPump() {
  int ntmDelay=InternalMemory.read(Mem_B_NTMDelay)*60;
  int ntmTime=InternalMemory.read(Mem_B_NTMTime)*60;
  boolean nightDCPump=InternalMemory.read(Mem_B_NightDCPump);
  boolean feedingDCPump=InternalMemory.read(Mem_B_FeedingDCPump);
  static time_t t;

  ReefAngel.DCPump.FeedingSpeed=InternalMemory.read(Mem_B_FeedingSpeed);
  ReefAngel.DCPump.WaterChangeSpeed=InternalMemory.read(Mem_B_WCSpeed);
 
  DCPumpMode=InternalMemory.DCPumpMode_read();
  DCPumpSpeed=InternalMemory.DCPumpSpeed_read();
  DCPumpDuration=InternalMemory.DCPumpDuration_read();

  if ((now()-t > ntmDelay && now()-t < ntmTime+ntmDelay) && feedingDCPump) {
    // Post feeding mode
    DCPumpMode=Smart_NTM;
    DCPumpSpeed=InternalMemory.read(Mem_B_NTMSpeed);
    DCPumpDuration=InternalMemory.read(Mem_B_NTMDuration);
  } else if (!sun.IsDaytime() && nightDCPump) {
    DCPumpMode=Night;
    DCPumpSpeed=InternalMemory.read(Mem_B_NightSpeed);
    DCPumpDuration=InternalMemory.read(Mem_B_NightDuration);
  } else {
    if (DCPumpMode!=Night && ReefAngel.DCPump.Mode==Night)
      ReefAngel.DCPump.SetMode(Night_Stop,0,0);
  }

  if (ReefAngel.DisplayedMenu==FEEDING_MODE) {
    t=now(); // Run post feeding mode when this counter stops
  } else if (ReefAngel.DisplayedMenu==WATERCHANGE_MODE) {
    // Not needed anymore.
    // ReefAngel.DCPump.SetMode(Constant,25,0);
  } else {
    if ((DCPumpMode==Smart_NTM) || (DCPumpMode==ShortPulse)) DCPumpDuration=InternalMemory.read(Mem_B_NTMDuration);
    (DCPumpMode==Custom) ? DCPumpCustom() : ReefAngel.DCPump.SetMode(DCPumpMode,DCPumpSpeed,DCPumpDuration);
  }
}

void RefugiumLight() {if (ReefAngel.DisplayedMenu==WATERCHANGE_MODE) {
        ReefAngel.Relay.On(Refugium);} }

void DCPumpCustom() {
  static boolean changeMode;
  byte rcSpeed, rcSpeedAS;

  // Define new modes
  const int BHazard=15;
  const int RA_ReefCrest=16;
  const int RA_Lagoon=17;
  const int RA_TidalSwell=18;
  const int RA_Smart_NTM=19;
  const int RA_ShortPulse=20;
  const int RA_LongPulse=21;
 
  byte tideSpeed=tide.CalcTide();
  byte tideMin=InternalMemory.read(Mem_B_TideMin);
  byte tideMax=InternalMemory.read(Mem_B_TideMax);
  byte tideMode=InternalMemory.read(Mem_B_TideMode);
  float pumpOffset=(float) InternalMemory.read(Mem_B_PumpOffset)/100;

  byte RandomModes[]={ ReefCrest, TidalSwell, Lagoon, ShortPulse, LongPulse, BHazard, Else, Sine };

//  if (now()%SECS_PER_DAY!=0 && InternalMemory.read(Mem_B_RandomMode)) changeMode=true;
//  if (now()%SECS_PER_DAY==0 && changeMode) {

  if (now()%(6*SECS_PER_HOUR)!=10 && InternalMemory.read(Mem_B_RandomMode)) changeMode=true;
  if (now()%(6*SECS_PER_HOUR)==10 && changeMode) {
    tideMode=random(100)%sizeof(RandomModes);
    InternalMemory.write(Mem_B_TideMode,tideMode);
    changeMode=false;
  }
 
  // Choose another random mode if triggered
  if (InternalMemory.read(Mem_B_ForceRandomTide)) {
    tideMode=random(100)%sizeof(RandomModes);
    InternalMemory.write(Mem_B_TideMode,tideMode);
    InternalMemory.write(Mem_B_ForceRandomTide,false);
  }
 
  ReefAngel.CustomVar[Var_TideMode]=tideMode+1;

  switch (RandomModes[tideMode]) {
    case ReefCrest: {
      ReefAngel.DCPump.SetMode(ReefCrest,tideSpeed,DCPumpDuration);
      return;
      break;
    }
    case Lagoon: {
      ReefAngel.DCPump.SetMode(Lagoon,tideSpeed,DCPumpDuration);
      return;
      break;
    }
    case TidalSwell: {
      ReefAngel.DCPump.SetMode(TidalSwell,tideSpeed,DCPumpDuration);
      return;
      break;
    }
    case Smart_NTM: {
      ReefAngel.DCPump.SetMode(Smart_NTM,tideSpeed,DCPumpDuration);
      return;
      break;
    }
    case ShortPulse: {
      ReefAngel.DCPump.SetMode(ShortPulse,tideSpeed,DCPumpDuration);
      return;
      break;
    }
    case LongPulse: {
      ReefAngel.DCPump.SetMode(LongPulse,tideSpeed,DCPumpDuration);
      return;
      break;
    }
    case RA_ReefCrest: {
      rcSpeed=ReefCrestMode(tideSpeed,DCPumpDuration*2,true);
      rcSpeedAS=ReefCrestMode(tideSpeed,DCPumpDuration*2,false);
      break;
    }
    case RA_Lagoon: {
      rcSpeed=ReefCrestMode(tideSpeed,DCPumpDuration,true);
      rcSpeedAS=ReefCrestMode(tideSpeed,DCPumpDuration,false);
      break;
    }
    case RA_TidalSwell: {
      rcSpeed=TidalSwellMode(tideSpeed,true);
      rcSpeedAS=TidalSwellMode(tideSpeed,false);
      break;
    }
    case RA_Smart_NTM: {
      rcSpeed=NutrientTransportMode(0,tideSpeed,DCPumpDuration*50,true);
      rcSpeedAS=NutrientTransportMode(0,tideSpeed,DCPumpDuration*50,false);
      break;
    }
    case RA_ShortPulse: {
      rcSpeed=ShortPulseMode(0,tideSpeed,DCPumpDuration*50,true);
      rcSpeedAS=ShortPulseMode(0,tideSpeed,DCPumpDuration*50,false);
      break;
    }
    case RA_LongPulse: {
      rcSpeed=LongPulseMode(0,tideSpeed,DCPumpDuration,true);
      rcSpeedAS=LongPulseMode(0,tideSpeed,DCPumpDuration,false);
      break;
    }   
    case Else: {
      rcSpeed=ElseMode(tideSpeed,DCPumpDuration*2,true);
      rcSpeedAS=ElseMode(tideSpeed,DCPumpDuration*2,false);
      break;
    }   
    case BHazard: {
      rcSpeed=millis()%1200>800?tideSpeed:0;
      rcSpeedAS=millis()%1200<400?0:tideSpeed;
      break;
    }
    case Sine: {
      rcSpeed=SineMode(tideSpeed-tideMin,tideSpeed+tideMin,DCPumpDuration*100,true);
      rcSpeedAS=SineMode(tideSpeed-tideMin,tideSpeed+tideMin,DCPumpDuration*100,false);
      break;
    }
    default: {
      rcSpeed=tideSpeed;
      rcSpeedAS=tideSpeed; 
      pumpOffset=(float) InternalMemory.read(Mem_B_GyreOffset)/100;
    }
  }

  ReefAngel.DCPump.SetMode(Custom,rcSpeedAS*pumpOffset,tide.isOutgoing());
  ReefAngel.DCPump.SetMode(Custom,rcSpeed,tide.isIncoming());
}

void NextDCPumpMode() {
      DCPumpMode++;
     
      if (DCPumpMode > 12) {
        DCPumpMode=0;
        DCPumpSpeed=50; // Constant
      } else if (DCPumpMode == 1) {
        DCPumpSpeed=40; // Lagoon
      } else if (DCPumpMode == 2) {
        DCPumpSpeed=45; // Reef Crest
      } else if (DCPumpMode == 3) {
        DCPumpSpeed=55; DCPumpDuration=10; // Short Pulse
      } else if (DCPumpMode == 4) {
        DCPumpSpeed=55; DCPumpDuration=20; // Long Pulse
      } else if (DCPumpMode == 5) {
        DCPumpSpeed=InternalMemory.read(Mem_B_NTMSpeed);
        DCPumpDuration=InternalMemory.read(Mem_B_NTMDuration); // Smart_NTM
      } else if (DCPumpMode == 6) {
        DCPumpSpeed=50; DCPumpDuration=10; // Smart_TSM
      } else if (DCPumpMode == 7) {
        DCPumpSpeed=InternalMemory.read(Mem_B_NightSpeed);
        DCPumpDuration=InternalMemory.read(Mem_B_NightDuration);
        DCPumpMode=9; // Night
      } else if (DCPumpMode == 10) {
        DCPumpSpeed=65; DCPumpDuration=5; // Storm
      } else if (DCPumpMode == 11) {
        DCPumpSpeed=45; DCPumpDuration=10; // Custom
      }

      if (DCPumpMode!=InternalMemory.DCPumpMode_read())
        InternalMemory.DCPumpMode_write(DCPumpMode);
      if (DCPumpSpeed!=InternalMemory.DCPumpSpeed_read())
        InternalMemory.DCPumpSpeed_write(DCPumpSpeed);
      if (DCPumpDuration!=InternalMemory.DCPumpDuration_read())
        InternalMemory.DCPumpDuration_write(DCPumpDuration);
    }



        // Menu Code       
void MenuEntry1() {
  ReefAngel.FeedingModeStart();
}
void MenuEntry2() {
  ReefAngel.WaterChangeModeStart();
}
void MenuEntry3() {
  ReefAngel.ATOClear();
  ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4() {
      NextDCPumpMode();
      ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
    }
void MenuEntry5() {
  ReefAngel.SetupCalibratePH();
}       
void MenuEntry6() {
  ReefAngel.OverheatClear();
  ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry7() {
  ReefAngel.SetupDateTime();
}
void MenuEntry8() {
      // Toggle refugium light between on/auto.
      ReefAngel.Relay.Override(Refugium, ReefAngel.Relay.Status(Refugium)+1);
      ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
    }

// Custom Main Screen
void DrawCustomMain() {
  const int NumScreens=4;
  static boolean drawGraph=true;
 
  // Main Header
  // ReefAngel.LCD.DrawText(DefaultFGColor, DefaultBGColor, 35, 2,"Troy's Reef");
  ReefAngel.LCD.DrawDate(5,2);
  ReefAngel.LCD.Clear(COLOR_BLACK, 1, 11, 128, 11);

  // Param Header
  DrawParams(5,14);
 
  switch (ScreenID) {
    case 0:
    {
      if (drawGraph) { ReefAngel.LCD.DrawGraph(5,40); drawGraph=false; }
      break;
    }
    case 1: { DrawStatus(5,40); break; }
    case 2: { DrawSunMoon(5,40); break; }
    case 3: { DrawClouds(5,50); break; }
  }
 
  // Draw Relays
  DrawRelays(12,94);
 
  // Date+Time
  // ReefAngel.LCD.DrawDate(5,122);
 
  if (ReefAngel.Joystick.IsLeft()) {
    ReefAngel.ClearScreen(DefaultBGColor);
    ScreenID--; drawGraph=true;
  }
  if (ReefAngel.Joystick.IsRight()) {
    ReefAngel.ClearScreen(DefaultBGColor);
    ScreenID++; drawGraph=true;
  }
  if (ScreenID<0) ScreenID=NumScreens-1;
  if (ScreenID>=NumScreens) ScreenID=0;
 
}

void DrawCustomGraph() {
  if (ScreenID==0)
    ReefAngel.LCD.DrawGraph(5, 40);
}

void DrawParams(int x, int y) {
  char buf[16];

  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,x+5,y,"Temp:");
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,x+80, y, "PH:");
  // Temp and PH
  y+=2;
  ConvertNumToString(buf, ReefAngel.Params.Temp[T2_PROBE], 10);
  ReefAngel.LCD.DrawText(T2TempColor, DefaultBGColor, x+45, y, buf);
  y+=6;
  ConvertNumToString(buf, ReefAngel.Params.Temp[T1_PROBE], 10);
  ReefAngel.LCD.DrawLargeText(T1TempColor, DefaultBGColor, x+5, y, buf, Num8x16);
  ConvertNumToString(buf, ReefAngel.Params.PH, 100);
  ReefAngel.LCD.DrawLargeText(PHColor, DefaultBGColor, x+80, y, buf, Num8x16);
  y+=5;
  ConvertNumToString(buf, ReefAngel.Params.Temp[T3_PROBE], 10);
  ReefAngel.LCD.DrawText(T3TempColor, DefaultBGColor, x+45, y, buf);
}

void DrawStatus(int x, int y) {
  int t=x;
 
  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,15,y,"High",Font8x16);
  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,85,y,"Low",Font8x16);
 
  if (ReefAngel.HighATO.IsActive()) {
    ReefAngel.LCD.FillCircle(55,y+3,5,COLOR_GREEN);
  } else {
    ReefAngel.LCD.FillCircle(55,y+3,5,COLOR_RED);
  }
 
  if (ReefAngel.LowATO.IsActive()) {
    ReefAngel.LCD.FillCircle(70,y+3,5,COLOR_GREEN);
  } else {
    ReefAngel.LCD.FillCircle(70,y+3,5,COLOR_RED);
  }
  y+=12;

 // DC Pump Mode
      ReefAngel.LCD.DrawText(0,255,x,y,"DC:"); x+=20;
      ReefAngel.LCD.Clear(DefaultBGColor,x,y,t+(128-t),y+8);
      if (DCPumpMode == 0) ReefAngel.LCD.DrawLargeText(COLOR_GREEN,255,x,y,"Constant");
      else if (DCPumpMode == 1) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,x,y,"Lagoon");
      else if (DCPumpMode == 2) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,x,y,"Reef Crest");
      else if (DCPumpMode == 3) ReefAngel.LCD.DrawLargeText(COLOR_RED,255,x,y,"Short Pulse");
      else if (DCPumpMode == 4) ReefAngel.LCD.DrawLargeText(COLOR_RED,255,x,y,"Long Pulse");
      else if (DCPumpMode == 5) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,x,y,"Smart NTM");
      else if (DCPumpMode == 6) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,x,y,"Tidal Swell");
      else if (DCPumpMode == 9) ReefAngel.LCD.DrawLargeText(COLOR_WHITE,0,x,y,"Night");
      else if (DCPumpMode == 10) ReefAngel.LCD.DrawLargeText(COLOR_BLUE,0,x,y,"Storm");
      else if (DCPumpMode == 11) ReefAngel.LCD.DrawLargeText(COLOR_BLUE,255,x,y,"Custom");
      y+=10; x=t;
     
      ReefAngel.LCD.DrawText(0,255,x,y,"DC Speed:"); x+=60;
      ReefAngel.LCD.Clear(DefaultBGColor,x,y,128,y+8);
      ReefAngel.LCD.DrawText(COLOR_BLUE, DefaultBGColor,x,y,DCPumpSpeed); x+=15;
      ReefAngel.LCD.DrawText(COLOR_BLUE, DefaultBGColor,x,y,"/"); x+=10;
      ReefAngel.LCD.DrawText(COLOR_BLUE, DefaultBGColor,x,y,DCPumpDuration);
      y+=10; x=t;
     
 
  // Display Acclimation timer
  byte acclDay=InternalMemory.read(Mem_B_AcclDay);
 
  if (acclDay > 0) {
    ReefAngel.LCD.DrawText(DefaultFGColor,DefaultBGColor,x,y,"Acclimation Day:"); x+=100;
    ReefAngel.LCD.DrawSingleMonitor(acclDay,DefaultFGColor,x,y,1);
  } else {
    ReefAngel.LCD.Clear(DefaultBGColor,x,y,128,y+8);
  }
}

void DrawSunMoon(int x, int y) {
  char buf[16];
  int t=x;

  y+=2;
  /// Display Sunrise / Sunset
  sprintf(buf, "%02d:%02d", sun.GetRiseHour(), sun.GetRiseMinute());
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,x,y,"Rise:"); x+=31;
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,x,y,buf);
  sprintf(buf, "%02d:%02d", sun.GetSetHour(), sun.GetSetMinute()); x+=36;
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,x,y,"Set:"); x+=25;
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,x,y,buf);
  y+=15; x=t;
 
  /// Display Moonrise / Moonset
  sprintf(buf, "%02d:%02d", Moon.riseH, Moon.riseM);
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,x,y,"MR:"); x+=21;
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,x,y,buf);
  sprintf(buf, "%02d:%02d", Moon.setH, Moon.setM); x+=36;
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,x,y,"MS:"); x+=21;
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,x,y,buf); x+=36;
  if (Moon.isUp) ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,x,y,"@");
    else ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,x,y,"_");
  y+=10; x=t;
 
  // MoonPhase
  ReefAngel.LCD.DrawText(0,255,x,y,"Moon:");
  ReefAngel.LCD.Clear(DefaultBGColor,x+32,y,128,y+8);
  ReefAngel.LCD.DrawText(COLOR_MAGENTA,255,x+32,y,MoonPhaseLabel());
  y+=10; x=t;
 
  // MoonLight %
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,x,y,"MoonLights:"); x+=68;
  ReefAngel.LCD.DrawSingleMonitor(ReefAngel.PWM.GetDaylightValue(),DPColor,x,y,1);
  x+=5*(intlength(ReefAngel.PWM.GetDaylightValue())+1);
  ReefAngel.LCD.DrawText(DPColor, DefaultBGColor, x, y, "%");
}

void DrawRelays(int x, int y) {
  // Draw Relays
  byte TempRelay = ReefAngel.Relay.RelayData;
  TempRelay &= ReefAngel.Relay.RelayMaskOff;
  TempRelay |= ReefAngel.Relay.RelayMaskOn;
  ReefAngel.LCD.DrawOutletBox(x, y, TempRelay);

  y+=12;
  TempRelay = ReefAngel.Relay.RelayDataE[0];
  TempRelay &= ReefAngel.Relay.RelayMaskOffE[0];
  TempRelay |= ReefAngel.Relay.RelayMaskOnE[0];
  ReefAngel.LCD.DrawOutletBox(x, y, TempRelay);
 
  y+=12;
  TempRelay = ReefAngel.Relay.RelayDataE[1];
  TempRelay &= ReefAngel.Relay.RelayMaskOffE[1];
  TempRelay |= ReefAngel.Relay.RelayMaskOnE[1];
  ReefAngel.LCD.DrawOutletBox(x, y, TempRelay);
}

void DelayedOnFeedMode(byte relay) {
  static unsigned long startTime=now();

  if ( (startTime==LastStart || ReefAngel.DisplayedMenu==WATERCHANGE_MODE) && ReefAngel.HighATO.IsActive()) {
    ReefAngel.Relay.On(relay);
  } else {
    ReefAngel.Relay.DelayedOn(relay);
  }
}

// ------------------------------------------------------------
// 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 InternalMemory.read(Mem_B_CloudsEveryXDays)

// 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 InternalMemory.read(Mem_B_CloudChancePerDay)

// Minimum number of minutes for cloud duration.  Don't use min duration of less than 6
#define Min_Cloud_Duration InternalMemory.read(Mem_B_MinCloudDuration)

// Maximum number of minutes for the cloud duration. Don't use max duration of more than 255
#define Max_Cloud_Duration InternalMemory.read(Mem_B_MaxCloudDuration)

// Minimum number of clouds that can happen per day
#define Min_Clouds_per_Day InternalMemory.read(Mem_B_MinCloudsPerDay)

// Maximum number of clouds that can happen per day
#define Max_Clouds_per_Day InternalMemory.read(Mem_B_MaxCloudsPerDay)

// Only start the cloud effect after this setting
// In this example, start cloud after noon
#define Start_Cloud_After NumMins(InternalMemory.read(Mem_B_StartCloudAfterHour),InternalMemory.read(Mem_B_StartCloudAfterMin))

// Always end the cloud effect before this setting
// In this example, end cloud before 9:00pm
#define End_Cloud_Before NumMins(InternalMemory.read(Mem_B_EndCloudBeforeHour),InternalMemory.read(Mem_B_EndCloudBeforeMin))

// 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_Chance_per_Cloud InternalMemory.read(Mem_B_LightningChance)

// 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 result 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.

// Add Random Lightning modes
#define Calm 0    // No lightning
#define Slow 1    // 5 seconds of slow lightning in the middle of a cloud for ELN style (slow response) drivers
#define Fast 2    // 5 seconds of fast lightning in the middle of a cloud for LDD style (fast response) drivers
#define Mega 3    // Lightning throughout the cloud, higher chance as it gets darker
#define Mega2 4   // Like Mega, but with more lightning
// ------------------------------------------------------------
// Do not change anything below here

static byte cloudchance=255;
static byte cloudduration=0;
static int cloudstart=0;
static byte numclouds=0;
static byte lightningchance=0;
static byte cloudindex=0;
static byte lightningstatus=0;
static byte lightningMode=0;
static boolean chooseLightning=true;

void CheckCloud()
{
    // Set which modes you want to use
  // Example:  { Calm, Fast, Mega, Mega2 } to randomize all four modes.
  // { Mega2 } for just Mega2.  { Mega, Mega, Fast} for Mega and Fast, with twice the chance of Mega.
  byte LightningModes[] = {Slow};

  // Change the values above to customize your cloud/storm effect

  static time_t DelayCounter=millis();    // Variable for lightning timing.
  static int DelayTime=random(1000);      // Variable for lightning timimg.

  // 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
    {
      // Commenting out to see if it's interfering with our other seed.
      // randomSeed(millis());    // Seed the random number generator
      //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_Chance_per_Cloud, we will not have lightning today
        if (lightningchance>Lightning_Chance_per_Cloud) lightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect

  if (ReefAngel.Relay.isMaskOn(LED_STORM)) {
    InternalMemory.write(Mem_B_EnableStorm,false);
  }
  if (ReefAngel.Relay.isMaskOff(LED_STORM)) {
    InternalMemory.write(Mem_B_EnableStorm,true);
  }

  if (InternalMemory.read(Mem_B_EnableStorm)) return;
 
 
  if (cloudchance)
  {
    if (ReefAngel.Relay.isMaskOff(TRIGGER_STORM))      // Change this to whatever port you want to use as a trigger.
    {
      cloudstart = NumMins(hour(), minute());
      ReefAngel.Relay.Auto(TRIGGER_STORM);    // Here, too.
    }
    //is it time for cloud yet?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      // Increase Blue channel first for better effect and to compensate for drop in Whites
      ActinicPWMValue1=ReversePWMSlopeHighRes(cloudstart,cloudstart+cloudduration,ActinicPWMValue1,ActinicPWMValue1+DaylightPWMValue0,180);
      ActinicPWMValue3=ReversePWMSlopeHighRes(cloudstart,cloudstart+cloudduration,ActinicPWMValue3,ActinicPWMValue3+DaylightPWMValue2,180);
     
      DaylightPWMValue0=ReversePWMSlopeHighRes(cloudstart,cloudstart+cloudduration,DaylightPWMValue0,0,180);
      DaylightPWMValue2=ReversePWMSlopeHighRes(cloudstart,cloudstart+cloudduration,DaylightPWMValue2,0,180);
      if (chooseLightning)
      {
        lightningMode=LightningModes[random(100)%sizeof(LightningModes)];
        chooseLightning=false;
      }
      switch (lightningMode)
      {
      case Calm:
        break;
      case Mega:
        // Lightning chance from beginning of cloud through the end.  Chance increases with darkness of cloud.
        if (lightningchance && random(ReversePWMSlope(cloudstart,cloudstart+cloudduration,100,0,180))<1 && (millis()-DelayCounter)>DelayTime)
        {
          // Send the trigger
          Strike();
          DelayCounter=millis();    // If we just had a round of flashes, then lets put in a longer delay
          DelayTime=random(1000);   // of up to a second for dramatic effect before we do another round.
        }
        break;
      case Mega2:
        // Higher lightning chance from beginning of cloud through the end.  Chance increases with darkness of cloud.
        if (lightningchance && random(ReversePWMSlope(cloudstart,cloudstart+cloudduration,100,0,180))<2)
        {
          Strike();
        }
        break;
      case Fast:
        // 5 seconds of lightning in the middle of the cloud
        if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5 && (millis()-DelayCounter)>DelayTime)
        {
          Strike();

          DelayCounter=millis();    // If we just had a round of flashes, then lets put in a longer delay
          DelayTime=random(1000);   // of up to a second for dramatic effect before we do another round.
        }
        break;
      case Slow:
        // Slow lightning for 5 seconds in the middle of the cloud.  Suitable for slower ELN style drivers
        if (lightningchance && second()%40<8)
        {
          SlowStrike();
        }
        break;
      default:
        break;
      }
    }
    else
    {
      chooseLightning=true; // Reset the flag to choose a new lightning type
    }

    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_Chance_per_Cloud, we will not have lightning today
        if (lightningchance>Lightning_Chance_per_Cloud) lightningchance=0;
      }
    }
  }
 
  // Cloud ON option - Clouds every minute
  if (ReefAngel.Relay.isMaskOn(TRIGGER_STORM) && now()%60<10)
  {
    SlowStrike();
  }
}

void SlowStrike()
{
    int r = random(100);
    if (r<20) lightningstatus=1;
    else lightningstatus=0;
    if (lightningstatus)
    {
      // Let's separate left and right both.
      if (r<14) {
        DaylightPWMValue0=4095;
        DaylightPWMValue2=4095;
        ActinicPWMValue1=4095;
        ActinicPWMValue3=4095;
      } else if (r<17) {
        DaylightPWMValue0=100;
        DaylightPWMValue2=4095;
        ActinicPWMValue3=4095;
      } else {
        DaylightPWMValue0=4095;
        ActinicPWMValue1=4095;
        DaylightPWMValue2=100;
      }
    }
    else
    {
      DaylightPWMValue0=100;
      DaylightPWMValue2=100;
    }
    delay(1);
}

void DrawClouds(int x, int y)
{
    // Write the times of the next cloud, next lightning, and cloud duration to the screen and into some customvars for the Portal.
    ReefAngel.LCD.DrawText(0,255,x,y,"C"); x+=6;
    ReefAngel.LCD.DrawText(0,255,x,y,"00:00"); x+=34;
    ReefAngel.LCD.DrawText(0,255,x,y,"L"); x+=6;
    ReefAngel.LCD.DrawText(0,255,x,y,"00:00"); x=5;
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11;
      else x=17;
      ReefAngel.LCD.DrawText(0,255,x,y,(cloudstart/60));
      ReefAngel.CustomVar[0]=cloudstart/60; // Write the hour of the next cloud to custom variable for Portal reporting
      if ((cloudstart%60)>=10) x=29;
      else x=35;
      ReefAngel.LCD.DrawText(0,255,x,y,(cloudstart%60));
      ReefAngel.CustomVar[1]=cloudstart%60; // Write the minute of the next cloud to custom variable for Portal reporting

    }
    ReefAngel.LCD.DrawText(0,255,x+85,y,cloudduration);
    ReefAngel.CustomVar[2]=(cloudduration);    // Put the duration of the next cloud in a custom var for the portal
    if (lightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/3))/60)>=10) x=51;
      else x=57;
      ReefAngel.LCD.DrawText(0,255,x,y,((cloudstart+(cloudduration/3))/60));
      ReefAngel.CustomVar[6]=(cloudstart+(cloudduration/2))/60;    // Write the hour of the next lightning to a custom variable for the Portal
      if (((cloudstart+(cloudduration/3))%60)>=10) x=69;
      else x=75;
        ReefAngel.LCD.DrawText(0,255,x,y,((cloudstart+(cloudduration/3))%60)); // Write the minute of the next lightning to a custom variable for the Portal
        ReefAngel.CustomVar[7]=(cloudstart+(cloudduration/2))%60;
    }
}

void Strike()
{
  int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4.
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*0));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 0.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*2));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 2.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.
    newdata=ReefAngel.PWM.GetChannelValueRaw(0);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*0));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(2);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*2));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}

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 (int) PWMStart;
}

int ReversePWMSlopeHighRes(long cstart,long cend,int PWMStart,int 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 (int) PWMStart;
Image

Posts: 81
Joined: Mon Aug 11, 2014 6:45 am
Location: Clermont, Florida
PostPosted: Thu Dec 29, 2016 6:23 pm
Troy,

Try changing the 0's (right before the 180) to 2 for the minimum. If this doesn't fix it, you may need to change the 0's to 82 for the high res. (2 * 40.95, see below)

Code: Select all
DaylightPWMValue0=ReversePWMSlopeHighRes(cloudstart,cloudstart+cloudduration,DaylightPWMValue0,0,180);
      DaylightPWMValue2=ReversePWMSlopeHighRes(cloudstart,cloudstart+cloudduration,DaylightPWMValue2,0,180);


The maximum of 90% would be addressed in the Strike code. Change all of the 4095 values to 3685. 4095 = 100%, with each percent equaling 40.95.

Code: Select all
void SlowStrike()
{
    int r = random(100);
    if (r<20) lightningstatus=1;
    else lightningstatus=0;
    if (lightningstatus)
    {
      // Let's separate left and right both.
      if (r<14) {
        DaylightPWMValue0=4095;
        DaylightPWMValue2=4095;
        ActinicPWMValue1=4095;
        ActinicPWMValue3=4095;
      } else if (r<17) {
        DaylightPWMValue0=100;
        DaylightPWMValue2=4095;
        ActinicPWMValue3=4095;
      } else {
        DaylightPWMValue0=4095;
        ActinicPWMValue1=4095;
        DaylightPWMValue2=100;
      }
    }
    else
    {
      DaylightPWMValue0=100;
      DaylightPWMValue2=100;
    }
    delay(1);
Image

Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm
PostPosted: Fri Dec 30, 2016 10:51 am
Ok ill have to look at it later i changed it to 2 and that didnt work so i tryed 200 still same towards the beginning and end of ramp slope. Ill post a screen shot when i get home
Image

Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm
PostPosted: Sat Dec 31, 2016 1:18 am
graphrapwm07day.png
graphrapwm07day.png (17.11 KiB) Viewed 1503 times
graphrapwm030day.png
graphrapwm030day.png (17.76 KiB) Viewed 1503 times
Image

Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm
PostPosted: Sat Dec 31, 2016 1:19 am
graphrapwm17day.png
graphrapwm17day.png (16.8 KiB) Viewed 1499 times
graphrapwm130day.png
graphrapwm130day.png (17.81 KiB) Viewed 1499 times
Image

Posts: 81
Joined: Mon Aug 11, 2014 6:45 am
Location: Clermont, Florida
PostPosted: Sat Dec 31, 2016 7:13 pm
Troy,

Here is my full code. I have a similar situation where I don't want whites turning off during storm. I keep strike max at 100% though. I also made some tweaks to the "Mega" mode and it works great for me using slow driver black boxes. I break out across 3 lights though, instead of 2. You might look through the storm code section to see if you can identify the issue.

Code: Select all
#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <PAR.h>
#include <ReefAngel.h>
#include <SunLocation.h>
#include <Tide.h>
#include <Moon.h>
#include <WiFiAlert.h>


////// Place global variable code below here
 
  SunLocation sl;
 
  int avgph[10];
    unsigned long totalavgph=0;
    byte avgindex=0;

void DrawCustomMain()
    {
      byte x;
      byte y = 2;
      char text[7];

      // *********** CHANGE TEMP READOUT COLOR DEPENDENT ON FAN AND HEATER STATUS ***********
      int TempColor;                                    // Color for drawing temperature
      boolean HeatOn = ReefAngel.Relay.Status(Port6);   // Get the status of the heater relay
      if (HeatOn)
      {
        TempColor = COLOR_NAVY;                         // Blue text, too cold, heater is on
      }

      if (!HeatOn)
      {
          TempColor = COLOR_GREEN;                      // Green text, no fan or heater on
      }
      // ***********************************************************************************
             int pHColor;                                 // Color for drawing pH
      boolean LowpH = (ReefAngel.Params.PH < 780) ;     // Check for Low pH Value
      boolean HighpH = (ReefAngel.Params.PH > 850);     // Check for High pH Value
      if (LowpH)
      {
        pHColor = COLOR_NAVY;                          // Blue text, Low pH value
      }
      if (HighpH)   
      {
        pHColor = COLOR_RED;                           // Red text, High pH value
      }
      if (!LowpH && !HighpH)
      {
        pHColor = COLOR_GREEN;                         // Green text, pH acceptable
      }
      // ***********************************************************************************
      ReefAngel.LCD.DrawLargeText(COLOR_DARKSLATEBLUE,DefaultBGColor, 6, 3, " Thunder Reef",Font8x8);   // Put a banner at the top
      ReefAngel.LCD.DrawDate(6, 119);                                                                   // Put the date and time at the bottom
      ReefAngel.LCD.Clear(COLOR_BLACK, 1, 12, 132, 12);                                                 // Draw a black line under the banner
      x = 6;
      y += MENU_START_ROW*1.4;                                                                          // MENU_START_ROW is 10, according to globals.h, so y=2+10+1=13
      ReefAngel.LCD.DrawLargeText(COLOR_BLUE, COLOR_WHITE, x, y+1, " Temp      pH");

      ConvertNumToString(text, ReefAngel.Params.Temp[T1_PROBE], 10);                                    // Get T1 temp and convert
      x = 2;
      y += MENU_START_ROW*1.6;
      ReefAngel.LCD.DrawHugeNumbers(COLOR_BLACK, TempColor, x, y, text);                                // Draw the temperature, white numbers on a colored background
      ConvertNumToString(text, ReefAngel.Params.PH, 100);                                               // Get pH reading and convert
      x = 2;
      y = MENU_START_ROW*2.6;
      ReefAngel.LCD.DrawHugeNumbers(COLOR_YELLOW, pHColor, x+65, y+6, text);                            // Put pH on the screen
     
      x += 6;
      y += MENU_START_ROW*3.3;
  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,25,y,"Sump Level",Font8x8);      // Draw the Sump Float switch status
  if (ReefAngel.LowATO.IsActive())
  {ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_RED);
  }
  else
  {
  ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_GREEN);
  }
  x += 6;
  y += MENU_START_ROW*2.0;
  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,25,y,"Skimmer Cup",Font8x8);     // Draw the Skimmer Cup Float switch status
  if (ReefAngel.HighATO.IsActive()) {ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_GREEN);
  }
  else
  {
  ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_RED);
  }
     
     
      byte TempRelay = ReefAngel.Relay.RelayData;                                          // Code for drawing the relay box
      TempRelay &= ReefAngel.Relay.RelayMaskOff;
      TempRelay |= ReefAngel.Relay.RelayMaskOn;
      ReefAngel.LCD.DrawOutletBox(12, 100, TempRelay);
   }
void DrawCustomGraph()
{
}

    int ActinicPWMValue0=1;        // For cloud code, channel 0, left blue
    int ActinicPWMValue2=1;        // For cloud code, chennel 2, center blue
    int ActinicPWMValue4=1;        // For cloud code, chennel 4, right blue

    int DaylightPWMValue1=1;        // For cloud code, channel 1, left white
    int DaylightPWMValue3=1;        // For cloud code, chennel 3, center white
    int DaylightPWMValue5=1;        // For cloud code, chennel 5, right white

////// Place global variable code above here


void setup()
{
    // This must be the first line
    ReefAngel.Init();                                                                        // Initialize controller
    ReefAngel.Use2014Screen();                                                               // Let's use 2014 Screen
    ReefAngel.FeedingModePorts = Port1Bit | Port6Bit | Port7Bit | Port8Bit;                  // Ports toggled in Feeding Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port6Bit | Port7Bit | Port8Bit;                  // Ports toggled in Water Change Mode
    ReefAngel.LightsOnPorts = Port2Bit | Port3Bit | Port4Bit | Port5Bit;                                // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.OverheatShutoffPorts = Port2Bit | Port3Bit | Port4Bit | Port5Bit | Port6Bit | Port7Bit;   // Ports turned off when Overheat temperature exceeded
    ReefAngel.TempProbe = T1_PROBE;                                                          // Use T1 probe as temperature and overheat functions
    ReefAngel.OverheatProbe = T1_PROBE;
    InternalMemory.OverheatTemp_write( 820 );                                                // Set the Overheat temperature setting
    CustomOverheatClear(T1_PROBE);



    // Ports that are always on
    ReefAngel.Relay.On( Port1 );
   
   
    // Virtual Ports that are always off
    ReefAngel.Relay.Off( Box1_Port1);
    ReefAngel.Relay.Off( Box1_Port2);
    ReefAngel.Relay.Off( Box1_Port3);
    ReefAngel.Relay.Off( Box1_Port4);
    ReefAngel.Relay.Off( Box1_Port5);
    ReefAngel.Relay.Off( Box1_Port6);
    ReefAngel.Relay.Off( Box1_Port7);
    ReefAngel.Relay.Off( Box1_Port8);
   
   
    // Delayed start for skimmer to allow sump level to return to normal after water change
    ReefAngel.Relay.DelayedOn( Port7,5 );

    ////// Place additional initialization code below here
   
    sl.Init(28.5700, -81.6800);    // Lat/long for Clermont, FL
    sl.SetOffset(-2,0,-2,0);       // rise_hour, rise_seconds, set_hour, set_seconds (set 2 hrs later for better viewing time (-4 offset = actual time))

    randomSeed(now()%SECS_PER_DAY);
     
      //Custom Variable [0] =  Month/Season
   
    ReefAngel.CustomLabels[0]="Return"; 
    ReefAngel.CustomLabels[1]="BlueLED"; 
    ReefAngel.CustomLabels[2]="WhiteLED"; 
    ReefAngel.CustomLabels[3]="Refugium"; 
    ReefAngel.CustomLabels[4]="Moonlights"; 
    ReefAngel.CustomLabels[5]="Heaters"; 
    ReefAngel.CustomLabels[6]="Skimmer"; 
    ReefAngel.CustomLabels[7]="ATO & Swabbie"; 

    // Virtual Ports reserved for custom lighting functions and effects

    ReefAngel.CustomLabels[8]="Lights:  B60/W40"; 
    ReefAngel.CustomLabels[9]="Lighta: Whites 40"; 
    ReefAngel.CustomLabels[10]="Lights: Blues 60"; 
    ReefAngel.CustomLabels[11]="Not Used"; 
    ReefAngel.CustomLabels[12]="Storm"; 
    ReefAngel.CustomLabels[13]="Fast Clouds"; 
    ReefAngel.CustomLabels[14]="Weather"; 
    ReefAngel.CustomLabels[15]="Weather"; 


    ////// Place additional initialization code above here
}

void loop()
{
 

    // seasonal temperatures
    SeasonalTemps();

// Lights on Dimming Expansion. (Left=East, Center=Center, Right=West)

//if (ReefAngel.Relay.Status( Box1_Port1 )); // Set Lights 60/40
//{
//   ActinicPWMValue0=60;        // Virtual port, channel 0, left blue
//   ActinicPWMValue2=60;        // Virtual port, chennel 2, center blue
//   ActinicPWMValue4=60;        // Virtual port, chennel 4, right blue
//   DaylightPWMValue1=40;        // Virtual port, channel 1, left white
//   DaylightPWMValue3=40;        // Virtual port, chennel 3, center white
//   DaylightPWMValue5=40;        // Virtual port, chennel 5, right white
//}


// Default lights program

ActinicPWMValue0=PWMSlopeHighRes(9,30,21,30,1,60,240,41);  // Default for blues
ActinicPWMValue2=PWMSlopeHighRes(9,45,21,45,1,60,240,41);
ActinicPWMValue4=PWMSlopeHighRes(10,00,22,0,1,60,240,41);

DaylightPWMValue1=PWMSlopeHighRes(11,30,19,30,1,25,165,41);    //  Default for whites
DaylightPWMValue3=PWMSlopeHighRes(11,45,19,45,1,25,165,41);
DaylightPWMValue5=PWMSlopeHighRes(12,00,20,0,1,25,165,41);

CheckCloud();    //  Check for cloud and slow lightning.
ReefAngel.PWM.SetChannelRaw(0,ActinicPWMValue0);  //  Write values for either default or cloud/slow
ReefAngel.PWM.SetChannelRaw(2,ActinicPWMValue2);  //  lightning if it's time.
ReefAngel.PWM.SetChannelRaw(4,ActinicPWMValue4);  //  lightning if it's time.

ReefAngel.PWM.SetChannelRaw(1,DaylightPWMValue1);  //  Write values for either default or cloud/slow
ReefAngel.PWM.SetChannelRaw(3,DaylightPWMValue3);  //  lightning if it's time.
ReefAngel.PWM.SetChannelRaw(5,DaylightPWMValue5);  //  lightning if it's time.

// Turn Actinic outlet on if any % is >= 2
(ReefAngel.PWM.GetChannelValue(0)>=2 || ReefAngel.PWM.GetChannelValue(2)>=2 || ReefAngel.PWM.GetChannelValue(4)>=2) ?  ReefAngel.Relay.On( Port2 ) :  ReefAngel.Relay.Off( Port2 );

// Turn Daylight outlet on if any % is >=2 
(ReefAngel.PWM.GetChannelValue(1)>=2 || ReefAngel.PWM.GetChannelValue(3)>=2 || ReefAngel.PWM.GetChannelValue(5)>=2) ?  ReefAngel.Relay.On( Port3 ) :  ReefAngel.Relay.Off( Port3 );

// Refugium Light: sPar38-Fuge
if (hour()<17 || hour() >=19)
{
 ReefAngel.Relay.On( Port4 );
}
else
{
 ReefAngel.Relay.Off( Port4 );
}

//  Moonlight Strip

if ((hour()>=21 && hour() <23) || (hour()>=8 && hour()<10))
{
 ReefAngel.Relay.On( Port5 );
}
else
{
 ReefAngel.Relay.Off( Port5 );
}
   
////// Place your custom code below here



// WP-25 powerheads schedule

if (hour()>=9 && hour()<13)
{
 ReefAngel.PWM.SetDaylight( ReefCrestMode(55,15,true) ); // reefcrest at 55% +/- 15% on sync mode       (Day Mode)
 ReefAngel.PWM.SetActinic( ReefCrestMode(55,15,false) ); // reefcrest at 55% +/- 15% on Anti-sync mode  (Day mode)
}
else if (hour()>=13 && hour()<19)
{
 byte random_speed=random(65,85);
 ReefAngel.PWM.SetDaylight( ShortPulseMode(1,random_speed,408,true) ); // Short pulse at 65%-85% with 408ms pulse on sync mode (surface wave)
 ReefAngel.PWM.SetActinic( ShortPulseMode(1,random_speed,408,false) ); // Short pulse at 65%-85%% with 408ms pulse on Anti-sync mode (surface wave)
}
else if (hour()>=19 && hour()<20)
{
 byte random_min=random(35,45);
 byte random_speed=random(65,80);
 byte random_duration=random(2,5);
 ReefAngel.PWM.SetDaylight( LongPulseMode(random_min,random_speed,random_duration,true) ); // Long Pulse 35% ramping up to 65%-80% for 2-5 seconds on sync mode (nutrient transport)
 ReefAngel.PWM.SetActinic( LongPulseMode(random_min,random_speed,random_duration,false) ); // Long Pulse 35% ramping up to 65%-80% for 2-5 seconds on Anti-sync mode  (nutrient transport)
}
else if (hour()>=20 && hour()<21)
{
 ReefAngel.PWM.SetDaylight( ReefCrestMode(55,15,true) ); // reefcrest at 55% +/- 15% on sync mode       (Day Mode)
 ReefAngel.PWM.SetActinic( ReefCrestMode(55,15,false) ); // reefcrest at 55% +/- 15% on Anti-sync mode  (Day mode)
}
else
{
 ReefAngel.PWM.SetDaylight( ReefCrestMode(50,10,true) ); // reefcrest at 50% +/- 10% on sync mode       (night Mode)
 ReefAngel.PWM.SetActinic( ReefCrestMode(50,10,false) ); // reefcrest at 50% +/- 10% on Anti-sync mode  (night mode)
}
 
if( ReefAngel.DisplayedMenu==FEEDING_MODE )
{
 ReefAngel.PWM.SetActinic(1);
 ReefAngel.PWM.SetDaylight(1);
}

if( ReefAngel.DisplayedMenu==WATERCHANGE_MODE )
{
 ReefAngel.PWM.SetActinic(35);
 ReefAngel.PWM.SetDaylight(35);
}

// enter feeding mode at 6:15pm
if ( (hour()==18 && minute()==15 && second()==0))
 {
 ReefAngel.FeedingModeStart(); // turn on feeding mode
 }
 
 
if(ReefAngel.HighATO.IsActive())           //  Float switch in Skimmer Locker
{
ReefAngel.Relay.DelayedOn( Port7,5 );
}
 else
{
 ReefAngel.Relay.Off(Port7);               //  Turn off Skimmer when locker full.
}
 

// ATO, Port 8 is ATO using Clear RoDi

 ReefAngel.SingleATO(true,Port8,400,0);   //  Sump switch.  If ATO/RoDi runs for 400 seconds, then shut off and send alert.

 
  {
    sl.CheckAndUpdate();  // handle updating sunrise and sunset values
  }
 
    ////// Place your custom code above here

    // This should always be the last line
    ReefAngel.Portal( "DmnYnkee" );
    ReefAngel.ShowInterface();
}

 void SeasonalTemps ()
 {
  static int heatArray[][2] = { {786,790},                  // default in case of error in month=0 (June)
                    {774,778},//January (winter)            // 77.6
                    {776,780},//February (winter)           // 77.8
                    {778,782},//March (early spring)        // 78.0
                    {780,784},//April (spring)              // 78.2
                    {782,786},//May (spring)                // 78.4
                    {786,790},//June (early summer)         // 78.8
                    {790,794},//July (summer)               // 79.2
                    {794,798},//August (summer)             // 79.6
                    {790,794},//September (early fall)      // 79.2
                    {786,790},//October (fall)              // 78.8
                    {782,786},//November (fall)             // 78.4
                    {778,782} };//December (early winter)   // 78.0
                   
               
  ReefAngel.StandardHeater( Port6,heatArray[month()][0],heatArray[month()][1]);
 }//end seasonalTemps
 
 
// ------------------------------------ Auto overheat clear

void CustomOverheatClear(byte probe)
{
if((bitRead(ReefAngel.AlertFlags, OverheatFlag)) && (ReefAngel.Params.Temp[probe] <= InternalMemory.OverheatTemp_read()-30))
ReefAngel.OverheatClear();
}

 
 
// ------------------------------------------------------------  Weather section
// Do not change anything below here

static byte cloudchance=255;
static byte cloudduration=0;
static int cloudstart=0;
static byte numclouds=0;
static byte lightningchance=0;
static byte cloudindex=0;
static byte lightningstatus=0;
static byte lightningMode=0;
static boolean chooseLightning=true;

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 1

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

  // Minimum number of minutes for cloud duration.  Don't use min duration of less than 6
#define Min_Cloud_Duration 8

  // Maximum number of minutes for the cloud duration. Don't use max duration of more than 255
#define Max_Cloud_Duration 16 

  // Minimum number of clouds that can happen per day
#define Min_Clouds_per_Day 1

  // Maximum number of clouds that can happen per day
#define Max_Clouds_per_Day 2

  // Only start the cloud effect after this setting
  // In this example, start cloud after 12:00pm
#define Start_Cloud_After NumMins(12,00)

  // Always end the cloud effect before this setting
  // In this example, end cloud before 9:00pm
#define End_Cloud_Before NumMins(21,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 100

  // 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 result 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.

  // Add Random Lightning modes
#define Calm 0    // No lightning
#define Slow 1    // 5 seconds of slow lightning in the middle of a cloud for ELN style (slow response) drivers
#define Fast 2    // 5 seconds of fast lightning in the middle of a cloud for LDD style (fast response) drivers
#define Mega 3    // Lightning throughout the cloud, higher chance as it gets darker
#define Mega2 4   // Like Mega, but with more lightning

  // Set which modes you want to use
  // Example:  { Calm, Fast, Mega, Mega2 } to randomize all four modes. 
  // { Mega2 } for just Mega2.  { Mega, Mega, Fast} for Mega and Fast, with twice the chance of Mega.

  byte LightningModes[] = { Mega, Mega, Calm };                                                                                                        // <---- set Storm modes here

  // Change the values above to customize your cloud/storm effect

  static time_t DelayCounter=millis();    // Variable for lightning timing. 
  static int DelayTime=random(1000);      // Variable for lightning timimg.

  // 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
    {
      randomSeed(millis());    // Seed the random number generator
      //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)
  {
    if (ReefAngel.Relay.isMaskOn(Box1_Port5))      // Change this to whatever port you want to use as a trigger.
    {
      cloudstart = NumMins(hour(), minute());
      ReefAngel.Relay.Auto(Box1_Port5);    // Here, too.
    }
    //is it time for cloud yet?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
         // Increase Blue channel first, for better effect and to compensate for drop in Whites
       
      ActinicPWMValue0=ReversePWMSlope(cloudstart,cloudstart+cloudduration,ActinicPWMValue0,ActinicPWMValue0+DaylightPWMValue1*.85,120);
      ActinicPWMValue2=ReversePWMSlope(cloudstart,cloudstart+cloudduration,ActinicPWMValue2,ActinicPWMValue2+DaylightPWMValue3*.85,120);
      ActinicPWMValue4=ReversePWMSlope(cloudstart,cloudstart+cloudduration,ActinicPWMValue4,ActinicPWMValue4+DaylightPWMValue5*.85,120);
     
         // Daylight dimming from cloud
     
      DaylightPWMValue1=ReversePWMSlope(cloudstart,cloudstart+cloudduration,DaylightPWMValue1/40.95,2,120)*40.95;
      DaylightPWMValue3=ReversePWMSlope(cloudstart,cloudstart+cloudduration,DaylightPWMValue3/40.95,2,120)*40.95;
      DaylightPWMValue5=ReversePWMSlope(cloudstart,cloudstart+cloudduration,DaylightPWMValue5/40.95,2,120)*40.95;
     


      if (chooseLightning)
      {
        lightningMode=LightningModes[random(100)%sizeof(LightningModes)];
        chooseLightning=false;
      }
      switch (lightningMode)
      {
      case Calm:
        break;
      case Mega:
        // Lightning chance from beginning of cloud through the end.  Chance increases with darkness of cloud.
        if (lightningchance && random(ReversePWMSlope(cloudstart,cloudstart+cloudduration,100,0,120))<1 && (millis()-DelayCounter)>DelayTime)
        {
          // Send the trigger
          int r=random(34);
         
          if (r<20) {
          Strike1();                // All 3 lights
     
        } else if (r<22) {
          Strike2();                // Left only
         
        } else if (r<24) {
          Strike3();                // Center only
         
        } else if (r<28) {
          Strike4();                // Right only
         
        } else if (r<30) {           
          Strike5();                // Left & Center
         
        } else if (r<34)
          Strike6();                // Center & Right
           
          DelayCounter=millis();    // If we just had a round of flashes, then lets put in a longer delay
          DelayTime=random(1800);   // of up to a second for dramatic effect before we do another round.
        }
        break;
      case Mega2:
        // Higher lightning chance from beginning of cloud through the end.  Chance increases with darkness of cloud.
        if (lightningchance && random(ReversePWMSlope(cloudstart,cloudstart+cloudduration,100,0,180))<2)
        {
          Strike();
        }
        break;
      case Fast:
        // 5 seconds of lightning in the middle of the cloud
        if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5 && (millis()-DelayCounter)>DelayTime)
        {
          Strike();

          DelayCounter=millis();    // If we just had a round of flashes, then lets put in a longer delay
          DelayTime=random(1000);   // of up to a second for dramatic effect before we do another round.
        }
        break;
      case Slow:
        // Slow lightning for 5 seconds in the middle of the cloud.  Suitable for slower ELN style drivers
        if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
        {
          SlowStrike();
        }
        break;
      default:
        break;
      }
    }
    else
    {
      chooseLightning=true; // Reset the flag to choose a new lightning type
    }

    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;
      }
    } 
  }
 
  // Cloud ON option - Clouds every minute
  if (ReefAngel.Relay.isMaskOff(Box1_Port5) && now()%60<10)
  {
    SlowStrike();
  }
  }

    void SlowStrike()

    {
    int r = random(80);
    if (r<20) lightningstatus=1;
    else lightningstatus=0;
    if (lightningstatus)
    {
      // Let's separate left, center, right, or All.
      if (r<10  ) {               // All 3
        DaylightPWMValue1=4095;
        DaylightPWMValue3=4095;
        DaylightPWMValue5=4095;
      } else if (r<12) {          // Left only
        DaylightPWMValue1=4095;
        DaylightPWMValue3=100;
        DaylightPWMValue5=100;
      } else if (r<14) {          // Center only
        DaylightPWMValue1=100;
        DaylightPWMValue3=4095;
        DaylightPWMValue5=100;
      } else if (r<16) {          // Right only
        DaylightPWMValue1=100;
        DaylightPWMValue3=100;
        DaylightPWMValue5=4095;
      } else if (r<18) {          // Left & Center only
        DaylightPWMValue1=4095;
        DaylightPWMValue3=4095;
        DaylightPWMValue5=100;
      } else {                    // Center & Right only
        DaylightPWMValue1=100;
        DaylightPWMValue3=4095;
        DaylightPWMValue5=4095;
      }
      }
        else
      {
        DaylightPWMValue1=100;
        DaylightPWMValue3=100;
        DaylightPWMValue5=100;
      }
        delay(1);
      } 

void DrawClouds(int x, int y)
{
    // Write the times of the next cloud, next lightning, and cloud duration to the screen and into some customvars for the Portal.
    ReefAngel.LCD.DrawText(0,255,x,y,"C"); x+=6;
    ReefAngel.LCD.DrawText(0,255,x,y,"00:00"); x+=34;
    ReefAngel.LCD.DrawText(0,255,x,y,"L"); x+=6;
    ReefAngel.LCD.DrawText(0,255,x,y,"00:00"); x=5;
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11;
      else x=17;
      ReefAngel.LCD.DrawText(0,255,x,y,(cloudstart/60));
      //ReefAngel.CustomVar[3]=cloudstart/60; // Write the hour of the next cloud to custom variable for Portal reporting
      if ((cloudstart%60)>=10) x=29;
      else x=35;
      ReefAngel.LCD.DrawText(0,255,x,y,(cloudstart%60));
      //ReefAngel.CustomVar[4]=cloudstart%60; // Write the minute of the next cloud to custom variable for Portal reporting

    }
    ReefAngel.LCD.DrawText(0,255,x+85,y,cloudduration);
    ReefAngel.CustomVar[7]=(cloudduration);    // Put the duration of the next cloud in a custom var for the portal
    if (lightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/3))/60)>=10) x=51;
      else x=57;
      ReefAngel.LCD.DrawText(0,255,x,y,((cloudstart+(cloudduration/3))/60));
      ReefAngel.CustomVar[5]=(cloudstart+(cloudduration/2))/60;    // Write the hour of the next lightning to a custom variable for the Portal
      if (((cloudstart+(cloudduration/3))%60)>=10) x=69;
      else x=75;
      ReefAngel.LCD.DrawText(0,255,x,y,((cloudstart+(cloudduration/3))%60)); // Write the minute of the next lightning to a custom variable for the Portal
      ReefAngel.CustomVar[6]=(cloudstart+(cloudduration/2))%60;
    }
}

void Strike1()          //  All 3 lights
{
  int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*1));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 1.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*3));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 3.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*5));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 5.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.
    newdata=ReefAngel.PWM.GetChannelValueRaw(1);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*1));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(3);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*3));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(5);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*5));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}

void Strike2()          // Left only
{
  int a=random(1,4);    // Pick a number of consecutive flashes from 1 to 3. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*1));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 1.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();

    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.
    newdata=ReefAngel.PWM.GetChannelValueRaw(1);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*1));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}
void Strike3()          // Center only
{
  int a=random(1,4);    // Pick a number of consecutive flashes from 1 to 3. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*3));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 3.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();

    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.

    newdata=ReefAngel.PWM.GetChannelValueRaw(3);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*3));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}
void Strike4()          // Right only
{
  int a=random(1,4);    // Pick a number of consecutive flashes from 1 to 3. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*5));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 5.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.

    newdata=ReefAngel.PWM.GetChannelValueRaw(5);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*5));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}
void Strike5()          //  Left & Center
{
  int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*1));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 1.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*3));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 3.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.
    newdata=ReefAngel.PWM.GetChannelValueRaw(1);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*1));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(3);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*3));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}
void Strike6()          // Center & Right
{
  int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*3));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 3.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*5));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 5.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.

    newdata=ReefAngel.PWM.GetChannelValueRaw(3);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*3));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(5);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*5));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}

void Strike()
{
  int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*1));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 1.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*3));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 3.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*5));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 5.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.
    newdata=ReefAngel.PWM.GetChannelValueRaw(1);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*1));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(3);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*3));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(5);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*5));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}

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 (int) PWMStart;
}

int ReversePWMSlope(long cstart,long cend,int PWMStart,int 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 (int) PWMStart;
}
Image

Posts: 81
Joined: Mon Aug 11, 2014 6:45 am
Location: Clermont, Florida
PostPosted: Sat Dec 31, 2016 7:21 pm
Troy,

Here is my full code. I have a similar situation where I don't want whites turning off during storm. I keep strike max at 100% though. I also made some tweaks to the "Mega" mode and it works great for me using slow driver black boxes. I break out across 3 lights though, instead of 2. You might look through the storm code section to see if you can identify the issue.

Code: Select all
#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <PAR.h>
#include <ReefAngel.h>
#include <SunLocation.h>
#include <Tide.h>
#include <Moon.h>
#include <WiFiAlert.h>


////// Place global variable code below here
 
  SunLocation sl;
 
  int avgph[10];
    unsigned long totalavgph=0;
    byte avgindex=0;

void DrawCustomMain()
    {
      byte x;
      byte y = 2;
      char text[7];

      // *********** CHANGE TEMP READOUT COLOR DEPENDENT ON FAN AND HEATER STATUS ***********
      int TempColor;                                    // Color for drawing temperature
      boolean HeatOn = ReefAngel.Relay.Status(Port6);   // Get the status of the heater relay
      if (HeatOn)
      {
        TempColor = COLOR_NAVY;                         // Blue text, too cold, heater is on
      }

      if (!HeatOn)
      {
          TempColor = COLOR_GREEN;                      // Green text, no fan or heater on
      }
      // ***********************************************************************************
             int pHColor;                                 // Color for drawing pH
      boolean LowpH = (ReefAngel.Params.PH < 780) ;     // Check for Low pH Value
      boolean HighpH = (ReefAngel.Params.PH > 850);     // Check for High pH Value
      if (LowpH)
      {
        pHColor = COLOR_NAVY;                          // Blue text, Low pH value
      }
      if (HighpH)   
      {
        pHColor = COLOR_RED;                           // Red text, High pH value
      }
      if (!LowpH && !HighpH)
      {
        pHColor = COLOR_GREEN;                         // Green text, pH acceptable
      }
      // ***********************************************************************************
      ReefAngel.LCD.DrawLargeText(COLOR_DARKSLATEBLUE,DefaultBGColor, 6, 3, " Thunder Reef",Font8x8);   // Put a banner at the top
      ReefAngel.LCD.DrawDate(6, 119);                                                                   // Put the date and time at the bottom
      ReefAngel.LCD.Clear(COLOR_BLACK, 1, 12, 132, 12);                                                 // Draw a black line under the banner
      x = 6;
      y += MENU_START_ROW*1.4;                                                                          // MENU_START_ROW is 10, according to globals.h, so y=2+10+1=13
      ReefAngel.LCD.DrawLargeText(COLOR_BLUE, COLOR_WHITE, x, y+1, " Temp      pH");

      ConvertNumToString(text, ReefAngel.Params.Temp[T1_PROBE], 10);                                    // Get T1 temp and convert
      x = 2;
      y += MENU_START_ROW*1.6;
      ReefAngel.LCD.DrawHugeNumbers(COLOR_BLACK, TempColor, x, y, text);                                // Draw the temperature, white numbers on a colored background
      ConvertNumToString(text, ReefAngel.Params.PH, 100);                                               // Get pH reading and convert
      x = 2;
      y = MENU_START_ROW*2.6;
      ReefAngel.LCD.DrawHugeNumbers(COLOR_YELLOW, pHColor, x+65, y+6, text);                            // Put pH on the screen
     
      x += 6;
      y += MENU_START_ROW*3.3;
  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,25,y,"Sump Level",Font8x8);      // Draw the Sump Float switch status
  if (ReefAngel.LowATO.IsActive())
  {ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_RED);
  }
  else
  {
  ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_GREEN);
  }
  x += 6;
  y += MENU_START_ROW*2.0;
  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,25,y,"Skimmer Cup",Font8x8);     // Draw the Skimmer Cup Float switch status
  if (ReefAngel.HighATO.IsActive()) {ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_GREEN);
  }
  else
  {
  ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_RED);
  }
     
     
      byte TempRelay = ReefAngel.Relay.RelayData;                                          // Code for drawing the relay box
      TempRelay &= ReefAngel.Relay.RelayMaskOff;
      TempRelay |= ReefAngel.Relay.RelayMaskOn;
      ReefAngel.LCD.DrawOutletBox(12, 100, TempRelay);
   }
void DrawCustomGraph()
{
}

    int ActinicPWMValue0=1;        // For cloud code, channel 0, left blue
    int ActinicPWMValue2=1;        // For cloud code, chennel 2, center blue
    int ActinicPWMValue4=1;        // For cloud code, chennel 4, right blue

    int DaylightPWMValue1=1;        // For cloud code, channel 1, left white
    int DaylightPWMValue3=1;        // For cloud code, chennel 3, center white
    int DaylightPWMValue5=1;        // For cloud code, chennel 5, right white

////// Place global variable code above here


void setup()
{
    // This must be the first line
    ReefAngel.Init();                                                                        // Initialize controller
    ReefAngel.Use2014Screen();                                                               // Let's use 2014 Screen
    ReefAngel.FeedingModePorts = Port1Bit | Port6Bit | Port7Bit | Port8Bit;                  // Ports toggled in Feeding Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port6Bit | Port7Bit | Port8Bit;                  // Ports toggled in Water Change Mode
    ReefAngel.LightsOnPorts = Port2Bit | Port3Bit | Port4Bit | Port5Bit;                                // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.OverheatShutoffPorts = Port2Bit | Port3Bit | Port4Bit | Port5Bit | Port6Bit | Port7Bit;   // Ports turned off when Overheat temperature exceeded
    ReefAngel.TempProbe = T1_PROBE;                                                          // Use T1 probe as temperature and overheat functions
    ReefAngel.OverheatProbe = T1_PROBE;
    InternalMemory.OverheatTemp_write( 820 );                                                // Set the Overheat temperature setting
    CustomOverheatClear(T1_PROBE);



    // Ports that are always on
    ReefAngel.Relay.On( Port1 );
   
   
    // Virtual Ports that are always off
    ReefAngel.Relay.Off( Box1_Port1);
    ReefAngel.Relay.Off( Box1_Port2);
    ReefAngel.Relay.Off( Box1_Port3);
    ReefAngel.Relay.Off( Box1_Port4);
    ReefAngel.Relay.Off( Box1_Port5);
    ReefAngel.Relay.Off( Box1_Port6);
    ReefAngel.Relay.Off( Box1_Port7);
    ReefAngel.Relay.Off( Box1_Port8);
   
   
    // Delayed start for skimmer to allow sump level to return to normal after water change
    ReefAngel.Relay.DelayedOn( Port7,5 );

    ////// Place additional initialization code below here
   
    sl.Init(28.5700, -81.6800);    // Lat/long for Clermont, FL
    sl.SetOffset(-2,0,-2,0);       // rise_hour, rise_seconds, set_hour, set_seconds (set 2 hrs later for better viewing time (-4 offset = actual time))

    randomSeed(now()%SECS_PER_DAY);
     
      //Custom Variable [0] =  Month/Season
   
    ReefAngel.CustomLabels[0]="Return"; 
    ReefAngel.CustomLabels[1]="BlueLED"; 
    ReefAngel.CustomLabels[2]="WhiteLED"; 
    ReefAngel.CustomLabels[3]="Refugium"; 
    ReefAngel.CustomLabels[4]="Moonlights"; 
    ReefAngel.CustomLabels[5]="Heaters"; 
    ReefAngel.CustomLabels[6]="Skimmer"; 
    ReefAngel.CustomLabels[7]="ATO & Swabbie"; 

    // Virtual Ports reserved for custom lighting functions and effects

    ReefAngel.CustomLabels[8]="Lights:  B60/W40"; 
    ReefAngel.CustomLabels[9]="Lighta: Whites 40"; 
    ReefAngel.CustomLabels[10]="Lights: Blues 60"; 
    ReefAngel.CustomLabels[11]="Not Used"; 
    ReefAngel.CustomLabels[12]="Storm"; 
    ReefAngel.CustomLabels[13]="Fast Clouds"; 
    ReefAngel.CustomLabels[14]="Weather"; 
    ReefAngel.CustomLabels[15]="Weather"; 


    ////// Place additional initialization code above here
}

void loop()
{
 

    // seasonal temperatures
    SeasonalTemps();

// Lights on Dimming Expansion. (Left=East, Center=Center, Right=West)

//if (ReefAngel.Relay.Status( Box1_Port1 )); // Set Lights 60/40
//{
//   ActinicPWMValue0=60;        // Virtual port, channel 0, left blue
//   ActinicPWMValue2=60;        // Virtual port, chennel 2, center blue
//   ActinicPWMValue4=60;        // Virtual port, chennel 4, right blue
//   DaylightPWMValue1=40;        // Virtual port, channel 1, left white
//   DaylightPWMValue3=40;        // Virtual port, chennel 3, center white
//   DaylightPWMValue5=40;        // Virtual port, chennel 5, right white
//}


// Default lights program

ActinicPWMValue0=PWMSlopeHighRes(9,30,21,30,1,60,240,41);  // Default for blues
ActinicPWMValue2=PWMSlopeHighRes(9,45,21,45,1,60,240,41);
ActinicPWMValue4=PWMSlopeHighRes(10,00,22,0,1,60,240,41);

DaylightPWMValue1=PWMSlopeHighRes(11,30,19,30,1,25,165,41);    //  Default for whites
DaylightPWMValue3=PWMSlopeHighRes(11,45,19,45,1,25,165,41);
DaylightPWMValue5=PWMSlopeHighRes(12,00,20,0,1,25,165,41);

CheckCloud();    //  Check for cloud and slow lightning.
ReefAngel.PWM.SetChannelRaw(0,ActinicPWMValue0);  //  Write values for either default or cloud/slow
ReefAngel.PWM.SetChannelRaw(2,ActinicPWMValue2);  //  lightning if it's time.
ReefAngel.PWM.SetChannelRaw(4,ActinicPWMValue4);  //  lightning if it's time.

ReefAngel.PWM.SetChannelRaw(1,DaylightPWMValue1);  //  Write values for either default or cloud/slow
ReefAngel.PWM.SetChannelRaw(3,DaylightPWMValue3);  //  lightning if it's time.
ReefAngel.PWM.SetChannelRaw(5,DaylightPWMValue5);  //  lightning if it's time.

// Turn Actinic outlet on if any % is >= 2
(ReefAngel.PWM.GetChannelValue(0)>=2 || ReefAngel.PWM.GetChannelValue(2)>=2 || ReefAngel.PWM.GetChannelValue(4)>=2) ?  ReefAngel.Relay.On( Port2 ) :  ReefAngel.Relay.Off( Port2 );

// Turn Daylight outlet on if any % is >=2 
(ReefAngel.PWM.GetChannelValue(1)>=2 || ReefAngel.PWM.GetChannelValue(3)>=2 || ReefAngel.PWM.GetChannelValue(5)>=2) ?  ReefAngel.Relay.On( Port3 ) :  ReefAngel.Relay.Off( Port3 );

// Refugium Light: sPar38-Fuge
if (hour()<17 || hour() >=19)
{
 ReefAngel.Relay.On( Port4 );
}
else
{
 ReefAngel.Relay.Off( Port4 );
}

//  Moonlight Strip

if ((hour()>=21 && hour() <23) || (hour()>=8 && hour()<10))
{
 ReefAngel.Relay.On( Port5 );
}
else
{
 ReefAngel.Relay.Off( Port5 );
}
   
////// Place your custom code below here



// WP-25 powerheads schedule

if (hour()>=9 && hour()<13)
{
 ReefAngel.PWM.SetDaylight( ReefCrestMode(55,15,true) ); // reefcrest at 55% +/- 15% on sync mode       (Day Mode)
 ReefAngel.PWM.SetActinic( ReefCrestMode(55,15,false) ); // reefcrest at 55% +/- 15% on Anti-sync mode  (Day mode)
}
else if (hour()>=13 && hour()<19)
{
 byte random_speed=random(65,85);
 ReefAngel.PWM.SetDaylight( ShortPulseMode(1,random_speed,408,true) ); // Short pulse at 65%-85% with 408ms pulse on sync mode (surface wave)
 ReefAngel.PWM.SetActinic( ShortPulseMode(1,random_speed,408,false) ); // Short pulse at 65%-85%% with 408ms pulse on Anti-sync mode (surface wave)
}
else if (hour()>=19 && hour()<20)
{
 byte random_min=random(35,45);
 byte random_speed=random(65,80);
 byte random_duration=random(2,5);
 ReefAngel.PWM.SetDaylight( LongPulseMode(random_min,random_speed,random_duration,true) ); // Long Pulse 35% ramping up to 65%-80% for 2-5 seconds on sync mode (nutrient transport)
 ReefAngel.PWM.SetActinic( LongPulseMode(random_min,random_speed,random_duration,false) ); // Long Pulse 35% ramping up to 65%-80% for 2-5 seconds on Anti-sync mode  (nutrient transport)
}
else if (hour()>=20 && hour()<21)
{
 ReefAngel.PWM.SetDaylight( ReefCrestMode(55,15,true) ); // reefcrest at 55% +/- 15% on sync mode       (Day Mode)
 ReefAngel.PWM.SetActinic( ReefCrestMode(55,15,false) ); // reefcrest at 55% +/- 15% on Anti-sync mode  (Day mode)
}
else
{
 ReefAngel.PWM.SetDaylight( ReefCrestMode(50,10,true) ); // reefcrest at 50% +/- 10% on sync mode       (night Mode)
 ReefAngel.PWM.SetActinic( ReefCrestMode(50,10,false) ); // reefcrest at 50% +/- 10% on Anti-sync mode  (night mode)
}
 
if( ReefAngel.DisplayedMenu==FEEDING_MODE )
{
 ReefAngel.PWM.SetActinic(1);
 ReefAngel.PWM.SetDaylight(1);
}

if( ReefAngel.DisplayedMenu==WATERCHANGE_MODE )
{
 ReefAngel.PWM.SetActinic(35);
 ReefAngel.PWM.SetDaylight(35);
}

// enter feeding mode at 6:15pm
if ( (hour()==18 && minute()==15 && second()==0))
 {
 ReefAngel.FeedingModeStart(); // turn on feeding mode
 }
 
 
if(ReefAngel.HighATO.IsActive())           //  Float switch in Skimmer Locker
{
ReefAngel.Relay.DelayedOn( Port7,5 );
}
 else
{
 ReefAngel.Relay.Off(Port7);               //  Turn off Skimmer when locker full.
}
 

// ATO, Port 8 is ATO using Clear RoDi

 ReefAngel.SingleATO(true,Port8,400,0);   //  Sump switch.  If ATO/RoDi runs for 400 seconds, then shut off and send alert.

 
  {
    sl.CheckAndUpdate();  // handle updating sunrise and sunset values
  }
 
    ////// Place your custom code above here

    // This should always be the last line
    ReefAngel.Portal( "DmnYnkee" );
    ReefAngel.ShowInterface();
}

 void SeasonalTemps ()
 {
  static int heatArray[][2] = { {786,790},                  // default in case of error in month=0 (June)
                    {774,778},//January (winter)            // 77.6
                    {776,780},//February (winter)           // 77.8
                    {778,782},//March (early spring)        // 78.0
                    {780,784},//April (spring)              // 78.2
                    {782,786},//May (spring)                // 78.4
                    {786,790},//June (early summer)         // 78.8
                    {790,794},//July (summer)               // 79.2
                    {794,798},//August (summer)             // 79.6
                    {790,794},//September (early fall)      // 79.2
                    {786,790},//October (fall)              // 78.8
                    {782,786},//November (fall)             // 78.4
                    {778,782} };//December (early winter)   // 78.0
                   
               
  ReefAngel.StandardHeater( Port6,heatArray[month()][0],heatArray[month()][1]);
 }//end seasonalTemps
 
 
// ------------------------------------ Auto overheat clear

void CustomOverheatClear(byte probe)
{
if((bitRead(ReefAngel.AlertFlags, OverheatFlag)) && (ReefAngel.Params.Temp[probe] <= InternalMemory.OverheatTemp_read()-30))
ReefAngel.OverheatClear();
}

 
 
// ------------------------------------------------------------  Weather section
// Do not change anything below here

static byte cloudchance=255;
static byte cloudduration=0;
static int cloudstart=0;
static byte numclouds=0;
static byte lightningchance=0;
static byte cloudindex=0;
static byte lightningstatus=0;
static byte lightningMode=0;
static boolean chooseLightning=true;

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 1

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

  // Minimum number of minutes for cloud duration.  Don't use min duration of less than 6
#define Min_Cloud_Duration 8

  // Maximum number of minutes for the cloud duration. Don't use max duration of more than 255
#define Max_Cloud_Duration 16 

  // Minimum number of clouds that can happen per day
#define Min_Clouds_per_Day 1

  // Maximum number of clouds that can happen per day
#define Max_Clouds_per_Day 2

  // Only start the cloud effect after this setting
  // In this example, start cloud after 12:00pm
#define Start_Cloud_After NumMins(12,00)

  // Always end the cloud effect before this setting
  // In this example, end cloud before 9:00pm
#define End_Cloud_Before NumMins(21,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 100

  // 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 result 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.

  // Add Random Lightning modes
#define Calm 0    // No lightning
#define Slow 1    // 5 seconds of slow lightning in the middle of a cloud for ELN style (slow response) drivers
#define Fast 2    // 5 seconds of fast lightning in the middle of a cloud for LDD style (fast response) drivers
#define Mega 3    // Lightning throughout the cloud, higher chance as it gets darker
#define Mega2 4   // Like Mega, but with more lightning

  // Set which modes you want to use
  // Example:  { Calm, Fast, Mega, Mega2 } to randomize all four modes. 
  // { Mega2 } for just Mega2.  { Mega, Mega, Fast} for Mega and Fast, with twice the chance of Mega.

  byte LightningModes[] = { Mega, Mega, Calm };                                                                                                        // <---- set Storm modes here

  // Change the values above to customize your cloud/storm effect

  static time_t DelayCounter=millis();    // Variable for lightning timing. 
  static int DelayTime=random(1000);      // Variable for lightning timimg.

  // 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
    {
      randomSeed(millis());    // Seed the random number generator
      //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)
  {
    if (ReefAngel.Relay.isMaskOn(Box1_Port5))      // Change this to whatever port you want to use as a trigger.
    {
      cloudstart = NumMins(hour(), minute());
      ReefAngel.Relay.Auto(Box1_Port5);    // Here, too.
    }
    //is it time for cloud yet?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
         // Increase Blue channel first, for better effect and to compensate for drop in Whites
       
      ActinicPWMValue0=ReversePWMSlope(cloudstart,cloudstart+cloudduration,ActinicPWMValue0,ActinicPWMValue0+DaylightPWMValue1*.85,120);
      ActinicPWMValue2=ReversePWMSlope(cloudstart,cloudstart+cloudduration,ActinicPWMValue2,ActinicPWMValue2+DaylightPWMValue3*.85,120);
      ActinicPWMValue4=ReversePWMSlope(cloudstart,cloudstart+cloudduration,ActinicPWMValue4,ActinicPWMValue4+DaylightPWMValue5*.85,120);
     
         // Daylight dimming from cloud
     
      DaylightPWMValue1=ReversePWMSlope(cloudstart,cloudstart+cloudduration,DaylightPWMValue1/40.95,2,120)*40.95;
      DaylightPWMValue3=ReversePWMSlope(cloudstart,cloudstart+cloudduration,DaylightPWMValue3/40.95,2,120)*40.95;
      DaylightPWMValue5=ReversePWMSlope(cloudstart,cloudstart+cloudduration,DaylightPWMValue5/40.95,2,120)*40.95;
     


      if (chooseLightning)
      {
        lightningMode=LightningModes[random(100)%sizeof(LightningModes)];
        chooseLightning=false;
      }
      switch (lightningMode)
      {
      case Calm:
        break;
      case Mega:
        // Lightning chance from beginning of cloud through the end.  Chance increases with darkness of cloud.
        if (lightningchance && random(ReversePWMSlope(cloudstart,cloudstart+cloudduration,100,0,120))<1 && (millis()-DelayCounter)>DelayTime)
        {
          // Send the trigger
          int r=random(34);
         
          if (r<20) {
          Strike1();                // All 3 lights
     
        } else if (r<22) {
          Strike2();                // Left only
         
        } else if (r<24) {
          Strike3();                // Center only
         
        } else if (r<28) {
          Strike4();                // Right only
         
        } else if (r<30) {           
          Strike5();                // Left & Center
         
        } else if (r<34)
          Strike6();                // Center & Right
           
          DelayCounter=millis();    // If we just had a round of flashes, then lets put in a longer delay
          DelayTime=random(1800);   // of up to a second for dramatic effect before we do another round.
        }
        break;
      case Mega2:
        // Higher lightning chance from beginning of cloud through the end.  Chance increases with darkness of cloud.
        if (lightningchance && random(ReversePWMSlope(cloudstart,cloudstart+cloudduration,100,0,180))<2)
        {
          Strike();
        }
        break;
      case Fast:
        // 5 seconds of lightning in the middle of the cloud
        if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5 && (millis()-DelayCounter)>DelayTime)
        {
          Strike();

          DelayCounter=millis();    // If we just had a round of flashes, then lets put in a longer delay
          DelayTime=random(1000);   // of up to a second for dramatic effect before we do another round.
        }
        break;
      case Slow:
        // Slow lightning for 5 seconds in the middle of the cloud.  Suitable for slower ELN style drivers
        if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
        {
          SlowStrike();
        }
        break;
      default:
        break;
      }
    }
    else
    {
      chooseLightning=true; // Reset the flag to choose a new lightning type
    }

    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;
      }
    } 
  }
 
  // Cloud ON option - Clouds every minute
  if (ReefAngel.Relay.isMaskOff(Box1_Port5) && now()%60<10)
  {
    SlowStrike();
  }
  }

    void SlowStrike()

    {
    int r = random(80);
    if (r<20) lightningstatus=1;
    else lightningstatus=0;
    if (lightningstatus)
    {
      // Let's separate left, center, right, or All.
      if (r<10  ) {               // All 3
        DaylightPWMValue1=4095;
        DaylightPWMValue3=4095;
        DaylightPWMValue5=4095;
      } else if (r<12) {          // Left only
        DaylightPWMValue1=4095;
        DaylightPWMValue3=100;
        DaylightPWMValue5=100;
      } else if (r<14) {          // Center only
        DaylightPWMValue1=100;
        DaylightPWMValue3=4095;
        DaylightPWMValue5=100;
      } else if (r<16) {          // Right only
        DaylightPWMValue1=100;
        DaylightPWMValue3=100;
        DaylightPWMValue5=4095;
      } else if (r<18) {          // Left & Center only
        DaylightPWMValue1=4095;
        DaylightPWMValue3=4095;
        DaylightPWMValue5=100;
      } else {                    // Center & Right only
        DaylightPWMValue1=100;
        DaylightPWMValue3=4095;
        DaylightPWMValue5=4095;
      }
      }
        else
      {
        DaylightPWMValue1=100;
        DaylightPWMValue3=100;
        DaylightPWMValue5=100;
      }
        delay(1);
      } 

void DrawClouds(int x, int y)
{
    // Write the times of the next cloud, next lightning, and cloud duration to the screen and into some customvars for the Portal.
    ReefAngel.LCD.DrawText(0,255,x,y,"C"); x+=6;
    ReefAngel.LCD.DrawText(0,255,x,y,"00:00"); x+=34;
    ReefAngel.LCD.DrawText(0,255,x,y,"L"); x+=6;
    ReefAngel.LCD.DrawText(0,255,x,y,"00:00"); x=5;
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11;
      else x=17;
      ReefAngel.LCD.DrawText(0,255,x,y,(cloudstart/60));
      //ReefAngel.CustomVar[3]=cloudstart/60; // Write the hour of the next cloud to custom variable for Portal reporting
      if ((cloudstart%60)>=10) x=29;
      else x=35;
      ReefAngel.LCD.DrawText(0,255,x,y,(cloudstart%60));
      //ReefAngel.CustomVar[4]=cloudstart%60; // Write the minute of the next cloud to custom variable for Portal reporting

    }
    ReefAngel.LCD.DrawText(0,255,x+85,y,cloudduration);
    ReefAngel.CustomVar[7]=(cloudduration);    // Put the duration of the next cloud in a custom var for the portal
    if (lightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/3))/60)>=10) x=51;
      else x=57;
      ReefAngel.LCD.DrawText(0,255,x,y,((cloudstart+(cloudduration/3))/60));
      ReefAngel.CustomVar[5]=(cloudstart+(cloudduration/2))/60;    // Write the hour of the next lightning to a custom variable for the Portal
      if (((cloudstart+(cloudduration/3))%60)>=10) x=69;
      else x=75;
      ReefAngel.LCD.DrawText(0,255,x,y,((cloudstart+(cloudduration/3))%60)); // Write the minute of the next lightning to a custom variable for the Portal
      ReefAngel.CustomVar[6]=(cloudstart+(cloudduration/2))%60;
    }
}

void Strike1()          //  All 3 lights
{
  int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*1));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 1.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*3));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 3.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*5));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 5.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.
    newdata=ReefAngel.PWM.GetChannelValueRaw(1);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*1));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(3);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*3));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(5);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*5));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}

void Strike2()          // Left only
{
  int a=random(1,4);    // Pick a number of consecutive flashes from 1 to 3. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*1));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 1.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();

    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.
    newdata=ReefAngel.PWM.GetChannelValueRaw(1);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*1));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}
void Strike3()          // Center only
{
  int a=random(1,4);    // Pick a number of consecutive flashes from 1 to 3. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*3));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 3.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();

    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.

    newdata=ReefAngel.PWM.GetChannelValueRaw(3);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*3));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}
void Strike4()          // Right only
{
  int a=random(1,4);    // Pick a number of consecutive flashes from 1 to 3. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*5));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 5.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.

    newdata=ReefAngel.PWM.GetChannelValueRaw(5);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*5));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}
void Strike5()          //  Left & Center
{
  int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*1));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 1.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*3));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 3.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.
    newdata=ReefAngel.PWM.GetChannelValueRaw(1);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*1));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(3);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*3));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}
void Strike6()          // Center & Right
{
  int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*3));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 3.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*5));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 5.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.

    newdata=ReefAngel.PWM.GetChannelValueRaw(3);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*3));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(5);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*5));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}

void Strike()
{
  int a=random(1,5);    // Pick a number of consecutive flashes from 1 to 4. 
  for (int i=0; i<a; i++)
  {
    // Flash on
    int newdata=4095;
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*1));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 1.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*3));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 3.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    Wire.beginTransmission(0x40);      // Address of the dimming expansion module
    Wire.write(0x8+(4*5));             // 0x8 is channel 0, 0x12 is channel 1, etc.  This is channel 5.
    Wire.write(newdata&0xff);          // Send the data 8 bits at a time.  This sends the LSB
    Wire.write(newdata>>8);            // This sends the MSB
    Wire.endTransmission();
   
    int randy=random(20,80);    // Random number for a delay
    if (randy>71) randy=((randy-70)/2)*100;    // Small chance of a longer delay
    delay(randy);                // Wait from 20 to 69 ms, or 100-400 ms
   
    // Flash off.  Return to baseline.
    newdata=ReefAngel.PWM.GetChannelValueRaw(1);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*1));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(3);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*3));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    newdata=ReefAngel.PWM.GetChannelValueRaw(5);   // Use the channel number you're flashing here
    Wire.beginTransmission(0x40);    // Same as above
    Wire.write(0x8+(4*5));
    Wire.write(newdata&0xff);
    Wire.write(newdata>>8);
    Wire.endTransmission();
   
    delay(random(30,50));                // Wait from 30 to 49 ms
    wdt_reset();    // Reset watchdog timer to avoid re-boots
  }
}

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 (int) PWMStart;
}

int ReversePWMSlope(long cstart,long cend,int PWMStart,int 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 (int) PWMStart;
}
Image

Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm
PostPosted: Sat Dec 31, 2016 11:06 pm
You got it where it dont turn off in yours ? And your using updateled relay sync code aswell?
Image
PreviousNext

Return to How do I code ...

Who is online

Users browsing this forum: No registered users and 4 guests

cron