Home

Arduino Based Auto TopOff Unit

An Arduino Uno based project that uses an Ethernet shield and a variety of sensors to control the water flow to an aquarium sump as well as storage container (topoff) where the water is prepared for use. Currently the ATOU relies on 5 float sensors (3 in the topoff and 2 in the sump), a temperature sensor, and a water detection sensor (in case of an overflow or spill). The ATOU can control (and expects to control) 5 outputs. Each output goes to a connector to trigger relays or 12v DC devices themselves. In this design those outputs are expected to be: 12vdc Fill Valve - A normally closed water valve that allows water to enter the topoff from an RO/DI system SSR Driven Transfer Pump - A pump that transfers water from the topoff to the sump DEPRECATED - SSR Driven Heater - A heater to warm the water in the topoff if needed The heater was determined to be unnecessary so has been changed into an unused 12v output for now. SSR Driven Circulation - A circulation pump in the topoff for when water is added or the heater started Alarm - An output to designate that there is an alarm condition (ex: overheat, spill, stuck floats, etc...) The Ethernet shield is meant to provide a status page for the ATOU and has no control capabilities at this time. It is indeterminate whether it ever will in fact. If it does it will likely be simple things like the timeout of the circulation and maybe a system shutoff. It also allows the ATOU to retreive the time from an NTP server although this is presently used only for status information and not control functions.
A .zip containing the program is here: ATOU.zip


ATOU.ino Example

/*
  Name: ATOU - Auto Top Off Unit
  Created by: David Snell
  Last Update: 12/8/2015
  Description: An Arduino Uno based project that uses an Ethernet shield and a variety of sensors
  to control the water flow to an aquarium sump as well as storage container (topoff) where the water
  is prepared for use. Currently the ATOU relies on 5 float sensors (3 in the topoff and 2 in the sump),
  a temperature sensor, and a water detection sensor (in case of an overflow or spill). The ATOU can
  control (and expects to control) 5 outputs. Each output goes to a connector to trigger relays or
  12v DC devices themselves. In this design those outputs are expected to be:
  12vdc Fill Valve - A normally closed water valve that allows water to enter the topoff from an RO/DI system
  SSR Driven Transfer Pump - A pump that transfers water from the topoff to the sump
  DEPRECATED - SSR Driven Output - A Output to warm the water in the topoff if needed
	  Output was removed as it was determined the topoff was fine without heating
  SSR Driven Circulation - A circulation pump in the topoff for when water is added or the Output started
  Alarm - An output to designate that there is an alarm condition (ex: overheat, spill, stuck floats, etc...)

  The Ethernet shield is meant to provide a status page for the ATOU and has no control capabilities at this
  time. It is indeterminate whether it ever will in fact. If it does it will likely be simple things like the
  timeout of the circulation and maybe a system shutoff. It also allows the ATOU to retreive the time from an
  NTP server although this is presently used only for status information and not control functions.
*/

// -- Begin Pin Assignments
/*
  Pin 0 = RX0 - Unused
  Pin 1 = TX0 - Unused
  Pin 2 = DIO - 12vdc Output - Used to be Output
  Pin 3 = PWM - Transfer Pump Output
  Pin 4 = DIO - Valve Output
  Pin 5 = PWM - Circulation Pump Output
  Pin 6 = PWM - Alarm Output
  Pin 7 = DIO - Float Switch 4 - Sump Low
  Pin 8 = DIO - Unused and blocked by "shield" due to absurd Arduino pin spacing
  Pin 9 = PWM - Unused and blocked by "shield" due to absurd Arduino pin spacing
  Pin 10 = PWM - Ethernet Shield
  Pin 11 = PWM - Ethernet Shield
  Pin 12 = DIO - Ethernet Shield
  Pin 13 = DIO - Ethernet Shield
  Pin A0 = AI - Water Sensor (for overflow emergency)
  Pin A1 = AI - Float Switch 0 - Topoff Full
  Pin A2 = AI - Float Switch 1 - Topoff Low
  Pin A3 = AI - Float Switch 2 - Topoff Empty
  Pin A4 = AI - Float Switch 3 - Sump Full
  Pin A5 = AI - Water Temperature Sensor
*/
// End PIN Assignments

