Fundamentos de MQTT

Opções de Assinatura do MQTT 5: No Local, Retain As Published e Retain Handling

O que cada opção de assinatura do MQTT 5 faz, quando usá-la e como configurá-la no MQTT.js e no paho-mqtt, com exemplos executáveis no servidor MQTT público com MQTT 5.

Diagrama das opções de assinatura MQTT mostrando os flags No Local, Retain As Published e Retain Handling em um pacote SUBSCRIBE
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

As opções de assinatura MQTT são flags por assinatura introduzidos no MQTT 5.0 que controlam como o broker entrega as mensagens a você: se você recebe as próprias publicações, se o flag de retenção é preservado e se as retained messages são reenviadas quando você se inscreve. Ao final deste artigo você vai saber o que cada opção faz, quais problemas reais ela resolve e como configurá-la em JavaScript e Python.

Onde ficam as opções de assinatura

No MQTT 3.1.1, um pacote SUBSCRIBE carrega um filtro de topic e um byte com o QoS solicitado. O MQTT 5.0 mantém esse byte, mas usa os bits livres para novas opções. Cada filtro de topic em um SUBSCRIBE tem seu próprio byte de opções:

Bits Opção Valores
0–1 Maximum QoS 0, 1 ou 2
2 No Local 0 = desligado, 1 = ligado
3 Retain As Published 0 = desligado, 1 = ligado
4–5 Retain Handling 0, 1 ou 2
6–7 Reservado deve ser 0

Como as opções são por filtro, um mesmo cliente pode se inscrever em chat/room1 com No Local ligado e em config/# com outro comportamento de retenção no mesmo pacote. As definições completas estão na especificação OASIS MQTT 5.0. As opções só existem quando o cliente se conecta com a versão 5 do protocolo; uma conexão 3.1.1 não consegue expressá-las. Para o contexto do restante da versão, veja a visão geral dos recursos do MQTT 5.

Maximum QoS

É o QoS conhecido do MQTT 3.1.1, renomeado para deixar o significado claro. É o QoS mais alto com que o broker vai entregar mensagens nessa assinatura. Uma mensagem publicada com QoS 2 em uma assinatura com Maximum QoS 1 chega com QoS 1. Uma mensagem publicada com QoS 0 nunca é promovida. Se os níveis de QoS do MQTT são novidade para você, leia esse guia primeiro, porque a regra de rebaixamento é a origem de muitas surpresas na entrega.

No Local: pare de receber as próprias mensagens

Por padrão, se um cliente publica em um topic no qual também está inscrito, o broker devolve a mensagem para ele. Com No Local ativado, o broker não encaminha a mensagem para a conexão que a publicou.

Casos típicos:

  • Aplicativos de chat e colaboração. Todo participante publica e se inscreve em chat/room1. Sem No Local, cada usuário vê a própria mensagem duas vezes: uma pela interface local e outra pelo eco do broker.
  • Sincronização bidirecional. Um serviço que publica e escuta em devices/+/config acabaria processando as próprias atualizações e poderia entrar em loop.
  • Bridges. Uma bridge que se inscreve e republica no mesmo broker, como a que liga o broker local de uma usina a um broker na nuvem, pode criar loops de mensagens; No Local no lado da bridge os interrompe.

Duas regras para lembrar. No Local funciona por conexão, identificada pelo client ID, então dois clientes diferentes executados pela mesma aplicação continuam recebendo as mensagens um do outro. E é erro de protocolo definir No Local em uma assinatura compartilhada ($share/...), porque "local" não tem significado para um grupo.

Retain As Published

Quando o broker encaminha uma mensagem ao vivo aos assinantes, por padrão ele limpa o flag RETAIN, mesmo que o publicador o tenha definido. O flag só fica ativo nas mensagens que o broker envia por causa de uma nova assinatura (a retained message armazenada). Esse padrão permite ao assinante distinguir "este é o último valor armazenado" de "isto acabou de acontecer".

Com Retain As Published ligado, o broker mantém o flag RETAIN exatamente como o publicador o definiu. Você precisa disso principalmente quando o assinante republica as mensagens em outro lugar e o status de retenção precisa sobreviver:

  • Bridges entre brokers que espelham o estado retido.
  • Gateways e proxies que encaminham para um segundo sistema que também respeita retained messages.
  • Ferramentas de depuração que querem mostrar o que o publicador realmente enviou.

