Um Arduino Uno ou Mega com Ethernet Shield continua sendo uma das formas mais confiáveis de colocar um sensor ou relé no MQTT: sem Wi-Fi para cair, sem interferência de rádio e com um link cabeado que simplesmente funciona dentro de um quadro ou no porão. A pilha padrão é a biblioteca Ethernet para o chip da WIZnet mais o PubSubClient para MQTT.
Este tutorial conecta um shield W5100 ou W5500 a um broker, publica o valor de um sensor e aceita comandos, com um loop de reconexão que sobrevive a cabos desconectados e reinícios do broker. Depois você verifica tudo de ponta a ponta pelo navegador.
Hardware e bibliotecas
| Peça | Observações |
|---|---|
| Arduino Uno R3 ou Mega 2560 | Uno: 2 KB de RAM, Mega: 8 KB de RAM |
| Ethernet Shield W5100 ou Ethernet Shield 2 (W5500) | Usa SPI, chip select no pino 10 |
| Cabo de rede até um roteador com DHCP | Ou um IP estático na sua LAN |
Instale duas bibliotecas pelo Library Manager: Ethernet (da Arduino, suporta W5100, W5200 e W5500) e PubSubClient (de Nick O'Leary). A maioria dos shields também tem um slot microSD com chip select no pino 4; se não usar cartão, coloque o pino 4 em nível alto para não interferir no barramento SPI.
O sketch completo
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Use um MAC único por placa na sua rede
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x10, 0x01 };
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* CLIENT_ID = "arduino-eth-0001";
const char* MQTT_USER = "username";
const char* MQTT_PASS = "password";
const char* T_STATUS = "testmqtt/arduino-eth/0001/status";
const char* T_TEMP = "testmqtt/arduino-eth/0001/temp";
const char* T_CMD = "testmqtt/arduino-eth/0001/cmd/led";
EthernetClient ethClient;
PubSubClient mqtt(ethClient);
unsigned long lastReconnect = 0;
unsigned long lastPublish = 0;
void onMessage(char* topic, byte* payload, unsigned int length) {
Serial.print(F("<- "));
Serial.print(topic);
Serial.print(F(" = "));
Serial.write(payload, length);
Serial.println();
if (strcmp(topic, T_CMD) == 0 && length > 0) {
digitalWrite(LED_BUILTIN, payload[0] == '1' ? HIGH : LOW);
}
}
bool connectMqtt() {
Serial.print(F("MQTT connecting... "));
// id, user, pass, willTopic, willQos, willRetain, willMessage
if (mqtt.connect(CLIENT_ID, MQTT_USER, MQTT_PASS, T_STATUS, 1, true, "offline")) {
Serial.println(F("connected"));
mqtt.publish(T_STATUS, "online", true);
mqtt.subscribe(T_CMD, 1);
return true;
}
Serial.print(F("failed, rc="));
Serial.println(mqtt.state());
return false;
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(4, OUTPUT);
digitalWrite(4, HIGH); // desseleciona o cartão SD
Serial.begin(9600);
Ethernet.init(10); // chip select do W5100/W5500
Serial.println(F("DHCP..."));
if (Ethernet.begin(mac) == 0) {
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println(F("No Ethernet shield found"));
} else {
Serial.println(F("DHCP failed - check the cable or use a static IP"));
}
while (true) delay(1000);
}
Serial.print(F("IP: "));
Serial.println(Ethernet.localIP());
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(30);
}
void loop() {
if (!mqtt.connected()) {
unsigned long now = millis();
if (now - lastReconnect > 5000) { // tenta de novo a cada 5 s sem bloquear
lastReconnect = now;
connectMqtt();
}
} else {
mqtt.loop();
}
if (mqtt.connected() && millis() - lastPublish > 10000) {
lastPublish = millis();
char buf[8];
int raw = analogRead(A0);
itoa(raw, buf, 10);
mqtt.publish(T_TEMP, buf);
}
Ethernet.maintain(); // renova a concessão DHCP
}Algumas escolhas deste sketch importam mais do que parecem:
- Strings
F("...")mantêm texto constante na flash em vez da RAM. No Uno, uma dúzia de strings de depuração pode consumir um quarto da memória. - A reconexão não bloqueia. O
loop()continua rodando entre as tentativas, então a lógica local (botões, relés, um watchdog) não congela enquanto o broker está inacessível. mqtt.loop()precisa rodar com frequência. Ele lê os pacotes recebidos e envia o PINGREQ do keep alive. Umdelay()longo no seu código faz o broker derrubar a conexão após 1,5 × keep alive.- Last Will e um
onlineretained dão a qualquer dashboard um indicador confiável de online/offline. Veja MQTT Last Will.
IP estático em vez de DHCP
Redes industriais muitas vezes não têm DHCP. Passe explicitamente o endereço, o servidor DNS, o gateway e a máscara; sem DNS o hostname broker.connectmq.com não pode ser resolvido:
IPAddress ip(192, 168, 1, 50);
IPAddress dns(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
Ethernet.begin(mac, ip, dns, gateway, subnet);Se não houver DNS, mqtt.setServer() também aceita um IPAddress, mas aí uma mudança de IP do broker quebra todos os dispositivos. Prefira um hostname sempre que a rede permitir.
Teste de ponta a ponta
- Abra o Monitor Serial a 9600 baud. Você deve ver um endereço IP e depois
MQTT connecting... connected. - Abra o cliente MQTT online, conecte em
broker.connectmq.comvia WebSocket seguro (porta 8084, path/mqtt) e assinetestmqtt/arduino-eth/0001/#. - O
status = onlineretained chega na hora, seguido de um valortempa cada dez segundos. - Publique
1emtestmqtt/arduino-eth/0001/cmd/led. O LED da placa acende e o Monitor Serial imprime a mensagem. Publique0para apagar. - Desconecte o cabo de rede. Depois de uns 45 segundos o broker envia o will e o status muda para
offline. Conecte de novo: o sketch reconecta em poucos segundos e publicaonlineoutra vez.
Acompanhe o Arduino no cliente online
Se preferir testar pelo terminal, mosquitto_pub e mosquitto_sub fazem o mesmo trabalho: mosquitto_sub -h broker.connectmq.com -t 'testmqtt/arduino-eth/#' -v.
Entendendo mqtt.state()
Quando connect() falha, state() diz o motivo. Estes são os códigos definidos pelo PubSubClient:
| Código | Constante | Significado |
|---|---|---|
| -4 | MQTT_CONNECTION_TIMEOUT | Sem resposta dentro do keep alive |
| -3 | MQTT_CONNECTION_LOST | A conexão de rede caiu |
| -2 | MQTT_CONNECT_FAILED | Falha na conexão TCP (DNS, IP, porta, firewall) |
| -1 | MQTT_DISCONNECTED | Desconectado de forma limpa |
| 1 | MQTT_CONNECT_BAD_PROTOCOL | O broker rejeitou a versão do protocolo |
| 2 | MQTT_CONNECT_BAD_CLIENT_ID | Client ID rejeitado |
| 3 | MQTT_CONNECT_UNAVAILABLE | Broker indisponível |
| 4 | MQTT_CONNECT_BAD_CREDENTIALS | Usuário ou senha incorretos |
| 5 | MQTT_CONNECT_UNAUTHORIZED | Não autorizado |
failed, rc=-2 é de longe o mais comum: o shield nunca alcançou o broker via TCP. Confira se Ethernet.localIP() não é 0.0.0.0, se o servidor DNS está acessível e se a porta 1883 de saída está liberada. O guia de problemas do PubSubClient detalha cada código.
Limites de memória e de tamanho de pacote
O PubSubClient aloca um único buffer para pacotes de entrada e de saída. O tamanho padrão é 256 bytes, incluindo o cabeçalho fixo, o topic e o payload. Um pacote maior que o buffer é descartado silenciosamente na recepção, e publish() retorna false no envio.
mqtt.setBufferSize(512); // chame uma vez em setup(), antes de connect()No Uno, mantenha o buffer modesto e os topics curtos. Você pode calcular exatamente o tamanho de um pacote com a calculadora de tamanho de pacote. No Mega, buffers de 1–2 KB são realistas. Se um payload grande continuar falhando, o guia de pacote grande demais cobre também os limites do lado do broker.
Outros limites para levar em conta:
- Apenas publicações QoS 0. O PubSubClient publica com QoS 0 e assina com QoS 0 ou 1. Use um topic de estado retained ou uma confirmação na aplicação quando a entrega importar.
- Apenas MQTT 3.1.1. Sem reason codes nem propriedades do MQTT 5.
- Quantidade de sockets. O W5100 tem 4 sockets e o W5500 tem 8. DHCP e DNS usam um brevemente, então um sketch que também roda um servidor web pode ficar sem sockets.
Quando ir além do Uno
A combinação Uno + Ethernet é perfeita para dispositivos locais, simples e cabeados. Se você precisa de TLS, payloads JSON maiores ou recursos do MQTT 5, o mesmo código do PubSubClient migra quase sem mudanças para um ESP32 com W5500 ou para placas Wi-Fi: veja ESP32 com Arduino e MQTT. Se quer publicar com QoS 1 em AVR, vale conhecer a biblioteca arduino-mqtt.
Um Arduino cabeado no MQTT é principalmente questão de disciplina: MAC e client ID únicos, strings F(), reconexão não bloqueante e um loop que nunca trava. Faça o log serial mostrar connected, comprove publish, comandos e Last Will pelo cliente online, e a placa vai rodar sem intervenção por anos.
Perguntas frequentes
Um Arduino Uno consegue usar MQTT sobre TLS?
Na prática, não. O ATmega328P tem 2 KB de RAM, pouco demais para um handshake TLS, e os chips W5100/W5500 não fazem TLS sozinhos. Use MQTT sem criptografia em uma rede confiável, um gateway local que encaminhe via TLS, ou passe para um ESP32 ou uma placa com mais memória.
Por que o PubSubClient não recebe minhas mensagens maiores?
O PubSubClient descarta qualquer pacote maior que o buffer, que por padrão tem 256 bytes incluindo topic e cabeçalhos. Chame setBufferSize() com um valor maior, lembrando que no Uno cada byte sai dos 2 KB de RAM.
O PubSubClient publica com QoS 1?
Não. O PubSubClient publica apenas com QoS 0 e suporta MQTT 3.1.1. Ele pode assinar com QoS 0 ou 1. Se você precisa de publicações QoS 1, adicione uma confirmação na aplicação ou use outra biblioteca.