// Begin Includes
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <math.h>
// End Includes

// Begin Network Settings
boolean Networked = true; // Indicates whether the ATOU should attempt network functions
byte MAC[] = { 0x90, 0xA2, 0xDA, 0x00, 0xDE, 0xB0 }; // MAC address must be populated if networked
IPAddress IP( 192, 168, 1, 120 ); // Static IP when DHCP unavailable
// End Network Settings

// Begin Time Settings
unsigned long Time = 0; // So one value of time is used throughout a loop
unsigned int StartTime[ 3 ]; // Array of the segments of time, for when the ATOU first started
unsigned int CurrentTime[ 3 ]; // Array of the segments of time, for the current time
unsigned long NTPTime = 1000; // Time before an NTP attempt is made
unsigned int NTPDelay = 60000; // Delay between NTP attempts with a default of 60 seconds
bool NTPStatus = false; // Indicates if the last NTP attempt was successful
// End Time Settings

// Begin General Settings
boolean Debugging = false; // Whether serial debugging status will be provided
unsigned int DebugDelay = 5000; // Delay for debug status with a default of 5 seconds
unsigned long DebugTime = 5000; // Time before a debug status occurs
unsigned long CirculationStop = 0; // Time that the circulation would stop
unsigned long CirculationDelay = 1800000; // Delay before circulation is stopped with a default of 30 minutes

#define WaterAlertPin A0 // Which pin the Water Alert is output on
#define TempSensorPin A5 // Which pin the Temp Sensor is input from
#define CirculationPin 5 // Which pin the Circulation is output on
#define OutputPin 2 // Which pin the 12v is output on
#define FillValvePin 4 // Which pin the Fill Valve is output on
#define TopoffFullFloatPin A1 // Which pin the Topoff Full is input from
#define TopoffLowFloatPin A2 // Which pin the Topoff Low is input from
#define TopoffEmptyFloatPin A3 // Which pin the Topoff Empty is input from
#define SumpFullFloatPin A4 // Which pin the Sump Full is input from
#define SumpLowFloatPin 7 // Which pin the Sump Empty is input from
#define TransferPumpPin 3 // Which pin the Circulation is output on
#define AlarmPin 6 // Which pin the Alarm is output on
boolean WaterAlertOn = false; // The Water Alert value
boolean AlarmOn = false; // The Alarm value
boolean FillValveOn = false; // The Fill Valve value
boolean TransferPumpOn = false; // The Transfer Pump value
boolean CirculationOn = false; // The Circulation value
boolean OutputOn = false; // The Output value
boolean TopoffFullFloatOn = true; // The Topoff Full value
boolean TopoffLowFloatOn = true; // The Topoff Low value
boolean TopoffEmptyFloatOn = true; // The Topoff Empty value
boolean SumpFullFloatOn = true; // The Sump Full value
boolean SumpLowFloatOn = true; // The Sump Empty value
String AlarmMessage;

double AlertTemp = 74.0; // Water temperature at which an alert should be triggered
double CurrentTemp; // Current temperature of the water
unsigned int SensorDelay = 5000; // Delay between sensor readings with a default of 5 seconds
unsigned long SensorTime = 0; // Time before a sensor reading occurs
// End General Settings

// Begin Initializing Functions
EthernetUDP udp; // UDP so that NTP will be able to be retrieved
EthernetServer Server( 80 ); // Setting the webserver
//End Initializing Functions

