Maintaining a healthy aquarium requires regular water changes, but manually siphoning water, mixing new water, and refilling the tank is time-consuming and easy to forget. In this tutorial, I’ll show you how to build a Python‑based automated aquarium water change system that handles the entire process—draining old water, preparing fresh water with proper temperature and chemistry, and refilling the tank—all with just a few lines of code and basic hardware.
Manual water changes are prone to human error:
A Python automation system solves these issues by standardizing the process, running on a schedule, and validating water conditions before any changes—keeping your aquatic life happy and healthy with minimal effort.
|
Component |
Purpose |
Budget Estimate |
|
Raspberry Pi (3/4/Zero) |
The “brain” to run Python code |
$30–$50 |
|
Submersible Water Pumps |
2 pumps (1 for draining, 1 for refilling) |
$15–$25 each |
|
Temperature Sensor (DS18B20) |
Monitor fresh water temperature (requires 1‑Wire enabled) |
$5–$10 |
|
Float Sensors |
Prevent over‑draining the tank or over‑filling during refilling |
$8–$12 each |
|
Relay Module (5V) |
Control pumps (Python cannot drive pumps directly) |
$5–$8 |
|
Power Supply |
Power Pi (5V/2.5A) and pumps |
$10–$15 |
|
Tubing & Fittings |
Connect pumps to tank and fresh water reservoir |
$10–$20 |
|
Water Conditioner/Salt |
For treating fresh water (as needed) |
$5–$10 |
RPi.GPIO, schedule, time, ds18b20Install the required packages:
pip install RPi.GPIO schedule ds18b20
The core logic of the system is straightforward:
raspi-config)This code includes safety checks, scheduled runs, and automatic level control.
import RPi.GPIO as GPIO
import schedule
import time
from ds18b20 import DS18B20
# --------------------------
# GPIO Pin Configuration
# --------------------------
DRAIN_PUMP_PIN = 17
REFILL_PUMP_PIN = 27
LOW_LEVEL_SENSOR_PIN = 22
HIGH_LEVEL_SENSOR_PIN = 23
FRESH_WATER_SENSOR_PIN = 24
TEMP_SENSOR = DS18B20()
TARGET_TEMP = 25.0
TEMP_TOLERANCE = 1.0
# --------------------------
# GPIO Setup
# --------------------------
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(DRAIN_PUMP_PIN, GPIO.OUT)
GPIO.setup(REFILL_PUMP_PIN, GPIO.OUT)
GPIO.setup(LOW_LEVEL_SENSOR_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(HIGH_LEVEL_SENSOR_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(FRESH_WATER_SENSOR_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# --------------------------
# Core Functions
# --------------------------
def read_temperature():
try:
temp = TEMP_SENSOR.get_temperature()
return round(temp, 1)
except Exception as e:
print(f"Error reading temperature: {e}")
return None
def check_safety_conditions():
if GPIO.input(FRESH_WATER_SENSOR_PIN) == 0:
print("ERROR: Fresh water reservoir is empty!")
return False
if GPIO.input(LOW_LEVEL_SENSOR_PIN) == 0:
print("ERROR: Tank water level is already too low!")
return False
fresh_temp = read_temperature()
if fresh_temp is None:
print("ERROR: Failed to read temperature!")
return False
if not (TARGET_TEMP - TEMP_TOLERANCE <= fresh_temp <= TARGET_TEMP + TEMP_TOLERANCE):
print(f"ERROR: Fresh water temp ({fresh_temp}°C) is outside target range!")
return False
return True
def drain_water():
print("Starting drain phase...")
GPIO.output(DRAIN_PUMP_PIN, GPIO.HIGH)
count = 0
while GPIO.input(LOW_LEVEL_SENSOR_PIN) == 1:
time.sleep(1)
count += 1
if count % 5 == 0:
print("Draining...")
GPIO.output(DRAIN_PUMP_PIN, GPIO.LOW)
print("Drain phase complete!")
def refill_water():
print("Starting refill phase...")
GPIO.output(REFILL_PUMP_PIN, GPIO.HIGH)
count = 0
while GPIO.input(HIGH_LEVEL_SENSOR_PIN) == 1:
time.sleep(1)
count += 1
if count % 5 == 0:
print("Refilling...")
GPIO.output(REFILL_PUMP_PIN, GPIO.LOW)
print("Refill phase complete!")
def full_water_change():
print("=== Starting Automated Water Change ===")
if not check_safety_conditions():
print("Aborting water change due to safety issues!")
return
drain_water()
print("Adding water conditioner / salt (10s delay)...")
time.sleep(10)
refill_water()
final_temp = read_temperature()
print("=== Water Change Complete! ===")
print(f"Final tank temp: {final_temp}°C")
print("Pumps off. All sensors normal.")
def cleanup():
GPIO.output(DRAIN_PUMP_PIN, GPIO.LOW)
GPIO.output(REFILL_PUMP_PIN, GPIO.LOW)
GPIO.cleanup()
print("GPIO cleaned up safely.")
# --------------------------
# Schedule & Run
# --------------------------
if __name__ == "__main__":
try:
schedule.every().sunday.at("09:00").do(full_water_change)
print("Automated Aquarium Water Change System Running...")
print("Scheduled: Every Sunday at 09:00")
print("Press Ctrl+C to stop.")
while True:
schedule.run_pending()
time.sleep(60)
except KeyboardInterrupt:
print("\nSystem stopped by user.")
cleanup()
except Exception as e:
print(f"Unexpected error: {e}")
cleanup()
We use internal pull‑up resistors to avoid false sensor readings, which is critical for stable operation.
The system verifies:
You can easily change how often the system runs:
schedule.every().day.at("18:00")schedule.every(7).days.do(full_water_change)The cleanup() function ensures pumps turn off if the script stops, preventing flooding or burnout.
TARGET_TEMP and TEMP_TOLERANCE for your fish.This Python & Raspberry Pi system fully automates aquarium water changes with reliable safety controls. The workflow is:
Safety check → Drain → Treat water → Refill
By running on a fixed schedule, you eliminate human error and save hours of maintenance. Always test thoroughly before leaving the system unattended.
Happy automating!