Page 1 of 2

Let's Build Troubadour11's Code

Posted: Sat Mar 15, 2014 12:31 pm
by troubadour11
Hello Everybody!

So it's been a long time coming, but I'm finally making some good progress on my aquarium build and am jumping into the Reef Angel Programming. I am sure I will be needing help with the code, both now and as I continue to build my system. So I'll be keeping this thread up to date with my latest code set up for my system. That way there will be a place for me to ask for help, with all the development history right in one place.

A short intro to where the system I'm developing is currently at equipment wise for programming the controller...
- 90 gallon mixed reef display with 75 gallon refugium
- protein skimmer
- uv sterilizer
- return pump
- 2 heaters
- refugium canister filter/pump
- (2) 1200gph power heads and wavemaker timer
- Static powerhead for added refugium flow
- Reef Angel Plus (PWM signal) and Dimming Expansion
- Gravity fed 20 gallon ATO
- 10 gallon QT w/ filter, air pump, heater, light
- DIY Rapid LED Dimmable Lighting
- 2 fixtures each with 3 strings
- Clear White / Neutral White / Deep Red / Cyan / Blue - Daylight C/W
- Royal Blue / UV (Violet, near UV) - Daylight Actinic B/UV
- Blue/UV - Moonlight
- All 3 strings per fixture are separately dimmable.
- The daylights are PWM signal, and the moonlights are 0-10v Analog
- I also have the refugium lighting running on a separate surge strip timer.

I expect to add the WiFi attachment eventually but do not have it yet.

That basically covers my equipment that's currently in the set up and I'm working toward controlling. I expect at some point to need to add things like dosing pumps, better powerheads to code functions for, LED refugium lights, and the list could go on and on I'm sure :lol: But you get the idea.

Now onto me setting up and building my code. Obviously I'm not going to tackle all those things at once. I've got my system up and running and just completed the Rapid LED build. I have the RA unit mounted and running the LED lights and equipment. But it is definitely a work in progress and just the barebones code to get the lights to dim on and off.

My first step here was to get it up and running and powering equipment, monitoring heaters, and dimming the new LEDs. Then I'd like to move onto custom menus, custom information displays and adding some functions into the menus. I'm thinking things like a "forced weather simulation" (for example fun), lighting cycle test (run 24 hour program over shorter time period), functional wave profiles for powerheads, a menu selection to run a programmable coral acclimation lighting schedule. I'd also like to learn a bit about information logging and portal use. But I expect those options to be quite limited until I can add on the WiFi attachment.

So those are the general long term goals of my RA code development. I'm sure it will morph and change as I accomplish phases and the system develops. But I want to log and share my set up and code work in case it can help anyone reach their goals in the future.

Now onto my first code... 8-) and thanks for your interest and help!

Re: Let's Build Troubadour11's Code

Posted: Sat Mar 15, 2014 12:49 pm
by troubadour11
So this is where I started...

I'm pretty sure I've uploaded the initial memory file correctly, but feel I need some clarification on what I should be storing in that file. And what is safe to store in the PDE code. Do lightning schedules and things need to be stored in memory in case of power outages and things? What really should I be keeping in this location and how to set it up?

Then I used the Wizard to step through and generate some barebones code to get me started. Since running through the wizard a few times, I've moved on to figuring out more advanced control through mostly copy and paste from the forums. Cheers to everybody's help that you've already given! ;)

So here is what my "1st code" turned into after a little dabbling. For the most part, it has a staggered sun rise / sun set effect. I believe I also have a moon rise / moon set function started. I think the cloud weather simulation is working correctly for only the left display tank light fixture. But I have been at work and not seen the weather sim yet. And it only works on the left display fixture because that is the fixture wired to the (2) PWM ports on the power relay. I used the "Weather Sim for Standard PWM Ports" code to get this set up.

A Starting Point:

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.
#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>

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

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

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


void setup()
{
    // This must be the first line
    ReefAngel.Init();  //Initialize controller
    ReefAngel.AddStandardMenu();  // Add Standard Menu

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

    // Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = EMPTY - Always On
      // Port4 = EMPTY - Always On
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = UV Sterilizer
      // Port8 = Refugium Pump
    
    ////// Place additional initialization code below here //////

    // Adds the date and time menu to controller. **Takes up lots of memory space**
    // - Can be removed after change or adding wifi. Time can be changed in portal or through phone app.
    ReefAngel.AddDateTimeMenu();

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

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
   
    ////// Place your custom code below here //////
     
//*************************************************************************************************************

    /// Calculate your regular sunrise/sunset PWM values ///
    
    //RA Power Unit - Left Display Tank White/Color - on @ 9:20 a.m. - off @ 9:20 p.m.
    // - Parabola dimming 10%-65%-10%
    LDT_DaylightPWMValue=PWMParabola(9,20,21,20,10,65,LDT_DaylightPWMValue);
    
    //RA Power Unit - Left Display Tank Blue/UV - on @ 8:50 a.m. - off @ 8:50 p.m.
    // - Parabola dimming 10%-45%-10%
    LDT_ActinicPWMValue=PWMParabola(8,50,20,50,10,45,LDT_ActinicPWMValue);
    
    //Dimming Expansion - Right Display Tank White/Color - on @ 9:00 a.m. - off @ 9:00 p.m.
    // - Parabola dimming 10%-65%-10%
    RDT_DaylightPWMValue=PWMParabola(9,00,21,00,10,65,RDT_DaylightPWMValue);
  
    //Dimming Expansion - Right Display Tank Blue/UV - on @ 8:30 a.m. - off @ 8:30 p.m.
    // - Parabola dimming 10%-45%-10%
    RDT_ActinicPWMValue=PWMParabola(8,30,20,30,10,45,RDT_ActinicPWMValue);
    
    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
    
//*************************************************************************************************************

    /// Set MoonLights Cycles ///
    
    //Dimming Expansion - Left Display Tank Moonlight - on @ 9:30 p.m. - off @ 1:00 a.m.
    // - Slope dimming 10% up to (1/2 * MoonPhase) over 30 minutes
    ReefAngel.PWM.SetChannel(LEDPWM2,PWMSlope(21,30,1,00,10,MoonPhase()/5,30,10));
   
    //Dimming Expansion - Right Display Tank Moonlight - on @ 9:10 p.m. - off @ 12:30 a.m.
    // - Slope dimming 10% up to (1/2 * MoonPhase) over 30 minutes
    ReefAngel.PWM.SetChannel(LEDPWM3,PWMSlope(21,10,0,30,10,MoonPhase()/5,30,10));
    
//*************************************************************************************************************

    //Turns all W/C and B/UV channels to 0% power between 9:21 p.m. and 8:29 a.m. to conserve energy
     if ((NumMins(hour(now()),minute(now())) > NumMins(21,21)) && (NumMins(hour(now()),minute(now())) < NumMins(8,29)))
   {
     ReefAngel.PWM.SetDaylight(0);
     ReefAngel.PWM.SetActinic(0);
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
   }

    //Turns Moonlight channels to 0% power between 1:01 a.m. and 9:09 p.m. to conserve energy
     if ((NumMins(hour(now()),minute(now())) > NumMins(1,1)) && (NumMins(hour(now()),minute(now())) < NumMins(21,9)))
   {
     ReefAngel.PWM.SetChannel(LEDPWM2,0);
     ReefAngel.PWM.SetChannel(LEDPWM3,0);
   }
   
//*************************************************************************************************************

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

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

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

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

  // Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
  // For testing purposes, you can use 1 and cause the cloud to occur everyday
#define Clouds_Every_X_Days 1 

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 100

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

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

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

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

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

  // Always end the cloud effect before this setting
     // In this example, end must be before 8:10pm
#define End_Cloud_Before NumMins(20,10)

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

  // Channels used by the actinic LEDs on the PWM Expansion module 
     // These channels will not be dimmed when the cloud effect is triggered 
     // Number is a binary form. B001100 means channel 2 and 3 are used for actinics
#define Actinic_Channels B000010
     
  // Channels used by the daylight LEDs on the PWM Expansion module 
     // These channels will be used for the spike when lightning effect is triggered 
     // Number is a binary form. B000011 means channel 0 and 1 are used for daylights
#define Daylight_Channels B000001
     
  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.

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


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

  static byte cloudchance=255;
  static byte cloudduration=0;
  static int cloudstart=0;
  static byte numclouds=0;
  static byte lightningchance=0;
  static byte cloudindex=0;
  static byte lightningstatus=0;
  static int LastNumMins=0;
  // Every day at midnight, we check for chance of cloud happening today
  if (hour()==0 && minute()==0 && second()==0) cloudchance=255;

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

  if (cloudchance)
  {
    //is it time for cloud yet?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,0,180);
      if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5) 
      {
        if (random(100)<20) lightningstatus=1; 
        else lightningstatus=0;
        if (lightningstatus)
        {
          LDT_DaylightPWMValue=100; 
          LDT_ActinicPWMValue=100;
        }
        else 
        {
          LDT_DaylightPWMValue=0;
          LDT_ActinicPWMValue=0;
        }
        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);
        //Pick a random number between 0 and 99
        lightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (lightningchance>Lightning_Change_per_Cloud) lightningchance=0;
      }
    }
  }
  
  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 (lightningchance) 
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      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
}
So there is my start to setting up my code. I know it works... more or less... there may be a few things to straighten out. And it's not everything I wanted yet. But baby steps! :lol: I got no errors when I verified it, and it successfully ran my lights and equipment for about a week.

