State changes not working for me on Aquarium controller project


Hi all,

I am building an aquarium controller to monitor temperatures and perform automatic water changes for me. I have all the hardware in place and working and am just working on programming my arduino.

I have it to a stage where it will display temps from two DS18B20 temperature probes on the LCD, it will also react to two float switches and a button to turn relays on/off to perform the water change.

Ultimately what I would like it to do is:
-Monitor 2 x temps, one in the actual aquarium and one in the header tank I have to provide water for top ups. - WORKING
-Have 1 button to perform a water change, this will turn off the canister filter relay and turn on the drain valve relay, then when the lower float switch activates it will turn off the drain relay and turn on the top up pump relay. Once the water level reaches the top float switch it will turn off the top up pump and turn the canister filter relay back on. - WORKING but issues above!
-pre heat header tank water using a water heater connected to a relay, prior to starting a water change. -NOT ADDED YET
-work with DS1302 RTC module to perform automatic water changes on a schedule - NOT ADDED YET
-use an ethernet shield to report water changes and temps via local network.

the issue I have is that I am trying to use statechange for the float switches to trigger events, the events trigger fine but the LCD doesn’t update to the appropriate message as I believe they are all constantly trying to overwrite each other on the screen. This leads me to believe the statechange actions isnt actually working and the system is just reacting to the current state of the float sensors rather than only performing an action when the float sensor state changes.

Code is below, the hardware I am using is:

Arduino Uno 3
I2C 20x4 Blue LCD Module
2 x DS18B20
DS1302 RTC
2 x Float switch
4 x Relay

#include <Wire.h>  // Comes with Arduino IDE
#include <LiquidCrystal_I2C.h> //For LCD


/*-----( Declare Constants )-----*/
/*-----( Declare objects )-----*/
// set the LCD address to 0x27 for a 20 chars 4 line display
// Set the pins on the I2C chip used for LCD connections:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address


//temperature probes
#include <OneWire.h>
#include <DallasTemperature.h>
//adds libraries for the temp sensors
/*-----( Declare Variables )-----*/
// temp sensor Data wire is plugged into pin 2 on the Arduino
#define ONE_WIRE_BUS 2

// Setup a oneWire instance to communicate with any OneWire devices
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);


//RELAY setup
#define BUTTON 4 //water change button
#define DRAINVALVE 7 //Solenoid valve to drain main tank
#define TOPUPPUMP 8 //Pump to top up main tank from top up tank
#define CANISTER 9 //Canister Filter pump Relay
#define TOPUPHEATER 10 //HEater for Top Up Tank

const int TANKFULL = 5; //define sensor pin for top float switch
const int TANKLOW = 6; //define sensor pin for low float switch

int TANKFULLState = 0; //current state of tank full sensor
int LastTANKFULLState = 0; //previous state of tank full sensor
int TANKLOWState = 0; //current state of tank low sensor
int LastTANKLOWState = 0; //previous state of tank low sensor

void setup()   /*----( SETUP: RUNS ONCE )----*/
{
  Serial.begin(9600);  // Used to type in characters

//set pins as inputs for float switches and button, no need for external resistor
pinMode(BUTTON, INPUT_PULLUP);
pinMode(TANKFULL, INPUT_PULLUP);
pinMode(TANKLOW, INPUT_PULLUP);

  //set pins as output for relays
  pinMode(DRAINVALVE, OUTPUT);
  pinMode(TOPUPPUMP, OUTPUT);
  pinMode(CANISTER, OUTPUT);
  pinMode(TOPUPHEATER, OUTPUT);

  //sets default relay states
  digitalWrite(DRAINVALVE, HIGH);
  digitalWrite(TOPUPPUMP, HIGH);
  digitalWrite(CANISTER, LOW);
  digitalWrite(TOPUPHEATER, HIGH);
  //starts with canister on, all other relays off


  lcd.begin(20, 4);        // initialize the lcd for 20 chars 4 lines, turn on backlight

  // ------- Quick 3 blinks of backlight  -------------
  for (int i = 0; i < 3; i++)
  {
    lcd.backlight();
    delay(100);
    lcd.noBacklight();
    delay(100);
  }
  lcd.backlight(); // finish with backlight on

  //-------- Write characters on the display ------------------
  // NOTE: Cursor Position: Lines and Characters start at 0
  lcd.setCursor(3, 0); //Start at character 4 on line 0
  lcd.print("Starting...");
  delay(1000);




}/*--(end setup )---*/