// Begin Setup
// The initial routine for starting up the project
void setup() {
  if( Debugging ){ // If debugging...
    Serial.begin( 9600 ); // Set serial communications rate
    Serial.println( "Starting up..." ); // Indicate startup of project
    Serial.println( "Setting I/O..." ); // Indicate that I/O pins are being set
  }
  // Setting output pins and initial value
  pinMode( FillValvePin, OUTPUT );
  pinMode( TransferPumpPin, OUTPUT );
  pinMode( AlarmPin, OUTPUT );
  pinMode( CirculationPin, OUTPUT );
  pinMode( OutputPin, OUTPUT );
  digitalWrite( FillValvePin, LOW );
  digitalWrite( TransferPumpPin, LOW );
  digitalWrite( AlarmPin, LOW );
  digitalWrite( CirculationPin, LOW );
  digitalWrite( OutputPin, LOW );
  if( Debugging ){ // If debugging...
    Serial.println( "Output pins set." ); // Indicate that output pins are set
  }
  // Setting input pins
  pinMode( WaterAlertPin, INPUT );
  pinMode( TempSensorPin, INPUT );
  pinMode( TopoffFullFloatPin, INPUT );
  pinMode( TopoffLowFloatPin, INPUT );
  pinMode( TopoffEmptyFloatPin, INPUT );
  pinMode( SumpFullFloatPin, INPUT );
  pinMode( SumpLowFloatPin, INPUT );
  if( Debugging ){ // If debugging...
    Serial.println( "Input pins set." ); // Indicate that input pins are set
    if( Networked ){ // If networking is enabled...
      Serial.println( "Starting networking..." ); // Indicate that networking will be started
    } else { // Networking is disabled
      Serial.println( "Networking disabled." ); // Indicate that networking is disabled
    }
  }
  if( Networked ){ // If networking is enabled...
    // Start networking
    if ( Ethernet.begin( MAC ) == 0 ) { // If the Ethernet does not work with MAC alone (ie: DHCP)...
      Ethernet.begin( MAC, IP ); // Set it up using Static IP...
    }
    Server.begin(); // Start the server up for webpages
    delay( 1000 ); // Delay to let the ethernet shield settle out
    GetTime();
    NTPTime = Time + NTPDelay; // Set the delay before the next NTP attempt
    if( NTPStatus ){ // Was the initial time retrieved from NTP...
      StartTime[ 0 ] = CurrentTime[ 0 ]; // Set the Start time to the current time received
      StartTime[ 1 ] = CurrentTime[ 1 ]; // Set the Start time to the current time received
      StartTime[ 2 ] = CurrentTime[ 2 ]; // Set the Start time to the current time received
    } else { // NTP retrieval failed.
      StartTime[ 0 ] = 25; // Set the Start hours to 25 so we know NTP failed initially
      StartTime[ 1 ] = 0; // Set the Start minutes to 0 because NTP failed
      StartTime[ 2 ] = 0; // Set the Start seconds to 0 because NTP failed
    }
    // Debugging value headers
    if( Debugging ){ // If debugging...
      Serial.print( "Network set IP is " ); // Indicate that networking is setup
      Serial.println( Ethernet.localIP() ); // Indicate that IP address received
    }
  }
  if( Debugging ){ // If debugging...
    Serial.print( "Startup at " ); // Indicate that the startup was completed
    if( !NTPStatus ){
    Serial.println( "unknown time due to NTP failure" ); // Indicate the startup hour
    } else {
      Serial.print( StartTime[ 0 ] ); // Indicate the startup hour
      Serial.print( ":" );
      if( StartTime[ 1 ] < 10 ){ // Is the minute value greater less than 10...
        Serial.print( "0" ); // Add a 0 ahead of the single digit minute
      }
      Serial.print( StartTime[ 1 ] ); // Indicate the startup minute
      if( StartTime[ 2 ] < 10 ){ // Is the second value greater less than 10...
        Serial.print( "0" ); // Add a 0 ahead of the single digit second
      }
      Serial.print( StartTime[ 2 ] ); // Indicate the startup second
      Serial.println( " UTC" ); // Indicate timezone
    }
    Serial.println( "----------" ); // Line to break up status sets
  }
}
// -- End Setup Code

