Built-in project · Arduino Uno R3

Water level controller with float switches on an Arduino Uno

Two float switches keep a tank between its low and high marks with a pump on a relay; a stuck float or a pump running dry stops it and blinks red. The whole build, an Arduino Uno and 8 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate Runs in your browser. Free, and no account needed.

The parts retail for about $39. On Mokxi they're free.

Water level controller with float switches · Arduino Uno R3live0.000 s 0.00x
Drag the floats’ water levels up and down
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Water level controller: two float switches keep a tank between a low mark
// and a high mark, running a pump through a relay, with a fault light for
// readings that cannot be true and a pump that runs too long.
//
//   pin 2    the LOW float, near the bottom of the tank (other wire to GND)
//   pin 3    the HIGH float, near the top (other wire to GND)
//   pin 4    a reset button to GND
//   pin 7    the relay module's IN (high trigger, VCC 5V, GND)
//   pin 12   the red fault LED through 220 ohms
//   relay    COM to the battery's +, NO to the pump's +, the pump's - to the
//            battery's -
//            (the battery's - also to GND, sharing the Uno's ground)
//
// Each float is a reed switch: normally open, closed when the water lifts the
// float. With INPUT_PULLUP a pin reads LOW when its float is up (wet) and
// HIGH when it hangs down (dry).
//
// Two floats give the pump a gap to work in. It starts when the water falls
// below the LOW float and keeps running, past the LOW float, until the HIGH
// float lifts. One float would switch the pump on and off every time a ripple
// crossed it. Each float must read the same for DEBOUNCE_MS before it counts.
//
// Two faults stop the pump and blink the red LED until the button is pressed:
//
//   HIGH wet while LOW is dry: the water cannot be at the top and not at the
//     bottom, so a float is stuck or a wire is off.
//   The pump has run MAX_RUN_MS without reaching the top: the supply tank is
//     empty (a pump running dry is ruined), or the water is leaking out.
//
// The tank does not fill in the simulator: the pump runs, and you move the
// floats' sliders to say where the water is. Under 30 mm a float hangs dry,
// over 30 mm it lifts.
//
// A float switch's contact is a signal contact, half an amp at most. The pump
// is on the relay, never on the float.

const int LOW_FLOAT = 2;
const int HIGH_FLOAT = 3;
const int RESET_BUTTON = 4;
const int RELAY_PIN = 7;
const int FAULT_LED = 12;

const unsigned long DEBOUNCE_MS = 500;
const unsigned long MAX_RUN_MS = 60000;

// A float that must hold its reading for DEBOUNCE_MS before it counts.
struct Float {
  int pin;
  bool wet;
  bool raw;
  unsigned long since;
};

Float lowFloat = {LOW_FLOAT, false, false, 0};
Float highFloat = {HIGH_FLOAT, false, false, 0};

bool pumping = false;
unsigned long pumpStarted = 0;
const char *fault = 0;

void readFloat(Float &f, unsigned long now) {
  bool r = digitalRead(f.pin) == LOW;  // LOW is the float lifted: wet
  if (r != f.raw) {
    f.raw = r;
    f.since = now;
  }
  if (now - f.since >= DEBOUNCE_MS) f.wet = f.raw;
}

void pump(bool on, unsigned long now) {
  if (on == pumping) return;
  pumping = on;
  pumpStarted = now;
  digitalWrite(RELAY_PIN, on ? HIGH : LOW);
  Serial.println(on ? "pump ON: filling" : "pump OFF");
}

void stopFor(const char *why, unsigned long now) {
  pump(false, now);
  fault = why;
  Serial.print("FAULT: ");
  Serial.println(why);
}

void setup() {
  pinMode(LOW_FLOAT, INPUT_PULLUP);
  pinMode(HIGH_FLOAT, INPUT_PULLUP);
  pinMode(RESET_BUTTON, INPUT_PULLUP);
  digitalWrite(RELAY_PIN, LOW);
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(FAULT_LED, OUTPUT);
  Serial.begin(9600);
  Serial.println("Water level controller");
  // Take the floats as they are at power-up, with no wait.
  lowFloat.wet = lowFloat.raw = digitalRead(LOW_FLOAT) == LOW;
  highFloat.wet = highFloat.raw = digitalRead(HIGH_FLOAT) == LOW;
}

void loop() {
  unsigned long now = millis();
  readFloat(lowFloat, now);
  readFloat(highFloat, now);

  if (fault) {
    digitalWrite(FAULT_LED, (now / 250) % 2);
    if (digitalRead(RESET_BUTTON) == LOW) {
      fault = 0;
      digitalWrite(FAULT_LED, LOW);
      Serial.println("reset");
    }
    return;
  }

  if (highFloat.wet && !lowFloat.wet) {
    stopFor("floats disagree: HIGH is wet, LOW is dry", now);
  } else if (highFloat.wet) {
    pump(false, now);
  } else if (!lowFloat.wet) {
    pump(true, now);
  }
  // Between the floats the pump carries on doing what it was doing.

  if (pumping && now - pumpStarted >= MAX_RUN_MS) {
    stopFor("pumped 60 s without reaching the top", now);
  }
  delay(20);
}

Parts list

10 parts, plus the jumper wires. They retail for about $39; on Mokxi they're free.

How it is wired

11 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • Arduino Uno R3 pin 12; Resistor, 220 Ω pin 1
  • Arduino Uno R3 pin 7; Relay module pin IN
  • Arduino Uno R3 pin 4; Pushbutton pin 1a; Pushbutton pin 1b
  • Arduino Uno R3 pin 3; Float switch, ZP4510 (2) pin 1
  • Arduino Uno R3 pin 2; Float switch, ZP4510 (1) pin 1
  • Arduino Uno R3 pin 5V; Relay module pin VCC
  • Ground: Arduino Uno R3 pin GND; Float switch, ZP4510 (1) pin 2; Float switch, ZP4510 (2) pin 2; Pushbutton pin 2a; Pushbutton pin 2b; LED, red pin C; Relay module pin GND
  • Resistor, 220 Ω pin 2; LED, red pin A
  • Relay module pin NO; Mini water pump pin V+
  • Relay module pin COM; Battery: AA, coin cell, LiPo pin V+
  • Ground: Battery: AA, coin cell, LiPo pin V-; Mini water pump pin V-

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.