Page 5 of 9

Re: Lee's Feature Complete PDE

Posted: Wed Sep 18, 2013 8:49 pm
by lnevo
I am going to add to the dosing log routine the tracking of my water change pump. All the routines are there already and ready to go, just have to add the variables and have it clear when water change mode starts. This will also tell me exactly how much water is in my mixing bin :) i want to calibrate the water level gauge at some point and then i can use that to calibrate the ro/di one. I have a week to get that done :)

Re: Lee's Feature Complete PDE

Posted: Tue Oct 08, 2013 3:57 pm
by InPandaWeTrust
Hi Lee, you've really written some awesome code here! I've been threading through it and adjusting it for my needs for 2 weeks solid :o Thanks for sharing!

What ReefAngel Class are you using? I'm getting a lot of compiling errors, things like "has no member named 'CustomVar'" so I assume you have edited the ReefAngel class that comes with the installer?
On the first page, you mention 3 lines that need to be put in the update/features txt. I can't seem to find the file you mention, could you please point me in the right direction?

Re: Lee's Feature Complete PDE

Posted: Tue Oct 08, 2013 4:06 pm
by lnevo
The ReefAngel class I'm using is from the wifialert patch that i have in my git repo.

http://github.com/lnevo/Libraries

The CustomVar array is part of the stock libraries...can you post the specific error?

Re: Lee's Feature Complete PDE

Posted: Tue Oct 08, 2013 4:08 pm
by lnevo
The feature.txt file should be in Documents\Arduino\update folder

If its not there Roberto please chime in :) Those *should* have been in current libraries but i think they were "misplaced". I'm hoping they will be in the next update.

So what elements have you used? Post a thread :) love to see what others are doing with it!

Re: Lee's Feature Complete PDE

Posted: Wed Oct 09, 2013 2:26 am
by InPandaWeTrust
lnevo wrote:The feature.txt file should be in Documents\Arduino\update folder

If its not there Roberto please chime in :) Those *should* have been in current libraries but i think they were "misplaced". I'm hoping they will be in the next update.

So what elements have you used? Post a thread :) love to see what others are doing with it!
I did have a look in both of my folders (Documents + Program Files) but found nothing like that. Have downloaded the zip from github now, hopefully this fixes my errors :) Will have to look tonight when I''m home from work.

I'm probably using about 70% of your code, the portions to do with certain things like water changes + auto feeding I've taken out. While automation is great, there are certain things I would just like to have control of myself :P I have not bought a reef angel unit yet, I'm saving up for a bit (just restarted my tank so lots of expenses there :roll: ) so I'm fiddling with an arduino mega to get the feel on how this all works.

Your tide class is amazing BTW. I'm hoping to use reef crest during the day and make a tidal surge(albeit lower flow rates :P) the nightmode.
In the section of your code where you set up all the flow modes, is there an option for randomly picking a mode for the day or morning/afternoon? How would i modify your code to make it use tidal swell mode as the night mode? Sorry for derailing your thread :oops: Will make a thread for myself later today. Will post the error that I'm getting later too :)

Re: Lee's Feature Complete PDE

Posted: Wed Oct 09, 2013 2:50 am
by lnevo
If you look in SetRF() you'll see where I'm setting it to night mode. Just change it to the mode you want and make sure the night mode memory location is set to true.

If its not in Documents\Arduino i have no clue where it installs to on Windows. Did you install the ReefAngel installer or did you manually install the libraries into the Arduino installation?

Re: Lee's Feature Complete PDE

Posted: Wed Oct 09, 2013 3:00 am
by InPandaWeTrust
lnevo wrote:If you look in SetRF() you'll see where I'm setting it to night mode. Just change it to the mode you want and make sure the night mode memory location is set to true.

If its not in Documents\Arduino i have no clue where it installs to on Windows. Did you install the ReefAngel installer or did you manually install the libraries into the Arduino installation?
Thinking back, I initially used the reef angel installer but had to reinstall my PC so i just saved my libraries and the code I was working on then copied it over. This might be the root of my probelm :roll: will reinstall using the reef angel installer.

One thing I wondered while I was going through your code, what does RF stand for? I've got jebao wp25s so I've been editing all the vortech stuff to make it work for my pumps. Have to DIY my own cables for them which is going to be fun :ugeek:

Re: Lee's Feature Complete PDE

Posted: Wed Oct 09, 2013 4:01 am
by lnevo
I have the vortechs which use an RF signal generated via the RF module.

Re: Lee's Feature Complete PDE

Posted: Wed Oct 16, 2013 12:01 am
by InPandaWeTrust
I have tried compiling your code(without any modifications) using only your libraries from github and I still get the error when compiling about CustomVar not being part of the ReefAngelClass. It mentions others as well, but maybe its part of the same problem. Its all with the reefangel class. I've tried through the reefangel installer and normal arduino. The lines it highlights are in a few of the functions, sunrise, dosing pumps etc. Any ideas on how I can fix that? I should probably start a new thread for this :/

Re: Lee's Feature Complete PDE

Posted: Wed Oct 16, 2013 4:29 am
by lnevo
Yeah my code doesn't compile so easily I've discovered but that doesn't explain why its complaining about CustomVar..

Can you create a wizard based code or use one of the examples and add a line in loop:

ReefAngel.CustomVar[0]=1;

And see what happens?

Re: Lee's Feature Complete PDE

Posted: Wed Oct 16, 2013 1:02 pm
by InPandaWeTrust
With this in the loop :

Code: Select all

void loop()
{
  ReefAngel.CustomVar[0]=1;
  ReefAngel.PWM.SetDaylight(MoonPhase());
  ReefAngel.PWM.SetActinic(MoonPhase());
  ReefAngel.ShowInterface();
}
I get the 'classReefAngelClass' has no member named 'CustomVar'. I tried the same code on the pre-loaded RA example and then there was no such error. What does CustomVar do? Maybe I can edit it out or change it

Re: Lee's Feature Complete PDE

Posted: Wed Oct 16, 2013 3:37 pm
by lnevo
Sounds like maybe your missing an include

Re: Lee's Feature Complete PDE

Posted: Sat Oct 19, 2013 1:56 pm
by lnevo
Pfft, I have a bug with my water change code and the ATO is getting overridden causing the timeout to trigger cause the pump never goes on... gonna have to find some time to troubleshoot this. :(

I don't see anywhere it's not working and I even went back a version and still not working.. WTF!

Re: Lee's Feature Complete PDE

Posted: Mon Oct 21, 2013 8:14 pm
by lnevo
Found my bug! Now I get to fix it. I setup the ATO Refill / WC Pump as a DosingPump so I could take advantage of my rate calculation and use it to determine the water change volume :) The problem is that with the RunDosingPumps() will turn it off... I could always move RefillATO and AutoWaterChange to run after the RunDosingPumps... or fix it in the code... let's see....

Re: Lee's Feature Complete PDE

Posted: Mon Oct 21, 2013 8:53 pm
by lnevo
Ok, Not *thrilled* with my fix... but it works. I created a new numPumps variable inside RunDosing that = numDPumps - 1 so that it wouldn't iterate over the last pump defined. I'll need to find a better way to do this.. maybe this will stimulate me to write the DosingPump class because then I can have an option belonging to that object to run or not run (i.e. log and/or calibrate only...) This has been needed for a while to make this mechanism easier for people to use.

Anyway, latest code is posted as usual. Hopefully next WC will not be as painful...

Re: Lee's Feature Complete PDE

Posted: Wed Jan 08, 2014 11:30 am
by bencollinz
so, I've been trying to copy/implement your random vortech modes/times code into mine. and I'm just not getting it. is there any way you can dumb it down for me please?

Re: Lee's Feature Complete PDE

Posted: Wed Jan 08, 2014 12:49 pm
by lnevo
I'm not really using the vortech modes in the random routine, but it could be easily modified to do so. I'll take a look at the code later and see what I can break out for you.

Re: Lee's Feature Complete PDE

