Page 1 of 2

Re: Let's Build Troubadour11's Code

Posted: Thu Oct 02, 2014 1:31 pm
by troubadour11
Hi everyone,

So it's been awhile since I was last in here. I spent some time messing with the actual aquarium rather then the code stuff for the RA unit lately.

I did get the custom menu I was working on previously up and running. So I can easily test my storm function when I try to develop it further. Right now I have it working where it's firing the clouds and everything randomly. I had to step back a bit and remove the delay's and offsets. When I was trying to get that, one fixture wouldn't run the cloud sim at all. So I rolled back to having them both run correctly at the same time and will work on building in the offset more in the future.

Right now I do have a couple questions that maybe someone could help me solve.

The first place I could use a little help is just getting a couple relay's on the power strip turning on/off according to my time schedule. When I looked in the example code for switching relays, it looked like the command

Code: Select all

ReefAngel.Relay.On( Port3 );
was what I needed to use. But I haven't been able to get Ports 3 and 4 to turn on/off via the code. I've been manually toggling them with the android app each day. I would love to get the code running it to remove a twice daily chore from my schedule.

Here is the beginning of my code from the void setup() and into the beginning of the void loop(). The Refugium and Phyto lights are on Ports 3 and 4 as noted in the set up section. And the if/else statement to toggle the ports on/off according to time is basically the first bit of code in my loop. There is more code beyond what I've pasted here, but that all has to do with dimming the LEDs and the storm function mostly.

Code: Select all

void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.
  
    // Initialize the custom menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    ReefAngel.AddStandardMenu();  // Add Standard Menu
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen
 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit | Port7Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = UV Sterilizer
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
 
     //Sets values of ALL lighting channels to 0% on Start Up correctly, I think.
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
     ReefAngel.PWM.SetChannel(LEDPWM4,0);
     ReefAngel.PWM.SetChannel(LEDPWM5,0);
     // I believe using this got rid of my bright flash to 100% when I uploaded new code. Specifically setting the Standard Daylight/Actinic

    // Adds the date and time menu to controller so I can change for Daylight Savings Time. **Takes up memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app. eventually
    //ReefAngel.AddDateTimeMenu();
         
    ////// Place additional initialization code above here //////
}

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
   
    //Set up for Refugium and Phytoplankton light cycle
 if ((NumMins(hour(now()),minute(now())) > NumMins(21,30)) && (NumMins(hour(now()),minute(now())) < NumMins(9,30)))
      {
        ReefAngel.Relay.On( Port3 );
        ReefAngel.Relay.On( Port4 );
      }
      else
      {
        ReefAngel.Relay.Off( Port3 );
        ReefAngel.Relay.Off( Port4 );
      }
I currently have Ports 3 & 4 listed as "Always On" in the setup section in the above code and the lights show as always off on the android app. I can manually turn them on/off still with the phone as an override. I recently had the same above code with Ports 3 & 4 commented out in the set up "always on" section. This made no difference and the ports always stayed in the off position unless manually changed via the app.

I believe I previously tried reversing the time and the less-than/greater-than signs in the if/else statement. But that didn't seem to work and was awhile ago now. I've just been manually changing it since.

Any ideas why I'm failing to get this command to work?

The second thing I've been trying to figure out is why my moonlight drivers don't turn down to 0% when not in use. I have them coded in a similar way to my other lights which all show 0% at the appropriate times. However my 2 moonlight drivers turn to 2% and 3% whenever they are not running the lights.

Here is the section of code in my loop that deals with the LED drivers:

Code: Select all

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:30 p.m. and 11:00 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV -
    // - Parabola dimming 11%-85%-11%
//_____________________________________________________________________________________________________________   
 if ((NumMins(hour(now()),minute(now())) > NumMins(11,00)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
      {
         RDT_ActinicPWMValue=PWMParabola(11,00,20,30,11,85,RDT_ActinicPWMValue);
      }
      else
      {
         RDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 9:00 p.m. and 11:15 p.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV -
    // - Parabola dimming 11%-85%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(11,15)) && (NumMins(hour(now()),minute(now())) < NumMins(21,00)))
     {
        LDT_ActinicPWMValue=PWMParabola(11,15,21,00,11,85,LDT_ActinicPWMValue);
      }
      else
      {
        LDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 8:30 p.m. and 11:25 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color -
    // - Parabola dimming 11%-60%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(11,25)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_DaylightPWMValue=PWMParabola(11,25,20,30,11,60,RDT_DaylightPWMValue);
      }
      else
      {
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 9:00 p.m. and 11:40 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color -
    // - Parabola dimming 11%-60%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(11,40)) && (NumMins(hour(now()),minute(now())) < NumMins(21,00)))
     {
        LDT_DaylightPWMValue=PWMParabola(11,40,21,00,11,60,LDT_DaylightPWMValue);
      }
      else
      {
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 
 
    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
//_________________________________________________________________________________________________



//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                                   //// Set MoonLights Cycles ////
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//_________________________________________________________________________________________________________________________________________________
    // - Turns RDT Moonlights to 0% power between 11:15 p.m. and 9:15 p.m. to conserve energy.
    // - Slope dimming 10% up to 1/5 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 30 minutes.
    // - Dimming Expansion - LEDPWM3 - RDT Moonlight - on @ 9:15 p.m. - off @ 11:15 p.m.
//_________________________________________________________________________________________________________________________________________________
if ((NumMins(hour(now()),minute(now())) > NumMins(23,15)) && (NumMins(hour(now()),minute(now())) < NumMins(11,45)))
     {
       ReefAngel.PWM.SetChannel(LEDPWM3,0);
      }
      else if ((NumMins(hour(now()),minute(now())) > NumMins(11,40)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        ReefAngel.PWM.SetChannel(LEDPWM3,PWMParabola(11,40,20,30,10,30,LEDPWM3));
      }
      else
      {
       ReefAngel.PWM.SetChannel(LEDPWM3,PWMSlope(21,15,23,15,10,MoonPhase()/5+10,30,LEDPWM3));
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_________________________________________________________________________________________________________________________________________________
    // - Turns LDT Moonlights to 0% power between 11:30 p.m. and 9:30 p.m. to conserve energy
    // - Slope dimming - 10% up to 1/5 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 30 minutes
    // - Dimming Expansion - LEDPWM2 - LDT Moonlight - on @ 9:30 p.m. - off @ 11:30 a.m.
//_________________________________________________________________________________________________________________________________________________
if ((NumMins(hour(now()),minute(now())) > NumMins(23,30)) && (NumMins(hour(now()),minute(now())) < NumMins(21,35)))
     {
       ReefAngel.PWM.SetChannel(LEDPWM2,0);
      }
       else if ((NumMins(hour(now()),minute(now())) > NumMins(11,55)) && (NumMins(hour(now()),minute(now())) < NumMins(21,00)))
     {
        ReefAngel.PWM.SetChannel(LEDPWM2,PWMParabola(11,55,21,00,10,30,LEDPWM2));
      }
      else
      {
       ReefAngel.PWM.SetChannel(LEDPWM2,PWMSlope(21,30,23,30,10,MoonPhase()/5+10,30,LEDPWM2));
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ////// Place your custom code above here //////
I recently added in the "else if" statements to run my moonlight channels as additional actinic supplements during the day time. So my noted headers are not completely accurate for the times that I have programmed right now. But the moonlight drivers have been running during the day and moonlight times correctly. It is just when they are supposed to be turned off, they don't turn all the way to 0% like my other drivers that I am trying to solve.

My next step beyond asking for help is to try to create some variables for the moonlights similar to the variables I use for the daylight drivers. Then set the values that way and see if it fixes the issue. But I've been stuck on this for a bit now and thought someone might have a quick answer for me that would let me move on to other sections of the code.

Thanks :-)

Oh, and here is the current version of my entire code all in one spot if you're interested in seeing that:

Code: Select all

//*************************************************************************************************************
//Start of PWM Expansion Code Header

//This is just how we are going to reference the PWM expansion ports within the code.
//You can change the labels if you would like, just as long as they are changed all throughout the code too.
                  //Standard PowerRelay PWM Daylight = L-DT White/Color
                  //Standard PowerRelay PWM Actinic = L-DT Blue/UV
#define LEDPWM0 0 // R-DT = White/Color
#define LEDPWM1 1 // R-DT = Blue/UV
#define LEDPWM2 2 // L-DT = MoonLight
#define LEDPWM3 3 // R-DT = Moonlight
#define LEDPWM4 4 // - EMPTY -
#define LEDPWM5 5 // - EMPTY -

//Initial values to all 6 channels at startup. They will always be 0.
byte PWMChannel[]={0,0,0,0,0,0};

//End of PWM Expansion Code Header
//*************************************************************************************************************

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <ReefAngel.h>

// - Creates a variable to control a "force cloud function" inside a menu.
boolean ForceCloud=false;

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
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";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
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();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

byte LDT_DaylightPWMValue=0;
byte LDT_ActinicPWMValue=0;
byte RDT_DaylightPWMValue=0;
byte RDT_ActinicPWMValue=0;

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.
  
    // Initialize the custom menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    ReefAngel.AddStandardMenu();  // Add Standard Menu
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen
 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit | Port7Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = UV Sterilizer
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up correctly, I think.
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
     ReefAngel.PWM.SetChannel(LEDPWM4,0);
     ReefAngel.PWM.SetChannel(LEDPWM5,0);
     // I believe using this got rid of my bright flash to 100% when I uploaded new code. Specifically setting the Standard Daylight/Actinic
//****************************************************************************************************************************************************************

    // Adds the date and time menu to controller so I can change for Daylight Savings Time. **Takes up memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app. eventually
    //ReefAngel.AddDateTimeMenu();
         
    ////// Place additional initialization code above here //////
}

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
   
    //Set up for Refugium and Phytoplankton light cycle
 if ((NumMins(hour(now()),minute(now())) > NumMins(21,30)) && (NumMins(hour(now()),minute(now())) < NumMins(9,30)))
      {
        ReefAngel.Relay.On( Port3 );
        ReefAngel.Relay.On( Port4 );
      }
      else
      {
        ReefAngel.Relay.Off( Port3 );
        ReefAngel.Relay.Off( Port4 );
      }

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:30 p.m. and 11:00 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV -
    // - Parabola dimming 11%-85%-11%
//_____________________________________________________________________________________________________________   
 if ((NumMins(hour(now()),minute(now())) > NumMins(11,00)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
      {
         RDT_ActinicPWMValue=PWMParabola(11,00,20,30,11,85,RDT_ActinicPWMValue);
      }
      else
      {
         RDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 9:00 p.m. and 11:15 p.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV -
    // - Parabola dimming 11%-85%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(11,15)) && (NumMins(hour(now()),minute(now())) < NumMins(21,00)))
     {
        LDT_ActinicPWMValue=PWMParabola(11,15,21,00,11,85,LDT_ActinicPWMValue);
      }
      else
      {
        LDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 8:30 p.m. and 11:25 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color -
    // - Parabola dimming 11%-60%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(11,25)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_DaylightPWMValue=PWMParabola(11,25,20,30,11,60,RDT_DaylightPWMValue);
      }
      else
      {
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 9:00 p.m. and 11:40 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color -
    // - Parabola dimming 11%-60%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(11,40)) && (NumMins(hour(now()),minute(now())) < NumMins(21,00)))
     {
        LDT_DaylightPWMValue=PWMParabola(11,40,21,00,11,60,LDT_DaylightPWMValue);
      }
      else
      {
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

 
 
    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
//_________________________________________________________________________________________________



//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                                   //// Set MoonLights Cycles ////
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//_________________________________________________________________________________________________________________________________________________
    // - Turns RDT Moonlights to 0% power between 11:15 p.m. and 9:15 p.m. to conserve energy.
    // - Slope dimming 10% up to 1/5 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 30 minutes.
    // - Dimming Expansion - LEDPWM3 - RDT Moonlight - on @ 9:15 p.m. - off @ 11:15 p.m.
//_________________________________________________________________________________________________________________________________________________
if ((NumMins(hour(now()),minute(now())) > NumMins(23,15)) && (NumMins(hour(now()),minute(now())) < NumMins(11,45)))
     {
       ReefAngel.PWM.SetChannel(LEDPWM3,0);
      }
      else if ((NumMins(hour(now()),minute(now())) > NumMins(11,40)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        ReefAngel.PWM.SetChannel(LEDPWM3,PWMParabola(11,40,20,30,10,30,LEDPWM3));
      }
      else
      {
       ReefAngel.PWM.SetChannel(LEDPWM3,PWMSlope(21,15,23,15,10,MoonPhase()/5+10,30,LEDPWM3));
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_________________________________________________________________________________________________________________________________________________
    // - Turns LDT Moonlights to 0% power between 11:30 p.m. and 9:30 p.m. to conserve energy
    // - Slope dimming - 10% up to 1/5 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 30 minutes
    // - Dimming Expansion - LEDPWM2 - LDT Moonlight - on @ 9:30 p.m. - off @ 11:30 a.m.
//_________________________________________________________________________________________________________________________________________________
if ((NumMins(hour(now()),minute(now())) > NumMins(23,30)) && (NumMins(hour(now()),minute(now())) < NumMins(21,35)))
     {
       ReefAngel.PWM.SetChannel(LEDPWM2,0);
      }
       else if ((NumMins(hour(now()),minute(now())) > NumMins(11,55)) && (NumMins(hour(now()),minute(now())) < NumMins(21,00)))
     {
        ReefAngel.PWM.SetChannel(LEDPWM2,PWMParabola(11,55,21,00,10,30,LEDPWM2));
      }
      else
      {
       ReefAngel.PWM.SetChannel(LEDPWM2,PWMSlope(21,30,23,30,10,MoonPhase()/5+10,30,LEDPWM2));
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ////// Place your custom code above here //////
  
    //Connects Reef Angel Head Unit to the Portal through WiFi Unit.
    ReefAngel.Portal("troubadour11");
    // This should always be the last line
    ReefAngel.ShowInterface();
}


//************************************************************************************************************************************************************************************************************************
  // - My lighting is set up as:
  //      - LDT (Left Display Tank) fixture on the "Standard" 2 (PWM signal) slots on my power relay box.
  //      - RDT (Right Display Tank) fixture on channels 0=daylight and 1=actinic (PWM signal) of the Dimming Expansion.
  //      - Moonlights = channels 2 and 3 of the PWM expansion. These drivers are Analog and the jumper pins have been set for 0-10v Analog in Dimming Expansion.
 
 // - I'd like to calculate the cloud effect for the LDT lights using the "Weather Sim for Standard PWM" code which I think
 //   I got copied, set up, and working correctly for the 1 fixture on the Standard Ports.
 //                 - Then find a way to feed those values into the RDT lights with a "Random Sample" of + or - a small time offset for the cloud effect on the RDT
 //                                - This way I could randomly generate a weather sim, and randomly have the cloud move Right-to-Left or Left-to-Right each time a cloud occurs

//                  - Lightning effect could either be off set with a similar way with +/- value OR left sync'd to LDT lightning (would need to test what looks good).
//                  - Or possibly more or less lightning per fixture during cloud.
//*************************************************************************************************************************************************************************************************************************

// Random Cloud/Thunderstorm effects function
void CheckCloud()
{

  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Change the values below to customize your cloud/storm effect

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
  // I should create a random variable here to randomize the days it has clouds?
#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 12

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

  // Only start the cloud effect after this setting
     // In this example, start could happen after 11:30am
#define Start_Cloud_After NumMins(13,00)

  // Always end the cloud effect before this setting
     // In this example, end must be before 6:45pm
#define End_Cloud_Before NumMins(19,45)

  // Percentage chance of lightning happening 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 results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

    //#define printdebug // Uncomment this for debug print on Serial Monitor window
  //#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.


  // Change the values above to customize your cloud/storm effect
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Do not change anything below here

  static byte cloudchance = 255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  //made individual lightningchance for each fixture
  static byte LDTlightningchance=0;
  //static byte RDTlightningchance=0;
  static byte cloudindex=0;
  //made separate lightning status for each fixture
  static byte LDTlightningstatus=0;
  //static byte RDTlightningstatus=0;
  static int LastNumMins=0;
//********************************************************************************************************************************************************
  // - I created this value to use as +/- offset value to make clouds pass left-to-right or vice-versa.
  //static int RDTcloudstartOffset=0;

  // - I added this to use as a randomly chosen value between (-)x to y.
  // - This will be added to "cloudstart" in order to determine the start time of the RDT cloud effect.
  //static byte RDTcloudstart=0;

  // - I added this to create a random sample between 80% and 100% dimming power for "random" lightning intensity effect on each fixture.
  static byte LDT_LightningFlashPower=0;
  static byte RDT_LightningFlashPower=0;

  // - I added these to create a variable to randomize when and how long each lightning strike happens for each cloud.
  //LDTlightningRandomStart=0;
  //LDTlightningLength=0;
  //RDTlightningRandomStart=0;
  //RDTlightningLength=0;
//********************************************************************************************************************************************************


  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

#ifdef forcecloudcalculation
    if (cloudchance==255)
#else
    if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
    {
      //Pick a random number between 0 and 99
      cloudchance=random(100);
      // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
      if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
      // Check if today is day for clouds.
      if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
      // If we have cloud today
      if (cloudchance)
      {
        // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
        numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
        // pick the time that the first cloud will start
        // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
        cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));

//********************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//********************************************************************************************************************************************************

        // 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
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

// Force Cloud from Menu Function jumps to here to start cloud effect immediately for a show!
if (ForceCloud)
{
ForceCloud=false;
cloudchance=1;
cloudduration=8;
LDTlightningchance=1;
//RDTlightningchance=1;
cloudstart=NumMins(hour(),minute())+2;
//RDTcloudstart=NumMins(hour(),minute())+1;
//RDTcloudstartOffset=random(-1,1);
//RDTcloudstart=cloudstart+RDTcloudstartOffset;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // - Controls Cloud and Lightning Effect timings for both fixtures
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  if (cloudchance)
  {
    //is it time for cloud yet over Left Display Tank?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      // - Dims LDT to create cloud and back up to Daylight Parabola. - maybe change to 11 in order to make minimum cloud power 11% so the lights don't click off.
      RDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,10,180);
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,10,180);
      RDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,50,180);
      LDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,50,180);

      // To adjust possible length of time of lightning strike, Change the "second()< 5 " ... where 5 is the # of seconds of lightning during cloud.
      // and the "minute())=(cloudstart_(cloudduration/2)))" sets start time... dividing by 2 is exactly half way through cloud to trigger 5 seconds lightning.
      // if there is lightning and current time is 1/2 way through cloud, but less then 1/2 way through cloud + 5 seconds

     // if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/LDTlightningRandomStart))) && second()<LDTlightningLength)
      if (LDTlightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
      {
        if (random(100)<20) LDTlightningstatus=1;
        else LDTlightningstatus=0;
        if (LDTlightningstatus)
        {
               // - This will correctly flash lightning up to 100% power every lightning strike.
          //LDT_DaylightPWMValue=100;
          //LDT_ActinicPWMValue=100;
//_______________________________________________________________________________________________________
          // - Trying to make a random sample between, say 80% & 100% for the brightness of the lightning?
          // - If there will be lightning, pick a random number between 80 and 100 for the lightning power.
          LDT_LightningFlashPower=random(80,100);
          RDT_LightningFlashPower=random(80,100);

          RDT_DaylightPWMValue=RDT_LightningFlashPower;
          RDT_ActinicPWMValue=RDT_LightningFlashPower;
          LDT_DaylightPWMValue=LDT_LightningFlashPower;
          LDT_ActinicPWMValue=LDT_LightningFlashPower;
//_______________________________________________________________________________________________________
        }
        else
        {
          // - Changed these values from 0 to 11 so at end of lightning cycle, the go to min. 11% power
          RDT_DaylightPWMValue=11;
          RDT_ActinicPWMValue=50;
          LDT_DaylightPWMValue=11;
          LDT_ActinicPWMValue=50;
//------------------------------------------------------------------
        }
        delay(1);
      }
    }
  }
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    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);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  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));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      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));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
  long n=elapsedSecsToday(now());
  cstart*=60;
  cend*=60;
  if (n<cstart) return PWMStart;
  if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
  if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
  if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
  if (n>cend) return PWMStart;
  //End Cloud & Lighting
}

Re: Let's Build Troubadour11's Code

Posted: Thu Oct 02, 2014 2:02 pm
by lnevo
First off great job on getting so far.

The issue with your moonlights is that you don't need all that if/then/else. Your first if is broken. I'll get back to that in a second..

Since you have different schedules you can nest them. The last argument in the slope and parabola functions is the previous value. You are passing the channel number which is 2/3 respectively. Replace that value with the second slope/parabola function. Since they each have their own time schedule they will work in tandem. No if/then/else required.

Now the reason your if is broken is because your asking RA if the time is later than 23:39 AND earlier than 21:35. Thats an impossible time since it can never meet both requirements. You can change it to || which is an OR question. This is a common mistake.

Finally for your always on ports...it may not sound intuitive but you will use the ReefAngel.StandardLight() function which takes the relay,onhour,onminute,offhour,offminute arguments to know when you want them to go on and then off.

Let me know if any if that makes sense :)

Re: Let's Build Troubadour11's Code

Posted: Fri Oct 03, 2014 1:58 pm
by troubadour11
Thanks for the help lnevo!

I think I have an idea of what you're getting at for it all.

First off, to solve the problem with my always on ports for the Refugium and Phyto lights. Does this look correct for toggling them on/off by time?

Code: Select all

    //Set up for Refugium and Phytoplankton light cycle

    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)
     //Turns Refugium Light plugged into port3 on at 9:30 PM and off at 9:30 AM.
     ReefAngel.StandardLight(Port3,21,30,9,30);
     //Turns Phyto Light plugged into port4 on at 9:30 PM and off at 9:30 AM.
     ReefAngel.StandardLight(Port4,21,30,9,30);
And if I am understanding correctly. To run my Moonlight channels during the day, then night, and turn off outside of those hours. Is this how you're recommending setting them up by nesting them? Does the nesting of the functions look like I have the concept down correctly?

*note, I created the LDT_MoonLightPWMValue variable yesterday and use whatever value stored there to set the channel. More on how that was implemented at the bottom. But basically the same as all my other channels.

Code: Select all

LDT_MoonLightPWMValue=PWMParabola(11,40,20,30,10,30,PWMSlope(21,15,23,15,10,MoonPhase()/5+10,30,LDT_MoonLightPWMValue=0))));
The above code should run this channel from 11:40 AM until 8:30 PM from 10%-30%-10%. Then turn them on from 9:15 PM until 11:15 PM using the slope function. Then by inserting "LDT_MoonLightPWMValue=0" into the last value of the slope function, it will turn the driver to 0% outside of those times?

And out of curiosity. Is there an advantage to coding it this way by nesting them over using the if/then/else method? Does it reduce memory because it's less/smaller code or something? The timing of the MoonLights were running correctly with the if/then/else, it was just during the "off" hours I was passing the (2 and 3) on from the labels which I had in the last value. So now it makes sense why they were sitting at 2% and 3% at least!

Yesterday before I saw your post helping me out. I had used my lunch break to re-work the moonlight section of code to use a variable similar to how I did the daylight and actinics drivers. I haven't uploaded the code as I'm planning to implement the changes you've helped me with and try it out this weekend. But here is how I did re-work that section before your tip on nesting the functions.

Code: Select all

//_________________________________________________________________________________________
    // - Turns RDT Moonlights to 0% power between 11:15 p.m. and 9:15 p.m. to conserve energy.
    // - Slope dimming 10% up to 1/5 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 30 minutes.
    // - Dimming Expansion - LEDPWM3 - RDT Moonlight - on @ 9:15 p.m. - off @ 11:15 p.m.
//_________________________________________________________________________________________

if ((NumMins(hour(now()),minute(now())) > NumMins(23,15)) && (NumMins(hour(now()),minute(now())) < NumMins(11,45)))
     {
       RDT_MoonLightPWMValue=0;

       //ReefAngel.PWM.SetChannel(LEDPWM3,0);
      }
      else if ((NumMins(hour(now()),minute(now())) > NumMins(11,40)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_MoonLightPWMValue=PWMParabola(11,40,20,30,10,30,RDT_MoonLightValue));

        //ReefAngel.PWM.SetChannel(LEDPWM3,PWMParabola(11,40,20,30,10,30,LEDPWM3));
      }
      else
      {
       RDT_MoonLightPWMValue=PWMSlope(21,15,23,15,10,MoonPhase()/5+10,30,RDT_MoonLightValue));

       //ReefAngel.PWM.SetChannel(LEDPWM3,PWMSlope(21,15,23,15,10,MoonPhase()/5+10,30,LEDPWM3));
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//_________________________________________________________________________________________
    // - Turns LDT Moonlights to 0% power between 11:30 p.m. and 9:30 p.m. to conserve energy
    // - Slope dimming - 10% up to 1/5 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 30 minutes
    // - Dimming Expansion - LEDPWM2 - LDT Moonlight - on @ 9:30 p.m. - off @ 11:30 a.m.
//_________________________________________________________________________________________
if ((NumMins(hour(now()),minute(now())) > NumMins(23,30)) && (NumMins(hour(now()),minute(now())) < NumMins(21,35)))
     {
       LDT_MoonLightPWMValue=0;

       //ReefAngel.PWM.SetChannel(LEDPWM2,0);
      }
       else if ((NumMins(hour(now()),minute(now())) > NumMins(11,55)) && (NumMins(hour(now()),minute(now())) < NumMins(21,00)))
     {
        LDT_MoonLightPWMValue=PWMParabola(11,40,20,30,10,30,LDT_MoonLightValue));

        //ReefAngel.PWM.SetChannel(LEDPWM2,PWMParabola(11,55,21,00,10,30,LEDPWM2));
      }
      else
      {
       LDT_MoonLightPWMValue=PWMSlope(21,15,23,15,10,MoonPhase()/5+10,30,LDT_MoonLightValue));

       //ReefAngel.PWM.SetChannel(LEDPWM2,PWMSlope(21,30,23,30,10,MoonPhase()/5+10,30,LEDPWM2));
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM2,LDT_MoonLightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM3,RDT_MoonLightPWMValue);
//_________________________________________________________________________________________
This was my thought on how to fix it before I got the feed back. I created the new variables for LDT_MoonLightPWMValue and RDT_MoonLightPWMValue and then used the if/then/else statement to set the values of the new variable and then applied those values to their channels like I had with the RDT_daylight/actinics that are hooked up to the first 2 ports of the dimming expansion.

