power head and moon ?

Do you have a question on how to do something.
Ask in here.
Post Reply
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

power head and moon ?

Post by troylong45 »

im looking to add moon and replicate the jabeos to nature to be more life like what do i add to my code for this and doo i need to remove .. i plan on useing the day and night ports for left the right moon lights and the dimming expansion is in below

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 <DCPump.h>
    #include <WiFiAlert.h>
    #include <DCPump.h>

    // Won't compile without this...
    // ReefAngel.DCPump.UseMemory=true; 
    // Custom menus
    #include <avr/pgmspace.h>
    prog_char menu1_label[] PROGMEM = "Feeding Mode";
    prog_char menu2_label[] PROGMEM = "Water Change";
    prog_char menu3_label[] PROGMEM = "ATO Clear";
    prog_char menu4_label[] PROGMEM = "Overheat Clear";
    prog_char menu5_label[] PROGMEM = "PH Calibration";
    prog_char menu6_label[] PROGMEM = "Date / Time";

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

    // Define Custom Memory Locations
    #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_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_LightsOffPerc   171

    #define Mem_B_PrintDebug      198
    #define Mem_B_ResetMemory     199

    void init_memory() {
      // Initialize Custom Memory Locations
      InternalMemory.write_int(Mem_I_Latitude,-21);
      InternalMemory.write_int(Mem_I_Longitude,-147);
      InternalMemory.write(Mem_B_AcclRiseOffset,12);
      InternalMemory.write(Mem_B_AcclSetOffset,13);
      InternalMemory.write(Mem_B_AcclDay,0);
      InternalMemory.write(Mem_B_LightOffset,10);
      InternalMemory.write(Mem_B_LightMode,1);
      InternalMemory.write_int(Mem_I_RiseOffset,21);
      InternalMemory.write_int(Mem_I_SetOffset,21);
      InternalMemory.write(Mem_B_AcclActinicOffset,250);
      InternalMemory.write(Mem_B_AcclDaylightOffset,125);
      InternalMemory.write(Mem_B_LightsOffPerc,1);

      InternalMemory.write(Mem_B_ResetMemory,false);
    }

    #define NUMBERS_8x16


    // Define Relay Ports by Name
    #define Return             1
    #define Heater             2
    #define WhiteLeft          3
    #define BlueLeft           4
    #define WhiteRight         5
    #define BlueRight          6
    #define Autotopoff         7
    #define Skimmer            8

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

    // Custom classes
    SunLocation sun;
    Tide tide;

    // 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
      ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items)); // Initialize Menu
     
      // Ports toggled in Feeding Mode
      ReefAngel.FeedingModePorts = Port1Bit | Port2Bit ;
      // Ports toggled in Water Change Mode
      ReefAngel.WaterChangePorts = Port1Bit | Port2Bit  | Port7Bit  | Port8Bit;
      // Ports toggled when Lights On / Off menu entry selected
      ReefAngel.LightsOnPorts = Port3Bit | Port4Bit | Port5Bit | Port6Bit;
      // Ports turned off when Overheat temperature exceeded
      ReefAngel.OverheatShutoffPorts = Port2Bit;
      // Use T1 probe as temperature and overheat functions
      ReefAngel.TempProbe = T1_PROBE;
      ReefAngel.OverheatProbe = T1_PROBE;
      
      // Feeeding and Water Change mode speed
      ReefAngel.DCPump.FeedingSpeed=0;
      ReefAngel.DCPump.WaterChangeSpeed=0;
      
       // Ports that are always on
    ReefAngel.Relay.On( Port1 ); // Return Pump
     
       
      ////// Place additional initialization code below here
    
       
      if (InternalMemory.read(Mem_B_ResetMemory))
        init_memory();
     ////// Place additional initialization code above here
    }

    void loop()
    {
    ReefAngel.Relay.DelayedOn( Port1 ); // Return Pump
    ReefAngel.SingleATO(true, Port7, InternalMemory.ATOExtendedTimeout_read(), 3); // ato cycle once every 3 hour
    ReefAngel.Relay.Set(Port8, ReefAngel.HighATO.IsActive()); // Skimmer no time out
    ReefAngel.DCPump.ExpansionChannel[4] = Sync; // Left Jebao RW4
    ReefAngel.DCPump.ExpansionChannel[5] = Sync; // Right jebao rw4
    ReefAngel.StandardHeater(Heater);
     
      ////// Place your custom code below here
     
      // Lighting and Flow
      SetSun();               // Setup Sun rise/set lighting
      AcclimateLED();         // Apply acclimation dimming
      LEDPresets();
      CheckCloud();           //  Check for cloud and lightning.
      UpdateLED();
     
     
      ////// Place your custom code above here

      // This should always be the last line
       ReefAngel.Portal( "troylong45" );
    ReefAngel.DDNS( "1" ); // Your DDNS is troylong45-1.myreefangel.com
    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 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  Box1_Port1
    #define LED_2to1  Box1_Port2
    #define LED_3to1  Box1_Port3
    #define LED_4to1  Box1_Port4
    #define LED_BLUE  Box1_Port5
    #define LED_WHITE Box1_Port6
    #define LED_STORM Box1_Port8

    void resetRelayBox(byte ID) {
      // toggle all relays except for the one selected
      for (int i=Box1_Port1;i<=Box1_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_4to1)) {
        if (lastPreset!=7) resetRelayBox(LED_4to1);
        DaylightPWMValue0=80*40.95;
        ActinicPWMValue1=20*40.95;
        DaylightPWMValue2=80*40.95;
        ActinicPWMValue3=20*40.95;
        lastPreset=7;
      }

      if (ReefAngel.Relay.isMaskOff(LED_4to1)) {
        if (lastPreset!=8) resetRelayBox(LED_4to1);
        DaylightPWMValue0=20*40.95;
        ActinicPWMValue1=80*40.95;
        DaylightPWMValue2=20*40.95;
        ActinicPWMValue3=80*40.95;
        lastPreset=8;
      }

      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;
      }  
      }

    // 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);
      if (ReefAngel.PWM.GetChannelValueRaw(2)>=LightsOffPerc) ReefAngel.Relay.On(WhiteRight); else ReefAngel.Relay.Off(WhiteRight);
      if (ReefAngel.PWM.GetChannelValueRaw(3)>=LightsOffPerc) ReefAngel.Relay.On(BlueRight); else ReefAngel.Relay.Off(BlueRight);
    }


    // Menu Code
    void MenuEntry1() {
      ReefAngel.FeedingModeStart();
    }
    void MenuEntry2() {
      ReefAngel.WaterChangeModeStart();
    }
    void MenuEntry3() {
      ReefAngel.ATOClear();
      ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
    }
    void MenuEntry4() {
      ReefAngel.OverheatClear();
      ReefAngel.DisplayMenuEntry("Clear Overheat");
    }
    void MenuEntry5() {
      ReefAngel.SetupCalibratePH();
    }
    void MenuEntry6() {
      ReefAngel.SetupDateTime();
    }
    // 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; }


      }
     
      // 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;
     
     
      // 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;
     
    }

    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);
    }

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

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

     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 50

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

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

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

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

          // Only start the cloud effect after this setting
          // In this example, start cloud after noon
        #define Start_Cloud_After NumMins(9,00)

          // Always end the cloud effect before this setting
          // In this example, end cloud before 9:00pm
        #define End_Cloud_Before NumMins(18,00)

          // Percentage chance of a lightning happen for every cloud
          // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
        #define Lightning_Change_per_Cloud 50

          // 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[] = { Slow };

          // Change the values above to customize your cloud/storm effect
          // ------------------------------------------------------------
          // 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 int LastNumMins=0;
          static byte lightningMode=0;
          static boolean chooseLightning=true;

          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)
          {
            //is it time for cloud yet?
            if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
            {
              DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,DaylightPWMValue/40.95,0,180)*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,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 && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
                {
                  if (random(100)<20) lightningstatus=1;
                  else lightningstatus=0;
                  if (lightningstatus)
                  {
                    DaylightPWMValue=4095;
                  }
                  else
                  {
                    DaylightPWMValue=0;
                  }
                  delay(1);
                }
                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;
              }
            }
          }

          // Write the times of the next cloud, next lightning, and cloud duration to the screen and into some customvars for the Portal.
          if (LastNumMins!=NumMins(hour(),minute()))
          {
            LastNumMins=NumMins(hour(),minute());
            /*ReefAngel.LCD.Clear(255,0,120,132,132);
             ReefAngel.LCD.DrawText(0,255,5,120,"C");
             ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
             ReefAngel.LCD.DrawText(0,255,45,120,"L");
             ReefAngel.LCD.DrawText(0,255,51,120,"00:00"); */
            if (cloudchance && (NumMins(hour(),minute())<cloudstart))
            {
              int x=0;
              if ((cloudstart/60)>=10) x=11;
              else x=17;
              //ReefAngel.LCD.DrawText(0,255,x,120,(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,120,(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,90,120,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/2))/60)>=10) x=51;
              else x=57;
              //ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/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/2))%60)>=10) x=69;
              else x=75;
              //ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60)); // Write the minute of the next lightning to a custom variable for the Portal
                ReefAngel.CustomVar[6]=(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.  I'm using 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*2));             // 0x8 is channel 0, 0x12 is channel 1, etc.  I'm using 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(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(0);   // 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;
        }
Image
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: power head and moon ?

Post by lnevo »

That's the Tide and the Moon class.

Code: Select all

// This goes up top with the rest.
#include <Moon.h>

// Put this stuff in loop in custom section

moon_init(21,-73);
ReefAngel.PWM.SetDaylightRaw(PWMParabolaHighRes(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM,0,MoonPhase(),0));
ReefAngel.PWM.SetActinicRaw(PWMParabolaHighRes(actRiseH,actRiseM,actSetH,actSetM,0,MoonPhase(),0));
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

Re: power head and moon ?

Post by troylong45 »

Ok and that will give me the tidal dc pump setting as well to simulat tide coming in and out high and low tide and ect.
.
Image
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

Re: power head and moon ?

Post by troylong45 »

wait i was wrong i need to have alot of stuff changed dc pumps will stay same channels( dimming expasion channel 4 and 5).

i need the moons to be on dimming expanson channels 2 (will be left) AND 3 (will be right)

and need all the atitnic channels moved from dimming expasion channel 1 and 3 to move to daylight and atitic relay box channels

and the dimming expasion channel 2 for day light to move to dimming expasion channel 1

so i will have relay box left and right blue channels 0-10v anolog
dimming expasion channel 0 and 1 for left and right daylights 0-10v anolog
dimming expasion channel 2 and 3 5v pwm for left and right moons(and will need to add update led relays 7 and 8 i belive which is currently my ato and skim and move the ato and skim to a 2nd relay box )
dimming expasion channel 4 and 5 5v anolog for the jebaos

and of course need all the storm / light off sets/ skim and ato floats edited if needed and any thing im missing
Image
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: power head and moon ?

Post by lnevo »

I didnt do the pumps its more complicated for the moment.

For the rest you've got me swimming...it doesnt matter what dimming. Just use my example for the other channels. The key is running moon_init it loop and then it will set the rise and set times
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

Re: power head and moon ?

Post by troylong45 »

The pumps are fine but I need to swap light channels for the moons cus I can't run pwm on the relay box right? I'll try to move everything around after tomorrow. And then see if you can proof Read it. Or when I get a 2nd relay box can't I get one that's pwm or is it the head unit that does that? That would make things way easyer
Image
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: power head and moon ?

Post by lnevo »

Gotcha.

Are you using the RA moonlights. I believe those can be used with either signal. Either way there's jumpers in your relay box to switch them. Yes you can wait till you get a second relay box and have that be pwm. You would just need to swap that relay box to the primary as the dimming channels on additional boxes are not able to be used.
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

Re: power head and moon ?

Post by troylong45 »

O I can change my current relay box to pwm with a jumper I'll do that then.
No on the ra moons I'm swapping out my sb boards with the 3ch models and getting pwm dimmable drivers for each one. Mike can't find me a anolog one so pmw it has to be lol. But this will make life easyer then swapping code around.
The tidal swell that makes the pumps follow the moon cycles and tides right?
Image
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

power head and moon ?

Post by lnevo »

Tide class not Tidal Swell mode. You need to declare a new Tide and set the parameters for it, then you can set your DCPump speed accordinly.
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

Re: power head and moon ?

Post by troylong45 »

got a error did i past in right area
  • sketch_apr24a.cpp: In function 'void loop()':
    sketch_apr24a:155: error: 'actRiseH' was not declared in this scope
    sketch_apr24a:155: error: 'actRiseM' was not declared in this scope
    sketch_apr24a:155: error: 'actSetH' was not declared in this scope
    sketch_apr24a:155: error: 'actSetM' was not declared in this scope

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 <DCPump.h>
        #include <WiFiAlert.h>
        #include <DCPump.h>
        #include <Moon.h>

        // Won't compile without this...
        // ReefAngel.DCPump.UseMemory=true;
        // Custom menus
        #include <avr/pgmspace.h>
        prog_char menu1_label[] PROGMEM = "Feeding Mode";
        prog_char menu2_label[] PROGMEM = "Water Change";
        prog_char menu3_label[] PROGMEM = "ATO Clear";
        prog_char menu4_label[] PROGMEM = "Overheat Clear";
        prog_char menu5_label[] PROGMEM = "PH Calibration";
        prog_char menu6_label[] PROGMEM = "Date / Time";

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

        // Define Custom Memory Locations
        #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_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_LightsOffPerc   171

        #define Mem_B_PrintDebug      198
        #define Mem_B_ResetMemory     199

        void init_memory() {
          // Initialize Custom Memory Locations
          InternalMemory.write_int(Mem_I_Latitude,-21);
          InternalMemory.write_int(Mem_I_Longitude,-147);
          InternalMemory.write(Mem_B_AcclRiseOffset,12);
          InternalMemory.write(Mem_B_AcclSetOffset,13);
          InternalMemory.write(Mem_B_AcclDay,0);
          InternalMemory.write(Mem_B_LightOffset,10);
          InternalMemory.write(Mem_B_LightMode,1);
          InternalMemory.write_int(Mem_I_RiseOffset,21);
          InternalMemory.write_int(Mem_I_SetOffset,21);
          InternalMemory.write(Mem_B_AcclActinicOffset,250);
          InternalMemory.write(Mem_B_AcclDaylightOffset,125);
          InternalMemory.write(Mem_B_LightsOffPerc,1);

          InternalMemory.write(Mem_B_ResetMemory,false);
        }

        #define NUMBERS_8x16


        // Define Relay Ports by Name
        #define Return             1
        #define Heater             2
        #define WhiteLeft          3
        #define BlueLeft           4
        #define WhiteRight         5
        #define BlueRight          6
        #define Autotopoff         7
        #define Skimmer            8

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

        // Custom classes
        SunLocation sun;
        Tide tide;

        // 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
          ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items)); // Initialize Menu
         
          // Ports toggled in Feeding Mode
          ReefAngel.FeedingModePorts = Port1Bit | Port2Bit ;
          // Ports toggled in Water Change Mode
          ReefAngel.WaterChangePorts = Port1Bit | Port2Bit  | Port7Bit  | Port8Bit;
          // Ports toggled when Lights On / Off menu entry selected
          ReefAngel.LightsOnPorts = Port3Bit | Port4Bit | Port5Bit | Port6Bit;
          // Ports turned off when Overheat temperature exceeded
          ReefAngel.OverheatShutoffPorts = Port2Bit;
          // Use T1 probe as temperature and overheat functions
          ReefAngel.TempProbe = T1_PROBE;
          ReefAngel.OverheatProbe = T1_PROBE;
         
          // Feeeding and Water Change mode speed
          ReefAngel.DCPump.FeedingSpeed=0;
          ReefAngel.DCPump.WaterChangeSpeed=0;
         
           // Ports that are always on
        ReefAngel.Relay.On( Port1 ); // Return Pump
         
           
          ////// Place additional initialization code below here
       
           
          if (InternalMemory.read(Mem_B_ResetMemory))
            init_memory();
         ////// Place additional initialization code above here
        }

        void loop()
        {
        ReefAngel.Relay.DelayedOn( Port1 ); // Return Pump
        ReefAngel.SingleATO(true, Port7, InternalMemory.ATOExtendedTimeout_read(), 3); // ato cycle once every 3 hour
        ReefAngel.Relay.Set(Port8, ReefAngel.HighATO.IsActive()); // Skimmer no time out
        ReefAngel.DCPump.ExpansionChannel[4] = Sync; // Left Jebao RW4
        ReefAngel.DCPump.ExpansionChannel[5] = Sync; // Right jebao rw4
        ReefAngel.StandardHeater(Heater);
        
         
          ////// Place your custom code below here
         
          // Lighting and Flow
          SetSun();               // Setup Sun rise/set lighting
          AcclimateLED();         // Apply acclimation dimming
          LEDPresets();
          CheckCloud();           //  Check for cloud and lightning.
          UpdateLED();
          moon_init(21,-73);
        ReefAngel.PWM.SetDaylightRaw(PWMParabolaHighRes(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM,0,MoonPhase(),0));
        ReefAngel.PWM.SetActinicRaw(PWMParabolaHighRes(actRiseH,actRiseM,actSetH,actSetM,0,MoonPhase(),0));

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

          // This should always be the last line
           ReefAngel.Portal( "troylong45" );
        ReefAngel.DDNS( "1" ); // Your DDNS is troylong45-1.myreefangel.com
        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 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  Box1_Port1
        #define LED_2to1  Box1_Port2
        #define LED_3to1  Box1_Port3
        #define LED_4to1  Box1_Port4
        #define LED_BLUE  Box1_Port5
        #define LED_WHITE Box1_Port6
        #define LED_STORM Box1_Port8

        void resetRelayBox(byte ID) {
          // toggle all relays except for the one selected
          for (int i=Box1_Port1;i<=Box1_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_4to1)) {
            if (lastPreset!=7) resetRelayBox(LED_4to1);
            DaylightPWMValue0=80*40.95;
            ActinicPWMValue1=20*40.95;
            DaylightPWMValue2=80*40.95;
            ActinicPWMValue3=20*40.95;
            lastPreset=7;
          }

          if (ReefAngel.Relay.isMaskOff(LED_4to1)) {
            if (lastPreset!=8) resetRelayBox(LED_4to1);
            DaylightPWMValue0=20*40.95;
            ActinicPWMValue1=80*40.95;
            DaylightPWMValue2=20*40.95;
            ActinicPWMValue3=80*40.95;
            lastPreset=8;
          }

          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;
          } 
          }

        // 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);
          if (ReefAngel.PWM.GetChannelValueRaw(2)>=LightsOffPerc) ReefAngel.Relay.On(WhiteRight); else ReefAngel.Relay.Off(WhiteRight);
          if (ReefAngel.PWM.GetChannelValueRaw(3)>=LightsOffPerc) ReefAngel.Relay.On(BlueRight); else ReefAngel.Relay.Off(BlueRight);
        }


        // Menu Code
        void MenuEntry1() {
          ReefAngel.FeedingModeStart();
        }
        void MenuEntry2() {
          ReefAngel.WaterChangeModeStart();
        }
        void MenuEntry3() {
          ReefAngel.ATOClear();
          ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
        }
        void MenuEntry4() {
          ReefAngel.OverheatClear();
          ReefAngel.DisplayMenuEntry("Clear Overheat");
        }
        void MenuEntry5() {
          ReefAngel.SetupCalibratePH();
        }
        void MenuEntry6() {
          ReefAngel.SetupDateTime();
        }
        // 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; }


          }
         
          // 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;
         
         
          // 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;
         
        }

        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);
        }

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

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

         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 50

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

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

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

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

              // Only start the cloud effect after this setting
              // In this example, start cloud after noon
            #define Start_Cloud_After NumMins(9,00)

              // Always end the cloud effect before this setting
              // In this example, end cloud before 9:00pm
            #define End_Cloud_Before NumMins(18,00)

              // Percentage chance of a lightning happen for every cloud
              // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
            #define Lightning_Change_per_Cloud 50

              // 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[] = { Slow };

              // Change the values above to customize your cloud/storm effect
              // ------------------------------------------------------------
              // 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 int LastNumMins=0;
              static byte lightningMode=0;
              static boolean chooseLightning=true;

              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)
              {
                //is it time for cloud yet?
                if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
                {
                  DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,DaylightPWMValue/40.95,0,180)*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,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 && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
                    {
                      if (random(100)<20) lightningstatus=1;
                      else lightningstatus=0;
                      if (lightningstatus)
                      {
                        DaylightPWMValue=4095;
                      }
                      else
                      {
                        DaylightPWMValue=0;
                      }
                      delay(1);
                    }
                    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;
                  }
                }
              }

              // Write the times of the next cloud, next lightning, and cloud duration to the screen and into some customvars for the Portal.
              if (LastNumMins!=NumMins(hour(),minute()))
              {
                LastNumMins=NumMins(hour(),minute());
                /*ReefAngel.LCD.Clear(255,0,120,132,132);
                 ReefAngel.LCD.DrawText(0,255,5,120,"C");
                 ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
                 ReefAngel.LCD.DrawText(0,255,45,120,"L");
                 ReefAngel.LCD.DrawText(0,255,51,120,"00:00"); */
                if (cloudchance && (NumMins(hour(),minute())<cloudstart))
                {
                  int x=0;
                  if ((cloudstart/60)>=10) x=11;
                  else x=17;
                  //ReefAngel.LCD.DrawText(0,255,x,120,(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,120,(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,90,120,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/2))/60)>=10) x=51;
                  else x=57;
                  //ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/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/2))%60)>=10) x=69;
                  else x=75;
                  //ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60)); // Write the minute of the next lightning to a custom variable for the Portal
                    ReefAngel.CustomVar[6]=(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.  I'm using 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*2));             // 0x8 is channel 0, 0x12 is channel 1, etc.  I'm using 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(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(0);   // 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;
            }
Image
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: power head and moon ?

Post by lnevo »

Sorry change the actRiseH to Moon.riseH etc
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

Re: power head and moon ?

Post by troylong45 »

Ok lol I thought so. I was looking at your code and was thinking tanking out parts like I did last time I really like the way u have a lot of stuff in memory. But I think I gotta look more and edit it a bit
Image
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

Re: power head and moon ?

Post by troylong45 »

here is a remake of code for mine edited from yours but i keep getting errors
sketch_apr24a_with_error.cpp: In function 'void SetTide()':
sketch_apr24a_with_error:546: error: 'vtSpeed' was not declared in this scope
sketch_apr24a_with_error:549: error: 'Var_Tide' was not declared in this scope
sketch_apr24a_with_error.cpp: At global scope:
sketch_apr24a_with_error:566: error: expected declaration before '}' token
not sure how to fix i fixed some that seemed easy to me but i must be missing something

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>
        prog_char menu1_label[] PROGMEM = "Feeding Mode";
        prog_char menu2_label[] PROGMEM = "Water Change";
        prog_char menu3_label[] PROGMEM = "ATO Clear";
        prog_char menu4_label[] PROGMEM = "Overheat Clear";
        prog_char menu5_label[] PROGMEM = "PH Calibration";
        prog_char menu6_label[] PROGMEM = "Date / Time";

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

        // Define Custom Memory Locations
        #define Mem_B_MoonOffset      100
        #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_NightSpeed      122
#define Mem_B_TideMode        143
        #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_MoonMode        170
        #define Mem_B_LightsOffPerc   171
        #define Mem_B_EnableStorm     178

        #define Mem_B_PrintDebug      198
        #define Mem_B_ResetMemory     199

        void init_memory() {
          // Initialize Custom Memory Locations
          InternalMemory.write(Mem_B_MoonOffset,30);
          InternalMemory.write_int(Mem_I_Latitude,-21);
          InternalMemory.write_int(Mem_I_Longitude,-147);
          InternalMemory.write(Mem_B_AcclRiseOffset,12);
          InternalMemory.write(Mem_B_AcclSetOffset,13);
          InternalMemory.write(Mem_B_AcclDay,0);
InternalMemory.write(Mem_B_TideMin,10);
InternalMemory.write(Mem_B_TideMax,20);
          InternalMemory.write(Mem_B_NightSpeed,35);
InternalMemory.write(Mem_B_TideMode,0);
          InternalMemory.write(Mem_B_LightOffset,10);
          InternalMemory.write(Mem_B_LightMode,1);
          InternalMemory.write_int(Mem_I_RiseOffset,21);
          InternalMemory.write_int(Mem_I_SetOffset,21);
          InternalMemory.write(Mem_B_AcclActinicOffset,250);
          InternalMemory.write(Mem_B_AcclDaylightOffset,125);
InternalMemory.write(Mem_B_MoonMode,1);
          InternalMemory.write(Mem_B_LightsOffPerc,1);
          InternalMemory.write(Mem_B_EnableStorm,true);

          InternalMemory.write(Mem_B_ResetMemory,false);
        }

        #define NUMBERS_8x16


        // Define Relay Ports by Name
        #define Return             1
        #define Heater             2
        #define WhiteLeft          3
        #define BlueLeft           4
        #define WhiteRight         5
        #define BlueRight          6
        #define Autotopoff         7
        #define Skimmer            8

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

        // Custom classes
        SunLocation sun;
Tide tide;

        // 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
          ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items)); // Initialize Menu
         
          // Ports toggled in Feeding Mode
          ReefAngel.FeedingModePorts = Port1Bit | Port2Bit ;
          // Ports toggled in Water Change Mode
          ReefAngel.WaterChangePorts = Port1Bit | Port2Bit  | Port7Bit  | Port8Bit;
          // Ports toggled when Lights On / Off menu entry selected
          ReefAngel.LightsOnPorts = Port3Bit | Port4Bit | Port5Bit | Port6Bit;
          // Ports turned off when Overheat temperature exceeded
          ReefAngel.OverheatShutoffPorts = Port2Bit;
          // Use T1 probe as temperature and overheat functions
          ReefAngel.TempProbe = T1_PROBE;
          ReefAngel.OverheatProbe = T1_PROBE;
         
          // Feeeding and Water Change mode speed
          ReefAngel.DCPump.FeedingSpeed=0;
          ReefAngel.DCPump.WaterChangeSpeed=0;
         
           // Ports that are always on
        ReefAngel.Relay.On( Port1 ); // Return Pump
        
         
           
          ////// Place additional initialization code below here
       
           
          if (InternalMemory.read(Mem_B_ResetMemory))
            init_memory();
         ////// Place additional initialization code above here
        }

        void loop()
        {
        ReefAngel.Relay.DelayedOn( Port1 ); // Return Pump
        ReefAngel.SingleATO(true, Port7, InternalMemory.ATOExtendedTimeout_read(), 3); // ato cycle once every 3 hour
        ReefAngel.Relay.Set(Port8, ReefAngel.HighATO.IsActive()); // Skimmer no time out
        ReefAngel.DCPump.ExpansionChannel[4] = Sync; // Left Jebao RW4
        ReefAngel.DCPump.ExpansionChannel[5] = Sync; // Right jebao rw4
        ReefAngel.StandardHeater(Heater);
        
         
          ////// Place your custom code below here
         
          // Lighting and Flow
  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();
  CheckCloud();           //  Check for cloud and lightning.
  UpdateLED();
  SetTide();              // Set High/Low tide properties
         

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

          // This should always be the last line
           ReefAngel.Portal( "troylong45" );
        ReefAngel.DDNS( "1" ); // Your DDNS is troylong45-1.myreefangel.com
        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_4to1  Box2_Port4
#define LED_BLUE  Box2_Port5
#define LED_WHITE Box2_Port6
#define LED_MOON  Box2_Port7
#define LED_STORM Box2_Port8

        void resetRelayBox(byte ID) {
  // toggle all relays except for the one selected
  for (int i=Box3_Port1;i<=Box3_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_4to1)) {
    if (lastPreset!=7) resetRelayBox(LED_4to1);
    DaylightPWMValue0=80*40.95;
    ActinicPWMValue1=20*40.95;
    DaylightPWMValue2=80*40.95;
    ActinicPWMValue3=20*40.95;
    lastPreset=7;
  }

  if (ReefAngel.Relay.isMaskOff(LED_4to1)) {
    if (lastPreset!=8) resetRelayBox(LED_4to1);
    DaylightPWMValue0=20*40.95;
    ActinicPWMValue1=80*40.95;
    DaylightPWMValue2=20*40.95;
    ActinicPWMValue3=80*40.95;
    lastPreset=8;
  }

  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);
  if (ReefAngel.PWM.GetChannelValueRaw(2)>=LightsOffPerc) ReefAngel.Relay.On(WhiteRight); else ReefAngel.Relay.Off(WhiteRight);
  if (ReefAngel.PWM.GetChannelValueRaw(3)>=LightsOffPerc) ReefAngel.Relay.On(BlueRight); else ReefAngel.Relay.Off(BlueRight);
}


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,vtSpeed,120,nightSpeed+tideMin));

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


        // Menu Code
