IoT industrial

Report by Exception (RBE) em Sistemas MQTT: Bandas Mortas, Heartbeats e um Exemplo de Gateway

O report by exception publica um valor apenas quando ele muda de forma significativa. Entenda como bandas mortas, limites de taxa e heartbeats funcionam juntos e rode um gateway Python que aplica RBE antes de publicar no MQTT.

Gráfico comparando amostras lidas por polling com publicações por report by exception após um filtro de banda morta em um gateway 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

Report by exception (RBE) é a prática de publicar um valor apenas quando ele muda de forma significativa, em vez de enviar cada amostra lida. É uma das técnicas mais simples e eficazes em sistemas MQTT industriais. Neste artigo você vai entender como bandas mortas, intervalos mínimos e heartbeats se encaixam e vai rodar um gateway Python que aplica RBE antes de publicar em um broker.

Polling vs Report by Exception

Sistemas SCADA tradicionais e muitas integrações Modbus ou OPC UA funcionam por polling: cada tag é lida em um ciclo fixo e cada resultado é encaminhado. É simples, mas a maioria dos valores industriais muda devagar. A temperatura de um tanque lida a cada segundo durante uma hora gera 3.600 mensagens, que podem conter apenas um punhado de mudanças significativas.

O MQTT foi projetado para o estilo oposto. Os clientes publicam quando algo acontece, e o broker entrega o novo valor a todos os assinantes. O RBE combina os dois: o gateway continua fazendo polling do dispositivo localmente (porque o protocolo do dispositivo exige), mas só publica para cima quando o valor mudou o suficiente para importar.

Abordagem Mensagens enviadas Atualização Risco típico
Publicação em intervalo fixo Toda amostra Previsível Desperdício de banda, armazenamento e processamento
Report by exception puro Só quando muda Imediata nas mudanças Um valor silencioso parece igual a um dispositivo morto
RBE com heartbeat Mudanças mais atualização periódica Imediata, com defasagem limitada Exige um pouco mais de lógica

A terceira linha é o que você quer na prática.

Os Componentes do Report by Exception

Banda Morta

A banda morta (deadband) é a variação mínima que conta como "diferente". Ela filtra o ruído do sensor e derivas insignificantes.

  • Banda morta absoluta: publique se a diferença absoluta entre o novo valor e o último valor publicado for de pelo menos, por exemplo, 0,5 grau.
  • Banda morta percentual: publique se a variação ultrapassar uma porcentagem do último valor ou, de forma mais robusta, da faixa do instrumento. A porcentagem sobre a faixa é mais segura perto de zero, onde uma porcentagem do valor faria flutuações minúsculas parecerem enormes.

Compare sempre com o último valor publicado, não com a última amostra lida. Comparar com a amostra anterior deixa uma deriva lenta passar despercebida para sempre, porque cada passo fica abaixo da banda morta.

Intervalo Mínimo (Limite de Taxa)

Um sinal ruidoso ou oscilante ainda pode cruzar a banda morta repetidamente. Um intervalo mínimo, por exemplo um segundo por tag, limita a taxa. Configure com cuidado para alarmes e mudanças de estado: normalmente você os quer na hora, então dê a eles intervalo mínimo zero.

Heartbeat (Intervalo Máximo)

Se um valor nunca muda, o RBE nunca o publica, e os consumidores não conseguem distinguir "estável" de "gateway morto". Um heartbeat publica o valor atual após um silêncio máximo, por exemplo a cada cinco minutos, mesmo sem mudança. Combinado com um Last Will do MQTT no topic de status do gateway, os assinantes sempre conseguem distinguir um valor quieto de uma conexão perdida.

Mudanças de Qualidade

Uma mudança na qualidade do dado (uma leitura de sensor falhando, um dispositivo ficando offline) deve ser sempre publicada, independentemente da banda morta. Um valor "GOOD" desatualizado que na verdade vem de um dispositivo com falha é pior do que nenhum valor.

Escolhendo Bandas Mortas: Diretrizes Práticas

  1. Parta da precisão do instrumento. Uma banda morta menor que a precisão do sensor só encaminha ruído.
  2. Pergunte ao consumidor de que resolução ele precisa. Um dashboard que mostra graus inteiros não precisa de variações de 0,01 grau.
  3. Use banda morta zero para valores discretos: estados, modos, alarmes, contadores e setpoints.
  4. Cuidado com totalizadores e contadores de energia. Use uma banda morta na unidade do contador e um heartbeat regular para que cálculos do tipo faturamento continuem precisos.
  5. Revise as bandas mortas após a entrada em produção comparando a taxa de mensagens por tag; algumas poucas tags "tagarelas" costumam dominar o tráfego.