Anyway, that was just me trying to problem solve it over lunch. If the first two sections of new code look correct, then hopefully I should be good to go for now if I implemented your recommendations correctly. I would appreciate it if you have a moment to call me out if I did something completely wrong or misunderstood.

Thanks again for the help! It is very much appreciated!

Re: Let's Build Troubadour11's Code

Posted: Thu Oct 09, 2014 2:15 pm
by troubadour11
So I don't have much time to go into details here. But after a bit of trouble shooting on the above code. I got things working better over last weekend.

The Refugium and Phyto lights turn on/off correctly. And the Moonlights are running on their correct time schedules with the new nested code.

Here's the latest version that I have loaded up to the head unit and running for the last few days.

Code: Select all

//*************************************************************************************************************
//Start of PWM Expansion Code Header

//This is just how we are going to reference the PWM expansion ports within the code.
//You can change the labels if you would like, just as long as they are changed all throughout the code too.
                  //Standard PowerRelay PWM Daylight = L-DT White/Color
                  //Standard PowerRelay PWM Actinic = L-DT Blue/UV
#define LEDPWM0 0 // R-DT = White/Color
#define LEDPWM1 1 // R-DT = Blue/UV
#define LEDPWM2 2 // L-DT = MoonLight
#define LEDPWM3 3 // R-DT = Moonlight
#define LEDPWM4 4 // - EMPTY -
#define LEDPWM5 5 // - EMPTY -

//Initial values to all 6 channels at startup. They will always be 0.
byte PWMChannel[]={0,0,0,0,0,0};

//End of PWM Expansion Code Header
//*************************************************************************************************************

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <ReefAngel.h>

// - Creates a variable to control a "force cloud function" inside a menu.
boolean ForceCloud=false;

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
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";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
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();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

byte LDT_DaylightPWMValue=0;
byte LDT_ActinicPWMValue=0;
byte LDT_MoonLightANALOGValue=0;
byte RDT_DaylightPWMValue=0;
byte RDT_ActinicPWMValue=0;
byte RDT_MoonLightANALOGValue=0;

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.
 
    // Initialize the custom menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    ReefAngel.AddStandardMenu();  // Add Standard Menu
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen
 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit | Port7Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = UV Sterilizer
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up correctly, I think.
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
     ReefAngel.PWM.SetChannel(LEDPWM4,0);
     ReefAngel.PWM.SetChannel(LEDPWM5,0);
     // I believe using this got rid of my bright flash to 100% when I uploaded new code. Specifically setting the Standard Daylight/Actinic
//****************************************************************************************************************************************************************

    // Adds the date and time menu to controller so I can change for Daylight Savings Time. **Takes up memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app. eventually
    //ReefAngel.AddDateTimeMenu();
         
    ////// Place additional initialization code above here //////
}

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
//-----------------------------------------------------------------------------------------------------------------------------------------------------//   
    //Set up for Refugium and Phytoplankton light cycle

    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)
     //Turns Refugium Light plugged into port3 on at 9:30 pm and off at 9:30 am.
     ReefAngel.StandardLights(Port3,21,30,9,30);
     //Turns Phyto Light plugged into port4 on at 9:30 pm and off at 9:30 am.
     ReefAngel.StandardLights(Port4,21,30,9,30);

//if ((NumMins(hour(now()),minute(now())) > NumMins(21,30)) && (NumMins(hour(now()),minute(now())) < NumMins(9,30)))
//      {
//        ReefAngel.Relay.On( Port3 );
//        ReefAngel.Relay.On( Port4 );
//      }
//      else
//      {
//        ReefAngel.Relay.Off( Port3 );
//        ReefAngel.Relay.Off( Port4 );
//      }

//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:30 p.m. and 11:00 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV -
    // - Parabola dimming 11%-85%-11%
//_____________________________________________________________________________________________________________   
 if ((NumMins(hour(now()),minute(now())) > NumMins(11,00)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
      {
         RDT_ActinicPWMValue=PWMParabola(11,00,20,20,11,85,RDT_ActinicPWMValue);
      }
      else
      {
         RDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 9:00 p.m. and 11:15 p.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV -
    // - Parabola dimming 11%-85%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(11,15)) && (NumMins(hour(now()),minute(now())) < NumMins(21,00)))
     {
        LDT_ActinicPWMValue=PWMParabola(11,15,20,50,11,85,LDT_ActinicPWMValue);
      }
      else
      {
        LDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 8:30 p.m. and 11:25 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color -
    // - Parabola dimming 11%-60%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(11,25)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_DaylightPWMValue=PWMParabola(11,25,20,40,11,60,RDT_DaylightPWMValue);
      }
      else
      {
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 9:00 p.m. and 11:40 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color -
    // - Parabola dimming 11%-60%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(11,40)) && (NumMins(hour(now()),minute(now())) < NumMins(21,00)))
     {
        LDT_DaylightPWMValue=PWMParabola(11,40,21,10,11,60,LDT_DaylightPWMValue);
      }
      else
      {
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                                   //// Set MoonLights Cycles ////
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    LDT_MoonLightANALOGValue=PWMParabola(11,40,21,00,10,30,PWMSlope(21,20,23,15,10,MoonPhase()/5+10,30,LDT_MoonLightANALOGValue=0));
    RDT_MoonLightANALOGValue=PWMParabola(11,25,20,30,10,30,PWMSlope(21,15,23,15,10,MoonPhase()/5+10,30,RDT_MoonLightANALOGValue=0));

//_________________________________________________________________________________________________________________________________________________
    // - Turns RDT Moonlights to 0% power between 11:15 p.m. and 9:15 p.m. to conserve energy.
    // - Slope dimming 10% up to 1/5 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 30 minutes.
    // - Dimming Expansion - LEDPWM3 - RDT Moonlight - on @ 9:15 p.m. - off @ 11:15 p.m.
//_________________________________________________________________________________________________________________________________________________
//if ((NumMins(hour(now()),minute(now())) > NumMins(23,15)) && (NumMins(hour(now()),minute(now())) < NumMins(11,45)))
//     {
//       RDT_MoonLightANALOGValue=0;
//       //ReefAngel.PWM.SetChannel(LEDPWM3,0);
//      }
//      else if ((NumMins(hour(now()),minute(now())) > NumMins(11,40)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
//     {
//        RDT_MoonLightANALOGValue=PWMParabola(11,40,20,30,10,30,RDT_MoonLightValue));
        //ReefAngel.PWM.SetChannel(LEDPWM3,PWMParabola(11,40,20,30,10,30,LEDPWM3));
//      }
//      else
//      {
//       RDT_MoonLightANALOGValue=PWMSlope(21,15,23,15,10,MoonPhase()/5+10,30,RDT_MoonLightValue));
       //ReefAngel.PWM.SetChannel(LEDPWM3,PWMSlope(21,15,23,15,10,MoonPhase()/5+10,30,LEDPWM3));
//      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_________________________________________________________________________________________________________________________________________________
    // - Turns LDT Moonlights to 0% power between 11:30 p.m. and 9:30 p.m. to conserve energy
    // - Slope dimming - 10% up to 1/5 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 30 minutes
    // - Dimming Expansion - LEDPWM2 - LDT Moonlight - on @ 9:30 p.m. - off @ 11:30 a.m.
//_________________________________________________________________________________________________________________________________________________
//if ((NumMins(hour(now()),minute(now())) > NumMins(23,30)) && (NumMins(hour(now()),minute(now())) < NumMins(21,35)))
//     {
//       LDT_MoonLightANALOGValue=0;
       //ReefAngel.PWM.SetChannel(LEDPWM2,0);
//      }
//       else if ((NumMins(hour(now()),minute(now())) > NumMins(11,55)) && (NumMins(hour(now()),minute(now())) < NumMins(21,00)))
//     {
//        LDT_MoonLightANALOGValue=PWMParabola(11,40,20,30,10,30,LDT_MoonLightValue));
        //ReefAngel.PWM.SetChannel(LEDPWM2,PWMParabola(11,55,21,00,10,30,LEDPWM2));
//      }
//      else
//      {
//       LDT_MoonLightANALOGValue=PWMSlope(21,15,23,15,10,MoonPhase()/5+10,30,LDT_MoonLightValue));
       //ReefAngel.PWM.SetChannel(LEDPWM2,PWMSlope(21,30,23,30,10,MoonPhase()/5+10,30,LEDPWM2));
//      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM2,LDT_MoonLightANALOGValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM3,RDT_MoonLightANALOGValue);
//_________________________________________________________________________________________________

    ////// Place your custom code above here //////
 
    //Connects Reef Angel Head Unit to the Portal through WiFi Unit.
    ReefAngel.Portal("troubadour11");
    // This should always be the last line
    ReefAngel.ShowInterface();
}


//************************************************************************************************************************************************************************************************************************
  // - My lighting is set up as:
  //      - LDT (Left Display Tank) fixture on the "Standard" 2 (PWM signal) slots on my power relay box.
  //      - RDT (Right Display Tank) fixture on channels 0=daylight and 1=actinic (PWM signal) of the Dimming Expansion.
  //      - Moonlights = channels 2 and 3 of the PWM expansion. These drivers are Analog and the jumper pins have been set for 0-10v Analog in Dimming Expansion.
 
 // - I'd like to calculate the cloud effect for the LDT lights using the "Weather Sim for Standard PWM" code which I think
 //   I got copied, set up, and working correctly for the 1 fixture on the Standard Ports.
 //                 - Then find a way to feed those values into the RDT lights with a "Random Sample" of + or - a small time offset for the cloud effect on the RDT
 //                                - This way I could randomly generate a weather sim, and randomly have the cloud move Right-to-Left or Left-to-Right each time a cloud occurs

//                  - Lightning effect could either be off set with a similar way with +/- value OR left sync'd to LDT lightning (would need to test what looks good).
//                  - Or possibly more or less lightning per fixture during cloud.
//*************************************************************************************************************************************************************************************************************************

// Random Cloud/Thunderstorm effects function
void CheckCloud()
{

  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Change the values below to customize your cloud/storm effect

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
  // I should create a random variable here to randomize the days it has clouds?
#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 12

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

  // Only start the cloud effect after this setting
     // In this example, start could happen after 11:30am
#define Start_Cloud_After NumMins(13,00)

  // Always end the cloud effect before this setting
     // In this example, end must be before 6:45pm
#define End_Cloud_Before NumMins(19,45)

  // Percentage chance of lightning happening 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 results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

    //#define printdebug // Uncomment this for debug print on Serial Monitor window
  //#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.


  // Change the values above to customize your cloud/storm effect
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Do not change anything below here

  static byte cloudchance = 255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  //made individual lightningchance for each fixture
  static byte LDTlightningchance=0;
  //static byte RDTlightningchance=0;
  static byte cloudindex=0;
  //made separate lightning status for each fixture
  static byte LDTlightningstatus=0;
  //static byte RDTlightningstatus=0;
  static int LastNumMins=0;
//********************************************************************************************************************************************************
  // - I created this value to use as +/- offset value to make clouds pass left-to-right or vice-versa.
  //static int RDTcloudstartOffset=0;

  // - I added this to use as a randomly chosen value between (-)x to y.
  // - This will be added to "cloudstart" in order to determine the start time of the RDT cloud effect.
  //static byte RDTcloudstart=0;

  // - I added this to create a random sample between 80% and 100% dimming power for "random" lightning intensity effect on each fixture.
  static byte LDT_LightningFlashPower=0;
  static byte RDT_LightningFlashPower=0;

  // - I added these to create a variable to randomize when and how long each lightning strike happens for each cloud.
  //LDTlightningRandomStart=0;
  //LDTlightningLength=0;
  //RDTlightningRandomStart=0;
  //RDTlightningLength=0;
//********************************************************************************************************************************************************


  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

#ifdef forcecloudcalculation
    if (cloudchance==255)
#else
    if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
    {
      //Pick a random number between 0 and 99
      cloudchance=random(100);
      // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
      if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
      // Check if today is day for clouds.
      if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
      // If we have cloud today
      if (cloudchance)
      {
        // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
        numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
        // pick the time that the first cloud will start
        // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
        cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));

//********************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//********************************************************************************************************************************************************

        // 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
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

// Force Cloud from Menu Function jumps to here to start cloud effect immediately for a show!
if (ForceCloud)
{
ForceCloud=false;
cloudchance=1;
cloudduration=8;
LDTlightningchance=1;
//RDTlightningchance=1;
cloudstart=NumMins(hour(),minute())+2;
//RDTcloudstart=NumMins(hour(),minute())+1;
//RDTcloudstartOffset=random(-1,1);
//RDTcloudstart=cloudstart+RDTcloudstartOffset;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // - Controls Cloud and Lightning Effect timings for both fixtures
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  if (cloudchance)
  {
    //is it time for cloud yet over Left Display Tank?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      // - Dims LDT to create cloud and back up to Daylight Parabola. - maybe change to 11 in order to make minimum cloud power 11% so the lights don't click off.
      RDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,10,180);
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,10,180);
      RDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,20,180);
      LDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,20,180);
      RDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_MoonLightANALOGValue,11,180);
      LDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_MoonLightANALOGValue,11,180);

      // To adjust possible length of time of lightning strike, Change the "second()< 5 " ... where 5 is the # of seconds of lightning during cloud.
      // and the "minute())=(cloudstart_(cloudduration/2)))" sets start time... dividing by 2 is exactly half way through cloud to trigger 5 seconds lightning.
      // if there is lightning and current time is 1/2 way through cloud, but less then 1/2 way through cloud + 5 seconds

     // if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/LDTlightningRandomStart))) && second()<LDTlightningLength)
      if (LDTlightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
      {
        if (random(100)<20) LDTlightningstatus=1;
        else LDTlightningstatus=0;
        if (LDTlightningstatus)
        {
               // - This will correctly flash lightning up to 100% power every lightning strike.
          //LDT_DaylightPWMValue=100;
          //LDT_ActinicPWMValue=100;
//_______________________________________________________________________________________________________
          // - Trying to make a random sample between, say 80% & 100% for the brightness of the lightning?
          // - If there will be lightning, pick a random number between 80 and 100 for the lightning power.
          LDT_LightningFlashPower=random(80,100);
          RDT_LightningFlashPower=random(80,100);

          RDT_DaylightPWMValue=RDT_LightningFlashPower;
          RDT_ActinicPWMValue=RDT_LightningFlashPower;
          RDT_MoonLightANALOGValue=RDT_LightningFlashPower;
          LDT_DaylightPWMValue=LDT_LightningFlashPower;
          LDT_ActinicPWMValue=LDT_LightningFlashPower;
          LDT_MoonLightANALOGValue=LDT_LightningFlashPower;
//_______________________________________________________________________________________________________
        }
        else
        {
          // - Changed these values from 0 to 11 so at end of lightning cycle, the go to min. 11% power
          RDT_DaylightPWMValue=11;
          RDT_ActinicPWMValue=20;
          RDT_MoonLightANALOGValue=11;
          LDT_DaylightPWMValue=11;
          LDT_ActinicPWMValue=20;
          LDT_MoonLightANALOGValue=11;
//------------------------------------------------------------------
        }
        delay(1);
      }
    }
  }
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    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);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  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));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      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));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
  long n=elapsedSecsToday(now());
  cstart*=60;
  cend*=60;
  if (n<cstart) return PWMStart;
  if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
  if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
  if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
  if (n>cend) return PWMStart;
  //End Cloud & Lighting
}
Next up... getting my PH probe to read something more accurate then a constant value of 1. I've failed twice at getting it to calibrate correctly.

