Community project

ESP32 Project Design

ESP32
Photo of ESP32 Project Design
Generated with AI

Info io03

Published October 24, 2026

This ESP32 project monitors water quality and environmental conditions using multiple sensors connected via I2C and one-wire protocols. It measures water temperature with a DS18B20 probe, air temperature and humidity with a BME280 sensor, pH levels with an Atlas EZO pH module, and detects water level changes with a float switch. All readings are displayed in real-time on a 128x64 OLED screen.

The guide provides a complete wiring diagram showing how to connect each sensor to the ESP32, a full parts list with component specifications, Arduino firmware that handles sensor initialization and data acquisition, and step-by-step assembly instructions. Builders will learn how to work with multiple I2C devices, implement one-wire temperature sensing, read analog level detection, and display live sensor data on a compact display.

Wiring diagram

Wiring diagram for ESP32 Project Design

Gather all the parts

QtyComponent
1

DS18B20

Sonda impermeable

Digital temperature sensor using OneWire protocol

1

Resistor

4.7 kΩ

Through-hole resistor (current-limiting in series with an LED)

1

BME280

Bosch BME280 environmental sensor on the exact Adafruit 2652 breakout. Supports I2C via SCK/SCL and SDI/SDA, optional SDO address select, and SPI pins when needed.

1

Atlas Ezo Ph

Industrial-grade pH monitoring circuit with dual interface support (UART and I2C). Provides high-accuracy pH readings (.001 resolution) with temperature compensation and ISO 10523 compliance. Supports any brand pH probe. Operating voltage 3.3V-5V with ASCII data format.

1

Float Switch

Float switch (liquid level switch): a buoyant float carries a magnet that actuates a hermetically sealed reed contact as the liquid rises or falls past the mounting point. Two-wire SPST dry contact (normally-open or normally-closed) with no power supply of its own -- wired like a push-button on a GPIO with INPUT_PULLUP, where open/closed indicates the level is above/below the float. Common for pump on/off control, tank full/empty detection, and sump alarms.

1

0.96" OLED Display 128x64 I2C (Blue)

Monochrome 0.96 inch OLED display with 128 x 64 blue pixels, driven by an SSD1306 controller over I2C (address 0x3C). It runs from 3.3 to 5 V and connects through a four-pin VCC, GND, SCL, SDA header.

Assemble it in 7 steps

1. Prepara la alimentación segura

Deja el ESP32-S3 sobre una mesa seca y aliméntalo únicamente con su cable USB mientras pruebas. No conectes todavía ninguna fuente externa ni metas la electrónica en la caja del tanque.

  • El puerto USB también será la alimentación de esta primera versión.
  • No acerques la placa, pantalla ni módulo de pH al agua; si se mojan pueden dañarse.

2. Conecta la sonda de temperatura del agua

Conecta la sonda DS18B20 water_temp: VCC → 3V3 (alimentación), GND → GND (tierra), DATA → GPIO4 (señal). Conecta la resistencia onewire_pullup de 4.7 kΩ entre 3V3 y el mismo cable DATA/GPIO4 (mantiene estable la señal).

  • En una sonda impermeable de tres hilos, confirma los colores con la etiqueta o ficha del vendedor antes de energizarla; no todos usan los mismos colores.
  • No intercambies VCC y GND: una conexión de alimentación invertida puede dañar la sonda.

3. Conecta el sensor de aire

Conecta el BME280 ambient_bme280: VCC → 3V3 (alimentación), GND → GND (tierra), SDA → GPIO8 (datos), SCL → GPIO9 (reloj). Déjalo fuera de la caja sellada o usa una zona ventilada y a la sombra, para que mida el aire y no el calor del ESP32.

  • No lo pongas al sol directo; el sol falsea la temperatura ambiente.
  • Asegúrate de que VCC y GND no estén cambiados — una alimentación invertida puede dañar el sensor.

4. Conecta el módulo y la sonda de pH

