Page 1 of 2

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.

Re: Let's Build Troubadour11's Code

Posted: Sun Nov 17, 2019 1:11 pm
by troubadour11
rimai wrote: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.
Thanks! That was easy. I already had static dns & port-forwarding set up from the wifi-connection and portal anyway. It's running on both my phone and desktop now with no problem.

Re: Let's Build Troubadour11's Code

Posted: Thu Nov 21, 2019 7:23 pm
by troubadour11
So the Uapp integration went just fine. Again, thank you for the help with getting my RA system caught up to date.

A couple probably quick / easy questions on updating the head-unit.

I've still been manually updating my code by taking a laptop into my fish tank closet and pushing the code up to it via the cable. It seems like that has been left behind in favor of moving the the Web Wizard? Which seems awesome, having access to a code writer and my code library from anywhere. So I'd like to eventually get all my software caught up to the most current / best way to go about doing things. Which is the Web Wizard and the Uapp, correct?

Is there anyway for me to push code updates to the head-unit with the RA+ and Wifi connection? I'm guessing no, but thought I would ask before I just assumed there was no solution. Is the solution probably the cloud function I'm guessing or the blue-tooth connection you don't sell anymore?

My current sketch compiled, uploaded, and runs on my head-unit with no problem via the Ardurino Software and manual upload like when I originally got the RA unit. But when I copy / paste that exact code into the Web Wizard, it fails to compile the loop.

So could you briefly walk me through what the new / best interfaces are that are being supported for working on my system / getting the code adjusted and uploaded. And maybe any hints at why it can compile on "my old way" and why it fails and can't use the Web Wizard to compile? I'm guessing here: but is it a libraries or something issue that I need updated to run things via the Web Wizard now?

I'm in the process of starting to modify my system some and build it after letting it run rock solid for all these years as it was set up. For now, I can make the simple necessary adjustments to the current code and push it up the old way as I just need to modify a couple Relay's and what they run.

But the goal is to get it all up-to-date and easier to adjust / modify via accessing the memory locations and variables instead of always pushing new code.

As always, thanks for the help! It is greatly appreciated!!!

Re: Let's Build Troubadour11's Code

Posted: Fri Nov 22, 2019 10:24 pm
by rimai
Can you post your code?
No, there is no way to update through wifi.

Re: Let's Build Troubadour11's Code

Posted: Sun Nov 24, 2019 9:45 am
by troubadour11
rimai wrote:Can you post your code?
No, there is no way to update through wifi.
Absolutely. You will find the code I just pushed up to the head-unit this morning in the next / latest post.

However, I have a more pressing question about the dosing pump memory locations and the offset over the web-wizard code update. It should be pretty simple I hope.

I just received my 2 ATO pumps and got them physically set up (thanks for getting the 2nd one sent out so quickly!) :)

I wanted to run the pumps simultaneously in/out for an AWC system. I hooked them all up, ran a 100 ml timed calibration test, etc.

I want to change 1 gal total per day spread out over 6 runs a day. I need to run each pump 177 and 170 seconds (6 x per day)

In the code I pushed this morning: (both ports removed from "always on" list)

Code: Select all

//Sets Port 7 on Power Unit to a dosing pump running on the Repeat and Time function set for Dosing Pump 1 in internal memory
    // Auto Water Change - New Sea Water going in
    ReefAngel.DosingPumpRepeat1(Port7);
    //Sets Port 8 on Power Unit to a dosing pump running on the Repeat and Time function set for Dosing Pump 2 in internal memory
    // Auto Water Change - Old Sea Water going out
    ReefAngel.DosingPumpRepeat2(Port8);
Then I wanted to use the Uapp to set the internal memory for the correct intervals
DP1 Repeat = 240 (in minutes, every 4 hours)
DP1 Timer = 177 (in seconds)

DP2 Repeat = 240 (in minutes, every 4 hours)
DP2 Timer = 170 (in seconds)

I noticed it says in there that DP-2 has a 5 min offset and DP-3 a 10 minute offset. Is this true in the way I have it set up and used?