Re: Let's Build Troubadour11's Code

Posted: Thu Oct 09, 2014 3:33 pm
by lnevo
Thats an odd issue. What happens when you try to calibrate? You should be seeing numbers register on the screen when in the 7 and 10 fluids.

Re: Let's Build Troubadour11's Code

Posted: Fri Oct 10, 2014 3:54 pm
by troubadour11
I'm not sure what I'm doing wrong on the PH calibration. I followed all directions I could find and haven't been successful yet.

I do see numbers when I place the probe in the 7 and 10 fluid packets. But it NEVER stops changing numbers and never really settles close to an average. It just keeps bouncing around constantly no matter how long I leave it in the calibration fluid and then gives inaccurate readings on whatever numbers I eventually select as it ping-pongs around.

The first time, I tried it gave drastically low readings following the calibration when the numbers were jumping around and never stopped. And I had just tested the PH, so it was not super low.

Because I was expecting to screw up at least once, I had ordered extra calibration packets. The second time I tried leaving it in the fluid even longer hoping it would settle close to an average and give an appropriate reading. Again, it never did and know reads a constant value of 1 in the sump all the time.

I still have 1 more set of calibration packets. So I'm hoping I can get it right on the 3rd try. I had jotted down there is code I can use to hard-code the calibration values. I'm wondering if I need to calibrate it with the fluid and write down what looks like the average number it should settle on. Then hard-code those calibration numbers.?.?

Re: Let's Build Troubadour11's Code

Posted: Fri Oct 10, 2014 4:19 pm
by lnevo
You can try that or you could hardcode the default valued again and see what happens...before wasting your last packets..

Re: Let's Build Troubadour11's Code

Posted: Fri Oct 10, 2014 4:21 pm
by lnevo
Sorry i hit send instead of enter.

What does the calibration number look like if you put it in a plastic cup with the probe and tank water? Does it settle? It should not be eratic..like +1/-1 when watching it. Sounds like either a bad probe or stray voltage.

Re: Let's Build Troubadour11's Code

Posted: Wed Jul 22, 2015 9:49 pm
by troubadour11
Some last minute help requested :?

So it's been awhile since my last update. I've still been working away a little bit. I never solved my PH probe issue. I haven't had time to get back to it. Most of the work has been additional fixes and adjustments to my lighting section.

But I have a new dosing pump that I could use a little bit of help with. I've been carbon dosing my system (50 mL per day) and need to leave town Friday morning. I have a friend that will feed my aquarium and keep an eye on things, but I picked up the dosing pump and would love to get it integrated to help my house sitter out.

Here is the current code I have loaded up on my controller:

Code: Select all

//*************************************************************************************************************
//Start of PWM Expansion Code Header

//This is just how we are going to reference the PWM expansion ports within the code.
//You can change the labels if you would like, just as long as they are changed all throughout the code too.
                  //Standard PowerRelay PWM Daylight = L-DT White/Color
                  //Standard PowerRelay PWM Actinic = L-DT Blue/UV
#define LEDPWM0 0 // R-DT = White/Color
#define LEDPWM1 1 // R-DT = Blue/UV
#define LEDPWM2 2 // L-DT = MoonLight
#define LEDPWM3 3 // R-DT = Moonlight
#define LEDPWM4 4 // - EMPTY -
#define LEDPWM5 5 // - EMPTY -

//Initial values to all 6 channels at startup. They will always be 0.
byte PWMChannel[]={0,0,0,0,0,0};

//End of PWM Expansion Code Header
//*************************************************************************************************************

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <ReefAngel.h>

// - Creates a variable to control a "force cloud function" inside a menu.
boolean ForceCloud=false;

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
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";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
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();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

byte LDT_DaylightPWMValue=0;
byte LDT_ActinicPWMValue=0;
byte LDT_MoonLightANALOGValue=0;
byte RDT_DaylightPWMValue=0;
byte RDT_ActinicPWMValue=0;
byte RDT_MoonLightANALOGValue=0;

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.
 
    // Initialize the custom menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    ReefAngel.AddStandardMenu();  // Add Standard Menu
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen
 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit | Port7Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Dosing Pump
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up correctly, I think.
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
     ReefAngel.PWM.SetChannel(LEDPWM4,0);
     ReefAngel.PWM.SetChannel(LEDPWM5,0);
     // I believe using this got rid of my bright flash to 100% when I uploaded new code. Specifically setting the Standard Daylight/Actinic
//****************************************************************************************************************************************************************

    // Adds the date and time menu to controller so I can change for Daylight Savings Time. **Takes up memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app. eventually
    //ReefAngel.AddDateTimeMenu();
        
    ////// Place additional initialization code above here //////
}

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //Set up for Refugium and Phytoplankton light cycle

    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)
     //Turns Refugium Light plugged into port3 on at 9:30 pm and off at 9:30 am.
     ReefAngel.StandardLights(Port3,21,30,9,30);
     //Turns Phyto Light plugged into port4 on at 9:30 pm and off at 9:30 am.
     ReefAngel.StandardLights(Port4,21,30,9,30);

//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:55 p.m. and 10:00 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________  
 if ((NumMins(hour(now()),minute(now())) > NumMins(10,00)) && (NumMins(hour(now()),minute(now())) < NumMins(20,55)))
      {
         RDT_ActinicPWMValue=PWMSlope(10,00,20,55,11,100,240,RDT_ActinicPWMValue);
      }
      else
      {
         RDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 9:05 p.m. and 10:15 p.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,15)) && (NumMins(hour(now()),minute(now())) < NumMins(21,05)))
     {
        LDT_ActinicPWMValue=PWMSlope(10,15,21,05,11,100,240,LDT_ActinicPWMValue);
      }
      else
      {
        LDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 8:30 p.m. and 10:25 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,25)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_DaylightPWMValue=PWMSlope(10,25,20,30,11,100,240,RDT_DaylightPWMValue);
      }
      else
      {
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 8:45 p.m. and 10:35 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color -
    // - Parabola dimming 11%-%100-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,35)) && (NumMins(hour(now()),minute(now())) < NumMins(20,45)))
     {
        LDT_DaylightPWMValue=PWMSlope(10,35,20,45,11,100,240,LDT_DaylightPWMValue);
      }
      else
      {
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                                   //// Set MoonLights Cycles ////
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    LDT_MoonLightANALOGValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_MoonLightANALOGValue=0));
    RDT_MoonLightANALOGValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_MoonLightANALOGValue=0));

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM2,LDT_MoonLightANALOGValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM3,RDT_MoonLightANALOGValue);
//_________________________________________________________________________________________________

    ////// Place your custom code above here //////
 
    //Connects Reef Angel Head Unit to the Portal through WiFi Unit.
    ReefAngel.Portal("troubadour11");
    // This should always be the last line
    ReefAngel.ShowInterface();
}


//************************************************************************************************************************************************************************************************************************
  // - My lighting is set up as:
  //      - LDT (Left Display Tank) fixture on the "Standard" 2 (PWM signal) slots on my power relay box.
  //      - RDT (Right Display Tank) fixture on channels 0=daylight and 1=actinic (PWM signal) of the Dimming Expansion.
  //      - Moonlights = channels 2 and 3 of the PWM expansion. These drivers are Analog and the jumper pins have been set for 0-10v Analog in Dimming Expansion.
 
 // - I'd like to calculate the cloud effect for the LDT lights using the "Weather Sim for Standard PWM" code which I think
 //   I got copied, set up, and working correctly for the 1 fixture on the Standard Ports.
 //                 - Then find a way to feed those values into the RDT lights with a "Random Sample" of + or - a small time offset for the cloud effect on the RDT
 //                                - This way I could randomly generate a weather sim, and randomly have the cloud move Right-to-Left or Left-to-Right each time a cloud occurs

//                  - Lightning effect could either be off set with a similar way with +/- value OR left sync'd to LDT lightning (would need to test what looks good).
//                  - Or possibly more or less lightning per fixture during cloud.
//*************************************************************************************************************************************************************************************************************************

// Random Cloud/Thunderstorm effects function
void CheckCloud()
{

  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Change the values below to customize your cloud/storm effect

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
  // I should create a random variable here to randomize the days it has clouds?
#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 12

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

  // Only start the cloud effect after this setting
     // In this example, start could happen after 11:30am
#define Start_Cloud_After NumMins(13,00)

  // Always end the cloud effect before this setting
     // In this example, end must be before 6:45pm
#define End_Cloud_Before NumMins(19,45)

  // Percentage chance of lightning happening 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 results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

    //#define printdebug // Uncomment this for debug print on Serial Monitor window
  //#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.


  // Change the values above to customize your cloud/storm effect
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Do not change anything below here

  static byte cloudchance = 255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  //made individual lightningchance for each fixture
  static byte LDTlightningchance=0;
  //static byte RDTlightningchance=0;
  static byte cloudindex=0;
  //made separate lightning status for each fixture
  static byte LDTlightningstatus=0;
  //static byte RDTlightningstatus=0;
  static int LastNumMins=0;
//********************************************************************************************************************************************************
  // - I created this value to use as +/- offset value to make clouds pass left-to-right or vice-versa.
  //static int RDTcloudstartOffset=0;

  // - I added this to use as a randomly chosen value between (-)x to y.
  // - This will be added to "cloudstart" in order to determine the start time of the RDT cloud effect.
  //static byte RDTcloudstart=0;

  // - I added this to create a random sample between 80% and 100% dimming power for "random" lightning intensity effect on each fixture.
  static byte LDT_LightningFlashPower=0;
  static byte RDT_LightningFlashPower=0;

  // - I added these to create a variable to randomize when and how long each lightning strike happens for each cloud.
  //LDTlightningRandomStart=0;
  //LDTlightningLength=0;
  //RDTlightningRandomStart=0;
  //RDTlightningLength=0;
//********************************************************************************************************************************************************


  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

#ifdef forcecloudcalculation
    if (cloudchance==255)
#else
    if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
    {
      //Pick a random number between 0 and 99
      cloudchance=random(100);
      // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
      if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
      // Check if today is day for clouds.
      if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
      // If we have cloud today
      if (cloudchance)
      {
        // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
        numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
        // pick the time that the first cloud will start
        // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
        cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));

//********************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//********************************************************************************************************************************************************

        // 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
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

// Force Cloud from Menu Function jumps to here to start cloud effect immediately for a show!
if (ForceCloud)
{
ForceCloud=false;
cloudchance=1;
cloudduration=8;
LDTlightningchance=1;
//RDTlightningchance=1;
cloudstart=NumMins(hour(),minute())+2;
//RDTcloudstart=NumMins(hour(),minute())+1;
//RDTcloudstartOffset=random(-1,1);
//RDTcloudstart=cloudstart+RDTcloudstartOffset;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // - Controls Cloud and Lightning Effect timings for both fixtures
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  if (cloudchance)
  {
    //is it time for cloud yet over Left Display Tank?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      // - Dims LDT to create cloud and back up to Daylight Parabola. - maybe change to 11 in order to make minimum cloud power 11% so the lights don't click off.
      RDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,10,180);
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,10,180);
      RDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,20,180);
      LDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,20,180);
      RDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_MoonLightANALOGValue,11,180);
      LDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_MoonLightANALOGValue,11,180);

      // To adjust possible length of time of lightning strike, Change the "second()< 5 " ... where 5 is the # of seconds of lightning during cloud.
      // and the "minute())=(cloudstart_(cloudduration/2)))" sets start time... dividing by 2 is exactly half way through cloud to trigger 5 seconds lightning.
      // if there is lightning and current time is 1/2 way through cloud, but less then 1/2 way through cloud + 5 seconds

     // if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/LDTlightningRandomStart))) && second()<LDTlightningLength)
      if (LDTlightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
      {
        if (random(100)<20) LDTlightningstatus=1;
        else LDTlightningstatus=0;
        if (LDTlightningstatus)
        {
               // - This will correctly flash lightning up to 100% power every lightning strike.
          //LDT_DaylightPWMValue=100;
          //LDT_ActinicPWMValue=100;
//_______________________________________________________________________________________________________
          // - Trying to make a random sample between, say 80% & 100% for the brightness of the lightning?
          // - If there will be lightning, pick a random number between 80 and 100 for the lightning power.
          LDT_LightningFlashPower=random(80,100);
          RDT_LightningFlashPower=random(80,100);

          RDT_DaylightPWMValue=RDT_LightningFlashPower;
          RDT_ActinicPWMValue=RDT_LightningFlashPower;
          RDT_MoonLightANALOGValue=RDT_LightningFlashPower;
          LDT_DaylightPWMValue=LDT_LightningFlashPower;
          LDT_ActinicPWMValue=LDT_LightningFlashPower;
          LDT_MoonLightANALOGValue=LDT_LightningFlashPower;
//_______________________________________________________________________________________________________
        }
        else
        {
          // - Changed these values from 0 to 11 so at end of lightning cycle, the go to min. 11% power
          RDT_DaylightPWMValue=11;
          RDT_ActinicPWMValue=20;
          RDT_MoonLightANALOGValue=11;
          LDT_DaylightPWMValue=11;
          LDT_ActinicPWMValue=20;
          LDT_MoonLightANALOGValue=11;
//------------------------------------------------------------------
        }
        delay(1);
      }
    }
  }
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    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);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  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));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      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));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
  long n=elapsedSecsToday(now());
  cstart*=60;
  cend*=60;
  if (n<cstart) return PWMStart;
  if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
  if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
  if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
  if (n>cend) return PWMStart;
  //End Cloud & Lighting
}
- - - - -