Posted: Thu Jan 09, 2014 8:07 pm
by lnevo
Here is PaulTurner911's code modified for the Vortech RF module. I have only tested to make sure this compiles.

What this will do is go into go into Smart_NTM for one hour AFTER feeding mode ends ***. It will go into night mode between 10:30pm and 8:30am and then finally if the mode is set to Custom in the portal, it will select a random mode from the modes[] array.

If you want more modes added just add it to the array. If you want a mode to happen more frequently... add it multiple times in the array.

Enjoy

Code: Select all

// Add random mode if we set to Mode to Custom in portal
static int rmode;
static boolean changeMode=true;

// These are the modes we can cycle through. You can add more and even repeat...
byte modes[] = { ReefCrest, TidalSwell, ShortPulse, NutrientTransport };

if (now()%SECS_PER_DAY==0 || changeMode==true) { // Change at midnight or if controller rebooted
rmode=random(100)%sizeof(modes); // Change the mode once per day to pick from our array
changeMode=false;
}

// Set timer when in feeding mode
static unsigned long feeding;
if (ReefAngel.DisplayedMenu==FEEDING_MODE) feeding=now();

if (now()-feeding>0 && now()-feeding<3600) { 
  // Continue NTM for the 60 minutes
  ReefAngel.RF.UseMemory=false;
  ReefAngel.RF.Mode=NutrientTransport;
} else if (now()%SECS_PER_DAY<30600 || now()%SECS_PER_DAY>=81000) { // 8:30am / 10:30pm
  // Night mode (go to 30%)
  ReefAngel.RF.UseMemory=false;
  ReefAngel.RF.Mode=Constant;
  ReefAngel.RF.Speed=30;
} else if (InternalMemory.RFMode_read()==11) { 
  // Custom Mode and nothing else going on
  ReefAngel.RF.UseMemory=false;
  ReefAngel.RF.Mode=modes[rmode];  // Put the mode to the random mode :)
  ReefAngel.RF.Speed=InternalMemory.RFSpeed_read(); // Set speed from portal
} else {
  ReefAngel.RF.UseMemory=true; // Will reset all values from memory
}
The original code made for DCPump is here http://forum.reefangel.com/viewtopic.php?p=32261#p32261

*** Paul's original request was to have Smart_NTM happen DURING feeding mode which I don't think the majority want. You wan the post feeding mode if statement to look like this if you want it after.

Code: Select all