And that is where I started. In a moment, I'll add a new post with the code I just verified uploaded to my controller. I'm hoping the next step works correctly! This next code was built from the one above with added work towards the lighting and weather simulation.

Re: Let's Build Troubadour11's Code

Posted: Sat Mar 15, 2014 1:13 pm
by troubadour11
So here is what I just uploaded.

I would appreciate we maybe start building from this code, if it is worthy. :? I will admit that I didn't entirely "know what I was doing." So I have noted the code with everything I added, altered, was trying to accomplish, and even a few questions. Right now this code is on the RA unit and I'm waiting to see if it works correctly.

I did go into the code for the "Weather Simulation for Standard PWM Ports" and added some lines to try and create an offset that would work for the right display tank fixture (which runs on channels 0 & 1 of the dimming expansion). I also re-worked my previous sun/moon, rise/set code. Now I believe it should turn each individual driver to 0% when it's not in use, otherwise runs the lighting, moon cycle, and weather sim.

New Code with variable offset weather sim attmept (Using BOTH Standard Dimming Ports and Dimming Expansion):

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>

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

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

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.

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

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = CURRENTLY EMPTY - Always On
      // Port4 = CURRENTLY EMPTY - Always On
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = UV Sterilizer
      // Port8 = Refugium Pump
   
    ////// Place additional initialization code below here //////
   
// ***PLEASE HELP HERE********************************************************************************************************************************************
    // Currently all lights look like they kick on and flash at 100% when I upload code, then turn off and a second later they ramp up to PWM-Parabola value.
    // All drivers are plugged into separate static ON Surge Strip.
    // I thought these 2 pieces of code written above accomplished this but it doesn't seem to be doing what I expect. That code is commented here for reference,
    //                 //byte PWMChannel[]={0,0,0,0,0,0};
    //                 //byte LDT_DaylightPWMValue=0; byte LDT_ActinicPWMValue=0; byte RDT_DaylightPWMValue=0;  byte RDT_ActinicPWMValue=0;

    // Will the below turn all lights to 0% on start up so they don't flash 100% then adjust to correct values after running through the loop?
    // Is what I'm trying to do possible? I'm sure I have some redundancies in here somewhere.

    //Sets values of ALL lighting channels to 0% on Start Up