I popped open the Wizard to try and generate the code I needed. But I think I'm missing something. I pretty much skipped over most options and tried to set up a dosing pump. Here is the code it gave me:

Code: Select all

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

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


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


void setup()
{
    // This must be the first line
    ReefAngel.Init();  //Initialize controller
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = 0;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = 0;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;


    // Ports that are always on
    ReefAngel.Relay.On( Port8 );

    ////// Place additional initialization code below here
    

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

void loop()
{
    ReefAngel.StandardHeater( Port5 );
    ReefAngel.StandardHeater( Port6 );
    ReefAngel.DosingPumpRepeat1( Port7 );
    ReefAngel.PWM.DaylightPWMSlope();
    ReefAngel.PWM.ActinicPWMSlope();
    ////// Place your custom code below here
    

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

    // This should always be the last line
    ReefAngel.AddWifi();
    ReefAngel.ShowInterface();
}

From that, it seems like all I need to add into my code is this to connect my pump to port 7:

Code: Select all

    ReefAngel.DosingPumpRepeat1( Port7 );
But as I mentioned, it seems like I'm missing some info I can input to control the timing of the port for the pump.

I did a few tests of the new pump. (tests done by hand and stop watch)
- I was getting about 14.15 seconds for 10 mL of tank water.
- 13.8 seconds for 10 mL of vinegar.

- Then 1:01.75 seconds for 50 mL of vinegar. (50 mL is the daily amount I have been dosing)

Based off that information. I figured I could run my pump (on Port7) for 5 seconds every 2 hours. If I could do a shorter time, more often; that would be preferred. But those were nice even numbers that I figured out first.

If anyone could help me out, point me in the right direction, or let me know whether or not I am missing something to get this integrated it would be very much appreciated. :) It would definitely put my mind at ease knowing I've got this covered and automated during this last minute / unexpected trip.

Thanks :D

Re: Let's Build Troubadour11's Code

Posted: Wed Jul 22, 2015 11:21 pm
by rimai
Try using hard coded settings.
They are missing because you chose internal memory.
But I think you need this:

Code: Select all

ReefAngel.DosingPumpRepeat( Port7,0,120,5 );

Re: Let's Build Troubadour11's Code

Posted: Thu Jul 23, 2015 7:56 am
by troubadour11
Thanks so much for the speedy help. :D I knew I had to be missing how to input the variables.

So I inserted this into my code between the heater ports and light cycle like in the example from the wizard:

Code: Select all

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
    
    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
    
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //Set up for Refugium and Phytoplankton light cycle
Was I also correct to exclude Port7 from the "always on" list then?

Code: Select all

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    //ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Dosing Pump
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
And I also removed Portbit7 from the water change function since I had a different pump on here.

Code: Select all

    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit;
. . .

All right. So the code verified and looks like it's good to go. It's time for me to head off to work this morning. So I'm going to upload my modified full code and set up the vinegar dripping into a measuring cup to test it out during the day while at work. Hopefully I come home to the proper amount and will have this all set up and ready to go for my tank sitter :)

I'll try to report back on the results before I head out of town :D

Current full code:

Code: Select all

//*************************************************************************************************************
//Start of PWM Expansion Code Header

//This is just how we are going to reference the PWM expansion ports within the code.
//You can change the labels if you would like, just as long as they are changed all throughout the code too.
                  //Standard PowerRelay PWM Daylight = L-DT White/Color
                  //Standard PowerRelay PWM Actinic = L-DT Blue/UV
#define LEDPWM0 0 // R-DT = White/Color
#define LEDPWM1 1 // R-DT = Blue/UV
#define LEDPWM2 2 // L-DT = MoonLight
#define LEDPWM3 3 // R-DT = Moonlight
#define LEDPWM4 4 // - EMPTY -
#define LEDPWM5 5 // - EMPTY -

//Initial values to all 6 channels at startup. They will always be 0.
byte PWMChannel[]={0,0,0,0,0,0};

//End of PWM Expansion Code Header
//*************************************************************************************************************

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <ReefAngel.h>

// - Creates a variable to control a "force cloud function" inside a menu.
boolean ForceCloud=false;

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
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";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
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();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

byte LDT_DaylightPWMValue=0;
byte LDT_ActinicPWMValue=0;
byte LDT_MoonLightANALOGValue=0;
byte RDT_DaylightPWMValue=0;
byte RDT_ActinicPWMValue=0;
byte RDT_MoonLightANALOGValue=0;

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.
 
    // Initialize the custom menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    ReefAngel.AddStandardMenu();  // Add Standard Menu
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen
 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    //ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Dosing Pump
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up correctly, I think.
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
     ReefAngel.PWM.SetChannel(LEDPWM4,0);
     ReefAngel.PWM.SetChannel(LEDPWM5,0);
     // I believe using this got rid of my bright flash to 100% when I uploaded new code. Specifically setting the Standard Daylight/Actinic
//****************************************************************************************************************************************************************

    // Adds the date and time menu to controller so I can change for Daylight Savings Time. **Takes up memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app. eventually
    //ReefAngel.AddDateTimeMenu();
        
    ////// Place additional initialization code above here //////
}

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
    
    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
    
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //Set up for Refugium and Phytoplankton light cycle

    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)
     //Turns Refugium Light plugged into port3 on at 9:30 pm and off at 9:30 am.
     ReefAngel.StandardLights(Port3,21,30,9,30);
     //Turns Phyto Light plugged into port4 on at 9:30 pm and off at 9:30 am.
     ReefAngel.StandardLights(Port4,21,30,9,30);

//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:55 p.m. and 10:00 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________  
 if ((NumMins(hour(now()),minute(now())) > NumMins(10,00)) && (NumMins(hour(now()),minute(now())) < NumMins(20,55)))
      {
         RDT_ActinicPWMValue=PWMSlope(10,00,20,55,11,100,240,RDT_ActinicPWMValue);
      }
      else
      {
         RDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 9:05 p.m. and 10:15 p.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,15)) && (NumMins(hour(now()),minute(now())) < NumMins(21,05)))
     {
        LDT_ActinicPWMValue=PWMSlope(10,15,21,05,11,100,240,LDT_ActinicPWMValue);
      }
      else
      {
        LDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 8:30 p.m. and 10:25 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,25)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_DaylightPWMValue=PWMSlope(10,25,20,30,11,100,240,RDT_DaylightPWMValue);
      }
      else
      {
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 8:45 p.m. and 10:35 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color -
    // - Parabola dimming 11%-%100-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,35)) && (NumMins(hour(now()),minute(now())) < NumMins(20,45)))
     {
        LDT_DaylightPWMValue=PWMSlope(10,35,20,45,11,100,240,LDT_DaylightPWMValue);
      }
      else
      {
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                                   //// Set MoonLights Cycles ////
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    LDT_MoonLightANALOGValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_MoonLightANALOGValue=0));
    RDT_MoonLightANALOGValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_MoonLightANALOGValue=0));

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM2,LDT_MoonLightANALOGValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM3,RDT_MoonLightANALOGValue);
//_________________________________________________________________________________________________

    ////// Place your custom code above here //////
 
    //Connects Reef Angel Head Unit to the Portal through WiFi Unit.
    ReefAngel.Portal("troubadour11");
    // This should always be the last line
    ReefAngel.ShowInterface();
}


//************************************************************************************************************************************************************************************************************************
  // - My lighting is set up as:
  //      - LDT (Left Display Tank) fixture on the "Standard" 2 (PWM signal) slots on my power relay box.
  //      - RDT (Right Display Tank) fixture on channels 0=daylight and 1=actinic (PWM signal) of the Dimming Expansion.
  //      - Moonlights = channels 2 and 3 of the PWM expansion. These drivers are Analog and the jumper pins have been set for 0-10v Analog in Dimming Expansion.
 
 // - I'd like to calculate the cloud effect for the LDT lights using the "Weather Sim for Standard PWM" code which I think
 //   I got copied, set up, and working correctly for the 1 fixture on the Standard Ports.
 //                 - Then find a way to feed those values into the RDT lights with a "Random Sample" of + or - a small time offset for the cloud effect on the RDT
 //                                - This way I could randomly generate a weather sim, and randomly have the cloud move Right-to-Left or Left-to-Right each time a cloud occurs

//                  - Lightning effect could either be off set with a similar way with +/- value OR left sync'd to LDT lightning (would need to test what looks good).
//                  - Or possibly more or less lightning per fixture during cloud.
//*************************************************************************************************************************************************************************************************************************

// Random Cloud/Thunderstorm effects function
void CheckCloud()
{

  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Change the values below to customize your cloud/storm effect

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
  // I should create a random variable here to randomize the days it has clouds?
#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 12

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

  // Only start the cloud effect after this setting
     // In this example, start could happen after 11:30am
#define Start_Cloud_After NumMins(13,00)

  // Always end the cloud effect before this setting
     // In this example, end must be before 6:45pm
#define End_Cloud_Before NumMins(19,45)

  // Percentage chance of lightning happening 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 results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

    //#define printdebug // Uncomment this for debug print on Serial Monitor window
  //#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.


  // Change the values above to customize your cloud/storm effect
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Do not change anything below here

  static byte cloudchance = 255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  //made individual lightningchance for each fixture
  static byte LDTlightningchance=0;
  //static byte RDTlightningchance=0;
  static byte cloudindex=0;
  //made separate lightning status for each fixture
  static byte LDTlightningstatus=0;
  //static byte RDTlightningstatus=0;
  static int LastNumMins=0;
//********************************************************************************************************************************************************
  // - I created this value to use as +/- offset value to make clouds pass left-to-right or vice-versa.
  //static int RDTcloudstartOffset=0;

  // - I added this to use as a randomly chosen value between (-)x to y.
  // - This will be added to "cloudstart" in order to determine the start time of the RDT cloud effect.
  //static byte RDTcloudstart=0;

  // - I added this to create a random sample between 80% and 100% dimming power for "random" lightning intensity effect on each fixture.
  static byte LDT_LightningFlashPower=0;
  static byte RDT_LightningFlashPower=0;

  // - I added these to create a variable to randomize when and how long each lightning strike happens for each cloud.
  //LDTlightningRandomStart=0;
  //LDTlightningLength=0;
  //RDTlightningRandomStart=0;
  //RDTlightningLength=0;
//********************************************************************************************************************************************************


  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

#ifdef forcecloudcalculation
    if (cloudchance==255)
#else
    if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
    {
      //Pick a random number between 0 and 99
      cloudchance=random(100);
      // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
      if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
      // Check if today is day for clouds.
      if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
      // If we have cloud today
      if (cloudchance)
      {
        // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
        numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
        // pick the time that the first cloud will start
        // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
        cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));

//********************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//********************************************************************************************************************************************************

        // 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
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

// Force Cloud from Menu Function jumps to here to start cloud effect immediately for a show!
if (ForceCloud)
{
ForceCloud=false;
cloudchance=1;
cloudduration=8;
LDTlightningchance=1;
//RDTlightningchance=1;
cloudstart=NumMins(hour(),minute())+2;
//RDTcloudstart=NumMins(hour(),minute())+1;
//RDTcloudstartOffset=random(-1,1);
//RDTcloudstart=cloudstart+RDTcloudstartOffset;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // - Controls Cloud and Lightning Effect timings for both fixtures
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  if (cloudchance)
  {
    //is it time for cloud yet over Left Display Tank?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      // - Dims LDT to create cloud and back up to Daylight Parabola. - maybe change to 11 in order to make minimum cloud power 11% so the lights don't click off.
      RDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,10,180);
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,10,180);
      RDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,20,180);
      LDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,20,180);
      RDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_MoonLightANALOGValue,11,180);
      LDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_MoonLightANALOGValue,11,180);

      // To adjust possible length of time of lightning strike, Change the "second()< 5 " ... where 5 is the # of seconds of lightning during cloud.
      // and the "minute())=(cloudstart_(cloudduration/2)))" sets start time... dividing by 2 is exactly half way through cloud to trigger 5 seconds lightning.
      // if there is lightning and current time is 1/2 way through cloud, but less then 1/2 way through cloud + 5 seconds

     // if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/LDTlightningRandomStart))) && second()<LDTlightningLength)
      if (LDTlightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
      {
        if (random(100)<20) LDTlightningstatus=1;
        else LDTlightningstatus=0;
        if (LDTlightningstatus)
        {
               // - This will correctly flash lightning up to 100% power every lightning strike.
          //LDT_DaylightPWMValue=100;
          //LDT_ActinicPWMValue=100;
//_______________________________________________________________________________________________________
          // - Trying to make a random sample between, say 80% & 100% for the brightness of the lightning?
          // - If there will be lightning, pick a random number between 80 and 100 for the lightning power.
          LDT_LightningFlashPower=random(80,100);
          RDT_LightningFlashPower=random(80,100);

          RDT_DaylightPWMValue=RDT_LightningFlashPower;
          RDT_ActinicPWMValue=RDT_LightningFlashPower;
          RDT_MoonLightANALOGValue=RDT_LightningFlashPower;
          LDT_DaylightPWMValue=LDT_LightningFlashPower;
          LDT_ActinicPWMValue=LDT_LightningFlashPower;
          LDT_MoonLightANALOGValue=LDT_LightningFlashPower;
//_______________________________________________________________________________________________________
        }
        else
        {
          // - Changed these values from 0 to 11 so at end of lightning cycle, the go to min. 11% power
          RDT_DaylightPWMValue=11;
          RDT_ActinicPWMValue=20;
          RDT_MoonLightANALOGValue=11;
          LDT_DaylightPWMValue=11;
          LDT_ActinicPWMValue=20;
          LDT_MoonLightANALOGValue=11;
//------------------------------------------------------------------
        }
        delay(1);
      }
    }
  }
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    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);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  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));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      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));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
  long n=elapsedSecsToday(now());
  cstart*=60;
  cend*=60;
  if (n<cstart) return PWMStart;
  if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
  if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
  if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
  if (n>cend) return PWMStart;
  //End Cloud & Lighting
}

Re: Let's Build Troubadour11's Code

Posted: Thu Jul 23, 2015 8:12 am
by troubadour11
Oh, I forgot to ask.

In the new code:

Code: Select all

    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
What does the 0 represent?

I get that you're telling the controller to do a repeating dosing pump cycle on Port 7. Every 120 minutes, for 5 seconds. Are those numbers always in minutes and seconds no matter what? Using the .DosingPump code tells it those will be minutes and seconds?

I just want to fully understand all the variables in case I ever need to modify it further. Thanks! :)

Re: Let's Build Troubadour11's Code

Posted: Thu Jul 23, 2015 8:36 am
by rimai
Correct, minutes for repeat and seconds to run.
The 0 is for offset.
If you were to dose alk and Ca, you would want to offset both of them.

Re: Let's Build Troubadour11's Code

Posted: Sat Aug 08, 2015 10:33 am
by troubadour11
So I didn't make it back to report the results before leaving town. But I was right at the amount expected after running a 12 hour test from 8am to 8pm. So I set it up into my sump and let it go. So far it seems to be working just fine!

Thanks so much for the timely help and for the follow up on the offset. Definitely good to know. If my system ever out-paces kalk dosing through my ATO, I'll be looking to use that to keep up.

Thanks again! :D

Re: Let's Build Troubadour11's Code

Posted: Sat Sep 10, 2016 12:39 pm
by troubadour11
I've been wanting to figure out how to set up my light cycles on variables using custom memory locations. That way I should be able to access the variables and adjust them as I desire without having to break out my laptop and upload a new hard coded version every time, correct? Either through the portal or the RA android app?

I spent some time attempting to get it going. I'm sure I am missing some things since I wasn't getting the results I expected.

Here is a copy of my full code that is currently running (without the custom memory locations):

Code: Select all

//*************************************************************************************************************
//Start of PWM Expansion Code Header

//This is just how we are going to reference the PWM expansion ports within the code.
//You can change the labels if you would like, just as long as they are changed all throughout the code too.
                  //Standard PowerRelay PWM Daylight = L-DT White/Color
                  //Standard PowerRelay PWM Actinic = L-DT Blue/UV
#define LEDPWM0 0 // R-DT = White/Color
#define LEDPWM1 1 // R-DT = Blue/UV
#define LEDPWM2 2 // L-DT = MoonLight
#define LEDPWM3 3 // R-DT = Moonlight
#define LEDPWM4 4 // - EMPTY -
#define LEDPWM5 5 // - EMPTY -

//Initial values to all 6 channels at startup. They will always be 0.
byte PWMChannel[]={0,0,0,0,0,0};

//End of PWM Expansion Code Header
//*************************************************************************************************************

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <ReefAngel.h>

// - Creates a variable to control a "force cloud function" inside a menu.
boolean ForceCloud=false;

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
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";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
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();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

byte LDT_DaylightPWMValue=0;
byte LDT_ActinicPWMValue=0;
byte LDT_MoonLightANALOGValue=0;
byte RDT_DaylightPWMValue=0;
byte RDT_ActinicPWMValue=0;
byte RDT_MoonLightANALOGValue=0;

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.
 
    // Initialize the custom menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    ReefAngel.AddStandardMenu();  // Add Standard Menu
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen
 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    //ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Dosing Pump
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up correctly, I think.
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
     ReefAngel.PWM.SetChannel(LEDPWM4,0);
     ReefAngel.PWM.SetChannel(LEDPWM5,0);
     // I believe using this got rid of my bright flash to 100% when I uploaded new code. Specifically setting the Standard Daylight/Actinic