I had planned to run in/out at the same time drawing from the drain chamber and feeding into the return chamber. I used a gravity fed ATO with Kalk, so lowering the level is not an option. With the way I have it set up, it looks like I might pump my 631 ml of NSW into the system, then 5 minutes offset pull 631 ml back out. Which is ok and better then having set it up the other way and lowering my water level. But I'll have to test if the extra water will constantly cause my skimmer to over-flow. If they were triggered at the same time, the level wouldn't fluctuate as much. But this small of an amount may not be an issue in the long run if that's the best way to do it while utilizing internal memory that I can adjust as needed.

So with how I set it up, will I be getting that offset, or will they both just run at the same time if I set them to the same Repeat Interval?

As a "next-step" to my AWC system. I'd like to make some functions that I can change "x-amount" of water with a push button trigger. Like a pre-programmed 5, 10, 20 gallon water-change using the AWC system. In case I ever need to do a big change for some emergency reason. But first, I just will be thrilled to have it running the 1 gal / day! :P

As always, awesome hardware and thanks for the help! :roll:

Latest code is solo in the next post:

Re: Let's Build Troubadour11's Code

Posted: Sun Nov 24, 2019 9:47 am
by troubadour11
This compiled and pushed up fine on my laptops Ardurino set up.

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
 
    // 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 = Port5Bit | Port6Bit;
    // 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 inside main loop section
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Refugium Pump
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Auto Water Change - New Sea Water
      // Port8 = Auto Water Change - Old Sea Water
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up, 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
//****************************************************************************************************************************************************************

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

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
    
    //Always on ports that have a delay. These ports will delay turning on for the specified MINUTE_DELAY
      // for: Controller Power Up, Feeding Mode (if toggled on), Water Change Mode (if toggled)
      // This function needs to be place dinside loop() and not inside setup()
      // Usage is DelayedOn (Port6, 5);
    ReefAngel.Relay.DelayedOn(Port6);
    
    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    //ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
    //Sets Port 7 on Power Unit to a dosing pump running on the Repeat and Time function set for Dosing Pump 1 in internal memory
    // Auto Water Change - New Sea Water going in
    ReefAngel.DosingPumpRepeat1(Port7);
    //Sets Port 8 on Power Unit to a dosing pump running on the Repeat and Time function set for Dosing Pump 2 in internal memory
    // Auto Water Change - Old Sea Water going out
    ReefAngel.DosingPumpRepeat2(Port8);
    
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //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);
//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// 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 Nov 24, 2019 11:27 am
by rimai
The code not compiling is because we changed a little bit how the menu items are declared.
This should compile:

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
const char  menu1_label[] PROGMEM = "Feeding";
const char  menu2_label[] PROGMEM = "Water Change";
const char  menu3_label[] PROGMEM = "ATO Clear";
const char  menu4_label[] PROGMEM = "Overheat Clear";
const char  menu5_label[] PROGMEM = "PH Calibration";
const char  menu6_label[] PROGMEM = "Date / Time";
const char  menu7_label[] PROGMEM = "Version";
const char  menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char * const menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// 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
 
    // 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 = Port5Bit | Port6Bit;
    // 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 inside main loop section
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Refugium Pump
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Auto Water Change - New Sea Water
      // Port8 = Auto Water Change - Old Sea Water
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up, 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
//****************************************************************************************************************************************************************

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

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
   
    //Always on ports that have a delay. These ports will delay turning on for the specified MINUTE_DELAY
      // for: Controller Power Up, Feeding Mode (if toggled on), Water Change Mode (if toggled)
      // This function needs to be place dinside loop() and not inside setup()
      // Usage is DelayedOn (Port6, 5);
    ReefAngel.Relay.DelayedOn(Port6);
   
    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    //ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
    //Sets Port 7 on Power Unit to a dosing pump running on the Repeat and Time function set for Dosing Pump 1 in internal memory
    // Auto Water Change - New Sea Water going in
    ReefAngel.DosingPumpRepeat1(Port7);
    //Sets Port 8 on Power Unit to a dosing pump running on the Repeat and Time function set for Dosing Pump 2 in internal memory
    // Auto Water Change - Old Sea Water going out
    ReefAngel.DosingPumpRepeat2(Port8);
   
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //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);
//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// 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 Nov 24, 2019 11:32 am
by rimai
troubadour11 wrote:
rimai wrote:Can you post your code?
No, there is no way to update through wifi.
Absolutely. You will find the code I just pushed up to the head-unit this morning in the next / latest post.