Um Gateway Python com Report by Exception

Este exemplo simula cinco tags lidas a cada 500 ms, aplica regras de banda morta, intervalo mínimo e heartbeat por tag e publica as mudanças como mensagens JSON retidas no servidor MQTT público broker.connectmq.com, feito para testes. Ele imprime quantas amostras foram lidas versus quantas mensagens foram enviadas, para você ver o efeito diretamente. Usa paho-mqtt 2.x e Python 3.10 ou mais recente.

import json
import random
import time
from dataclasses import dataclass
from datetime import datetime, timezone

import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
PORT = 1883
CLIENT_ID = "rbe-gateway-6e41"
BASE = "connectmq/demo/rbe-6e41/plant1/boiler1"
POLL_S = 0.5


@dataclass
class Tag:
    name: str
    deadband: float            # absoluta, em unidades de engenharia (0 = qualquer mudança)
    min_interval: float = 1.0  # segundos entre publicações
    heartbeat: float = 300.0   # segundos, publica mesmo sem mudança
    last_value: float | None = None
    last_quality: str | None = None
    last_ts: float = 0.0

    def should_publish(self, value, quality, now):
        if self.last_value is None or quality != self.last_quality:
            return True                                   # primeiro valor ou mudança de qualidade
        if now - self.last_ts >= self.heartbeat:
            return True                                   # heartbeat
        if now - self.last_ts < self.min_interval:
            return False                                  # limite de taxa
        if self.deadband == 0:
            return value != self.last_value
        return abs(value - self.last_value) >= self.deadband

    def mark(self, value, quality, now):
        self.last_value, self.last_quality, self.last_ts = value, quality, now


TAGS = {
    "temperature_c": Tag("temperature_c", deadband=0.5),
    "pressure_bar":  Tag("pressure_bar", deadband=0.05),
    "flow_m3h":      Tag("flow_m3h", deadband=0.2),
    "burner_on":     Tag("burner_on", deadband=0, min_interval=0),
    "alarm_code":    Tag("alarm_code", deadband=0, min_interval=0),
}

state = {"temperature_c": 82.0, "pressure_bar": 3.20, "flow_m3h": 12.0,
         "burner_on": 1, "alarm_code": 0}


def poll_device():
    """Simula a leitura de um dispositivo: pequeno ruído e mudanças reais ocasionais."""
    state["temperature_c"] += random.gauss(0, 0.08)
    state["pressure_bar"] += random.gauss(0, 0.008)
    state["flow_m3h"] += random.gauss(0, 0.03)
    if random.random() < 0.005:
        state["burner_on"] = 1 - state["burner_on"]
    quality = "BAD" if random.random() < 0.002 else "GOOD"
    return {k: round(v, 3) for k, v in state.items()}, quality


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    client.publish(f"{BASE}/status", "online", qos=1, retain=True)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
client.on_connect = on_connect
client.will_set(f"{BASE}/status", "offline", qos=1, retain=True)
client.connect(BROKER, PORT, keepalive=60)
client.loop_start()

samples = sent = 0
started = time.monotonic()
try:
    while True:
        values, quality = poll_device()
        now = time.monotonic()
        for name, value in values.items():
            samples += 1
            tag = TAGS[name]
            if tag.should_publish(value, quality, now):
                payload = {
                    "v": value,
                    "q": quality,
                    "ts": datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
                }
                client.publish(f"{BASE}/{name}", json.dumps(payload), qos=1, retain=True)
                tag.mark(value, quality, now)
                sent += 1
        if now - started >= 60:
            print(f"last 60 s: {samples} samples read, {sent} messages published "
                  f"({100 * sent / samples:.1f} %)")
            samples = sent = 0
            started = now
        time.sleep(POLL_S)
except KeyboardInterrupt:
    client.loop_stop()
    client.disconnect()

Acompanhe o resultado:

mosquitto_sub -h broker.connectmq.com -p 1883 -i rbe-6e41-viewer -t "connectmq/demo/rbe-6e41/#" -v

A linha de resumo, exibida uma vez por minuto, mostra quanto dos dados lidos realmente precisou ser enviado. Com bandas mortas realistas, a redução é grande, e as mensagens que sobram são as que carregam informação. Ajuste as bandas mortas e observe a proporção mudar.