//****************************************************************************************************************************************************************

    // Adds the date and time menu to controller so I can change for Daylight Savings Time. **Takes up memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app. eventually
    //ReefAngel.AddDateTimeMenu();
        
    ////// Place additional initialization code above here //////
}

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
    
    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
    
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //Set up for Refugium and Phytoplankton light cycle

    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)
     //Turns Refugium Light plugged into port3 on at 10:30 pm and off at 5:30 am.
     ReefAngel.StandardLights(Port3,22,30,8,30);
     //Turns Phyto Light plugged into port4 on at 10:30 pm and off at 5:30 am.
     ReefAngel.StandardLights(Port4,22,30,8,30);

//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:55 p.m. and 10:00 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________  
 if ((NumMins(hour(now()),minute(now())) > NumMins(10,00)) && (NumMins(hour(now()),minute(now())) < NumMins(20,55)))
      {
         //RDT_ActinicPWMValue=PWMSlope(10,00,20,55,11,100,240,RDT_ActinicPWMValue);
         RDT_MoonLightANALOGValue=PWMSlope(10,00,20,55,11,100,240,RDT_MoonLightANALOGValue);
      }
      else
      {
         //RDT_ActinicPWMValue=0;
         RDT_MoonLightANALOGValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 9:05 p.m. and 10:15 p.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,15)) && (NumMins(hour(now()),minute(now())) < NumMins(21,05)))
     {
        //LDT_ActinicPWMValue=PWMSlope(10,15,21,05,11,100,240,LDT_ActinicPWMValue);
        LDT_MoonLightANALOGValue=PWMSlope(10,15,21,05,11,100,240,LDT_MoonLightANALOGValue);
      }
      else
      {
        //LDT_ActinicPWMValue=0;
        LDT_MoonLightANALOGValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 8:30 p.m. and 10:25 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color -
    // - Parabola dimming 11%-50%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,25)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_DaylightPWMValue=PWMSlope(10,25,20,30,11,50,240,RDT_DaylightPWMValue);
      }
      else
      {
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 8:45 p.m. and 10:35 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color -
    // - Parabola dimming 11%-50%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,35)) && (NumMins(hour(now()),minute(now())) < NumMins(20,45)))
     {
        LDT_DaylightPWMValue=PWMSlope(10,35,20,45,11,50,240,LDT_DaylightPWMValue);
      }
      else
      {
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                                   //// Set MoonLights Cycles ////
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //LDT_MoonLightANALOGValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_MoonLightANALOGValue=0));
    LDT_ActinicPWMValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_ActinicPWMValue=0));
    //RDT_MoonLightANALOGValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_MoonLightANALOGValue=0));
    RDT_ActinicPWMValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_ActinicPWMValue=0));

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM2,LDT_MoonLightANALOGValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM3,RDT_MoonLightANALOGValue);
//_________________________________________________________________________________________________

    ////// Place your custom code above here //////
 
    //Connects Reef Angel Head Unit to the Portal through WiFi Unit.
    ReefAngel.Portal("troubadour11");
    // This should always be the last line
    ReefAngel.ShowInterface();
}


//************************************************************************************************************************************************************************************************************************
  // - My lighting is set up as:
  //      - LDT (Left Display Tank) fixture on the "Standard" 2 (PWM signal) slots on my power relay box.
  //      - RDT (Right Display Tank) fixture on channels 0=daylight and 1=actinic (PWM signal) of the Dimming Expansion.
  //      - Moonlights = channels 2 and 3 of the PWM expansion. These drivers are Analog and the jumper pins have been set for 0-10v Analog in Dimming Expansion.
 
 // - I'd like to calculate the cloud effect for the LDT lights using the "Weather Sim for Standard PWM" code which I think
 //   I got copied, set up, and working correctly for the 1 fixture on the Standard Ports.
 //                 - Then find a way to feed those values into the RDT lights with a "Random Sample" of + or - a small time offset for the cloud effect on the RDT
 //                                - This way I could randomly generate a weather sim, and randomly have the cloud move Right-to-Left or Left-to-Right each time a cloud occurs

//                  - Lightning effect could either be off set with a similar way with +/- value OR left sync'd to LDT lightning (would need to test what looks good).
//                  - Or possibly more or less lightning per fixture during cloud.
//*************************************************************************************************************************************************************************************************************************

// Random Cloud/Thunderstorm effects function
void CheckCloud()
{

  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Change the values below to customize your cloud/storm effect

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
  // I should create a random variable here to randomize the days it has clouds?
#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 12

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

  // Only start the cloud effect after this setting
     // In this example, start could happen after 11:30am
#define Start_Cloud_After NumMins(13,00)

  // Always end the cloud effect before this setting
     // In this example, end must be before 6:45pm
#define End_Cloud_Before NumMins(19,45)

  // Percentage chance of lightning happening 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 results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

    //#define printdebug // Uncomment this for debug print on Serial Monitor window
  //#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.


  // Change the values above to customize your cloud/storm effect
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Do not change anything below here

  static byte cloudchance = 255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  //made individual lightningchance for each fixture
  static byte LDTlightningchance=0;
  //static byte RDTlightningchance=0;
  static byte cloudindex=0;
  //made separate lightning status for each fixture
  static byte LDTlightningstatus=0;
  //static byte RDTlightningstatus=0;
  static int LastNumMins=0;
//********************************************************************************************************************************************************
  // - I created this value to use as +/- offset value to make clouds pass left-to-right or vice-versa.
  //static int RDTcloudstartOffset=0;

  // - I added this to use as a randomly chosen value between (-)x to y.
  // - This will be added to "cloudstart" in order to determine the start time of the RDT cloud effect.
  //static byte RDTcloudstart=0;

  // - I added this to create a random sample between 80% and 100% dimming power for "random" lightning intensity effect on each fixture.
  static byte LDT_LightningFlashPower=0;
  static byte RDT_LightningFlashPower=0;

  // - I added these to create a variable to randomize when and how long each lightning strike happens for each cloud.
  //LDTlightningRandomStart=0;
  //LDTlightningLength=0;
  //RDTlightningRandomStart=0;
  //RDTlightningLength=0;
//********************************************************************************************************************************************************


  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

#ifdef forcecloudcalculation
    if (cloudchance==255)
#else
    if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
    {
      //Pick a random number between 0 and 99
      cloudchance=random(100);
      // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
      if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
      // Check if today is day for clouds.
      if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
      // If we have cloud today
      if (cloudchance)
      {
        // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
        numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
        // pick the time that the first cloud will start
        // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
        cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));

//********************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//********************************************************************************************************************************************************

        // 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
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

// Force Cloud from Menu Function jumps to here to start cloud effect immediately for a show!
if (ForceCloud)
{
ForceCloud=false;
cloudchance=1;
cloudduration=8;
LDTlightningchance=1;
//RDTlightningchance=1;
cloudstart=NumMins(hour(),minute())+2;
//RDTcloudstart=NumMins(hour(),minute())+1;
//RDTcloudstartOffset=random(-1,1);
//RDTcloudstart=cloudstart+RDTcloudstartOffset;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // - Controls Cloud and Lightning Effect timings for both fixtures
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  if (cloudchance)
  {
    //is it time for cloud yet over Left Display Tank?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      // - Dims LDT to create cloud and back up to Daylight Parabola. - maybe change to 11 in order to make minimum cloud power 11% so the lights don't click off.
      RDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,10,180);
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,10,180);
      RDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,20,180);
      LDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,20,180);
      RDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_MoonLightANALOGValue,11,180);
      LDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_MoonLightANALOGValue,11,180);

      // To adjust possible length of time of lightning strike, Change the "second()< 5 " ... where 5 is the # of seconds of lightning during cloud.
      // and the "minute())=(cloudstart_(cloudduration/2)))" sets start time... dividing by 2 is exactly half way through cloud to trigger 5 seconds lightning.
      // if there is lightning and current time is 1/2 way through cloud, but less then 1/2 way through cloud + 5 seconds

     // if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/LDTlightningRandomStart))) && second()<LDTlightningLength)
      if (LDTlightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
      {
        if (random(100)<20) LDTlightningstatus=1;
        else LDTlightningstatus=0;
        if (LDTlightningstatus)
        {
               // - This will correctly flash lightning up to 100% power every lightning strike.
          //LDT_DaylightPWMValue=100;
          //LDT_ActinicPWMValue=100;
//_______________________________________________________________________________________________________
          // - Trying to make a random sample between, say 80% & 100% for the brightness of the lightning?
          // - If there will be lightning, pick a random number between 80 and 100 for the lightning power.
          LDT_LightningFlashPower=random(80,100);
          RDT_LightningFlashPower=random(80,100);

          RDT_DaylightPWMValue=RDT_LightningFlashPower;
          RDT_ActinicPWMValue=RDT_LightningFlashPower;
          RDT_MoonLightANALOGValue=RDT_LightningFlashPower;
          LDT_DaylightPWMValue=LDT_LightningFlashPower;
          LDT_ActinicPWMValue=LDT_LightningFlashPower;
          LDT_MoonLightANALOGValue=LDT_LightningFlashPower;
//_______________________________________________________________________________________________________
        }
        else
        {
          // - Changed these values from 0 to 11 so at end of lightning cycle, the go to min. 11% power
          RDT_DaylightPWMValue=11;
          RDT_ActinicPWMValue=20;
          RDT_MoonLightANALOGValue=11;
          LDT_DaylightPWMValue=11;
          LDT_ActinicPWMValue=20;
          LDT_MoonLightANALOGValue=11;
//------------------------------------------------------------------
        }
        delay(1);
      }
    }
  }
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    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);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  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));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      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));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
  long n=elapsedSecsToday(now());
  cstart*=60;
  cend*=60;
  if (n<cstart) return PWMStart;
  if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
  if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
  if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
  if (n>cend) return PWMStart;
  //End Cloud & Lighting
}
To start out my attempt at setting this up, I used my refugium lights which are just 2 spots on a timer running on Port3 and Port4 of my power strip.

Here is how I modified the code... it resulted in my refugium lights being set to on at noon when I was testing it.

I added this section into the top, I copied the lines above and below what I added to clip out the changes;

Code: Select all

// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

//Define Custom Memory Locations
#define Mem_B_Refugium03_onHour    100
#define Mem_B_Refugium03_onMins    101
#define Mem_B_Refugium03_offHour   102
#define Mem_B_Refugium03_offMins   103
#define Mem_B_Refugium04_onHour    104
#define Mem_B_Refugium04_onMins    105
#define Mem_B_Refugium04_offHour   106
#define Mem_B_Refugium04_offMins   107
         
void init_memory() {
  // Initialize Custom Memory Locations
  InternalMemory.write(Mem_B_Refugium03_onHour,22);
  InternalMemory.write(Mem_B_Refugium03_onMins,30);
  InternalMemory.write(Mem_B_Refugium03_offHour,8);
  InternalMemory.write(Mem_B_Refugium03_offMins,30);
  InternalMemory.write(Mem_B_Refugium04_onHour,22);
  InternalMemory.write(Mem_B_Refugium04_onMins,30);
  InternalMemory.write(Mem_B_Refugium04_offHour,8);
  InternalMemory.write(Mem_B_Refugium04_offMins,30);
}

////// Place global variable code below here //////
and then adjusted my refugium light schedule section like this:

Code: Select all

//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //Set up for Refugium and Phytoplankton light cycle
    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)

     //Turns Refugium Light plugged into port3 on/off at times specified by internal memory via web or app
     ReefAngel.StandardLights(Port3,
         InternalMemory.read(Mem_B_Refugium03_onHour),
         InternalMemory.read(Mem_B_Refugium03_onMins),
         InternalMemory.read(Mem_B_Refugium03_offHour),
         InternalMemory.read(Mem_B_Refugium03_offMins));
     //Turns Refugium Light plugged into port3 on at 10:30 pm and off at 5:30 am. - older code
     //ReefAngel.StandardLights(Port3,22,30,5,30); - old code
     
     //Turns Refugium Light plugged into port4 on/off at times specified by internal memory via web or app
     ReefAngel.StandardLights(Port4,
         InternalMemory.read(Mem_B_Refugium04_onHour),
         InternalMemory.read(Mem_B_Refugium04_onMins),
         InternalMemory.read(Mem_B_Refugium04_offHour),
         InternalMemory.read(Mem_B_Refugium04_offMins));
     //Turns Phyto Light plugged into port4 on at 10:30 pm and off at 5:30 am. - old code
     //ReefAngel.StandardLights(Port4,22,30,5,30); - old code

//-----------------------------------------------------------------------------------------------------------------------------------------------------//
My only guess right now is that I need to create custom global variables and set them to the InternalMemory.read?

So create the variables for each custom memory location and set them like this?

Code: Select all

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

byte Refugium03_onHour=InternalMemory.read(Mem_B_Refugium03_onHour);

byte LDT_DaylightPWMValue=0;
byte LDT_ActinicPWMValue=0;
byte LDT_MoonLightANALOGValue=0;
byte RDT_DaylightPWMValue=0;
byte RDT_ActinicPWMValue=0;
byte RDT_MoonLightANALOGValue=0;

////// Place global variable code above here //////
And then insert each custom variable into the light schedule code similar to this?

Code: Select all

     //Turns Refugium Light plugged into port3 on/off at times specified by internal memory via web or app
     ReefAngel.StandardLights(Port3,Refugium03_onHour,Refugium03_onMins,Refugium03_offHour,Refugium03_offMins);
I appreciate any help I can get on setting this up. I'd love to be able to adjust my light schedule quickly and easily at times. This can open the door to controlling a lot more things eventually without having to hard code the values.

Then once I get it coded right, I need to find them via the app or portal. This previous attempt didn't yield many results as I couldn't find any Mem_B_Refugium03_onHour , etc. labels anywhere. But first, I need to make sure it's coded to work right and then I can go about finding them to be able to adjust them.

Re: Let's Build Troubadour11's Code

Posted: Sun Sep 11, 2016 10:21 am
by troubadour11
Now I have another question and need some help with the DelayedOn command for my skimmer. I really do not understand why this is not working.

Here is a copy of my working code without the custom memory stuff I'm also working on where I tried to add in a simple delayed start for my skimmer so it doesn't over flow at times. It seemed like this would be easy to implement. I commented out Port6 from the always on list in the start up and added the delayed on command at the beginning of my main loop.

Code: Select all

//*************************************************************************************************************
//Start of PWM Expansion Code Header

//This is just how we are going to reference the PWM expansion ports within the code.
//You can change the labels if you would like, just as long as they are changed all throughout the code too.
                  //Standard PowerRelay PWM Daylight = L-DT White/Color
                  //Standard PowerRelay PWM Actinic = L-DT Blue/UV
#define LEDPWM0 0 // R-DT = White/Color
#define LEDPWM1 1 // R-DT = Blue/UV
#define LEDPWM2 2 // L-DT = MoonLight
#define LEDPWM3 3 // R-DT = Moonlight
#define LEDPWM4 4 // - EMPTY -
#define LEDPWM5 5 // - EMPTY -

//Initial values to all 6 channels at startup. They will always be 0.
byte PWMChannel[]={0,0,0,0,0,0};

//End of PWM Expansion Code Header
//*************************************************************************************************************

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <ReefAngel.h>

// - Creates a variable to control a "force cloud function" inside a menu.
boolean ForceCloud=false;

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
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";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
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();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

byte LDT_DaylightPWMValue=0;
byte LDT_ActinicPWMValue=0;
byte LDT_MoonLightANALOGValue=0;
byte RDT_DaylightPWMValue=0;
byte RDT_ActinicPWMValue=0;
byte RDT_MoonLightANALOGValue=0;

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.
 
    // Initialize the custom menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    ReefAngel.AddStandardMenu();  // Add Standard Menu
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen
 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    //ReefAngel.Relay.On( Port6 ); // removed to add delayed start feature to skimmer in main loop
    //ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Dosing Pump
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
 
     //Sets values of ALL lighting channels to 0% on Start Up correctly, I think.
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
     ReefAngel.PWM.SetChannel(LEDPWM4,0);
     ReefAngel.PWM.SetChannel(LEDPWM5,0);
     // I believe using this got rid of my bright flash to 100% when I uploaded new code. Specifically setting the Standard Daylight/Actinic

    // Adds the date and time menu to controller so I can change for Daylight Savings Time. **Takes up memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app. eventually
    //ReefAngel.AddDateTimeMenu();
        
    ////// Place additional initialization code above here //////
}

void loop()
{

    ////// Place your custom code below here //////

    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);

    // Always on ports that have a delay. These ports will delay turning on for the specified MINUTE_DELAY during the following modes:
      // Controller Power Up
      // Feeding Mode (if toggled during mode)
      // Water Change Mode (if toggled during mode)
    // This function needs to be placed inside loop() and not inside setup()
    // Usage is DelayedOn(PORT, MINUTE DELAY)  
    ReefAngel.DelayedOn(Port6, 5);
    
    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
    
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //Set up for Refugium and Phytoplankton light cycle

    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)
     //Turns Refugium Light plugged into port3 on at 10:30 pm and off at 5:30 am.
     ReefAngel.StandardLights(Port3,22,30,8,30);
     //Turns Phyto Light plugged into port4 on at 10:30 pm and off at 5:30 am.
     ReefAngel.StandardLights(Port4,22,30,8,30);

