
ESP32 enviando dados para SCADA e MES
O SCADA não quer o seu JSON: ele quer tags. Modelagem de tópicos, qualidade do dado, heartbeat e por que o timestamp do gateway não serve.
Requisitos de hardware:
- Placa ESP32 ou ESP32-S3
- Um broker MQTT alcançável pela rede da planta
- Um SCADA ou plataforma que consuma MQTT
Baixe o material deste tutorial:
- Projeto completo (ZIP)143 KBplatformio.ini, src/main.cpp comentado, include/config.h com a hierarquia de tópicos, README e o manual em PDF.
- Manual em PDF (5 páginas)171 KBModelagem de tópicos, carimbo de tempo, tabela de qualidade, heartbeat, retenção e checklist de entrega.
Quer receber novos manuais e esquemas industriais? Entre no nosso canal do Telegram / WhatsApp
O erro de partida: achar que basta publicar
Fazer o ESP32 publicar num tópico MQTT é a parte de dez minutos. Quase todo tutorial para aí, e é por isso que tanta integração morre na primeira reunião com quem opera o SCADA.
O SCADA não quer o seu JSON. Ele trabalha com tags: um ponto nomeado, com tipo definido, unidade, faixa, qualidade e estampa de tempo. Quem opera espera abrir a tela e ver PAINEL_03.MOTOR_A.TEMPERATURA com valor, unidade e um indicador de que aquele número é confiável agora.
Entregar um JSON com nomes de campo inventados por quem escreveu o firmware transfere para o outro lado o trabalho de adivinhar o que é cada coisa. E é aí que a integração trava.
Este artigo trata do que vem depois do publish funcionar.
Modelagem de tópicos: o contrato entre os dois lados
A hierarquia do tópico é a primeira decisão, e a mais difícil de mudar depois que estiver em produção.
A convenção que funciona vai do geral ao específico, com um nível fixo por dimensão:
| 1 | planta/area/linha/equipamento/grandeza |
| 2 | usina1/moagem/linha2/motor03/temperatura |
| 3 | usina1/moagem/linha2/motor03/corrente |
| 4 | usina1/utilidades/compressor01/pressao |
Três regras que evitam retrabalho.
Nunca ponha valor no nome do tópico. Um tópico por grandeza, e o valor na carga. Colocar motor03/temperatura/78 gera um tópico novo a cada leitura e destrói qualquer assinatura por curinga.
Use identificadores estáveis, não descrições. O motor 03 continua sendo o 03 quando trocarem o modelo. motor-weg-w22-2024 vira mentira na primeira manutenção.
Sem acento, sem espaço, sem maiúscula misturada. Broker aceita, e vira dor de cabeça em toda ferramenta que consome depois.
O carimbo de tempo do gateway não serve
Este é o ponto que separa uma integração que passa em auditoria de uma que não passa.
O SCADA carimba o dado quando ele chega. Se a rede engasgou por dois minutos, todas as leituras daquele intervalo chegam juntas e recebem o mesmo horário — e o histórico passa a mostrar um degrau que nunca existiu no processo.
Pior: se você tem buffer offline, seis horas de dados guardados chegam com o horário da reconexão. O gráfico fica inútil justamente no período que interessava investigar.
A carga precisa carregar o instante da leitura:
| 1 | { |
| 2 | "t": 1755990000, |
| 3 | "v": 78.4, |
| 4 | "q": 192, |
| 5 | "u": "C" |
| 6 | } |
Campos curtos porque a carga se repete milhares de vezes por dia, e cada byte multiplica. O t é o instante da leitura em segundos desde 1970, o v é o valor, o q é a qualidade e o u a unidade. Para variáveis rápidas, publique t em milissegundos e combine essa unidade com quem configura o SCADA.
E aqui volta o problema do artigo de buffer: o ESP32 não tem relógio ao ligar. Sem NTP sincronizado, ele não pode carimbar. A saída honesta é publicar a qualidade como incerta enquanto o relógio não estiver sincronizado — nunca inventar um horário.
Qualidade do dado: o campo que ninguém preenche
Todo protocolo industrial sério tem qualidade, e é o campo que mais é ignorado por quem vem do lado web.
A razão é simples: zero é um valor válido. Se o sensor queimou e o firmware publica 0, o SCADA mostra zero graus e o operador acredita. Com qualidade, ele mostra zero marcado como ruim, e a diferença é entre um alarme falso e uma decisão errada.
A convenção herdada do OPC usa três faixas:
| 1 | 192 Boa leitura normal, dentro do esperado |
| 2 | 64 Incerta valor antigo, relógio sem NTP, sensor fora de faixa |
| 3 | 0 Ruim falha de leitura, sensor sem resposta, barramento mudo |
Publicar sempre 192 é o mesmo que não ter o campo. O valor de qualidade está em ele mudar.
Heartbeat: distinguir parado de morto
Um valor que não muda há dez minutos pode significar duas coisas opostas: o processo está estável, ou o gateway morreu.
Sem heartbeat, o SCADA não tem como saber. E a resposta errada custa caro nos dois sentidos — alarme falso quando está tudo bem, ou silêncio quando a planta parou.
A solução são duas camadas.
O Last Will, do artigo de MQTT, cobre a morte súbita: o broker publica offline por você quando a conexão cai sem despedida. Para funcionar, o Last Will precisa ser retido e publicado no mesmo tópico de status descrito abaixo: assim ele sobrescreve o último "online" que ficou guardado no broker.
E um tópico de status periódico, publicado a cada trinta segundos com retenção, cobre o resto:
| 1 | usina1/moagem/linha2/gw01/status |
| 2 | {"t":1755990000,"online":true,"uptime":86400,"rssi":-62,"fw":"1.4.2","reset":"POWERON"} |
O reset vem do artigo de watchdog, e vale ouro no lado do SCADA: quando um gateway começa a reportar TASK_WDT repetidamente, a manutenção descobre antes de o equipamento parar.
Configure no SCADA um alarme por falta de atualização: se o t do status ficar mais velho que dois minutos, o gateway sumiu. Alarme por "tópico ausente" não serve aqui, porque com retenção o broker devolve o último status mesmo com o gateway morto.
Retenção: o que o SCADA vê ao reiniciar
Mensagem MQTT comum é entregue e esquecida. Se o SCADA reiniciar, ele fica sem valor nenhum até a próxima publicação — e num sensor de temperatura ambiente que publica a cada cinco minutos, isso são cinco minutos de tela vazia.
A flag de retenção faz o broker guardar a última mensagem de cada tópico e entregá-la a quem se inscrever. O SCADA sobe e já tem o último valor conhecido.
A regra prática: retenha estado, não retenha evento.
Retenha o valor de processo, porque o último valor conhecido tem significado, e o status do gateway, que é estado por definição.
Não retenha alarme momentâneo, porque reentregar alarme velho confunde o operador, nem comando — um comando reentregue reexecuta a ação.
Esse último caso é perigoso de verdade. Comando retido pode reacionar um equipamento quando o SCADA reconectar.
TLS na planta: o que muda
A comunicação com o SCADA quase sempre exige TLS, e no ESP32 isso tem três consequências práticas.
Custa RAM. O handshake consome dezenas de KB. Num firmware apertado, o sintoma é reinício por falta de memória, e não erro de rede — as pessoas perdem horas procurando problema de conectividade.
Exige relógio. Validar certificado depende de saber a data: um certificado válido parece expirado se a placa acha que é 1970. Sincronize NTP antes de tentar o TLS.
Exige o certificado raiz. Ele vai embutido no firmware, e vence. Um dia a conexão para de funcionar sozinha, sem ninguém ter mexido em nada. Anote a data de validade junto da documentação do equipamento — é o tipo de coisa que ninguém lembra três anos depois.
Sparkplug B: quando vale a pena
Se o seu SCADA suporta, vale conhecer. O Sparkplug B padroniza justamente o que este artigo vem descrevendo: hierarquia de tópicos, tipos de dados, qualidade, nascimento e morte de dispositivos.
A vantagem real é o birth certificate: ao conectar, o dispositivo publica a lista completa de suas tags com tipo e unidade. O SCADA descobre sozinho o que aquele gateway oferece, sem ninguém cadastrar tag na mão.
O custo é a carga binária, que exige biblioteca dos dois lados e é bem mais pesada no ESP32 que um JSON curto.
A recomendação honesta: se o seu SCADA já fala Sparkplug e você tem dezenas de gateways, compensa. Para poucos dispositivos com JSON bem modelado, não compensa a complexidade.
Tutorial: do publish à tag na tela
Passo 1: combinar a modelagem antes de codificar
Sente-se com quem opera o SCADA antes de escrever a primeira linha. Defina a hierarquia de tópicos, os nomes das grandezas e as unidades.
Mudar isso depois significa reconfigurar o SCADA e reprogramar todos os gateways. É o passo mais barato de fazer e o mais caro de pular.
Passo 2: sincronizar o relógio antes de tudo
Configure NTP e espere a sincronização antes de publicar ou de abrir TLS. Enquanto não sincronizar, publique com qualidade incerta e sem o campo t: nunca invente um horário.
Passo 3: publicar uma grandeza só
Comece com um tópico. Confirme no SCADA que a tag aparece com valor, unidade e horário corretos.
Uma grandeza funcionando ponta a ponta vale mais que vinte publicando errado.
Passo 4: acrescentar qualidade e testar a degradação
Desconecte o sensor de propósito e confirme que o valor chega com qualidade ruim, e que o SCADA mostra isso em vez de exibir zero como se fosse leitura.
Se o SCADA não distingue, o problema está na configuração da tag do lado dele.
Passo 5: heartbeat e alarme por ausência
Publique o status periódico e configure no SCADA o alarme por falta de atualização do tópico. Depois desligue o gateway e cronometre até o alarme disparar.
Esse é o teste que prova que uma falha silenciosa será percebida.
Passo 6: ligar TLS
Só depois que tudo funciona em texto claro. Embuta o certificado raiz, confirme que o relógio está sincronizado e observe o heap livre depois da conexão.
Se a placa reiniciar aqui, é memória, não rede.
Passo 7: testar a reconexão com dado acumulado
Derrube o broker por alguns minutos com o gateway lendo. Ao voltar, confirme que os dados chegam com o horário da leitura, e não o da reconexão.
É o teste que valida o carimbo de tempo, e o que quase ninguém faz.
Erros comuns e como resolver
O SCADA não reconhece o dado. Modelagem. O JSON precisa ser combinado antes; nomes inventados no firmware não viram tag sozinhos.
Todos os valores com o mesmo horário. O carimbo está sendo posto na chegada. Publique o instante da leitura na carga.
O operador viu zero e acreditou. Falta o campo de qualidade, ou o SCADA não está configurado para exibi-lo.
Alarme falso quando o valor não muda. Falta heartbeat. Configure alarme por falta de atualização do tópico de status, não por valor parado.
O SCADA reinicia e fica sem dado. Falta retenção nos tópicos de estado.
Um comando foi reexecutado sozinho. Comando publicado com retenção. Nunca retenha comando.
A conexão TLS parou de funcionar sem ninguém mexer. Certificado raiz vencido, ou relógio sem sincronizar.
Reinicia ao ligar TLS. RAM do handshake. Use placa com PSRAM ou reduza o firmware.