void loop()   /*----( LOOP: RUNS CONSTANTLY )----*/


{
  // call sensors.requestTemperatures() to issue a global temperature
  // request to all devices on the bus
  /********************************************************************/

  sensors.requestTemperatures(); // Send the command to get temperature readings

  /********************************************************************/
  lcd.setCursor(0, 0); //Start at character 4 on line 0
  lcd.print("Tank Temp:");
  lcd.print(sensors.getTempCByIndex(0)); // Why "byIndex"?
  // You can have more than one DS18B20 on the same bus.
  // 0 refers to the first IC on the wire
  lcd.setCursor(0, 1); //Start at character 4 on line 0
  lcd.print("Topup Temp:");
  lcd.print(sensors.getTempCByIndex(1)); // Why "byIndex"?
  // You can have more than one DS18B20 on the same bus.
  // 0 refers to the first IC on the wire
  delay(500);

  if(digitalRead(BUTTON) == LOW) // water vhange button pressed
  {
    digitalWrite(CANISTER, HIGH); //turn off canister filter pump
    digitalWrite(DRAINVALVE, LOW); //turn on drain valve
    lcd.setCursor(0,3); // bottom line of lcd
    lcd.print("Draining Water   "); //display message
    delay(10000);
  }

TANKLOWState = digitalRead(TANKLOW); //read the tanklow sensor state
if(TANKLOWState !=   LastTANKLOWState);  //has it changed since last time?
{
if(TANKLOWState == LOW){
 
    digitalWrite(DRAINVALVE, HIGH); //turn off drainvalve
  
    lcd.setCursor(0,3);
    lcd.print("Starting Refill");
    digitalWrite(TOPUPPUMP, LOW); //turn on top up pump
  }
}

TANKFULLState = digitalRead(TANKFULL); //read the tanklow sensor state
if(TANKFULLState !=   LastTANKFULLState);  //has it changed since last time?
{
if(TANKFULLState == HIGH)
  {
    digitalWrite(TOPUPPUMP, HIGH); //turn off top up pump
  lcd.setCursor(0,3);
  lcd.print("TANK FULL        ");
  digitalWrite(CANISTER, LOW); //turn on canister filter pump
  }
else if(TANKFULLState == LOW)
 {digitalWrite(CANISTER, HIGH); //turn off canister filter
  lcd.setCursor(0,3);
  lcd.print("TANK LOW    ");
  }
}}
/* ( THE END ) */

Where are you copying the current tank states to the previous states so that you can check whether they have changed when you next read them ?

hi Helibob, I must have accidentally removed this when I was playing around trying to get it to work, have added it in to the code now, however it still doesnt seem to perform correctly.

When the TANKLOW float switch goes LOW it does turn off DRAINVALVE relay and turn on TOPUPPUMP relay as it should. However the “Starting Refill” message on the LCD is overwritten with the “TANK LOW” message from the other float switch routine, so the LCD reads "TANK LOW ill " I have attached the new code and a picture below.

Thanks

#include <Wire.h>  // Comes with Arduino IDE
#include <LiquidCrystal_I2C.h> //For LCD


/*-----( Declare Constants )-----*/
/*-----( Declare objects )-----*/
// set the LCD address to 0x27 for a 20 chars 4 line display
// Set the pins on the I2C chip used for LCD connections:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address


//temperature probes
#include <OneWire.h>
#include <DallasTemperature.h>
//adds libraries for the temp sensors
/*-----( Declare Variables )-----*/
// temp sensor Data wire is plugged into pin 2 on the Arduino
#define ONE_WIRE_BUS 2

// Setup a oneWire instance to communicate with any OneWire devices
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);