if (now()-feeding>0 && now()-feeding<3600) { 

Re: Lee's Feature Complete PDE

Posted: Fri Jan 10, 2014 12:18 pm
by bencollinz
ok. thank you very much. I really do appreciate you continually helping me! I will look this over.

Re: Lee's Feature Complete PDE

Posted: Sun Jan 12, 2014 8:33 pm
by aranax
Hey Lee et al,
Have you seen this issue before? I used your PDE an altered it slightly to fit my needs (changed port assignment, commented out things I don't use, etc.). The issue i have is my controller freezes on what looks to be a corrupted chart-like screen.

Symptom
- RA randomly freezes
--- Current state of ports are frozen
--- Unit does not respond to menu initiation
--- Requires hard restart to get the controller back online

I tried to resolve it by enabling the watchdog timer that I used with my older RA but that didn't work.

Can anyone please help? Thanks.

Frozen:
Image


After Reboot/Normal
Image

Code: Select all

    #include <ReefAngel_Features.h>
    #include <Globals.h>
    #include <RA_Wifi.h>
    #include <Wire.h>
    #include <OneWire.h>
    #include <Time.h>
    #include <DS1307RTC.h>
    #include <InternalEEPROM.h>
    #include <RA_NokiaLCD.h>
    #include <RA_ATO.h>
    #include <RA_Joystick.h>
    #include <LED.h>
    #include <RA_TempSensor.h>
    #include <Relay.h>
    #include <RA_PWM.h>
    #include <Timer.h>
    #include <Memory.h>
    #include <RA_Colors.h>
    #include <RA_CustomColors.h>
    #include <RF.h>
    #include <ReefAngel.h>
    #include <SunLocation.h>
    #include <WaterLevel.h>
    #include <Tide.h>
    #include <Moon.h>
    #include <WiFiAlert.h>

    // Custom menus
    #include <avr/pgmspace.h>
    prog_char menu1_label[] PROGMEM = "Refugium Light";
    prog_char menu2_label[] PROGMEM = "Feeding Mode";
    prog_char menu3_label[] PROGMEM = "Water Change";
    prog_char menu4_label[] PROGMEM = "Vortech Mode";
    prog_char menu5_label[] PROGMEM = "ATO Clear";
    prog_char menu6_label[] PROGMEM = "Overheat Clear";
    prog_char menu7_label[] PROGMEM = "PH Calibration";
    prog_char menu8_label[] PROGMEM = "WLS Calibration";
    prog_char menu9_label[] PROGMEM = "Date / Time";

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

    // Define Custom Memory Locations
    #define Mem_B_MoonSlope       100
    #define Mem_B_Vacation        101
    #define Mem_B_AutoFeed        102
    #define Mem_B_AutoFeedPress   103
    #define Mem_B_AutoFeedRepeat  104
    #define Mem_B_AutoFeedOffset  105
    #define Mem_I_WCFillTime      106
    #define Mem_I_Latitude        108
    #define Mem_I_Longitude       110
    #define Mem_B_AcclRiseOffset  112
    #define Mem_B_AcclSetOffset   113
    #define Mem_B_AcclDay         114
    #define Mem_B_SwabbieRepeat   115
    #define Mem_B_SwabbieTime     116
    #define Mem_B_TideMin         117
    #define Mem_B_TideMax         118
    #define Mem_B_PumpOffset      119
    #define Mem_B_FeedingRF       120
    #define Mem_B_NightRF         121
    #define Mem_B_NightSpeed      122
    #define Mem_B_NightDuration   123
    #define Mem_B_NTMSpeed        124
    #define Mem_B_NTMDuration     125
    #define Mem_B_NTMDelay        126
    #define Mem_B_NTMTime         127
    #define Mem_I_CalDP1Vol       128
    #define Mem_I_CalDP1Time      130
    #define Mem_I_DP1Volume       132
    #define Mem_I_CalDP2Vol       134
    #define Mem_I_CalDP2Time      136
    #define Mem_I_DP2Volume       138
    #define Mem_B_LogATO          140
    #define Mem_B_LogPrevATO      141
    #define Mem_B_RateAlarm       142
    #define Mem_B_TideMode        143
    #define Mem_B_MaintGAC        144
    #define Mem_B_MaintGFO        145
    #define Mem_B_MaintCal        146
    #define Mem_B_MaintAlk        147
    #define Mem_B_MaintWC         148
    #define Mem_B_MaintATO        149
    #define Mem_B_MaintFeeding    150
    #define Mem_B_MaintSkimmer    151
    #define Mem_B_MaintSocks      152
    #define Mem_B_MaintWCVol      153
    #define Mem_I_CalDP3Vol       154
    #define Mem_I_CalDP3Time      156
    #define Mem_I_DP3Volume       158

    #define Mem_B_PrintDebug      198
    #define Mem_B_ResetMemory     199

    void init_memory() {
      // Initialize Custom Memory Locations
      InternalMemory.write(Mem_B_MoonSlope,60);
      InternalMemory.write(Mem_B_Vacation,false);
      InternalMemory.write(Mem_B_AutoFeed,true);
      InternalMemory.write(Mem_B_AutoFeedPress,2);
      InternalMemory.write(Mem_B_AutoFeedRepeat,4);
      InternalMemory.write(Mem_B_AutoFeedOffset,3);
      InternalMemory.write_int(Mem_I_WCFillTime,240);
      InternalMemory.write_int(Mem_I_Latitude,21);
      InternalMemory.write_int(Mem_I_Longitude,-73);
      InternalMemory.write(Mem_B_AcclRiseOffset,4);
      InternalMemory.write(Mem_B_AcclSetOffset,2);
      InternalMemory.write(Mem_B_AcclDay,0);
      InternalMemory.write(Mem_B_SwabbieRepeat,6); 
      InternalMemory.write(Mem_B_SwabbieTime,1); 
      InternalMemory.write(Mem_B_TideMin,10);
      InternalMemory.write(Mem_B_TideMax,30);
      InternalMemory.write(Mem_B_PumpOffset,70);
      InternalMemory.write(Mem_B_FeedingRF,true);
      InternalMemory.write(Mem_B_NightRF,false);
      InternalMemory.write(Mem_B_NightSpeed,30);
      InternalMemory.write(Mem_B_NightDuration,10);
      InternalMemory.write(Mem_B_NTMSpeed,65);
      InternalMemory.write(Mem_B_NTMDuration,5);
      InternalMemory.write(Mem_B_NTMDelay,15);
      InternalMemory.write(Mem_B_NTMTime,150);
      InternalMemory.write_int(Mem_I_CalDP1Vol,11); 
      InternalMemory.write_int(Mem_I_CalDP1Time,600); 
      InternalMemory.write_int(Mem_I_DP1Volume,30); 
      InternalMemory.write_int(Mem_I_CalDP2Vol,11); 
      InternalMemory.write_int(Mem_I_CalDP2Time,600);   
      InternalMemory.write_int(Mem_I_DP2Volume,30);   
      InternalMemory.write_int(Mem_I_CalDP3Vol,3); 
      InternalMemory.write_int(Mem_I_CalDP3Time,270);   
      InternalMemory.write_int(Mem_I_DP3Volume,0);     
      InternalMemory.write(Mem_B_RateAlarm,15);
      InternalMemory.write(Mem_B_TideMode,0);

      InternalMemory.write(Mem_B_LogATO,0);
      InternalMemory.write(Mem_B_LogPrevATO,0);
    //  InternalMemory.write(Mem_B_MaintGAC,0);
    //  InternalMemory.write(Mem_B_MaintGFO,0);
    //  InternalMemory.write(Mem_B_MaintCal,0);
    //  InternalMemory.write(Mem_B_MaintAlk,0);
      InternalMemory.write(Mem_B_MaintWC,0);
      InternalMemory.write(Mem_B_MaintATO,0);
      InternalMemory.write(Mem_B_MaintFeeding,0);
      InternalMemory.write(Mem_B_MaintSkimmer,0);
      InternalMemory.write(Mem_B_MaintSocks,0);

      InternalMemory.write(Mem_B_ResetMemory,false);
    }

    #define NUMBERS_8x16

    // Define Portal Variables
    #define Var_Feedings     0
    #define Var_DPump1       1
    #define Var_DPump2       2
    #define Var_AcclDay      3
    #define Var_Tide         4
    #define Var_TideMode     5
    #define Var_LogATO       6
    #define Var_WCVol        7

    // Define Relay Ports by Name
    #define RightLED           1   
    #define ATOSump            2
    #define MidLED             3
    #define Heater             4   
    #define Skimmer            5
    #define SumpCirc           6
    #define Return             7
    #define WaveRight          8
    //#define WhiteLEDs          3
    //#define BlueLEDs           4
    //#define Extension          5
    //#define Refugium           7
    //#define Reactor            8

    #define LeftLED            Box1_Port1
    #define Vortech1             Box1_Port2
    #define Reflights          Box1_Port3
    #define Dosing             Box1_Port4
    #define TBD2               Box1_Port5
    #define WaveLeft           Box1_Port6
    #define Feeder             Box1_Port7
    #define ATOres             Box1_Port8

    //#define Swabbie            Box1_Port1
    //#define Feeder             Box1_Port2

    //#define Vortech2           Box1_Port4
    //#define VortechUPS         Box1_Port5
    //#define Unused             Box1_Port6
    //#define DPump1             Box1_Port7
    //#define DPump2             Box1_Port8

    #define VO_RefillATO       Box2_Port1
    #define VO_EnableATO       Box2_Port2
    #define VO_StartFill       Box2_Port3
    #define VO_Vacation        Box2_Port4
    #define VO_AutoFeed        Box2_Port5
    #define VO_Unused          Box2_Port6
    #define VO_Calibrate       Box2_Port7
    #define VO_LockPorts       Box2_Port8

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

    boolean dosing=false;
    byte vtechmode;
    //boolean bFeeding=false;

    // Custom classes
    SunLocation sun;
    Tide tide;

    // Vortech Variables
    byte vtMode, vtSpeed, vtDuration;

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

    // Setup on controller startup/reset
    void setup()
    {
      // This must be the first line
      wdt_enable(WDTO_1S);
      ReefAngel.Init();  //Initialize controller
      ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items)); // Initialize Menu
       
      // Ports toggled in Feeding Mode
      ReefAngel.FeedingModePorts = Port7Bit | Port5Bit ;
      // Ports toggled in Water Change Mode
      ReefAngel.WaterChangePorts = Port7Bit | Port5Bit ;
      // Ports toggled when Lights On / Off menu entry selected
      ReefAngel.LightsOnPorts = Box1_Port3;
      // Ports turned off when Overheat temperature exceeded
      ReefAngel.OverheatShutoffPorts = Port4Bit ;
      // Use T1 probe as temperature and overheat functions
      ReefAngel.TempProbe = T1_PROBE;
      ReefAngel.OverheatProbe = T1_PROBE;
       
      // Ports that default on
      ReefAngel.Relay.On(Return);
      ReefAngel.Relay.On(ATOSump);
      ReefAngel.Relay.On(Vortech1);
      ReefAngel.Relay.On(SumpCirc);
      //ReefAngel.Relay.On(VortechUPS);
      //ReefAngel.Relay.On(VO_LockPorts);     
     
      // Ports that default off
      //ReefAngel.Relay.Off(Extension);
      //ReefAngel.Relay.Off(Swabbie);
      //ReefAngel.Relay.Off(Feeder);
      //ReefAngel.Relay.Off(Unused);
       
      ////// Place additional initialization code below here
      ReefAngel.RF.UseMemory=false;
      randomSeed(now()/SECS_PER_DAY);
       
      if (InternalMemory.read(Mem_B_ResetMemory))
        init_memory();
      ////// Place additional initialization code above here
    }

    void loop()
    {
      //ReefAngel.MoonLights(Refugium);
      //ReefAngel.ActinicLights(BlueLEDs);
      //ReefAngel.StandardLights(WhiteLEDs);
      ReefAngel.StandardHeater( (Heater),751,780 );
      //ReefAngel.StandardLights( (Skimmer),0,0,20,0 );
      ReefAngel.StandardLights( (WaveRight),6,0,11,30 );
      ReefAngel.DosingPumpRepeat( (Dosing),0,60,10 );
      ReefAngel.StandardLights( (WaveLeft),11,30,17,30 );
      ReefAngel.StandardHeater(Heater);

      ////// Place your custom code below here
      CheckPower();           // Monitor for power outages
//      CheckSwitches();        // Monitor for float switches
      SetSun();               // Setup Sun rise/set lighting
      SetMoon();              // Setup Moon rise/set lighting
      SetTide();              // Set High/Low tide properties
      SetRF();                // Set Vortech modes
      CheckATO();             // Calculate ATO reservoir evaporation
//      RefillATO();            // Automatic ATO reservoir refill
//      Vacation();             // Automation while on Vacation
//      LogFeedings();          // Track a relay
//      RunFeeder();            // Automatic Fish feeder
 //     RunSwabbie();           // Skimmer neck cleaner
 //     RunDosingPumps();       // Dose by volume or time
 //     LogDosingPumps();       // Keep track of dosing
 //     CalibrateDPumps();      // Calibrate Dosing pumps
 //     AutoWaterChange();      // Automated water change
      Reminders();            // Increment maintenance counters
 //     LockPorts();            // Clear overrides for critical ports
     
      DelayedOnModes(Skimmer); // DelayedOn after mode change only
      
    //Dosing
    if (ReefAngel.Params.PH<800 && !dosing)
    {
      ReefAngel.Timer[1].Start();
      ReefAngel.Timer[2].Start();
      ReefAngel.Relay.On((Dosing));
      dosing=true;
    }
 
    if (ReefAngel.Timer[1].IsTriggered())
    {
      ReefAngel.Relay.Off((Dosing));
    }
 
    if (ReefAngel.Timer[2].IsTriggered())
    {
      dosing=false;
    }
    
    if (ReefAngel.Params.PH>855 && dosing)
    {
      ReefAngel.Relay.Off((Dosing));
    }

      //ATO Res.
      ReefAngel.WaterLevelATO( Box1_Port8,600,10,80 );
      
      //Skimmer failsafe
    if (ReefAngel.Relay.Status((Return)) != 1)
    {
    ReefAngel.Relay.Off((Skimmer));
    }
      
      ////// Place your custom code above here


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

    void CheckPower() {
      static boolean powerOutage=false;
      static WiFiAlert powerAlert;

      // Power Outage - turn off everything
      if (!ReefAngel.Relay.IsRelayPresent(EXP1_RELAY)) // Expansion Relay NOT present
      {
        powerOutage=true;
        ReefAngel.Relay.Off(Skimmer);
        //ReefAngel.Relay.Off(WhiteLEDs);
        //ReefAngel.Relay.Off(BlueLEDs);
        //ReefAngel.Relay.Off(Extension);
        ReefAngel.Relay.Off(Heater);
        //ReefAngel.Relay.Off(Refugium);
        //ReefAngel.Relay.Off(Reactor);
        ReefAngel.Relay.Off(Vortech1);
        //ReefAngel.Relay.Off(Vortech2);
        //ReefAngel.Relay.Off(VortechUPS);
        powerAlert.Send("Power+outage!");
      }

      // Power Restored - Turn things back on
      if (powerOutage && ReefAngel.Relay.IsRelayPresent(EXP1_RELAY))
      {
        LastStart=now();
        powerOutage=false;
        //ReefAngel.Relay.On(Reactor);
        ReefAngel.Relay.On(Vortech1);
        //ReefAngel.Relay.On(Vortech2);
        //ReefAngel.Relay.On(VortechUPS);
        powerAlert.Send("Power+restored.",true);
      }
    }

    void CheckSwitches() {
      static boolean returnOverride=true;
      static WiFiAlert switchAlert, skimmerAlert, atoAlert;
      skimmerAlert.SetDelay(3600);
      atoAlert.SetDelay(3600);
     
      if (ReefAngel.DisplayedMenu==FEEDING_MODE || ReefAngel.DisplayedMenu==WATERCHANGE_MODE)
       returnOverride=true;
     
      // Turn off return if sump overflowing or out of water in return
      ReefAngel.ReverseATOHigh(); // swap switch behavior so we can reuse as ATO
      if (!ReefAngel.HighATO.IsActive()) { // switch on by default
        if (!returnOverride) {
          switchAlert.Send("Sump+level+alarm!+Return+pump+disabled.");
          ReefAngel.Relay.Override(Return,0);
          returnOverride=true;
        }
      } else {
        returnOverride=false;
      }

      // Turn off Skimmer if waste collector is full.
      if (!ReefAngel.LowATO.IsActive()) { // switch is on by default
        skimmerAlert.Send("Skimmate+container+full!+Skimmer+disabled.");
        ReefAngel.Relay.Override(Skimmer,0);
        if (InternalMemory.read(Mem_B_MaintSkimmer) > 0) // Reset last Skimmer counter
          InternalMemory.write(Mem_B_MaintSkimmer,0);
      }
      
      // Turn off Skimmer if Return pump has been shutoff.   
      if (!ReefAngel.Relay.Status(Return)) {
        ReefAngel.Relay.Override(Skimmer,0);
      }
      
      // Alert if ATO timeput flag
      if (bitRead(ReefAngel.AlertFlags,ATOTimeOutFlag))
        atoAlert.Send("ATO+timeout!+ATO+disabled.");
    }

    void SetSun() {
      // Start acclimation routine
      int acclRiseOffset=InternalMemory.read(Mem_B_AcclRiseOffset)*60;
      int acclSetOffset=InternalMemory.read(Mem_B_AcclSetOffset)*60;
      byte acclDay=InternalMemory.read(Mem_B_AcclDay);
      ReefAngel.CustomVar[Var_AcclDay]=acclDay;

      // See if we are acclimating corals and decrease the countdown each day
      static boolean acclCounterReady=false;
      if (now()%SECS_PER_DAY!=0) acclCounterReady=true;
      if (now()%SECS_PER_DAY==0 && acclCounterReady && acclDay>0) {
        acclDay--;
        acclCounterReady=false;
        InternalMemory.write(Mem_B_AcclDay,acclDay);
      }
      // End acclimation

      sun.Init(InternalMemory.read_int(Mem_I_Latitude), InternalMemory.read_int(Mem_I_Longitude));
      sun.SetOffset(-1,(acclDay*acclRiseOffset),-1,(-acclDay*acclSetOffset)); // Bahamas
      sun.CheckAndUpdate(); // Calculate today's Sunrise / Sunset
    }

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

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

      static byte mp=MoonPhase();
     
      if (mp!=MoonPhase()) {
        InternalMemory.write(Mem_B_PWMSlopeEndD,mp);
        InternalMemory.write(Mem_B_PWMSlopeEndA,mp);
        mp=MoonPhase();
      }
     
      moon_init(InternalMemory.read_int(Mem_I_Latitude), InternalMemory.read_int(Mem_I_Longitude));
     
      ReefAngel.PWM.SetDaylight(PWMSlope(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM, startD,endD,timeD,0));
      ReefAngel.PWM.SetActinic(PWMSlope(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM, startA,endA,timeA,0));
    }

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

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

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

    void SetRF() {
      int ntmDelay=InternalMemory.read(Mem_B_NTMDelay)*60;
      int ntmTime=InternalMemory.read(Mem_B_NTMTime)*60;
      boolean nightRF=InternalMemory.read(Mem_B_NightRF);
      boolean feedingRF=InternalMemory.read(Mem_B_FeedingRF);
      static time_t t;

      vtMode=InternalMemory.RFMode_read();
      vtSpeed=InternalMemory.RFSpeed_read();
      vtDuration=InternalMemory.RFDuration_read();

      if ((now()-t > ntmDelay && now()-t < ntmTime+ntmDelay) && feedingRF) {
        // Post feeding mode
        vtMode=Smart_NTM;
        vtSpeed=InternalMemory.read(Mem_B_NTMSpeed);
        vtDuration=InternalMemory.read(Mem_B_NTMDuration);
      } else if (!sun.IsDaytime() && nightRF) {
        vtMode=Night;
        vtSpeed=InternalMemory.read(Mem_B_NightSpeed);
        vtDuration=InternalMemory.read(Mem_B_NightDuration);
      } else {
        if (vtMode!=Night && ReefAngel.RF.Mode==Night)
          ReefAngel.RF.SetMode(Night_Stop,0,0);
      }

      if (ReefAngel.DisplayedMenu==FEEDING_MODE) {
        t=now(); // Run post feeding mode when this counter stops
      } else if (ReefAngel.DisplayedMenu==WATERCHANGE_MODE) {
        ReefAngel.RF.SetMode(Constant,25,0);
      } else {
        if ((vtMode==Smart_NTM) || (vtMode==ShortPulse)) vtDuration=InternalMemory.read(Mem_B_NTMDuration);
        (vtMode==Custom) ? RFCustom() : ReefAngel.RF.SetMode(vtMode,vtSpeed,vtDuration);
      }
    }

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

      // Define new modes
      const int BHazard=15;
      const int Else=16;
      const int Sine=17;
     
      byte tideSpeed=tide.CalcTide();
      byte tideMin=InternalMemory.read(Mem_B_TideMin);
      byte tideMax=InternalMemory.read(Mem_B_TideMax);
      byte tideMode=InternalMemory.read(Mem_B_TideMode);
      float pumpOffset=(float) InternalMemory.read(Mem_B_PumpOffset)/100;

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

      if (now()%SECS_PER_DAY!=0) changeMode=true;
      if ((now()%SECS_PER_DAY==0 && changeMode)) {
        tideMode=random(100)%sizeof(RandomModes);
        InternalMemory.write(Mem_B_TideMode,tideMode);
        changeMode=false;
      }
     
      ReefAngel.CustomVar[Var_TideMode]=tideMode+1;

      switch (RandomModes[tideMode]) {
        case ReefCrest: {
          rcSpeed=ReefCrestMode(tideSpeed,vtDuration*2,true);
          rcSpeedAS=ReefCrestMode(tideSpeed,vtDuration*2,false);
          break;
        }
        case Lagoon: {
          rcSpeed=ReefCrestMode(tideSpeed,vtDuration,true);
          rcSpeedAS=ReefCrestMode(tideSpeed,vtDuration,false);
          break;
        }
        case TidalSwell: {
          rcSpeed=TidalSwellMode(tideSpeed,true);
          rcSpeedAS=TidalSwellMode(tideSpeed,false);
          break;
        }
        case Smart_NTM: {
          rcSpeed=NutrientTransportMode(0,tideSpeed,vtDuration*50,true);
          rcSpeedAS=NutrientTransportMode(0,tideSpeed,vtDuration*50,false);
          break;
        }
        case ShortPulse: {
          rcSpeed=ShortPulseMode(0,tideSpeed,vtDuration*50,true);
          rcSpeedAS=ShortPulseMode(0,tideSpeed,vtDuration*50,false);
          break;
        }
        case LongPulse: {
          rcSpeed=LongPulseMode(0,tideSpeed,vtDuration,true);
          rcSpeedAS=LongPulseMode(0,tideSpeed,vtDuration,false);
          break;
        }
        case BHazard: {
          rcSpeed=millis()%1200>800?tideSpeed:0;
          rcSpeedAS=millis()%1200<400?0:tideSpeed;
          break;
        }   
        case Else: {
          rcSpeed=ElseMode(tideSpeed,vtDuration*2,true);
          rcSpeedAS=ElseMode(tideSpeed,vtDuration*2,false);
          break;
        }   
        case Sine: {
          rcSpeed=SineMode(tideSpeed-tideMin,tideSpeed+tideMin,vtDuration*100,true);
          rcSpeedAS=SineMode(tideSpeed-tideMin,tideSpeed+tideMin,vtDuration*100,false);
          break;
        }
        default: {
          rcSpeed=tideSpeed;
          rcSpeedAS=tideSpeed; 
        }
      }

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

    // Monitor ATO reservoir
    void CheckATO() {
      static time_t t;
      static WiFiAlert atoLogAlert;   
      static boolean atoSaved;
      byte prevLevelOld, prevLevel, currLevel;
      byte numHours, rate, rateAlarm;
      char msg[32];
     
      atoLogAlert.SetDelay(SECS_PER_HOUR*6);
      currLevel=ReefAngel.WaterLevel.GetLevel();
      prevLevel=InternalMemory.read(Mem_B_LogATO);
      prevLevelOld=InternalMemory.read(Mem_B_LogPrevATO);
      numHours=(now()%(SECS_PER_HOUR*6))/SECS_PER_HOUR;
      rate=(prevLevelOld-currLevel)*(24/(6+numHours));
      rateAlarm=InternalMemory.read(Mem_B_RateAlarm);
      ReefAngel.CustomVar[Var_LogATO]=rate; 
     
      if ((rate<rateAlarm && rateAlarm!=0) && !ReefAngel.Relay.Status(VO_Vacation)) {
        if (!ReefAngel.Relay.Status(VO_RefillATO)) {
          sprintf(msg,"ATO+rate+(%d)+is+too+low!",rate);
          atoLogAlert.Send(msg);
        }
      }
     
      if (now()%(SECS_PER_HOUR*6)==0) { // Save level and alert every 6 hours
        if (!atoSaved) {
          atoSaved=true;
          InternalMemory.write(Mem_B_LogATO,currLevel);
          InternalMemory.write(Mem_B_LogPrevATO,prevLevel);
        }
      } else {
        atoSaved=false;
      }
     
    }

    // ATO Refill mode. Top off ATO reservoir until it's at 100%   
