Um nó ESP32 BME280 MQTT mede temperatura, umidade e pressão barométrica com um único chip I2C pequeno e publica os valores em um broker. Neste tutorial, você vai ligar o sensor, lê-lo no modo forçado de baixo consumo, publicar JSON no broker.connectmq.com e alterar o intervalo de publicação remotamente com uma mensagem MQTT que sobrevive a reinicializações.
Por que o BME280
O Bosch BME280 é uma evolução popular em relação ao DHT22. Ele usa I2C, então não há temporização frágil de fio único, lê os três valores em milissegundos e sua saída de pressão permite acompanhar tendências do tempo ou estimar a altitude. Ele também tem um modo forçado de economia de energia, no qual faz uma medição e volta a dormir, o que reduz o autoaquecimento.
Um aviso antes de comprar: muitos módulos baratos rotulados como "BME280" na verdade trazem um BMP280, que não tem sensor de umidade. Os dois parecem idênticos. O sketch abaixo lê o ID do chip e informa qual você tem.
Peças e bibliotecas
- Uma placa de desenvolvimento ESP32.
- Um módulo BME280 (versão 3,3 V ou com regulador na placa).
- Quatro jumpers.
- Arduino IDE 2.x com o pacote de placas esp32.
- Bibliotecas: Adafruit BME280 Library, Adafruit Unified Sensor, Adafruit BusIO (instaladas como dependências) e PubSubClient.
Ligação
| Pino do BME280 | Pino do ESP32 (clássico) | Observações |
|---|---|---|
| VIN ou VCC | 3V3 | Não use 5 V em módulos de 3,3 V sem regulador |
| GND | GND | |
| SCL | GPIO 22 | Clock I2C padrão |
| SDA | GPIO 21 | Dados I2C padrão |
| SDO (se houver) | GND ou 3V3 | Seleciona o endereço 0x76 ou 0x77 |
| CSB (se houver) | 3V3 | Mantém o chip no modo I2C |
No ESP32-S3, C3 e C6, os pinos I2C padrão são diferentes. Passe pinos explícitos para Wire.begin(SDA, SCL), como o sketch faz, e ajuste os números para a sua placa.
Design de topics com intervalo remoto
| Topic | Direção | Retido | Exemplo de payload |
|---|---|---|---|
connectmq/demo/bme5t8n/telemetry |
Dispositivo publica | Não | {"temperature":21.84,"humidity":46.2,"pressure":1013.6} |
connectmq/demo/bme5t8n/config/interval/set |
Você publica | Sim | 30 |
connectmq/demo/bme5t8n/config/interval |
Dispositivo publica | Sim | 30 |
connectmq/demo/bme5t8n/status |
Dispositivo publica | Sim | online / offline |
O comando é retido para que um dispositivo que estava offline quando você o enviou ainda o receba na próxima conexão. O dispositivo então informa o valor efetivamente aplicado em um topic retido separado. Manter os valores "desejado" e "reportado" em topics diferentes evita loops de realimentação e permite ver rapidamente se uma configuração foi aceita. O guia de níveis de QoS do MQTT explica por que QoS 1 é usado na assinatura de comandos.
O sketch ESP32 BME280 MQTT
#include <WiFi.h>
#include <Wire.h>
#include <PubSubClient.h>
#include <Preferences.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* PREFIX = "connectmq/demo/bme5t8n"; // use seu próprio id aleatório
const int I2C_SDA = 21, I2C_SCL = 22;
const uint32_t MIN_INTERVAL_S = 5, MAX_INTERVAL_S = 3600;
Adafruit_BME280 bme;
WiFiClient net;
PubSubClient mqtt(net);
Preferences prefs;
String clientId;
String tTelemetry, tIntervalSet, tIntervalState, tStatus;
uint32_t intervalS = 60;
unsigned long lastPublish = 0;
bool hasHumidity = true;
void reportInterval() {
char buf[12];
snprintf(buf, sizeof(buf), "%lu", (unsigned long)intervalS);
mqtt.publish(tIntervalState.c_str(), buf, true);
}
void onMessage(char* topic, byte* payload, unsigned int len) {
if (tIntervalSet != topic) return;
char buf[12];
len = min(len, (unsigned int)sizeof(buf) - 1);
memcpy(buf, payload, len);
buf[len] = '\0';
long requested = atol(buf);
if (requested < (long)MIN_INTERVAL_S || requested > (long)MAX_INTERVAL_S) {
Serial.printf("Ignoring interval %s (allowed %lu-%lu)\n", buf,
(unsigned long)MIN_INTERVAL_S, (unsigned long)MAX_INTERVAL_S);
reportInterval(); // reafirma o valor em uso
return;
}
if ((uint32_t)requested != intervalS) {
intervalS = requested;
prefs.putUInt("interval", intervalS);
Serial.printf("Interval set to %lu s\n", (unsigned long)intervalS);
}
reportInterval();
}
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(400);
Serial.printf("Wi-Fi OK, IP %s\n", WiFi.localIP().toString().c_str());
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(clientId.c_str(), tStatus.c_str(), 1, true, "offline")) {
mqtt.publish(tStatus.c_str(), "online", true);
mqtt.subscribe(tIntervalSet.c_str(), 1);
reportInterval();
Serial.println("MQTT connected");
} else {
Serial.printf("MQTT failed, state %d\n", mqtt.state());
delay(5000);
}
}
}
bool startSensor() {
Wire.begin(I2C_SDA, I2C_SCL);
if (!bme.begin(0x76, &Wire) && !bme.begin(0x77, &Wire)) return false;
uint8_t id = bme.sensorID();
hasHumidity = (id == 0x60);
Serial.printf("Sensor chip ID 0x%02X (%s)\n", id, hasHumidity ? "BME280" : "BMP280, no humidity");
// Modo forçado: uma medição sob demanda e depois sono. Recomendado para monitoramento do tempo.
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // temperatura
Adafruit_BME280::SAMPLING_X1, // pressão
Adafruit_BME280::SAMPLING_X1, // umidade
Adafruit_BME280::FILTER_OFF);
return true;
}
void setup() {
Serial.begin(115200);
prefs.begin("bme-node", false);
intervalS = prefs.getUInt("interval", 60);
if (!startSensor()) {
Serial.println("No BME280 found at 0x76 or 0x77. Check wiring.");
while (true) delay(1000);
}
clientId = "esp32-bme280-" + String((uint32_t)ESP.getEfuseMac(), HEX);
tTelemetry = String(PREFIX) + "/telemetry";
tIntervalSet = String(PREFIX) + "/config/interval/set";
tIntervalState = String(PREFIX) + "/config/interval";
tStatus = String(PREFIX) + "/status";
connectWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setBufferSize(512);
connectMqtt();
lastPublish = millis() - intervalS * 1000UL; // publica imediatamente
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWifi();
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
if (millis() - lastPublish < intervalS * 1000UL) return;
lastPublish = millis();
if (!bme.takeForcedMeasurement()) {
Serial.println("Forced measurement failed");
return;
}
float t = bme.readTemperature(); // graus C
float p = bme.readPressure() / 100.0f; // Pa -> hPa
float h = hasHumidity ? bme.readHumidity() : NAN;
char payload[200];
if (hasHumidity) {
snprintf(payload, sizeof(payload),
"{\"temperature\":%.2f,\"humidity\":%.1f,\"pressure\":%.1f,\"interval\":%lu}",
t, h, p, (unsigned long)intervalS);
} else {
snprintf(payload, sizeof(payload),
"{\"temperature\":%.2f,\"pressure\":%.1f,\"interval\":%lu}",
t, p, (unsigned long)intervalS);
}
mqtt.publish(tTelemetry.c_str(), payload);
Serial.println(payload);
}
Testando as leituras ESP32 BME280 MQTT e alterando o intervalo
Acompanhe tudo abaixo do seu prefixo:
mosquitto_sub -h broker.connectmq.com -p 1883 -i bme-view-5t8n -t "connectmq/demo/bme5t8n/#" -v
Agora mude o intervalo para 10 segundos com um comando retido:
mosquitto_pub -h broker.connectmq.com -p 1883 -i bme-cmd-5t8n -q 1 -r \
-t "connectmq/demo/bme5t8n/config/interval/set" -m "10"
Em instantes, config/interval informa 10 e a telemetria acelera. Reinicie a placa: ela lê 10 da flash e, mesmo que a flash fosse apagada, o comando retido o definiria de novo na conexão. Envie 2 e o dispositivo rejeita o valor e informa novamente o atual. Para remover o comando retido depois, publique uma mensagem retida vazia no mesmo topic com -r -n.
O servidor MQTT público (gratuito) é compartilhado, então qualquer pessoa poderia publicar no seu topic de comando se o adivinhasse. Isso é aceitável para uma demonstração ou protótipo, mas nunca para dados de produção, e é exatamente por isso que dispositivos de produção devem usar um broker próprio e autenticado.
Entendendo as leituras
- A pressão é informada no nível da estação, em hPa. Os serviços meteorológicos informam a pressão reduzida ao nível do mar, então seu número será menor em altitude. As tendências importam mais do que o valor absoluto: uma queda constante ao longo de algumas horas costuma anteceder piora no tempo.
- A altitude pode ser estimada com
bme.readAltitude(1013.25), mas ela só é tão boa quanto a pressão ao nível do mar que você informa e varia com o tempo. - O autoaquecimento é mínimo no modo forçado com taxas de amostragem baixas. No modo normal, com oversampling e tempo de standby curto, o chip e o ESP32 ao lado podem aquecer a leitura de forma perceptível.
Solução de problemas
- "No BME280 found". Rode um sketch scanner de I2C. Nenhum dispositivo significa problema de ligação; um dispositivo em endereço inesperado indica a configuração do pino SDO.
- Umidade sempre 0 ou NaN. Você tem um BMP280. A impressão do ID do chip confirma.
- Pressão cerca de 10 por cento errada. Você esqueceu de dividir os pascals por 100, ou está comparando com a pressão ao nível do mar.
- Comando de intervalo ignorado. Verifique se o topic confere exatamente, incluindo maiúsculas e minúsculas, e se o payload é um número simples.
- A placa reinicia durante a conexão Wi-Fi. Uma alimentação USB fraca causa brownout no ESP32. Use um cabo ou fonte melhor.
Configuração para produção
Para implantações reais, crie um broker em app.connectmq.com e mude o sketch para WiFiClientSecure na porta 8883 com o certificado ISRG Root X1, conectando com YOUR_CLIENT_ID, YOUR_USERNAME e YOUR_PASSWORD. Assim, apenas clientes com suas credenciais podem enviar comandos de intervalo. Guardar as credenciais de Wi-Fi no código é o próximo ponto fraco; o guia de configuração com WiFiManager mostra como informá-las por um captive portal. Se você está comparando sensores, o tutorial ESP32 DHT22 cobre a opção mais barata de fio único.
Armazenando e gerando gráficos dos dados meteorológicos
O broker entrega mensagens; ele não é um banco de dados. Para ter histórico do tempo, você precisa de um assinante que grave cada leitura em um lugar durável. Três padrões comuns funcionam bem com este payload:
- Telegraf com o input MQTT consumer assina
.../telemetry, interpreta os campos JSON e os grava no InfluxDB, prontos para gráficos no Grafana. - Node-RED assina com um nó
mqtt in, remodela a mensagem e grava em qualquer banco de dados para o qual tenha um nó, o que é prático se você também quer enviar notificações. - Um pequeno script Python usando paho-mqtt insere linhas no SQLite ou PostgreSQL. É a opção mais transparente quando você está aprendendo.
Os três se conectam ao broker como clientes MQTT comuns, com seus próprios client IDs, exatamente como o ESP32. No portal do ConnectMQ, eles aparecem ao lado dos seus dispositivos, e o explorador de topics ao vivo é uma forma rápida de confirmar a estrutura do JSON antes de escrever qualquer código de parsing. Como o dispositivo inclui o interval ativo em cada mensagem, seus gráficos também podem mostrar quando a taxa de amostragem mudou, o que evita confundir uma lacuna com uma queda.
Comece com o ConnectMQ
Um nó meteorológico só é tão útil quanto o broker que coleta seus dados. O ConnectMQ é um broker MQTT gerenciado com TLS, mensagens retidas, MQTT 3.1.1 e 5.0 e um portal com explorador de topics ao vivo e alertas por limite, para você ser avisado quando a pressão cair rápido. O plano gratuito Serverless não exige cartão de crédito. Se os conceitos são novos para você, leia o que faz um broker MQTT e depois explore a série completa tutorial de IoT com ESP32.
Perguntas frequentes
Qual é a diferença entre o BME280 e o BMP280?
O BME280 mede temperatura, umidade e pressão; o BMP280 mede apenas temperatura e pressão. Eles compartilham o encapsulamento e a biblioteca, e o ID do chip (0x60 versus 0x58) os diferencia.
Por que tornar o intervalo de publicação configurável via MQTT?
Isso permite amostrar mais rápido durante a depuração ou um evento e mais devagar no restante do tempo, sem regravar o firmware. Salvar o valor na flash mantém a configuração entre reinicializações.
O comando de intervalo deve ser retido?
Sim, para configurações. Um comando retido chega ao dispositivo mesmo que ele estivesse offline quando você o enviou, e o dispositivo informa o valor aplicado em seu próprio topic retido.
Posso alimentar o nó BME280 com bateria?
Sim. Use deep sleep entre as leituras, mantenha o sensor no modo forçado e conecte ao Wi-Fi apenas para publicar. O tempo de Wi-Fi domina o consumo de energia.



