Water Quality Monitoring System
Intermediate20/6/2026- Author: IoTSpark Maker

Water Quality Monitoring System

Build a water quality monitoring system that measures temperature, TDS, pH, and turbidity, shows an alert status, and can be extended to send real-time data over MQTT. Suited for monitoring fish tank, hydroponic, or water tank quality, or as a reference monitoring model. Note: readings from consumer-grade sensors do not replace certified testing equipment or lab analysis.

ESP32IoTWater QualityMQTTArduino IDETDSpHDS18B20
7 steps9 components
← Back to project
Detailed guide

Part 4: Programming the Water-Quality-Reading Firmware

Build the complete Arduino IDE program to read temperature, TDS, pH, and turbidity, and evaluate water status.

Updated 31/08/2026

Part 4: Programming the Water-Quality-Reading Firmware

In this part, we combine the sensors into a single complete program. The firmware will read the data, average multiple samples to filter noise, compute TDS, compute pH, compute relative turbidity, and evaluate water status.

4.1. Main Processing Flow

1. Read temperature from the DS18B20
2. Read TDS, pH, and turbidity voltage via the ADC
3. Filter the data by averaging multiple samples
4. Apply simple temperature compensation for TDS
5. Compute pH from a 2-point calibration
6. Compute relative turbidity as 0100%
7. Evaluate status: NORMAL / WARNING / DANGER / SENSOR_ERROR
8. Drive the LED and buzzer
9. Print the result to the Serial Monitor

4.2. Full Arduino IDE Code

#include <OneWire.h>
#include <DallasTemperature.h>

#define PIN_ONE_WIRE    4
#define PIN_TDS         34
#define PIN_PH          35
#define PIN_TURBIDITY   32

#define PIN_BUZZER      25
#define PIN_LED_GREEN   26
#define PIN_LED_RED     27

const float ADC_MAX = 4095.0;
const float ESP32_ADC_REF_VOLTAGE = 3.3;
const float TURBIDITY_DIVIDER_FACTOR = 1.5;

float PH7_VOLTAGE = 2.50;
float PH4_VOLTAGE = 3.00;

float CLEAR_WATER_VOLTAGE = 4.10;
float DIRTY_WATER_VOLTAGE = 2.50;

const float PH_MIN_NORMAL = 6.5;
const float PH_MAX_NORMAL = 8.5;
const float TDS_WARNING = 500.0;
const float TDS_DANGER = 1000.0;
const float TURBIDITY_WARNING = 50.0;
const float TURBIDITY_DANGER = 80.0;

OneWire oneWire(PIN_ONE_WIRE);
DallasTemperature tempSensor(&oneWire);

float readAdcVoltage(int pin) {
  const int sampleCount = 30;
  long total = 0;

  for (int i = 0; i < sampleCount; i++) {
    total += analogRead(pin);
    delay(5);
  }

  float rawAverage = total / (float)sampleCount;
  return rawAverage * ESP32_ADC_REF_VOLTAGE / ADC_MAX;
}

float readTemperatureC() {
  tempSensor.requestTemperatures();
  float temperatureC = tempSensor.getTempCByIndex(0);

  if (temperatureC < -50 || temperatureC > 125) {
    return NAN;
  }

  return temperatureC;
}

float calculateTds(float voltage, float temperatureC) {
  if (isnan(temperatureC)) {
    temperatureC = 25.0;
  }

  float compensationCoefficient = 1.0 + 0.02 * (temperatureC - 25.0);
  float compensationVoltage = voltage / compensationCoefficient;

  float tdsValue =
    (133.42 * compensationVoltage * compensationVoltage * compensationVoltage
    - 255.86 * compensationVoltage * compensationVoltage
    + 857.39 * compensationVoltage) * 0.5;

  if (tdsValue < 0) {
    tdsValue = 0;
  }

  return tdsValue;
}

float calculatePh(float voltage) {
  float slope = (7.0 - 4.0) / (PH7_VOLTAGE - PH4_VOLTAGE);
  float intercept = 7.0 - slope * PH7_VOLTAGE;
  return slope * voltage + intercept;
}

float calculateTurbidityPercent(float sensorVoltage) {
  float percent =
    (CLEAR_WATER_VOLTAGE - sensorVoltage)
    * 100.0
    / (CLEAR_WATER_VOLTAGE - DIRTY_WATER_VOLTAGE);

  if (percent < 0) percent = 0;
  if (percent > 100) percent = 100;

  return percent;
}