/*    void RefillATO() {
      static boolean refillActive=false;
      static WiFiAlert refillAlert;
      byte level, prevRate;

      if (ReefAngel.Relay.Status(VO_EnableATO) || ReefAngel.Relay.Status(VO_Vacation)) {
        ReefAngel.Relay.Override(VO_RefillATO,2);
      }

      level=ReefAngel.WaterLevel.GetLevel();
     
      if (ReefAngel.Relay.Status(VO_RefillATO)) {
        if (level<100) {
          ReefAngel.Relay.On(ATOres);
          refillActive=true;
        } else {
          ReefAngel.Relay.Override(VO_RefillATO,2);
          refillAlert.Send("ATO+Reservoir+is+full.");
          if (InternalMemory.read(Mem_B_MaintATO) > 0) // Reset last ATO counter
            InternalMemory.write(Mem_B_MaintATO,0);
        }
      } else {
        if (refillActive) {
          prevRate=InternalMemory.read(Mem_B_LogPrevATO)-InternalMemory.read(Mem_B_LogATO);
          InternalMemory.write(Mem_B_LogPrevATO, level+prevRate);
          InternalMemory.write(Mem_B_LogATO, level+1);

          ReefAngel.Relay.Off(ATORes);
          refillActive=false;
        }
      }
   }

    void Vacation() {
      static boolean onVacation=InternalMemory.read(Mem_B_Vacation);

      ReefAngel.Relay.Set(VO_Vacation, onVacation); // Sets RelayData only
      if (ReefAngel.Relay.Status(VO_Vacation)) { // Looks at actual status
        onVacation=ReefAngel.Relay.Status(VO_Vacation);
        InternalMemory.write(Mem_B_Vacation, onVacation);
        ReefAngel.Relay.Override(VO_Vacation,2); // Back to auto mode
      }
     
      if (onVacation) {
        ReefAngel.WaterLevelATO(ATORes);   

        if (now()%SECS_PER_DAY==19*SECS_PER_HOUR) {
          ReefAngel.FeedingModeStart();
        }
      }
    }
*/
    void LogFeedings() {
      static byte feedings;
      static boolean feedStatus;

      if (ReefAngel.Relay.Status(Feeder)) {
        feedStatus=true;
      } else {
        if (feedStatus) {
          feedStatus=false;
          feedings++;
        }
      }

      if (ReefAngel.DisplayedMenu==FEEDING_MODE) {
       if (InternalMemory.read(Mem_B_MaintFeeding) > 0) // Reset last time in Feeding Mode
         InternalMemory.write(Mem_B_MaintFeeding,0);
      }

      ReefAngel.CustomVar[Var_Feedings]=feedings;
      if (now()%SECS_PER_DAY==SECS_PER_DAY-1) feedings=0; // Clear feedings at end of day 
    }

    void RunFeeder() {
      int press=InternalMemory.read(Mem_B_AutoFeedPress);
      int repeat=InternalMemory.read(Mem_B_AutoFeedRepeat)*SECS_PER_HOUR;
      int offset=InternalMemory.read(Mem_B_AutoFeedOffset)*SECS_PER_HOUR;
      boolean autoFeed=InternalMemory.read(Mem_B_AutoFeed);
      static time_t t;
     
      ReefAngel.Relay.Set(VO_AutoFeed, autoFeed);
      if (ReefAngel.Relay.Status(VO_AutoFeed)!=autoFeed) {
        autoFeed=ReefAngel.Relay.Status(VO_AutoFeed);
        InternalMemory.write(Mem_B_AutoFeed, autoFeed);
        ReefAngel.Relay.Override(VO_AutoFeed,2);
      }
     
      if (autoFeed) {
        if (sun.IsDaytime() && (now()+offset)%repeat==0) {
          t=now();
        }
      }
     
      if (ReefAngel.Relay.isMaskOn(Feeder)) {
        ReefAngel.Relay.Override(Feeder,2);
        t=now()-now()%20; // One feeding has already happened.
      }
         
      // Press the button once every 20 seconds
      if (now()-t < press*20) {
        ReefAngel.Relay.Set(Feeder,now()%20==0);
      } else {
        ReefAngel.Relay.Off(Feeder);
      }
    }

