Um sensor de nível de caixa d'água com ESP32 é um dos primeiros projetos IoT mais úteis: ele responde "quanta água ainda tem?" de qualquer lugar e avisa antes que a bomba trabalhe a seco. Neste tutorial você vai ligar um sensor ultrassônico à prova d'água, converter a distância medida em percentual cheio e litros e publicar os dois valores em um broker MQTT, junto com um alerta de nível baixo com histerese.
Como Funciona a Medição Ultrassônica de Nível
Um sensor ultrassônico instalado na tampa do reservatório envia um pulso sonoro curto para baixo e mede quanto tempo o eco leva para voltar da superfície da água. A distância é metade do tempo de ida e volta multiplicado pela velocidade do som. O nível da água é então simplesmente a profundidade do reservatório menos essa distância.
sensor (lid)
---------[S]--------- <- distance d measured by the sensor
| | |
| v |
|~~~~~~~~~~~~~~~~~~~| <- water surface
| | level = tank_depth - (d - mount_offset)
| |
--------------------- <- tank bottom
A medição ultrassônica é sem contato, então nada fica dentro da água, e funciona com qualquer líquido que não forme espuma. Os pontos fracos são o alcance mínimo (uma "zona cega" perto do sensor), a condensação no transdutor e os ecos nas paredes ou tubulações em reservatórios estreitos. Os três podem ser controlados com bom posicionamento e filtragem.
Lista de Materiais
| Peça | Função |
|---|---|
| ESP32 DevKit (qualquer variante com Wi-Fi) | Lê o sensor e publica via MQTT |
| Sensor ultrassônico à prova d'água JSN-SR04T ou AJ-SR04M | Mede a distância até a superfície da água |
| Resistores de 1 kΩ e 2 kΩ | Divisor de tensão para o sinal de eco de 5 V |
| Fonte de 5 V | Alimenta o sensor e o ESP32 pelo pino 5V |
| Pedaço curto de cano de PVC (opcional) | Tubo tranquilizador que reduz ecos e ondulações |
O HC-SR04, mais barato, serve para testes na bancada, mas não é à prova d'água e corrói rapidamente no ar úmido acima do reservatório. Uma sonda selada com cabo é a escolha certa para uma instalação real.
Ligando o Sensor ao ESP32
| Pino do JSN-SR04T | Pino do ESP32 | Observações |
|---|---|---|
| 5V | 5V (VIN) | O sensor precisa de 5 V para um alcance confiável |
| GND | GND | Terra comum |
| Trig | GPIO 5 | Aceita pulso de disparo de 3,3 V |
| Echo | GPIO 18 via divisor | 1 kΩ em série, 2 kΩ para o GND: cerca de 3,3 V no pino |
Não ligue o pino de eco diretamente: as GPIOs do ESP32 não toleram 5 V. O divisor leva poucos segundos para montar e salva a placa.
Calibrando o Seu Reservatório
Antes de escrever o código, meça três números com uma trena:
- Offset de montagem: distância da face do sensor até a linha de água "cheio". Mantenha-a maior que a zona cega do sensor, normalmente 20 a 25 cm no JSN-SR04T.
- Profundidade útil: distância da linha de cheio até a saída ou o nível mais baixo que interessa.
- Capacidade em litros na linha de cheio. Em um cilindro vertical, os litros equivalem à área da seção em decímetros quadrados multiplicada pela profundidade em decímetros; em reservatórios irregulares, use o valor do fabricante.
O percentual cheio é então (usable_depth - (distance - mount_offset)) / usable_depth * 100, limitado entre 0 e 100.
O Sketch de Nível de Caixa d'Água com ESP32
O sketch faz sete leituras, usa a mediana para descartar ecos espúrios, calcula nível, percentual e litros e publica uma mensagem JSON a cada 30 segundos. Instale antes a PubSubClient pelo Library Manager.
| Topic | Retido | Exemplo de payload |
|---|---|---|
connectmq/demo/tank-91c2/level |
sim | {"distance_cm":63.4,"level_cm":96.6,"percent":64.4,"litres":644} |
connectmq/demo/tank-91c2/alert |
sim | LOW ou OK |
connectmq/demo/tank-91c2/status |
sim | online / offline (Last Will) |
Substitua tank-91c2 pelo seu próprio identificador aleatório.
#include <WiFi.h>
#include <PubSubClient.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* CLIENT_ID = "tank-91c2-esp32";
const char* TOPIC_LEVEL = "connectmq/demo/tank-91c2/level";
const char* TOPIC_ALERT = "connectmq/demo/tank-91c2/alert";
const char* TOPIC_STATUS = "connectmq/demo/tank-91c2/status";
const int TRIG_PIN = 5;
const int ECHO_PIN = 18;
// Calibração do reservatório (centímetros / litros)
const float MOUNT_OFFSET_CM = 25.0; // face do sensor até a linha de cheio
const float USABLE_DEPTH_CM = 150.0; // linha de cheio até a linha de vazio
const float CAPACITY_L = 1000.0;
// Histerese do alerta: alarme abaixo de 20 %, normaliza acima de 25 %
const float LOW_ON_PCT = 20.0;
const float LOW_OFF_PCT = 25.0;
const uint32_t PUBLISH_INTERVAL_MS = 30000;
const float SOUND_CM_PER_US = 0.0343; // a cerca de 20 graus C
WiFiClient net;
PubSubClient mqtt(net);
uint32_t lastPublish = 0;
bool lowAlarm = false;
bool alarmPublished = false;
float readDistanceOnce() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(5);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(20); // o JSN-SR04T prefere um disparo mais longo
digitalWrite(TRIG_PIN, LOW);
unsigned long us = pulseIn(ECHO_PIN, HIGH, 30000); // timeout de 30 ms
if (us == 0) return NAN;
return us * SOUND_CM_PER_US / 2.0;
}
float readDistanceMedian() {
const int N = 7;
float v[N];
int n = 0;
for (int i = 0; i < N; i++) {
float d = readDistanceOnce();
if (!isnan(d)) v[n++] = d;
delay(60); // espera os ecos se dissiparem
}
if (n < 3) return NAN;
for (int i = 1; i < n; i++) { // insertion sort
float key = v[i];
int j = i - 1;
while (j >= 0 && v[j] > key) { v[j + 1] = v[j]; j--; }
v[j + 1] = key;
}
return v[n / 2];
}
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(500);
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(CLIENT_ID, TOPIC_STATUS, 1, true, "offline")) {
mqtt.publish(TOPIC_STATUS, "online", true);
alarmPublished = false; // republica o estado do alerta após reconectar
} else {
delay(5000);
}
}
}
void publishLevel() {
float d = readDistanceMedian();
if (isnan(d)) {
mqtt.publish(TOPIC_LEVEL, "{\"error\":\"no_echo\"}", true);
return;
}
float level = USABLE_DEPTH_CM - (d - MOUNT_OFFSET_CM);
level = constrain(level, 0.0f, USABLE_DEPTH_CM);
float pct = level / USABLE_DEPTH_CM * 100.0;
float litres = CAPACITY_L * pct / 100.0;
char json[160];
snprintf(json, sizeof(json),
"{\"distance_cm\":%.1f,\"level_cm\":%.1f,\"percent\":%.1f,\"litres\":%.0f,\"rssi\":%d}",
d, level, pct, litres, WiFi.RSSI());
mqtt.publish(TOPIC_LEVEL, json, true);
Serial.println(json);
bool prev = lowAlarm;
if (!lowAlarm && pct < LOW_ON_PCT) lowAlarm = true;
if (lowAlarm && pct > LOW_OFF_PCT) lowAlarm = false;
if (lowAlarm != prev || !alarmPublished) {
mqtt.publish(TOPIC_ALERT, lowAlarm ? "LOW" : "OK", true);
alarmPublished = true;
}
}
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
connectWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
connectMqtt();
publishLevel();
lastPublish = millis();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWifi();
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
if (millis() - lastPublish >= PUBLISH_INTERVAL_MS) {
lastPublish = millis();
publishLevel();
}
}
Acompanhe as leituras pelo seu computador:
mosquitto_sub -h broker.connectmq.com -p 1883 -i tank-91c2-viewer -t "connectmq/demo/tank-91c2/#" -v
Como os topics de nível, alerta e status são retidos, um dashboard no celular que se conecte a qualquer momento mostra o estado atual na hora, em vez de esperar até 30 segundos. Se a flag retained e o Last Will são novidade para você, o artigo Last Will MQTT no ESP32 explica os dois.
Por Que o Alerta Usa Histerese
A superfície da água ondula, principalmente enquanto uma bomba está enchendo ou puxando água. Com um único limite em 20 por cento, uma leitura oscilando entre 19,8 e 20,3 ligaria e desligaria o alerta a cada ciclo e inundaria todos os assinantes. Dois limites (liga abaixo de 20, desliga acima de 25) criam uma zona morta que absorve esse ruído. O sketch também publica o alerta apenas quando o estado muda, mais uma vez após cada reconexão, o que mantém o tráfego baixo e o valor retido correto.
Você pode adicionar uma segunda camada sem mexer no firmware. No portal da ConnectMQ, regras de alerta por limite monitoram um topic e registram uma entrada no log de alertas quando um valor ultrapassa o seu limite, o que é útil como verificação independente da lógica do dispositivo.
Dicas de Precisão para um Sensor de Nível de Caixa d'Água com ESP32
- Instale o sensor perfeitamente na vertical e longe da entrada de água, para que a água caindo e as ondulações não atinjam o feixe.
- Use um tubo tranquilizador em reservatórios estreitos ou turbulentos: um cano de PVC da tampa até o fundo, com um pequeno furo perto da base, garante uma superfície calma e um caminho limpo para o eco.
- Respeite a zona cega. Se a água puder subir até os primeiros 20 a 25 cm, eleve o sensor com um pequeno tubo prolongador.
- Compense a temperatura se o reservatório fica em um sótão quente ou em um galpão gelado. A velocidade do som aumenta com a temperatura do ar, cerca de 0,6 m/s por grau Celsius; um DS18B20 ao lado do sensor permite corrigir isso.
- Cuidado com a condensação. Gotículas no transdutor causam leituras "sem eco". Um pequeno furo de ventilação na tampa ajuda.
- Valide com uma medição manual em vários níveis e ajuste
MOUNT_OFFSET_CMaté os valores baterem.
Reservatórios Alimentados por Bateria
Reservatórios remotos muitas vezes não têm energia da rede. O sketch acima mantém Wi-Fi e MQTT conectados o tempo todo, o que drena baterias rapidamente. Para um nó solar ou a bateria, reestruture o código: acordar, fazer a leitura, conectar, publicar o nível e o alerta retidos e então entrar em deep sleep por 10 ou 15 minutos. As mensagens retidas importam ainda mais nesse desenho, porque o dispositivo fica offline a maior parte do tempo. O guia de deep sleep com MQTT no ESP32 mostra o padrão completo, e você pode alimentar o sensor por um MOSFET para que ele não consuma nada enquanto o ESP32 dorme.
Indo para Produção
O servidor MQTT público em broker.connectmq.com é perfeito para testes e desenvolvimento, mas tudo ali pode ser lido por qualquer pessoa que adivinhe o seu topic. Antes de instalar o sensor de forma permanente:
- Crie um broker privado e copie do portal o nome de usuário, a senha e o client ID gerados.
- Mude para
WiFiClientSecure, porta 8883 e o certificado de CA ISRG Root X1. - Conecte com
mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD", TOPIC_STATUS, 1, true, "offline").
Manter o seu próprio broker para um punhado de reservatórios raramente compensa a manutenção, e por isso um broker MQTT gerenciado é a escolha habitual. Se quiser comparar opções de hospedagem antes, leia sobre a abordagem de broker MQTT na nuvem; para payloads estruturados, o guia de ArduinoJson com MQTT é uma alternativa mais limpa ao snprintf. Mais projetos com sensores estão reunidos no tutorial de IoT com ESP32.
Comece a Usar a ConnectMQ
O seu monitor de reservatório merece um endpoint privado e criptografado. A ConnectMQ oferece TLS na porta 8883, credenciais geradas, um explorador de topics ao vivo e regras de alerta por limite em um único portal. Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito, e altere três linhas do sketch.
Perguntas frequentes
Qual sensor ultrassônico é melhor para uma caixa d'água?
Uma sonda selada e à prova d'água, como o JSN-SR04T ou o AJ-SR04M. O HC-SR04 serve para testes na bancada, mas não resiste por muito tempo à umidade acima do reservatório.
Por que o meu sensor indica o reservatório cheio quando ele não está?
A água ou algum obstáculo está dentro da zona cega, ou o eco está refletindo em um cano ou na parede. Eleve o sensor, aponte-o para uma área livre ou adicione um tubo tranquilizador.
Com que frequência o ESP32 deve publicar o nível do reservatório?
A cada 30 a 60 segundos é mais que suficiente para a maioria das caixas d'água residenciais; o nível muda devagar. Com bateria, a cada 10 a 15 minutos com deep sleep é um bom equilíbrio.
Posso usar isso para diesel ou outros líquidos?
A medição ultrassônica funciona com a maioria dos líquidos que não formam espuma, mas tanques de combustível têm regras de segurança sobre eletrônicos em áreas classificadas. Verifique a regulamentação e use equipamentos certificados quando exigido.