However, I have a more pressing question about the dosing pump memory locations and the offset over the web-wizard code update. It should be pretty simple I hope.

I just received my 2 ATO pumps and got them physically set up (thanks for getting the 2nd one sent out so quickly!) :)

I wanted to run the pumps simultaneously in/out for an AWC system. I hooked them all up, ran a 100 ml timed calibration test, etc.

I want to change 1 gal total per day spread out over 6 runs a day. I need to run each pump 177 and 170 seconds (6 x per day)

In the code I pushed this morning: (both ports removed from "always on" list)

Code: Select all

//Sets Port 7 on Power Unit to a dosing pump running on the Repeat and Time function set for Dosing Pump 1 in internal memory
    // Auto Water Change - New Sea Water going in
    ReefAngel.DosingPumpRepeat1(Port7);
    //Sets Port 8 on Power Unit to a dosing pump running on the Repeat and Time function set for Dosing Pump 2 in internal memory
    // Auto Water Change - Old Sea Water going out
    ReefAngel.DosingPumpRepeat2(Port8);
Then I wanted to use the Uapp to set the internal memory for the correct intervals
DP1 Repeat = 240 (in minutes, every 4 hours)
DP1 Timer = 177 (in seconds)

DP2 Repeat = 240 (in minutes, every 4 hours)
DP2 Timer = 170 (in seconds)

I noticed it says in there that DP-2 has a 5 min offset and DP-3 a 10 minute offset. Is this true in the way I have it set up and used?

I had planned to run in/out at the same time drawing from the drain chamber and feeding into the return chamber. I used a gravity fed ATO with Kalk, so lowering the level is not an option. With the way I have it set up, it looks like I might pump my 631 ml of NSW into the system, then 5 minutes offset pull 631 ml back out. Which is ok and better then having set it up the other way and lowering my water level. But I'll have to test if the extra water will constantly cause my skimmer to over-flow. If they were triggered at the same time, the level wouldn't fluctuate as much. But this small of an amount may not be an issue in the long run if that's the best way to do it while utilizing internal memory that I can adjust as needed.

So with how I set it up, will I be getting that offset, or will they both just run at the same time if I set them to the same Repeat Interval?

As a "next-step" to my AWC system. I'd like to make some functions that I can change "x-amount" of water with a push button trigger. Like a pre-programmed 5, 10, 20 gallon water-change using the AWC system. In case I ever need to do a big change for some emergency reason. But first, I just will be thrilled to have it running the 1 gal / day! :P

As always, awesome hardware and thanks for the help! :roll:

Latest code is solo in the next post:
In this case, you can use the raw function.

Code: Select all

ReefAngel.	DosingPumpRepeat(Port7, 0, InternalMemory.DP1RepeatInterval_read(), InternalMemory.DP1Timer_read());
ReefAngel.	DosingPumpRepeat(Port8, 0, InternalMemory.DP2RepeatInterval_read(), InternalMemory.DP2Timer_read());
The zero in the function is the offset.
DosingPumpRepeat1 basically calls the above function with zero offset and DosingPumpRepeat2 calls it with 5 offset.

Re: Let's Build Troubadour11's Code

Posted: Mon Nov 25, 2019 3:38 pm
by troubadour11
In this case, you can use the raw function.

Code: Select all