// -- Begin Loop Code
void loop() {
  Time = millis(); // Use the same Time value for this loop at least
  if( Networked ){
    // Check an NTP source for real time
    if( Time > NTPTime ) {
      GetTime(); // Get the time by NTP if possible
      NTPTime = Time + NTPDelay; // Set the delay before the next NTP attempt
    }
    // Run the webserver to provide basic status and eventually control
    Webserver();
  }
  // Section for reading the sensors
  if( Time > SensorTime ) {
    WaterAlertOn = digitalRead( WaterAlertPin );
    TopoffFullFloatOn = digitalRead( TopoffFullFloatPin );
    TopoffLowFloatOn = digitalRead( TopoffLowFloatPin );
    TopoffEmptyFloatOn = digitalRead( TopoffEmptyFloatPin );
    SumpFullFloatOn = digitalRead( SumpFullFloatPin );
    SumpLowFloatOn = digitalRead( SumpLowFloatPin );
    CurrentTemp = TempCheck();
    CheckAll();
    SensorTime = Time + SensorDelay;
  }
  PerformActions();
  // Section for debugging purposes
  if( Debugging && Time > DebugTime ){ // If debugging and it is time for a debug status
    DebugStatus(); // Run the DebugStatus routine
    DebugTime = Time + DebugDelay; // Set the next time a debug status should be provided
  }
}
// -- End Loop Code

// Begin TempCheck
// Based on unclaimed code found online for reading a thermistor input. If you have a claim to this code, let me know.
// TempCheck takes the analog reading from the 10K temp sensor and recalculates it as a Fahrenheit value.
// If desired, the value can be left as Celsius if the last Temp = line is commented out.
double TempCheck(){
  double Temp; // Declaring a temporary double value for processing the input
  Temp = log( 10000.0 * ( ( 1024.0 / analogRead( TempSensorPin ) - 1 ) ) ); // Get the initial analog reading
  Temp = 1 / ( 0.001129148 + ( 0.000234125 + ( 0.0000000876741 * Temp * Temp ) ) * Temp ); // Maths
  Temp = Temp - 273.15; // Maths
  Temp = ( Temp * 9.0 ) / 5.0 + 32.0; // Convert from Celsius to Fahrenheit
  return Temp; // Return the value
}
// End TempCheck