//RELAY setup
#define BUTTON 4 //water change button
#define DRAINVALVE 7 //Solenoid valve to drain main tank
#define TOPUPPUMP 8 //Pump to top up main tank from top up tank
#define CANISTER 9 //Canister Filter pump Relay
#define TOPUPHEATER 10 //HEater for Top Up Tank

const int TANKFULL = 5; //define sensor pin for top float switch
const int TANKLOW = 6; //define sensor pin for low float switch

int TANKFULLState = 0; //current state of tank full sensor
int LastTANKFULLState = 0; //previous state of tank full sensor
int TANKLOWState = 0; //current state of tank low sensor
int LastTANKLOWState = 0; //previous state of tank low sensor

void setup()   /*----( SETUP: RUNS ONCE )----*/
{
  Serial.begin(9600);  // Used to type in characters

//set pins as inputs for float switches and button, no need for external resistor
pinMode(BUTTON, INPUT_PULLUP);
pinMode(TANKFULL, INPUT_PULLUP);
pinMode(TANKLOW, INPUT_PULLUP);

  //set pins as output for relays
  pinMode(DRAINVALVE, OUTPUT);
  pinMode(TOPUPPUMP, OUTPUT);
  pinMode(CANISTER, OUTPUT);
  pinMode(TOPUPHEATER, OUTPUT);

  //sets default relay states
  digitalWrite(DRAINVALVE, HIGH);
  digitalWrite(TOPUPPUMP, HIGH);
  digitalWrite(CANISTER, LOW);
  digitalWrite(TOPUPHEATER, HIGH);
  //starts with canister on, all other relays off


  lcd.begin(20, 4);        // initialize the lcd for 20 chars 4 lines, turn on backlight

  // ------- Quick 3 blinks of backlight  -------------
  for (int i = 0; i < 3; i++)
  {
    lcd.backlight();
    delay(100);
    lcd.noBacklight();
    delay(100);
  }
  lcd.backlight(); // finish with backlight on

  //-------- Write characters on the display ------------------
  // NOTE: Cursor Position: Lines and Characters start at 0
  lcd.setCursor(3, 0); //Start at character 4 on line 0
  lcd.print("Starting...");
  delay(1000);




}/*--(end setup )---*/


void loop()   /*----( LOOP: RUNS CONSTANTLY )----*/


{
  // call sensors.requestTemperatures() to issue a global temperature
  // request to all devices on the bus
  /********************************************************************/

  sensors.requestTemperatures(); // Send the command to get temperature readings

  /********************************************************************/
  lcd.setCursor(0, 0); //Start at character 4 on line 0
  lcd.print("Tank Temp:");
  lcd.print(sensors.getTempCByIndex(0)); // Why "byIndex"?
  // You can have more than one DS18B20 on the same bus.
  // 0 refers to the first IC on the wire
  lcd.setCursor(0, 1); //Start at character 4 on line 0
  lcd.print("Topup Temp:");
  lcd.print(sensors.getTempCByIndex(1)); // Why "byIndex"?
  // You can have more than one DS18B20 on the same bus.
  // 0 refers to the first IC on the wire
  delay(500);

  if(digitalRead(BUTTON) == LOW) // water vhange button pressed
  {
    digitalWrite(CANISTER, HIGH); //turn off canister filter pump
    digitalWrite(DRAINVALVE, LOW); //turn on drain valve
    lcd.setCursor(0,3); // bottom line of lcd
    lcd.print("Draining Water   "); //display message
    delay(10000);
  }

TANKLOWState = digitalRead(TANKLOW); //read the tanklow sensor state
if(TANKLOWState !=   LastTANKLOWState);  //has it changed since last time?
{
if(TANKLOWState == LOW){
 
    digitalWrite(DRAINVALVE, HIGH); //turn off drainvalve
  
    lcd.setCursor(0,3);
    lcd.print("Starting Refill");
    digitalWrite(TOPUPPUMP, LOW); //turn on top up pump
  }
LastTANKLOWState = TANKLOWState;
}

TANKFULLState = digitalRead(TANKFULL); //read the tanklow sensor state
if(TANKFULLState !=   LastTANKFULLState);  //has it changed since last time?
{
if(TANKFULLState == HIGH)
  {
    digitalWrite(TOPUPPUMP, HIGH); //turn off top up pump
  lcd.setCursor(0,3);
  lcd.print("TANK FULL        ");
  digitalWrite(CANISTER, LOW); //turn on canister filter pump
  }
else if(TANKFULLState == LOW)
 {digitalWrite(CANISTER, HIGH); //turn off canister filter
  lcd.setCursor(0,3);
  lcd.print("TANK LOW    ");
  }
  
}
LastTANKFULLState = TANKFULLState;
}
/* ( THE END ) */

    LastTANKLOWState = TANKLOWState;Should not be conditional on anything. The code must save the previous state every time before reading the current state.