void MenuEntry1() {
  ReefAngel.FeedingModeStart();
}
void MenuEntry2() {
  ReefAngel.WaterChangeModeStart();
}
void MenuEntry3() {
  ReefAngel.ATOClear();
  ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4() {
  ReefAngel.SetupCalibratePH();
}        }
void MenuEntry5() {
  ReefAngel.OverheatClear();
  ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry6() {
  ReefAngel.SetupDateTime();
}

// 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;
 
  // 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 DelayedOnModes(byte relay) {
  static unsigned long startTime=now();

  if ( (startTime==LastStart) && 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 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 50

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

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

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

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

// Only start the cloud effect after this setting
// In this example, start cloud after noon
#define Start_Cloud_After NumMins(10,00)

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

// 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 (InternalMemory.read(Mem_B_EnableStorm)) return;
 
  if (cloudchance)
  {
    if (ReefAngel.Relay.isMaskOff(LED_STORM))      // Change this to whatever port you want to use as a trigger.
    {
      cloudstart = NumMins(hour(), minute());
      ReefAngel.Relay.Auto(LED_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(LED_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[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 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
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: power head and moon ?

Post by lnevo »

Code: Select all

sketch_apr24a_with_error.cpp: In function 'void SetTide()':
sketch_apr24a_with_error:546: error: 'vtSpeed' was not declared in this scope
sketch_apr24a_with_error:549: error: 'Var_Tide' was not declared in this scope
sketch_apr24a_with_error.cpp: At global scope:
You copied my SetTide function but I have those variables (vtSpeed and Var_Tide) set in global. You have to either declare them or replace that with what you want.

Code: Select all

sketch_apr24a_with_error:566: error: expected declaration before '}' token
You missed a } somewhere.
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

Re: power head and moon ?

Post by troylong45 »

lnevo wrote:

Code: Select all

sketch_apr24a_with_error.cpp: In function 'void SetTide()':
sketch_apr24a_with_error:546: error: 'vtSpeed' was not declared in this scope
sketch_apr24a_with_error:549: error: 'Var_Tide' was not declared in this scope
sketch_apr24a_with_error.cpp: At global scope:
You copied my SetTide function but I have those variables (vtSpeed and Var_Tide) set in global. You have to either declare them or replace that with what you want.

Code: Select all

sketch_apr24a_with_error:566: error: expected declaration before '}' token
You missed a } somewhere.

are these vortech settings ? isthat what they are
Image
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

Re: power head and moon ?

Post by troylong45 »

ok i added the

// Vortech Variables
byte vtMode, vtSpeed, vtDuration; from yours but what is that? can i set up dc pump in its place .

if you have time does the code look like moon/tide/ and the pumps will run in sync ,and produce more natural effects of the ocean,(like tides and flow throughout the day and night) and lunar cycle like this?

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>
        prog_char menu1_label[] PROGMEM = "Feeding Mode";
        prog_char menu2_label[] PROGMEM = "Water Change";
        prog_char menu3_label[] PROGMEM = "ATO Clear";
        prog_char menu4_label[] PROGMEM = "Overheat Clear";
        prog_char menu5_label[] PROGMEM = "PH Calibration";
        prog_char menu6_label[] PROGMEM = "Date / Time";

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

        // Define Custom Memory Locations
        #define Mem_B_MoonOffset      100
        #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_NightSpeed      122
#define Mem_B_TideMode        143
        #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_MoonMode        170
        #define Mem_B_LightsOffPerc   171
        #define Mem_B_EnableStorm     178

        #define Mem_B_PrintDebug      198
        #define Mem_B_ResetMemory     199

        void init_memory() {
          // Initialize Custom Memory Locations
          InternalMemory.write(Mem_B_MoonOffset,30);
          InternalMemory.write_int(Mem_I_Latitude,-21);
          InternalMemory.write_int(Mem_I_Longitude,-147);
          InternalMemory.write(Mem_B_AcclRiseOffset,12);
          InternalMemory.write(Mem_B_AcclSetOffset,13);
          InternalMemory.write(Mem_B_AcclDay,0);
InternalMemory.write(Mem_B_TideMin,10);
InternalMemory.write(Mem_B_TideMax,20);
          InternalMemory.write(Mem_B_NightSpeed,35);
InternalMemory.write(Mem_B_TideMode,0);
          InternalMemory.write(Mem_B_LightOffset,10);
          InternalMemory.write(Mem_B_LightMode,1);
          InternalMemory.write_int(Mem_I_RiseOffset,21);
          InternalMemory.write_int(Mem_I_SetOffset,21);
          InternalMemory.write(Mem_B_AcclActinicOffset,250);
          InternalMemory.write(Mem_B_AcclDaylightOffset,125);
InternalMemory.write(Mem_B_MoonMode,1);
          InternalMemory.write(Mem_B_LightsOffPerc,1);
          InternalMemory.write(Mem_B_EnableStorm,true);

          InternalMemory.write(Mem_B_ResetMemory,false);
        }

        #define NUMBERS_8x16


        // Define Relay Ports by Name
        #define Return             1
        #define Heater             2
        #define WhiteLeft          3
        #define BlueLeft           4
        #define WhiteRight         5
        #define BlueRight          6
        #define Autotopoff         7
        #define Skimmer            8

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

        // Custom classes
        SunLocation sun;
Tide tide;

// Vortech Variables
    byte vtMode, vtSpeed, vtDuration;

        // 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
          ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items)); // Initialize Menu
         
          // Ports toggled in Feeding Mode
          ReefAngel.FeedingModePorts = Port1Bit | Port2Bit ;
          // Ports toggled in Water Change Mode
          ReefAngel.WaterChangePorts = Port1Bit | Port2Bit  | Port7Bit  | Port8Bit;
          // Ports toggled when Lights On / Off menu entry selected
          ReefAngel.LightsOnPorts = Port3Bit | Port4Bit | Port5Bit | Port6Bit;
          // Ports turned off when Overheat temperature exceeded
          ReefAngel.OverheatShutoffPorts = Port2Bit;
          // Use T1 probe as temperature and overheat functions
          ReefAngel.TempProbe = T1_PROBE;
          ReefAngel.OverheatProbe = T1_PROBE;
         
          // Feeeding and Water Change mode speed
          ReefAngel.DCPump.FeedingSpeed=0;
          ReefAngel.DCPump.WaterChangeSpeed=0;
         
           // Ports that are always on
        ReefAngel.Relay.On( Port1 ); // Return Pump
        
         
           
          ////// Place additional initialization code below here
       
           
          if (InternalMemory.read(Mem_B_ResetMemory))
            init_memory();
         ////// Place additional initialization code above here
        }

        void loop()
        {
        ReefAngel.Relay.DelayedOn( Port1 ); // Return Pump
        ReefAngel.SingleATO(true, Port7, InternalMemory.ATOExtendedTimeout_read(), 3); // ato cycle once every 3 hour
        ReefAngel.Relay.Set(Port8, ReefAngel.HighATO.IsActive()); // Skimmer no time out
        ReefAngel.DCPump.ExpansionChannel[4] = Sync; // Left Jebao RW4
        ReefAngel.DCPump.ExpansionChannel[5] = Sync; // Right jebao rw4
        ReefAngel.StandardHeater(Heater);
        
         
          ////// Place your custom code below here
         
          // Lighting and Flow
  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();
  CheckCloud();           //  Check for cloud and lightning.
  UpdateLED();
  SetTide();              // Set High/Low tide properties
         

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

          // This should always be the last line
           ReefAngel.Portal( "troylong45" );
        ReefAngel.DDNS( "1" ); // Your DDNS is troylong45-1.myreefangel.com
        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_4to1  Box2_Port4
#define LED_BLUE  Box2_Port5
#define LED_WHITE Box2_Port6
#define LED_MOON  Box2_Port7
#define LED_STORM Box2_Port8

        void resetRelayBox(byte ID) {
  // toggle all relays except for the one selected
  for (int i=Box3_Port1;i<=Box3_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_4to1)) {
    if (lastPreset!=7) resetRelayBox(LED_4to1);
    DaylightPWMValue0=80*40.95;
    ActinicPWMValue1=20*40.95;
    DaylightPWMValue2=80*40.95;
    ActinicPWMValue3=20*40.95;
    lastPreset=7;
  }

  if (ReefAngel.Relay.isMaskOff(LED_4to1)) {
    if (lastPreset!=8) resetRelayBox(LED_4to1);
    DaylightPWMValue0=20*40.95;
    ActinicPWMValue1=80*40.95;
    DaylightPWMValue2=20*40.95;
    ActinicPWMValue3=80*40.95;
    lastPreset=8;
  }

  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);
  if (ReefAngel.PWM.GetChannelValueRaw(2)>=LightsOffPerc) ReefAngel.Relay.On(WhiteRight); else ReefAngel.Relay.Off(WhiteRight);
  if (ReefAngel.PWM.GetChannelValueRaw(3)>=LightsOffPerc) ReefAngel.Relay.On(BlueRight); else ReefAngel.Relay.Off(BlueRight);
}


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,vtSpeed,120,nightSpeed+tideMin));

  
}


        // Menu Code