//     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);
//****************************************************************************************************************************************************************

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

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:30 p.m. and 8:30 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV - on @ 8:30 a.m. - off @ 8:30 p.m.
    // - Parabola dimming 11%-75%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(20,30)) && (NumMins(hour(now()),minute(now())) < NumMins(8,30)))
     {
// - Which one is the correct way to set these to 0% during non-operational hours?
     ReefAngel.PWM.SetChannel(LEDPWM1,0);
//   LEDPWM1=0;
//   RDT_ActinicPWMValue=0;
      }
      else
      {
    RDT_ActinicPWMValue=PWMParabola(8,30,20,30,11,75,RDT_ActinicPWMValue);
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 8:50 p.m. and 8:50 a.m. to conserve energy
    // - RA Power Unit - LDT Blue/UV - on @ 8:50 a.m. - off @ 8:50 p.m.
    // - Parabola dimming 11%-65%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(20,50)) && (NumMins(hour(now()),minute(now())) < NumMins(8,50)))
     {
// - Which one is the correct way to set these to 0% during non-operational hours?
      ReefAngel.PWM.SetActinic(0);
//    LDT_ActinicPWMValue=0;
      }
      else
      {
      LDT_ActinicPWMValue=PWMParabola(8,50,20,50,11,65,LDT_ActinicPWMValue);
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 9:00 p.m. and 9:00 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color - on @ 9:00 a.m. - off @ 9:00 p.m.
    // - Parabola dimming 11%-85%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(21,0)) && (NumMins(hour(now()),minute(now())) < NumMins(9,0)))
     {
// - Which one is the correct way to set these to 0% during non-operational hours?
     ReefAngel.PWM.SetChannel(LEDPWM0,0);
//   LEDPWM0=0;
//   RDT_DaylightPWMValue=0;
      }
      else
      {
      RDT_DaylightPWMValue=PWMParabola(9,00,21,00,11,85,RDT_DaylightPWMValue);
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 9:20 p.m. and 9:20 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color - on @ 9:20 a.m. - off @ 9:20 p.m.
    // - Parabola dimming 11%-75%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(21,20)) && (NumMins(hour(now()),minute(now())) < NumMins(9,20)))
     {
// - Which one is the correct way to set these to 0% during non-operational hours with the code I'm attempting?
      ReefAngel.PWM.SetDaylight(0);
//    LDT_DaylightPWMValue=0;
      }
      else
      {
      LDT_DaylightPWMValue=PWMParabola(9,20,21,20,11,75,LDT_DaylightPWMValue);
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   
    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
//_________________________________________________________________________________________________   



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

//_________________________________________________________________________________________________________________________________________________
    // - Turns RDT Moonlights to 0% power between 12:30 a.m. and 9:10 p.m. to conserve energy.
    // - Slope dimming 10% up to 1/2 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 30 minutes.
    // - Dimming Expansion - LEDPWM3 - RDT Moonlight - on @ 9:10 p.m. - off @ 12:30 a.m.
//_________________________________________________________________________________________________________________________________________________
if ((NumMins(hour(now()),minute(now())) > NumMins(0,30)) && (NumMins(hour(now()),minute(now())) < NumMins(21,10)))
     {
// - Which one is the correct way to set these to 0% during non-operational hours with the code I'm attempting?
      ReefAngel.PWM.SetChannel(LEDPWM3,0);
//    LEDPWM3=0;
      }
      else
      {
       ReefAngel.PWM.SetChannel(LEDPWM3,PWMSlope(21,10,0,30,10,MoonPhase()/2+10,30,LEDPWM3));
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_________________________________________________________________________________________________________________________________________________
    // - Turns LDT Moonlights to 0% power between 1:00 a.m. and 9:30 p.m. to conserve energy
    // - Slope dimming - 10% up to 1/2 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 30 minutes
    // - Dimming Expansion - LEDPWM2 - LDT Moonlight - on @ 9:30 p.m. - off @ 1:00 a.m.
//_________________________________________________________________________________________________________________________________________________
if ((NumMins(hour(now()),minute(now())) > NumMins(1,0)) && (NumMins(hour(now()),minute(now())) < NumMins(21,30)))
     {
// - Which one is the correct way to set these to 0% during non-operational hours with the code I'm attempting?
      ReefAngel.PWM.SetChannel(LEDPWM2,0);
//    LEDPWM2=0;
      }
      else
      {
      ReefAngel.PWM.SetChannel(LEDPWM2,PWMSlope(21,30,1,00,10,MoonPhase()/2+10,30,LEDPWM2));
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  


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

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

    // 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
#define Clouds_Every_X_Days 1

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 100

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

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

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

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

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

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

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

//*********************************************************************************************************************************************************************
  // I'm not sure if these binary labels below are needed or not with they way I was thinking of setting this up.?.?. Please Advise...
  // Should the RDT_DaylightPWMValue be based off the LDT_DaylightPWMValue?

  // Channels used by the actinic LEDs on the PWM Expansion module
     // These channels will not be dimmed when the cloud effect is triggered
     // Number is a binary form. B001100 means channel 2 and 3 are used for actinics
//#define Actinic_Channels B000010
   
  // Channels used by the daylight LEDs on the PWM Expansion module
     // These channels will be used for the spike when lightning effect is triggered
     // Number is a binary form. B000011 means channel 0 and 1 are used for daylights
//#define Daylight_Channels B000001
//*********************************************************************************************************************************************************************
    
  // 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;

//********************************************************************************************************************************************************
  // - 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 number of minutes plus, or minus to offset the RDT cloud by.
  static byte RDTcloudstartRandom=0;

  // - I added this to create a random sample between 50% and 100% dimming power for "random" lightning intensity effect.
  static byte LightningFlashPower=0;
//********************************************************************************************************************************************************

  static byte numclouds=0;
  static byte lightningchance=0;
  static byte cloudindex=0;
  static byte lightningstatus=0;
  static int LastNumMins=0;

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

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

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

        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        lightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (lightningchance>Lightning_Change_per_Cloud) lightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect

  if (cloudchance)
  {
    //is it time for cloud yet?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,0,180);

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // Can I re-use the "ReversePWMSlope" funtion, or do I need to duplicate this at the bottom and make a separate RDT_ReversePWMSlope?
      // What if I do not want the Cloud Effect to dim my white lights all the way to 0%...
      // If I only want them to drop down to the min. 11% that my MeanWells can run the strings at, and not completely turn off the Daylights during a cloud, do I change the 0 here to 11?
      // And what does the 180 value set or tell the program here?

      RDT_DaylightPWMValue=ReversePWMSlope(RDTcloudstartOffset,RDTcloudstartOffset+cloudduration,RDT_DaylightPWMValue,0,180);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
      {
        if (random(100)<20) lightningstatus=1;
        else lightningstatus=0;
        if (lightningstatus)
        {
          // - I will remove these 2 lines below if the code I commented out just a few lines below will set the lightning flashes correctly.
          LDT_DaylightPWMValue=100;
          LDT_ActinicPWMValue=100;
          // - I added this to include the RDT lights in the lightning event.
          RDT_DaylightPWMValue=100;
          RDT_ActinicPWMValue=100;
//_______________________________________________________________________________________________________
          // - If there will be lightning, pick a random number between 49 and 99 for the lightning power.
          //LightningFlashPower=random(50,100);

          // - Trying to make a random sample between, say 50% & 100% for the brightness of the lightning?
          //LDT_DaylightPWMValue=LightningFlashPower;
          //LDT_ActinicPWMValue=LightningFlashPower;
          // - Will this work correctly for synced lightning?
          //RDT_DaylightPWMValue=LightningFlashPower;
          //RDT_ActinicPWMValue=LightningFlashPower;
//_______________________________________________________________________________________________________

        }
        else
        {
          LDT_DaylightPWMValue=0;
          LDT_ActinicPWMValue=0;
//------------------------------------------------------------------
          // - IS this correctl?
          RDT_DaylightPWMValue=0;
          RDT_ActinicPWMValue=0;
//------------------------------------------------------------------

        }
        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);
        //Pick a random number between 0 and 99
        lightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (lightningchance>Lightning_Change_per_Cloud) lightningchance=0;
      }
    }
  }
 
  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 (lightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      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
}
All right, so that is where I am currently at. I thank you for making it through these first 3 drawn out posts. I'll keep them much shorter in the future :mrgreen: I promise.

Re: Let's Build Troubadour11's Code

Posted: Sat Mar 15, 2014 1:38 pm
by rimai
Awesome!!!
That's a pretty good start :)

Re: Let's Build Troubadour11's Code

Posted: Mon Mar 17, 2014 7:40 pm
by troubadour11
Thanks Roboerto! I thought it was pretty good for not really knowing what I was doing and just figuring it out based off of what was already there. Especially for my first try!

But I am having a few issues with the code that I'm struggling to straighten out. And it's making it really difficult to trouble shoot and try any modifications.

To start with, I have slightly tweaked my last posted code. So here is the latest version I have loaded to 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>

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

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

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


void setup()
{
    //This must be the first line.
    ReefAngel.Init();  //Initialize controller
    //This must be the first line.

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

    //Ports that are always on
    ReefAngel.Relay.On( Port3 );
    ReefAngel.Relay.On( Port4 );
    ReefAngel.Relay.On( Port5 );
    ReefAngel.Relay.On( Port6 );
    ReefAngel.Relay.On( Port7 );
    ReefAngel.Relay.On( Port8 );
      // Port1 = Heater
      // Port2 = Heater
      // Port3 = CURRENTLY EMPTY - Always On
      // Port4 = CURRENTLY EMPTY - Always On
      // Port5 = Return Pump
      // Port6 = Protein Skimmer
      // Port7 = UV Sterilizer
      // Port8 = Refugium Pump
   
    ////// Place additional initialization code below here //////
   
// ***PLEASE HELP HERE********************************************************************************************************************************************
    // Currently all lights look like they kick on and flash at 100% when I upload code, then turn off and a second later they ramp up to PWM-Parabola value.
    // All drivers are plugged into separate static ON Surge Strip.
    // I thought these 2 pieces of code written above accomplished this but it doesn't seem to be doing what I expect. That code is commented here for reference,
    //                 //byte PWMChannel[]={0,0,0,0,0,0};
    //                 //byte LDT_DaylightPWMValue=0; byte LDT_ActinicPWMValue=0; byte RDT_DaylightPWMValue=0;  byte RDT_ActinicPWMValue=0;

    // Will the below turn all lights to 0% on start up so they don't flash 100% then adjust to correct values after running through the loop?
    // Is what I'm trying to do possible? I'm sure I have some redundancies in here somewhere.

    //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();
    
    //Forces "CheckCloud();" on Startup to calculate/recalculate weather sim data so it runs correctly after uploading new code or power interruption.
    // - I'm not sure this will work???
    CheckCloud();
    // - Above did not reset the menu clock or storm fuction correctly. I uploaded around 1:30 or 2:30 and no lightning events have occured as of 6:30.
    
    ////// Place additional initialization code above here //////
}

void loop()
{
    ReefAngel.StandardHeater(Port1);
    ReefAngel.StandardHeater(Port2);
  
    ////// Place your custom code below here //////

//*********************************************************************************************************************************************************
                                   //// Calculate your regular sunrise/sunset PWM values ////
//*********************************************************************************************************************************************************
 //_____________________________________________________________________________________________________________
    // - Turns RDT - B/UV channel to 0% power between 8:30 p.m. and 8:30 a.m. to conserve energy
    // - Dimming Expansion - LEDPWM1 - RDT Blue/UV - on @ 8:30 a.m. - off @ 8:30 p.m.
    // - Parabola dimming 11%-75%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(8,30)) && (NumMins(hour(now()),minute(now())) < NumMins(20,30)))
      {
         RDT_ActinicPWMValue=PWMParabola(8,30,20,30,11,75,RDT_ActinicPWMValue);
      }
      else
      {
//  - Which one is the correct way to set these to 0% during non-operational hours?
//    ReefAngel.PWM.SetChannel(LEDPWM1,0);
//    LEDPWM1=0;
         RDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - B/UV channel to 0% power between 8:50 p.m. and 8:50 a.m. to conserve energy
    // - RA Standard Power Unit - LDT Blue/UV - on @ 8:50 a.m. - off @ 8:50 p.m.
    // - Parabola dimming 11%-65%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(8,50)) && (NumMins(hour(now()),minute(now())) < NumMins(20,50)))
     {
        LDT_ActinicPWMValue=PWMParabola(8,50,20,50,11,75,LDT_ActinicPWMValue);
      }
      else
      {
//  - Which one is the correct way to set these to 0% during non-operational hours?
//    ReefAngel.PWM.SetActinic(0);
        LDT_ActinicPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns RDT - W/C channel to 0% power between 9:00 p.m. and 9:00 a.m. to conserve energy
    // - Dimming Expansion - RDT White/Color - on @ 9:00 a.m. - off @ 9:00 p.m.
    // - Parabola dimming 11%-85%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(9,0)) && (NumMins(hour(now()),minute(now())) < NumMins(21,0)))
     {
        RDT_DaylightPWMValue=PWMParabola(9,00,21,00,11,85,RDT_DaylightPWMValue);
      }
      else
      {
//  - Which one is the correct way to set these to 0% during non-operational hours?
//   ReefAngel.PWM.SetChannel(LEDPWM0,0);
//   LEDPWM0=0;
        RDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_____________________________________________________________________________________________________________
    // - Turns LDT - W/C channel to 0% power between 9:20 p.m. and 9:20 a.m. to conserve energy
    // - RA Power Unit - LDT White/Color - on @ 9:20 a.m. - off @ 9:20 p.m.
    // - Parabola dimming 11%-75%-11%
//_____________________________________________________________________________________________________________
     if ((NumMins(hour(now()),minute(now())) > NumMins(9,20)) && (NumMins(hour(now()),minute(now())) < NumMins(21,20)))
     {
        LDT_DaylightPWMValue=PWMParabola(9,20,21,20,11,75,LDT_DaylightPWMValue);
      }
      else
      {
//   - Which one is the correct way to set these to 0% during non-operational hours with the code I'm attempting?
//    ReefAngel.PWM.SetDaylight(0);
        LDT_DaylightPWMValue=0;
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   
    CheckCloud();
    ReefAngel.PWM.SetDaylight(LDT_DaylightPWMValue);
    ReefAngel.PWM.SetActinic(LDT_ActinicPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM0,RDT_DaylightPWMValue);
    ReefAngel.PWM.SetChannel(LEDPWM1,RDT_ActinicPWMValue);
//_________________________________________________________________________________________________   



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

//_________________________________________________________________________________________________________________________________________________
    // - Turns RDT Moonlights to 0% power between 12:30 a.m. and 9:10 p.m. to conserve energy.
    // - Slope dimming 10% up to 1/2 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 50 minutes.
    // - Dimming Expansion - LEDPWM3 - RDT Moonlight - on @ 9:10 p.m. - off @ 12:30 a.m.
//_________________________________________________________________________________________________________________________________________________
if ((NumMins(hour(now()),minute(now())) > NumMins(0,30)) && (NumMins(hour(now()),minute(now())) < NumMins(21,10)))
     {
// - Which one is the correct way to set these to 0% during non-operational hours with the code I'm attempting?
      ReefAngel.PWM.SetChannel(LEDPWM3,0);
//    LEDPWM3=0;
      }
      else
      {
       ReefAngel.PWM.SetChannel(LEDPWM3,PWMSlope(21,10,0,30,10,MoonPhase()/2+10,50,LEDPWM3));
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//_________________________________________________________________________________________________________________________________________________
    // - Turns LDT Moonlights to 0% power between 1:00 a.m. and 9:30 p.m. to conserve energy
    // - Slope dimming - 10% up to 1/2 * MoonPhase +10 (to offest for 10% min driver start power). Fades up to current Moonphase over 50 minutes
    // - Dimming Expansion - LEDPWM2 - LDT Moonlight - on @ 9:30 p.m. - off @ 1:00 a.m.
//_________________________________________________________________________________________________________________________________________________
if ((NumMins(hour(now()),minute(now())) > NumMins(1,0)) && (NumMins(hour(now()),minute(now())) < NumMins(21,30)))
     {
// - Which one is the correct way to set these to 0% during non-operational hours with the code I'm attempting?
      ReefAngel.PWM.SetChannel(LEDPWM2,0);
//    LEDPWM2=0;
      }
      else
      {
      ReefAngel.PWM.SetChannel(LEDPWM2,PWMSlope(21,30,1,00,10,MoonPhase()/2+10,50,LEDPWM2));
      }
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  


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

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

    // 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
#define Clouds_Every_X_Days 1

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 100

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

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

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

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

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

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

  // 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 100
    
  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen 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;

//********************************************************************************************************************************************************
  // - 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 number of minutes plus, or minus to offset the RDT cloud by.
  static byte RDTcloudstartRandom=0;

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

  static byte numclouds=0;
  static byte lightningchance=0;
  static byte cloudindex=0;
  static byte lightningstatus=0;
  static int LastNumMins=0;

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

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

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

        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        lightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (lightningchance>Lightning_Change_per_Cloud) lightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect

  if (cloudchance)
  {
    //is it time for cloud yet?
    if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
    {
      //changed 0 to 11 in order to make minimum cloud power 11% so the lights don't click off.
      LDT_DaylightPWMValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,LDT_DaylightPWMValue,11,180);

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      // Can I re-use the "ReversePWMSlope" funtion, or do I need to duplicate this at the bottom and make a separate RDT_ReversePWMSlope?
      // What if I do not want the Cloud Effect to dim my white lights all the way to 0%...
      // If I only want them to drop down to the min. 11% that my MeanWells can run the strings at, and not completely turn off the Daylights during a cloud, do I change the 0 here to 11?
      // And what does the 180 value set or tell the program here?

      //changed 0 to 11 in order to make minimum cloud power 11% so the lights don't click off.
      RDT_DaylightPWMValue=ReversePWMSlope(RDTcloudstartOffset,RDTcloudstartOffset+cloudduration,RDT_DaylightPWMValue,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/2))) && second()<5)
      {
        if (random(100)<20) lightningstatus=1;
        else lightningstatus=0;
        if (lightningstatus)
        {
          // - Thes 4 lines sync the lightning to flash at the same time at identical levels on both fixtures correctly -
          //LDT_DaylightPWMValue=100;
          //LDT_ActinicPWMValue=100;
          //RDT_DaylightPWMValue=100;
          //RDT_ActinicPWMValue=100;
          // - Thes 4 lines sync the lightning to flash at the same time at identical levels on both fixtures correctly -
//_______________________________________________________________________________________________________
          // - Trying to make a random sample between, say 50% & 100% for the brightness of the lightning?
          // - If there will be lightning, pick a random number between 49 and 99 for the lightning power.
          LDT_LightningFlashPower=random(50,100);
          RDT_LightningFlashPower=random(50,100);

          LDT_DaylightPWMValue=LDT_LightningFlashPower;
          LDT_ActinicPWMValue=LDT_LightningFlashPower;
          RDT_DaylightPWMValue=RDT_LightningFlashPower;
          RDT_ActinicPWMValue=RDT_LightningFlashPower;
//_______________________________________________________________________________________________________

        }
        else
        {
          // - Changed these values from 0 to 11 so at end of lightning cycle, the go to min. 11% power
          LDT_DaylightPWMValue=11;
          LDT_ActinicPWMValue=11;
          RDT_DaylightPWMValue=11;
          RDT_ActinicPWMValue=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
        RDTcloudstartRandom=random(-2,2);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        RDTcloudstartOffset=cloudstart+RDTcloudstartRandom;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        lightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (lightningchance>Lightning_Change_per_Cloud) lightningchance=0;
      }
    }
  }
 
  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 (lightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      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
}
Mostly I just swapped around the "if" statements for my sunrise/set effect. But I did add in a couple variables into the lightning function in the weather sim to try and create a random power variable for each lightning flash. That's mostly what I changed.

As before, there are lots of comments and things noted inside the code wherever I've added or changed things along with some questions on things I'm trying to understand or figure out. But there are a few issues that I'm trying to tackle right now that are leaving me scratching my head.

Both the previously posted code that I had loaded in and this current code *seem* to be working correctly. But both have had similar unexpected issues.

**Note, I have not seem a cloud/lightning sim on this new code yet. I mention why as an issue after running through the lighting loop here.

Starting at midnight. All daylights and actinics are turned off and at 0% with the moonlights at their appropriate values. Each morning I've gotten up, lights are still off and there are new numbers displayed on the Cloud Sim Info section of the head unit display. Which is as expected, those numbers have been correct for the first cloud occurrence 2 days in a row.

Also, the sun rise programming turns on exactly as expected, and looks awesome 8-) . A staggered start up based on the coded times beginning with the blue/uvs and then the white/colors.

It gets to the designated time for the first cloud simulation and I believe is running mostly correctly. Both times I've seen this happen, the LDT has dimmed 1 or 2 minutes before the RDT fixture. So there is an offset being established between the 2 fixtures, although for the 2 mornings I've seen it, it's been the same offset of LDT prior to RDT.
- I think I fixed this back to a random offset in this code version by adding some lines in near the end of the cloudcheck function that should be noted. But again, this has not been tested and viewed yet.

The lightning on first cloud event was as expected. Both fixtures flash at same power and timing.

After the lightning, the clouds ran the remainder of their time and then adjusted back to their correct values.

The weather sim clock on head unit now displays the next times and data for the next cloud event as expected. But this is where things begin to unravel.

On the next cloud event, and every subsequent one for the rest of the day. At the time corresponding to the next event, only the LDT fixture dims for the cloud effect. But BOTH fixtures flash correctly for the lightning event during these remaining clouds.
- I wonder if this is fixed by the offset code I noted adding to this version above.

The LDT fixture stays in cloud mode for the simulated amount of time after lightning and then ramps back up to correct values.
- So why AND how can my cloud sim work with an offset on the 1st event of the day, but then not work for the remaining events in the same day?

Then if the code is left to run without interruption. At the sunset times, all channels dim to their lowest outputs as expected at the right times. But then none of the 4 daylight channels turn off.
- This was the reason for re-working my IF statements on my normal sun rise/set parabolas in this code version. But again, has not been tested yet.

Finally, with all the day light channels at their lowest out put the moonlights kick on and ramp as expected.

So that is what is happening if I let the code run for 24 hours.

The first night of running this code, I waited until both the RDT_Daylight and RDT_Actinic channels were supposed to be turned off and only the LDT fixture on. I re-uploaded the exact previous code that was running on the unit. After it uploaded, the lights on the RDT fixture turned off like it was supposed to. I waited and the RDT moonlights kicked on correctly. But then the LDT_Daylight and LDT_Actinic never turned off and stayed at the min %. So after the LDT fixture was supposed to be turned off, I again re-uploaded the exact same code. After the upload, all daylight and actinic channels were correctly off and the moonlights kicked in and ran their code as expected.

Now, another issue that's REALLY bothering me that I'd like to get worked around is this. Anytime I upload code or a new version of the code. It completely turns off the cloud sim for the remainder of the day. How do I get the cloud calculation to run for the remainder of the day if I adjust code to test the cloud/lightning effect?

Currently, I have to watch what happens on the 1st sim. Then wait for the second sim to see if it's only the LDT fixture again. Adjust new code to try, upload new code. Then wait basically 24 hours for it to recalculate at midnight and be ready to run the next cloud sim sometime the next morning. I need to be able to test the outcome faster then that or it will take me months to do these minor tweaks.

So I'd love any thoughts or ideas on these code issues. I think primarily getting it so I can test new weather sim code easier and faster then once every 24 hours would help a ton. Whether it's just a temp button in a temp menu to force a cloud event, or just getting the cloud sim to run the rest of the day if I upload new code. Then we can move onto to trouble shooting. Because like I mentioned, I think I have some potential solutions possibly in place already in this new posted code. But I can't check or test it since I won't see a cloud event from 2:00 this afternoon until sometime tomorrow morning.

Once again, thanks... and sorry for the book again! :P

Re: Let's Build Troubadour11's Code

Posted: Mon Mar 17, 2014 9:39 pm
by troubadour11
Hey, hey! Good news!

I'm happy to report the latest updated code worked perfectly for the sunset effect tonight. Everything dimmed and turned off at the exact right times. So check that off the list for now. I like checking things off the list!!! :mrgreen:

For now I'm going to wait until tomorrow and see what happens with the cloud effect. I expect everything to run correctly until after the first cloud sim. I'll have to see where it goes from there. Maybe it will be fixed too with the latest updates! :-D I'm anxious to see what happens with the random lightning power code I tried in there. I'm not really sure what that will look like.

It's fun playing with this stuff. I'd still be interested in trouble shooting being able to upload new code and quickly see a cloud/lightning sim test if anyone has any thoughts.

I'll let you all know what happens tomorrow with the cloud effects once I see them run 2 times...

Re: Let's Build Troubadour11's Code

Posted: Mon Mar 17, 2014 10:23 pm
by rimai
One easy way is to change the time of the controller forward to be like 1 minute before the cloud.
There is a custom menu in the forums that we worked in the past that fires off the cloud.
Take a look at this thread:
http://forum.reefangel.com/viewtopic.php?f=12&t=425

Re: Let's Build Troubadour11's Code

Posted: Wed Mar 19, 2014 9:59 am
by troubadour11
Ah ha! Oh man. I love it when I'm so focused and close to a problem that I can't see the simple solution that's right in front of me. :roll:

Thanks for the tips Roberto! Switching the time is truly a simple solution that can work just fine for now. I'm surprised and embarrassed I didn't think of that. LOL

And I haven't had free time the last few days to mess with the weather sim updates I've been working on. But I can easily trouble shoot at night now, if I have the time by, just changing the clock to 1 minute before midnight. Waiting for it to calculate the clouds for the day, then switch the clock to just before the effect. I am going to take a look at the code in the link you gave me though. I'd still like to add in a menu option for the long term. So after I get a moment to check that out some more, I'll see if I can work it into my code update soon.

I have been taking some videos to try and capture the weather sim effect on my tank. So far I have 2 vids capturing the different incarnations of the code. I'll share those when I have some more time. Maybe I can go back and edit the previous posts so the videos are in with the code that made them.?.?.

Thanks for the tips, and I'll let you all know how it goes on the next update 8-)

Re: Let's Build Troubadour11's Code

Posted: Wed Mar 19, 2014 12:52 pm
by troubadour11
Quick question...

I was starting to add in the code you linked to above to add a menu function to force a weather sim. I haven't done any real menu editing on here yet. Other then adding the Date/Time menu to adjust for daylight savings time.

So where does this code go?

Code: Select all

    void MenuEntry1()
    {
    ForceCloud=true;
    ReefAngel.DisplayedMenu=RETURN_MAIN_MODE;
    }
- Dose this get added to the "void setup()" section to add the menu?

Thanks!

Re: Let's Build Troubadour11's Code

Posted: Wed Mar 19, 2014 12:55 pm
by rimai
There is a custom menu tutorial. You should read through it before you add the code.

Sent from my Galaxy S3 using Tapatalk

Re: Let's Build Troubadour11's Code

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

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

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

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

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

Code: Select all

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

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

Code: Select all

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

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

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

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

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

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

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

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

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

Code: Select all

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

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

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

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

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



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

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

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


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

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

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

Thanks :-)

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

Code: Select all

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

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

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

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

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

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

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
prog_char menu2_label[] PROGMEM = "Water Change";
prog_char menu3_label[] PROGMEM = "ATO Clear";
prog_char menu4_label[] PROGMEM = "Overheat Clear";
prog_char menu5_label[] PROGMEM = "PH Calibration";
prog_char menu6_label[] PROGMEM = "Date / Time";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}
void MenuEntry3()
{
ReefAngel.ATOClear();
ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4()
{
ReefAngel.OverheatClear();
ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry5()
{
ReefAngel.SetupCalibratePH();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

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

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


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

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

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

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

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

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

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

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

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

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



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

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

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


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

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


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

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

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

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

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

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 50

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

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

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

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

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

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

  // Percentage chance of lightning happening for every cloud
     // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Change_per_Cloud 50
 
  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

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


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

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

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

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

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


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

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

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

        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

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

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

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

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

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

    if (NumMins(hour(),minute())>(cloudstart+cloudduration))
    {
      cloudindex++;
      if (cloudindex < numclouds)
      {
        cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  if (LastNumMins!=NumMins(hour(),minute()))
  {
    LastNumMins=NumMins(hour(),minute());
    ReefAngel.LCD.Clear(255,0,120,132,132);
    ReefAngel.LCD.DrawText(0,255,5,120,"C");
    ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
    ReefAngel.LCD.DrawText(0,255,45,120,"L");
    ReefAngel.LCD.DrawText(0,255,51,120,"00:00");
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11; else x=17;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart/60));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

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

Re: Let's Build Troubadour11's Code

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

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

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

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

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

Let me know if any if that makes sense :)

Re: Let's Build Troubadour11's Code

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

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

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

Code: Select all

    //Set up for Refugium and Phytoplankton light cycle

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

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

Code: Select all

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

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

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

Code: Select all

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

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

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

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

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

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

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

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

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

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

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

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

Re: Let's Build Troubadour11's Code

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

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

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

Code: Select all

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

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

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

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

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

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

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
prog_char menu2_label[] PROGMEM = "Water Change";
prog_char menu3_label[] PROGMEM = "ATO Clear";
prog_char menu4_label[] PROGMEM = "Overheat Clear";
prog_char menu5_label[] PROGMEM = "PH Calibration";
prog_char menu6_label[] PROGMEM = "Date / Time";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}
void MenuEntry3()
{
ReefAngel.ATOClear();
ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4()
{
ReefAngel.OverheatClear();
ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry5()
{
ReefAngel.SetupCalibratePH();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 50

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

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

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

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

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

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

  // Percentage chance of lightning happening for every cloud
     // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Change_per_Cloud 50
 
  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

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


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

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

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

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

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


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

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

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

        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

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

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

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

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

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

    if (NumMins(hour(),minute())>(cloudstart+cloudduration))
    {
      cloudindex++;
      if (cloudindex < numclouds)
      {
        cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  if (LastNumMins!=NumMins(hour(),minute()))
  {
    LastNumMins=NumMins(hour(),minute());
    ReefAngel.LCD.Clear(255,0,120,132,132);
    ReefAngel.LCD.DrawText(0,255,5,120,"C");
    ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
    ReefAngel.LCD.DrawText(0,255,45,120,"L");
    ReefAngel.LCD.DrawText(0,255,51,120,"00:00");
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11; else x=17;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart/60));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

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

Re: Let's Build Troubadour11's Code

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

Re: Let's Build Troubadour11's Code

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

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

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

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

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

Re: Let's Build Troubadour11's Code

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

Re: Let's Build Troubadour11's Code

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

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

Re: Let's Build Troubadour11's Code

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

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

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

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

Code: Select all

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

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

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

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

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

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

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
prog_char menu2_label[] PROGMEM = "Water Change";
prog_char menu3_label[] PROGMEM = "ATO Clear";
prog_char menu4_label[] PROGMEM = "Overheat Clear";
prog_char menu5_label[] PROGMEM = "PH Calibration";
prog_char menu6_label[] PROGMEM = "Date / Time";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}
void MenuEntry3()
{
ReefAngel.ATOClear();
ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4()
{
ReefAngel.OverheatClear();
ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry5()
{
ReefAngel.SetupCalibratePH();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 50

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

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

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

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

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

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

  // Percentage chance of lightning happening for every cloud
     // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Change_per_Cloud 50
 
  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

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


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

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

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

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

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


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

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

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

        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

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

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

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

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

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

    if (NumMins(hour(),minute())>(cloudstart+cloudduration))
    {
      cloudindex++;
      if (cloudindex < numclouds)
      {
        cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  if (LastNumMins!=NumMins(hour(),minute()))
  {
    LastNumMins=NumMins(hour(),minute());
    ReefAngel.LCD.Clear(255,0,120,132,132);
    ReefAngel.LCD.DrawText(0,255,5,120,"C");
    ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
    ReefAngel.LCD.DrawText(0,255,45,120,"L");
    ReefAngel.LCD.DrawText(0,255,51,120,"00:00");
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11; else x=17;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart/60));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

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

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

Code: Select all

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

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


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


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


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

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

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

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

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

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

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

Code: Select all

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

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

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

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

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

Thanks :D

Re: Let's Build Troubadour11's Code

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

Code: Select all

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

Re: Let's Build Troubadour11's Code

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

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

Code: Select all

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

Code: Select all

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

Code: Select all

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

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

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

Current full code:

Code: Select all

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

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

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

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

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

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

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
prog_char menu2_label[] PROGMEM = "Water Change";
prog_char menu3_label[] PROGMEM = "ATO Clear";
prog_char menu4_label[] PROGMEM = "Overheat Clear";
prog_char menu5_label[] PROGMEM = "PH Calibration";
prog_char menu6_label[] PROGMEM = "Date / Time";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}
void MenuEntry3()
{
ReefAngel.ATOClear();
ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4()
{
ReefAngel.OverheatClear();
ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry5()
{
ReefAngel.SetupCalibratePH();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 50

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

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

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

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

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

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

  // Percentage chance of lightning happening for every cloud
     // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Change_per_Cloud 50
 
  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

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


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

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

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

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

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


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

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

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

        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

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

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

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

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

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

    if (NumMins(hour(),minute())>(cloudstart+cloudduration))
    {
      cloudindex++;
      if (cloudindex < numclouds)
      {
        cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  if (LastNumMins!=NumMins(hour(),minute()))
  {
    LastNumMins=NumMins(hour(),minute());
    ReefAngel.LCD.Clear(255,0,120,132,132);
    ReefAngel.LCD.DrawText(0,255,5,120,"C");
    ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
    ReefAngel.LCD.DrawText(0,255,45,120,"L");
    ReefAngel.LCD.DrawText(0,255,51,120,"00:00");
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11; else x=17;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart/60));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

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

Re: Let's Build Troubadour11's Code

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

In the new code:

Code: Select all

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

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

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

Re: Let's Build Troubadour11's Code

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

Re: Let's Build Troubadour11's Code

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

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

Thanks again! :D

Re: Let's Build Troubadour11's Code

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

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

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

Code: Select all

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

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

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

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

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

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

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
prog_char menu2_label[] PROGMEM = "Water Change";
prog_char menu3_label[] PROGMEM = "ATO Clear";
prog_char menu4_label[] PROGMEM = "Overheat Clear";
prog_char menu5_label[] PROGMEM = "PH Calibration";
prog_char menu6_label[] PROGMEM = "Date / Time";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}
void MenuEntry3()
{
ReefAngel.ATOClear();
ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4()
{
ReefAngel.OverheatClear();
ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry5()
{
ReefAngel.SetupCalibratePH();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 50

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

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

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

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

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

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

  // Percentage chance of lightning happening for every cloud
     // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Change_per_Cloud 50
 
  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

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


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

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

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

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

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


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

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

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

        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

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

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

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

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

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

    if (NumMins(hour(),minute())>(cloudstart+cloudduration))
    {
      cloudindex++;
      if (cloudindex < numclouds)
      {
        cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  if (LastNumMins!=NumMins(hour(),minute()))
  {
    LastNumMins=NumMins(hour(),minute());
    ReefAngel.LCD.Clear(255,0,120,132,132);
    ReefAngel.LCD.DrawText(0,255,5,120,"C");
    ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
    ReefAngel.LCD.DrawText(0,255,45,120,"L");
    ReefAngel.LCD.DrawText(0,255,51,120,"00:00");
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11; else x=17;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart/60));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

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

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

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

Code: Select all

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

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

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

Code: Select all

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

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

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

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

Code: Select all

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

byte Refugium03_onHour=InternalMemory.read(Mem_B_Refugium03_onHour);

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

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

Code: Select all

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

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

Re: Let's Build Troubadour11's Code

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

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

Code: Select all

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

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

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

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

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

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

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
prog_char menu2_label[] PROGMEM = "Water Change";
prog_char menu3_label[] PROGMEM = "ATO Clear";
prog_char menu4_label[] PROGMEM = "Overheat Clear";
prog_char menu5_label[] PROGMEM = "PH Calibration";
prog_char menu6_label[] PROGMEM = "Date / Time";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}
void MenuEntry3()
{
ReefAngel.ATOClear();
ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4()
{
ReefAngel.OverheatClear();
ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry5()
{
ReefAngel.SetupCalibratePH();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

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

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


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

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

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

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

void loop()
{

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 50

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

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

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

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

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

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

  // Percentage chance of lightning happening for every cloud
     // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Change_per_Cloud 50
 
  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

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


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

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

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

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

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


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

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

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

        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

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

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

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

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

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

    if (NumMins(hour(),minute())>(cloudstart+cloudduration))
    {
      cloudindex++;
      if (cloudindex < numclouds)
      {
        cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  if (LastNumMins!=NumMins(hour(),minute()))
  {
    LastNumMins=NumMins(hour(),minute());
    ReefAngel.LCD.Clear(255,0,120,132,132);
    ReefAngel.LCD.DrawText(0,255,5,120,"C");
    ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
    ReefAngel.LCD.DrawText(0,255,45,120,"L");
    ReefAngel.LCD.DrawText(0,255,51,120,"00:00");
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11; else x=17;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart/60));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

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

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

Code: Select all

The following features were automatically added:
Watchdog Timer
Version Menu

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

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

Re: Let's Build Troubadour11's Code

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

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

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

Currently my code has this in there driving the pump.

Code: Select all

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

Code: Select all

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

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

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

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

Code: Select all

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

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

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

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

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

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

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
prog_char menu2_label[] PROGMEM = "Water Change";
prog_char menu3_label[] PROGMEM = "ATO Clear";
prog_char menu4_label[] PROGMEM = "Overheat Clear";
prog_char menu5_label[] PROGMEM = "PH Calibration";
prog_char menu6_label[] PROGMEM = "Date / Time";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}
void MenuEntry3()
{
ReefAngel.ATOClear();
ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4()
{
ReefAngel.OverheatClear();
ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry5()
{
ReefAngel.SetupCalibratePH();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 50

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

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

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

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

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

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

  // Percentage chance of lightning happening for every cloud
     // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Change_per_Cloud 50
 
  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

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


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

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

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

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

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


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

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

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

        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

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

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

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

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

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

    if (NumMins(hour(),minute())>(cloudstart+cloudduration))
    {
      cloudindex++;
      if (cloudindex < numclouds)
      {
        cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  if (LastNumMins!=NumMins(hour(),minute()))
  {
    LastNumMins=NumMins(hour(),minute());
    ReefAngel.LCD.Clear(255,0,120,132,132);
    ReefAngel.LCD.DrawText(0,255,5,120,"C");
    ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
    ReefAngel.LCD.DrawText(0,255,45,120,"L");
    ReefAngel.LCD.DrawText(0,255,51,120,"00:00");
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11; else x=17;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart/60));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

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

Re: Let's Build Troubadour11's Code

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

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

Re: Let's Build Troubadour11's Code

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

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

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

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

Code: Select all

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

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

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

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

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

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

//added for custom menu set up
#include <avr/pgmspace.h>
// Create the menu entries
prog_char menu1_label[] PROGMEM = "Feeding";
prog_char menu2_label[] PROGMEM = "Water Change";
prog_char menu3_label[] PROGMEM = "ATO Clear";
prog_char menu4_label[] PROGMEM = "Overheat Clear";
prog_char menu5_label[] PROGMEM = "PH Calibration";
prog_char menu6_label[] PROGMEM = "Date / Time";
prog_char menu7_label[] PROGMEM = "Version";
prog_char menu8_label[] PROGMEM = "Storm Effect";
// Group the menu entries together
PROGMEM const char *menu_items[] = {menu1_label, menu2_label, menu3_label, menu4_label, menu5_label, menu6_label, menu7_label, menu8_label};
// Creating the custom menu fucntions. menu2_label corresponds to MenuEntry2 funtion
void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}
void MenuEntry3()
{
ReefAngel.ATOClear();
ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry4()
{
ReefAngel.OverheatClear();
ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry5()
{
ReefAngel.SetupCalibratePH();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry6()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.DisplayVersion();
}
// Trying to add a menu to the RA Head Unit so I can trigger a cloud effect on demand.
void MenuEntry8()
{
ForceCloud=true;
ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

  // Percentage chance of a cloud happening today
  // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 50

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

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

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

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

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

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

  // Percentage chance of lightning happening for every cloud
     // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Change_per_Cloud 50
 
  // Note: Make sure to choose correct values that will work within your PWMSLope settings.
  // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
  // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen results could happen.
    // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
  // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510 minutes window.
    // It's a tight fit, but it did.
//____________________________________________________________________________________________________________________________________________________________
  //***Question***
  // Why do you have to double the amount of time from 250 to 500 in your example? - This is just a question to understand the programming better, I don't see why it needs to be doubled.
  // I see it in the code dividing by (numclouds*2), etc. What's the purpose of multiplying it up by 2? I'm just trying to learn on this question if you have time to explain. Thanks :-)
//____________________________________________________________________________________________________________________________________________________________

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


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

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

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

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

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


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

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

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

        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning on the next cloud.
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;
      }
    }
  // Now that we have all the parameters for the cloud, let's create the effect down below.

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

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

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

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

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

    if (NumMins(hour(),minute())>(cloudstart+cloudduration))
    {
      cloudindex++;
      if (cloudindex < numclouds)
      {
        cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
        // pick a random number for the cloud duration of first cloud.
        cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//************************************************************************************************************************************************************************************************
//// - I think this resets the random offset and cloudstart time for the RDT Fixture to match the new/next "cloudstart" time if there will be another cloud today ////
//************************************************************************************************************************************************************************************************
        // pick a random number between these values to offset the RDT cloud by this many minutes
        //RDTcloudstartOffset=random(-1,1);
        // adds the random offset value to "cloudstart" and sets the cloudstart time for RDT light fixture
        //RDTcloudstart=cloudstart+RDTcloudstartOffset;
//***********************************************************************************************************************************************************************
        //Pick a random number between 0 and 99
        LDTlightningchance=random(100);
        //RDTlightningchance=random(100);
        // if picked number is greater than Lightning_Change_per_Cloud, we will not have lightning today
        if (LDTlightningchance>Lightning_Change_per_Cloud) LDTlightningchance=0;
        //if (RDTlightningchance>Lightning_Change_per_Cloud) RDTlightningchance=0;

      }
    }
 
  if (LastNumMins!=NumMins(hour(),minute()))
  {
    LastNumMins=NumMins(hour(),minute());
    ReefAngel.LCD.Clear(255,0,120,132,132);
    ReefAngel.LCD.DrawText(0,255,5,120,"C");
    ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
    ReefAngel.LCD.DrawText(0,255,45,120,"L");
    ReefAngel.LCD.DrawText(0,255,51,120,"00:00");
    if (cloudchance && (NumMins(hour(),minute())<cloudstart))
    {
      int x=0;
      if ((cloudstart/60)>=10) x=11; else x=17;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart/60));
      if ((cloudstart%60)>=10) x=29; else x=35;
      ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
    }
    ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
    if (LDTlightningchance)
    {
      int x=0;
      if (((cloudstart+(cloudduration/2))/60)>=10) x=51; else x=57;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
      if (((cloudstart+(cloudduration/2))%60)>=10) x=69; else x=75;
      ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
    }
  }
}

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

Re: Let's Build Troubadour11's Code

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

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

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

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

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

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

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

Thanks again for looking at things for me.