IoT Hydroponics and Aquaculture_The TDS sensor
If you’ve ever tried growing plants in a hydroponic system or keeping fish in an aquaculture tank, you already know one thing: water quality is king. It’s like the air we breathe—get it wrong and everything goes downhill fast. That’s where the magic of DIY IoT comes in. With just a few low-cost components like an Arduino Nano, ESP-01 WiFi module, turbidity sensor, TDS sensor, and pH sensor, you can build a smart water monitoring system.
And here’s the best part: you don’t have to stand over your tank or plants 24/7. Thanks to IoT, your water readings—pH, total dissolved solids (TDS), and turbidity—can be tracked remotely on your smartphone using the Blynk dashboard.
In this guide, I’ll walk you through how we built a portable, battery-powered, IoT-enabled water quality monitor. We’ll talk about the components, how they fit together, why they matter, and where you can take the project next.
Hydroponics and aquaculture have one thing in common: everything depends on the condition of water. If the pH level shifts, plants stop absorbing nutrients. If turbidity increases, fish suffer from stress. If TDS rises too high, both systems struggle.
IoT makes it easy to monitor these parameters in real-time. Instead of waiting for plants to wilt or fish to act strangely, you get instant alerts and live data streamed to your phone. Think of it as having a lab assistant working around the clock, checking water quality while you sleep.
The Arduino Nano acts as the main controller. It reads data from all the sensors, processes it, and then communicates with the ESP-01 for IoT functions. It’s small, affordable, and easy to program in Arduino IDE.
The ESP8266-01 (ESP-01) is what connects your system to the internet. Without it, your readings would stay trapped inside the Arduino. With it, the data goes straight to the Blynk IoT dashboard, letting you see your water quality on your phone from anywhere.
The DS18B20 Temperature sensor module was also very useful in determining the temperature of the water solution at any given time. Since it was already waterproofed encased. we immersed it into the container as shown in the picture above.
We used two 3.7V Li-ion batteries in series and a DC-DC buck converter to step down the voltage to about 3.7V for the ESP-01. This way, the setup is portable and independent of wall sockets.
Imagine building a LEGO set, but instead of colorful bricks, you have sensors, wires, and boards. The veroboard acts like the foundation. The Arduino Nano plugs into female headers, which means you can replace it without re-soldering. The ESP-01 connects via pins, powered through the buck converter.
Wiring is neat with JST connectors, which makes replacing sensors a plug-and-play experience. The SPST switch ensures you don’t drain the batteries accidentally.
On the outside, the pattress box keeps everything protected while the water sample sits in a transparent container, making testing simple and safe.
You may also like to read this: DIY Weather Station with DHT11 Sensor.
All the sensors talk to the Arduino Nano, and we write a program (sketch) on Arduino IDE to read their values. It then sends this data to the ESP-01 using Serial Communication protocol.
// Include the libraries we need
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SoftwareSerial.h>
#include "DFRobot_PH.h"
#include <EEPROM.h>
SoftwareSerial mySerial(6, 5); // RX, TX
// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUS A2
// 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);
//for turbidity
unsigned char str[5] = {}; //Serial receives data
unsigned char col;
unsigned int distance = 0;
unsigned char a[5] = { 0x18, 0x05, 0x00, 0x01, 0x0D};
//for tds sensor
#define TdsSensorPin A1
#define VREF 5.0 // analog reference voltage(Volt) of the ADC
#define SCOUNT 30 // sum of sample point
int analogBuffer[SCOUNT]; // store the analog value in the array, read from ADC
int analogBufferTemp[SCOUNT];
int analogBufferIndex = 0,copyIndex = 0;
float averageVoltage = 0,temperature = 25;
float tdsValue;
//for pH sensor
#define PH_PIN A3
float voltage,phValue = 25;
DFRobot_PH ph;
float tempC;
void setup() {
Serial.begin(115200);
mySerial.begin(9600);
// Start up the library
sensors.begin();
pinMode(TdsSensorPin,INPUT);
ph.begin();
}
float tdsSensor(){
static unsigned long analogSampleTimepoint = millis();
if(millis()-analogSampleTimepoint > 40U) //every 40 milliseconds,read the analog value from the ADC
{
analogSampleTimepoint = millis();
analogBuffer[analogBufferIndex] = analogRead(TdsSensorPin); //read the analog value and store into the buffer
analogBufferIndex++;
if(analogBufferIndex == SCOUNT)
analogBufferIndex = 0;
}
static unsigned long printTimepoint = millis();
if(millis()-printTimepoint > 800U) {
printTimepoint = millis();
for(copyIndex=0;copyIndex<SCOUNT;copyIndex++)
analogBufferTemp[copyIndex]= analogBuffer[copyIndex];
averageVoltage = getMedianNum(analogBufferTemp,SCOUNT) * (float)VREF / 1024.0; // read the analog value more stable by the median filtering algorithm, and convert to voltage value
float compensationCoefficient=1.0+0.02*(temperature-25.0); //temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0));
float compensationVolatge=averageVoltage/compensationCoefficient; //temperature compensation
tdsValue=(133.42*compensationVolatge*compensationVolatge*compensationVolatge - 255.86*compensationVolatge*compensationVolatge + 857.39*compensationVolatge)*0.5; //convert voltage value to tds value
//Serial.print("voltage:");
//Serial.print(averageVoltage,2);
//Serial.print("V ");
// Serial.print("TDS Value:");
// Serial.print(tdsValue,0);
// Serial.println("ppm");
}
return tdsValue;
}
int getMedianNum(int bArray[], int iFilterLen){
int bTab[iFilterLen];
for (byte i = 0; i<iFilterLen; i++)
bTab[i] = bArray[i];
int i, j, bTemp;
for (j = 0; j < iFilterLen - 1; j++)
{
for (i = 0; i < iFilterLen - j - 1; i++)
{
if (bTab[i] > bTab[i + 1])
{
bTemp = bTab[i];
bTab[i] = bTab[i + 1];
bTab[i + 1] = bTemp;
}
}
}
if ((iFilterLen & 1) > 0)
bTemp = bTab[(iFilterLen - 1) / 2];
else
bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
return bTemp;
}
float tempSensor(){
// call sensors.requestTemperatures() to issue a global temperature
// request to all devices on the bus
// Serial.print("Requesting temperatures...");
sensors.requestTemperatures(); // Send the command to get temperatures
// Serial.println("DONE");
// After we got the temperatures, we can print them here.
// We use the function ByIndex, and as an example get the temperature from the first sensor only.
tempC = sensors.getTempCByIndex(0);
// Check if reading was successful
if(tempC != DEVICE_DISCONNECTED_C)
{
// Serial.print("Temperature for the device 1 (index 0) is: ");
// Serial.println(tempC);
}
else {
// Serial.println("Error: Could not read temperature data");
}
return tempC;
}
float pHSensor(){
tempSensor();
static unsigned long timepoint = millis();
if(millis()-timepoint>1000U){ //time interval: 1s
timepoint = millis();
temperature = tempSensor(); // read your temperature sensor to execute temperature compensation
voltage = analogRead(PH_PIN)/1024.0*5000; // read the voltage
phValue = ph.readPH(voltage, temperature); // convert voltage to pH with temperature compensation
// Serial.print("temperature:");
// Serial.print(temperature, 1);
// Serial.print("^C pH:");
// Serial.println(phValue,2);
}
ph.calibration(voltage,temperature); // calibration p
return phValue, temperature;
}
int turbiditySensor(){
mySerial.write(a, 5);
while (!mySerial.available());
while (mySerial.available() > 0) //Detect if there is data on serial port
{
for (int i = 0; i < 5; i++)
{
str[i]=mySerial.read();
delay(5);
}
// Serial.println(str[3],DEC);
mySerial.flush();
}
return str[3];
}
void loop() {
tdsSensor();
pHSensor();
turbiditySensor();
// tempSensor();
Serial.print(tdsValue); Serial.print("A");
Serial.print(phValue); Serial.print("B");
Serial.print(tempC); Serial.print("C");
Serial.print(str[3]); Serial.print("D");
Serial.print("\n");
delay(500);
}
This Arduino code is designed to read data from various sensors and transmit that data via Serial communication to an ESP-01 module. The sensors include a temperature sensor, a TDS (Total Dissolved Solids) sensor, a pH sensor, and a turbidity sensor. Below is a detailed breakdown of the code.
This line initializes a software serial port on pins 6 (RX) and 5 (TX) for communication with the ESP-01.
The temperature sensor is connected to pin A2, and an instance of the OneWire and DallasTemperature classes is created for reading temperature data.
The code initializes several sensors:
The setup()
function initializes the serial communication at different baud rates for the Arduino and ESP-01, starts the temperature sensor library, and sets the TDS sensor pin as an input.
float tdsSensor() {
// Code to read and calculate TDS value
}
This function reads the analog value from the TDS sensor every 40 milliseconds, stores it in a buffer, and calculates the TDS value using a temperature compensation formula. The TDS value is derived from the voltage reading.
float tempSensor() {
// Code to read temperature
}
This function requests temperature readings from the Dallas temperature sensor and returns the temperature in Celsius.
void loop() {
tdsSensor();
pHSensor();
turbiditySensor();
Serial.print(tdsValue); Serial.print("A");
Serial.print(phValue); Serial.print("B");
Serial.print(tempC); Serial.print("C");
Serial.print(str[3]); Serial.print("D");
Serial.print("\n");
delay(500);
}
The loop()
function continuously reads data from the TDS, pH, and turbidity sensors. It then prints the values to the Serial monitor, appending specific letters to identify each value. The loop runs every 500 milliseconds.
#include <SoftwareSerial.h>
/*ESP-01 pins are GPIO, Tx is GPIO1 (here define as 1, code line 6),
*/#define rxPin 0 // GPIO0 is the pin next to the ESP-01 Rx pin. The Rx (GPIO3), the Tx (GPIO1)
#define txPin 2 //GPIO2
#define pumpVirtualPin 1
SoftwareSerial nodeMCU(rxPin, txPin);
#define BLYNK_TEMPLATE_ID "TMPL2bPdN-5lm"
#define BLYNK_TEMPLATE_NAME "IoT Aquaculture Monitoring Project"
#define BLYNK_AUTH_TOKEN "XODdqSnfZEzEC99weZMpRGt7-IY5XZjg"
/* Comment this out to disable prints and save space */#define BLYNK_PRINT Serial
int Button;
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "Galaxy A51 917E";
char pass[] = "tosin@345";
BlynkTimer timer;
char c;
String dataIn;
int8_t indexOfA, indexOfB,indexOfC, indexOfD;
String data1, data2, data3, data4;
BLYNK_WRITE(V3) {
Button = param.asInt();
if (Button==1){
digitalWrite(pumpVirtualPin, HIGH);
}
else if(Button==0){
digitalWrite(pumpVirtualPin, LOW);
}
}
void setup(){
// Debug console
Serial.begin(115200);
nodeMCU.begin(115200);
pinMode(pumpVirtualPin, OUTPUT);
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
// Setup a function to be called every second
//timer.setInterval(1000L, parse_data);
}
void recvData(){
while(nodeMCU.available() >0){
c = nodeMCU.read();
if( c == '\n'){
break;
}
else{
dataIn += c;
}
}
if(c == '\n'){
//Serial.println(c);
parse_data();
Serial.println("data 1= " + data1);
Serial.println("data 2= " + data2);
Serial.println("data 3= " + data3);
Serial.println("............................");
c = 0;
dataIn = "";
}
}
void loop(){
recvData();
Blynk.run();
timer.run();
}
void parse_data(){
indexOfA = dataIn.indexOf("A");
indexOfB = dataIn.indexOf("B");
indexOfC = dataIn.indexOf("C");
indexOfD = dataIn.indexOf("D");
data1 = dataIn.substring(0, indexOfA);
data2 = dataIn.substring(indexOfA+1, indexOfB);
data3 = dataIn.substring(indexOfB+1, indexOfC);
data4 = dataIn.substring(indexOfC+1, indexOfD);
float ch1 = data1.toFloat();
float ch2 = data2.toFloat();
float ch3 = data3.toFloat();
int ch4 = data4.toInt();
Blynk.virtualWrite(V0, ch1);
Blynk.virtualWrite(V1, ch2);
Blynk.virtualWrite(V2, ch3);
Blynk.virtualWrite(V3, ch4);
}
The Arduino code above shows how the ESP-01 receives the data read from Arduino Nano. And then sends this to the Blynk IoT dashboard for monitoring and real-time feedback to the user.
Here’s where things get fun. With Blynk, you can design your own dashboard—sliders, graphs, widgets—to monitor real-time values of pH, TDS, Temperature and turbidity. You can even set up notifications if the values cross safe thresholds. However, for this particular design, we didn’t see the need to include an in-app alerts.
We did design the Blynk dashboard to run on both PC and mobile platforms so that is can be used anywhere. The mobile version was looking different that is because we used radial gauges to monitoring their readings and measurements.
To know how to set up the Blynk 2.0 dashboard for a particular project, you can read this project IoT Light Bulb – Remote Control Light Bulb Blynk App.
You’ll need at least pH, TDS, turbidity, and temperature sensor, in our case, we used the DS18B20 Temperature sensor (waterproof type) For aquaculture, you might also add dissolved oxygen (DO) and ammonia sensors.
The Arduino sends sensor readings to the ESP-01 via serial communication. The ESP-01 then transmits this data over WiFi to the Blynk cloud server, which pushes it to your smartphone or PC as in our own case. Since we could take the monitoring readings and measurements either using our smart phone or using the Personal Computer (PC).
Yes. The turbidity sensor uses light scattering, while the TDS sensor measures electrical conductivity. Both provide analog signals the Arduino can read easily.
You can also read up on the topic: Smart Farming Solutions for Sustainable Agriculture Practices
You use a DC-DC buck converter to step down voltage from ~7.4V (two batteries in series) to ~3.7V–3.8V, which is safe for the ESP-01.
Assembling the project together is another feat. You would have to know how to connect and manage the cables for the project design. It would be advisable to find a way to make the batteries rechargeable.
Openings for the casing to ensure entry of the module sensors and to easily connect the USB programming cords to program the boards. This example is shown in the image above too.
Sensors don’t work straight out of the box like a plug-in kettle. They need calibration.
Once calibrated, dip the sensors in your transparent container of water and check the dashboard readings. We checked our sensor modules individually. As shown above, our non-contact turbidity sensor module was tested, so was the TDS and pH sensor module using different solutions.
Nothing’s perfect. Here are some issues we faced:
Use a step-down buck converter with Li-ion batteries. Alternatively, small 3.7V LiPo batteries with protection circuits work well.
The dashboard can show real-time charts, digital displays, and even push alerts if pH or TDS levels drift out of range.
This project is exactly that—affordable, DIY, and effective for small-scale growers.
The project doesn’t have to stop here. Some ideas:
Building your own DIY IoT hydroponic and aquaculture monitor isn’t just a fun project. It’s practical, cost-saving, and future-ready. Whether you’re into gardening, fish farming, or just tinkering with electronics, this setup gives you control over water quality at your fingertips.
It’s like having superpowers—you can “see” what’s happening in your water without dipping a finger in. And once you’ve tried it, you’ll never want to go back to guesswork.
Yes. The same sensors (pH, TDS, turbidity and Temperature) apply to both, though aquaculture may need extra sensors like dissolved oxygen.
It depends on WiFi usage and sensor power draw, but typically 6–12 hours of continuous operation. With optimization, it can last longer.
Yes, the ESP-01 relies on WiFi. But you could set up a local server if internet access is limited.
Absolutely. If you can follow simple wiring and upload sketches in Arduino IDE, you’re good to go.
Definitely. You can add temperature, DO, ammonia, or even automate pumps and feeders with relays.
Introduction The role of Food in Korean Movies The Role of Food in Korean Movies…
Introduction https://youtu.be/Dfv_-Rp0FEw Imagine being able to switch off your lights, monitor your room, or get…
Introduction Remember how your grandma tended her garden, cooked with local grains, and built with…
Have you ever wondered how a country as diverse and dynamic as Nigeria came to…
Have you ever wondered about the powerful symbols that adorn the heads of kings and…
Have you ever felt it? That nagging pressure, that pervasive idea that unless you've got…
This website uses cookies.