Automação IoT

Estação Meteorológica DIY com MQTT: Vento, Chuva e Temperatura com um ESP32

Ligue um anemômetro, um pluviômetro e um BME280 a um ESP32, publique vento, chuva, temperatura, umidade e pressão como JSON via MQTT e depois registre e plote os dados com um script Python curto.

Mastro no quintal com anemômetro, pluviômetro e abrigo de sensores ligados a um ESP32 em uma estação meteorológica DIY com 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

Uma estação meteorológica DIY com MQTT fornece leituras locais de vento, chuva, temperatura, umidade e pressão que qualquer dashboard, sistema de automação residencial ou controlador de irrigação, de um quintal a uma fazenda de café, pode assinar. Neste tutorial você vai ligar sensores meteorológicos comuns a um ESP32, publicar uma mensagem JSON por minuto em um broker e usar um subscriber em Python para registrar as leituras e desenhar um gráfico. Tudo é testado primeiro no servidor MQTT público (gratuito), feito para testes e protótipos, e depois migrado para credenciais privadas.

Peças e Ligações do Nó da Estação Meteorológica DIY com MQTT

O projeto usa peças fáceis de encontrar e bem documentadas. Os sensores de vento e chuva seguem o estilo clássico dos kits "weather meter": um anemômetro de conchas e um pluviômetro de báscula, ambos com reed switches que fecham brevemente a cada rotação ou basculamento.

Peça Finalidade Conexão no ESP32
Placa ESP32 DevKit Wi-Fi e MQTT USB ou fonte de 5 V
Módulo BME280 (I2C) Temperatura, umidade, pressão SDA GPIO 21, SCL GPIO 22, 3,3 V, GND
Anemômetro de conchas (reed switch) Velocidade do vento Um fio no GPIO 27, o outro no GND
Pluviômetro de báscula Precipitação Um fio no GPIO 26, o outro no GND
Abrigo contra radiação Mantém o BME280 fora do sol direto Mecânico
Caixa à prova de intempéries Protege o ESP32 Mecânico

Os reed switches só precisam de um pull-up; o sketch ativa os pull-ups internos do ESP32, então cada fechamento leva o pino a nível baixo. Esses kits costumam incluir também um cata-vento; ele usa uma escada de resistores que você lê com um pino ADC, e pode ser adicionado depois que o básico estiver funcionando.

Duas constantes de calibração vêm do datasheet do sensor. No kit comum usado aqui, um fechamento do anemômetro por segundo corresponde a 2,4 km/h, e cada basculamento equivale a 0,2794 mm de chuva. Se o seu hardware for diferente, altere as duas constantes no topo do sketch.

Design de Topics e Payload

Use um único topic de telemetria com um payload JSON compacto, além de um topic de status retido controlado pelo Last Will do MQTT:

connectmq/demo/<random-id>/weather/station1/telemetry   (JSON every 60 s)
connectmq/demo/<random-id>/weather/station1/status      (online/offline, retained)
{
  "temp_c": 14.8,
  "hum_pct": 71.2,
  "press_hpa": 1012.6,
  "wind_kmh": 9.6,
  "gust_kmh": 16.8,
  "rain_mm": 0.28,
  "rain_today_mm": 3.07,
  "rssi": -61,
  "uptime_s": 86412
}

Enviar médias por minuto em vez de cada pulso mantém o tráfego mínimo e bem dentro dos limites do servidor MQTT público, de 10 mensagens por segundo e 64 KB por mensagem. Um valor de rajada (a maior média de 3 segundos dentro do minuto) preserva a informação que a média esconderia. O guia do servidor MQTT público lista os demais limites, como 8 níveis de topic e 10 assinaturas por cliente, que este projeto respeita com folga.

Por que não reter a mensagem de telemetria? Você pode, e em uma estação meteorológica isso costuma ajudar: um dashboard aberto entre duas publicações mostra imediatamente a última leitura em vez de um widget vazio. A contrapartida é que uma estação que parou de funcionar há horas continua parecendo informar seus últimos valores, e é exatamente por isso que o topic status retido e separado, com Last Will, é importante. Exiba ambos e deixe a leitura em cinza quando o status for offline.

O Sketch do ESP32

Instale estas bibliotecas pelo Library Manager do Arduino: PubSubClient de Nick O'Leary, Adafruit BME280 Library e Adafruit Unified Sensor. Altere as credenciais do Wi-Fi e o ID aleatório no prefixo do topic.