//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:55 p.m. and 10:00 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________  
 if ((NumMins(hour(now()),minute(now())) > NumMins(10,00)) && (NumMins(hour(now()),minute(now())) < NumMins(20,55)))
      {
         //RDT_ActinicPWMValue=PWMSlope(10,00,20,55,11,100,240,RDT_ActinicPWMValue);
         RDT_MoonLightANALOGValue=PWMSlope(10,00,20,55,11,100,240,RDT_MoonLightANALOGValue);
      }
      else
      {
         //RDT_ActinicPWMValue=0;
         RDT_MoonLightANALOGValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 9:05 p.m. and 10:15 p.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,15)) && (NumMins(hour(now()),minute(now())) < NumMins(21,05)))
     {
        //LDT_ActinicPWMValue=PWMSlope(10,15,21,05,11,100,240,LDT_ActinicPWMValue);
        LDT_MoonLightANALOGValue=PWMSlope(10,15,21,05,11,100,240,LDT_MoonLightANALOGValue);
      }
      else
      {
        //LDT_ActinicPWMValue=0;
        LDT_MoonLightANALOGValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 8:30 p.m. and 10:25 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color -
    // - Parabola dimming 11%-50%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,25)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_DaylightPWMValue=PWMSlope(10,25,20,30,11,50,240,RDT_DaylightPWMValue);
      }
      else
      {
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 8:45 p.m. and 10:35 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color -
    // - Parabola dimming 11%-50%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,35)) && (NumMins(hour(now()),minute(now())) < NumMins(20,45)))
     {
        LDT_DaylightPWMValue=PWMSlope(10,35,20,45,11,50,240,LDT_DaylightPWMValue);
      }
      else
      {
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                                   //// Set MoonLights Cycles ////
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //LDT_MoonLightANALOGValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_MoonLightANALOGValue=0));
    LDT_ActinicPWMValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_ActinicPWMValue=0));
    //RDT_MoonLightANALOGValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_MoonLightANALOGValue=0));
    RDT_ActinicPWMValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_ActinicPWMValue=0));

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM2,LDT_MoonLightANALOGValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM3,RDT_MoonLightANALOGValue);
//_________________________________________________________________________________________________

    ////// Place your custom code above here //////
 
    //Connects Reef Angel Head Unit to the Portal through WiFi Unit.
    ReefAngel.Portal("troubadour11");
    // This should always be the last line
    ReefAngel.ShowInterface();
}


//************************************************************************************************************************************************************************************************************************
  // - My lighting is set up as:
  //      - LDT (Left Display Tank) fixture on the "Standard" 2 (PWM signal) slots on my power relay box.
  //      - RDT (Right Display Tank) fixture on channels 0=daylight and 1=actinic (PWM signal) of the Dimming Expansion.
  //      - Moonlights = channels 2 and 3 of the PWM expansion. These drivers are Analog and the jumper pins have been set for 0-10v Analog in Dimming Expansion.
 
 // - I'd like to calculate the cloud effect for the LDT lights using the "Weather Sim for Standard PWM" code which I think
 //   I got copied, set up, and working correctly for the 1 fixture on the Standard Ports.
 //                 - Then find a way to feed those values into the RDT lights with a "Random Sample" of + or - a small time offset for the cloud effect on the RDT
 //                                - This way I could randomly generate a weather sim, and randomly have the cloud move Right-to-Left or Left-to-Right each time a cloud occurs

//                  - Lightning effect could either be off set with a similar way with +/- value OR left sync'd to LDT lightning (would need to test what looks good).
//                  - Or possibly more or less lightning per fixture during cloud.
//*************************************************************************************************************************************************************************************************************************

// Random Cloud/Thunderstorm effects function
void CheckCloud()
{

  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Change the values below to customize your cloud/storm effect

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
  // I should create a random variable here to randomize the days it has clouds?
#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 12

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

  // Only start the cloud effect after this setting
     // In this example, start could happen after 11:30am
#define Start_Cloud_After NumMins(13,00)

  // Always end the cloud effect before this setting
     // In this example, end must be before 6:45pm
#define End_Cloud_Before NumMins(19,45)

  // Percentage chance of lightning happening 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 results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

    //#define printdebug // Uncomment this for debug print on Serial Monitor window
  //#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.


  // Change the values above to customize your cloud/storm effect
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Do not change anything below here

  static byte cloudchance = 255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  //made individual lightningchance for each fixture
  static byte LDTlightningchance=0;
  //static byte RDTlightningchance=0;
  static byte cloudindex=0;
  //made separate lightning status for each fixture
  static byte LDTlightningstatus=0;
  //static byte RDTlightningstatus=0;
  static int LastNumMins=0;
//********************************************************************************************************************************************************
  // - I created this value to use as +/- offset value to make clouds pass left-to-right or vice-versa.
  //static int RDTcloudstartOffset=0;

  // - I added this to use as a randomly chosen value between (-)x to y.
  // - This will be added to "cloudstart" in order to determine the start time of the RDT cloud effect.
  //static byte RDTcloudstart=0;

  // - I added this to create a random sample between 80% and 100% dimming power for "random" lightning intensity effect on each fixture.
  static byte LDT_LightningFlashPower=0;
  static byte RDT_LightningFlashPower=0;

  // - I added these to create a variable to randomize when and how long each lightning strike happens for each cloud.
  //LDTlightningRandomStart=0;
  //LDTlightningLength=0;
  //RDTlightningRandomStart=0;
  //RDTlightningLength=0;
//********************************************************************************************************************************************************


  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

#ifdef forcecloudcalculation
    if (cloudchance==255)
#else
    if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
    {
      //Pick a random number between 0 and 99
      cloudchance=random(100);
      // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
      if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
      // Check if today is day for clouds.
      if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
      // If we have cloud today
      if (cloudchance)
      {
        // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
        numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
        // pick the time that the first cloud will start
        // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
        cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));

//********************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//********************************************************************************************************************************************************

        // 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
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

// Force Cloud from Menu Function jumps to here to start cloud effect immediately for a show!
if (ForceCloud)
{
ForceCloud=false;
cloudchance=1;
cloudduration=8;
LDTlightningchance=1;
//RDTlightningchance=1;
cloudstart=NumMins(hour(),minute())+2;
//RDTcloudstart=NumMins(hour(),minute())+1;
//RDTcloudstartOffset=random(-1,1);
//RDTcloudstart=cloudstart+RDTcloudstartOffset;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // - Controls Cloud and Lightning Effect timings for both fixtures
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  if (cloudchance)
  {
    //is it time for cloud yet over Left Display Tank?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      // - Dims LDT to create cloud and back up to Daylight Parabola. - maybe change to 11 in order to make minimum cloud power 11% so the lights don't click off.
      RDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,10,180);
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,10,180);
      RDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,20,180);
      LDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,20,180);
      RDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_MoonLightANALOGValue,11,180);
      LDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_MoonLightANALOGValue,11,180);

      // To adjust possible length of time of lightning strike, Change the "second()< 5 " ... where 5 is the # of seconds of lightning during cloud.
      // and the "minute())=(cloudstart_(cloudduration/2)))" sets start time... dividing by 2 is exactly half way through cloud to trigger 5 seconds lightning.
      // if there is lightning and current time is 1/2 way through cloud, but less then 1/2 way through cloud + 5 seconds

     // if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/LDTlightningRandomStart))) && second()<LDTlightningLength)
      if (LDTlightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
      {
        if (random(100)<20) LDTlightningstatus=1;
        else LDTlightningstatus=0;
        if (LDTlightningstatus)
        {
               // - This will correctly flash lightning up to 100% power every lightning strike.
          //LDT_DaylightPWMValue=100;
          //LDT_ActinicPWMValue=100;
//_______________________________________________________________________________________________________
          // - Trying to make a random sample between, say 80% & 100% for the brightness of the lightning?
          // - If there will be lightning, pick a random number between 80 and 100 for the lightning power.
          LDT_LightningFlashPower=random(80,100);
          RDT_LightningFlashPower=random(80,100);

          RDT_DaylightPWMValue=RDT_LightningFlashPower;
          RDT_ActinicPWMValue=RDT_LightningFlashPower;
          RDT_MoonLightANALOGValue=RDT_LightningFlashPower;
          LDT_DaylightPWMValue=LDT_LightningFlashPower;
          LDT_ActinicPWMValue=LDT_LightningFlashPower;
          LDT_MoonLightANALOGValue=LDT_LightningFlashPower;
//_______________________________________________________________________________________________________
        }
        else
        {
          // - Changed these values from 0 to 11 so at end of lightning cycle, the go to min. 11% power
          RDT_DaylightPWMValue=11;
          RDT_ActinicPWMValue=20;
          RDT_MoonLightANALOGValue=11;
          LDT_DaylightPWMValue=11;
          LDT_ActinicPWMValue=20;
          LDT_MoonLightANALOGValue=11;
//------------------------------------------------------------------
        }
        delay(1);
      }
    }
  }
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    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);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  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));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      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));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
  long n=elapsedSecsToday(now());
  cstart*=60;
  cend*=60;
  if (n<cstart) return PWMStart;
  if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
  if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
  if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
  if (n>cend) return PWMStart;
  //End Cloud & Lighting
}
I looked up both the example files and code examples here in the forum and I can't find anything that I'm doing incorrectly.

When I try to verify the code, I get the following error:

Code: Select all

The following features were automatically added:
Watchdog Timer
Version Menu

The following features were detected:
Dimming Expansion Module
Dimming Signal
Wifi Attachment
Date/Time Setup Menu
Custom Menu
2014 Main Screen
Extra Font - Medium Size (8x8 pixels)
Number of Menu Options: 8
sketch_sept_11a_delayedOn.cpp: In function 'void setup()':
sketch_sept_11a_delayedOn:148: error: 'class ReefAngelClass' has no member named 'DelayedOn'
I was previously already running libraries 1.1.3 but had upgraded them from an old installer. Since it was not finding the class, I backed up my sketches, uninstalled the controller, deleted my documents/arduino and programs/arduino folders, restarted the computer, and re-installed from the ReefAngelInstaller_1.1.3 installer.

When I opened it back up, I'm still getting the same error. Based off the reference material I found, I'm not sure how else I'm supposed to use it in order to get it to function.

Re: Let's Build Troubadour11's Code

Posted: Sun May 05, 2019 11:04 am
by troubadour11
Hi,

I was wondering if anyone could help me set up a variable that change be changed via the app rather then hard-coded and requiring me to hook up a computer to the head-unit in order to change a function.

I am currently running a dosing pump off of one of my outlets and would like to be able to adjust the primary variables for it (how often it runs, and for how long).

Currently my code has this in there driving the pump.

Code: Select all

    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
So I'm assuming somewhere within the outer loop I'll need to create and declare 2 primary variables like "Dos7_HRinterval" and "Dos7_SECrun" for the interval hours and run-time seconds. Then drop that into above code similar to:

Code: Select all

    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    ReefAngel.DosingPumpRepeat( Port7,0,Dos7_HRinterval,Dos7_SECrun );
Beyond that, how do I access and connect those custom variables to the Reef Angel app on my phone so I can adjust values such as these without uploading new hard-coded values to the head-unit every time?

I see the "Memory" section in the app with the ability to see some variables that look to be there by default inside the code builder. But I'd like to get a clear example set up the connection between my dosing pump, the code, and the app all the way through. The memory page in the app I believe is where I would be able to access and change those custom variables in my above example? If I could get this example working, I could then extrapolate it out to other potential variables as well for things such as lighting schedules (pmw & analog slope curves ), etc?

Thanks in advance for anyone willing to help out. Very much appreciated :)

Here is the current full version of my code running on the head unit:

Code: Select all

//*************************************************************************************************************
//Start of PWM Expansion Code Header

//This is just how we are going to reference the PWM expansion ports within the code.
//You can change the labels if you would like, just as long as they are changed all throughout the code too.
                  //Standard PowerRelay PWM Daylight = L-DT White/Color
                  //Standard PowerRelay PWM Actinic = L-DT Blue/UV
#define LEDPWM0 0 // R-DT = White/Color
#define LEDPWM1 1 // R-DT = Blue/UV
#define LEDPWM2 2 // L-DT = MoonLight
#define LEDPWM3 3 // R-DT = Moonlight
#define LEDPWM4 4 // - EMPTY -
#define LEDPWM5 5 // - EMPTY -

//Initial values to all 6 channels at startup. They will always be 0.
byte PWMChannel[]={0,0,0,0,0,0};

//End of PWM Expansion Code Header
//*************************************************************************************************************

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <ReefAngel.h>

// - Creates a variable to control a "force cloud function" inside a menu.
boolean ForceCloud=false;

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
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";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
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();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

byte LDT_DaylightPWMValue=0;
byte LDT_ActinicPWMValue=0;
byte LDT_MoonLightANALOGValue=0;
byte RDT_DaylightPWMValue=0;
byte RDT_ActinicPWMValue=0;
byte RDT_MoonLightANALOGValue=0;

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.
 
    // Initialize the custom menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    ReefAngel.AddStandardMenu();  // Add Standard Menu
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen
 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    //ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Dosing Pump
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up correctly, I think.
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
     ReefAngel.PWM.SetChannel(LEDPWM4,0);
     ReefAngel.PWM.SetChannel(LEDPWM5,0);
     // I believe using this got rid of my bright flash to 100% when I uploaded new code. Specifically setting the Standard Daylight/Actinic
//****************************************************************************************************************************************************************

    // Adds the date and time menu to controller so I can change for Daylight Savings Time. **Takes up memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app. eventually
    //ReefAngel.AddDateTimeMenu();
        
    ////// Place additional initialization code above here //////
}

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
    
    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
    
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //Set up for Refugium and Phytoplankton light cycle

    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)
     //Turns Refugium Light plugged into port3 on at 10:30 pm and off at 5:30 am.
     ReefAngel.StandardLights(Port3,22,30,8,30);
     //Turns Phyto Light plugged into port4 on at 10:30 pm and off at 5:30 am.
     ReefAngel.StandardLights(Port4,22,30,8,30);

//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:55 p.m. and 10:00 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________  
 if ((NumMins(hour(now()),minute(now())) > NumMins(10,00)) && (NumMins(hour(now()),minute(now())) < NumMins(20,55)))
      {
         //RDT_ActinicPWMValue=PWMSlope(10,00,20,55,11,100,240,RDT_ActinicPWMValue);
         RDT_MoonLightANALOGValue=PWMSlope(10,00,20,55,11,100,240,RDT_MoonLightANALOGValue);
      }
      else
      {
         //RDT_ActinicPWMValue=0;
         RDT_MoonLightANALOGValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 9:05 p.m. and 10:15 p.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,15)) && (NumMins(hour(now()),minute(now())) < NumMins(21,05)))
     {
        //LDT_ActinicPWMValue=PWMSlope(10,15,21,05,11,100,240,LDT_ActinicPWMValue);
        LDT_MoonLightANALOGValue=PWMSlope(10,15,21,05,11,100,240,LDT_MoonLightANALOGValue);
      }
      else
      {
        //LDT_ActinicPWMValue=0;
        LDT_MoonLightANALOGValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 8:30 p.m. and 10:25 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color -
    // - Parabola dimming 11%-50%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,25)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_DaylightPWMValue=PWMSlope(10,25,20,30,11,50,240,RDT_DaylightPWMValue);
      }
      else
      {
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 8:45 p.m. and 10:35 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color -
    // - Parabola dimming 11%-50%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,35)) && (NumMins(hour(now()),minute(now())) < NumMins(20,45)))
     {
        LDT_DaylightPWMValue=PWMSlope(10,35,20,45,11,50,240,LDT_DaylightPWMValue);
      }
      else
      {
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                                   //// Set MoonLights Cycles ////
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //LDT_MoonLightANALOGValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_MoonLightANALOGValue=0));
    LDT_ActinicPWMValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_ActinicPWMValue=0));
    //RDT_MoonLightANALOGValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_MoonLightANALOGValue=0));
    RDT_ActinicPWMValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_ActinicPWMValue=0));

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM2,LDT_MoonLightANALOGValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM3,RDT_MoonLightANALOGValue);
//_________________________________________________________________________________________________

    ////// Place your custom code above here //////
 
    //Connects Reef Angel Head Unit to the Portal through WiFi Unit.
    ReefAngel.Portal("troubadour11");
    // This should always be the last line
    ReefAngel.ShowInterface();
}


//************************************************************************************************************************************************************************************************************************
  // - My lighting is set up as:
  //      - LDT (Left Display Tank) fixture on the "Standard" 2 (PWM signal) slots on my power relay box.
  //      - RDT (Right Display Tank) fixture on channels 0=daylight and 1=actinic (PWM signal) of the Dimming Expansion.
  //      - Moonlights = channels 2 and 3 of the PWM expansion. These drivers are Analog and the jumper pins have been set for 0-10v Analog in Dimming Expansion.
 
 // - I'd like to calculate the cloud effect for the LDT lights using the "Weather Sim for Standard PWM" code which I think
 //   I got copied, set up, and working correctly for the 1 fixture on the Standard Ports.
 //                 - Then find a way to feed those values into the RDT lights with a "Random Sample" of + or - a small time offset for the cloud effect on the RDT
 //                                - This way I could randomly generate a weather sim, and randomly have the cloud move Right-to-Left or Left-to-Right each time a cloud occurs

//                  - Lightning effect could either be off set with a similar way with +/- value OR left sync'd to LDT lightning (would need to test what looks good).
//                  - Or possibly more or less lightning per fixture during cloud.
//*************************************************************************************************************************************************************************************************************************