Conecta el circuito Atlas EZO pH ph_ezo: VCC → 3V3 (alimentación), GND → GND (tierra), SDA → GPIO8 (datos), SCL → GPIO9 (reloj). Enchufa la sonda de pH al conector BNC del circuito y mantenla húmeda usando su tapa con solución de almacenamiento cuando no esté en el tanque.

  • Calibra la sonda con soluciones patrón pH 7 y pH 4 antes de confiar en los datos.
  • Mantén el cable de la sonda de pH separado de cables USB y fuentes de alimentación para reducir lecturas inestables.
  • Nunca guardes una sonda de pH seca ni en agua destilada; se degrada y los valores dejarán de ser fiables.

5. Conecta el aviso de nivel

Conecta un cable del interruptor flotante level_float → GND (tierra) y el otro → GPIO5 (señal). Instálalo a la altura mínima o máxima que quieras vigilar; al cambiar de posición cerrará o abrirá el contacto.

  • Prueba el flotador con la mano antes de montarlo: la pantalla debe cambiar entre “normal” y “contacto activo”.
  • No uses este pequeño interruptor para alimentar una bomba; sólo sirve para avisar al ESP32.

6. Conecta la pantalla de estado

Conecta la pantalla OLED status_oled siguiendo los nombres impresos en su placa: VCC → 3V3 (alimentación), GND → GND (tierra), SDA → GPIO8 (datos), SCL → GPIO9 (reloj). Comparte GPIO8 y GPIO9 con el BME280 y el EZO pH; esos tres módulos pueden usar los mismos dos cables de datos.

  • La pantalla debe mostrar agua, aire, pH y nivel tras encender el equipo.
  • Algunas pantallas iguales cambian el orden físico de sus pines; sigue siempre las etiquetas VCC, GND, SCL y SDA impresas.
  • No conectes la pantalla al revés: si VCC y GND se intercambian puede dañarse.

7. Prueba antes de instalar cerca del tanque

Con todos los módulos sujetos y secos, conecta el ESP32-S3 por USB. Coloca la sonda de temperatura y el flotador en agua limpia; coloca la sonda de pH en su solución o en el tanque sólo después de calibrarla. La pantalla OLED mostrará las lecturas cada diez segundos.

  • Durante esta fase, guarda los valores impresos por el equipo como tus primeras filas de datos.
  • Después de validar varios días de medidas, añade conductividad y oxígeno disuelto como módulos separados.
  • No cierres el equipo en una caja estanca hasta comprobar que todas las lecturas son correctas; la condensación y un cable mal prensado pueden causar fallos.

Review all connections

1. Connections between "water_temp" and "ESP32"

Functionwater_tempESP32
powerVCC → alimentación de la sonda3V3
groundGND → retorno eléctrico de la sondaGND
dataDATA → lectura digital de la temperatura del aguaGPIO 4

2. Connections between "onewire_pullup" and "ESP32"

Functiononewire_pullupESP32
powerP1 → extremo de 3.3 V de la resistencia de 4.7 kΩ3V3
digitalP2 → DS18B20 DATAEXT

3. Connections between "ambient_bme280" and "ESP32"

Functionambient_bme280ESP32
powerVCC → alimentación del sensor ambiental3V3
groundGND → retorno eléctrico del sensor ambientalGND
i2cSDA → datos compartidos del sensor ambientalGPIO 8
i2cSCL → reloj compartido del sensor ambientalGPIO 9

4. Connections between "ph_ezo" and "ESP32"

Functionph_ezoESP32
powerVCC → alimentación del módulo de pH3V3
groundGND → retorno eléctrico del módulo de pHGND
i2cSDA → datos del módulo de pHGPIO 8
i2cSCL → reloj del módulo de pHGPIO 9

5. Connections between "level_float" and "ESP32"

Functionlevel_floatESP32
groundGND → referencia del interruptor de nivelGND
dataSIGNAL → aviso de nivel bajo o altoGPIO 5

6. Connections between "status_oled" and "ESP32"

Functionstatus_oledESP32
powerVCC → alimentación de la pantalla3V3
groundGND → retorno eléctrico de la pantallaGND
i2cSDA → datos compartidos de la pantallaGPIO 8
i2cSCL → reloj compartido de la pantallaGPIO 9

Deploy the firmware

#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Adafruit_BME280.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