Para assinantes comuns de aplicação, deixe desligado.

Retain Handling: controle o reenvio de mensagens retidas

As retained messages são enviadas a um cliente quando ele se inscreve. O Retain Handling decide quando isso acontece:

Valor Comportamento Use quando
0 Envia retained messages a cada subscribe (padrão) Dashboards e consumidores que precisam do estado atual
1 Envia retained messages apenas se a assinatura ainda não existia Clientes com sessões persistentes que se reinscrevem ao reconectar
2 Nunca envia retained messages para esta assinatura Processadores de eventos que só se importam com mensagens novas

O valor 1 resolve um problema sutil. Um dispositivo com sessão persistente se reconecta e se reinscreve por precaução. Com o valor 0, ele recebe todas as retained messages de novo e, se forem mensagens de configuração, pode reaplicá-las. Com o valor 1, o broker só reenvia retained messages quando a assinatura é realmente nova.

O valor 2 é útil para loggers e serviços de alerta. Se um serviço de alerta se inscreve em alarms/# e o broker guarda alarmes retidos de horas atrás, o valor 0 faria com que ele disparasse alertas antigos a cada reinicialização.

Configurando as opções de assinatura MQTT no código

Os dois exemplos se conectam ao servidor MQTT público (gratuito) em broker.connectmq.com via TLS com MQTT 5. Use um prefixo de topic único só seu; o servidor MQTT público pode ser lido por qualquer pessoa e serve apenas para testes e protótipos, então mantenha os dados inofensivos.

MQTT.js (Node.js)

// npm install mqtt
const mqtt = require("mqtt");

const prefix = "connectmq/demo/c41e";
const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
  clientId: "subopts-js-c41e",
  protocolVersion: 5,
});

client.on("connect", () => {
  // No Local: não devolve para nós as nossas próprias mensagens de chat
  client.subscribe(`${prefix}/chat/room1`, { qos: 1, nl: true }, (err, granted) => {
    if (err) return console.error(err);
    console.log("chat granted:", granted);
    client.publish(`${prefix}/chat/room1`, "hello from js", { qos: 1 });
  });

  // Retain Handling 2: ignora alarmes armazenados, só recebe os novos
  client.subscribe(`${prefix}/alarms/#`, { qos: 1, rh: 2 });

  // Retain As Published: mantém o flag RETAIN como foi enviado
  client.subscribe(`${prefix}/config/#`, { qos: 1, rap: true, rh: 1 });
});

client.on("message", (topic, payload, packet) => {
  console.log(`${topic} retain=${packet.retain} -> ${payload.toString()}`);
});

Execute o script e você não verá "hello from js" impresso, porque o No Local suprime o eco. Publique no mesmo topic de chat a partir de outro cliente e a mensagem aparece.

Python paho-mqtt 2.x

import ssl
import paho.mqtt.client as mqtt
from paho.mqtt.subscribeoptions import SubscribeOptions

PREFIX = "connectmq/demo/c41e"

def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print("Connect failed:", reason_code)
        return
    client.subscribe([
        (f"{PREFIX}/chat/room1", SubscribeOptions(qos=1, noLocal=True)),
        (f"{PREFIX}/alarms/#", SubscribeOptions(
            qos=1, retainHandling=SubscribeOptions.RETAIN_DO_NOT_SEND)),
        (f"{PREFIX}/config/#", SubscribeOptions(
            qos=1, retainAsPublished=True,
            retainHandling=SubscribeOptions.RETAIN_SEND_IF_NEW_SUB)),
    ])

def on_subscribe(client, userdata, mid, reason_code_list, properties):
    print("SUBACK:", [str(rc) for rc in reason_code_list])

def on_message(client, userdata, msg):
    print(f"{msg.topic} retain={msg.retain} -> {msg.payload.decode()}")

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id="subopts-py-c41e", protocol=mqtt.MQTTv5)
client.tls_set(cert_reqs=ssl.CERT_REQUIRED)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.connect("broker.connectmq.com", 8883, keepalive=60)
client.loop_forever()

Verificando o comportamento

