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/+/configacabaria 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:
- Publique uma retained message em
connectmq/demo/c41e/alarms/pump1a partir de qualquer cliente, por exemplo o cliente MQTT no navegador do portal ConnectMQ oumosquitto_pub -r. - Inicie o script Python. A assinatura de alarmes usa Retain Handling 2, então o alarme retido não é entregue.
- Publique um alarme novo, não retido. Ele chega imediatamente.
- Publique uma retained message em
.../config/rate. Como o Retain As Published está ligado, o script imprimeretain=Truepara a mensagem ao vivo. - 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: 5ouprotocol=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
0x80ou0x87significa 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.