I suggest that you move it to immediately before you read the state again as it makes your intentions clearer, and do the same for LastTANKFULLState as well.

done as suggested but it is still oing the same thing. Also I notice the TANKLOW actions of turning off DRAINVALVE and turning on TOPUPPUMP, will only occur if the TANKFULL sensor is LOW. This works fine as the TANKLOW sensor should never go LOW without the TANKFULL sensor also being LOW, but may be a clue as to whats going wrong? it seems the TANKLOW actions require TANKFULL to be LOW when they shouldnt depend on each other as far as I can see in the code?

Updated code:

#include <Wire.h>  // Comes with Arduino IDE
#include <LiquidCrystal_I2C.h> //For LCD


/*-----( Declare Constants )-----*/
/*-----( Declare objects )-----*/
// set the LCD address to 0x27 for a 20 chars 4 line display
// Set the pins on the I2C chip used for LCD connections:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address


//temperature probes
#include <OneWire.h>
#include <DallasTemperature.h>
//adds libraries for the temp sensors
/*-----( Declare Variables )-----*/
// temp sensor Data wire is plugged into pin 2 on the Arduino
#define ONE_WIRE_BUS 2

// Setup a oneWire instance to communicate with any OneWire devices
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);


//RELAY setup
#define BUTTON 4 //water change button
#define DRAINVALVE 7 //Solenoid valve to drain main tank
#define TOPUPPUMP 8 //Pump to top up main tank from top up tank
#define CANISTER 9 //Canister Filter pump Relay
#define TOPUPHEATER 10 //HEater for Top Up Tank

const int TANKFULL = 5; //define sensor pin for top float switch
const int TANKLOW = 6; //define sensor pin for low float switch

int TANKFULLState = 0; //current state of tank full sensor
int LastTANKFULLState = 0; //previous state of tank full sensor
int TANKLOWState = 0; //current state of tank low sensor
int LastTANKLOWState = 0; //previous state of tank low sensor

void setup()   /*----( SETUP: RUNS ONCE )----*/
{
  Serial.begin(9600);  // Used to type in characters

//set pins as inputs for float switches and button, no need for external resistor
pinMode(BUTTON, INPUT_PULLUP);
pinMode(TANKFULL, INPUT_PULLUP);
pinMode(TANKLOW, INPUT_PULLUP);

  //set pins as output for relays
  pinMode(DRAINVALVE, OUTPUT);
  pinMode(TOPUPPUMP, OUTPUT);
  pinMode(CANISTER, OUTPUT);
  pinMode(TOPUPHEATER, OUTPUT);

  //sets default relay states
  digitalWrite(DRAINVALVE, HIGH);
  digitalWrite(TOPUPPUMP, HIGH);
  digitalWrite(CANISTER, LOW);
  digitalWrite(TOPUPHEATER, HIGH);
  //starts with canister on, all other relays off


  lcd.begin(20, 4);        // initialize the lcd for 20 chars 4 lines, turn on backlight

  // ------- Quick 3 blinks of backlight  -------------
  for (int i = 0; i < 3; i++)
  {
    lcd.backlight();
    delay(100);
    lcd.noBacklight();
    delay(100);
  }
  lcd.backlight(); // finish with backlight on

  //-------- Write characters on the display ------------------
  // NOTE: Cursor Position: Lines and Characters start at 0
  lcd.setCursor(3, 0); //Start at character 4 on line 0
  lcd.print("Starting...");
  delay(1000);




}/*--(end setup )---*/


