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.
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.
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);
}
}