// Begin CheckAll
// CheckAll runs through all inputs and sets whether outputs should be on or off
//   and checks if the topoff has sufficient water (above the low float sensor) to transfer water
//   to the sump (if needed). It also checks if the topoff needs filling and heating and in either case turns
//   on the circulation for a while. The sump only fills once it reaches the sump low float sensor and fills
//   until it reaches the sump full sensor (or the topoff becomes low).
void CheckAll(){
  if( WaterAlertOn ){ // If the water alert is true... Implying water has flooded the floor somehow...
    AlarmOn = true; // Set the alarm
		AlarmMessage = "spill";
  }
  if( CurrentTemp > AlertTemp ){ // If water in topoff is too hot...
    AlarmOn = true; // Set the alarm
		AlarmMessage = "temperature";
  }
  // Topoff checks... if the topoff is empty but low or full, alarm... or if the topoff is low but also full, alarm.
  // Implication here is that a topoff float is stuck.
  if( !TopoffEmptyFloatOn && TopoffLowFloatOn ){
    AlarmOn = true; // Set the alarm
		AlarmMessage = "topoff empty but low";
  }
  // Implication here is that a topoff float is stuck.
  if( !TopoffEmptyFloatOn && TopoffFullFloatOn ){
    AlarmOn = true; // Set the alarm
		AlarmMessage = "topoff empty but full";
  }
  // Implication here is that a topoff float is stuck.
  if( !TopoffLowFloatOn && TopoffFullFloatOn ){
    AlarmOn = true; // Set the alarm
		AlarmMessage = "topoff full but low";
  }
 // If the sump is full but the sump low at the same time...
  // Implication here is that the sump floats are stuck.
  if( SumpFullFloatOn && !SumpLowFloatOn ){
    AlarmOn = true; // Set the alarm
		AlarmMessage = "sump full but low";
  }
  if( !AlarmOn ){ // If the alarm is not on go about normal business...
    if( !TopoffEmptyFloatOn ){ // If the topoff empty, shut things down until it fills up a bit...
      TransferPumpOn = false; // Turn off power to the pump
      CirculationOn = false; // Turn off power to circulation
      OutputOn = false; // Turn off power to the Output
      FillValveOn = true; // Turn on power to the fill valve
    } else if( !TopoffLowFloatOn ){ // If the topoff is low, shut things down until it fills up a bit...
      TransferPumpOn = false; // Turn off power to the pump
      CirculationOn = false; // Turn off power to circulation
      OutputOn = false; // Turn off power to the Output
      FillValveOn = true; // Turn on power to the fill valve
    } else if( !TopoffFullFloatOn ){ // If the topoff is not full, things can run but fill it a bit...
      FillValveOn = true; // Turn on power to the fill valve
      CirculationOn = true; // Turn on power to circulation
      CirculationStop = Time + CirculationDelay; // Set the timeout for the circulation to stop
    } else { // The topoff is full so things can run if needed except the fill valve.
      FillValveOn = false; // Turn off power to the fill valve
    }
    if( !SumpLowFloatOn ){ // If the sump is low, start filling...
      TransferPumpOn = true; // Turn on power to the pump
    } else if( SumpFullFloatOn ){ // If sump is full...
      TransferPumpOn = false; // Turn off power to the pump
    }
  }
}
// End CheckAll

// Begin PerformActions
// PerformActions sets the output pins to their expected values
void PerformActions(){
  if( AlarmOn ){ // If the Alarm is on shut everything down EXCEPT the Alarm
    digitalWrite( AlarmPin, HIGH ); // Turn on power to the alarm
    digitalWrite( FillValvePin, LOW ); // Turn off power to the valve
    digitalWrite( TransferPumpPin, LOW ); // Turn off power to the pump
    digitalWrite( CirculationPin, LOW ); // Turn off power for circulation
    digitalWrite( OutputPin, LOW ); // Turn off power to the Output
    FillValveOn = false; // Make sure the fill valve is set to be off
    TransferPumpOn = false; // Make sure the pump is set to be off
    CirculationOn = false; // Make sure circulation is set to be off
    OutputOn = false; // Make sure the Output is set to be off
  } else { // Alarm is not on so perform normal functions
    digitalWrite( AlarmPin, LOW ); // Make sure the Alarm is not on.
    if( FillValveOn ){ // If the fill valve should be on...
    digitalWrite( FillValvePin, HIGH ); // Turn on power to the valve
    } else { // Fill valve should be off.
      digitalWrite( FillValvePin, LOW ); // Turn off power to the valve
    }
    if( TransferPumpOn ){ // If the transfer pump should be on...
      digitalWrite( TransferPumpPin, HIGH ); // Turn on power to the pump
    } else { // Transfer pump should be off.
      digitalWrite( TransferPumpPin, LOW ); // Turn off power to the pump
    }	
    if( CirculationOn && ( millis() < CirculationStop ) ){ // If circulation should be on and has not hit timeout...
      digitalWrite( CirculationPin, HIGH ); // Turn on power for circulation
    } else { // Circulation should be off or is past timeout.
      CirculationOn = false; // Set CirculationOn to false in case it surpassed the timeout
      digitalWrite( CirculationPin, LOW ); // Turn off power for circulation
    }
    if( OutputOn ){ // If the Output should be on...
      digitalWrite( OutputPin, HIGH ); // Turn on power to the Output
    } else { // Output should be off.
      digitalWrite( OutputPin, LOW ); // Turn off power to the Output
    }
  }
}
// End PerformActions

