-
Posts: 5430 Joined: Fri Jul 20, 2012 9:42 am
|
 Posted: Fri Dec 28, 2012 9:56 pm
Here is the current code. I will keep this post updated with my latest version. - Code: Select all
#include <Salinity.h> #include <ReefAngel_Features.h> #include <Globals.h> #include <RA_TS.h> #include <RA_TouchLCD.h> #include <RA_TFT.h> #include <RA_TS.h> #include <Font.h> #include <RA_Wifi.h> #include <RA_Wiznet5100.h> #include <SD.h> #include <SPI.h> #include <Ethernet.h> #include <EthernetDHCP.h> #include <PubSubClient.h> #include <Wire.h> #include <OneWire.h> #include <Time.h> #include <DS1307RTC.h> #include <InternalEEPROM.h> #include <RA_ATO.h> #include <LED.h> #include <RA_TempSensor.h> #include <Relay.h> #include <RA_PWM.h> #include <Timer.h> #include <Memory.h> #include <InternalEEPROM.h> #include <RA_Colors.h> #include <RA_CustomColors.h> #include <RA_CustomLabels.h> #include <RF.h> #include <IO.h> #include <ORP.h> #include <AI.h> #include <PH.h> #include <WaterLevel.h> #include <Humidity.h> #include <PAR.h> #include <DCPump.h> #include <ReefAngel.h> #include <SoftwareSerial.h> #include <SunLocation.h> #include <Tide.h> #include <WiFiAlert.h> #include <Moon.h>
////// Place global variable code below here // Define Custom Memory Locations #define Mem_B_MoonOffset 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 // Free #define Mem_B_AlkMaxDelta 142 // Free #define Mem_B_TideMode 143 #define Mem_B_MaintGAC 144 // KRS #define Mem_B_MaintGFO 145 // Scrubber #define Mem_B_MaintCal 146 #define Mem_B_MaintAlk 147 #define Mem_B_MaintWC 148 // Reactor #define Mem_B_MaintATO 149 #define Mem_B_MaintFeeding 150 #define Mem_B_MaintSkimmer 151 #define Mem_B_MaintSocks 152 // Klir #define Mem_B_MaintWCVol 153 // Free #define Mem_I_CalDP3Vol 154 #define Mem_I_CalDP3Time 156 #define Mem_I_DP3Volume 158 #define Mem_B_LightMode 160 #define Mem_B_LightOffset 161 #define Mem_I_RiseOffset 162 #define Mem_I_SetOffset 164 #define Mem_B_AcclActinicOffset 166 #define Mem_B_AcclDaylightOffset 167 #define Mem_B_RandomMode 168 #define Mem_B_GyreOffset 169 #define Mem_B_MoonMode 170 #define Mem_B_LightsOffPerc 171 #define Mem_B_FeedingSpeed 172 #define Mem_B_WCSpeed 173 #define Mem_B_AlkTarget 174 #define Mem_B_PowerOutageSpeed 175 #define Mem_B_VinegarWeek 176 #define Mem_B_MaintVinegar 177 // Magnesium #define Mem_B_EnableStorm 178 #define Mem_B_ForceRandomTide 179 #define Mem_I_AWCTime 180 #define Mem_I_AWCFrequency 182 #define Mem_I_AWCOffset 184 #define Mem_I_MixTime 186 #define Mem_I_MixFrequency 188 #define Mem_B_FlushTime 190 #define Mem_I_DP1Log 191 #define Mem_I_DP2Log 193 #define Mem_I_DP3Log 195 #define Mem_B_AlkAdjVol 197 // Defines the total daily volume we're allowed to add #define Mem_B_AlkAdjDelta 198 // Defines the bounds of our adjustment (ie. +/- 20)
#define Mem_B_ResetMemory 199
void init_memory() { // Initialize Custom Memory Locations InternalMemory.write(Mem_B_MoonOffset,30); 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,270); 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,20); InternalMemory.write(Mem_B_PumpOffset,80); InternalMemory.write(Mem_B_FeedingRF,false); InternalMemory.write(Mem_B_NightRF,false); InternalMemory.write(Mem_B_NightSpeed,35); InternalMemory.write(Mem_B_NightDuration,16); InternalMemory.write(Mem_B_NTMSpeed,60); InternalMemory.write(Mem_B_NTMDuration,5); InternalMemory.write(Mem_B_NTMDelay,15); InternalMemory.write(Mem_B_NTMTime,150); InternalMemory.write_int(Mem_I_CalDP1Vol,10); InternalMemory.write_int(Mem_I_CalDP1Time,470); InternalMemory.write_int(Mem_I_DP1Volume,42); InternalMemory.write_int(Mem_I_CalDP2Vol,10); InternalMemory.write_int(Mem_I_CalDP2Time,490); InternalMemory.write_int(Mem_I_DP2Volume,42); InternalMemory.write_int(Mem_I_CalDP3Vol,10); InternalMemory.write_int(Mem_I_CalDP3Time,480); InternalMemory.write_int(Mem_I_DP3Volume,45); InternalMemory.write(Mem_B_AlkMaxDelta,10); InternalMemory.write(Mem_B_TideMode,0); InternalMemory.write(Mem_B_LightOffset,20); InternalMemory.write(Mem_B_LightMode,1); InternalMemory.write_int(Mem_I_RiseOffset,-1); InternalMemory.write_int(Mem_I_SetOffset,-1); InternalMemory.write(Mem_B_AcclActinicOffset,100); InternalMemory.write(Mem_B_AcclDaylightOffset,100); InternalMemory.write(Mem_B_RandomMode,false); InternalMemory.write(Mem_B_GyreOffset,10); InternalMemory.write(Mem_B_MoonMode,1); InternalMemory.write(Mem_B_LightsOffPerc,0); InternalMemory.write(Mem_B_FeedingSpeed,3); InternalMemory.write(Mem_B_WCSpeed,25); InternalMemory.write(Mem_B_AlkTarget,145); InternalMemory.write(Mem_B_PowerOutageSpeed,15); InternalMemory.write(Mem_B_EnableStorm,true); InternalMemory.write(Mem_B_ForceRandomTide,false); InternalMemory.write_int(Mem_I_AWCTime,60); InternalMemory.write_int(Mem_I_AWCFrequency,120); InternalMemory.write_int(Mem_I_AWCOffset,0); InternalMemory.write_int(Mem_I_MixTime,60); InternalMemory.write_int(Mem_I_MixFrequency,120); InternalMemory.write(Mem_B_FlushTime,30); InternalMemory.write(Mem_B_AlkAdjVol,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_MaintVinegar,0);
InternalMemory.write(Mem_B_ResetMemory,false); }
// Define Portal Variables #define Var_DPump1 0 #define Var_DPump2 1 #define Var_DPump3 2 #define Var_Baseline 3 #define Var_Tide 4 #define Var_TideMode 5 #define Var_LogATO 6 #define Var_AdjustAlk 7
// Define Relay Ports by Name #define DPump1 Box1_Port1 // Return #define Skimmer Box1_Port2 #define DPump2 Box1_Port3 // Fan #define Heater Box1_Port4 #define DPump3 Box1_Port5 // Vortech #define Unused Box1_Port6 // DPump3 #define Fan Box1_Port7 // DPump2 #define Vortechs Box1_Port8 // DPump1
#define Swabbie Box2_Port1 #define Return Box2_Port2 // Unused #define Feeder Box2_Port3 #define ATOSolenoid Box2_Port4 #define Refugium Box2_Port5 #define BlueLED Box2_Port6 #define Reactor Box2_Port7 #define WhiteLED Box2_Port8
// Box 3 and 4 are virtual ports #define RelayPS Box5_Port1 #define MixPump Box5_Port2 #define ROSolenoid Box5_Port7 #define AWCPump Box5_Port8
#define VO_RefillATO Box3_Port1 #define VO_EnableATO Box3_Port2 #define VO_StartFill Box3_Port3 #define VO_Vacation Box3_Port4 #define VO_AutoFeed Box3_Port5 #define VO_SumpLights Box3_Port6 #define VO_Calibrate Box3_Port7 #define VO_LockPorts Box3_Port8 // Box4 Defined later
// Custom classes SunLocation sun; Tide tide;
// Vortech Variables byte vtMode, vtSpeed, vtDuration;
// For Cloud and preset code int DaylightPWMValue=0; int ActinicPWMValue=0; int Daylight2PWMValue=0; int Actinic2PWMValue=0; int DaylightPWMValue0=0; // For cloud code, channel 0 int DaylightPWMValue2=0; // For cloud code, chennel 2 int ActinicPWMValue1=0; // For cloud code, channel 0 int ActinicPWMValue3=0; // For cloud code, chennel 2 int DaylightPWMValue4=0; int ActinicPWMValue5=0;
////// Place global variable code above here
void setup() { // This must be the first line ReefAngel.Init(); //Initialize controller ReefAngel.Star(); // Ports toggled in Feeding Mode ReefAngel.FeedingModePortsE[0] = Port2Bit; ReefAngel.FeedingModePortsE[1] = Port2Bit | Port5Bit | Port7Bit; // Ports toggled in Water Change Mode // ReefAngel.WaterChangePorts = Port2Bit | Port8Bit; ReefAngel.WaterChangePortsE[0] = Port2Bit; ReefAngel.WaterChangePortsE[1] = Port4Bit | Port7Bit; // Ports turned off when Overheat temperature exceeded ReefAngel.OverheatShutoffPortsE[0] = Port4Bit; ReefAngel.OverheatShutoffPortsE[0] = Port4Bit | Port6Bit | Port8Bit; // Ports toggled when Lights On / Off menu entry selected ReefAngel.LightsOnPortsE[2] = Port6Bit; // Use T1 probe as temperature and overheat functions ReefAngel.TempProbe = T1_PROBE; ReefAngel.OverheatProbe = T2_PROBE; // Set the Overheat temperature setting InternalMemory.OverheatTemp_write( 830 ); ////// Place additional initialization code below here
ReefAngel.AddPHExpansion(); ReefAngel.AddExtraTempProbes(); // Additional Temp Probes on Hub ReefAngel.AddMultiChannelWaterLevelExpansion(); // MultiChannelWaterLevelExpansion ReefAngel.AddSalinityExpansion(); // Salinity Expansion Module ReefAngel.Salinity.SetCompensation(0);
// Ports that default on ReefAngel.Relay.On(Return); ReefAngel.Relay.On(Vortechs); ReefAngel.Relay.On(VO_LockPorts); // Ports that default off ReefAngel.Relay.Off(DPump1); ReefAngel.Relay.Off(DPump2); ReefAngel.Relay.Off(DPump3); ReefAngel.Relay.Off(Heater); ReefAngel.Relay.Off(ATOSolenoid); ReefAngel.Relay.Off(Swabbie); ReefAngel.Relay.Off(Feeder); ////// 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.Relay.On(Reactor); ReefAngel.StandardHeater(Heater); ReefAngel.StandardFan(Fan); // ReefAngel.MoonLights(Refugium); ReefAngel.Relay.On(Refugium); DelayedOnModes(Skimmer); // DelayedOn after mode change only ////// Place your custom code below here // Lighting and Flow SetSun(); // Setup Sun rise/set lighting AcclimateLED(); // Apply acclimation dimming SetMoon(); // Setup Moon rise/set lighting FillInMoon(); // Fill in 5% to 0% gap in main LEDs LEDPresets(); CheckCloud(); // Check for cloud and lightning. UpdateLED(); SetTide(); // Set High/Low tide properties SetRF(); // Set Vortech modes // Automation CheckATO(); // Calculate ATO reservoir evaporation RefillATO(); // Automatic ATO reservoir refill Vacation(); // Automation while on Vacation RunFeeder(); // Automatic Fish feeder RunSwabbie(); // Skimmer neck cleaner RunDosingPumps(); // Dose by volume or time CalibrateDPumps(); // Calibrate Dosing pumps AutoWaterChange(); // Automated water change adjustAlk(); // Adjust Alkalinity to desired PPM (thanks AlanM) Reminders(); // Increment maintenance counters DailyReport(); // Needs portal to initialize first LogFeedings(); // Track a relay LogDosingPumps(); // Keep track of dosing RunMixingStation(); // Run the Mixing Station
SumpLights(); // Turn off refugium lights LockPorts(); // Clear overrides for critical ports CheckPower(); // Monitor for power outages CheckSwitches(); // Monitor for float switches ////// Place your custom code above here
ReefAngel.Network.Cloud(); // This should always be the last line ReefAngel.ShowTouchInterface(); }
void CheckPower() { static time_t powerOutage=false; static WiFiAlert powerAlert;
// Power Outage - turn off everything if (!ReefAngel.Relay.IsRelayPresent(EXP1_RELAY) && !powerOutage) // Expansion Relay NOT present { byte rfPowerOutageSpeed=InternalMemory.read(Mem_B_PowerOutageSpeed); powerOutage=now(); if (now()-powerOutage > 300) { ReefAngel.Relay.Set(Return, now()%3600<900); } ReefAngel.Relay.Off(Heater); ReefAngel.Relay.Off(Skimmer); ReefAngel.RF.SetMode(Constant,rfPowerOutageSpeed,0); if (now()-powerOutage > 10) { // Wait 10 seconds before sending to avoid false alarms powerAlert.Send("Power+outage!"); } }
// Power Restored - Turn things back on if (powerOutage && ReefAngel.Relay.IsRelayPresent(EXP1_RELAY)) { powerOutage=0; LastStart=now(); ReefAngel.RF.SetMode(vtMode,vtSpeed,vtDuration); powerAlert.Send("Power+restored.",true); } if(powerAlert.IsActive()) powerAlert.Send(); }
void CheckSwitches() { static boolean returnOverride=true; static WiFiAlert switchAlert, skimmerAlert, atoAlert; //skimmerAlert.SetDelay(SECS_PER_HOUR); skimmerAlert.SetDelay(SECS_PER_DAY); atoAlert.SetDelay(SECS_PER_HOUR); 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(); ReefAngel.ReverseATOLow(); if (!ReefAngel.HighATO.IsActive() || !ReefAngel.LowATO.IsActive()) { 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.AlarmInput.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); // 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
// Add some customizable offsets sun.Init(InternalMemory.read_int(Mem_I_Latitude), InternalMemory.read_int(Mem_I_Longitude)); int riseOffset=InternalMemory.read_int(Mem_I_RiseOffset); int setOffset=InternalMemory.read_int(Mem_I_SetOffset); sun.SetOffset(riseOffset,(acclDay*acclRiseOffset),setOffset,(-acclDay*acclSetOffset)); // Bahamas sun.CheckAndUpdate(); // Calculate today's Sunrise / Sunset
byte lightOffset=InternalMemory.read(Mem_B_LightOffset); // left right separation byte actinicOffset=InternalMemory.ActinicOffset_read(); // Make sure light resets to zero at night. for(int i=0;i<4;i++) { ReefAngel.PWM.SetChannel(i,0); } switch(InternalMemory.read(Mem_B_LightMode)) { case 0: { // Daylights ReefAngel.PWM.Channel0PWMSlope(0,lightOffset); ReefAngel.PWM.Channel2PWMSlope(lightOffset,0); // Actinics ReefAngel.PWM.Channel1PWMSlope(actinicOffset,actinicOffset+lightOffset); ReefAngel.PWM.Channel3PWMSlope(actinicOffset+lightOffset,actinicOffset); break; } case 1: { // Daylights ReefAngel.PWM.Channel0PWMParabola(0,lightOffset); ReefAngel.PWM.Channel2PWMParabola(lightOffset,0); // Actinics ReefAngel.PWM.Channel1PWMParabola(actinicOffset,actinicOffset+lightOffset); ReefAngel.PWM.Channel3PWMParabola(actinicOffset+lightOffset,actinicOffset); break; } case 2: { // Daylights ReefAngel.PWM.Channel0PWMSmoothRamp(0,lightOffset); ReefAngel.PWM.Channel2PWMSmoothRamp(lightOffset,0); // Actinics ReefAngel.PWM.Channel1PWMSmoothRamp(actinicOffset,actinicOffset+lightOffset); ReefAngel.PWM.Channel3PWMSmoothRamp(actinicOffset+lightOffset,actinicOffset); break; } case 3: { // Daylights ReefAngel.PWM.Channel0PWMSigmoid(0,lightOffset); ReefAngel.PWM.Channel2PWMSigmoid(lightOffset,0); // Actinics ReefAngel.PWM.Channel1PWMSigmoid(actinicOffset,actinicOffset+lightOffset); ReefAngel.PWM.Channel3PWMSigmoid(actinicOffset+lightOffset,actinicOffset); break; } } }
void SetMoon() { byte offset=InternalMemory.read(Mem_B_MoonOffset); byte startD=InternalMemory.read(Mem_B_PWMSlopeStart4); byte endD=InternalMemory.read(Mem_B_PWMSlopeEnd4); byte timeD=InternalMemory.read(Mem_B_PWMSlopeDuration4);
byte startA=InternalMemory.read(Mem_B_PWMSlopeStart5); byte endA=InternalMemory.read(Mem_B_PWMSlopeEnd5); byte timeA=InternalMemory.read(Mem_B_PWMSlopeDuration5);
time_t onTime=ScheduleTime(Moon.riseH, Moon.riseM,0); time_t offTime=ScheduleTime(Moon.setH, Moon.setM,0); time_t offsetOnTime=ScheduleTime(Moon.riseH, Moon.riseM,0)-(offset*60); time_t offsetOffTime=ScheduleTime(Moon.setH, Moon.setM,0)-(offset*60);
byte actRiseH=(offsetOnTime%SECS_PER_DAY)/SECS_PER_HOUR; byte actRiseM=((offsetOnTime%SECS_PER_DAY)%SECS_PER_HOUR)/60; byte actSetH=(offsetOffTime%SECS_PER_DAY)/SECS_PER_HOUR; byte actSetM=((offsetOffTime%SECS_PER_DAY)%SECS_PER_HOUR)/60; static byte mp=MoonPhase(); if (mp!=MoonPhase()) { InternalMemory.write(Mem_B_PWMSlopeEnd4,mp); InternalMemory.write(Mem_B_PWMSlopeEnd5,mp); mp=MoonPhase(); } moon_init(InternalMemory.read_int(Mem_I_Latitude), InternalMemory.read_int(Mem_I_Longitude)); // Make sure light resets to zero at night. ReefAngel.PWM.SetChannel(4,0); ReefAngel.PWM.SetChannel(5,0); switch(InternalMemory.read(Mem_B_MoonMode)) { case 0: { // Daylights ReefAngel.PWM.SetChannelRaw(4,PWMSlopeHighRes(actRiseH,actRiseM,actSetH,actSetM,startD,endD,timeD,0)); ReefAngel.PWM.SetChannelRaw(5,PWMSlopeHighRes(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM,startA,endA,timeA,0)); break; } case 1: { ReefAngel.PWM.SetChannelRaw(4,PWMParabolaHighRes(actRiseH,actRiseM,actSetH,actSetM, startD,endD,0)); ReefAngel.PWM.SetChannelRaw(5,PWMParabolaHighRes(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM, startA,endA,0)); break; } case 2: { ReefAngel.PWM.SetChannelRaw(4,PWMSmoothRampHighRes(actRiseH,actRiseM,actSetH,actSetM,startD,endD,timeD,0)); ReefAngel.PWM.SetChannelRaw(5,PWMSmoothRampHighRes(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM,startA,endA,timeA,0)); break; } case 3: { ReefAngel.PWM.SetChannelRaw(4,PWMSigmoidHighRes(actRiseH,actRiseM,actSetH,actSetM,startD,endD,0)); ReefAngel.PWM.SetChannelRaw(5,PWMSigmoidHighRes(Moon.riseH,Moon.riseM,Moon.setH,Moon.setM,startA,endA,0)); break; } } }
void FillInMoon() { // Extend the sunrise/sunset to fill in gaps when fixtures shut off. int actinicOffset=60*InternalMemory.ActinicOffset_read(); int lightOffset=60*InternalMemory.read(Mem_B_MoonOffset); // left right separation int LightsOffVal=40.95*(1+InternalMemory.read(Mem_B_LightsOffPerc)); int moonVal1, moonVal2,channelVal1, channelVal2 = 0; int offset=actinicOffset+lightOffset; time_t onTime=ScheduleTime(InternalMemory.StdLightsOnHour_read(),InternalMemory.StdLightsOnMinute_read(),0)-offset; int onTime1=NumMins(hour(onTime-lightOffset+300),minute(onTime-lightOffset+300)); int onTime2=NumMins(hour(onTime-lightOffset),minute(onTime-lightOffset)); time_t offTime=ScheduleTime(InternalMemory.StdLightsOffHour_read(),InternalMemory.StdLightsOffMinute_read(),0)+offset; int offTime1=NumMins(hour(offTime+lightOffset+300),minute(offTime+lightOffset+300)); int offTime2=NumMins(hour(offTime+lightOffset),minute(offTime+lightOffset)); if(ReefAngel.PWM.GetChannelValueRaw(1)<=LightsOffVal) { moonVal1=ReefAngel.PWM.GetChannelValueRaw(4); // Left Moon channelVal1=PWMSlopeHighRes(onTime1/60,onTime1%60,offTime1/60,offTime1%60,0,100,lightOffset/60,0); if (channelVal1>moonVal1) ReefAngel.PWM.SetChannelRaw(4,channelVal1); } if(ReefAngel.PWM.GetChannelValueRaw(3)<=LightsOffVal) { moonVal2=ReefAngel.PWM.GetChannelValueRaw(5); // Right Moon channelVal2=PWMSlopeHighRes(onTime2/60,onTime2%60,offTime2/60,offTime2%60,0,100,lightOffset/60,0); if (channelVal2>moonVal2) ReefAngel.PWM.SetChannelRaw(5,channelVal2); }
DaylightPWMValue4=ReefAngel.PWM.GetChannelValueRaw(4); ActinicPWMValue5=ReefAngel.PWM.GetChannelValueRaw(5); }
void AcclimateLED() { byte acclDay=InternalMemory.read(Mem_B_AcclDay); if (acclDay > 0) { float acclActinicOffset=acclDay*(40.95*(((float)InternalMemory.read(Mem_B_AcclActinicOffset)/100))); float acclDaylightOffset=acclDay*(40.95*((float)InternalMemory.read(Mem_B_AcclDaylightOffset)/100)); float endPerc; endPerc=40.95*InternalMemory.PWMSlopeEnd1_read(); ReefAngel.PWM.SetChannelRaw(1,map(ReefAngel.PWM.GetChannelValueRaw(1),0,endPerc,0,endPerc-acclActinicOffset)); endPerc=40.95*InternalMemory.PWMSlopeEnd3_read(); ReefAngel.PWM.SetChannelRaw(3,map(ReefAngel.PWM.GetChannelValueRaw(3),0,endPerc,0,endPerc-acclActinicOffset)); endPerc=40.95*InternalMemory.PWMSlopeEnd0_read(); ReefAngel.PWM.SetChannelRaw(0,map(ReefAngel.PWM.GetChannelValueRaw(0),0,endPerc,0,endPerc-acclDaylightOffset)); endPerc=40.95*InternalMemory.PWMSlopeEnd2_read(); ReefAngel.PWM.SetChannelRaw(2,map(ReefAngel.PWM.GetChannelValueRaw(2),0,endPerc,0,endPerc-acclDaylightOffset)); } }
#define LED_1to1 Box4_Port1 #define LED_4to1 Box4_Port2 #define LED_3to1 Box4_Port3 #define LED_2to1 Box4_Port4 #define LED_BLUE Box4_Port5 #define LED_WHITE Box4_Port6 #define LED_MOON Box4_Port7 #define LED_STORM Box4_Port8
void resetRelayBox(byte ID) { // toggle all relays except for the one selected for (int i=Box4_Port1;i<=Box4_Port4;i++) { if (i!=ID) ReefAngel.Relay.Auto(i); } }
void LEDPresets() { static byte lastPreset=0; DaylightPWMValue0=ReefAngel.PWM.GetChannelValueRaw(0); ActinicPWMValue1=ReefAngel.PWM.GetChannelValueRaw(1); DaylightPWMValue2=ReefAngel.PWM.GetChannelValueRaw(2); ActinicPWMValue3=ReefAngel.PWM.GetChannelValueRaw(3); DaylightPWMValue4=ReefAngel.PWM.GetChannelValueRaw(4); ActinicPWMValue5=ReefAngel.PWM.GetChannelValueRaw(5);
if (ReefAngel.Relay.isMaskOn(LED_1to1)) { if (lastPreset!=1) resetRelayBox(LED_1to1); DaylightPWMValue0=90*40.95; ActinicPWMValue1=10*40.95; DaylightPWMValue2=90*40.95; ActinicPWMValue3=10*40.95; lastPreset=1; } if (ReefAngel.Relay.isMaskOff(LED_1to1)) { if (lastPreset!=2) resetRelayBox(LED_1to1); DaylightPWMValue0=10*40.95; ActinicPWMValue1=90*40.95; DaylightPWMValue2=10*40.95; ActinicPWMValue3=90*40.95; lastPreset=2; } if (ReefAngel.Relay.isMaskOn(LED_2to1)) { if (lastPreset!=3) resetRelayBox(LED_2to1); DaylightPWMValue0=60*40.95; ActinicPWMValue1=40*40.95; DaylightPWMValue2=60*40.95; ActinicPWMValue3=40*40.95; lastPreset=3; }
if (ReefAngel.Relay.isMaskOff(LED_2to1)) { if (lastPreset!=4) resetRelayBox(LED_2to1); DaylightPWMValue0=40*40.95; ActinicPWMValue1=60*40.95; DaylightPWMValue2=40*40.95; ActinicPWMValue3=60*40.95; lastPreset=4; }
if (ReefAngel.Relay.isMaskOn(LED_3to1)) { if (lastPreset!=5) resetRelayBox(LED_3to1); DaylightPWMValue0=75*40.95; ActinicPWMValue1=25*40.95; DaylightPWMValue2=75*40.95; ActinicPWMValue3=25*40.95; lastPreset=5; }
if (ReefAngel.Relay.isMaskOff(LED_3to1)) { if (lastPreset!=6) resetRelayBox(LED_3to1); DaylightPWMValue0=25*40.95; ActinicPWMValue1=75*40.95; DaylightPWMValue2=25*40.95; ActinicPWMValue3=75*40.95; lastPreset=6; }
if (ReefAngel.Relay.isMaskOn(LED_4to1)) { if (lastPreset!=7) resetRelayBox(LED_4to1); DaylightPWMValue0=80*40.95; ActinicPWMValue1=20*40.95; DaylightPWMValue2=80*40.95; ActinicPWMValue3=20*40.95; lastPreset=7; }
if (ReefAngel.Relay.isMaskOff(LED_4to1)) { if (lastPreset!=8) resetRelayBox(LED_4to1); DaylightPWMValue0=20*40.95; ActinicPWMValue1=80*40.95; DaylightPWMValue2=20*40.95; ActinicPWMValue3=80*40.95; lastPreset=8; }
if (ReefAngel.Relay.isMaskOn(LED_BLUE)) { if (lastPreset!=9) resetRelayBox(LED_BLUE); DaylightPWMValue0=0; ActinicPWMValue1=80*40.95; DaylightPWMValue2=0; ActinicPWMValue3=80*40.95; lastPreset=9; }
if (ReefAngel.Relay.isMaskOff(LED_BLUE)) { if (lastPreset!=10) resetRelayBox(LED_BLUE); ActinicPWMValue1=0; ActinicPWMValue3=0; lastPreset=10; } if (ReefAngel.Relay.isMaskOn(LED_WHITE)) { if (lastPreset!=11) resetRelayBox(LED_WHITE); DaylightPWMValue0=80*40.95; ActinicPWMValue1=0; DaylightPWMValue2=80*40.95; ActinicPWMValue3=0; lastPreset=11; }
if (ReefAngel.Relay.isMaskOff(LED_WHITE)) { if (lastPreset!=12) resetRelayBox(LED_WHITE); DaylightPWMValue0=0; DaylightPWMValue2=0; lastPreset=12; } if (ReefAngel.Relay.isMaskOn(LED_MOON)) { if (lastPreset!=13) resetRelayBox(LED_MOON); DaylightPWMValue4=4095; ActinicPWMValue5=4095; lastPreset=13; }
if (ReefAngel.Relay.isMaskOff(LED_MOON)) { if (lastPreset!=14) resetRelayBox(LED_MOON); DaylightPWMValue4=0; ActinicPWMValue5=0; lastPreset=14; } }
// Write updated values to the channels void UpdateLED() { ReefAngel.PWM.SetChannelRaw(0,DaylightPWMValue0); ReefAngel.PWM.SetChannelRaw(1,ActinicPWMValue1); ReefAngel.PWM.SetChannelRaw(2,DaylightPWMValue2); ReefAngel.PWM.SetChannelRaw(3,ActinicPWMValue3); ReefAngel.PWM.SetChannelRaw(4,DaylightPWMValue4); ReefAngel.PWM.SetChannelRaw(5,ActinicPWMValue5); ReefAngel.PWM.SetActinic(ActinicPWMValue); ReefAngel.PWM.SetDaylight(DaylightPWMValue); ReefAngel.PWM.SetActinic2(Actinic2PWMValue); ReefAngel.PWM.SetDaylight2(Daylight2PWMValue); byte LightsOffPerc=40.95*InternalMemory.read(Mem_B_LightsOffPerc); (ReefAngel.PWM.GetChannelValueRaw(0)>=LightsOffPerc || ReefAngel.PWM.GetChannelValueRaw(2)>=LightsOffPerc)? ReefAngel.Relay.On(WhiteLED) : ReefAngel.Relay.Off(WhiteLED); (ReefAngel.PWM.GetChannelValueRaw(1)>=LightsOffPerc || ReefAngel.PWM.GetChannelValueRaw(3)>=LightsOffPerc)? ReefAngel.Relay.On(BlueLED) : ReefAngel.Relay.Off(BlueLED); }
void SetTide() { 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;
ReefAngel.RF.FeedingSpeed=InternalMemory.read(Mem_B_FeedingSpeed); ReefAngel.RF.WaterChangeSpeed=InternalMemory.read(Mem_B_WCSpeed); 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) { // Not needed anymore. // 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); } }
// Define new modes #define BHazard 15 #define RA_ReefCrest 16 #define RA_Lagoon 17 #define RA_TidalSwell 18 #define RA_Smart_NTM 19 #define RA_ShortPulse 20 #define RA_LongPulse 21 void RFCustom() { static boolean changeMode; byte rcSpeed, rcSpeedAS;
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;
const byte RandomModes[]={ ReefCrest, TidalSwell, Smart_NTM, Lagoon, ShortPulse, LongPulse, BHazard, Else, Sine, Constant };
// if (now()%SECS_PER_DAY!=0 && InternalMemory.read(Mem_B_RandomMode)) changeMode=true; // if (now()%SECS_PER_DAY==0 && changeMode) {
if (now()%(6*SECS_PER_HOUR)!=10 && InternalMemory.read(Mem_B_RandomMode)) changeMode=true; if (now()%(6*SECS_PER_HOUR)==10 && changeMode) { tideMode=random(100)%sizeof(RandomModes); InternalMemory.write(Mem_B_TideMode,tideMode); changeMode=false; } // Choose another random mode if triggered if (InternalMemory.read(Mem_B_ForceRandomTide)) { tideMode=random(100)%sizeof(RandomModes); InternalMemory.write(Mem_B_TideMode,tideMode); InternalMemory.write(Mem_B_ForceRandomTide,false); } ReefAngel.CustomVar[Var_TideMode]=tideMode+1;
switch (RandomModes[tideMode]) { case ReefCrest: { ReefAngel.RF.SetMode(ReefCrest,tideSpeed,vtDuration); return; break; } case Lagoon: { ReefAngel.RF.SetMode(Lagoon,tideSpeed,vtDuration); return; break; } case TidalSwell: { ReefAngel.RF.SetMode(TidalSwell,tideSpeed,vtDuration); return; break; } case Smart_NTM: { ReefAngel.RF.SetMode(Smart_NTM,tideSpeed,vtDuration); return; break; } case ShortPulse: { ReefAngel.RF.SetMode(ShortPulse,tideSpeed,vtDuration); return; break; } case LongPulse: { ReefAngel.RF.SetMode(LongPulse,tideSpeed,vtDuration); return; break; } case RA_ReefCrest: { rcSpeed=ReefCrestMode(tideSpeed,vtDuration*2,true); rcSpeedAS=ReefCrestMode(tideSpeed,vtDuration*2,false); break; } case RA_Lagoon: { rcSpeed=ReefCrestMode(tideSpeed,vtDuration,true); rcSpeedAS=ReefCrestMode(tideSpeed,vtDuration,false); break; } case RA_TidalSwell: { rcSpeed=TidalSwellMode(tideSpeed,true); rcSpeedAS=TidalSwellMode(tideSpeed,false); break; } case RA_Smart_NTM: { rcSpeed=NutrientTransportMode(0,tideSpeed,vtDuration*50,true); rcSpeedAS=NutrientTransportMode(0,tideSpeed,vtDuration*50,false); break; } case RA_ShortPulse: { rcSpeed=ShortPulseMode(0,tideSpeed,vtDuration*50,true); rcSpeedAS=ShortPulseMode(0,tideSpeed,vtDuration*50,false); break; } case RA_LongPulse: { rcSpeed=LongPulseMode(0,tideSpeed,vtDuration,true); rcSpeedAS=LongPulseMode(0,tideSpeed,vtDuration,false); break; } case Else: { rcSpeed=ElseMode(tideSpeed,vtDuration*2,true); rcSpeedAS=ElseMode(tideSpeed,vtDuration*2,false); break; } case BHazard: { rcSpeed=millis()%1200>800?tideSpeed:0; rcSpeedAS=millis()%1200<400?0:tideSpeed; 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; pumpOffset=(float) InternalMemory.read(Mem_B_GyreOffset)/100; } }
ReefAngel.RF.SetMode(Custom,rcSpeedAS*pumpOffset,tide.isOutgoing()); ReefAngel.RF.SetMode(Custom,rcSpeed,tide.isIncoming()); }
// ATO Refill mode. Top off ATO reservoir until it's at 100% void RefillATO() { static boolean refillActive=false; static WiFiAlert refillAlert; byte level=ReefAngel.WaterLevel.GetLevel(); byte lowLevel=InternalMemory.WaterLevelLow_read(); byte highLevel=InternalMemory.WaterLevelHigh_read(); static byte prevLevel;
if (ReefAngel.Relay.isMaskOff(VO_RefillATO)) return; level=ReefAngel.WaterLevel.GetLevel(); if (now()%SECS_PER_DAY-300==20*SECS_PER_HOUR && level<lowLevel) { ReefAngel.Relay.Override(VO_RefillATO,1); ReefAngel.Relay.Override(ROSolenoid,1); } if (ReefAngel.Relay.Status(VO_RefillATO)) { highLevel=InternalMemory.WaterLevelHigh_read(); if (level<=highLevel) { /* Since we've activated this mode once we've reached the lowLevel in memory, we can essentially ignore the ATO Low Level moving forward or until it times out. We only really care about the High Level at this point. It is as if we were using a SingleATO function. This should also allows us to override the lowlevel when we want to manually top off the reservoir. It also prevents a mis-read on the sensor from causing the ATO to StopTopping() and wait again till it hits the lowlevel to restart. */ ReefAngel.WaterLevelATO(ATOSolenoid,InternalMemory.ATOExtendedTimeout_read(),highLevel-1,highLevel); if (!refillActive) { prevLevel=level; refillAlert.Send("ATO+Refill+is+starting.",true); } refillActive=true; } else { ReefAngel.Relay.Auto(VO_RefillATO); ReefAngel.Relay.Off(ATOSolenoid); refillAlert.Send("ATO+Refill+is+complete.",true); } } else { if (refillActive) { if (InternalMemory.read(Mem_B_MaintATO) > 0) // Reset last ATO counter InternalMemory.write(Mem_B_MaintATO,0); ReefAngel.Relay.Off(ATOSolenoid); ReefAngel.Relay.Override(ROSolenoid,1); refillActive=false; } } if (refillAlert.IsActive()) refillAlert.Send(); // Something in our timing with the WLATO is causing a TimeOut. if ((hour()==20 && minute()<=5) && ReefAngel.Relay.Status(VO_RefillATO)) { // ReefAngel.ATOClear(); } }
// Calculate evaportation rate void CheckATO() { byte numHours, index, level, delta; float duration; const int logHours=12; static float rate, logLevel[logHours]; static boolean rateSaved, rateInit, refillActive; numHours=(now()%(SECS_PER_HOUR*logHours))/SECS_PER_HOUR; level=ReefAngel.WaterLevel.GetLevel(); // Initialize the array based on last rate // Wait a little to make sure WL reading properly. Or we're refilling. if (now()-LastStart>1 && !rateInit) { rateInit=true; index=numHours; rate=InternalMemory.read(Mem_B_LogATO); for (byte i=0;i<logHours;i++) { logLevel[index]=level+(i*(rate/24.0)); //Serial.print(i); //Serial.print(": "); //Serial.print(index); //Serial.print(": "); //Serial.println(logLevel[index]); index>0?index--:index=11; } }
if (!ReefAngel.Relay.Status(VO_RefillATO) && rateInit) { if (!refillActive) { duration=logHours-1+(float)minute()/60.0; // decimal time since oldest record index=numHours+1<logHours?numHours+1:0; logLevel[index]>level?delta=logLevel[index]-level:delta=0; rate=24*(delta/duration); } else { refillActive=false; rateInit=false; } } else { refillActive=true; } if(minute()==59 && rateSaved==false) { InternalMemory.write(Mem_B_LogATO, rate); logLevel[numHours]=level; rateSaved=true; } else { rateSaved=false; } ReefAngel.CustomVar[Var_LogATO]=rate; // if(now()%30==0) Serial.println(rate); }
void Vacation() { boolean onVacation=InternalMemory.read(Mem_B_Vacation);
ReefAngel.Relay.Set(VO_Vacation, onVacation); // Sets RelayData only if (ReefAngel.Relay.Status(VO_Vacation)!=onVacation) { // Looks at actual status onVacation=ReefAngel.Relay.Status(VO_Vacation); InternalMemory.write(Mem_B_Vacation, onVacation); ReefAngel.Relay.Auto(VO_Vacation); // Back to auto mode } if (onVacation) { if (now()%SECS_PER_DAY==20*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); }
if (InternalMemory.read(Mem_B_AutoFeed)) { ReefAngel.CustomVar[Var_AdjustAlk]=feedings; } if (now()%SECS_PER_DAY==SECS_PER_DAY-1) feedings=0; // Clear feedings at end of day }
void RunFeeder() { boolean autoFeed; static time_t t; int press, repeat, offset; autoFeed=InternalMemory.read(Mem_B_AutoFeed); press=InternalMemory.read(Mem_B_AutoFeedPress); repeat=InternalMemory.read(Mem_B_AutoFeedRepeat)*SECS_PER_HOUR; offset=InternalMemory.read(Mem_B_AutoFeedOffset)*SECS_PER_HOUR; 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.Auto(VO_AutoFeed); } if (autoFeed) { if (sun.IsDaytime() && (now()+offset)%repeat==0) { t=now(); } } if (ReefAngel.Relay.isMaskOn(Feeder)) { ReefAngel.Relay.Auto(Feeder); 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; if (!ReefAngel.Relay.Status(VO_EnableATO)) { // Manual mode if (ReefAngel.Relay.isMaskOn(Swabbie)) { ReefAngel.Relay.Auto(Swabbie); t=now(); }
if (now()-t < runtime) { ReefAngel.Relay.On(Swabbie); } else { ReefAngel.DosingPumpRepeat(Swabbie,0,repeat,runtime); } } }
void SumpLights() { int runtime=1800; static boolean trigger=false; static time_t t; // Refugium Light Turned on if (ReefAngel.Relay.isMaskOn(VO_SumpLights) && trigger==false) { DaylightPWMValue=100; ActinicPWMValue=100; Daylight2PWMValue=100; Actinic2PWMValue=100; trigger=true; t=now(); } if ((!ReefAngel.Relay.Status(VO_SumpLights) || (now()-t > runtime)) && trigger==true) { ReefAngel.Relay.Auto(VO_SumpLights); bitClear(ReefAngel.StatusFlags,LightsOnFlag); DaylightPWMValue=0; ActinicPWMValue=0; Daylight2PWMValue=0; Actinic2PWMValue=0; trigger=false; } }
// Definitions for dosing pumps const byte numDPumps=3; const byte pump[numDPumps]={ DPump1, DPump2, DPump3}; // Pump 3 is just for logging/calibration routine const byte varReport[numDPumps]={ Var_DPump1, Var_DPump2, Var_DPump3}; const int memDPTime[numDPumps]={ Mem_B_DP1Timer, Mem_B_DP2Timer, Mem_B_DP3Timer}; const int memDPVolume[numDPumps]={ Mem_I_DP1Volume, Mem_I_DP2Volume, Mem_I_DP3Volume }; const int memDPRepeat[numDPumps]={ Mem_I_DP1RepeatInterval, Mem_I_DP2RepeatInterval, Mem_I_DP3RepeatInterval }; const int memCalTime[numDPumps]={ Mem_I_CalDP1Time, Mem_I_CalDP2Time, Mem_I_CalDP3Time }; const 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;
static byte doseTime[numPumps]={ InternalMemory.read(memDPTime[0]), InternalMemory.read(memDPTime[1]), InternalMemory.read(memDPTime[2]) }; 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 (i==2) { Serial.println("Start"); Serial.println(memDPTime[2]); Serial.println(memDPVolume[2]); Serial.println(memDPRepeat[2]); Serial.println(dpTime); Serial.println(doseTime[2]); Serial.println(calcTime); Serial.println(dpRepeat); Serial.println(rate); Serial.println(totalVolume); Serial.println(memCalTime[2]); Serial.println(memCalVol[2]); Serial.println("End"); } */ 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; }
byte alkTarget, alkAdjVol, alkAdjDelta, adjTime; alkTarget=InternalMemory.read(Mem_B_AlkTarget); alkAdjVol=InternalMemory.read(Mem_B_AlkAdjVol); alkAdjDelta=InternalMemory.read(Mem_B_AlkAdjDelta); adjTime=doseTime[i]; if (ReefAngel.Params.PHExp/5.6 < alkTarget-1 && ReefAngel.CustomVar[Var_AdjustAlk]==alkTarget-1) { // Increase dosage time if we're under our threshold if (alkAdjDelta<InternalMemory.read(Mem_B_AlkMaxDelta)) { adjTime+=alkAdjVol/rate; } else { ReefAngel.CustomVar[Var_AdjustAlk]=0; } } if (ReefAngel.Params.PHExp/5.6 > alkTarget+1 && ReefAngel.CustomVar[Var_AdjustAlk]==alkTarget+1 ) { // Decrease dosage time if we're over our threshold if (alkAdjDelta>0) { adjTime<alkAdjVol/rate ? adjTime=0 : adjTime-=alkAdjVol/rate; } else { ReefAngel.CustomVar[Var_AdjustAlk]=0; } }
// If any of these are on if (ReefAngel.Relay.Status(VO_Calibrate) || !ReefAngel.Relay.Status(Return)) { // Do nothing } else { ReefAngel.DosingPumpRepeat(pump[i], i*5, dpRepeat, adjTime); } } }
void adjustAlk() { static time_t alktime; byte alkTarget=InternalMemory.read(Mem_B_AlkTarget); byte alkPump=DPump2; int alkneeded=0; float onTime; // check to see if extra alk was requested // CustomVar is the value read on the checker // Added the port lock to make sure we're doing this conciously. if (ReefAngel.CustomVar[Var_AdjustAlk] > 0 && !ReefAngel.Relay.Status(VO_LockPorts)) { alkneeded = alkTarget - ReefAngel.CustomVar[Var_AdjustAlk]; onTime = 0; if (alkneeded > 0 && alkneeded < 10) { onTime = 2.19*(float)alkneeded; // onTime is minutes to run, for 70 gallons volume, 1ppm is 2.7ml and at 1.28ml/min, so 2.19 minutes per ppm of alk. } else { ReefAngel.CustomVar[Var_AdjustAlk] = 0; // else it must be either negative or at 150, so reset and do nothing. return; } if (alktime == 0) { // it must have just gotten noticed alktime = now()+(onTime*60UL); // should get rounded to integer seconds here ReefAngel.Relay.On(alkPump); // set it to on } else if (now() > alktime) { ReefAngel.Relay.Off(alkPump); // turn it off ReefAngel.CustomVar[Var_AdjustAlk] = 0; // clear the custom variable alktime = 0; // set alktime back to 0 } else { // must be less than alktime, so keep it on ReefAngel.Relay.On(alkPump); } } else { return; } }
void LogDosingPumps() { static time_t pumpTimer[numDPumps]; static boolean pumpStatus[numDPumps], initLog; const byte memDPLogs[numDPumps]={ Mem_I_DP1Log, Mem_I_DP2Log, Mem_I_DP3Log}; float rate;
if (!initLog) { initLog=true; for (int i=0;i< numDPumps;i++) { pumpTimer[i] = InternalMemory.read_int(memDPLogs[i]); Serial.print("Init Pump "); Serial.print(i); Serial.print(":"); Serial.println(pumpTimer[i]); rate=(float)InternalMemory.read_int(memCalVol[i])/InternalMemory.read_int(memCalTime[i]); ReefAngel.CustomVar[varReport[i]]=pumpTimer[i]*rate; if (i==1) { ReefAngel.CustomVar[Var_Baseline]=rate*(InternalMemory.read(memDPTime[1])*(NumMins(hour(),minute())/InternalMemory.read(memDPRepeat[1]))); } } }
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; byte alkAdjDelta;
alkAdjDelta=InternalMemory.read(Mem_B_AlkAdjDelta); if (i==1) { // Write Baseline ReefAngel.CustomVar[Var_Baseline]=rate*(InternalMemory.read(memDPTime[1])*(NumMins(hour(),minute())/InternalMemory.read(memDPRepeat[1]))); // Reset CustomVar as dosing adjustment is now complete. Increment the delta if (ReefAngel.CustomVar[Var_AdjustAlk] == InternalMemory.read(Mem_B_AlkTarget)+1) { alkAdjDelta-2 < 0 ? alkAdjDelta=0 : alkAdjDelta-=2; InternalMemory.write(Mem_B_AlkAdjDelta, alkAdjDelta); ReefAngel.CustomVar[Var_AdjustAlk]=0; } else if (ReefAngel.CustomVar[Var_AdjustAlk] == InternalMemory.read(Mem_B_AlkTarget)-1) { InternalMemory.write(Mem_B_AlkAdjDelta, alkAdjDelta+2); ReefAngel.CustomVar[Var_AdjustAlk]=0; } else { if (alkAdjDelta > 0) InternalMemory.write(Mem_B_AlkAdjDelta,alkAdjDelta-1); } }
InternalMemory.write_int(memDPLogs[i],pumpTimer[i]); Serial.print("Writing Pump "); Serial.print(i); Serial.print(":"); Serial.println(pumpTimer[i]); } }
/* Serial.print(rate, 4); Serial.print(" * "); Serial.print(InternalMemory.read(memDPTime[1])); Serial.print(" * ("); Serial.print(NumMins(hour(now()-300),minute(now()-300))); Serial.print(" / "); Serial.println(InternalMemory.read(memDPRepeat[1])); */ // Maintenence routine if (now()%SECS_PER_DAY==SECS_PER_DAY-1) { pumpTimer[i]=0; // Clear timer at end of day ReefAngel.CustomVar[varReport[i]]=0; // Clear portal variable ReefAngel.CustomVar[Var_Baseline]=0; InternalMemory.write_int(memDPLogs[i],pumpTimer[i]); } } // See if we are dosing vinegar and adjust dosage each week. byte vinegarWeek=InternalMemory.read(Mem_B_VinegarWeek); static boolean vinegarCounter=false; if (dayOfWeek(now())!=2) vinegarCounter=true; if (dayOfWeek(now())==2 && vinegarCounter && vinegarWeek<=16) { vinegarWeek++; vinegarCounter=false; // Update Mem_I_DP3Volume and add 4ml. InternalMemory.write_int(Mem_I_DP3Volume, InternalMemory.read_int(Mem_I_DP3Volume)+4); // Update Vinegar week in memory. InternalMemory.write(Mem_B_VinegarWeek,vinegarWeek); } }
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.Auto(pump[i]); // 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.SingleATO(false,Swabbie,30,InternalMemory.ATOHourInterval_read()); // 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.Auto(VO_StartFill); // Reset Start switch if (started) wcAlert.Send("Reactor+disabled.", true); started=false; } if (wcAlert.IsActive()) wcAlert.Send(); } else { // Done with ATO ReefAngel.Relay.Off(Swabbie); // Clear stale timeout ReefAngel.ATOClear(); } } else { ReefAngel.Relay.Auto(VO_EnableATO); ReefAngel.Relay.Auto(VO_StartFill); } }
void RunMixingStation() { int mixTime=InternalMemory.read_int(Mem_I_MixTime); int mixFreq=InternalMemory.read_int(Mem_I_MixFrequency); int awcTime=InternalMemory.read_int(Mem_I_AWCTime); int awcFreq=InternalMemory.read_int(Mem_I_AWCFrequency); int awcOffset=InternalMemory.read_int(Mem_I_AWCOffset); byte flushTime=InternalMemory.read(Mem_B_FlushTime); static time_t t; ReefAngel.Relay.Set(MixPump,now()%mixFreq<mixTime); ReefAngel.Relay.Set(AWCPump,now()%awcFreq+awcOffset<awcTime); if (ReefAngel.Relay.isMaskOn(ROSolenoid)) { ReefAngel.Relay.Auto(ROSolenoid); t=now(); }
if (now()-t < flushTime) { ReefAngel.Relay.On(ROSolenoid); } else { ReefAngel.Relay.Off(ROSolenoid); } // Turn on the RelayPS when needed. ReefAngel.Relay.Set(RelayPS, (ReefAngel.Relay.Status(ROSolenoid) || ReefAngel.Relay.Status(AWCPump))); }
// 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] = Port1Bit | Port3Bit | Port5Bit; // Dosing Pumps 6,7,8 ReefAngel.OverridePortsE[1] = Port4Bit; // ATO Solenoid ReefAngel.OverridePortsE[2] = Port4Bit | Port5Bit | Port7Bit; // Vacaton/AutoFeed/Calibrate } else { ReefAngel.OverridePortsE[0] = 0; ReefAngel.OverridePortsE[1] = 0; ReefAngel.OverridePortsE[2] = 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, Mem_B_MaintVinegar }; 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 DailyReport() { static WiFiAlert dailyReport; static boolean sent; static char msg[64]; char temp[10]; char ph[10]; char alk[10]; char sal[10];
if ((now()+(6*SECS_PER_HOUR))%(12*SECS_PER_HOUR)==5 && !sent) { dtostrf((float)ReefAngel.Params.Temp[T1_PROBE]/10,3, 1, temp); dtostrf((float)ReefAngel.Params.PH/100,4, 2, ph); dtostrf((float)ReefAngel.Params.PHExp/100,4, 2, alk); dtostrf((float)ReefAngel.Params.Salinity/10,3, 1, sal); sprintf(msg,"T1:%s+PH:%s+KH:%s+S:%s+WL:%d",temp,ph,alk,sal,ReefAngel.WaterLevel.GetLevel()); dailyReport.Send(msg,true); sent=true; } else { if (dailyReport.IsActive()) { dailyReport.Send(); } else { sent=false; } } }
void DelayedOnModes(byte relay) { static unsigned long startTime=now();
if ( (startTime==LastStart) && ReefAngel.HighATO.IsActive()) { ReefAngel.Relay.On(relay); } else { ReefAngel.Relay.DelayedOn(relay); } }
// ------------------------------------------------------------ // Change the values below to customize your cloud/storm effect
// Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc) // For testing purposes, you can use 1 and cause the cloud to occur everyday #define Clouds_Every_X_Days 1
// Percentage chance of a cloud happening today // For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening #define Cloud_Chance_per_Day 50
// Minimum number of minutes for cloud duration. Don't use min duration of less than 6 #define Min_Cloud_Duration 10
// Maximum number of minutes for the cloud duration. Don't use max duration of more than 255 #define Max_Cloud_Duration 20
// Minimum number of clouds that can happen per day #define Min_Clouds_per_Day 1
// Maximum number of clouds that can happen per day #define Max_Clouds_per_Day 4
// Only start the cloud effect after this setting // In this example, start cloud after noon #define Start_Cloud_After NumMins(9,00)
// Always end the cloud effect before this setting // In this example, end cloud before 9:00pm #define End_Cloud_Before NumMins(23,00)
// Percentage chance of a lightning happen for every cloud // For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening #define Lightning_Chance_per_Cloud 65
// Note: Make sure to choose correct values that will work within your PWMSLope settings. // For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes. // Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes // of effects or unforseen result could happen. // Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before. // In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the // 250 minutes (or 500 minutes) can fit in that 510 minutes window. // It's a tight fit, but it did.
//#define printdebug // Uncomment this for debug print on Serial Monitor window #define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.
// Add Random Lightning modes #define Calm 0 // No lightning #define Slow 1 // 5 seconds of slow lightning in the middle of a cloud for ELN style (slow response) drivers #define Fast 2 // 5 seconds of fast lightning in the middle of a cloud for LDD style (fast response) drivers #define Mega 3 // Lightning throughout the cloud, higher chance as it gets darker #define Mega2 4 // Like Mega, but with more lightning // ------------------------------------------------------------ // Do not change anything below here
static byte cloudchance=255; static byte cloudduration=0; static int cloudstart=0; static byte numclouds=0; static byte lightningchance=0; static byte cloudindex=0; static byte lightningstatus=0; static byte lightningMode=0; static boolean chooseLightning=true;
void CheckCloud() { // Set which modes you want to use // Example: { Calm, Fast, Mega, Mega2 } to randomize all four modes. // { Mega2 } for just Mega2. { Mega, Mega, Fast} for Mega and Fast, with twice the chance of Mega. byte LightningModes[] = {Slow};
// Change the values above to customize your cloud/storm effect
static time_t DelayCounter=millis(); // Variable for lightning timing. static int DelayTime=random(1000); // Variable for lightning timimg.
// Every day at midnight, we check for chance of cloud happening today if (hour()==0 && minute()==0 && second()==0) cloudchance=255;
#ifdef forcecloudcalculation if (cloudchance==255) #else if (hour()==0 && minute()==0 && second()==1 && cloudchance==255) #endif { // Commenting out to see if it's interfering with our other seed. // randomSeed(millis()); // Seed the random number generator //Pick a random number between 0 and 99 cloudchance=random(100); // if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today if (cloudchance>Cloud_Chance_per_Day) cloudchance=0; // Check if today is day for clouds. if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0; // If we have cloud today if (cloudchance) { // pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day); // pick the time that the first cloud will start // the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day. cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))); // pick a random number for the cloud duration of first cloud. cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration); //Pick a random number between 0 and 99 lightningchance=random(100); // if picked number is greater than Lightning_Chance_per_Cloud, we will not have lightning today if (lightningchance>Lightning_Chance_per_Cloud) lightningchance=0; } } // Now that we have all the parameters for the cloud, let's create the effect
if (!InternalMemory.read(Mem_B_EnableStorm)) return; if (cloudchance) { if (ReefAngel.Relay.isMaskOff(LED_STORM)) // Change this to whatever port you want to use as a trigger. { cloudstart = NumMins(hour(), minute()); ReefAngel.Relay.Auto(LED_STORM); // Here, too. } //is it time for cloud yet? if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration)) { // Cloud Dimming DaylightPWMValue0=ReversePWMSlopeHighRes(cloudstart,cloudstart+cloudduration,DaylightPWMValue0,100,240); DaylightPWMValue2=ReversePWMSlopeHighRes(cloudstart,cloudstart+cloudduration,DaylightPWMValue2,100,240);
if (chooseLightning) { lightningMode=LightningModes[random(100)%sizeof(LightningModes)]; chooseLightning=false; } switch (lightningMode) { case Calm: break; case Mega: // Lightning chance from beginning of cloud through the end. Chance increases with darkness of cloud. if (lightningchance && random(ReversePWMSlope(cloudstart,cloudstart+cloudduration,100,0,180))<1 && (millis()-DelayCounter)>DelayTime) { // Send the trigger Strike(); DelayCounter=millis(); // If we just had a round of flashes, then lets put in a longer delay DelayTime=random(1000); // of up to a second for dramatic effect before we do another round. } break; case Mega2: // Higher lightning chance from beginning of cloud through the end. Chance increases with darkness of cloud. if (lightningchance && random(ReversePWMSlope(cloudstart,cloudstart+cloudduration,100,0,180))<2) { Strike(); } break; case Fast: // 5 seconds of lightning in the middle of the cloud if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5 && (millis()-DelayCounter)>DelayTime) { Strike();
DelayCounter=millis(); // If we just had a round of flashes, then lets put in a longer delay DelayTime=random(1000); // of up to a second for dramatic effect before we do another round. } break; case Slow: // Slow lightning for 5 seconds in the middle of the cloud. Suitable for slower ELN style drivers if (lightningchance && second()%40<8) { SlowStrike(); } break; default: break; } } else { chooseLightning=true; // Reset the flag to choose a new lightning type }
if (NumMins(hour(),minute())>(cloudstart+cloudduration)) { cloudindex++; if (cloudindex < numclouds) { cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))); // pick a random number for the cloud duration of first cloud. cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration); //Pick a random number between 0 and 99 lightningchance=random(100); // if picked number is greater than Lightning_Chance_per_Cloud, we will not have lightning today if (lightningchance>Lightning_Chance_per_Cloud) lightningchance=0; } } } // Cloud ON option - Clouds every minute if (ReefAngel.Relay.isMaskOn(LED_STORM) && now()%60<10) { SlowStrike(); } }
void SlowStrike() { int r = random(100); if (r<20) lightningstatus=1; else lightningstatus=0; if (lightningstatus) { // Let's separate left and right both. if (r<13) { DaylightPWMValue0=4095; DaylightPWMValue2=4095; } else if (r<17) { DaylightPWMValue0=100; DaylightPWMValue2=4095; } else { DaylightPWMValue0=4095; DaylightPWMValue2=100; } } else { DaylightPWMValue0=100; DaylightPWMValue2=100; } delay(2); }
void Strike() { int a=random(1,5); // Pick a number of consecutive flashes from 1 to 4. for (int i=0; i<a; i++) { // Flash on int newdata=4095; Wire.beginTransmission(0x40); // Address of the dimming expansion module Wire.write(0x8+(4*0)); // 0x8 is channel 0, 0x12 is channel 1, etc. This is channel 0. Wire.write(newdata&0xff); // Send the data 8 bits at a time. This sends the LSB Wire.write(newdata>>8); // This sends the MSB Wire.endTransmission(); Wire.beginTransmission(0x40); // Address of the dimming expansion module Wire.write(0x8+(4*2)); // 0x8 is channel 0, 0x12 is channel 1, etc. This is channel 2. Wire.write(newdata&0xff); // Send the data 8 bits at a time. This sends the LSB Wire.write(newdata>>8); // This sends the MSB Wire.endTransmission(); int randy=random(20,80); // Random number for a delay if (randy>71) randy=((randy-70)/2)*100; // Small chance of a longer delay delay(randy); // Wait from 20 to 69 ms, or 100-400 ms // Flash off. Return to baseline. newdata=ReefAngel.PWM.GetChannelValueRaw(0); // Use the channel number you're flashing here Wire.beginTransmission(0x40); // Same as above Wire.write(0x8+(4*0)); Wire.write(newdata&0xff); Wire.write(newdata>>8); Wire.endTransmission(); newdata=ReefAngel.PWM.GetChannelValueRaw(2); // Use the channel number you're flashing here Wire.beginTransmission(0x40); // Same as above Wire.write(0x8+(4*2)); Wire.write(newdata&0xff); Wire.write(newdata>>8); Wire.endTransmission(); delay(random(30,50)); // Wait from 30 to 49 ms wdt_reset(); // Reset watchdog timer to avoid re-boots } }
byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength) { long n=elapsedSecsToday(now()); cstart*=60; cend*=60; if (n<cstart) return PWMStart; if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd); if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd; if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart); if (n>cend) return (int) PWMStart; }
int ReversePWMSlopeHighRes(long cstart,long cend,int PWMStart,int PWMEnd, byte clength) { long n=elapsedSecsToday(now()); cstart*=60; cend*=60; if (n<cstart) return PWMStart; if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd); if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd; if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart); if (n>cend) return (int) PWMStart; }
You no longer need any extra classes to use this code with the current libraries: 1.1.1
Last edited by lnevo on Mon Jan 07, 2013 10:30 pm, edited 8 times in total.
|