Um plano de teste rápido que você executa em poucos minutos:

  1. Publique uma retained message em connectmq/demo/c41e/alarms/pump1 a partir de qualquer cliente, por exemplo o cliente MQTT no navegador do portal ConnectMQ ou mosquitto_pub -r.
  2. Inicie o script Python. A assinatura de alarmes usa Retain Handling 2, então o alarme retido não é entregue.
  3. Publique um alarme novo, não retido. Ele chega imediatamente.
  4. Publique uma retained message em .../config/rate. Como o Retain As Published está ligado, o script imprime retain=True para a mensagem ao vivo.
  5. Reinicie o script sem mudar mais nada e confirme que o comportamento se repete.

Armadilhas comuns

  • Conectar com MQTT 3.1.1. As bibliotecas ignoram silenciosamente as opções de assinatura em uma conexão 3.1.1. Sempre defina protocolVersion: 5 ou protocol=mqtt.MQTTv5.
  • No Local em assinaturas compartilhadas. O broker trata isso como erro de protocolo e fecha a conexão.
  • Esperar que Retain Handling 1 funcione com clean sessions. Se a sessão é nova toda vez, toda assinatura é nova, e o valor 1 se comporta como o valor 0.
  • Usar Retain As Published em dashboards. O dashboard deixa de conseguir distinguir um valor armazenado de um valor recente. Mantenha o padrão, a menos que você encaminhe mensagens.
  • Ignorar o SUBACK. Um reason code como 0x80 ou 0x87 significa que a assinatura foi recusada. Registre os reason codes do SUBACK.

Escolhendo as opções por função

Função do cliente Max QoS No Local Retain As Published Retain Handling
Dashboard 1 desligado desligado 0
Participante de chat 1 ligado desligado 0 ou 2
Serviço de alertas 1 desligado desligado 2
Dispositivo com sessão persistente 1 desligado desligado 1
Bridge ou encaminhador 2 ligado ligado 0

Essas opções fazem parte do que torna o ConnectMQ e outros brokers MQTT 5 mais fáceis de usar como base do que seus antecessores 3.1.1: um comportamento que antes exigia filtragem na aplicação agora é um flag no pacote SUBSCRIBE. Combine-as com um bom design de topics a partir de curingas MQTT e mantenha o tutorial de MQTT à mão se ainda estiver aprendendo o básico.

Comece a usar o ConnectMQ

Teste as opções de assinatura no servidor MQTT público e depois migre para um broker MQTT gerenciado privado com MQTT 5.0, TLS e credenciais geradas. Crie um broker no plano Serverless (Free) em app.connectmq.com, sem cartão de crédito.

Perguntas frequentes

As opções de assinatura MQTT estão disponíveis no MQTT 3.1.1?

Não. O MQTT 3.1.1 carrega apenas o QoS solicitado. No Local, Retain As Published e Retain Handling exigem uma conexão MQTT 5.0.

Qual é o valor padrão de cada opção de assinatura?

No Local e Retain As Published ficam desligados por padrão, e Retain Handling tem padrão 0, que envia retained messages a cada subscribe. Isso corresponde ao comportamento do MQTT 3.1.1.

Por que ainda recebo minhas próprias mensagens com No Local ativado?

Verifique se você está conectado com MQTT 5 e se o publicador e o assinante são a mesma conexão. Um segundo cliente, mesmo no mesmo processo, não é local.

Posso mudar as opções de assinatura sem cancelar a inscrição?

Sim. Enviar um novo SUBSCRIBE para o mesmo filtro de topic substitui as opções da assinatura existente. Com Retain Handling 1, as retained messages não são reenviadas, porque a assinatura já existe.

Artigos relacionados

Por que Seu Cliente MQTT Fica Desconectando (e Como Corrigir)

Loops de reconexão estão entre os problemas mais comuns do MQTT. Conheça as cinco causas habituais, de client IDs duplicados a timeouts de keep alive e pacotes grandes demais, e siga um fluxo de diagnóstico que identifica a sua.

9 min de leitura

Quem Inventou o MQTT? A História dos Oleodutos à IoT

O MQTT foi inventado em 1999 por Andy Stanford-Clark, da IBM, e Arlen Nipper, da Arcom, para monitorar oleodutos via satélite. Acompanhe sua trajetória até o open source, a OASIS, a ISO e os brokers MQTT gerenciados na nuvem de hoje.

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.