Por Que Retained Messages Importam no Report by Exception

Quando os valores são publicados apenas quando mudam, um assinante que se conecta agora poderia esperar minutos pela próxima atualização. Publicar cada tag como retained message resolve isso: o broker guarda o último valor de cada topic e o entrega imediatamente a todo novo assinante. Para isso, um topic por tag funciona melhor do que um grande bloco JSON, porque cada tag mantém seu próprio último valor retido. Se quiser revisar as garantias de entrega, veja níveis de QoS do MQTT explicados.

RBE no Sparkplug B

O report by exception faz parte da especificação do Sparkplug B. Os edge nodes enviam um birth certificate completo com todas as métricas quando se conectam e depois enviam apenas as métricas alteradas nas mensagens DATA. Se uma aplicação host perder o controle do estado, ela solicita um rebirth. Isso dá ao Sparkplug o mesmo comportamento de "estado completo na conexão, mudanças depois" que retained messages mais RBE oferecem no MQTT simples. A ConnectMQ consegue decodificar payloads Sparkplug B e republicá-los em topics ISA-95 legíveis, então o tráfego Sparkplug otimizado por RBE continua fácil de inspecionar.

Onde o RBE Mais Compensa

  • Links tarifados ou limitados. Em conexões celulares e via satélite, como as de fazendas, mineradoras e estações remotas de saneamento, cada byte conta. O RBE costuma ser a primeira otimização a aplicar, como descrito em MQTT sobre rede celular.
  • Buffers de store-and-forward. Menos mensagens significam que uma queda enche o buffer mais devagar e o reenvio é mais rápido; veja store and forward para MQTT.
  • Historiadores e armazenamento. Dados baseados em mudança comprimem bem e ainda reconstroem o sinal dentro da banda morta, o que também basta para manter um gêmeo digital atualizado.
  • Cotas do broker. Limites de taxa de mensagens e de conexões existem em todo broker. No servidor MQTT público, por exemplo, cada cliente pode enviar 10 mensagens por segundo.

O RBE normalmente é implementado em um gateway de borda IIoT, ao lado dos drivers de protocolo. Para o panorama arquitetural mais amplo, veja o guia de IoT industrial com MQTT.

Erros Comuns

  1. Sem heartbeat. Os consumidores não conseguem distinguir um valor estável de um gateway morto.
  2. Comparar com a amostra anterior em vez do último valor publicado, deixando a deriva passar despercebida.
  3. Bandas mortas em sinais discretos. Uma mudança de estado de 1 para 2 nunca pode ser filtrada.
  4. Não reter os valores. Novos assinantes não veem nada até a próxima mudança.
  5. Uma única banda morta para todas as tags. Ajuste por tag, com base na precisão e nas necessidades dos consumidores.

Publique de Forma Mais Inteligente com a ConnectMQ

O RBE reduz o que você envia; um broker confiável garante que o que você envia chegue. A ConnectMQ é um broker MQTT gerenciado com retained messages, MQTT 5, TLS e um explorador de topics ao vivo para conferir o tráfego por tag, e você pode testar o exemplo acima antes no servidor MQTT público gratuito. Quando estiver pronto, crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito.

Perguntas frequentes

O que é report by exception, em termos simples?

Significa enviar um valor apenas quando ele muda de forma significativa, em vez de a cada leitura. A banda morta define o que é "significativo", e um heartbeat garante que o valor ainda seja enviado de tempos em tempos.

O report by exception perde dados?

Ele descarta, por definição, variações menores que a banda morta. Escolha as bandas mortas com base na precisão do sensor e no que os consumidores realmente precisam, e mantenha os dados brutos localmente se os engenheiros puderem precisar da resolução completa depois.

Qual é um bom intervalo de heartbeat?

As escolhas comuns vão de um a quinze minutos. Ele deve ser menor que o tempo após o qual um consumidor consideraria um valor desatualizado.

Alarmes devem usar report by exception?

Sim, com banda morta zero e sem intervalo mínimo, para que toda transição seja publicada imediatamente.

Artigos relacionados

Cliente MQTT no CODESYS: Publicando Variáveis do CLP em um Broker

Configure a biblioteca IIoT MQTT Client do CODESYS para publicar variáveis do CLP em JSON, verifique-as no servidor MQTT público broker.connectmq.com e depois migre para um broker privado com credenciais e TLS.

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.