/*    void RunSwabbie() {
      int repeat=InternalMemory.read(Mem_B_SwabbieRepeat)*60;
      int runtime=InternalMemory.read(Mem_B_SwabbieTime)*60;
      static time_t t;
     
      // Manual mode
      if (ReefAngel.Relay.isMaskOn(Swabbie)) {
        ReefAngel.Relay.Override(Swabbie,2);
        t=now();
      } 

      if (now()-t < runtime) {
        ReefAngel.Relay.On(Swabbie);
      } else {
        ReefAngel.DosingPumpRepeat(Swabbie,0,repeat,runtime);   
      } 
    }

    // Definitions for dosing pumps
    #define numDPumps 3
    byte pump[numDPumps]={ DPump1, DPump2, Extension}; // Pump 3 is just for logging/calibration routine
    byte varReport[numDPumps]={ Var_DPump1, Var_DPump2, Var_WCVol};
    byte memDPTime[numDPumps]={ Mem_B_DP1Timer, Mem_B_DP2Timer, Mem_B_DP3Timer};
    int memDPVolume[numDPumps]={ Mem_I_DP1Volume, Mem_I_DP2Volume, Mem_I_DP3Volume };
    int memDPRepeat[numDPumps]={ Mem_I_DP1RepeatInterval, Mem_I_DP2RepeatInterval, Mem_I_DP3RepeatInterval };
    int memCalTime[numDPumps]={ Mem_I_CalDP1Time, Mem_I_CalDP2Time, Mem_I_CalDP3Time };
    int memCalVol[numDPumps]={ Mem_I_CalDP1Vol, Mem_I_CalDP2Vol, Mem_I_CalDP3Vol };

    void RunDosingPumps() {
      float rate;
      byte dpTime;
      int dpRepeat, calcTime, totalVolume;
      const int numPumps = numDPumps-1; // -1 = Remove "Extension" from the equation.
     
      static byte doseTime[numPumps]={
        InternalMemory.read(memDPTime[0]),
        InternalMemory.read(memDPTime[1])
      };
     
      for (int i=0;i < numPumps; i++) {
        dpTime=InternalMemory.read(memDPTime[i]);
        dpRepeat=InternalMemory.read_int(memDPRepeat[i]);
        rate=(float)InternalMemory.read_int(memCalVol[i])/InternalMemory.read_int(memCalTime[i]);
        calcTime=(InternalMemory.read_int(memDPVolume[i])/rate)/(1440/dpRepeat);
        totalVolume=rate*(dpTime*(1440/dpRepeat));

        if (dpTime!=doseTime[i]) { // Memory has changed.
            InternalMemory.write_int(memDPVolume[i], totalVolume); // Update volume
            doseTime[i]=dpTime;
        } else if (dpTime!=calcTime) { // Calculated time has changed.
            InternalMemory.write(memDPTime[i], calcTime); // Update time
            doseTime[i]=calcTime;
        }

        // Make sure we're not calibrating
        if (!ReefAngel.Relay.Status(VO_Calibrate))
          ReefAngel.DosingPumpRepeat(pump[i], i*5, dpRepeat, doseTime[i]);
      }
    }

    void LogDosingPumps() {
      static time_t pumpTimer[numDPumps];
      static boolean pumpStatus[numDPumps];
      float rate;

      for (int i=0;i< numDPumps;i++) {
        if (ReefAngel.Relay.Status(pump[i])) {
          if (!pumpStatus[i]) {
            pumpTimer[i]=now()-pumpTimer[i]; // Pump was off, timer is now a time
            pumpStatus[i]=true;
          }
        } else {
          if (pumpStatus[i]) {
            pumpTimer[i]=now()-pumpTimer[i]; // Pump was on, timer is now a timer
            pumpStatus[i]=false;
       
            rate=(float)InternalMemory.read_int(memCalVol[i])/InternalMemory.read_int(memCalTime[i]);
            ReefAngel.CustomVar[varReport[i]]=pumpTimer[i]*rate;
          }
        }

        if (now()%SECS_PER_DAY==SECS_PER_DAY-1 && i!=2) { // Don't overwrite WC log
          pumpTimer[i]=0; // Clear timer at end of day
          ReefAngel.CustomVar[varReport[i]]=0; // Clear portal variable
        }
      } 
     
      // Reset logging for WC fill pump at start of WC
      static boolean wcStart;
      if (ReefAngel.DisplayedMenu!=WATERCHANGE_MODE) wcStart=false;
      if (ReefAngel.DisplayedMenu==WATERCHANGE_MODE && !wcStart) {
        ReefAngel.CustomVar[varReport[2]]=0;
        pumpTimer[2]=0;
        wcStart=true;
      }
    }

    void CalibrateDPumps() {
      static time_t pumpTimer[numDPumps];
      static boolean pumpStatus[numDPumps];
      static boolean running=false;
         
      if (ReefAngel.Relay.Status(VO_Calibrate)) {
        running=true;
       
        for (int i=0;i < numDPumps;i++) {
          if (ReefAngel.Relay.Status(pump[i])) {
            if (!pumpStatus[i]) {
              pumpTimer[i]=now()-pumpTimer[i]; // Pump was off, timer is now a time
              pumpStatus[i]=true;
            }
          } else {
            if (pumpStatus[i]) {
              pumpTimer[i]=now()-pumpTimer[i]; // Pump was on, timer is now a timer
              pumpStatus[i]=false;
            }
          }
        }     
      } else {
        if (running) {
          running=false;
         
          for (int i=0;i < numDPumps;i++) {
            if (pumpTimer[i]>0 && !ReefAngel.Relay.Status(VO_LockPorts)) {
              InternalMemory.write_int(memCalTime[i], pumpTimer[i]);
            }
            ReefAngel.Relay.Override(pump[i],2); // Go back to auto mode
            pumpStatus[i]=false;
            pumpTimer[i]=0;
          }
        }
      }   
    }

   void AutoWaterChange() {
      int runtime=InternalMemory.read_int(Mem_I_WCFillTime);
      static WiFiAlert wcAlert;
      static boolean started;
      static time_t t;

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

        if (ReefAngel.Relay.Status(VO_EnableATO)) {
         
          ReefAngel.SingleATOHigh(Extension); // Refill fresh SW as needed

          if (InternalMemory.read(Mem_B_MaintWC) > 0) // Reset last WC counter
            InternalMemory.write(Mem_B_MaintWC,0);
         
          if (ReefAngel.Relay.Status(VO_StartFill) && !started) {
            ReefAngel.Relay.Override(Reactor,1); // Start draining
            started=true;
            t=now();
          }
         
          if ( (now()-t > runtime && started) || bitRead(ReefAngel.AlertFlags,ATOTimeOutFlag) ) {
            ReefAngel.Relay.Override(Reactor,0); // Stop draining
            ReefAngel.Relay.Override(VO_StartFill,2); // Reset Start switch
            if (started) wcAlert.Send("Reactor+disabled.", true);
            started=false;
          }
         
        } else {
          // Done with ATO
          ReefAngel.Relay.Off(Extension);
          // Backup WC Vol
          if (InternalMemory.read(Mem_B_MaintWCVol) != ReefAngel.CustomVar[Var_WCVol])
            InternalMemory.write(Mem_B_MaintWCVol, ReefAngel.CustomVar[Var_WCVol]);
          // Clear stale timeout
          ReefAngel.ATOClear();
        }
      } else {
        ReefAngel.Relay.Override(VO_EnableATO,2);
        ReefAngel.Relay.Override(VO_StartFill,2);   
      }
    }

    // Disable masks for things that should not be turned on by mistake
    void LockPorts() {
      ReefAngel.AddPortOverrides();

      if (ReefAngel.Relay.Status(VO_LockPorts)) {
        ReefAngel.OverridePortsE[0] = Port7Bit | Port8Bit;
        ReefAngel.OverridePortsE[1] = Port4Bit | Port5Bit | Port7Bit;   
      } else {
        ReefAngel.OverridePortsE[0] = 0;
        ReefAngel.OverridePortsE[1] = 0;   
      }
    }
*/
    void Reminders() {
      static boolean counterReady;
      byte counter[]= { Mem_B_MaintGAC, Mem_B_MaintGFO, Mem_B_MaintCal, Mem_B_MaintAlk,
        Mem_B_MaintWC, Mem_B_MaintATO, Mem_B_MaintFeeding, Mem_B_MaintSkimmer, Mem_B_MaintSocks };
     
      if (now()%SECS_PER_DAY!=0) counterReady=true;
      if (now()%SECS_PER_DAY==0 && counterReady) {
        for (int i=0;i<sizeof(counter);i++) {
          InternalMemory.write(counter[i],InternalMemory.read(counter[i])+1);
        }
        counterReady=false; 
      }
    }

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

      if (vtMode!=InternalMemory.RFMode_read())
        InternalMemory.RFMode_write(vtMode);
      if (vtSpeed!=InternalMemory.RFSpeed_read())
        InternalMemory.RFSpeed_write(vtSpeed);
      if (vtDuration!=InternalMemory.RFDuration_read())
        InternalMemory.RFDuration_write(vtDuration);
    }

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

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

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

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

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

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

    void DrawStatus(int x, int y) {
      int t=x;
     
      ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,15,y,"High",Font8x16);
      ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,85,y,"Low",Font8x16);
     
      if (ReefAngel.HighATO.IsActive()) {
        ReefAngel.LCD.FillCircle(55,y+3,5,COLOR_GREEN);
      } else {
        ReefAngel.LCD.FillCircle(55,y+3,5,COLOR_RED);
      }
     
      if (ReefAngel.LowATO.IsActive()) {
        ReefAngel.LCD.FillCircle(70,y+3,5,COLOR_GREEN);
      } else {
        ReefAngel.LCD.FillCircle(70,y+3,5,COLOR_RED);
      }
      y+=12;
     
      // Display Water level
      ReefAngel.LCD.DrawText(DefaultFGColor,DefaultBGColor,x,y,"AT0 Level:"); x+=65;
      ReefAngel.LCD.DrawSingleMonitor(ReefAngel.WaterLevel.GetLevel(),DPColor,x,y,1);
      x+=5*(intlength(ReefAngel.WaterLevel.GetLevel())+1);
      ReefAngel.LCD.DrawText(DPColor, DefaultBGColor, x, y, "%");
      y+=12; x=t;
     
      // Vortech Mode
      ReefAngel.LCD.DrawText(0,255,x,y,"RF:"); x+=20;
      ReefAngel.LCD.Clear(DefaultBGColor,x,y,t+(128-t),y+8);
      if (vtMode == 0) ReefAngel.LCD.DrawLargeText(COLOR_GREEN,255,x,y,"Constant");
      else if (vtMode == 1) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,x,y,"Lagoon");
      else if (vtMode == 2) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,x,y,"Reef Crest");
      else if (vtMode == 3) ReefAngel.LCD.DrawLargeText(COLOR_RED,255,x,y,"Short Pulse");
      else if (vtMode == 4) ReefAngel.LCD.DrawLargeText(COLOR_RED,255,x,y,"Long Pulse");
      else if (vtMode == 5) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,x,y,"Smart NTM");
      else if (vtMode == 6) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,x,y,"Tidal Swell");
      else if (vtMode == 9) ReefAngel.LCD.DrawLargeText(COLOR_WHITE,0,x,y,"Night");
      else if (vtMode == 10) ReefAngel.LCD.DrawLargeText(COLOR_BLUE,0,x,y,"Storm");
      else if (vtMode == 11) ReefAngel.LCD.DrawLargeText(COLOR_BLUE,255,x,y,"Custom");
      y+=10; x=t;
     
      ReefAngel.LCD.DrawText(0,255,x,y,"RF Speed:"); x+=60;
      ReefAngel.LCD.Clear(DefaultBGColor,x,y,128,y+8);
      ReefAngel.LCD.DrawText(COLOR_BLUE, DefaultBGColor,x,y,vtSpeed); x+=15;
      ReefAngel.LCD.DrawText(COLOR_BLUE, DefaultBGColor,x,y,"/"); x+=10;
      ReefAngel.LCD.DrawText(COLOR_BLUE, DefaultBGColor,x,y,vtDuration);
      y+=10; x=t;
     
      // Display Acclimation timer
      if (ReefAngel.CustomVar[Var_AcclDay] > 0) {
        ReefAngel.LCD.DrawText(DefaultFGColor,DefaultBGColor,x,y,"Acclimation Day:"); x+=100;
        ReefAngel.LCD.DrawSingleMonitor(ReefAngel.CustomVar[Var_AcclDay],DefaultFGColor,x,y,1);
      } else {
        ReefAngel.LCD.Clear(DefaultBGColor,x,y,128,y+8);
      }
    }

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

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

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

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

    byte ElseMode( byte MidPoint, byte Offset, boolean WaveSync )
    {
      // Static's only initialize the first time they are called
      static unsigned long LastChange=millis();        // Set the inital time that the last change occurred
      static int Delay = random( 500, 3000);           // Set the initial delay
      static int NewSpeed = MidPoint;                  // Set the initial speed
      static int AntiSpeed = MidPoint;                 // Set the initial anti sync speed
      if ((millis()-LastChange) > Delay)               // Check if the delay has elapsed
      {
        Delay=random(500,5000);                        // If so, come up with a new delay
        int ChangeUp = random(Offset);                 // Amount to go up or down
        if (random(100)<50)                            // 50/50 chance of speed going up or going down
        {
          NewSpeed = MidPoint - ChangeUp;
          AntiSpeed = MidPoint + ChangeUp;
        }
        else
        {
          NewSpeed = MidPoint + ChangeUp;
          AntiSpeed = MidPoint - ChangeUp;
        }
        LastChange=millis();                           // Reset the time of the last change
      }
      if (WaveSync)
      {
        return NewSpeed;
      }
      else
      {
        return AntiSpeed;
      }
    }

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

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

Lee's Feature Complete PDE

Posted: Mon Jan 13, 2014 5:27 am
by lnevo
I would revert back to a wizard code and see if you still have issues.

If you want to troubleshoot then comment out the functions called in loop() one by one till you find the culprit.

But i don't think anything could cause an RA lockup..

Re: Lee's Feature Complete PDE

Posted: Mon Jan 20, 2014 7:03 am
by Visser
Hi lee. Your code is really very cool.

I'm trying to figure out how to code the "high" & "low" float indicators from your main screen into mine, but struggling with the code.
As in your picture below:
Image
Can you perhaps indicate the code lines with any other supporting code required to do this?
Thanks

Lee's Feature Complete PDE

Posted: Mon Jan 20, 2014 8:56 am
by lnevo
It's in the DrawStatus() function. You can add this code to the end of your INO and then call it in your DrawCustomMain with the coordinates you want it drawn at...

DrawStatus(10,20);

Code: Select all

void DrawStatus(int x, int y) {
  int t=x;
  
  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,15,y,"High",Font8x16);
  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,85,y,"Low",Font8x16);
  
  if (ReefAngel.HighATO.IsActive()) {
    ReefAngel.LCD.FillCircle(55,y+3,5,COLOR_GREEN);
  } else {
    ReefAngel.LCD.FillCircle(55,y+3,5,COLOR_RED);
  }
  
  if (ReefAngel.LowATO.IsActive()) {
    ReefAngel.LCD.FillCircle(70,y+3,5,COLOR_GREEN);
  } else {
    ReefAngel.LCD.FillCircle(70,y+3,5,COLOR_RED);
  }
}
I removed the rest of the function to just give you the switch status.