// Random Cloud/Thunderstorm effects function
void CheckCloud()
{

  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Change the values below to customize your cloud/storm effect

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
  // I should create a random variable here to randomize the days it has clouds?
#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 12

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

  // Only start the cloud effect after this setting
     // In this example, start could happen after 11:30am
#define Start_Cloud_After NumMins(13,00)

  // Always end the cloud effect before this setting
     // In this example, end must be before 6:45pm
#define End_Cloud_Before NumMins(19,45)

  // Percentage chance of lightning happening 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 results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

    //#define printdebug // Uncomment this for debug print on Serial Monitor window
  //#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.


  // Change the values above to customize your cloud/storm effect
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Do not change anything below here

  static byte cloudchance = 255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  //made individual lightningchance for each fixture
  static byte LDTlightningchance=0;
  //static byte RDTlightningchance=0;
  static byte cloudindex=0;
  //made separate lightning status for each fixture
  static byte LDTlightningstatus=0;
  //static byte RDTlightningstatus=0;
  static int LastNumMins=0;
//********************************************************************************************************************************************************
  // - I created this value to use as +/- offset value to make clouds pass left-to-right or vice-versa.
  //static int RDTcloudstartOffset=0;

  // - I added this to use as a randomly chosen value between (-)x to y.
  // - This will be added to "cloudstart" in order to determine the start time of the RDT cloud effect.
  //static byte RDTcloudstart=0;

  // - I added this to create a random sample between 80% and 100% dimming power for "random" lightning intensity effect on each fixture.
  static byte LDT_LightningFlashPower=0;
  static byte RDT_LightningFlashPower=0;

  // - I added these to create a variable to randomize when and how long each lightning strike happens for each cloud.
  //LDTlightningRandomStart=0;
  //LDTlightningLength=0;
  //RDTlightningRandomStart=0;
  //RDTlightningLength=0;
//********************************************************************************************************************************************************


  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

#ifdef forcecloudcalculation
    if (cloudchance==255)
#else
    if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
    {
      //Pick a random number between 0 and 99
      cloudchance=random(100);
      // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
      if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
      // Check if today is day for clouds.
      if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
      // If we have cloud today
      if (cloudchance)
      {
        // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
        numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
        // pick the time that the first cloud will start
        // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
        cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));

//********************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//********************************************************************************************************************************************************

        // 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
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

// Force Cloud from Menu Function jumps to here to start cloud effect immediately for a show!
if (ForceCloud)
{
ForceCloud=false;
cloudchance=1;
cloudduration=8;
LDTlightningchance=1;
//RDTlightningchance=1;
cloudstart=NumMins(hour(),minute())+2;
//RDTcloudstart=NumMins(hour(),minute())+1;
//RDTcloudstartOffset=random(-1,1);
//RDTcloudstart=cloudstart+RDTcloudstartOffset;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // - Controls Cloud and Lightning Effect timings for both fixtures
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  if (cloudchance)
  {
    //is it time for cloud yet over Left Display Tank?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      // - Dims LDT to create cloud and back up to Daylight Parabola. - maybe change to 11 in order to make minimum cloud power 11% so the lights don't click off.
      RDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,10,180);
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,10,180);
      RDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,20,180);
      LDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,20,180);
      RDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_MoonLightANALOGValue,11,180);
      LDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_MoonLightANALOGValue,11,180);

      // To adjust possible length of time of lightning strike, Change the "second()< 5 " ... where 5 is the # of seconds of lightning during cloud.
      // and the "minute())=(cloudstart_(cloudduration/2)))" sets start time... dividing by 2 is exactly half way through cloud to trigger 5 seconds lightning.
      // if there is lightning and current time is 1/2 way through cloud, but less then 1/2 way through cloud + 5 seconds

     // if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/LDTlightningRandomStart))) && second()<LDTlightningLength)
      if (LDTlightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
      {
        if (random(100)<20) LDTlightningstatus=1;
        else LDTlightningstatus=0;
        if (LDTlightningstatus)
        {
               // - This will correctly flash lightning up to 100% power every lightning strike.
          //LDT_DaylightPWMValue=100;
          //LDT_ActinicPWMValue=100;
//_______________________________________________________________________________________________________
          // - Trying to make a random sample between, say 80% & 100% for the brightness of the lightning?
          // - If there will be lightning, pick a random number between 80 and 100 for the lightning power.
          LDT_LightningFlashPower=random(80,100);
          RDT_LightningFlashPower=random(80,100);

          RDT_DaylightPWMValue=RDT_LightningFlashPower;
          RDT_ActinicPWMValue=RDT_LightningFlashPower;
          RDT_MoonLightANALOGValue=RDT_LightningFlashPower;
          LDT_DaylightPWMValue=LDT_LightningFlashPower;
          LDT_ActinicPWMValue=LDT_LightningFlashPower;
          LDT_MoonLightANALOGValue=LDT_LightningFlashPower;
//_______________________________________________________________________________________________________
        }
        else
        {
          // - Changed these values from 0 to 11 so at end of lightning cycle, the go to min. 11% power
          RDT_DaylightPWMValue=11;
          RDT_ActinicPWMValue=20;
          RDT_MoonLightANALOGValue=11;
          LDT_DaylightPWMValue=11;
          LDT_ActinicPWMValue=20;
          LDT_MoonLightANALOGValue=11;
//------------------------------------------------------------------
        }
        delay(1);
      }
    }
  }
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    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);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  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));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      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));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
  long n=elapsedSecsToday(now());
  cstart*=60;
  cend*=60;
  if (n<cstart) return PWMStart;
  if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
  if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
  if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
  if (n>cend) return PWMStart;
  //End Cloud & Lighting
}

Re: Let's Build Troubadour11's Code

Posted: Sun May 05, 2019 3:26 pm
by lnevo
Are you already using DosingPump built-in variables for Pump1,2, and 3?

For the DelayedOn issue, the post was old, so if you haven't figured it out, it should have been ReefAngel.Relay.DelayedOn(...); but you didn't include your code to confirm.

Re: Let's Build Troubadour11's Code

Posted: Sun May 05, 2019 5:27 pm
by troubadour11
Hello, hello :-)

No, I never did get the delayed on set up. That was the last time I was trying to update my code. But now I'm hitting a point I need to adjust the dosing pump and I'd love to be able to do it through the app if it's possible instead of having to upload new hard code.

I'm sorry, I thought I included my currently running code at the end of the last post.

I'll try it again here. I believe this is what I currently have loaded into my head unit:

Code: Select all

//*************************************************************************************************************
//Start of PWM Expansion Code Header

//This is just how we are going to reference the PWM expansion ports within the code.
//You can change the labels if you would like, just as long as they are changed all throughout the code too.
                  //Standard PowerRelay PWM Daylight = L-DT White/Color
                  //Standard PowerRelay PWM Actinic = L-DT Blue/UV
#define LEDPWM0 0 // R-DT = White/Color
#define LEDPWM1 1 // R-DT = Blue/UV
#define LEDPWM2 2 // L-DT = MoonLight
#define LEDPWM3 3 // R-DT = Moonlight
#define LEDPWM4 4 // - EMPTY -
#define LEDPWM5 5 // - EMPTY -

//Initial values to all 6 channels at startup. They will always be 0.
byte PWMChannel[]={0,0,0,0,0,0};

//End of PWM Expansion Code Header
//*************************************************************************************************************

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <ReefAngel.h>

// - Creates a variable to control a "force cloud function" inside a menu.
boolean ForceCloud=false;

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
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";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
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();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

byte LDT_DaylightPWMValue=0;
byte LDT_ActinicPWMValue=0;
byte LDT_MoonLightANALOGValue=0;
byte RDT_DaylightPWMValue=0;
byte RDT_ActinicPWMValue=0;
byte RDT_MoonLightANALOGValue=0;

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.
 
    // Initialize the custom menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    ReefAngel.AddStandardMenu();  // Add Standard Menu
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen
 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    //ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Dosing Pump
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up correctly, I think.
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
     ReefAngel.PWM.SetChannel(LEDPWM4,0);
     ReefAngel.PWM.SetChannel(LEDPWM5,0);
     // I believe using this got rid of my bright flash to 100% when I uploaded new code. Specifically setting the Standard Daylight/Actinic
//****************************************************************************************************************************************************************

    // Adds the date and time menu to controller so I can change for Daylight Savings Time. **Takes up memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app. eventually
    //ReefAngel.AddDateTimeMenu();
        
    ////// Place additional initialization code above here //////
}

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
    
    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
    
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //Set up for Refugium and Phytoplankton light cycle

    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)
     //Turns Refugium Light plugged into port3 on at 10:30 pm and off at 5:30 am.
     ReefAngel.StandardLights(Port3,22,30,8,30);
     //Turns Phyto Light plugged into port4 on at 10:30 pm and off at 5:30 am.
     ReefAngel.StandardLights(Port4,22,30,8,30);

//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:55 p.m. and 10:00 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________  
 if ((NumMins(hour(now()),minute(now())) > NumMins(10,00)) && (NumMins(hour(now()),minute(now())) < NumMins(20,55)))
      {
         //RDT_ActinicPWMValue=PWMSlope(10,00,20,55,11,100,240,RDT_ActinicPWMValue);
         RDT_MoonLightANALOGValue=PWMSlope(10,00,20,55,11,100,240,RDT_MoonLightANALOGValue);
      }
      else
      {
         //RDT_ActinicPWMValue=0;
         RDT_MoonLightANALOGValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 9:05 p.m. and 10:15 p.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,15)) && (NumMins(hour(now()),minute(now())) < NumMins(21,05)))
     {
        //LDT_ActinicPWMValue=PWMSlope(10,15,21,05,11,100,240,LDT_ActinicPWMValue);
        LDT_MoonLightANALOGValue=PWMSlope(10,15,21,05,11,100,240,LDT_MoonLightANALOGValue);
      }
      else
      {
        //LDT_ActinicPWMValue=0;
        LDT_MoonLightANALOGValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 8:30 p.m. and 10:25 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color -
    // - Parabola dimming 11%-50%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,25)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_DaylightPWMValue=PWMSlope(10,25,20,30,11,50,240,RDT_DaylightPWMValue);
      }
      else
      {
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 8:45 p.m. and 10:35 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color -
    // - Parabola dimming 11%-50%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,35)) && (NumMins(hour(now()),minute(now())) < NumMins(20,45)))
     {
        LDT_DaylightPWMValue=PWMSlope(10,35,20,45,11,50,240,LDT_DaylightPWMValue);
      }
      else
      {
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                                   //// Set MoonLights Cycles ////
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //LDT_MoonLightANALOGValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_MoonLightANALOGValue=0));
    LDT_ActinicPWMValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_ActinicPWMValue=0));
    //RDT_MoonLightANALOGValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_MoonLightANALOGValue=0));
    RDT_ActinicPWMValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_ActinicPWMValue=0));

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM2,LDT_MoonLightANALOGValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM3,RDT_MoonLightANALOGValue);
//_________________________________________________________________________________________________

    ////// Place your custom code above here //////
 
    //Connects Reef Angel Head Unit to the Portal through WiFi Unit.
    ReefAngel.Portal("troubadour11");
    // This should always be the last line
    ReefAngel.ShowInterface();
}


//************************************************************************************************************************************************************************************************************************
  // - My lighting is set up as:
  //      - LDT (Left Display Tank) fixture on the "Standard" 2 (PWM signal) slots on my power relay box.
  //      - RDT (Right Display Tank) fixture on channels 0=daylight and 1=actinic (PWM signal) of the Dimming Expansion.
  //      - Moonlights = channels 2 and 3 of the PWM expansion. These drivers are Analog and the jumper pins have been set for 0-10v Analog in Dimming Expansion.
 
 // - I'd like to calculate the cloud effect for the LDT lights using the "Weather Sim for Standard PWM" code which I think
 //   I got copied, set up, and working correctly for the 1 fixture on the Standard Ports.
 //                 - Then find a way to feed those values into the RDT lights with a "Random Sample" of + or - a small time offset for the cloud effect on the RDT
 //                                - This way I could randomly generate a weather sim, and randomly have the cloud move Right-to-Left or Left-to-Right each time a cloud occurs

//                  - Lightning effect could either be off set with a similar way with +/- value OR left sync'd to LDT lightning (would need to test what looks good).
//                  - Or possibly more or less lightning per fixture during cloud.
//*************************************************************************************************************************************************************************************************************************

// Random Cloud/Thunderstorm effects function
void CheckCloud()
{

  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Change the values below to customize your cloud/storm effect

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
  // I should create a random variable here to randomize the days it has clouds?
#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 12

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

  // Only start the cloud effect after this setting
     // In this example, start could happen after 11:30am
#define Start_Cloud_After NumMins(13,00)

  // Always end the cloud effect before this setting
     // In this example, end must be before 6:45pm
#define End_Cloud_Before NumMins(19,45)

  // Percentage chance of lightning happening 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 results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

    //#define printdebug // Uncomment this for debug print on Serial Monitor window
  //#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.


  // Change the values above to customize your cloud/storm effect
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Do not change anything below here

  static byte cloudchance = 255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  //made individual lightningchance for each fixture
  static byte LDTlightningchance=0;
  //static byte RDTlightningchance=0;
  static byte cloudindex=0;
  //made separate lightning status for each fixture
  static byte LDTlightningstatus=0;
  //static byte RDTlightningstatus=0;
  static int LastNumMins=0;
//********************************************************************************************************************************************************
  // - I created this value to use as +/- offset value to make clouds pass left-to-right or vice-versa.
  //static int RDTcloudstartOffset=0;

  // - I added this to use as a randomly chosen value between (-)x to y.
  // - This will be added to "cloudstart" in order to determine the start time of the RDT cloud effect.
  //static byte RDTcloudstart=0;

  // - I added this to create a random sample between 80% and 100% dimming power for "random" lightning intensity effect on each fixture.
  static byte LDT_LightningFlashPower=0;
  static byte RDT_LightningFlashPower=0;

  // - I added these to create a variable to randomize when and how long each lightning strike happens for each cloud.
  //LDTlightningRandomStart=0;
  //LDTlightningLength=0;
  //RDTlightningRandomStart=0;
  //RDTlightningLength=0;
//********************************************************************************************************************************************************


  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

#ifdef forcecloudcalculation
    if (cloudchance==255)
#else
    if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
    {
      //Pick a random number between 0 and 99
      cloudchance=random(100);
      // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
      if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
      // Check if today is day for clouds.
      if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
      // If we have cloud today
      if (cloudchance)
      {
        // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
        numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
        // pick the time that the first cloud will start
        // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
        cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));

//********************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//********************************************************************************************************************************************************

        // 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
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

// Force Cloud from Menu Function jumps to here to start cloud effect immediately for a show!
if (ForceCloud)
{
ForceCloud=false;
cloudchance=1;
cloudduration=8;
LDTlightningchance=1;
//RDTlightningchance=1;
cloudstart=NumMins(hour(),minute())+2;
//RDTcloudstart=NumMins(hour(),minute())+1;
//RDTcloudstartOffset=random(-1,1);
//RDTcloudstart=cloudstart+RDTcloudstartOffset;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // - Controls Cloud and Lightning Effect timings for both fixtures
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  if (cloudchance)
  {
    //is it time for cloud yet over Left Display Tank?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      // - Dims LDT to create cloud and back up to Daylight Parabola. - maybe change to 11 in order to make minimum cloud power 11% so the lights don't click off.
      RDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,10,180);
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,10,180);
      RDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,20,180);
      LDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,20,180);
      RDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_MoonLightANALOGValue,11,180);
      LDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_MoonLightANALOGValue,11,180);

      // To adjust possible length of time of lightning strike, Change the "second()< 5 " ... where 5 is the # of seconds of lightning during cloud.
      // and the "minute())=(cloudstart_(cloudduration/2)))" sets start time... dividing by 2 is exactly half way through cloud to trigger 5 seconds lightning.
      // if there is lightning and current time is 1/2 way through cloud, but less then 1/2 way through cloud + 5 seconds

     // if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/LDTlightningRandomStart))) && second()<LDTlightningLength)
      if (LDTlightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
      {
        if (random(100)<20) LDTlightningstatus=1;
        else LDTlightningstatus=0;
        if (LDTlightningstatus)
        {
               // - This will correctly flash lightning up to 100% power every lightning strike.
          //LDT_DaylightPWMValue=100;
          //LDT_ActinicPWMValue=100;
//_______________________________________________________________________________________________________
          // - Trying to make a random sample between, say 80% & 100% for the brightness of the lightning?
          // - If there will be lightning, pick a random number between 80 and 100 for the lightning power.
          LDT_LightningFlashPower=random(80,100);
          RDT_LightningFlashPower=random(80,100);

          RDT_DaylightPWMValue=RDT_LightningFlashPower;
          RDT_ActinicPWMValue=RDT_LightningFlashPower;
          RDT_MoonLightANALOGValue=RDT_LightningFlashPower;
          LDT_DaylightPWMValue=LDT_LightningFlashPower;
          LDT_ActinicPWMValue=LDT_LightningFlashPower;
          LDT_MoonLightANALOGValue=LDT_LightningFlashPower;
//_______________________________________________________________________________________________________
        }
        else
        {
          // - Changed these values from 0 to 11 so at end of lightning cycle, the go to min. 11% power
          RDT_DaylightPWMValue=11;
          RDT_ActinicPWMValue=20;
          RDT_MoonLightANALOGValue=11;
          LDT_DaylightPWMValue=11;
          LDT_ActinicPWMValue=20;
          LDT_MoonLightANALOGValue=11;
//------------------------------------------------------------------
        }
        delay(1);
      }
    }
  }
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    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);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  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));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      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));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
  long n=elapsedSecsToday(now());
  cstart*=60;
  cend*=60;
  if (n<cstart) return PWMStart;
  if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
  if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
  if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
  if (n>cend) return PWMStart;
  //End Cloud & Lighting
}

Re: Let's Build Troubadour11's Code

Posted: Sun May 05, 2019 5:40 pm
by troubadour11
lnevo wrote:Are you already using DosingPump built-in variables for Pump1,2, and 3?
I believe the only thing that I know of driving my dosing pump is this code right after the heater declaration just inside the void loop start.

//Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
ReefAngel.DosingPumpRepeat( Port7,0,120,5 );

other then that line of code, I have the "always on" port commented out for the dosing pump on port 7 so that it's off unless driven by the above code. Or at least I thought that is what was happening.

//Ports that are always on
ReefAngel.Relay.On( Port3 );
ReefAngel.Relay.On( Port4 );
ReefAngel.Relay.On( Port5 );
ReefAngel.Relay.On( Port6 );
//ReefAngel.Relay.On( Port7 );
ReefAngel.Relay.On( Port8 );
// Port1 = Heater
// Port2 = Heater
// Port3 = Refugium Light
// Port4 = Phyto Light
// Port5 = Return Pump
// Port6 = Protein Skimmer
// Port7 = Dosing Pump

Other then that I haven't set up or used anything to drive it. The built-in dosing pump variables for Pump 1,2,3 you mentioned. I can see in the Memory Location inside the app spots for Dosing Pump 1 & 2 with options for "On Hour" & On Minute, Repeat Interval, & Timer. Are these the correct pump variables you're referring to?

If yes, does that mean I can use those built-in options just dropped into the code running the dosing pump on port7 and easily change them via the app?

Basically drop built-in variables into this?:
//Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
ReefAngel.DosingPumpRepeat( Port7,0,120,5 );

Thanks again for looking at things for me.

Re: Let's Build Troubadour11's Code

Posted: Sun May 05, 2019 9:56 pm
by lnevo
Yeah, you just need to use the function:

ReefAngel.DosingPumpRepeat1(Port7);

The built in libraries support up to 3 pumps with the default memory variables which you can set in the uapp or on the portal.

Re: Let's Build Troubadour11's Code