constexpr int ONEWIRE_PIN = 4;
constexpr int LEVEL_PIN = 5;
constexpr int I2C_SDA_PIN = 8;
constexpr int I2C_SCL_PIN = 9;
constexpr uint8_t PH_ADDRESS = 0x63;
constexpr uint8_t OLED_ADDRESS = 0x3C;
constexpr uint8_t BME_ADDRESS = 0x77;
constexpr unsigned long SAMPLE_INTERVAL_MS = 10000;

OneWire oneWire(ONEWIRE_PIN);
DallasTemperature waterProbe(&oneWire);
Adafruit_BME280 bme;
Adafruit_SSD1306 display(128, 64, &Wire, -1);

bool bmeReady = false;
bool displayReady = false;
float waterTemperatureC = NAN;
float airTemperatureC = NAN;
float humidityPercent = NAN;
float phValue = NAN;
bool levelContactClosed = false;
unsigned long lastSampleMs = 0;

void sendPhCommand(const String &command) {
  Wire.beginTransmission(PH_ADDRESS);
  for (size_t index = 0; index < command.length(); ++index) {
    Wire.write(static_cast<uint8_t>(command[index]));
  }
  Wire.endTransmission();
}

float readPh() {
  Wire.requestFrom(static_cast<int>(PH_ADDRESS), 20);
  if (!Wire.available()) {
    return NAN;
  }

  const uint8_t status = Wire.read();
  String answer;
  while (Wire.available()) {
    const char c = static_cast<char>(Wire.read());
    if (c != 0) {
      answer += c;
    }
  }

  if (status != 1) {
    return NAN;
  }
  const float result = answer.toFloat();
  return (result >= 0.0f && result <= 14.0f) ? result : NAN;
}

void drawDashboard() {
  if (!displayReady) {
    return;
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("VARK - Nodo inicial");
  display.drawLine(0, 10, 127, 10, SSD1306_WHITE);

  display.setCursor(0, 15);
  display.print("Agua: ");
  if (isnan(waterTemperatureC)) display.println("--.- C");
  else { display.print(waterTemperatureC, 1); display.println(" C"); }

  display.setCursor(0, 27);
  display.print("Aire: ");
  if (isnan(airTemperatureC)) display.println("--.- C");
  else { display.print(airTemperatureC, 1); display.print(" C "); display.print(humidityPercent, 0); display.println("%"); }

  display.setCursor(0, 39);
  display.print("pH: ");
  if (isnan(phValue)) display.println("sin lectura");
  else display.println(phValue, 2);

  display.setCursor(0, 51);
  display.print("Nivel: ");
  display.println(levelContactClosed ? "contacto activo" : "normal");
  display.display();
}

void sampleSensors() {
  waterProbe.requestTemperatures();
  const float measuredWater = waterProbe.getTempCByIndex(0);
  waterTemperatureC = (measuredWater == DEVICE_DISCONNECTED_C) ? NAN : measuredWater;

  levelContactClosed = digitalRead(LEVEL_PIN) == LOW;

  if (bmeReady) {
    airTemperatureC = bme.readTemperature();
    humidityPercent = bme.readHumidity();
  }

  if (!isnan(waterTemperatureC)) {
    sendPhCommand("T," + String(waterTemperatureC, 1));
    delay(300);
  }
  sendPhCommand("R");
  delay(1000);
  phValue = readPh();

  Serial.print("water_c="); Serial.print(waterTemperatureC, 2);
  Serial.print(",air_c="); Serial.print(airTemperatureC, 2);
  Serial.print(",humidity_pct="); Serial.print(humidityPercent, 1);
  Serial.print(",ph="); Serial.print(phValue, 2);
  Serial.print(",level_contact="); Serial.println(levelContactClosed ? 1 : 0);

  drawDashboard();
}

void setup() {
  Serial.begin(115200);
  pinMode(LEVEL_PIN, INPUT_PULLUP);
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  waterProbe.begin();

  displayReady = display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS);
  bmeReady = bme.begin(BME_ADDRESS, &Wire);

  if (displayReady) {
    drawDashboard();
  }
}

void loop() {
  const unsigned long now = millis();
  if (now - lastSampleMs >= SAMPLE_INTERVAL_MS || lastSampleMs == 0) {
    lastSampleMs = now;
    sampleSensors();
  }
  delay(50);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik