ESP32 e Arduino

Sensor de Nível de Caixa d'Água com ESP32 e MQTT: Percentual Cheio e Alertas de Nível Baixo

Meça o nível do reservatório com um sensor ultrassônico à prova d'água, converta a distância em litros e percentual cheio no ESP32 e publique leituras MQTT retidas e alertas de nível baixo com histerese.

Montagem de sensor de nível de caixa d'água com ESP32 e sonda ultrassônica na tampa enviando o percentual via MQTT
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

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:

  1. 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.
  2. Profundidade útil: distância da linha de cheio até a saída ou o nível mais baixo que interessa.
  3. 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

  1. 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.
  2. 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.
  3. Respeite a zona cega. Se a água puder subir até os primeiros 20 a 25 cm, eleve o sensor com um pequeno tubo prolongador.
  4. 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.
  5. Cuidado com a condensação. Gotículas no transdutor causam leituras "sem eco". Um pequeno furo de ventilação na tampa ajuda.
  6. Valide com uma medição manual em vários níveis e ajuste MOUNT_OFFSET_CM até 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.

Artigos relacionados

Exemplo de MQTT com ESP-IDF: Monte um Cliente esp-mqtt do Zero

Monte um projeto ESP-IDF a partir de uma pasta vazia com o componente esp-mqtt: configuração do Wi-Fi em modo station, o handler de eventos MQTT, publicação e inscrição, propriedades do MQTT 5 e TLS com o bundle de certificados.

8 min de leitura

Usando os Recursos do MQTT 5 do esp-mqtt no ESP32 com ESP-IDF

Um projeto ESP-IDF prático que ativa o MQTT 5.0 no esp-mqtt e usa user properties, expiração de mensagens, topic aliases, expiração de sessão e response topics com um broker MQTT 5 real.

8 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.