Posted: Mon May 06, 2019 11:50 am
by troubadour11
Excellent. Thank you for the help with this!

So if I'm reading and understanding it correctly, I'd just modify my code to be like this as you said:
ReefAngel.DosingPumpRepeat1(Port7);

instead of original code: ReefAngel.DosingPumpRepeat( Port7,0,120,5 );

- Then go into the Memory function in the phone app and do a Read Value on Dosing Pump 1 Repeat Interval (is this every "x" minutes from midnight?).
- once I've read the value (currently it gives back a value of 60)
- I can then type in 120? (which is a repeat interval of every 120 minutes, or every 2 hrs?) into the value field
- Then click the Write Value button to update to the desired value

If that is correct, it's making sense to me so far and should be easy enough to implement.

In the new code layout using the built-in library support for up to 3 pumps. Where do I set the amount of time the pump run will run for each time?
- Is that just a read/write adjustment on the "Dosing Pump 1 Timer" in the uapp memory like the Repeat Interval?

I also see the memory positions for the Dosing Pump 1 On Hour/Minute. Do these need to be set up as well drive the pump correctly via the above update? Or can it be run with just the Repeat Interval and Timer settings?

Thanks again. I'm really looking forward to finally getting access to the internal memory for adjusting some things as needed. I appreciate the help.

Re: Let's Build Troubadour11's Code

Posted: Mon May 06, 2019 5:01 pm
by lnevo
I would recommend using the Uapp instead of the old ReefAngel app. The Internal Memory section is just like the portal page and you can browse the fields instead of trying to peek/poke the values directly.

There is a separate variable for Mem_B_DP1Timer and Mem_I_DP1RepeatInterval.

Yes the 120 would be 2 hours repeat and the time would be what is set in the Mem_B_DP1Timer variable.

Re: Let's Build Troubadour11's Code

Posted: Sat May 18, 2019 7:51 am
by troubadour11
Thanks for the help getting the Dosing Pump variable set up.

I was able to get it uploaded to the head unit and can adjust it accordingly now with the app.

I also went back to put in the DelayedOn function for my skimmer.

Does this look correct?

ReefAngel.Relay.DelayedOn(Port6, 5);

I also removed Port6 from the "Always On" list for the power unit in the setup section. This is because we want it off initially and then let it be controlled by the above line of code in the main loop, correct?

The code checked out fine when I tested it. But I just wanted to have it double checked to make sure it wouldn't cause any issues.

Re: Let's Build Troubadour11's Code

Posted: Sat May 18, 2019 9:43 pm
by lnevo
Yes, leave off the ,5 if you want to set that in memory as well. :)

Re: Let's Build Troubadour11's Code

Posted: Sat Jul 27, 2019 7:47 pm
by troubadour11
Here's the latest version of my code which I'm currently running:

Code: Select all

//*************************************************************************************************************
//Start of PWM Expansion Code Header

//This is just how we are going to reference the PWM expansion ports within the code.
//You can change the labels if you would like, just as long as they are changed all throughout the code too.
                  //Standard PowerRelay PWM Daylight = L-DT White/Color
                  //Standard PowerRelay PWM Actinic = L-DT Blue/UV
#define LEDPWM0 0 // R-DT = White/Color
#define LEDPWM1 1 // R-DT = Blue/UV
#define LEDPWM2 2 // L-DT = MoonLight
#define LEDPWM3 3 // R-DT = Moonlight
#define LEDPWM4 4 // - EMPTY -
#define LEDPWM5 5 // - EMPTY -

//Initial values to all 6 channels at startup. They will always be 0.
byte PWMChannel[]={0,0,0,0,0,0};

//End of PWM Expansion Code Header
//*************************************************************************************************************

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <PH.h>
#include <WaterLevel.h>
#include <Humidity.h>
#include <DCPump.h>
#include <ReefAngel.h>

// - Creates a variable to control a "force cloud function" inside a menu.
boolean ForceCloud=false;

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
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";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
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();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

byte LDT_DaylightPWMValue=0;
byte LDT_ActinicPWMValue=0;
byte LDT_MoonLightANALOGValue=0;
byte RDT_DaylightPWMValue=0;
byte RDT_ActinicPWMValue=0;
byte RDT_MoonLightANALOGValue=0;

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.
 
    // Initialize the custom menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    ReefAngel.AddStandardMenu();  // Add Standard Menu
    ReefAngel.Use2014Screen();  // Let's use 2014 Screen
 
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port5Bit | Port6Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = 0;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port1Bit | Port2Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    //ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Phyto Light
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Dosing Pump
      // Port8 = Refugium Pump
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up correctly, I think.
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
     ReefAngel.PWM.SetChannel(LEDPWM4,0);
     ReefAngel.PWM.SetChannel(LEDPWM5,0);
     // I believe using this got rid of my bright flash to 100% when I uploaded new code. Specifically setting the Standard Daylight/Actinic
//****************************************************************************************************************************************************************

    // Adds the date and time menu to controller so I can change for Daylight Savings Time. **Takes up memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app. eventually
    //ReefAngel.AddDateTimeMenu();
        
    ////// Place additional initialization code above here //////
}

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
    
    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    //ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
    ReefAngel.DosingPumpRepeat1(Port7);
    
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //Set up for Refugium and Phytoplankton light cycle

    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)
     //Turns Refugium Light plugged into port3 on at 10:30 pm and off at 5:30 am.
     ReefAngel.StandardLights(Port3,22,30,8,30);
     //Turns Phyto Light plugged into port4 on at 10:30 pm and off at 5:30 am.
     ReefAngel.StandardLights(Port4,22,30,8,30);

//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:55 p.m. and 10:00 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________  
 if ((NumMins(hour(now()),minute(now())) > NumMins(10,00)) && (NumMins(hour(now()),minute(now())) < NumMins(20,55)))
      {
         //RDT_ActinicPWMValue=PWMSlope(10,00,20,55,11,100,240,RDT_ActinicPWMValue);
         RDT_MoonLightANALOGValue=PWMSlope(10,00,20,55,11,100,240,RDT_MoonLightANALOGValue);
      }
      else
      {
         //RDT_ActinicPWMValue=0;
         RDT_MoonLightANALOGValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 9:05 p.m. and 10:15 p.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV -
    // - Parabola dimming 11%-100%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,15)) && (NumMins(hour(now()),minute(now())) < NumMins(21,05)))
     {
        //LDT_ActinicPWMValue=PWMSlope(10,15,21,05,11,100,240,LDT_ActinicPWMValue);
        LDT_MoonLightANALOGValue=PWMSlope(10,15,21,05,11,100,240,LDT_MoonLightANALOGValue);
      }
      else
      {
        //LDT_ActinicPWMValue=0;
        LDT_MoonLightANALOGValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 8:30 p.m. and 10:25 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color -
    // - Parabola dimming 11%-50%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,25)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
     {
        RDT_DaylightPWMValue=PWMSlope(10,25,20,30,11,50,240,RDT_DaylightPWMValue);
      }
      else
      {
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 8:45 p.m. and 10:35 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color -
    // - Parabola dimming 11%-50%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(10,35)) && (NumMins(hour(now()),minute(now())) < NumMins(20,45)))
     {
        LDT_DaylightPWMValue=PWMSlope(10,35,20,45,11,50,240,LDT_DaylightPWMValue);
      }
      else
      {
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                                   //// Set MoonLights Cycles ////
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    //LDT_MoonLightANALOGValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_MoonLightANALOGValue=0));
    LDT_ActinicPWMValue=PWMSlope(10,30,21,00,10,100,240,PWMSlope(21,20,23,15,20,MoonPhase()/5+10,30,LDT_ActinicPWMValue=0));
    //RDT_MoonLightANALOGValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_MoonLightANALOGValue=0));
    RDT_ActinicPWMValue=PWMSlope(10,20,20,50,10,100,240,PWMSlope(21,15,23,15,20,MoonPhase()/5+10,30,RDT_ActinicPWMValue=0));

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM2,LDT_MoonLightANALOGValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM3,RDT_MoonLightANALOGValue);
//_________________________________________________________________________________________________

    ////// Place your custom code above here //////
 
    //Connects Reef Angel Head Unit to the Portal through WiFi Unit.
    ReefAngel.Portal("troubadour11");
    // This should always be the last line
    ReefAngel.ShowInterface();
}


//************************************************************************************************************************************************************************************************************************
  // - My lighting is set up as:
  //      - LDT (Left Display Tank) fixture on the "Standard" 2 (PWM signal) slots on my power relay box.
  //      - RDT (Right Display Tank) fixture on channels 0=daylight and 1=actinic (PWM signal) of the Dimming Expansion.
  //      - Moonlights = channels 2 and 3 of the PWM expansion. These drivers are Analog and the jumper pins have been set for 0-10v Analog in Dimming Expansion.
 
 // - I'd like to calculate the cloud effect for the LDT lights using the "Weather Sim for Standard PWM" code which I think
 //   I got copied, set up, and working correctly for the 1 fixture on the Standard Ports.
 //                 - Then find a way to feed those values into the RDT lights with a "Random Sample" of + or - a small time offset for the cloud effect on the RDT
 //                                - This way I could randomly generate a weather sim, and randomly have the cloud move Right-to-Left or Left-to-Right each time a cloud occurs

//                  - Lightning effect could either be off set with a similar way with +/- value OR left sync'd to LDT lightning (would need to test what looks good).
//                  - Or possibly more or less lightning per fixture during cloud.
//*************************************************************************************************************************************************************************************************************************

// Random Cloud/Thunderstorm effects function
void CheckCloud()
{

  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Change the values below to customize your cloud/storm effect

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
  // I should create a random variable here to randomize the days it has clouds?
#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 12

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

  // Only start the cloud effect after this setting
     // In this example, start could happen after 11:30am
#define Start_Cloud_After NumMins(13,00)

  // Always end the cloud effect before this setting
     // In this example, end must be before 6:45pm
#define End_Cloud_Before NumMins(19,45)

  // Percentage chance of lightning happening 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 results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

    //#define printdebug // Uncomment this for debug print on Serial Monitor window
  //#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.


  // Change the values above to customize your cloud/storm effect
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Do not change anything below here

  static byte cloudchance = 255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  //made individual lightningchance for each fixture
  static byte LDTlightningchance=0;
  //static byte RDTlightningchance=0;
  static byte cloudindex=0;
  //made separate lightning status for each fixture
  static byte LDTlightningstatus=0;
  //static byte RDTlightningstatus=0;
  static int LastNumMins=0;
//********************************************************************************************************************************************************
  // - I created this value to use as +/- offset value to make clouds pass left-to-right or vice-versa.
  //static int RDTcloudstartOffset=0;

  // - I added this to use as a randomly chosen value between (-)x to y.
  // - This will be added to "cloudstart" in order to determine the start time of the RDT cloud effect.
  //static byte RDTcloudstart=0;

  // - I added this to create a random sample between 80% and 100% dimming power for "random" lightning intensity effect on each fixture.
  static byte LDT_LightningFlashPower=0;
  static byte RDT_LightningFlashPower=0;

  // - I added these to create a variable to randomize when and how long each lightning strike happens for each cloud.
  //LDTlightningRandomStart=0;
  //LDTlightningLength=0;
  //RDTlightningRandomStart=0;
  //RDTlightningLength=0;
//********************************************************************************************************************************************************


  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

#ifdef forcecloudcalculation
    if (cloudchance==255)
#else
    if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
    {
      //Pick a random number between 0 and 99
      cloudchance=random(100);
      // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
      if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
      // Check if today is day for clouds.
      if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
      // If we have cloud today
      if (cloudchance)
      {
        // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
        numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
        // pick the time that the first cloud will start
        // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
        cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));

//********************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//********************************************************************************************************************************************************

        // 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
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

// Force Cloud from Menu Function jumps to here to start cloud effect immediately for a show!
if (ForceCloud)
{
ForceCloud=false;
cloudchance=1;
cloudduration=8;
LDTlightningchance=1;
//RDTlightningchance=1;
cloudstart=NumMins(hour(),minute())+2;
//RDTcloudstart=NumMins(hour(),minute())+1;
//RDTcloudstartOffset=random(-1,1);
//RDTcloudstart=cloudstart+RDTcloudstartOffset;
}

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  // - Controls Cloud and Lightning Effect timings for both fixtures
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  if (cloudchance)
  {
    //is it time for cloud yet over Left Display Tank?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      // - Dims LDT to create cloud and back up to Daylight Parabola. - maybe change to 11 in order to make minimum cloud power 11% so the lights don't click off.
      RDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,10,180);
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,10,180);
      RDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_DaylightPWMValue,20,180);
      LDT_ActinicPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,20,180);
      RDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,RDT_MoonLightANALOGValue,11,180);
      LDT_MoonLightANALOGValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_MoonLightANALOGValue,11,180);

      // To adjust possible length of time of lightning strike, Change the "second()< 5 " ... where 5 is the # of seconds of lightning during cloud.
      // and the "minute())=(cloudstart_(cloudduration/2)))" sets start time... dividing by 2 is exactly half way through cloud to trigger 5 seconds lightning.
      // if there is lightning and current time is 1/2 way through cloud, but less then 1/2 way through cloud + 5 seconds

     // if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/LDTlightningRandomStart))) && second()<LDTlightningLength)
      if (LDTlightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
      {
        if (random(100)<20) LDTlightningstatus=1;
        else LDTlightningstatus=0;
        if (LDTlightningstatus)
        {
               // - This will correctly flash lightning up to 100% power every lightning strike.
          //LDT_DaylightPWMValue=100;
          //LDT_ActinicPWMValue=100;
//_______________________________________________________________________________________________________
          // - Trying to make a random sample between, say 80% & 100% for the brightness of the lightning?
          // - If there will be lightning, pick a random number between 80 and 100 for the lightning power.
          LDT_LightningFlashPower=random(80,100);
          RDT_LightningFlashPower=random(80,100);

          RDT_DaylightPWMValue=RDT_LightningFlashPower;
          RDT_ActinicPWMValue=RDT_LightningFlashPower;
          RDT_MoonLightANALOGValue=RDT_LightningFlashPower;
          LDT_DaylightPWMValue=LDT_LightningFlashPower;
          LDT_ActinicPWMValue=LDT_LightningFlashPower;
          LDT_MoonLightANALOGValue=LDT_LightningFlashPower;
//_______________________________________________________________________________________________________
        }
        else
        {
          // - Changed these values from 0 to 11 so at end of lightning cycle, the go to min. 11% power
          RDT_DaylightPWMValue=11;
          RDT_ActinicPWMValue=20;
          RDT_MoonLightANALOGValue=11;
          LDT_DaylightPWMValue=11;
          LDT_ActinicPWMValue=20;
          LDT_MoonLightANALOGValue=11;
//------------------------------------------------------------------
        }
        delay(1);
      }
    }
  }
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    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);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  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));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      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));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
  long n=elapsedSecsToday(now());
  cstart*=60;
  cend*=60;
  if (n<cstart) return PWMStart;
  if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
  if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
  if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
  if (n>cend) return PWMStart;
  //End Cloud & Lighting
}

Re: Let's Build Troubadour11's Code

Posted: Sat Jul 27, 2019 8:07 pm
by troubadour11
A question on setting up my refugium light cycles with variables in memory.

In my current code shared in the previous post I have this section setting up my refugium lights on ports 3 & 4.

I do not need the ability to ramp the lighting at all on the fuge. Just turn it on/off at said times like I have hard-coded in there now. But I would like to set up the code like we did for my dosing pump so I can modify it for longer/shorter photo-periods through the app instead of uploading new hard-coded numbers.

Here's what I'm currently using clipped out for reference.

Code: Select all

    //Set up for Refugium and Phytoplankton light cycle
    //USE THIS TO TOGGLE PORTS ON/OFF - ReefAngel.StandardLight(relay,onhour,onminute,offhour,offminute)
     ReefAngel.StandardLights(Port3,22,30,8,30);
     ReefAngel.StandardLights(Port4,22,30,8,30);
How do I call some of the pre-set memory slots to use for the simple on/off times? Do I use the code below, similar to how we set up the dosing pump repeat function?
ReefAngel.StandardLights(Port3);
ReefAngel.StandardLights(Port4);

Then set the custom variables for StdLights On Hour/Minute and StdLights Off Hour/Minute and that will drive the on/off times?

Thanks for any help you can offer.

My hope is to take this and grow it out to my LED light controls so I can adjust the on/off and ramp times through memory instead of hard-coding them as well.

Re: Let's Build Troubadour11's Code

Posted: Sat Jul 27, 2019 8:43 pm
by rimai
Yeah, that should work.

Re: Let's Build Troubadour11's Code

Posted: Sat Oct 19, 2019 4:18 pm
by troubadour11
lnevo wrote:I would recommend using the Uapp instead of the old ReefAngel app. The Internal Memory section is just like the portal page and you can browse the fields instead of trying to peek/poke the values directly.
Just a quick question on interfaces and versions.

I have an RA+ unit with Dimming and Wifi expansions. Am I able to use the U-app as was recommended above? Seems like it's something I don't have access too. Or maybe I just can't find the info on how to set it up correctly.

I've struggled finding documentation on how to get it to work despite my Portal functioning just fine. And from what was mentioned above it seems like an easier way to update my controller then the "old ReefAngel app."

From what I can find, the only way to get full function and capabilities of the supporting software for a ReefAngel system is to drop $400 on a new version of the entire system or if I'm lucky, just an update of $120 to the Cloud Wifi attachment when I already had purchased a head-unit and Wifi Attachment for the system?

Thanks for the info and sorry for any confusion on my end. I'm thinking of building out the system some more, but I'm not sure if my hardware got orphaned or not.

Re: Let's Build Troubadour11's Code

Posted: Sat Oct 19, 2019 8:15 pm
by rimai
Yes, you can use it.
The only thing you get with the cloud wifi attachment over the one you have is the ability to connect to Uapp without having to setup port forwarding on your router. The cloud feature also lets you stream data so Uapp updates on the fly without having to hit the refresh button on Uapp. Because the connection is always established, the cloud feature is a lot more reliable.
To setup Uapp for the your regular wifi, simply add a controller and make sure you enter the ip address and port and do not enter the cloud credentials. Make sure you setup port forwarding or you won't be able to connect.