// Begin DebugStatus
// DebugStatus is meant to only be used when connected to a serial port for debugging purposes.
// It provides a list of all the expected output settings and input values.
void DebugStatus(){
  Serial.print( "Status at " );
  Serial.print( CurrentTime[ 0 ] );
  Serial.print( ":" );
  if( CurrentTime[ 1 ] < 10 ){
    Serial.print( "0" );
  }
  Serial.print( CurrentTime[ 1 ] );
  Serial.print( ":" );
  if( CurrentTime[ 2 ] < 10 ){
    Serial.print( "0" );
  }
  Serial.print( CurrentTime[ 2 ] );
  if( !NTPStatus ){
    Serial.println( " since startup" );
  } else {
    Serial.println( " UTC" );
  }
  Serial.print( "Alarm is " );
  if( AlarmOn ){
    Serial.println( AlarmMessage );
  } else {
    Serial.println( "Off" );
  }
  Serial.print( "Fill is " );
  if( FillValveOn ){
    Serial.println( "Open" );
  } else {
    Serial.println( "Closed" );
  }
  Serial.print( "Transfer is " );
  if( TransferPumpOn ){
    Serial.println( "on" );
  } else {
    Serial.println( "off" );
  }
  Serial.print( "Circulation is " );
  if( CirculationOn ){
    Serial.print( "on for " );
    unsigned long TempTime = ( ( CirculationStop - Time ) / 1000 );
    if( TempTime > 120 ){
      Serial.print( ( TempTime / 60 ) );
      Serial.println( " minutes" );
    } else {
      Serial.print( ( ( CirculationStop - Time ) / 1000 ) );
      Serial.println( " seconds" );
    }
  } else {
    Serial.println( "off" );
  }
	
  Serial.print( "Water temp is " );
  Serial.println( CurrentTemp );
	
  Serial.print( "Output is " );
  if( OutputOn ){
    Serial.println( "on" );
  } else {
    Serial.println( "off" );
  }
  Serial.print( "Water Alert is " );
  if( WaterAlertOn ){
    Serial.println( "On" );
  } else {
    Serial.println( "Off" );
  }
  Serial.print( "Topoff is " );
  if( TopoffFullFloatOn ){
    Serial.println( "full" );
  } else if( TopoffLowFloatOn ){
    Serial.println( "OK" );
	} else if( TopoffEmptyFloatOn ){
    Serial.println( "low" );
	} else {
    Serial.println( "empty" );
  }
  Serial.print( "Sump is " );
  if( SumpFullFloatOn ){
    Serial.println( "full" );
  } else if( SumpLowFloatOn ){
    Serial.println( "low" );
	} else {
    Serial.println( "empty" );
  }
  Serial.println( "--" );
}
// End DebugStatus

// Begin GetTime
// GetTime splits the epoch seconds into the CurrentTime array and sets whether NTP was successful
void GetTime() {
  unsigned long epoch = ntpUnixTime( udp ); // Set epoch to the NTP value returned
  if( epoch == 0 ){ // NTP did not return a value...
    CurrentTime[ 0 ] = ( ( ( Time / 1000 ) % 86400L ) / 3600 ); // Current hours since startup
    CurrentTime[ 1 ] = ( ( ( Time / 1000 ) % 3600 ) / 60 ); // Current minutes since startup
    CurrentTime[ 2 ] = ( ( Time / 1000 ) % 60 ); // Current seconds since startup
    NTPStatus = false; // Set NTP status to false
  } else { // NTP returned a value
    CurrentTime[ 0 ] = ( ( epoch % 86400L ) / 3600 ); // Current hours from NTP
    CurrentTime[ 1 ] = ( ( epoch % 3600 ) / 60 ); // Current minutes from NTP
    CurrentTime[ 2 ] = ( epoch % 60 ); // Current seconds from NTP
    NTPStatus = true; // NTP was successful
  }
}
/*
 * © Francesco Potortì 2013 - GPLv3 - Revision: 1.13
 *
 * Send an NTP packet and wait for the response, return the Unix time
 *
 * To lower the memory footprint, no buffers are allocated for sending
 * and receiving the NTP packets.  Four bytes of memory are allocated
 * for transmision, the rest is random garbage collected from the data
 * memory segment, and the received packet is read one byte at a time.
 * The Unix time is returned, that is, seconds from 1970-01-01T00:00.
 */