void loop()   /*----( LOOP: RUNS CONSTANTLY )----*/


{
  // call sensors.requestTemperatures() to issue a global temperature
  // request to all devices on the bus
  /********************************************************************/

  sensors.requestTemperatures(); // Send the command to get temperature readings

  /********************************************************************/
  lcd.setCursor(0, 0); //Start at character 4 on line 0
  lcd.print("Tank Temp:");
  lcd.print(sensors.getTempCByIndex(0)); // Why "byIndex"?
  // You can have more than one DS18B20 on the same bus.
  // 0 refers to the first IC on the wire
  lcd.setCursor(0, 1); //Start at character 4 on line 0
  lcd.print("Topup Temp:");
  lcd.print(sensors.getTempCByIndex(1)); // Why "byIndex"?
  // You can have more than one DS18B20 on the same bus.
  // 0 refers to the first IC on the wire
  delay(500);

  if(digitalRead(BUTTON) == LOW) // water vhange button pressed
  {
    digitalWrite(CANISTER, HIGH); //turn off canister filter pump
    digitalWrite(DRAINVALVE, LOW); //turn on drain valve
    lcd.setCursor(0,3); // bottom line of lcd
    lcd.print("Draining Water   "); //display message
    delay(10000);
  }
LastTANKLOWState = TANKLOWState;
TANKLOWState = digitalRead(TANKLOW); //read the tanklow sensor state
if(TANKLOWState !=   LastTANKLOWState);  //has it changed since last time?
{
if(TANKLOWState == LOW){
 
    digitalWrite(DRAINVALVE, HIGH); //turn off drainvalve
  
    lcd.setCursor(0,3);
    lcd.print("Starting Refill");
    digitalWrite(TOPUPPUMP, LOW); //turn on top up pump
  }

}
LastTANKFULLState = TANKFULLState;
TANKFULLState = digitalRead(TANKFULL); //read the tanklow sensor state
if(TANKFULLState !=   LastTANKFULLState);  //has it changed since last time?
{
if(TANKFULLState == HIGH)
  {
    digitalWrite(TOPUPPUMP, HIGH); //turn off top up pump
  lcd.setCursor(0,3);
  lcd.print("TANK FULL        ");
  digitalWrite(CANISTER, LOW); //turn on canister filter pump
  }
else if(TANKFULLState == LOW)
 {digitalWrite(CANISTER, HIGH); //turn off canister filter
  lcd.setCursor(0,3);
  lcd.print("TANK LOW    ");
  }
  
}

}
/* ( THE END ) */

Many thanks

Draw a state diagram with all the transitions labelled with the actions they should trigger, and mark any that should never happen as error. (for instance tank both full and empty at the same time)

Your code should simply do the corresponding action(s) for each transition and also log any errors and go a safe state and stop...

Name your states:

define TANK_FILLING 1

define TANK_DRAINING 2

define TANK_IDLE 3

define ERROR 0

And use these names in the code so its readable.

Its a great idea for all your actions to be well-named functions:

void drain_pump_on () {...} void drain_pump_off ()

and so forth - the code then reads easily, therefore mistakes are much less likely. And you can test each and every action function independently first....

For instance part of your code might be