Re: Lee's Feature Complete PDE

Posted: Mon Jan 20, 2014 1:43 pm
by Visser
Thanks Lee! The code worked fantastic!!!
Sump + skimmer deactivated, Normal temps
Image

Sump & skimmer active, Normal temps
Image

Sump + skimmer deactivated, Low temps
Image

Sump + skimmer deactivated, Overhead temps
Image


Works like a charm. Thanks so much guys!!!
Here is the code if anyone wants to try.

Code: Select all

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

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

      // *********** CHANGE TEMP READOUT COLOR DEPENDENT ON FAN AND HEATER STATUS ***********
      int TempColor;        // Color for drawing temperature
      boolean FanOn = ReefAngel.Relay.Status(Port7);    // Get the status of the fan relay
      boolean HeatOn = ReefAngel.Relay.Status(Port6);   // Get the status of the heater relay
      if (HeatOn) 
      {
        TempColor = COLOR_NAVY;    // Blue text, too cold, heater is on
      }
      if (FanOn)   
      {
          TempColor = COLOR_RED;   // Red text, too warm, fan is on
      }
      if (!HeatOn && !FanOn) 
      {
          TempColor = COLOR_GREEN;  // Green text, no fan or heater on
      }
      // ***********************************************************************************
     
      ReefAngel.LCD.DrawText(DefaultFGColor, DefaultBGColor, 30, 2, "CJ's PICO REEF");       // Put a banner at the top
      ReefAngel.LCD.DrawDate(6, 119);                                                      // Put the date and time at the bottom
      ReefAngel.LCD.Clear(COLOR_BLACK, 1, 11, 132, 11);                                    // Draw a black line under the banner
      x = 6;
      y += MENU_START_ROW*1.0;                                                               // MENU_START_ROW is 10, according to globals.h, so y=2+10+1=13
      ReefAngel.LCD.DrawText(COLOR_BLUE, COLOR_WHITE, x, y+1, "Pico Temp   PH");
      x = 6;
      y = MENU_START_ROW*0.8;
      ConvertNumToString(text, ReefAngel.Params.PH, 100);                                  // Get pH reading and convert
      ReefAngel.LCD.DrawLargeText(PHColor, DefaultBGColor, x+73, y+12, text, Font8x16);    // Put pH on the screen
      ConvertNumToString(text, ReefAngel.Params.Temp[T1_PROBE], 10);                       // Get T1 temp and convert
      x = 6;
      y += MENU_START_ROW*1.3;
      ReefAngel.LCD.DrawHugeNumbers(COLOR_WHITE, TempColor, x, y, text);                   // Draw the temperature, white numbers on a colored background
      x += 6;
      y += MENU_START_ROW*1.2;
      ReefAngel.LCD.DrawText(COLOR_BLUE, COLOR_WHITE, x-6, y+5, "Room Temp");
      ConvertNumToString(text, ReefAngel.Params.Temp[T2_PROBE], 10);                       // Get T2 temp and convert
      y += MENU_START_ROW*1.2;
      x = 6;
      ReefAngel.LCD.DrawHugeNumbers(COLOR_WHITE, TempColor, x, y, text);                   // Draw the temperature, white numbers on a colored background
      x += 6;
      y += MENU_START_ROW*2.2;

  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,25,y,"Sump Level",Font8x8);            // Draw the Float switch status
  if (ReefAngel.HighATO.IsActive()) {
    ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_RED);
  } else {
    ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_GREEN);
  }
  x += 6;
      y += MENU_START_ROW*1.6;
  ReefAngel.LCD.DrawLargeText(COLOR_INDIGO,DefaultBGColor,25,y,"Skimmer Cup",Font8x8);
  if (ReefAngel.LowATO.IsActive()) {
    ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_RED);
  } else {
    ReefAngel.LCD.FillCircle(15,y+3,7,COLOR_GREEN);
  }
      
      
      byte TempRelay = ReefAngel.Relay.RelayData;                                          // Code for drawing the relay box
      TempRelay &= ReefAngel.Relay.RelayMaskOff;
      TempRelay |= ReefAngel.Relay.RelayMaskOn;
      ReefAngel.LCD.DrawOutletBox(12, 100, TempRelay);
   }