unsigned long inline ntpUnixTime( UDP &udp )
{
  static int udpInited = udp.begin( 123 ); // open socket on arbitrary port

  const char timeServer[] = "pool.ntp.org"; // NTP server

  // Only the first four bytes of an outgoing NTP packet need to be set
  // appropriately, the rest can be whatever.
  const long ntpFirstFourBytes = 0xEC0600E3; // NTP request header

  // Fail if WiFiUdp.begin() could not init a socket
  if (! udpInited)
    return 0;

  // Clear received data from possible stray received packets
  udp.flush();

  // Send an NTP request
  if (! (udp.beginPacket(timeServer, 123) // 123 is the NTP port
    && udp.write((byte *)&ntpFirstFourBytes, 48) == 48
    && udp.endPacket()))
    return 0;				// sending request failed

  // Wait for response; check every pollIntv ms up to maxPoll times
  const int pollIntv = 150;		// poll every this many ms
  const byte maxPoll = 15;		// poll up to this many times
  int pktLen;				// received packet length
  for (byte i=0; i<maxPoll; i++) {
    if ((pktLen = udp.parsePacket()) == 48)
      break;
    delay(pollIntv);
  }
  if (pktLen != 48)
    return 0;				// no correct packet received

  // Read and discard the first useless bytes
  // Set useless to 32 for speed; set to 40 for accuracy.
  const byte useless = 40;
  for (byte i = 0; i < useless; ++i)
    udp.read();

  // Read the integer part of sending time
  unsigned long time = udp.read();	// NTP time
  for (byte i = 1; i < 4; i++)
    time = time << 8 | udp.read();

  // Round to the nearest second if we want accuracy
  // The fractionary part is the next byte divided by 256: if it is
  // greater than 500ms we round to the next second; we also account
  // for an assumed network delay of 50ms, and (0.5-0.05)*256=115;
  // additionally, we account for how much we delayed reading the packet
  // since its arrival, which we assume on average to be pollIntv/2.
  time += (udp.read() > 115 - pollIntv/8);

  // Discard the rest of the packet
  udp.flush();

  return time - 2208988800ul;		// convert NTP time to Unix time
}
// End Get Time

// Begin Webserver
// Webserver provides a website for monitoring the ATOU the same as debugging at this time
void Webserver() {
  EthernetClient Web = Server.available();
  if( Web ) {
    if( Debugging ){
      Serial.println( "Client connected to website..." );
    }
    boolean currentLineIsBlank = true;
    while( Web.connected() ) {
      if( Web.available() ) {
        char c = Web.read();
        if( c == '\n' && currentLineIsBlank ) {
          // Basic HTTP and HTML header information as well as title and initial lines
          Web.println( "HTTP/1.1 200 OK" );
          Web.println( "Content-Type: text/html" );
          Web.println();
          Web.println( "<html>" );
          Web.println( "<head>" );
          Web.println( "<meta http-equiv='refresh' content='5'>" );
          Web.println( "<title>ATOU</title>" );
          Web.println( "</head>" );
          Web.println( "<body>" );
          Web.println( "<u>ATOU</u></br>" );
          Web.print( "Startup was " );
          if( StartTime[ 0 ] == 25 ){ // If the Start hours is 25 due to NTP failure at startup
            Web.println( "unknown" ); // Indicate startup time was unknown
          } else { // The Start time is good
            Web.print( StartTime[ 0 ] ); // Indicate startup hours
            Web.print( ":" );
            if( StartTime[ 1 ] < 10 ){ // Minutes is less than 10...
              Web.print( "0" ); // Add a 0 digit
            }
            Web.print( StartTime[ 1 ] ); // Indicate startup minutes
            Web.print( ":" );
            if( StartTime[ 2 ] < 10 ){ // Seconds is less than 10...
              Web.print( "0" ); // Add a 0 digit
            }
            Web.print( StartTime[ 2 ] ); // Indicate startup seconds
            Web.println( " UTC" );
          }
          Web.print( "</br>Current time is " );
          Web.print( CurrentTime[ 0 ] ); // Indicate current hours
          Web.print( ":" );
          if( CurrentTime[ 1 ] < 10 ){ // Minutes is less than 10...
            Web.print( "0" ); // Add a 0 digit
          }
          Web.print( CurrentTime[ 1 ] ); // Indicate current minutes
          Web.print( ":" );
          if( CurrentTime[ 2 ] < 10 ){ // Seconds is less than 10...
            Web.print( "0" ); // Add a 0 digit
          }
          Web.print( CurrentTime[ 2 ] ); // Indicate current seconds
          if( !NTPStatus ){ // Did last NTP fail...
            Web.println( " since startup" ); // Indicate time is based on startup
          } else { // NTP was successful
            Web.println( " UTC" ); // Indicate time is UTC
          }
          // Show Alarm status
          Web.print( "</br></br>Alarm is " );
          if( AlarmOn ){
            Web.println( AlarmMessage );
          } else {
            Web.println( "off" );
          }
          // Show fill valve status
          Web.print( "</br></br>Fill is " );
          if( FillValveOn ){
            Web.println( "on" );
          } else {
            Web.println( "off" );
          }
          // Show transfer pump status
          Web.print( "</br>Transfer is " );
          if( TransferPumpOn ){
            Web.println( "on" );
          } else {
            Web.println( "off" );
          }
          // Show circulation status
          Web.print( "</br>Circulation is " );
          if( CirculationOn ){ // Circulation is on so indicate how much time is remaining it will be on
            Web.print( "on for " );
            unsigned long TempTime = ( ( CirculationStop - Time ) / 1000 );
            if( TempTime > 120 ){
              Web.print( ( TempTime / 60 ) );
              Web.println( " minutes" );
            } else {
              Web.print( ( ( CirculationStop - Time ) / 1000 ) );
              Web.println( " seconds" );
            }
          } else { // Circulation is off
            Web.println( "off" );
          }
          // Show water temperature
          Web.print( "</br></br>Water temp is " );
          Web.print( CurrentTemp );
          Web.println( "</br>" );
          // Show Output status
          Web.print( "Output is " );
          if( OutputOn ){
            Web.println( "on" );
          } else {
            Web.println( "off" );
          }
          // Show status of topoff full float
          Web.print( "</br></br>Topoff is " );
          if( TopoffFullFloatOn ){
            Web.println( "full" );
          } else if( TopoffLowFloatOn ){
            Web.println( "OK" );
          } else if( TopoffEmptyFloatOn ){
            Web.println( "low" );
          } else {
            Web.println( "empty" );
          }
          Web.print( "</br>Sump is " );
          if( SumpFullFloatOn ){
            Web.println( "full" );
          } else if( SumpLowFloatOn ){
            Web.println( "low" );
          } else {
            Web.println( "empty" );
          }
          Web.println( "</br>" );
          // Finish off HTML
          Web.println( "</body>" );
          Web.println( "</html>" );
          break;
        }
        if (c == '\n') {
          // No characters received
          currentLineIsBlank = true;
        } else if (c != '\r') {
          // Received a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    delay( 1 );
    Web.stop();
  }
}
// End Webserver