String evaluateWaterStatus(float temperatureC, float ph, float tds, float turbidityPercent) {
  if (isnan(temperatureC) || isnan(ph) || isnan(tds) || isnan(turbidityPercent)) {
    return "SENSOR_ERROR";
  }

  if (ph < 4.0 || ph > 11.0) {
    return "DANGER";
  }

  if (tds >= TDS_DANGER || turbidityPercent >= TURBIDITY_DANGER) {
    return "DANGER";
  }

  if (ph < PH_MIN_NORMAL || ph > PH_MAX_NORMAL || tds >= TDS_WARNING || turbidityPercent >= TURBIDITY_WARNING) {
    return "WARNING";
  }

  return "NORMAL";
}

void updateAlertOutput(String status) {
  if (status == "NORMAL") {
    digitalWrite(PIN_LED_GREEN, HIGH);
    digitalWrite(PIN_LED_RED, LOW);
    digitalWrite(PIN_BUZZER, LOW);
  } else if (status == "WARNING") {
    digitalWrite(PIN_LED_GREEN, LOW);
    digitalWrite(PIN_LED_RED, HIGH);
    digitalWrite(PIN_BUZZER, LOW);
  } else {
    digitalWrite(PIN_LED_GREEN, LOW);
    digitalWrite(PIN_LED_RED, HIGH);
    digitalWrite(PIN_BUZZER, HIGH);
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  analogReadResolution(12);
  analogSetAttenuation(ADC_11db);

  tempSensor.begin();

  pinMode(PIN_BUZZER, OUTPUT);
  pinMode(PIN_LED_GREEN, OUTPUT);
  pinMode(PIN_LED_RED, OUTPUT);

  digitalWrite(PIN_BUZZER, LOW);
  digitalWrite(PIN_LED_GREEN, LOW);
  digitalWrite(PIN_LED_RED, LOW);

  Serial.println("System started");
  Serial.println("ESP32 Water Quality Monitor");
}

void loop() {
  float temperatureC = readTemperatureC();
  float tdsVoltage = readAdcVoltage(PIN_TDS);
  float phVoltage = readAdcVoltage(PIN_PH);
  float turbidityEsp32Voltage = readAdcVoltage(PIN_TURBIDITY);
  float turbiditySensorVoltage = turbidityEsp32Voltage * TURBIDITY_DIVIDER_FACTOR;

  float tds = calculateTds(tdsVoltage, temperatureC);
  float ph = calculatePh(phVoltage);
  float turbidityPercent = calculateTurbidityPercent(turbiditySensorVoltage);

  String status = evaluateWaterStatus(temperatureC, ph, tds, turbidityPercent);
  updateAlertOutput(status);

  Serial.println("====================================");
  Serial.println("ESP32 Water Quality Monitor");
  Serial.print("Temperature: "); Serial.print(temperatureC, 2); Serial.println(" °C");
  Serial.print("TDS Voltage: "); Serial.print(tdsVoltage, 3); Serial.println(" V");
  Serial.print("TDS: "); Serial.print(tds, 1); Serial.println(" ppm");
  Serial.print("pH Voltage: "); Serial.print(phVoltage, 3); Serial.println(" V");
  Serial.print("pH: "); Serial.println(ph, 2);
  Serial.print("Turbidity ESP32 Voltage: "); Serial.print(turbidityEsp32Voltage, 3); Serial.println(" V");
  Serial.print("Turbidity Sensor Voltage: "); Serial.print(turbiditySensorVoltage, 3); Serial.println(" V");
  Serial.print("Turbidity Relative: "); Serial.print(turbidityPercent, 1); Serial.println(" %");
  Serial.print("Status: "); Serial.println(status);
  Serial.println("====================================");
  Serial.println();

  delay(3000);
}

4.3. Expected Serial Monitor Output

ESP32 Water Quality Monitor
Temperature: 28.37 °C
TDS Voltage: 0.732 V
TDS: 184.5 ppm
pH Voltage: 2.496 V
pH: 7.02
Turbidity Relative: 2.2 %
Status: NORMAL

If the status is WARNING or DANGER, recheck the water sample, the sensors, and the calibration values.