void DrawCustomGraph()
{
}
////// Place global variable code above here

Re: Lee's Feature Complete PDE

Posted: Wed Mar 12, 2014 6:37 pm
by lnevo
Haven't had any updates in a while...mainly because i have nothing new to add yet.

Today I re-calibrated my pumps. It was the coolest method. Since I'm storing the calibration values on the RA and I keep track of the last time i filled my dosing containers...here is what I did...

In the past 31 days I used 1250ml of part a and 1200ml of part b. I calculated how much time the doser ran over the 31 days. 79 seconds * 24 doses * 31 days... Then i was able to calculate the actual flow rate. 1.276 ml/min on pump a and 1.224 on pump b. I reduced the fraction to my normal calibration range which came to 470 and 490 seconds respectively to fill 10ml of fluid.

This obviously reset my dosing time since my target volume didn't change. I then modified the volume to get back to the time i was at originally. Based on the new rate to get the same dosing time on pump a i had to change 35 ml to 40 ml. So now pump a went from 79 seconds to 78 seconds and 79 on pump b went to 81 seconds. That should get me an extra 30-50 ml a month to make up for the difference. Pretty accurate calculations I would say.

I am thinking of making this more accurate by calculating the dosing time in milliseconds. This would allow you to dose really low values per day or increase to a lot more doses than 24 per day. Anyone interested in that kind of accuracy?

I really need to objectify this code so it can be added to the libraries. Maybe thats when I'll add the millisecond accuracy.

All this came from a thread on MR about how inaccurate the bubble magus dosing pumps can be. :)

Re: Lee's Feature Complete PDE

Posted: Fri Mar 28, 2014 6:07 am
by lnevo
So just to see just how accurate my dosing has been...my alk has dropped a bit. For the past month it was hovering around 120ppm. Wanted to bring it closer to ~135. My dosers were currently set to 40ml/day. So I raised it to 45ml to see how it would change. 5 days later, tested my alk and it was at 130. Just to check on how my tank was responding, I verified on the reef chemistry calculator at http://reef.diesyst.com/chemcalc/chemcalc.html and put in my starting alk (121ppm) and my current alk (130) and it came back with 25.2ml. That matches perfectly with how many ml I am adding (5 days x 5ml).

It is very cool to see how accurately the dosers are dialed in. The next day I reset the dosing back to 40ml and I'll continue my normal monitoring.

I will definitely be turning the dosing routines I've created into a self contained class soon. Time is so tight, i don't really get time to sit down and code, but I'd really like to treat the AWC, RO Solenoid and automated salt mixing that I'll be doing as "Dosing Pumps". This way i can track exactly how much water I'm changing, how much water I'm evaporating, etc. there is something wrong with the current logging of my water change volume, but not sure what yet it is...so really the only solution for me is to improve the code :)

Re: Lee's Feature Complete PDE

Posted: Sun Mar 30, 2014 7:09 pm
by lnevo
The result of well calibrated dosing pumps :)

Image

Re: Lee's Feature Complete PDE

Posted: Wed Jun 25, 2014 7:59 pm
by james211
So I'm very interested in the calibration aspect of this code specifically. I'm trying to install Inevo's code but I keep getting a boat load of errors on verification. Most of them are some where along this line but there is a lot of them. I'm probably doing something really stupid. Any help would be great.

Code: Select all

InitialInternalMemory:197: error: 'SunLocation' does not name a type
Thank you!

I also need some help setting up the ethernet shield, any suggestions?

Re: Lee's Feature Complete PDE

Posted: Thu Jun 26, 2014 4:52 am
by lnevo
Cant help you with the ethernet shield but there are packages you need to install that are posted on the second post of this thread if your trying to use my code as-is.

Re: Lee's Feature Complete PDE

Posted: Sat Jul 05, 2014 3:12 pm
by 89delta
Great read Lee. Am very interested in using your dosing calibration code. Could you explain more about it and how we'd be able to use it as I dont see a menu item for calibrating them.