ReefAngel.	DosingPumpRepeat(Port7, 0, InternalMemory.DP1RepeatInterval_read(), InternalMemory.DP1Timer_read());
ReefAngel.	DosingPumpRepeat(Port8, 0, InternalMemory.DP2RepeatInterval_read(), InternalMemory.DP2Timer_read());
The zero in the function is the offset.
DosingPumpRepeat1 basically calls the above function with zero offset and DosingPumpRepeat2 calls it with 5 offset.
This worked great. Thank you! :D

The Auto Water Change is up and running with the new pumps as expected!

Thanks again! I'll work on checking out and applying the fixes to move to the Web Wizard update soon. I'm glad it sounds like an easy update to the code.

Re: Let's Build Troubadour11's Code

Posted: Sun Mar 22, 2020 2:39 pm
by troubadour11
I have a couple horizontal mount float switches that I would like to install and set up a few alerts to let me know when the switches are triggered.
1.) bottom of NewSeaWater barrel in the AWC system to alert when empty and turn off AWC system (2 ports on relay) until cleared
2.) bottom of ATO container to alert when empty and refill needed
3.) I do have a standard float I'd set up in my OldSeaWater barrel in the AWC system as safety measure to alert and turn off the AWC system if the barrel is to full. This would just be a fail-safe back-up to the low-water trigger in the NSW barrel once I have the first two installs working.

Right now I'm not using any of the float switch ports on my RA+.

On the hardware side:
- Is it as simple as installing the switches in place, and then running a pair of wires to bridge
the distance between the RA+ pig-tails plugged into the port?
- Does matter which wire connects to which end of the pig-tail connector at the RA unit?
- Is there a max distance the wires can run between water storage barrels and the head-unit to trigger alerts and turn on/off relay outlets?

On the software side:
I see reference to setting up the WifiAlerts. Which is I think what I need to get going to flag when the float switches triggered. Is there some good existing documentation you can point me to on setting that up? Or give me a point in the right direction for this update.

Thanks for any help! It's been nice slowly adding more helpful tools into my system as it grows and develops. And the community has always been helpful in building the design!

I believe this should be my latest code that I pushed around 11/24/19:

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
 
    // 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 = Port5Bit | Port6Bit;
    // 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 inside main loop section
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = Refugium Light
      // Port4 = Refugium Pump
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = Auto Water Change - New Sea Water
      // Port8 = Auto Water Change - Old Sea Water
 
    ////// Place additional initialization code below here //////
 
// ***PLEASE HELP HERE********************************************************************************************************************************************
     //Sets values of ALL lighting channels to 0% on Start Up, 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
//****************************************************************************************************************************************************************

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

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
 
    ////// Place your custom code below here //////
    
    //Always on ports that have a delay. These ports will delay turning on for the specified MINUTE_DELAY
      // for: Controller Power Up, Feeding Mode (if toggled on), Water Change Mode (if toggled)
      // This function needs to be place dinside loop() and not inside setup()
      // Usage is DelayedOn (Port6, 5);
    ReefAngel.Relay.DelayedOn(Port6);
    
    //Sets Port 7 on the Power Unit to a dosing pump running for 5 seconds every 2 hours.
    //ReefAngel.DosingPumpRepeat( Port7,0,120,5 );
    //Sets Port 7 on Power Unit to a dosing pump running on the Repeat and Time function set for Dosing Pump 1 in internal memory
    // Auto Water Change - New Sea Water going in
    ReefAngel.DosingPumpRepeat1(Port7);
    //Sets Port 8 on Power Unit to a dosing pump running on the Repeat and Time function set for Dosing Pump 2 in internal memory
    // Auto Water Change - Old Sea Water going out
    ReefAngel.DosingPumpRepeat2(Port8);
    
//-----------------------------------------------------------------------------------------------------------------------------------------------------//  
    //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);
//-----------------------------------------------------------------------------------------------------------------------------------------------------//

//*********************************************************************************************************************************************************
                                   //// 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 Mar 22, 2020 6:19 pm
by rimai
Yes, you can just splice a pair of wires. There is no polarity and distance shouldn't matter.
You don't need to use the wifi alerts.
You can use the built-in portal alerts for ATO High and ATO Low ports with a condition to be = 1 or > 0. Either should work fine.