How do I code delayed start from feed mode, i.e. for skimmer

Do you have a question on how to do something.
Ask in here.
Post Reply
alexwbush
Posts: 327
Joined: Tue Mar 22, 2011 12:45 am
Location: San Diego, CA

How do I code delayed start from feed mode, i.e. for skimmer

Post by alexwbush »

I know I brought this up before, but I figured I could collaborate here.

I'm not sure if many of you are in this same situation, but when I go into feeding mode or water change mode, my pumps turn off and my skimmer turns off. At that point the water rises in my sump (and in my skimmer area). If everything turned back on at the same time when the mode is complete, I'd have quite a bit of wet skimming going on and my collection cup would fill up pretty quickly. I'd like to code in a 15 min delay. If this could also be added in with the RAGen, I'm sure some folks would find it helpful (here's looking at you Curt ;) ).
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: How do I code delayed start from feed mode, i.e. for ski

Post by rimai »

Use a timer to trigger the relay you use for the skimmer.
Attached code is an example.
This example code actually implements the delay on startup and on feeding mode.
Attachments
SkimmerDelay.pde
(1.2 KiB) Downloaded 506 times
Roberto.
alexwbush
Posts: 327
Joined: Tue Mar 22, 2011 12:45 am
Location: San Diego, CA

Re: How do I code delayed start from feed mode, i.e. for ski

Post by alexwbush »

I understand the code you posted Roberto, but I don't understand how to implement it with the RA Gen code. :oops:

Code: Select all

        case MainMenu_FeedingMode:
        {
            // turn off ports
#ifdef SaveRelayState
            // TODO Test SaveRelayState
            byte CurrentRelayState = Relay.RelayData;
#endif  // SaveRelayState
            Relay.RelayMaskOff = ~FeedingModePorts;
#ifdef RelayExp
			byte i;
			for ( i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
			{
				Relay.RelayMaskOffE[i] = ~FeedingModePortsE[i];
			}
#endif  // RelayExp
            Relay.Write();
            // run feeding mode
            FeedingMode();
            // turn on ports
            Relay.RelayMaskOff = B11111111;
#ifdef RelayExp
			for ( i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
			{
				Relay.RelayMaskOffE[i] = B11111111;
			}
#endif  // RelayExp
            // restore ports
#ifdef SaveRelayState
            Relay.RelayData = CurrentRelayState;
#endif  // SaveRelayState
            Relay.Write();
            break;
        }
or if you dare, here's the whole first 1/2 of the code (stopped at the setup menu screens function):

Code: Select all

/*
 * Copyright 2010 Reef Angel / Roberto Imai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


#include <ReefAngel_Globals.h>
#include <Wire.h>
#include <DS1307RTC.h>
#include "ReefAngel.h"
#include <ReefAngel_Wifi.h>

byte ButtonPress = 0;

SIGNAL(PCINT0_vect) {
	if (millis()-ButtonDebounce>600)
	{
		ButtonDebounce=millis();
		ButtonPress++;
	}

}

// NOTE for nested menus
// NOTE Menu labels can be a max of 20 characters
// Associate a menu name to a numeric value
// the total number must match the max number of menus
enum Menus {
    MainMenu,
    SetupMenu,
#ifndef RemoveAllLights
    LightsMenu,
#endif  // RemoveAllLights
    TempsMenu,
#if defined SetupExtras || defined ATOSetup
    TimeoutsMenu
#endif  // if defined SetupExtras || defined ATOSetup
};

// Main Menu
prog_char mainmenu_0_label[] PROGMEM = "Feeding";
prog_char mainmenu_1_label[] PROGMEM = "Water Change";
#ifndef RemoveAllLights
prog_char mainmenu_2_label[] PROGMEM = "Lights ->";
#endif  // RemoveAllLights
prog_char mainmenu_3_label[] PROGMEM = "Temps ->";
#if defined SetupExtras || defined ATOSetup
prog_char mainmenu_4_label[] PROGMEM = "Timeouts ->";
#endif  // if defined SetupExtras || defined ATOSetup
prog_char mainmenu_5_label[] PROGMEM = "Setup ->";
#ifdef VersionMenu
prog_char mainmenu_6_label[] PROGMEM = "Version";
#endif  // VersionMenu
PROGMEM const char *mainmenu_items[] = {
                    mainmenu_0_label,
                    mainmenu_1_label,
#ifndef RemoveAllLights
                    mainmenu_2_label,
#endif  // RemoveAllLights
                    mainmenu_3_label,
#if defined SetupExtras || defined ATOSetup
                    mainmenu_4_label,
#endif  // if defined SetupExtras || defined ATOSetup
                    mainmenu_5_label,
#ifdef VersionMenu
                    mainmenu_6_label
#endif  // VersionMenu
                    };
enum MainMenuItem {
    MainMenu_FeedingMode,
    MainMenu_WaterChangeMode,
#ifndef RemoveAllLights
    MainMenu_Lights,
#endif  // RemoveAllLights
    MainMenu_Temps,
#if defined SetupExtras || defined ATOSetup
    MainMenu_Timeouts,
#endif  // if defined SetupExtras || defined ATOSetup
    MainMenu_Setup,
#ifdef VersionMenu
    MainMenu_Version
#endif  // VersionMenu
};


// Setup MenuWavemakerSetup
#ifdef WavemakerSetup
prog_char setupmenu_0_label[] PROGMEM = "Wavemaker";
#endif  // WavemakerSetup
#ifdef DosingPumpSetup
prog_char setupmenu_1_label[] PROGMEM = "Single Dose";
#endif  // DosingPumpSetup
#ifdef DosingPumpIntervalSetup
prog_char setupmenu_2_label[] PROGMEM = "Multi Dose";
#endif  // DosingPumpIntervalSetup
prog_char setupmenu_3_label[] PROGMEM = "Calibrate pH";
#ifdef DateTimeSetup
prog_char setupmenu_4_label[] PROGMEM = "Date / Time";
#endif  // DateTimeSetup
PROGMEM const char *setupmenu_items[] = {
#ifdef WavemakerSetup
                    setupmenu_0_label,
#endif  // WavemakerSetup
#ifdef DosingPumpSetup
                    setupmenu_1_label,
#endif  // DosingPumpSetup
#ifdef DosingPumpIntervalSetup
					setupmenu_2_label,
#endif  // DosingPumpIntervalSetup
                    setupmenu_3_label,
#ifdef DateTimeSetup
                    setupmenu_4_label
#endif  // DateTimeSetup
                    };
enum SetupMenuItem {
#ifdef WavemakerSetup
    SetupMenu_Wavemaker,
#endif  // WavemakerSetup
#ifdef DosingPumpSetup
    SetupMenu_DosingPump,
#endif  // DosingPumpSetup
#ifdef DosingPumpIntervalSetup
	SetupMenu_DosingPumpInterval,
#endif  // DosingPumpIntervalSetup
    SetupMenu_CalibratePH,
#ifdef DateTimeSetup
    SetupMenu_DateTime
#endif  // DateTimeSetup
};


#ifndef RemoveAllLights
// Lights Menu
prog_char lightsmenu_0_label[] PROGMEM = "Lights On";
prog_char lightsmenu_1_label[] PROGMEM = "Lights Off";
#ifdef MetalHalideSetup
prog_char lightsmenu_2_label[] PROGMEM = "Metal Halides";
prog_char lightsmenu_3_label[] PROGMEM = "MH On Delay";
#endif  // MetalHalideSetup
#ifdef StandardLightSetup
prog_char lightsmenu_4_label[] PROGMEM = "Standard Lights";
#endif  // StandardLightSetup
#ifdef DisplayLEDPWM
prog_char lightsmenu_5_label[] PROGMEM = "LED PWM";
#endif  // DisplayLEDPWM
PROGMEM const char *lightsmenu_items[] = {
                            lightsmenu_0_label, lightsmenu_1_label,
#ifdef MetalHalideSetup
                            lightsmenu_2_label,
                            lightsmenu_3_label,
#endif  // MetalHalideSetup
#ifdef StandardLightSetup
                            lightsmenu_4_label,
#endif  // StandardLightSetup
#ifdef DisplayLEDPWM
                            lightsmenu_5_label
#endif  // DisplayLEDPWM
                            };
enum LightsMenuItem {
    LightsMenu_On,
    LightsMenu_Off,
#ifdef MetalHalideSetup
    LightsMenu_MetalHalides,
    LightsMenu_MetalHalideDelay,
#endif  // MetalHalideSetup
#ifdef StandardLightSetup
    LightsMenu_StandardLights,
#endif  // StandardLightSetup
#ifdef DisplayLEDPWM
    LightsMenu_LEDPWM
#endif  // DisplayLEDPWM
};
#endif  // RemoveAllLights

// Temps Menu
prog_char tempsmenu_0_label[] PROGMEM = "Heater";
prog_char tempsmenu_1_label[] PROGMEM = "Chiller";
#ifdef OverheatSetup
prog_char tempsmenu_2_label[] PROGMEM = "Overheat Set";
#endif  // OverheatSetup
prog_char tempsmenu_3_label[] PROGMEM = "Overheat Clear";
PROGMEM const char *tempsmenu_items[] = {
                        tempsmenu_0_label,
                        tempsmenu_1_label,
#ifdef OverheatSetup
                        tempsmenu_2_label,
#endif  // OverheatSetup
                        tempsmenu_3_label};
enum TempsMenuItem {
    TempsMenu_Heater,
    TempsMenu_Chiller,
#ifdef OverheatSetup
    TempsMenu_Overheat,
#endif  // OverheatSetup
    TempsMenu_OverheatClr
};

// Timeouts Menu
#if defined SetupExtras || defined ATOSetup
#ifdef ATOSetup
prog_char timeoutsmenu_0_label[] PROGMEM = "ATO Set";
#ifdef SingleATOSetup
prog_char timeoutsmenu_1_label[] PROGMEM = "ATO Interval";
#endif  // SingleATOSetup
prog_char timeoutsmenu_2_label[] PROGMEM = "ATO Clear";
#endif  // ATOSetup
#ifdef SetupExtras
prog_char timeoutsmenu_3_label[] PROGMEM = "Feeding";
prog_char timeoutsmenu_4_label[] PROGMEM = "LCD";
#endif  // SetupExtras
PROGMEM const char *timeoutsmenu_items[] = {
#ifdef ATOSetup
                        timeoutsmenu_0_label,
#ifdef SingleATOSetup
                        timeoutsmenu_1_label,
#endif  // SingleATOSetup
                        timeoutsmenu_2_label,
#endif  // ATOSetup
#ifdef SetupExtras
                        timeoutsmenu_3_label,
                        timeoutsmenu_4_label
#endif  // SetupExtras
                        };
enum TimeoutsMenuItem {
#ifdef ATOSetup
    TimeoutsMenu_ATOSet,
#ifdef SingleATOSetup
    TimeoutsMenu_ATOHrInterval,
#endif  // SingleATOSetup
    TimeoutsMenu_ATOClear,
#endif  // ATOSetup
#ifdef SetupExtras
    TimeoutsMenu_Feeding,
    TimeoutsMenu_LCD
#endif  // SetupExtras
};
#endif // if defined SetupExtras || defined ATOSetup


ReefAngelClass::ReefAngelClass()
{
#if defined(__AVR_ATmega2560__)
	PCMSK0 |= 128;
#else  // __AVR_ATmega2560__
	PCMSK0 |= 32;
#endif  // __AVR_ATmega2560__
	PCICR |= 1;
}

void ReefAngelClass::Init()
{
	Wire.begin();
	Serial.begin(57600);
	pinMode(Piezo, OUTPUT);
	digitalWrite(lowATOPin,HIGH); //pull up resistor on lowATOPin
	digitalWrite(highATOPin,HIGH); //pull up resistor on highATOPin
	LCD.Init();
	Joystick.Init();
	TempSensor.Init();
	setSyncProvider(RTC.get);   // the function to get the time from the RTC
	setSyncInterval(SECS_PER_HOUR);  // Changed to sync every hour.
	RAStart=now();
	LCD.BacklightOn();
	Relay.AllOff();
	/*
	TODO Check this code, is it needed?
    PHMin=EEPROM.read(PH_Min)*256 + EEPROM.read(PH_Min+1);
	PHMax=EEPROM.read(PH_Max)*256 + EEPROM.read(PH_Max+1);
	*/
    PHMin = InternalMemory.PHMin_read();
    PHMax = InternalMemory.PHMax_read();
	taddr = InternalMemory.T1Pointer_read();

	if ((taddr>120) || (taddr<0))
	{
		InternalMemory.T1Pointer_write(0);
		taddr = 0;
	}

#ifdef SetupExtras
	Timer[0].SetInterval(InternalMemory.FeedingTimer_read());  // Default Feeding timer
	Timer[3].SetInterval(InternalMemory.LCDTimer_read());  // LCD Sleep Mode timer
#else
	Timer[0].SetInterval(900);  // Default Feeding timer
	Timer[3].SetInterval(600);  // LCD Sleep Mode timer
#endif  // SetupExtras
	Timer[3].Start();  // start timer
	Timer[5].SetInterval(720);  // Store Params
	Timer[5].ForceTrigger();


#ifdef DisplayLEDPWM
    // Restore PWM values
    PWM.SetActinic(InternalMemory.LEDPWMActinic_read());
    PWM.SetDaylight(InternalMemory.LEDPWMDaylight_read());
#endif  // DisplayLEDPWM

    // Set the default ports to be turned on & off during the 2 modes
    // To enable a port to be toggled, place a 1 in the appropriate position
    // Default to have ports 4, 5, & 8 toggled
    // Override in Setup function of PDE
    //           Port   87654321
    FeedingModePorts = B10011000;
    WaterChangePorts = B10011000;

    // Set the ports that get shutoff when the overheat value is reached
    // Default to have port 3 shutoff
    //                 Port 87654321
    OverheatShutoffPorts = B00000100;

#ifdef RelayExp
	// Expansion Module ports to toggle, defaults to not toggle any ports
	for ( byte i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
	{
		FeedingModePortsE[i] = 0;
		WaterChangePortsE[i] = 0;
		OverheatShutoffPortsE[i] = 0;
#ifndef RemoveAllLights
		LightsOnPortsE[i] = 0;
#endif  // RemoveAllLights
	}
#endif  // RelayExp

#ifndef RemoveAllLights
    // Set the ports that get turned on when you select the Lights On
    // Default to have ports 2 & 3 turn on
    //          Port 87654321
    LightsOnPorts = B00000110;
#endif  // RemoveAllLights

    // Initialize the Nested Menus
    InitMenus();
}

void ReefAngelClass::Refresh()
{
    pingSerial();
	if (ds.read_bit()==0) return;  // ds for OneWire TempSensor
	now();
#ifdef DirectTempSensor
	LCD.Clear(DefaultBGColor,0,0,1,1);
	Params.Temp1=TempSensor.ReadTemperature(TempSensor.addrT1);
	LCD.Clear(DefaultBGColor,0,0,1,1);
	Params.Temp2=TempSensor.ReadTemperature(TempSensor.addrT2);
	LCD.Clear(DefaultBGColor,0,0,1,1);
	Params.Temp3=TempSensor.ReadTemperature(TempSensor.addrT3);
	LCD.Clear(DefaultBGColor,0,0,1,1);
	Params.PH=analogRead(PHPin);
	Params.PH=map(Params.PH, PHMin, PHMax, 700, 1000); // apply the calibration to the sensor reading
	LCD.Clear(DefaultBGColor,0,0,1,1);
	TempSensor.RequestConvertion();
	LCD.Clear(DefaultBGColor,0,0,1,1);
#else  // DirectTempSensor
    int x = TempSensor.ReadTemperature(TempSensor.addrT1);
    LCD.Clear(DefaultBGColor,0,0,1,1);
    int y;
    y = x - Params.Temp1;
    // check to make sure the temp readings aren't beyond max allowed
#ifdef DEV_MODE
    Serial.print("T1: ");
    Serial.print(x);
    Serial.print("(");
    Serial.print(y);
    Serial.print(")");
#endif  // DEV_MODE
    if ( abs(y) < MAX_TEMP_SWING || Params.Temp1 == 0) Params.Temp1 = x;
    x = TempSensor.ReadTemperature(TempSensor.addrT2);
    LCD.Clear(DefaultBGColor,0,0,1,1);
    y = x - Params.Temp2;
#ifdef DEV_MODE
    Serial.print(", T2: ");
    Serial.print(x);
    Serial.print("(");
    Serial.print(y);
    Serial.print(")");
#endif  // DEV_MODE
    if ( abs(y) < MAX_TEMP_SWING || Params.Temp2 == 0) Params.Temp2 = x;
    x = TempSensor.ReadTemperature(TempSensor.addrT3);
    LCD.Clear(DefaultBGColor,0,0,1,1);
    y = x - Params.Temp3;
#ifdef DEV_MODE
    Serial.print(", T3: ");
    Serial.print(x);
    Serial.print("(");
    Serial.print(y);
    Serial.println(")");
#endif  // DEV_MODE
    if ( abs(y) < MAX_TEMP_SWING || Params.Temp3 == 0) Params.Temp3 = x;
	Params.PH=analogRead(PHPin);
    LCD.Clear(DefaultBGColor,0,0,1,1);
	Params.PH=map(Params.PH, PHMin, PHMax, 700, 1000); // apply the calibration to the sensor reading
	TempSensor.RequestConvertion();
#endif  // DirectTempSensor
}

void ReefAngelClass::SetTemperatureUnit(byte unit)
{
    // 0 (or DEGREE_F) for farenheit
    // 1 (or DEGREE_C) for celcius
    TempSensor.unit = unit;
}

void ReefAngelClass::StandardLights(byte LightsRelay, byte OnHour, byte OnMinute, byte OffHour, byte OffMinute)
{
	if (NumMins(OffHour,OffMinute) > NumMins(OnHour,OnMinute))
	{
		if (NumMins(hour(),minute()) >= NumMins(OnHour,OnMinute)) Relay.On(LightsRelay); else Relay.Off(LightsRelay);
		if (NumMins(hour(),minute()) >= NumMins(OffHour,OffMinute)) Relay.Off(LightsRelay);
	}
	else
	{
		if (NumMins(hour(),minute()) >= NumMins(OffHour,OffMinute)) Relay.Off(LightsRelay); else Relay.On(LightsRelay);
		if (NumMins(hour(),minute()) >= NumMins(OnHour,OnMinute)) Relay.On(LightsRelay);
	}
}

void ReefAngelClass::MHLights(byte LightsRelay, byte OnHour, byte OnMinute, byte OffHour, byte OffMinute, byte MHDelay)
{
    unsigned int MHTimer = MHDelay;
    MHTimer *= SECS_PER_MIN;
    if ( now()-RAStart > MHTimer )
        StandardLights(LightsRelay, OnHour, OnMinute, OffHour, OffMinute);
}

void ReefAngelClass::StandardHeater(byte HeaterRelay, int LowTemp, int HighTemp)
{
    if (Params.Temp1 == 0) return;  // Don't turn the heater on if the temp is reading 0
    if (Params.Temp1 <= LowTemp && Params.Temp1 > 0) Relay.On(HeaterRelay);  // If sensor 1 temperature <= LowTemp - turn on heater
    if (Params.Temp1 >= HighTemp) Relay.Off(HeaterRelay);  // If sensor 1 temperature >= HighTemp - turn off heater
}

void ReefAngelClass::StandardFan(byte FanRelay, int LowTemp, int HighTemp)
{
  if (Params.Temp1 >= HighTemp) Relay.On(FanRelay);  // If sensor 1 temperature >= HighTemp - turn on fan
  if (Params.Temp1 <= LowTemp) Relay.Off(FanRelay);  // If sensor 1 temperature <= LowTemp - turn off fan
}

void ReefAngelClass::StandardATO(byte ATORelay, int ATOTimeout)
{
    // Input:  Relay port and timeout value (max number of seconds that ATO pump is allowed to run)
	unsigned long TempTimeout = ATOTimeout;
	TempTimeout *= 1000;

	/*
	Is the low switch active (meaning we need to top off) and are we not currently topping off
	Then we set the timer to be now and start the topping pump
	*/
    if ( LowATO.IsActive() && ( !LowATO.IsTopping()) )
    {
        LowATO.Timer = millis();
        LowATO.StartTopping();
        Relay.On(ATORelay);
    }

    // If the high switch is activated, this is a safeguard to prevent over running of the top off pump
    if ( HighATO.IsActive() )
    {
		LowATO.StopTopping();  // stop the low ato timer
		Relay.Off(ATORelay);
    }

    /*
    If the current time minus the start time of the ATO pump is greater than the specified timeout value
    AND the ATO pump is currently running:
    We turn on the status LED and shut off the ATO pump
    This prevents the ATO pump from contniously running.
    */
	if ( (millis()-LowATO.Timer > TempTimeout) && LowATO.IsTopping() )
	{
		LED.On();
		Relay.Off(ATORelay);
	}
}

void ReefAngelClass::SingleATO(bool bLow, byte ATORelay, byte byteTimeout, byte byteHrInterval)
{
    // if switch is active, stop the pump because the resevoir is full
    // when the switch is not active, we need to turn on the relay to fill up resevoir
    bool bCanRun = true;
    static int iLastTop = 0;
    if ( byteHrInterval )
    {
        int iSafeTop = NumMins(hour(), minute()) - iLastTop;
        if ( iSafeTop < 0 )
        {
            iSafeTop += 1440;
        }
        if ( iSafeTop < (byteHrInterval * 60) )
        {
            bCanRun = false;
        }
    }
    ReefAngel_ATOClass *ato;
    if ( bLow )
    {
        ato = &LowATO;
    }
    else
    {
        ato = &HighATO;
    }
    unsigned long t = byteTimeout;
    t *= 1000;
    if ( ato->IsActive() )
    {
        iLastTop = NumMins(hour(), minute());
        ato->StopTopping();
        Relay.Off(ATORelay);
    }
    else if ( !ato->IsTopping() )
    {
        if ( bCanRun )
        {
            ato->Timer = millis();
            ato->StartTopping();
            Relay.On(ATORelay);
        }
    }
    if ( (millis() - ato->Timer > t) && ato->IsTopping() )
    {
        LED.On();
        Relay.Off(ATORelay);
    }
}

void ReefAngelClass::DosingPump(byte DPRelay, byte DPTimer, byte OnHour, byte OnMinute, byte RunTime)
{
    /*
    This function configures and sets up the dosing pump and turns it on at the appropriate time
    Once the timer has expired for the dosing pump, it shuts it off

    DPRelay - relay dosing pump is plugged into (0-8)
    Timer - timer to control dosing pump
    OnHour & OnMinute - time to turn on the dosing pump (in 24hr based time)
    RunTime - duration to run the pump
    */

    // Let's see if it's supposed to start running the timer now
    if ( (NumMins(hour(), minute()) == NumMins(OnHour, OnMinute)) && (second() == 0) )
    {
        Relay.On(DPRelay);
        //LED.On();
        Timer[DPTimer].SetInterval(RunTime);
        Timer[DPTimer].Start();
    }

    // is the timer expired?
    if ( Timer[DPTimer].IsTriggered() )
    {
        // timer expired, so let's shut off the pump
        Relay.Off(DPRelay);
        //LED.Off();
    }
}

void ReefAngelClass::DosingPumpRepeat(byte DPRelay, byte DPTimer, int RepeatMinute, byte RunTime)
{
	/*
	This function runs the specified relay for the RunTime seconds every RepeatMinute minutes.
	So you can run the relay for 10 seconds every 60 minutes (1 hour)

	This function bases the RepeatMinute off of Midnight (00:00) of the current day.  It uses midnight to
	compute when the pump will run.

	DPRelay - Relay that contains the dosing pump
	Timer - number of the timer in the timer array to use
	RepeatMinute - number of minutes to wait before running the pump again
	RunTime - duration (in seconds) to run the pump
	*/

	// if the current minutes since midnight are divisible by the repeat interval and the current seconds
	// are zero (top of the minute), then we can run the pump
	time_t t = now();
	uint8_t h = hour(t);
	if ( (h == 0) && (minute(t) == 0) )
	{
		// if we are at midnight, change hours to 24 so we can get the correct minutes for computation
		h = 24;
	}
	int current_min = NumMins(h, minute(t));
	int r = current_min % RepeatMinute;
	if ( (r == 0) && (second(t) == 0) )
	{
		Relay.On(DPRelay);
		Timer[DPTimer].SetInterval(RunTime);
		Timer[DPTimer].Start();
	}

	// Should change the timer to be a Dosing Pump Timer
	// is the timer expired?
	if ( Timer[DPTimer].IsTriggered() )
	{
		Relay.Off(DPRelay);
	}
}

void ReefAngelClass::Wavemaker(byte WMRelay, byte WMTimer)
{
    // TODO Update Timers appropriately
    if ( Timer[WMTimer].IsTriggered() )
    {
        Timer[WMTimer].Start();
        Relay.Toggle(WMRelay);
    }
}

// Simplified for PDE file
void ReefAngelClass::StandardLights(byte Relay)
{
    StandardLights(Relay,
                   InternalMemory.StdLightsOnHour_read(),
                   InternalMemory.StdLightsOnMinute_read(),
                   InternalMemory.StdLightsOffHour_read(),
                   InternalMemory.StdLightsOffMinute_read());
}

void ReefAngelClass::MHLights(byte Relay)
{
    MHLights(Relay,
             InternalMemory.MHOnHour_read(),
             InternalMemory.MHOnMinute_read(),
             InternalMemory.MHOffHour_read(),
             InternalMemory.MHOffMinute_read(),
             InternalMemory.MHDelay_read());
}

void ReefAngelClass::StandardHeater(byte Relay)
{
    StandardHeater(Relay,
                   InternalMemory.HeaterTempOn_read(),
                   InternalMemory.HeaterTempOff_read());
}

void ReefAngelClass::StandardFan(byte Relay)
{
    StandardFan(Relay,
                InternalMemory.ChillerTempOff_read(),
                InternalMemory.ChillerTempOn_read());
}

void ReefAngelClass::StandardATO(byte Relay)
{
    StandardATO(Relay, InternalMemory.ATOTimeout_read());
}

void ReefAngelClass::SingleATOLow(byte Relay)
{
    SingleATO(true, Relay, InternalMemory.ATOTimeout_read(), InternalMemory.ATOHourInterval_read());
}

void ReefAngelClass::SingleATOHigh(byte Relay)
{
    SingleATO(false, Relay, InternalMemory.ATOHighTimeout_read(), InternalMemory.ATOHighHourInterval_read());
}

void ReefAngelClass::DosingPump1(byte Relay)
{
    // TODO Update Timers appropriately
    DosingPump(Relay, 1,
               InternalMemory.DP1OnHour_read(),
               InternalMemory.DP1OnMinute_read(),
               InternalMemory.DP1Timer_read());
}

void ReefAngelClass::DosingPump2(byte Relay)
{
    // TODO Update Timers appropriately
    DosingPump(Relay, 2,
               InternalMemory.DP2OnHour_read(),
               InternalMemory.DP2OnMinute_read(),
               InternalMemory.DP2Timer_read());
}

void ReefAngelClass::DosingPumpRepeat1(byte Relay)
{
	// TODO Update Timers appropriately
	DosingPumpRepeat(Relay, 1,
					InternalMemory.DP1RepeatInterval_read(),
					InternalMemory.DP1Timer_read());
}

void ReefAngelClass::DosingPumpRepeat2(byte Relay)
{
	// TODO Update Timers appropriately
	DosingPumpRepeat(Relay, 2,
					InternalMemory.DP2RepeatInterval_read(),
					InternalMemory.DP2Timer_read());
}

void ReefAngelClass::Wavemaker1(byte WMRelay)
{
    // TODO Update Timers appropriately
    static bool bSetup = false;
    if ( ! bSetup )
    {
        Timer[1].SetInterval(InternalMemory.WM1Timer_read());
        Timer[1].Start();
        Relay.On(WMRelay);
#ifdef WavemakerSetup
        WM1Port = WMRelay;
#endif  // WavemakerSetup
        // once setup, don't setup again
        bSetup = true;
    }

    Wavemaker(WMRelay, 1);
}

void ReefAngelClass::Wavemaker2(byte WMRelay)
{
    // TODO Update Timers appropriately
    static bool bSetup = false;
    if ( ! bSetup )
    {
        Timer[2].SetInterval(InternalMemory.WM2Timer_read());
        Timer[2].Start();
        Relay.On(WMRelay);
#ifdef WavemakerSetup
        WM2Port = WMRelay;
#endif  // Wavemakersetup
        // once setup, don't setup again
        bSetup = true;
    }

    Wavemaker(WMRelay, 2);
}

#ifdef VersionMenu
void ReefAngelClass::DisplayVersion()
{
    // Display the Software Version
    LCD.DrawText(ModeScreenColor,DefaultBGColor,10,10,"Reef Angel");
    LCD.DrawText(ModeScreenColor,DefaultBGColor,10,20,"v"ReefAngel_Version);
#ifdef DEV_MODE
    LCD.DrawText(ModeScreenColor,DefaultBGColor,10,30,"Dev Mode");
#endif  // DEV_MODE

#ifdef wifi
    // Display wifi related information
    // Place holder information currently, need wifi module
    // to be able to write functions to retrieve actual information
    LCD.DrawText(ModeScreenColor,DefaultBGColor,10,40,"Wifi Enabled");
#endif  // wifi
}
#endif  // VersionMenu

void ReefAngelClass::ClearScreen(byte Color)
{
    // clears the entire screen
    LCD.Clear(Color, 0, 0, 131, 131);
}

#ifdef DisplayLEDPWM
void ReefAngelClass::MoonlightPWM(byte RelayID, bool ShowPWM)
{
	int m,d,y;
	int yy,mm;
	long K1,K2,K3,J,V;
	byte PWMvalue;
	m = month();
	d = day();
	y = year();
	yy = y - ((12-m)/10);
	mm = m + 9;
	if (mm>=12) mm -= 12;
	K1 = 365.25 * (yy+4712);
	K2 = 30.6 * mm+.5;
	K3 = int(int((yy/100)+49)*.75)-38;
	J = K1+K2+d+59-K3;
	V = (J-2451550.1)/0.29530588853;
	V -= int(V/100)*100;
	V = abs(V-50);
	PWMvalue = 4*abs(50-V);  // 5.12=100%    4=~80%
	pinMode(lowATOPin,OUTPUT);
	if (RelayID && (bitRead(Relay.RelayData,RelayID-1)==0)) PWMvalue=0;
	analogWrite(lowATOPin,PWMvalue);
	if (ShowPWM) PWM.SetActinic((PWMvalue*100)/255);
}
#endif  // DisplayLEDPWM

void ReefAngelClass::PCLogging()
{
	// This function is used for logging the data to Dave's PC Client software
	// It prints the strings from program memory instead of RAM
	PROGMEMprint(XML_T1);
	Serial.print(Params.Temp1);
	PROGMEMprint(XML_T2);
	Serial.print(Params.Temp2);
	PROGMEMprint(XML_T3);
	Serial.print(Params.Temp3);
	PROGMEMprint(XML_PH);
	Serial.print(Params.PH);
	PROGMEMprint(XML_R);
	Serial.print(Relay.RelayData,DEC);
	PROGMEMprint(XML_RON);
	Serial.print(Relay.RelayMaskOn,DEC);
	PROGMEMprint(XML_ROFF);
	Serial.print(Relay.RelayMaskOff,DEC);
	PROGMEMprint(XML_RE_CLOSE);
	PROGMEMprint(XML_RE_OFF);
	Serial.print(">");
#ifdef RelayExp
	for ( int EID = 0; EID < MAX_RELAY_EXPANSION_MODULES; EID++ )
	{
		// relay data
		PROGMEMprint(XML_RE_OPEN);
		Serial.print(EID, DEC);
		Serial.print(">");
		Serial.print(Relay.RelayDataE[EID],DEC);
		PROGMEMprint(XML_RE_CLOSE);
		Serial.print(EID, DEC);
		Serial.print(">");
		// relay on mask
		PROGMEMprint(XML_RE_OPEN);
		PROGMEMprint(XML_RE_ON);
		Serial.print(EID, DEC);
		Serial.print(">");
		Serial.print(Relay.RelayMaskOnE[EID],DEC);
		PROGMEMprint(XML_RE_CLOSE);
		PROGMEMprint(XML_RE_ON);
		Serial.print(EID, DEC);
		Serial.print(">");
		// relay off mask
		PROGMEMprint(XML_RE_OPEN);
		PROGMEMprint(XML_RE_OFF);
		Serial.print(EID, DEC);
		Serial.print(">");
		Serial.print(Relay.RelayMaskOffE[EID],DEC);
		PROGMEMprint(XML_RE_CLOSE);
		PROGMEMprint(XML_RE_OFF);
		Serial.print(EID, DEC);
		Serial.print(">");
	}
#endif  // RelayExp
	PROGMEMprint(XML_ATOLOW);
	Serial.print(LowATO.IsActive());
	PROGMEMprint(XML_ATOHIGH);
	Serial.print(HighATO.IsActive());
	PROGMEMprint(XML_END);
}

#ifdef wifi
void ReefAngelClass::LoadWebBanner(int pointer, byte qty)
{
	webbannerpointer = pointer;
	webbannerqty = qty;
}

void ReefAngelClass::WebBanner()
{
	char buffer[22];
	int ptr = webbannerpointer;
	int tagptr = pgm_read_word(&(webbanner_tags[0]));

	PROGMEMprint(BannerGET);
	Serial.print(Params.Temp1, DEC);
	PROGMEMprint(BannerT2);
	Serial.print(Params.Temp2, DEC);
	PROGMEMprint(BannerT3);
	Serial.print(Params.Temp3, DEC);
	PROGMEMprint(BannerPH);
	Serial.print(Params.PH, DEC);
	PROGMEMprint(BannerRelayData);
	// compute the correct relay data, when we force a port on or off, it does not get reflected in the relaydata
	// so we must compute it based on the mask and send the data to the banner (compute just like when sending the
	// relay data on the wire)
    byte TempRelay = Relay.RelayData;
    TempRelay &= Relay.RelayMaskOff;
    TempRelay |= Relay.RelayMaskOn;
	Serial.print(TempRelay, DEC);

	if ( webbannerqty == WEB_BANNER_QTY )
	{
		for ( int i = 0; i < WEB_BANNER_QTY; i++ )
		{
			strcpy_P(buffer, (char *)tagptr++);
			tagptr += strlen(buffer);
			Serial.print("&");
			Serial.print(buffer);
			Serial.print("=");
			strcpy_P(buffer, (char *)ptr++);
			ptr += strlen(buffer);
			Serial.print(buffer);
		}  // for i
	}
	else
	{
		strcpy_P(buffer, (char *)tagptr++);
		tagptr += strlen(buffer);
		Serial.print("&");
		Serial.print(buffer);
		Serial.print("=");
		strcpy_P(buffer, (char *)ptr++);
		ptr += strlen(buffer);
		Serial.print(buffer);
		Serial.print("&");
		P(badmsg)=BAD;
		printP(badmsg);
	}
	Serial.println("\n\n");
}
#endif  // wifi

void ReefAngelClass::InitMenus()
{
    // loads all the menus
    menusptr[MainMenu] = pgm_read_word(&(mainmenu_items[0]));
    menuqtysptr[MainMenu] = SIZE(mainmenu_items);
    menusptr[SetupMenu] = pgm_read_word(&(setupmenu_items[0]));
    menuqtysptr[SetupMenu] = SIZE(setupmenu_items);
#ifndef RemoveAllLights
    menusptr[LightsMenu] = pgm_read_word(&(lightsmenu_items[0]));
    menuqtysptr[LightsMenu] = SIZE(lightsmenu_items);
#endif  // RemoveAllLights
    menusptr[TempsMenu] = pgm_read_word(&(tempsmenu_items[0]));
    menuqtysptr[TempsMenu] = SIZE(tempsmenu_items);
#if defined SetupExtras || defined ATOSetup
    menusptr[TimeoutsMenu] = pgm_read_word(&(timeoutsmenu_items[0]));
    menuqtysptr[TimeoutsMenu] = SIZE(timeoutsmenu_items);
#endif  // if defined SetupExtras || defined ATOSetup

    // initialize menus
    PreviousMenu = DEFAULT_MENU;
    DisplayedMenu = DEFAULT_MENU;  // default menu to display
    SelectedMenuItem = DEFAULT_MENU_ITEM;  // default item to have selected
    redrawmenu = true;
    showmenu = false;  // initially we are showing the main graphic and no menu
}

void ReefAngelClass::ShowInterface()
{
    Refresh();

    // are we displaying the menu or not??
    if ( showmenu )
    {
        DisplayMenuHeading();
        DisplayMenu();
    }
    else
    {
        // not displaying the menu, so we're gonna show the default screen
        if ( DisplayedMenu == DEFAULT_MENU )
        {
            // process screensaver timeout
            if ( Timer[3].IsTriggered() )
            {
                // Screensaver timeout expired
                LCD.BacklightOff();
            }

            if ( Joystick.IsButtonPressed() )
            {
                // turn the backlight on
                LCD.BacklightOn();

                // TODO check Timer[3] code
                if ( Timer[3].Trigger == 0 )
                {
                    Timer[3].Start();
                    return;
                }

                // Clears the screen to draw the menu
                // Displays main menu, select first item, save existing menu
                ClearScreen(DefaultBGColor);
                SelectedMenuItem = DEFAULT_MENU_ITEM;
                PreviousMenu = DEFAULT_MENU;
                DisplayedMenu = MAIN_MENU;
                showmenu = true;
                redrawmenu = true;
                menutimeout = now();
                // get out of this function and display the menu
                return;
            }

            if ( Joystick.IsUp() || Joystick.IsDown() || Joystick.IsRight() || Joystick.IsLeft() )
            {
                // Turn backlight on
                LCD.BacklightOn();
                Timer[3].Start();
            }

            pingSerial();
            // display everything on the home screen except the graph
            // the graph is drawn/updated when we exit the main menu & when the parameters are saved
            LCD.DrawDate(6, 112);
            pingSerial();
#if defined DisplayLEDPWM && ! defined RemoveAllLights
            LCD.DrawMonitor(15, 60, Params, PWM.GetDaylightValue(), PWM.GetActinicValue());
#else  // defined DisplayLEDPWM && ! defined RemoveAllLights
            LCD.DrawMonitor(15, 60, Params);
#endif  // defined DisplayLEDPWM && ! defined RemoveAllLights
            pingSerial();
            byte TempRelay = Relay.RelayData;
            TempRelay &= Relay.RelayMaskOff;
            TempRelay |= Relay.RelayMaskOn;
            LCD.DrawOutletBox(12, 93, TempRelay);
            pingSerial();

            // Process any checks/tests/events that can happen while displaying the home screen
            // This can be the timers for wavemakers or any overheat temperatures

            // process timers
            if ( Timer[5].IsTriggered() )
            {
                int CurTemp;

                // Values are stored in the I2CEEPROM1
                taddr++;
                if ( taddr >= 120 ) taddr = 0;
                Timer[5].Start();
                CurTemp = map(Params.Temp1, T1LOW, T1HIGH, 0, 50); // apply the calibration to the sensor reading
                CurTemp = constrain(CurTemp, 0, 50); // in case the sensor value is outside the range seen during calibration
                //LCD.Clear(DefaultBGColor,0,0,1,1);
                Memory.Write(taddr, CurTemp);
                pingSerial();
                LCD.Clear(DefaultBGColor,0,0,1,1);
                CurTemp = map(Params.Temp2, T2LOW, T2HIGH, 0, 50); // apply the calibration to the sensor reading
                CurTemp = constrain(CurTemp, 0, 50); // in case the sensor value is outside the range seen during calibration
                LCD.Clear(DefaultBGColor,0,0,1,1);
                Memory.Write(taddr+120, CurTemp);
                pingSerial();
                LCD.Clear(DefaultBGColor,0,0,1,1);
                CurTemp = map(Params.Temp3, T3LOW, T3HIGH, 0, 50); // apply the calibration to the sensor reading
                CurTemp = constrain(CurTemp, 0, 50); // in case the sensor value is outside the range seen during calibration
                //LCD.Clear(DefaultBGColor,0,0,1,1);
                Memory.Write(taddr+240, CurTemp);
                pingSerial();
                LCD.Clear(DefaultBGColor,0,0,1,1);
                CurTemp = map(Params.PH, PHLOW, PHHIGH, 0, 50); // apply the calibration to the sensor reading
                CurTemp = constrain(CurTemp, 0, 50); // in case the sensor value is outside the range seen during calibration
                //LCD.Clear(DefaultBGColor,0,0,1,1);
                Memory.Write(taddr+360, CurTemp);
                pingSerial();
                LCD.Clear(DefaultBGColor,0,0,1,1);
                if ((taddr%10)==0) InternalMemory.T1Pointer_write(taddr);
                LCD.DrawGraph(5, 5);
            }

            // if temp2 exceeds overheat temp
#ifdef OverheatSetup
            if ( Params.Temp2 >= InternalMemory.OverheatTemp_read() )
#else  // OverheatSetup
            if ( Params.Temp2 >= 1500 )  // 150.0 F is the default
#endif // OverheatSetup
            {
                LED.On();
                // invert the ports that are activated
                Relay.RelayMaskOff = ~OverheatShutoffPorts;
#ifdef RelayExp
				for ( byte i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
				{
					Relay.RelayMaskOffE[i] = ~OverheatShutoffPortsE[i];
				}
#endif  // RelayExp
            }
            // commit relay changes
            Relay.Write();
        }
        else
        {
            // we are viewing another screen
            if ( Joystick.IsButtonPressed() )
            {
                // button is pressed, so we gotta exit out, show the menu & redraw it too
                redrawmenu = true;
                showmenu = true;
                Timer[0].ForceTrigger();
                Timer[3].Start();
            }
        }  // if DisplayedMenu == DEFAULT_MENU
    }  // if showmenu
}

void ReefAngelClass::DisplayMenu()
{
    // redrawmenu should only get set from within this function when we move the joystick or press the button
    byte qty = menuqtysptr[DisplayedMenu];
    int ptr = menusptr[DisplayedMenu];

    if ( Joystick.IsUp() )
    {
        // process UP press
        if ( (--SelectedMenuItem) > qty )
        {
            // we're moving up and we hit the top of the list
            // gotta wrap down to the bottom of the list
            // qty - 1 gives us the last item in our list
            //SelectedMenuItem = qty - 1;
            // This allows us to add in our last item
            SelectedMenuItem = qty;
        }
        redrawmenu = true;
        menutimeout = now();
    }

    if ( Joystick.IsDown() )
    {
        // process DOWN press
        // > allows for selection of last item, >= skips it
        if ( (++SelectedMenuItem) > qty )
        {
            // we've hit the bottom of the list
            // wrap around to the top of the list
            SelectedMenuItem = DEFAULT_MENU_ITEM;
        }
        redrawmenu = true;
        menutimeout = now();
    }

    // TODO Have ability to customize menu timeout delay
    if ( (now() - menutimeout) > MENU_TIMEOUT )
    {
        // menu timeout returns to the main screen
        // skip all the other menu checks
        SelectedMenuItem = EXCEED_TIMEOUT_MENU;
        DisplayedMenu = EXCEED_TIMEOUT_MENU;
        ButtonPress++;
    }

    if ( Joystick.IsButtonPressed() )
    {
        // button gets pressed, so we need to handle the button press
        ProcessButtonPress();
        redrawmenu = true;
        // Don't finish processing the rest of the menu
        return;
    }

    // don't redraw the menu if we don't have to
    if ( ! redrawmenu )
        return;

    byte i;
    byte bcolor, fcolor;
    char buffer[22];
    for ( i = 0; i <= qty; i++ )
    {
        bcolor = DefaultBGColor;
        fcolor = DefaultFGColor;
        if ( i < qty )
        {
            strcpy_P(buffer, (char *)ptr++);
        }
        else
        {
            // the last item in the list is either Exit or Prev Menu
            if ( DisplayedMenu == MainMenu )
            {
                strcpy(buffer, "Exit");
            }
            else
            {
                strcpy(buffer, "<- Prev Menu");
            }
        }
        ptr += strlen(buffer);

        // change the background color on the selected menu entry
        if ( i == SelectedMenuItem )
        {
            bcolor = SelectionBGColor;
            fcolor = SelectionFGColor;
        }
        LCD.Clear(bcolor, MENU_START_COL-3,
                         (i*MENU_START_ROW)+MENU_HEADING_SIZE-1,
                          MENU_END_COL,
                         (i*MENU_START_ROW)+(MENU_HEADING_SIZE+MENU_ITEM_HEIGHT-1));
        LCD.DrawText(fcolor, bcolor, MENU_START_COL, (i*MENU_START_ROW)+MENU_HEADING_SIZE, buffer);
    }  // for i
    // once drawn, no need to redraw yet
    redrawmenu = false;
}

void ReefAngelClass::DisplayMenuHeading()
{
    // NOTE do we redraw the menu heading or not?  use same logic as with the menu
    if ( ! redrawmenu )
        return;

    char buffer[10];

    switch ( DisplayedMenu )
    {
        default:
//            {
//                //strcpy(buffer, "Menu:");
//                sprintf(buffer, "Menu (%d):", MenuNum);
//            }
//            break;
        case MainMenu:
            {
                strcpy(buffer, "Main:");
            }
            break;
        case SetupMenu:
            {
                strcpy(buffer, "Setup:");
            }
            break;
#ifndef RemoveAllLights
        case LightsMenu:
            {
                strcpy(buffer, "Lights:");
            }
            break;
#endif  // RemoveAllLights
        case TempsMenu:
            {
                strcpy(buffer, "Temp:");
            }
            break;
#if defined SetupExtras || defined ATOSetup
        case TimeoutsMenu:
            {
                strcpy(buffer, "Timeouts:");
            }
            break;
#endif  // if defined SetupExtras || defined ATOSetup
    }  // switch MenuNum

    // clear the line that has the menu heading on it
    LCD.Clear(DefaultBGColor, MENU_START_COL, MENU_START_ROW, MAX_X, MAX_Y);
    // Display the menu heading
    LCD.DrawText(DefaultFGColor, DefaultBGColor, MENU_START_COL, MENU_START_ROW, buffer);
}

void ReefAngelClass::DisplayMenuEntry(char *text)
{
    ClearScreen(DefaultBGColor);
    LCD.DrawText(DefaultFGColor, DefaultBGColor, MENU_START_COL, MENU_START_ROW, text);
    LCD.DrawText(DefaultFGColor, DefaultBGColor, MENU_START_COL, MENU_START_ROW*4, "Press to exit...");
}

void ReefAngelClass::FeedingMode()
{
	LCD.DrawText(ModeScreenColor, DefaultBGColor, 30, 10, "Feeding Mode");
	Timer[0].Start();  //Start Feeding Mode timer
#ifdef DisplayImages
    LCD.DrawEEPromImage(40,50, 40, 30, I2CEEPROM2, I2CEEPROM2_Feeding);
#endif  // DisplayImages

    int t;
    bool bDone = false;
    do
    {
        t = Timer[0].Trigger - now();
        if ( (t >= 0) && ! Timer[0].IsTriggered() )
        {
            LCD.Clear(DefaultBGColor,60+(intlength(t)*5),100,100,108);
            LCD.DrawText(DefaultFGColor,DefaultBGColor,60,100,t);
        }
        else
        {
            bDone = true;
        }

        if ( Joystick.IsButtonPressed() )
        {
            // joystick button pressed, so we stop the feeding mode
            bDone = true;
        }
        delay(200);
    } while ( ! bDone );

    // we're finished, so let's clear the screen and return
    ClearScreen(DefaultBGColor);
    Timer[3].Start();  // start LCD shutoff timer
}

void ReefAngelClass::WaterChangeMode()
{
	LCD.DrawText(ModeScreenColor, DefaultBGColor, 20, 10, "Water Change Mode");
#ifdef DisplayImages
	LCD.DrawEEPromImage(51,55, 40, 30, I2CEEPROM2, I2CEEPROM2_Water_Change);
#endif  // DisplayImages
	do
	{
	    // just wait for the button to be pressed
	    delay(200);
	} while ( ! Joystick.IsButtonPressed() );
	ClearScreen(DefaultBGColor);
	Timer[3].Start();  // start LCD shutoff timer
}

void ReefAngelClass::ProcessButtonPress()
{
    bool bResetMenuTimeout = true;
    switch ( DisplayedMenu )
    {
        default:  // DEFAULT_MENU == 255
//        {
//            // Default Screen
            break;
//        }
        case MainMenu:
        {
            ProcessButtonPressMain();
            break;
        }
        case SetupMenu:
        {
            ProcessButtonPressSetup();
            break;
        }
#ifndef RemoveAllLights
        case LightsMenu:
        {
            ProcessButtonPressLights();
            break;
        }
#endif  // RemoveAllLights
        case TempsMenu:
        {
            ProcessButtonPressTemps();
            break;
        }
#if defined SetupExtras || defined ATOSetup
        case TimeoutsMenu:
        {
            ProcessButtonPressTimeouts();
            break;
        }
#endif  // if defined SetupExtras || defined ATOSetup
        case EXCEED_TIMEOUT_MENU:
        {
            // we bypass all the other menus when the timeout has exceeded
            // we need to mimic the default action for the main menu in addition to
            // clearing out the stack
            SelectedMenuItem = DEFAULT_MENU_ITEM;
            DisplayedMenu = DEFAULT_MENU;
            showmenu = false;
            ClearScreen(DefaultBGColor);
            bResetMenuTimeout = false;
            // we are exiting the menu, so draw the graph
            LCD.DrawGraph(5, 5);  // Redraw graphic of params
            break;
        }
    }
    // if a button was pressed, we have to reset the timeout after processing the press
    if ( bResetMenuTimeout )
    {
        menutimeout = now();
    }
}

void ReefAngelClass::ProcessButtonPressMain()
{
    showmenu = true;
    ClearScreen(DefaultBGColor);
    switch ( SelectedMenuItem )
    {
        case MainMenu_FeedingMode:
        {
            // turn off ports
#ifdef SaveRelayState
            // TODO Test SaveRelayState
            byte CurrentRelayState = Relay.RelayData;
#endif  // SaveRelayState
            Relay.RelayMaskOff = ~FeedingModePorts;
#ifdef RelayExp
			byte i;
			for ( i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
			{
				Relay.RelayMaskOffE[i] = ~FeedingModePortsE[i];
			}
#endif  // RelayExp
            Relay.Write();
            // run feeding mode
            FeedingMode();
            // turn on ports
            Relay.RelayMaskOff = B11111111;
#ifdef RelayExp
			for ( i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
			{
				Relay.RelayMaskOffE[i] = B11111111;
			}
#endif  // RelayExp
            // restore ports
#ifdef SaveRelayState
            Relay.RelayData = CurrentRelayState;
#endif  // SaveRelayState
            Relay.Write();
            break;
        }
        case MainMenu_WaterChangeMode:
        {
            // turn off ports
#ifdef SaveRelayState
            // TODO Test SaveRelayState
            byte CurrentRelayState = Relay.RelayData;
#endif  // SaveRelayState
            Relay.RelayMaskOff = ~WaterChangePorts;
#ifdef RelayExp
			byte i;
			for ( i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
			{
				Relay.RelayMaskOffE[i] = ~WaterChangePortsE[i];
			}
#endif  // RelayExp
            Relay.Write();
            // Display the water change mode
            WaterChangeMode();
            // turn on ports
            Relay.RelayMaskOff = B11111111;
#ifdef RelayExp
			for ( i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
			{
				Relay.RelayMaskOffE[i] = B11111111;
			}
#endif  // RelayExp
#ifdef SaveRelayState
            Relay.RelayData = CurrentRelayState;
#endif  // SaveRelayState
            Relay.Write();
            break;
        }
#ifndef RemoveAllLights
        case MainMenu_Lights:
        {
            SelectedMenuItem = DEFAULT_MENU_ITEM;
            PreviousMenu = DisplayedMenu;
            DisplayedMenu = LightsMenu;
            break;
        }
#endif  // RemoveAllLights
        case MainMenu_Temps:
        {
            SelectedMenuItem = DEFAULT_MENU_ITEM;
            PreviousMenu = DisplayedMenu;
            DisplayedMenu = TempsMenu;
            break;
        }
#if defined SetupExtras || defined ATOSetup
        case MainMenu_Timeouts:
        {
            SelectedMenuItem = DEFAULT_MENU_ITEM;
            PreviousMenu = DisplayedMenu;
            DisplayedMenu = TimeoutsMenu;
            break;
        }
#endif  // if defined SetupExtras || defined ATOSetup
        case MainMenu_Setup:
        {
            SelectedMenuItem = DEFAULT_MENU_ITEM;
            PreviousMenu = DisplayedMenu;
            DisplayedMenu = SetupMenu;
            break;
        }
#ifdef VersionMenu
        case MainMenu_Version:
        {
            DisplayVersion();
            // turn off the menu so we can wait for the button press to exit
            showmenu = false;
            break;
        }
#endif  // VersionMenu
        default:
        {
            // This will be the EXIT choice
            SelectedMenuItem = DEFAULT_MENU_ITEM;
            // switch to the previous menu
            DisplayedMenu = DEFAULT_MENU;
            // disable the menu, display main screen
            showmenu = false;
            // When we exit the main menu, we will redraw the graph
            LCD.DrawGraph(5, 5);  // Redraw graphic of params
            break;
        }
    }
}

void ReefAngelClass::ProcessButtonPressSetup()
{
    showmenu = true;
    ClearScreen(DefaultBGColor);
    switch ( SelectedMenuItem )
    {
#ifdef WavemakerSetup
        case SetupMenu_Wavemaker:
        {
            int v = InternalMemory.WM1Timer_read();
            int y = InternalMemory.WM2Timer_read();
            if ( SetupOption(v, y, 0, 21600, 5, "s", "", "Setup Wavemakers", "WM1:", "WM2:") )
            {
                InternalMemory.WM1Timer_write(v);
                InternalMemory.WM2Timer_write(y);
                // after we set the values we need to update the timers
                // TODO Update Timers appropriately
                Timer[1].Trigger = 0;
                Timer[1].SetInterval(v);
                Timer[1].Start();
                Relay.On(WM1Port);
                Timer[2].Trigger = 0;
                Timer[2].SetInterval(y);
                Timer[2].Start();
                Relay.On(WM2Port);
                Relay.Write();
            }
            break;
        }
#endif  // WavemakerSetup
#ifdef DosingPumpSetup
        case SetupMenu_DosingPump:
        {
            SetupDosingPump();
            break;
        }
#endif  // DosingPumpSetup
#ifdef DosingPumpIntervalSetup
		case SetupMenu_DosingPumpInterval:
		{
            int v = InternalMemory.DP1RepeatInterval_read();
            int y = InternalMemory.DP2RepeatInterval_read();
            if ( SetupOption(v, y, 1, 1440, 4, "m", "", "Repeat Interval", "DP1:", "DP2:") )
            {
            	InternalMemory.DP1RepeatInterval_write(v);
            	InternalMemory.DP2RepeatInterval_write(y);
            }
            v = InternalMemory.DP1Timer_read();
            y = InternalMemory.DP2Timer_read();
            if ( SetupOption(v, y, 1, 255, 3, "s", "", "Run Time", "DP1:", "DP2:") )
            {
            	InternalMemory.DP1Timer_write(v);
            	InternalMemory.DP2Timer_write(y);
            }
			break;
		}
#endif  // DosingPumpIntervalSetup
        case SetupMenu_CalibratePH:
        {
            SetupCalibratePH();
            break;
        }
#ifdef DateTimeSetup
        case SetupMenu_DateTime:
        {
            SetupDateTime();
            break;
        }
#endif  // DateTimeSetup
        default:
        {
            SelectedMenuItem = DEFAULT_MENU_ITEM;
            // switch to the previous menu
            DisplayedMenu = PreviousMenu;
            break;
        }
    }
}

#ifndef RemoveAllLights
void ReefAngelClass::ProcessButtonPressLights()
{
    showmenu = true;  // set to true when displaying setup screens
    ClearScreen(DefaultBGColor);
    switch ( SelectedMenuItem )
    {
        case LightsMenu_On:
        {
            // turn on ports
            Relay.RelayMaskOn = LightsOnPorts;
#ifdef RelayExp
			for ( byte i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
			{
				Relay.RelayMaskOnE[i] = LightsOnPortsE[i];
			}
#endif  // RelayExp
#ifdef DisplayLEDPWM
            PWM.SetActinic(InternalMemory.LEDPWMActinic_read());
            PWM.SetDaylight(InternalMemory.LEDPWMDaylight_read());
#endif  // DisplayLEDPWM
            Relay.Write();
            DisplayMenuEntry("Lights On");
            showmenu = false;
            break;
        }
        case LightsMenu_Off:
        {
            // reset ports
            Relay.RelayMaskOn = B00000000;
#ifdef RelayExp
			for ( byte i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
			{
				Relay.RelayMaskOnE[i] = B00000000;
			}
#endif  // RelayExp
#ifdef DisplayLEDPWM
            // sets PWM to 0%
            PWM.SetActinic(0);
            PWM.SetDaylight(0);
#endif  // DisplayLEDPWM
            Relay.Write();
            DisplayMenuEntry("Restore Lights");
            showmenu = false;
            break;
        }
#ifdef MetalHalideSetup
        case LightsMenu_MetalHalides:
        {
            SetupLightsOptionDisplay(true);
            break;
        }
        case LightsMenu_MetalHalideDelay:
        {
            int v = InternalMemory.MHDelay_read();
            int y = -1;
            if ( SetupOption(v, y, 0, 255, 3, "m", "", "Setup MH Delay", "", "") )
            {
                InternalMemory.MHDelay_write((uint8_t)v);
            }
            break;
        }
#endif  // MetalHalideSetup
#ifdef StandardLightSetup
        case LightsMenu_StandardLights:
        {
            SetupLightsOptionDisplay(false);
            break;
        }
#endif  // StandardLightSetup
#ifdef DisplayLEDPWM
        case LightsMenu_LEDPWM:
        {
            int v = InternalMemory.LEDPWMActinic_read();
            int y = InternalMemory.LEDPWMDaylight_read();
            if ( SetupOption(v, y, 0, 100, 3, "%", "", "Setup LED", "AP:", "DP:") )
            {
                InternalMemory.LEDPWMActinic_write((uint8_t)v);
                InternalMemory.LEDPWMDaylight_write((uint8_t)y);
                // Restore PWM values
                PWM.SetActinic((uint8_t)v);
                PWM.SetDaylight((uint8_t)y);
            }
            break;
        }
#endif  // DisplayLEDPWM
        default:
        {
            SelectedMenuItem = DEFAULT_MENU_ITEM;
            // switch to the previous menu
            DisplayedMenu = PreviousMenu;
            break;
        }
    }
}
#endif  // RemoveAllLights

void ReefAngelClass::ProcessButtonPressTemps()
{
    showmenu = true;
    ClearScreen(DefaultBGColor);
    switch ( SelectedMenuItem )
    {
        case TempsMenu_Heater:
        {
            int v = InternalMemory.HeaterTempOn_read();
            int y = InternalMemory.HeaterTempOff_read();
            if ( SetupOption(v, y, 700, 900, 3, "F", ".", "Setup Heater", "On @", "Off @") )
            {
                InternalMemory.HeaterTempOn_write(v);
                InternalMemory.HeaterTempOff_write(y);
            }
            break;
        }
        case TempsMenu_Chiller:
        {
            int v = InternalMemory.ChillerTempOn_read();
            int y = InternalMemory.ChillerTempOff_read();
            if ( SetupOption(v, y, 700, 900, 3, "F", ".", "Setup Chiller", "On @", "Off @") )
            {
                InternalMemory.ChillerTempOn_write(v);
                InternalMemory.ChillerTempOff_write(y);
            }
            break;
        }
#ifdef OverheatSetup
        case TempsMenu_Overheat:
        {
            int v = InternalMemory.OverheatTemp_read();
            int y = -1;
            if ( SetupOption(v, y, 800, 2000, 4, "F", ".", "Setup Overheat", "", "") )
            {
                InternalMemory.OverheatTemp_write(v);
            }
            break;
        }
#endif  // OverheatSetup
        case TempsMenu_OverheatClr:
        {
            LED.Off();
            Relay.RelayMaskOff = B11111111;
#ifdef RelayExp
			for ( byte i = 0; i < MAX_RELAY_EXPANSION_MODULES; i++ )
			{
				Relay.RelayMaskOffE[i] = B11111111;
			}
#endif  // RelayExp
            Relay.Write();
            DisplayMenuEntry("Clear Overheat");
            showmenu = false;
            break;
        }
        default:
        {
            SelectedMenuItem = DEFAULT_MENU_ITEM;
            // switch to the previous menu
            DisplayedMenu = PreviousMenu;
            break;
        }
    }
}

#if defined SetupExtras || defined ATOSetup
void ReefAngelClass::ProcessButtonPressTimeouts()
{
    showmenu = true;
    ClearScreen(DefaultBGColor);
    switch ( SelectedMenuItem )
    {
#ifdef ATOSetup
        case TimeoutsMenu_ATOSet:
        {
#ifdef SingleATOSetup
            int v = InternalMemory.ATOTimeout_read();
            int y = InternalMemory.ATOHighTimeout_read();
            if ( SetupOption(v, y, 0, 255, 3, "s", "", "ATO Timeout", "Low:", "High:") )
            {
                InternalMemory.ATOTimeout_write((uint8_t)v);
                InternalMemory.ATOHighTimeout_write((uint8_t)y);
            }
#else  // SingleATOSetup
            int v = InternalMemory.ATOTimeout_read();
            int y = -1;
            if ( SetupOption(v, y, 0, 255, 3, "s", "", "ATO Timeout", "", "") )
            {
                InternalMemory.ATOTimeout_write((uint8_t)v);
            }
#endif  // SingleATOSetup
            break;
        }
#ifdef SingleATOSetup
        case TimeoutsMenu_ATOHrInterval:
        {
            int v = InternalMemory.ATOHourInterval_read();
            int y = InternalMemory.ATOHighHourInterval_read();
            if ( SetupOption(v, y, 0, 24, 2, "h", "", "ATO Interval", "Low:", "High:") )
            {
                InternalMemory.ATOHourInterval_write((uint8_t)v);
                InternalMemory.ATOHighHourInterval_write((uint8_t)y);
            }
            break;
        }
#endif  // SingleATOSetup
        case TimeoutsMenu_ATOClear:
        {
            // Need delay for clearing & returning screen
            LED.Off();
            LowATO.StopTopping();
            HighATO.StopTopping();
            DisplayMenuEntry("Clear ATO Timeout");
            showmenu = false;
            break;
        }
#endif  // ATOSetup
#ifdef SetupExtras
        case TimeoutsMenu_Feeding:
        {
            int v = InternalMemory.FeedingTimer_read();
            int y = -1;
            if ( SetupOption(v, y, 0, 3600, 4, "s", "", "Feeding Timer", "", "") )
            {
                InternalMemory.FeedingTimer_write(v);
                // update the feeding timer value
                Timer[0].SetInterval(v);
            }
            break;
        }
        case TimeoutsMenu_LCD:
        {
            int v = InternalMemory.LCDTimer_read();
            int y = -1;
            if ( SetupOption(v, y, 0, 3600, 4, "s", "", "Screen Timeout", "" "") )
            {
                InternalMemory.LCDTimer_write(v);
                // update the timer value
                Timer[3].SetInterval(v);
                Timer[3].Start();
            }
            break;
        }
#endif  // SetupExtras
        default:
        {
            SelectedMenuItem = DEFAULT_MENU_ITEM;
            // switch to the previous menu
            DisplayedMenu = PreviousMenu;
            break;
        }
    }
}
#endif  // if defined SetupExtras || defined ATOSetup
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: How do I code delayed start from feed mode, i.e. for ski

Post by rimai »

I was looking at the libraries core code and it's going to be tricky...
We definitely do not want to mess with the libraries code, otherwise you may end up having to edit the libraries everytime Curt has an update.
The problem I see is that there is no indication of when feeding mode has begun or ended as far as you PDE is concerned. All we have access is functions on the PDE and if we can't find out start and end time of feeding mode, it becomes hard to trigger the delay.
The FeedingMode() function is just looping inside the core code and only comes out when it's done.
Let's wait for Curt's ideas.
Roberto.
alexwbush
Posts: 327
Joined: Tue Mar 22, 2011 12:45 am
Location: San Diego, CA

Re: How do I code delayed start from feed mode, i.e. for ski

Post by alexwbush »

Ok, at least I know it is as difficult as I thought. I wasn't sure if it was just me.

I think I could code it in my RA Gen PDE, but then I don't know if the menu would be functional anymore... essentially making it so I couldn't manually feed. I think this with the automatic feeding timer would work together though if we can't figure out how to make this code better.

BTW, for anyone else who is wondering where the code I pasted from is... it's the ReefAngel.cpp file in the libraries/ReefAngel folder
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: How do I code delayed start from feed mode, i.e. for ski

Post by binder »

rimai wrote: The FeedingMode() function is just looping inside the core code and only comes out when it's done.
Let's wait for Curt's ideas.
Yeah, I left the current functionality of simply just staying inside of the FeedingMode function while in feeding mode (and same for WaterChangeMode).

Now that things are becoming more advanced and we are wanting more "granular" control over the flow of the controller, it appears that an alternative solution is going to need to be devised. I know the StandardGui (or ShowInterface) function is already designed to handle multiple states. I would think that adding in additional states for FeedingMode or WaterChangeMode wouldn't be difficult. If these states were added in, then the additional functionality one desires would be possible. HOWEVER, the main loop would still be processing and the other functions would still be working and operating just as before (at least in its current state). Additional checks for what mode we are in would be necessary for the other functions.

Without having an opportunity (yet) to fully think this through, it seems like getting the fine tuned control of delays on exiting feeding mode and slowly starting up each device is going to be complex and there's not a simple straight forward way to handle it. It will have to be hard coded manually on a per user basis unless we start getting really complex and maintaining what ports are on what length of delay and so forth. To be honest, this type of complexity is possible but not very feasible with the current limitations of the ReefAngel controller software size.

As I've been writing this, I've had a chance to think a little more and what I'm thinking is I can add a skeleton function (empty function) that gets called at the end (or beginning) of FeedingMode. Then that can override the standard functionality of exiting FeedingMode. Then users can create their own code inside the libraries inside that function. When an update occurs, they would just have to copy that function and paste it into the library again. Also, even better, would be for me to have a separate file for them to create their own code in and if they tell the libraries to use their custom function when exiting FeedingMode, then they wouldn't have to re-copy anything at all. Their function would be intact and all that would have to be done is make sure that nothing has changed with their function.

Now, this is just me rambling and talking out several ideas. It is going to require more thought, BUT I at least wanted to offer up my input and get some feedback on the various suggestions.

Curt
alexwbush
Posts: 327
Joined: Tue Mar 22, 2011 12:45 am
Location: San Diego, CA

Re: How do I code delayed start from feed mode, i.e. for ski

Post by alexwbush »

binder wrote: As I've been writing this, I've had a chance to think a little more and what I'm thinking is I can add a skeleton function (empty function) that gets called at the end (or beginning) of FeedingMode. Then that can override the standard functionality of exiting FeedingMode. Then users can create their own code inside the libraries inside that function. When an update occurs, they would just have to copy that function and paste it into the library again. Also, even better, would be for me to have a separate file for them to create their own code in and if they tell the libraries to use their custom function when exiting FeedingMode, then they wouldn't have to re-copy anything at all. Their function would be intact and all that would have to be done is make sure that nothing has changed with their function.
This sounds like a great idea. I can't imagine that I am the first one to stumble on this problem (the fact that all equipment turns back on at the same time). What happens with your skimmers when you exit feeding or water change mode?? Does your sump not rise?
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: How do I code delayed start from feed mode, i.e. for ski

Post by rimai »

Is it possible to reference a function to be called as parameter?
This way anyone could create their own fundtion on PDE and just reference it as a parameter.
Just thinking out loud.
Roberto.
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: How do I code delayed start from feed mode, i.e. for ski

Post by binder »

alexwbush wrote: This sounds like a great idea. I can't imagine that I am the first one to stumble on this problem (the fact that all equipment turns back on at the same time). What happens with your skimmers when you exit feeding or water change mode?? Does your sump not rise?
I don't ever shut my sump off when I'm in feeding mode. My sump area can hold all the additional water overflow/backflow if all pumps are off. My sump contains my protein skimmer as well. So if the sump is off, I'm still skimming but it doesn't increase any additional skimmate in my skimmer. So it doesn't affect me.
Now if I were to shut off my sump in feeding mode, I would have to stop several of my filters because they would be halfway out of the water (because my water level drops about 2 inches or so when the sump is off.
Last edited by binder on Fri Mar 25, 2011 8:13 pm, edited 1 time in total.
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: How do I code delayed start from feed mode, i.e. for ski

Post by binder »

rimai wrote:Is it possible to reference a function to be called as parameter?
This way anyone could create their own fundtion on PDE and just reference it as a parameter.
Just thinking out loud.
Yeah, that's what I was thinking too. It "may" be possible but I'd have to explore it more too. I know with standard C++ you can have functions be passed as parameters and reference them. I do something similar to this with my single ato function. This sort of stuff was what I had in mind with the separate file containing the function.

I will say that if it's a user created function in the PDE, having a parameter being passed to the Feeding Mode function would be tricky to do since the FeedingMode function is inside the libraries and there's not a direct way to call it from the PDE file. Plus, the function would have to be called the same thing inside the PDE file and the prototype for it would have to be created and stored inside either it's own class (and do a sort of Virtual Function overriding) or have it be a global function inside the Globals file (or inside my ReefAngel.h file) this way the FeedingMode function would know what to call it.
I would think the class and Virtual Function overriding might be the "best" idea, but again it would need some testing to see what way offers the most flexibility with the least amount of confusion. :)

Going along with the least amount of confusion concept, it would have to be simple and straight forward for people to code it if they desired. I doubt it would be handled from within RAGen, unless I give them a "scratch pad" area to create the function and have it inserted automatically for them (again, talking out loud).
b_s_c1
Posts: 16
Joined: Fri Mar 25, 2011 7:00 pm

Re: How do I code delayed start from feed mode, i.e. for ski

Post by b_s_c1 »

Has there been any development on this matter. I have the same problem as the original poster. For now I do not plan on using feed mode so I really just need a delay for starting up the skimmer after power is restored to the controller.
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: How do I code delayed start from feed mode, i.e. for ski

Post by binder »

b_s_c1 wrote:Has there been any development on this matter.
No development yet on my end. I've mostly just been talking things out on the possible logic for this. Investigation/testing still needs to be done and then long-term maintenance needs to be thought out a bit too. Don't want to implement something and not have it be functional or not end accomplishing what we want it to do. :)
lucaeff
Posts: 2
Joined: Tue Apr 12, 2011 8:42 am

Re: How do I code delayed start from feed mode, i.e. for ski

Post by lucaeff »

Hello everyone
I would very delay on a skimmer in the event of restart after blackout we tried with this, What do you say?

void RitardoSkimmer(byte SKPort, byte SKDelay)
{
if (now()-RAStart < SKDelay) ReefAngel.Relay.On(SKPort);
}


RitardoSkimmer(Port8,120);
alexwbush
Posts: 327
Joined: Tue Mar 22, 2011 12:45 am
Location: San Diego, CA

Re: How do I code delayed start from feed mode, i.e. for ski

Post by alexwbush »

binder wrote:
alexwbush wrote: This sounds like a great idea. I can't imagine that I am the first one to stumble on this problem (the fact that all equipment turns back on at the same time). What happens with your skimmers when you exit feeding or water change mode?? Does your sump not rise?
I don't ever shut my sump off when I'm in feeding mode. My sump area can hold all the additional water overflow/backflow if all pumps are off. My sump contains my protein skimmer as well. So if the sump is off, I'm still skimming but it doesn't increase any additional skimmate in my skimmer. So it doesn't affect me.
Now if I were to shut off my sump in feeding mode, I would have to stop several of my filters because they would be halfway out of the water (because my water level drops about 2 inches or so when the sump is off.
I'm curious to see your setup Curt. If I shut off the return pump my water level rises in my sump due to siphoning from the return. And when the water level rises the skimmer starts wet skimming or sucking the water up into the collection cup (which I now have drilled to drain to another bucket). I could drill a small hole or something, but it doesn't really bother me that much and is kind of the basis for how I know how much to drain during water changes.
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: How do I code delayed start from feed mode, i.e. for ski

Post by rimai »

Ok, after a good refreshing vacation, I think I know how to solve this problem.
Drawback, you must have at least another socket controlled by the feeding mode.
Example:
Socket 8: Return
Socket 7: Skimmer
On Ragen, you only select socket 8 as being part of feeding mode.
Then you will use a custom function called DelayStart(), which I'll create tonight.
The sole purpose of this function is going to be monitoring socket 8 and turn on socket 7 after x seconds everytime socket 8 turns on.
Does it make sense?
Roberto.
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: How do I code delayed start from feed mode, i.e. for ski

Post by rimai »

Sorry to raise expectations, but this idea also did not work on my tests.
Roberto.
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: How do I code delayed start from feed mode, i.e. for ski

Post by rimai »

This is not a real solution for the problem, but it can be used while Curt is working on a more permanent solution:

Code: Select all

void DelayStart(byte DelayedPort, int SecsDelay)
{
  static unsigned long lastCheck;
  static unsigned long lastOff;
  
  if (lastCheck!=0 && (now()-lastCheck>100)) 
  {
    lastOff=now();
    ReefAngel.Relay.Off(DelayedPort);
  }
  lastCheck=now();
  if (lastOff!=0 && now()-lastOff>SecsDelay)
  {
    ReefAngel.Relay.On(DelayedPort);
  } 
}
What this function does is actually monitor for the last time it was called. If it was last called 100 seconds ago, it probably means it was in feeding/water change mode. This triggers the delay start.
The problem you will encounter is if you go to the setup menu and spend more than 100 seconds in one of the setup screens, it will be counted towards the 100 seconds and when you exit from the setup screen, you will see the port being turned off and start the delay too.
I hope this can help some.
Attached is a code with an example on how to use it.
Attachments
DelayStart.pde
(1.07 KiB) Downloaded 462 times
Roberto.
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: How do I code delayed start from feed mode, i.e. for ski

Post by binder »

alexwbush wrote:I'm curious to see your setup Curt.
Here is an old shot of my sump / filtration area. I'll try to take a more recent photo next week to show you, but you should at least get the idea. The major difference is the divider on the far right has been removed and there is not a refugium anymore. It's just a larger reservoir area.

Image
Image

And it's not as full as it gets. This shot was taken while I was still testing out the water levels in my tank before I got it running. The water comes up just below the black rim after everything is shut off. So no worries about anything skimming too much because the water level stays below where all the skimming action takes place.

curt
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: How do I code delayed start from feed mode, i.e. for ski

Post by binder »

lucaeff wrote:Hello everyone
I would very delay on a skimmer in the event of restart after blackout we tried with this, What do you say?

void RitardoSkimmer(byte SKPort, byte SKDelay)
{
if (now()-RAStart < SKDelay) ReefAngel.Relay.On(SKPort);
}
That code would have to be modified to be Greater Than the delay then turn on the port, otherwise it would only be on while the delay is in effect. The delay would be in minutes (just like with the MHLights). If you didn't want a minute delay, then you would need to remove the timer variable and place the Delay variable back in the if statement. So it should be changed to this:

Code: Select all

void DelayedOn(byte Port, byte Delay)
{
  unsigned int timer = Delay;
  timer *= SECS_PER_MIN;
  if ( (now() - RAStart) > timer ) ReefAngel.Relay.On(Port);
  else ReefAngel.Relay.Off(Port);  // this is probably overkill but ensures it is off 
}
If you implemented this, then you would not have the ReefAngel.Relay.On line in the setup, you would have to use the DelayedOn function in the Loop code. So that would look something like this:

Code: Select all

void loop()
{
  // ... other stuff here
  DelayedOn(Port4, 2);  // 2 minute delay for starting up Port4
  // ... other stuff here
}
I have not tested this, but it should work.

This solution, however, would not work for the entering and exiting of feeding mode and water change mode. It would only work on power outage / loss. I still have to work on the feeding mode & water change mode solutions. It will require some other changes and modifications and I have some ideas in my head, so be patient please.... :geek:

curt
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: How do I code delayed start from feed mode, i.e. for ski

Post by binder »

I created a poll to see what delay interval would be most useful for people: seconds or minutes.
Your feedback will help determine how it gets coded. All feedback is welcomed.

Here's a link to the poll.

http://forum.reefangel.com/viewtopic.php?f=9&t=30
lucaeff
Posts: 2
Joined: Tue Apr 12, 2011 8:42 am

Re: How do I code delayed start from feed mode, i.e. for ski

Post by lucaeff »

thanks guys, I know you're working hard, I'll wait patiently
alexwbush
Posts: 327
Joined: Tue Mar 22, 2011 12:45 am
Location: San Diego, CA

Re: How do I code delayed start from feed mode, i.e. for ski

Post by alexwbush »

Curt, mine rises about 4-6" as well in my skimmer compartment when the return is shut off. The change in water level will cause more skimmate and eventually start wet skimming. I'm surprised you hadn't run into this issue. I actually use mine now for water changes. I'll let it wet skim out what I want to change out, then unplug my ATO and plug in a pump to pump in new saltwater. I'd like to automate the process, but it's much easier than it used to be before anyhow.
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: How do I code delayed start from feed mode, i.e. for ski

Post by binder »

I just finished coding in a DelayedOn function. :ugeek:

I've tested it and it appears to be working the way it should but will need some further testing to be certain. It currently has a minute delay and is in my latest code on my github account (https://github.com/curtbinder/ReefAngel).

I also have a PDE file created that shows how to use the function. It is attached to this post.

Here is how the function is designed to work:

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

So that means that at controller startup, any port that is specified as a DelayedOn port will delay starting up for the given minute delay. The Feeding and Water Change modes will only do the delay if the port was actually turned off during those modes.

Here is an example scenario:
If you don't turn off your skimmer during feeding mode but do during water change mode, the skimmer will only be delayed in turning on when the controller powers up and after leaving water change mode. Nothing happens during feeding mode because it was never turned off for feeding mode.

curt
Attachments
DelayedOn.pde
DelayedOn function example
(1.58 KiB) Downloaded 456 times
alexwbush
Posts: 327
Joined: Tue Mar 22, 2011 12:45 am
Location: San Diego, CA

Re: How do I code delayed start from feed mode, i.e. for ski

Post by alexwbush »

Curt, your delay on function seems to work great... however, it doesn't seem to work for power losses just as the halide does. I have a float switch on my collection bucket so that if it's full, the skimmer turns off. If the float switch is triggered, then cleared, the skimer will turn off for one minute. But, I also want it to delay start if the power goes out just as the halide function does. Any suggestions?

my PDE

Code: Select all

// Autogenerated file by RAGen (v1.0.4.92), (05/08/2011 17:03)
// RA_050811_1703.pde
//
// This version designed for v0.8.5 Beta 12 or later

/* The following features are enabled for this PDE File: 
#define DisplayImages - ok
#define WavemakerSetup - ok
#define DateTimeSetup - ok
#define ATOSetup - ok, but probably not needed since the ato timeouts are not used
#define MetalHalideSetup - ok
#define DirectTempSensor - ok
#define RelayExp - ok
#define SingleATOSetup - ok, but probably not needed since the single ato function isn't used
#define StandardLightSetup -ok
#define CUSTOM_MAIN - ok
#define COLORS_PDE - ok
*/

//Custom colors
#include <ReefAngel_Colors.h>
#include <ReefAngel_CustomColors.h>

#include <ReefAngel_Features.h>
#include <ReefAngel_Globals.h>
#include <ReefAngel_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <ReefAngel_EEPROM.h>
#include <ReefAngel_NokiaLCD.h>
#include <ReefAngel_ATO.h>
#include <ReefAngel_Joystick.h>
#include <ReefAngel_LED.h>
#include <ReefAngel_TempSensor.h>
#include <ReefAngel_Relay.h>
#include <ReefAngel_PWM.h>
#include <ReefAngel_Timer.h>
#include <ReefAngel_Memory.h>
#include <ReefAngel.h>

/*
#define Box1_Port1    11    Fuge LED
#define Box1_Port2    12    Carbon Reactor
#define Box1_Port3    13    
#define Box1_Port4    14    Vortech
#define Box1_Port5    15    WC Heater
#define Box1_Port6    16    WC Pump
#define Box1_Port7    17    Actinic LED
#define Box1_Port8    18    
*/

/*
//#include <avr/pgmspace.h>
prog_char id_label[] PROGMEM = "wolfpack1206";
prog_char probe1_label[] PROGMEM = "Water";
prog_char probe2_label[] PROGMEM = "Room";
prog_char probe3_label[] PROGMEM = "Not%20Used";
prog_char relay1_label[] PROGMEM = "ATO";
prog_char relay2_label[] PROGMEM = "Moonlight";
prog_char relay3_label[] PROGMEM = "Actinic";
prog_char relay4_label[] PROGMEM = "Halide";
prog_char relay5_label[] PROGMEM = "Sump%20Light";
prog_char relay6_label[] PROGMEM = "Powerhead";
prog_char relay7_label[] PROGMEM = "Heater";
prog_char relay8_label[] PROGMEM = "Return";
#ifdef RelayExp
prog_char relay11_label[] PROGMEM = "Fuge%20LED";
prog_char relay12_label[] PROGMEM = "Carbon%20Reactor";
prog_char relay13_label[] PROGMEM = "Not%20Used";
prog_char relay14_label[] PROGMEM = "Vortech";
prog_char relay15_label[] PROGMEM = "WC%20Heater";
prog_char relay16_label[] PROGMEM = "WC%20Pump";
prog_char relay17_label[] PROGMEM = "Actinic%20LED";
prog_char relay18_label[] PROGMEM = "Not%20Used";
#endif  // RelayExp

PROGMEM const char *webbanner_items[] = {
    id_label, probe1_label, probe2_label, probe3_label, 
    relay1_label, relay2_label, relay3_label, relay4_label, 
    relay5_label, relay6_label, relay7_label, relay8_label,
#ifdef RelayExp
    relay11_label, relay12_label, relay13_label, relay14_label, 
    relay15_label, relay16_label, relay17_label, relay18_label,
#endif  // RelayExp
};
*/

void DrawCustomMain()
{
  // Change the 30 to adjust the horizontal position of the text on the screen, max 20-21 chars
  ReefAngel.LCD.DrawText(DefaultFGColor, DefaultBGColor, 35, 2, "Alex's Cube");
  ReefAngel.LCD.DrawDate(6, 119);
  pingSerial();
  ReefAngel.LCD.DrawMonitor(15, 63, ReefAngel.Params,
                            ReefAngel.PWM.GetDaylightValue(), 
                            ReefAngel.PWM.GetActinicValue());
  pingSerial();
  // draw main relay
  byte TempRelay = ReefAngel.Relay.RelayData;
  TempRelay &= ReefAngel.Relay.RelayMaskOff;
  TempRelay |= ReefAngel.Relay.RelayMaskOn;
  ReefAngel.LCD.DrawOutletBox(12, 93, TempRelay);
#ifdef RelayExp
  // draw 1st expansion relay
  TempRelay = ReefAngel.Relay.RelayDataE[0];
  TempRelay &= ReefAngel.Relay.RelayMaskOffE[0];
  TempRelay |= ReefAngel.Relay.RelayMaskOnE[0];
  ReefAngel.LCD.DrawOutletBox(12, 105, TempRelay);
#endif  // RelayExp
}

void DrawCustomGraph()
{
  ReefAngel.LCD.DrawGraph(5, 10);
}



void setup()
{
    ReefAngel.Init();  //Initialize controller
    //ReefAngel.LoadWebBanner(pgm_read_word(&(webbanner_items[0])), SIZE(webbanner_items));

    // Initialize and start the timer
    //ReefAngel.Timer[4].SetInterval(120);  // set interval to 180 seconds
    //ReefAngel.Timer[4].Start();

    ReefAngel.FeedingModePorts = B10101000;
    //ReefAngel.WaterChangePorts = B10000010;
    ReefAngel.WaterChangePorts = B00000001;
    ReefAngel.OverheatShutoffPorts = B00000110;

    // Ports that are always on
    ReefAngel.Relay.On(Port6);
    ReefAngel.Relay.On(Port8);
    
    ReefAngel.Relay.On(Box1_Port1);
    ReefAngel.Relay.On(Box1_Port2);
    ReefAngel.Relay.On(Box1_Port4);
}

void loop()
{
    ReefAngel.ShowInterface();

    // Specific functions
    // ReefAngel.SingleATOHigh(Port1);
    if(ReefAngel.HighATO.IsActive())
    {
        ReefAngel.Relay.On(Port1);
        ReefAngel.Relay.On(Box1_Port6);
    }
    if(ReefAngel.HighATO.IsActive()==false)
    {
        ReefAngel.Relay.Off(Port1);
        ReefAngel.Relay.Off(Box1_Port6);
    }
    if(ReefAngel.LowATO.IsActive())
        ReefAngel.DelayedOn(Port6, 1);
    if(ReefAngel.LowATO.IsActive()==false)
        ReefAngel.Relay.Off(Port6);
    
    ReefAngel.StandardLights(Port2);
    ReefAngel.MHLights(Port3);
    //ReefAngel.Wavemaker1(Port4);
    ReefAngel.StandardFan(Port5);
    ReefAngel.StandardHeater(Port7);
    
    ReefAngel.StandardLights(Box1_Port7,12,15,21,45);
}
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: How do I code delayed start from feed mode, i.e. for ski

Post by binder »

alexwbush wrote:Curt, your delay on function seems to work great... however, it doesn't seem to work for power losses just as the halide does.
The code is exactly the same for both of the delays. DelayedOn uses a variable called LastStart and the Halides use a variable called RAStart. When the controller initializes, LastStart gets set with the value from RAStart.

From your code, it looks like your skimmer is on Port6. If it is, your problem is how you turn it on. If you want a DelayedOn, you cannot turn it ON in the setup. You have the ReefAngel.Relay.On(Port6) inside setup(), which turns it on every time the controller powers up.

You already have your conditional statement that checks whether or not to turn on the port. So just stop turning it on when the controller starts up and you should be fine.

curt
alexwbush
Posts: 327
Joined: Tue Mar 22, 2011 12:45 am
Location: San Diego, CA

Re: How do I code delayed start from feed mode, i.e. for ski

Post by alexwbush »

great catch! I can't believe I overlooked that, thanks!
Post Reply