</>CoderDIY
← Back to list
Article

Build an Automated Aquarium Controller with ESP32

Author: CoderDIY··Đọc bằng tiếng Việt
🐠

Keeping an aquarium means feeding on a consistent schedule and keeping water temperature stable — both easy to miss when you’re traveling or away for a few days. An automated aquarium controller built around an ESP32 handles both: scheduled feeding via a servo, continuous water temperature monitoring with a waterproof sensor, and a Telegram alert the moment temperature crosses a safe threshold — all trackable remotely from your phone.

How it works

  1. The ESP32 keeps real-time sync via NTP (pulling accurate time from the internet) and compares it against pre-set feeding times (e.g., 8:00 and 18:00 daily).
  2. At the scheduled time, a servo mounted on the food hopper rotates a small angle to release a pre-measured amount of pellet food, then rotates back to the closed position.
  3. A waterproof DS18B20 temperature sensor continuously measures water temperature; if it drifts outside a safe range (say, below 22°C or above 30°C depending on the fish species), the ESP32 sends an alert via the Telegram Bot API.
  4. All state (current temperature, last feeding time) can be published over MQTT for display on a dashboard or in Home Assistant.
<rect x="10" y="20" width="140" height="60" rx="10" class="box" />
<text x="80" y="45" text-anchor="middle" class="label">Sync time (NTP)</text>
<text x="80" y="62" text-anchor="middle" class="label">check feed schedule</text>

<rect x="170" y="20" width="140" height="60" rx="10" class="box" />
<text x="240" y="45" text-anchor="middle" class="label">Read temperature</text>
<text x="240" y="62" text-anchor="middle" class="label">(DS18B20)</text>

<rect x="330" y="20" width="140" height="60" rx="10" class="box" />
<text x="400" y="45" text-anchor="middle" class="label">Feed time or</text>
<text x="400" y="62" text-anchor="middle" class="label">temp out of range?</text>

<rect x="490" y="20" width="140" height="60" rx="10" class="box" />
<text x="560" y="45" text-anchor="middle" class="label">Turn servo /</text>
<text x="560" y="62" text-anchor="middle" class="label">Send Telegram</text>

<line x1="150" y1="50" x2="170" y2="50" class="arrow" />
<line x1="310" y1="50" x2="330" y2="50" class="arrow" />
<line x1="470" y1="50" x2="490" y2="50" class="arrow" />

<path d="M 560 80 L 560 190 L 80 190 L 80 80" class="arrow" fill="none" />
<text x="320" y="210" text-anchor="middle" class="sublabel">Checked continuously, repeats every few seconds</text>
Main loop: continuously check feed time and water temperature, act immediately when a condition is met.

Parts list

  • An ESP32 (with WiFi, for NTP sync and sending Telegram messages)
  • An SG90 servo mounted on the pellet food hopper
  • A waterproof DS18B20 temperature sensor (1-Wire interface)
  • A 4.7kΩ resistor (pull-up for the DS18B20)
  • A food hopper, DIY or store-bought, with a rotating gate matching the servo shaft
  • A stable 5V power supply; keep the controller itself somewhere dry, away from tank moisture
<rect x="30" y="60" width="170" height="160" rx="10" class="box" />
<text x="115" y="145" text-anchor="middle" class="label">ESP32</text>

<rect x="440" y="30" width="170" height="80" rx="10" class="box" />
<text x="525" y="75" text-anchor="middle" class="label">Servo (feeder gate)</text>

<rect x="440" y="160" width="170" height="80" rx="10" class="box" />
<text x="525" y="195" text-anchor="middle" class="label">DS18B20</text>
<text x="525" y="212" text-anchor="middle" class="label">(in the water)</text>

<line x1="200" y1="70" x2="440" y2="70" class="wire" />
<text x="320" y="62" text-anchor="middle" class="sublabel">GPIO 13 → servo signal</text>
<circle cx="200" cy="70" r="4" class="pin" />
<circle cx="440" cy="70" r="4" class="pin" />

<line x1="200" y1="200" x2="440" y2="200" class="wire" />
<text x="320" y="192" text-anchor="middle" class="sublabel">GPIO 4 → DATA (1-Wire, via 4.7kΩ)</text>
<circle cx="200" cy="200" r="4" class="pin" />
<circle cx="440" cy="200" r="4" class="pin" />
The servo takes a PWM signal from GPIO 13; the DS18B20 uses 1-Wire on GPIO 4 with a 4.7kΩ pull-up resistor.

Sample code

#include <WiFi.h>
#include <time.h>
#include <ESP32Servo.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <UniversalTelegramBot.h>
#include <WiFiClientSecure.h>

const char* WIFI_SSID = "your-wifi";
const char* WIFI_PASS = "your-wifi-password";
const char* BOT_TOKEN = "TELEGRAM_BOT_TOKEN";
const char* CHAT_ID = "TELEGRAM_CHAT_ID";

Servo feederServo;
OneWire oneWire(4);
DallasTemperature tempSensor(&oneWire);
WiFiClientSecure secureClient;
UniversalTelegramBot bot(BOT_TOKEN, secureClient);

const int FEED_HOURS[] = {8, 18}; // feed at 8am and 6pm daily
int lastFedHour = -1;
const float TEMP_MIN = 22.0;
const float TEMP_MAX = 30.0;
bool alertSent = false;

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(500);

  configTime(0, 0, "pool.ntp.org"); // set to your local UTC offset
  feederServo.attach(13);
  feederServo.write(0); // closed position
  tempSensor.begin();
  secureClient.setInsecure();
}

void feedFish() {
  feederServo.write(90);  // open the gate, release food
  delay(500);
  feederServo.write(0);   // close it again
  bot.sendMessage(CHAT_ID, "Fed the fish at " + String(millis() / 1000), "");
}

void checkTemperature() {
  tempSensor.requestTemperatures();
  float tempC = tempSensor.getTempCByIndex(0);

  if ((tempC < TEMP_MIN || tempC > TEMP_MAX) && !alertSent) {
    bot.sendMessage(CHAT_ID, "ALERT: aquarium temperature is " + String(tempC) + " C", "");
    alertSent = true;
  } else if (tempC >= TEMP_MIN && tempC <= TEMP_MAX) {
    alertSent = false; // reset so a new alert fires if it drifts out again
  }
}

void loop() {
  struct tm timeInfo;
  if (getLocalTime(&timeInfo)) {
    for (int h : FEED_HOURS) {
      if (timeInfo.tm_hour == h && timeInfo.tm_min == 0 && lastFedHour != h) {
        feedFish();
        lastFedHour = h;
      }
    }
  }

  checkTemperature();
  delay(30000); // check every 30 seconds
}

Common pitfalls

  • The servo jamming on pellets caught in the mechanism — design the hopper’s outlet so it’s only wide enough for a small amount of food per rotation, keeping pellets out of the servo’s gears.
  • No debounce on alerts floods Telegram with repeated messages when temperature hovers right at the threshold — use the alertSent flag to only send one alert until the reading returns to normal.
  • Mounting the ESP32 too close to the water surface risks damage from humidity or splashes — only the waterproof DS18B20 sensor should sit in the tank; keep the ESP32 board mounted higher up, somewhere dry.
  • Losing WiFi while traveling prevents NTP sync and breaks the feeding schedule — consider adding an RTC module (DS3231) to keep accurate time even through brief network outages.

Extensions

Add a water level sensor to alert when topping off is needed, attach a small pump for scheduled partial water changes, or add an ESP32-CAM like the one in the ESP32-CAM smart doorbell post to check on your fish remotely from your phone.

Tags:#esp32#sensors#automation

Comments