void MenuEntry1() {
  ReefAngel.FeedingModeStart();
}
void MenuEntry2() {
  ReefAngel.WaterChangeModeStart();
}
void MenuEntry3() {
  ReefAngel.ATOClear();
  ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4() {
  ReefAngel.SetupCalibratePH();
}        
void MenuEntry5() {
  ReefAngel.OverheatClear();
  ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry6() {
  ReefAngel.SetupDateTime();
}

// 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;
 
  // 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 DelayedOnModes(byte relay) {
  static unsigned long startTime=now();

  if ( (startTime==LastStart) && 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 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 50

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

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

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

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

// Only start the cloud effect after this setting
// In this example, start cloud after noon
#define Start_Cloud_After NumMins(10,00)

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

// 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 (InternalMemory.read(Mem_B_EnableStorm)) return;
 
  if (cloudchance)
  {
    if (ReefAngel.Relay.isMaskOff(LED_STORM))      // Change this to whatever port you want to use as a trigger.
    {
      cloudstart = NumMins(hour(), minute());
      ReefAngel.Relay.Auto(LED_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(LED_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[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 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
troylong45
Posts: 214
Joined: Sat Oct 10, 2015 9:17 pm

Re: power head and moon ?

Post by troylong45 »

ok i got moon working ok looks like but the tide was not working i copied you vortec and tide code and i went in and changed everything from rf to dcpump will that work and every thing

I have one jabeo on dimming expansion channel 4 and one on channel 5

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>
        prog_char menu1_label[] PROGMEM = "Feeding Mode";
        prog_char menu2_label[] PROGMEM = "Water Change";
        prog_char menu3_label[] PROGMEM = "ATO Clear";
        prog_char menu4_label[] PROGMEM = "Overheat Clear";
        prog_char menu5_label[] PROGMEM = "PH Calibration";
        prog_char menu6_label[] PROGMEM = "Date / Time";

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

        // Define Custom Memory Locations
        #define Mem_B_MoonOffset      100
        #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_RandomMode      168
        #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_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_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_PrintDebug      198
        #define Mem_B_ResetMemory     199

        void init_memory() {
          // Initialize Custom Memory Locations
          InternalMemory.write(Mem_B_MoonOffset,30);
          InternalMemory.write_int(Mem_I_Latitude,-21);
          InternalMemory.write_int(Mem_I_Longitude,-147);
          InternalMemory.write(Mem_B_AcclRiseOffset,12);
          InternalMemory.write(Mem_B_AcclSetOffset,13);
          InternalMemory.write(Mem_B_AcclDay,0);
          InternalMemory.write(Mem_B_TideMin,10);
          InternalMemory.write(Mem_B_TideMax,20);
          InternalMemory.write(Mem_B_PumpOffset,80);
          InternalMemory.write(Mem_B_FeedingDCPump,true);
          InternalMemory.write(Mem_B_NightDCPump,false);
          InternalMemory.write(Mem_B_NightSpeed,35);
          InternalMemory.write(Mem_B_NightDuration,16);
          InternalMemory.write(Mem_B_NTMSpeed,60);
          InternalMemory.write(Mem_B_NTMDuration,5);
          InternalMemory.write(Mem_B_NTMDelay,15);
          InternalMemory.write(Mem_B_NTMTime,150);
          InternalMemory.write(Mem_B_TideMode,0);
          InternalMemory.write(Mem_B_LightOffset,10);
          InternalMemory.write(Mem_B_LightMode,1);
          InternalMemory.write_int(Mem_I_RiseOffset,21);
          InternalMemory.write_int(Mem_I_SetOffset,21);
          InternalMemory.write(Mem_B_AcclActinicOffset,250);
          InternalMemory.write(Mem_B_AcclDaylightOffset,125);
          InternalMemory.write(Mem_B_MoonMode,1);
          InternalMemory.write(Mem_B_LightsOffPerc,1); 
          InternalMemory.write(Mem_B_FeedingSpeed,3);
          InternalMemory.write(Mem_B_RandomMode,false);
          InternalMemory.write(Mem_B_GyreOffset,10);
          InternalMemory.write(Mem_B_WCSpeed,25);
          InternalMemory.write(Mem_B_EnableStorm,true);

          InternalMemory.write(Mem_B_ResetMemory,false);
        }
  

        #define NUMBERS_8x16

        #define Var_Tide         4
        #define Var_TideMode     5
        
        // Define Relay Ports by Name
        #define Return             1
        #define Heater             2
        #define WhiteLeft          3
        #define BlueLeft           4
        #define WhiteRight         5
        #define BlueRight          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
          ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items)); // Initialize Menu
         
          // Ports toggled in Feeding Mode
          ReefAngel.FeedingModePorts = Port1Bit | Port2Bit ;
          // Ports toggled in Water Change Mode
          ReefAngel.WaterChangePorts = Port1Bit | Port2Bit  | Port7Bit  | Port8Bit;
          // Ports toggled when Lights On / Off menu entry selected
          ReefAngel.LightsOnPorts = Port3Bit | Port4Bit | Port5Bit | Port6Bit;
          // Ports turned off when Overheat temperature exceeded
          ReefAngel.OverheatShutoffPorts = Port2Bit;
          // 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( Port1 ); // 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()
        {
        ReefAngel.Relay.DelayedOn( Port1 ); // Return Pump
        ReefAngel.SingleATO(true, Port7, InternalMemory.ATOExtendedTimeout_read(), 3); // ato cycle once every 3 hour
        ReefAngel.Relay.Set(Port8, ReefAngel.HighATO.IsActive()); // Skimmer no time out
        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
         
          // Lighting and Flow
          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();
          CheckCloud();           //  Check for cloud and lightning.
          UpdateLED();
          SetTide();              // Set High/Low tide properties
          SetDCPump();                // Set Vortech modes      

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

          // This should always be the last line
           ReefAngel.Portal( "troylong45" );
        ReefAngel.DDNS( "1" ); // Your DDNS is troylong45-1.myreefangel.com
        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_4to1  Box2_Port4
#define LED_BLUE  Box2_Port5
#define LED_WHITE Box2_Port6
#define LED_MOON  Box2_Port7
#define LED_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_4to1)) {
    if (lastPreset!=7) resetRelayBox(LED_4to1);
    DaylightPWMValue0=80*40.95;
    ActinicPWMValue1=20*40.95;
    DaylightPWMValue2=80*40.95;
    ActinicPWMValue3=20*40.95;
    lastPreset=7;
  }

  if (ReefAngel.Relay.isMaskOff(LED_4to1)) {
    if (lastPreset!=8) resetRelayBox(LED_4to1);
    DaylightPWMValue0=20*40.95;
    ActinicPWMValue1=80*40.95;
    DaylightPWMValue2=20*40.95;
    ActinicPWMValue3=80*40.95;
    lastPreset=8;
  }

  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);
  if (ReefAngel.PWM.GetChannelValueRaw(2)>=LightsOffPerc) ReefAngel.Relay.On(WhiteRight); else ReefAngel.Relay.Off(WhiteRight);
  if (ReefAngel.PWM.GetChannelValueRaw(3)>=LightsOffPerc) ReefAngel.Relay.On(BlueRight); else ReefAngel.Relay.Off(BlueRight);
}


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 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, Smart_NTM, Lagoon, ShortPulse, LongPulse, BHazard, Else, Sine, Constant };

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

// 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;
 
  // 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 DelayedOnModes(byte relay) {
  static unsigned long startTime=now();

  if ( (startTime==LastStart) && 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 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 50

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

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

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

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

// Only start the cloud effect after this setting
// In this example, start cloud after noon
#define Start_Cloud_After NumMins(10,00)

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

// 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 (InternalMemory.read(Mem_B_EnableStorm)) return;
 
  if (cloudchance)
  {
    if (ReefAngel.Relay.isMaskOff(LED_STORM))      // Change this to whatever port you want to use as a trigger.
    {
      cloudstart = NumMins(hour(), minute());
      ReefAngel.Relay.Auto(LED_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(LED_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[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 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
Post Reply