#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* PREFIX = "connectmq/demo/k3x9q2/weather/station1";

const int PIN_WIND = 27;
const int PIN_RAIN = 26;
const float KMH_PER_HZ = 2.4;     // anemômetro: 1 fechamento/s = 2,4 km/h
const float MM_PER_TIP = 0.2794;  // pluviômetro: 1 basculamento = 0,2794 mm

WiFiClient net;
PubSubClient mqtt(net);
Adafruit_BME280 bme;

volatile uint32_t windCount = 0, rainCount = 0;
volatile uint32_t lastWindUs = 0, lastRainMs = 0;

void IRAM_ATTR onWind() {
  uint32_t now = micros();
  if (now - lastWindUs > 5000) { windCount++; lastWindUs = now; }  // debounce de 5 ms
}
void IRAM_ATTR onRain() {
  uint32_t now = millis();
  if (now - lastRainMs > 200) { rainCount++; lastRainMs = now; }   // debounce de 200 ms
}

char clientId[32];
char statusTopic[96];
float rainTodayMm = 0;
float maxGust = 0;
uint32_t lastGustCount = 0, lastGustMs = 0, lastPublishMs = 0, windAtPublish = 0;

void connectMqtt() {
  while (!mqtt.connected()) {
    if (mqtt.connect(clientId, nullptr, nullptr, statusTopic, 1, true, "offline")) {
      mqtt.publish(statusTopic, "online", true);
    } else {
      Serial.printf("MQTT failed, state=%d\n", mqtt.state());
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(PIN_WIND, INPUT_PULLUP);
  pinMode(PIN_RAIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_WIND), onWind, FALLING);
  attachInterrupt(digitalPinToInterrupt(PIN_RAIN), onRain, FALLING);

  if (!bme.begin(0x76) && !bme.begin(0x77)) {
    Serial.println("BME280 not found, check wiring");
  }

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }

  snprintf(clientId, sizeof(clientId), "wx-%llx", ESP.getEfuseMac());
  snprintf(statusTopic, sizeof(statusTopic), "%s/status", PREFIX);
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  connectMqtt();
  lastPublishMs = lastGustMs = millis();
}

void loop() {
  if (!mqtt.connected()) connectMqtt();
  mqtt.loop();

  uint32_t now = millis();

  // Rajada: maior média de 3 segundos dentro da janela de publicação
  if (now - lastGustMs >= 3000) {
    noInterrupts(); uint32_t c = windCount; interrupts();
    float gust = (c - lastGustCount) / ((now - lastGustMs) / 1000.0) * KMH_PER_HZ;
    if (gust > maxGust) maxGust = gust;
    lastGustCount = c; lastGustMs = now;
  }

  if (now - lastPublishMs >= 60000) {
    noInterrupts();
    uint32_t w = windCount; uint32_t r = rainCount; rainCount = 0;
    interrupts();

    float seconds = (now - lastPublishMs) / 1000.0;
    float windKmh = (w - windAtPublish) / seconds * KMH_PER_HZ;
    float rainMm = r * MM_PER_TIP;
    rainTodayMm += rainMm;   // zere à meia-noite pelo logger ou com NTP

    char payload[256];
    snprintf(payload, sizeof(payload),
      "{\"temp_c\":%.1f,\"hum_pct\":%.1f,\"press_hpa\":%.1f,"
      "\"wind_kmh\":%.1f,\"gust_kmh\":%.1f,\"rain_mm\":%.2f,"
      "\"rain_today_mm\":%.2f,\"rssi\":%d,\"uptime_s\":%lu}",
      bme.readTemperature(), bme.readHumidity(), bme.readPressure() / 100.0,
      windKmh, maxGust, rainMm, rainTodayMm, WiFi.RSSI(), now / 1000);

    char topic[96];
    snprintf(topic, sizeof(topic), "%s/telemetry", PREFIX);
    mqtt.publish(topic, payload);
    Serial.println(payload);

    windAtPublish = w;
    maxGust = 0;
    lastPublishMs = now;
  }
}

Abra o monitor serial a 115200 baud. Gire o anemômetro com a mão e incline o balde do pluviômetro algumas vezes; a mensagem do minuto seguinte deve mostrar valores diferentes de zero. Em outro terminal, confirme que o broker está recebendo os dados:

mosquitto_sub -h broker.connectmq.com -p 1883 -i wx-viewer-5521 -t "connectmq/demo/k3x9q2/weather/#" -v

Se a placa nunca conectar, o tutorial de ESP32 com MQTT mostra passo a passo como resolver problemas de Wi-Fi e de broker.

Registrando e Plotando as Leituras

O broker entrega mensagens ao vivo; ele não armazena o histórico para você. Um subscriber Python curto pode acrescentar cada leitura a um arquivo CSV e gerar um gráfico. Instale as dependências:

pip install "paho-mqtt>=2.0" matplotlib
import csv
import json
import os
import sys
from datetime import datetime, timezone
import paho.mqtt.client as mqtt

TOPIC = "connectmq/demo/k3x9q2/weather/station1/telemetry"
CSV_FILE = "weather.csv"
FIELDS = ["time", "temp_c", "hum_pct", "press_hpa", "wind_kmh", "gust_kmh", "rain_mm"]


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    client.subscribe(TOPIC, qos=1)


def on_message(client, userdata, msg):
    data = json.loads(msg.payload)
    row = {"time": datetime.now(timezone.utc).isoformat(timespec="seconds")}
    row.update({k: data.get(k) for k in FIELDS[1:]})
    new_file = not os.path.exists(CSV_FILE)
    with open(CSV_FILE, "a", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        if new_file:
            writer.writeheader()
        writer.writerow(row)
    print(row)


def plot():
    import matplotlib.pyplot as plt
    times, temp, wind, gust, rain = [], [], [], [], []
    with open(CSV_FILE) as f:
        for r in csv.DictReader(f):
            times.append(datetime.fromisoformat(r["time"]))
            temp.append(float(r["temp_c"]))
            wind.append(float(r["wind_kmh"]))
            gust.append(float(r["gust_kmh"]))
            rain.append(float(r["rain_mm"]))
    fig, (ax1, ax2, ax3) = plt.subplots(3, 1, sharex=True, figsize=(10, 8))
    ax1.plot(times, temp); ax1.set_ylabel("Temp (C)")
    ax2.plot(times, wind, label="wind"); ax2.plot(times, gust, label="gust")
    ax2.set_ylabel("km/h"); ax2.legend()
    ax3.bar(times, rain, width=0.0007); ax3.set_ylabel("Rain (mm/min)")
    fig.tight_layout()
    fig.savefig("weather.png", dpi=120)
    print("Saved weather.png")


if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "plot":
        plot()
    else:
        client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="wx-logger-8812")
        client.on_connect = on_connect
        client.on_message = on_message
        client.connect("broker.connectmq.com", 1883, keepalive=60)
        client.loop_forever()

Execute python weather_logger.py para coletar dados e python weather_logger.py plot para gerar weather.png. Em uma instalação permanente, muita gente troca o CSV por um InfluxDB alimentado pelo Telegraf ou por um dashboard no Node-RED; ambos se conectam ao broker como clientes MQTT comuns.

Checklist de Precisão

Uma estação meteorológica é tão boa quanto o local onde está instalada. Percorra esta lista antes de confiar nos números:

  1. Proteja o sensor de temperatura. Um BME280 sob sol direto ou dentro de uma caixa fechada mede vários graus a mais. Use um abrigo contra radiação ventilado.
  2. Instale o anemômetro alto e desobstruído. Telhados e muros criam turbulência; um mastro acima dos obstáculos próximos fornece um vento muito mais representativo.
  3. Nivele o pluviômetro e mantenha-o longe de paredes e árvores que bloqueiem ou pinguem água.
  4. Mantenha o ESP32 longe do BME280. O calor da própria placa distorce as leituras se os dois estiverem na mesma caixa.
  5. Faça debounce no software e verifique a fiação. Cabos longos sem blindagem captam ruído que parece basculamento fantasma de chuva.
  6. Zere o total diário de chuva de forma consistente, seja à meia-noite local no logger, seja no ESP32 com horário NTP.

Ampliando sua Estação Meteorológica DIY com MQTT

Quando a estação básica estiver estável, alguns acréscimos a tornam bem mais útil.