if (state == TANK_IDLE and tank_full()) { if (tank_empty()) make_tank_safe_and_stop () ; // catch error case drain_pump_on () ; state = TANK_DRAINING ; }

Thanks MarkT, I have just been reading up on states and got it to work perfectly, have also tidied up my code a lot!! It is now working perfectly to perform a water change. I will now get to work on incorporating the RTC into the code to do automatic changes!

New improved code:

#include <Wire.h>
#include <LiquidCrystal_I2C.h> //For LCD
#include <OneWire.h> // for temp probes
#include <DallasTemperature.h> //for temp probes

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address
#define ONE_WIRE_BUS 2 // temp sensor Data wire is plugged into pin 2 on the Arduino
OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices
DallasTemperature sensors(&oneWire); // Pass our oneWire reference to Dallas Temperature.

//RELAY setup
#define BUTTON 4 //water change button pin 4
#define DRAINVALVE 7 //Solenoid valve to drain main tank pin 7
#define TOPUPPUMP 8 //Pump to top up main tank from top up tank pin 8
#define CANISTER 9 //Canister Filter pump Relay pin 9
#define TOPUPHEATER 10 //HEater for Top Up Tank pin 10

//States setup
#define TANK_IDLE 0
#define TANK_DRAINING 1
#define TANK_FILLING 2

// Sensors Setup
#define TOPFLOAT  5 //define sensor pin for top float switch
#define BOTTOMFLOAT 6 //define sensor pin for low float switch


void setup()   /*----( SETUP: RUNS ONCE )----*/
{
  Serial.begin(9600);  // Used to type in characters

//set pins as inputs for float switches and button, no need for external resistor
pinMode(BUTTON, INPUT_PULLUP);
pinMode(TOPFLOAT, INPUT_PULLUP);
pinMode(BOTTOMFLOAT, INPUT_PULLUP);

  //set pins as output for relays
  pinMode(DRAINVALVE, OUTPUT);
  pinMode(TOPUPPUMP, OUTPUT);
  pinMode(CANISTER, OUTPUT);
  pinMode(TOPUPHEATER, OUTPUT);

  //sets default relay states
  digitalWrite(DRAINVALVE, HIGH);
  digitalWrite(TOPUPPUMP, HIGH);
  digitalWrite(CANISTER, LOW);
  digitalWrite(TOPUPHEATER, HIGH);
  //starts with canister on, all other relays off

  lcd.begin(20, 4);        // initialize the lcd for 20 chars 4 lines, turn on backlight

}/*--(end setup )---*/


void loop()   /*----( LOOP: RUNS CONSTANTLY )----*/


{


  sensors.requestTemperatures(); // Send the command to get temperature readings

  lcd.setCursor(0, 0); //Start at character 4 on line 0
  lcd.print("Tank Temp:");
  lcd.print(sensors.getTempCByIndex(0)); 
  lcd.setCursor(0, 1); //Start at character 4 on line 0
  lcd.print("Topup Temp:");
  lcd.print(sensors.getTempCByIndex(1)); 
  // You can have more than one DS18B20 on the same bus.
  // 0 refers to the first IC on the wire

static int state = TANK_IDLE; // initial state is the idle state
static unsigned long ts;  // To store the "current" time in for delays.

switch (state)
{
  case TANK_IDLE:
    digitalWrite(DRAINVALVE, HIGH);
  digitalWrite(TOPUPPUMP, HIGH);
  digitalWrite(CANISTER, LOW);
  digitalWrite(TOPUPHEATER, HIGH);
   lcd.setCursor(0,3); // bottom line of lcd
    lcd.print("Tank Idle    "); //display message
       if(digitalRead(BUTTON) == LOW) 
       {state=1;}
    break;
  
  case TANK_DRAINING:
  
    digitalWrite(DRAINVALVE, LOW);
  digitalWrite(TOPUPPUMP, HIGH);
  digitalWrite(CANISTER, HIGH);
  digitalWrite(TOPUPHEATER, HIGH);
   lcd.setCursor(0,3); // bottom line of lcd
    lcd.print("Draining Tank   "); //display message
      if(digitalRead(BOTTOMFLOAT) == LOW) 
      {state = 2;}
    break;
  
  case TANK_FILLING:
    digitalWrite(DRAINVALVE, HIGH);
  digitalWrite(TOPUPPUMP, LOW);
  digitalWrite(CANISTER, HIGH);
  digitalWrite(TOPUPHEATER, HIGH);
   lcd.setCursor(0,3); // bottom line of lcd
    lcd.print("Filling Tank   "); //display message
    if(digitalRead(TOPFLOAT) == HIGH) 
    {state = 0;}
    break;
}
}
       state = 1;
       state = 2;
       state = 0;

Why not make the code more readable by using the state names that you set up instead of anonymous numbers ?

Good Point! done…

#include <Wire.h>
#include <LiquidCrystal_I2C.h> //For LCD
#include <OneWire.h> // for temp probes
#include <DallasTemperature.h> //for temp probes

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address
#define ONE_WIRE_BUS 2 // temp sensor Data wire is plugged into pin 2 on the Arduino
OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices
DallasTemperature sensors(&oneWire); // Pass our oneWire reference to Dallas Temperature.

//RELAY setup
#define BUTTON 4 //water change button pin 4
#define DRAINVALVE 7 //Solenoid valve to drain main tank pin 7
#define TOPUPPUMP 8 //Pump to top up main tank from top up tank pin 8
#define CANISTER 9 //Canister Filter pump Relay pin 9
#define TOPUPHEATER 10 //HEater for Top Up Tank pin 10

//States setup
#define TANK_IDLE 0
#define TANK_DRAINING 1
#define TANK_FILLING 2

// Sensors Setup
#define TOPFLOAT  5 //define sensor pin for top float switch
#define BOTTOMFLOAT 6 //define sensor pin for low float switch


void setup()   /*----( SETUP: RUNS ONCE )----*/
{
  Serial.begin(9600);  // Used to type in characters

//set pins as inputs for float switches and button, no need for external resistor
pinMode(BUTTON, INPUT_PULLUP);
pinMode(TOPFLOAT, INPUT_PULLUP);
pinMode(BOTTOMFLOAT, INPUT_PULLUP);

  //set pins as output for relays
  pinMode(DRAINVALVE, OUTPUT);
  pinMode(TOPUPPUMP, OUTPUT);
  pinMode(CANISTER, OUTPUT);
  pinMode(TOPUPHEATER, OUTPUT);

  //sets default relay states
  digitalWrite(DRAINVALVE, HIGH);
  digitalWrite(TOPUPPUMP, HIGH);
  digitalWrite(CANISTER, LOW);
  digitalWrite(TOPUPHEATER, HIGH);
  //starts with canister on, all other relays off

  lcd.begin(20, 4);        // initialize the lcd for 20 chars 4 lines, turn on backlight

}/*--(end setup )---*/


void loop()   /*----( LOOP: RUNS CONSTANTLY )----*/


{


  sensors.requestTemperatures(); // Send the command to get temperature readings

  lcd.setCursor(0, 0); //Start at character 4 on line 0
  lcd.print("Tank Temp:");
  lcd.print(sensors.getTempCByIndex(0)); 
  lcd.setCursor(0, 1); //Start at character 4 on line 0
  lcd.print("Topup Temp:");
  lcd.print(sensors.getTempCByIndex(1)); 
  // You can have more than one DS18B20 on the same bus.
  // 0 refers to the first IC on the wire

static int state = TANK_IDLE; // initial state is the idle state
static unsigned long ts;  // To store the "current" time in for delays.

switch (state)
{
  case TANK_IDLE:
    digitalWrite(DRAINVALVE, HIGH);
  digitalWrite(TOPUPPUMP, HIGH);
  digitalWrite(CANISTER, LOW);
  digitalWrite(TOPUPHEATER, HIGH);
   lcd.setCursor(0,3); // bottom line of lcd
    lcd.print("Tank Idle    "); //display message
       if(digitalRead(BUTTON) == LOW) 
       {state=TANK_DRAINING;}
    break;
  
  case TANK_DRAINING:
  
    digitalWrite(DRAINVALVE, LOW);
  digitalWrite(TOPUPPUMP, HIGH);
  digitalWrite(CANISTER, HIGH);
  digitalWrite(TOPUPHEATER, HIGH);
   lcd.setCursor(0,3); // bottom line of lcd
    lcd.print("Draining Tank   "); //display message
      if(digitalRead(BOTTOMFLOAT) == LOW) 
      {state = TANK_FILLING;}
    break;
  
  case TANK_FILLING:
    digitalWrite(DRAINVALVE, HIGH);
  digitalWrite(TOPUPPUMP, LOW);
  digitalWrite(CANISTER, HIGH);
  digitalWrite(TOPUPHEATER, HIGH);
   lcd.setCursor(0,3); // bottom line of lcd
    lcd.print("Filling Tank   "); //display message
    if(digitalRead(TOPFLOAT) == HIGH) 
    {state = TANK_IDLE;}
    break;
}
}