Direção do vento. O cata-vento da maioria dos kits alterna entre resistores internos conforme gira. Ligue-o como divisor de tensão com um resistor fixo (o datasheet do kit sugere um valor) a um pino ADC1, como o GPIO 34, leia com analogRead() e associe cada faixa de tensão a um dos 16 pontos da rosa dos ventos. Use apenas pinos ADC1, porque o ADC2 não pode ser usado enquanto o Wi-Fi estiver ativo no ESP32. Publique a direção em graus, que é mais fácil de plotar do que "NNE".

Energia solar e bateria. Um painel pequeno, uma célula de lítio e um controlador de carga podem manter a estação fora da rede, mas um ESP32 sempre ligado com Wi-Fi consome demais para um painel pequeno no inverno. Use deep sleep entre as publicações e conte os pulsos do anemômetro com o coprocessador ULP ou um chip contador dedicado, ou aceite um intervalo de publicação maior. Inclua a tensão da bateria no JSON para perceber problemas com antecedência.

Resumos diários. Faça o logger publicar um resumo diário retido (temperatura mínima e máxima, chuva total, rajada máxima) em um topic separado logo após a meia-noite. Assim, ferramentas de automação residencial podem usar a chuva do dia anterior para pular um ciclo de irrigação sem ler todo o histórico.

Valores derivados. Ponto de orvalho e sensação térmica são fáceis de calcular a partir da temperatura e da umidade, no logger ou no Node-RED. Calcule-os mais adiante no fluxo, e não no ESP32, para poder mudar a fórmula sem regravar o firmware.

Tirando a Estação do Broker de Testes

O servidor MQTT público é ideal para validar a montagem, mas não para dados de produção: tudo nele pode ser lido por qualquer pessoa e o prefixo do topic é a sua única separação. Para uma estação permanente, especialmente uma que alimenta um sistema de irrigação automática, use um broker privado com TLS e credenciais. Um broker MQTT gerenciado como o ConnectMQ oferece isso sem que você precise manter um servidor em casa. Troque o cliente para WiFiClientSecure, a porta para 8883 e use o usuário, a senha e o client ID do portal:

#include <WiFiClientSecure.h>
WiFiClientSecure net;          // substitui o WiFiClient
// no setup(): carregue o certificado ISRG Root X1 do Let's Encrypt
// net.setCACert(ISRG_ROOT_X1_PEM);
mqtt.setServer("broker.connectmq.com", 8883);
// conecte com: mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD", statusTopic, 1, true, "offline");

O explorador de topics ao vivo do portal ConnectMQ passa então a mostrar cada mensagem assim que ela chega, e regras de alerta por limite podem avisar sobre geada ou vento forte sem código extra. Em uma propriedade rural, a mesma telemetria pode alimentar um gêmeo digital que combina clima, umidade do solo e irrigação. Mais ideias voltadas ao campo estão no artigo sobre IoT na agricultura inteligente e no guia de automação com IoT, mais abrangente.

Publique seus Dados Meteorológicos no ConnectMQ

O ConnectMQ é um broker MQTT gerenciado com TLS, acesso via WebSocket para dashboards no navegador e um cliente no próprio navegador para verificações rápidas. Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito, e leve sua estação do servidor MQTT público para credenciais privadas em minutos.

Perguntas frequentes

Com que frequência uma estação meteorológica DIY deve publicar via MQTT?

Uma vez por minuto é um bom padrão para valores médios. Isso mantém o tráfego mínimo e ainda captura as rajadas, desde que você envie a velocidade máxima de vento em 3 segundos junto com a média.

Por que minhas leituras de temperatura estão altas demais?

Quase sempre por causa do aquecimento solar ou do calor do próprio ESP32. Coloque o BME280 em um abrigo contra radiação ventilado, longe do microcontrolador e do regulador de tensão.

Posso usar um BME680 ou SHT31 no lugar do BME280?

Sim. Troque a biblioteca e as funções de leitura; a parte MQTT continua igual, desde que você mantenha os nomes dos campos do JSON.

O broker guarda meu histórico meteorológico?

Não. Brokers MQTT entregam mensagens ao vivo e o último valor retido. Armazene o histórico com um subscriber, como o logger Python acima, com o Telegraf e um banco de dados de séries temporais ou com o Node-RED.

Artigos relacionados

Guia de Configuração do Zigbee2MQTT: do Coordenador ao Painel

Instale o Zigbee2MQTT com Docker, configure o coordenador e a conexão MQTT, pareie e renomeie dispositivos, controle-os via MQTT e veja seus estados em um painel, testando primeiro no servidor MQTT público (gratuito).

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.