9.18. MQTT, byte a byte¶
A esta altura, a câmera tem todas as peças de que precisa para conversar com um serviço real na internet aberta: um socket TCP, TLS para envolvê-lo, DNS para nomear o par e asyncio para permitir que o mesmo script faça outras tarefas enquanto a conexão está aberta. O MQTT é o primeiro protocolo de fio que reúne todos esses elementos em algo que um produto implantado realmente usa.
Esta página aborda o protocolo em si – o formato no fio, os papéis que cada participante desempenha e os compromissos de seu projeto – de forma honesta o bastante para que o cliente mqtt incluído pareça um encapsulamento óbvio do que já é conhecido, em vez de um salto de fé.
9.18.1. Pub/sub vs. requisição/resposta¶
O HTTP – o protocolo que a maioria dos projetos de câmera busca primeiro – é de requisição/resposta. Um cliente pede um recurso específico a um servidor específico; o servidor responde. Cada troca é um-para-um, e ambas as pontas conhecem o endereço uma da outra de antemão.
O MQTT é de publicação/assinatura (publish/subscribe). Os clientes se conectam a um terceiro no meio chamado broker. Um publicador envia uma mensagem a um tópico nomeado sem saber ou se importar com quem está ouvindo. Um assinante informa ao broker quais tópicos ele quer e, a partir daí, recebe toda mensagem publicada nesses tópicos. O broker é quem faz a distribuição (fan-out): uma publicação em yard-cam/motion chega a todo dispositivo assinante de yard-cam/motion, sejam eles zero, um ou cinquenta.
Três consequências decorrem dessa mudança de modelo:
Desacoplamento. Os publicadores não precisam saber que os assinantes existem. Os assinantes podem aparecer e desaparecer sem que o publicador perceba. Adicionar um segundo painel é uma linha de código no novo painel; a câmera não muda.
Distribuição (fan-out). O broker cuida de cada duplicata, então a câmera envia um único pacote independentemente de quantos dispositivos o leem. Esse é o caso de uso para o qual o MQTT foi criado.
Assimetria. O broker agora é uma peça de infraestrutura obrigatória – sem um, o protocolo não funciona. Para projetos domésticos, normalmente esse é um broker público gratuito (
test.mosquitto.org,broker.hivemq.com) ou um pequeno que você mesmo executa.
9.18.2. Tópicos¶
Tópicos são strings separadas por barras. A convenção é do mais geral à esquerda para o mais específico à direita:
yard-cam/motion
yard-cam/temperature
workshop-cam/motion
workshop-cam/temperature/sensor-3
Dois curingas funcionam em assinaturas (não em publicações):
+corresponde a um único nível.+/motionassina o tópico motion de cada câmera;yard-cam/+assina cada subtópico de yard-cam.#corresponde a um ou mais níveis finais.yard-cam/#assinayard-cam/motion,yard-cam/temperature,yard-cam/temperature/sensor-3e qualquer outra coisa sobyard-cam/. Ele deve aparecer no final da assinatura.
Strings de tópico diferenciam maiúsculas de minúsculas. Conforme a especificação, um $ inicial marca tópicos internos do broker ($SYS/...) nos quais os publicadores não devem escrever.
9.18.3. O formato do pacote¶
O MQTT roda sobre TCP. Todo pacote de controle começa com um cabeçalho fixo de um byte, seguido por um campo Remaining Length de comprimento variável, depois um cabeçalho variável específico do tipo de pacote e, então, o payload. O mesmo formato externo cobre todos os comandos – CONNECT, PUBLISH, SUBSCRIBE, PUBACK, DISCONNECT e os demais – razão pela qual um cliente MQTT pode ser escrito em algumas centenas de linhas.
O cabeçalho fixo tem um byte:
Os bits 7..4 são o tipo de pacote de controle.
0x3é PUBLISH (então o primeiro byte geralmente começa com0x3?).0x1é CONNECT,0x2CONNACK,0x8SUBSCRIBE,0xCPINGREQ,0xEDISCONNECT, etc.Os bits 3..0 são flags específicas do tipo de pacote. Para PUBLISH, as flags codificam a flag de retransmissão DUP, o nível de QoS (2 bits) e a flag RETAIN.
O Remaining Length é um inteiro de comprimento variável de 1 a 4 bytes que conta todos os bytes após si mesmo. O bit mais alto de cada byte é um marcador de continuação – 1 significa “segue outro byte de comprimento”, 0 significa “este é o último”. Um comprimento abaixo de 128 cabe em um byte; payloads maiores usam mais. O comprimento codificado máximo é de 256 MiB.
Para um PUBLISH, o cabeçalho variável é o nome do tópico – um comprimento de 2 bytes, depois os bytes UTF-8 – seguido por um identificador de pacote de 2 bytes que só existe quando o QoS é 1 ou 2. Os bytes restantes são o payload, tratado como bytes opacos pelo protocolo.
Um PUBLISH mínimo com QoS 0 de ok para a/b é:
30 07 00 03 'a' '/' 'b' 'o' 'k'
30– PUBLISH, todas as flags em zero.07– seguem 7 bytes.00 03– comprimento do tópico 3.'a' '/' 'b'– tópico.'o' 'k'– payload.
Nove bytes no fio e a mensagem chega a todo assinante de a/b no broker.
9.18.4. Níveis de QoS¶
A Qualidade de Serviço (Quality-of-Service) controla o quanto o broker (e o cliente) se esforçam para garantir a entrega. Os três níveis:
QoS 0 – no máximo uma vez. Dispare e esqueça. O pacote PUBLISH é enviado e nunca confirmado. Se o TCP entregar, o broker encaminha. Se a conexão cair no meio do envio, a mensagem se perde. A maior parte da telemetria de sensores funciona bem com QoS 0 – uma única leitura de temperatura perdida em um fluxo que emite a cada 30 segundos não faz diferença.
QoS 1 – pelo menos uma vez. O publicador inclui um identificador de pacote e aguarda um PUBACK. Se nenhum PUBACK chegar antes de um tempo limite, o publicador retransmite com a flag DUP ativada. O broker pode acabar entregando a mesma mensagem duas vezes a um assinante no mesmo nível; o assinante precisa estar disposto a lidar com duplicatas.
QoS 2 – exatamente uma vez. Um handshake de quatro etapas (PUBREC / PUBREL / PUBCOMP) garante que a mensagem chegue exatamente uma vez, mesmo entre reconexões. Custoso em idas e voltas e em estado no broker. Poucos aplicativos de câmera precisam dele.
O cliente mqtt incluído implementa QoS 0 e QoS 1; o QoS 2 gera erro se você o solicitar. Para uma câmera que reporta leituras de sensor, o QoS 0 é quase sempre a resposta certa.
9.18.5. Mensagens retidas e last will¶
Vale a pena conhecer dois recursos, porque eles mudam o que o broker memoriza sobre seu tópico.
RETAIN. Se um PUBLISH tiver a flag RETAIN ativada, o broker armazena a mensagem e a encaminha a todo futuro assinante no momento em que ele assina. É assim que o MQTT lida com “qual é o valor atual?” – um sensor publica sua leitura mais recente como retida, e um painel que assina dez minutos depois ainda recebe o valor mais recente em vez de esperar a próxima publicação. Republicar com o mesmo tópico sobrescreve o valor retido; publicar um payload vazio o limpa.
Last will. Ao se conectar, um cliente pode dar ao broker um “last will and testament” (última vontade e testamento): um tópico, um payload, um QoS e uma flag de retenção. Se esse cliente desconectar de forma abrupta – RESET de TCP, perda de energia, queda de rede sem pacote DISCONNECT – o broker publica a vontade em nome do cliente. Os assinantes a veem como a notificação da câmera de que ela ficou offline. A própria câmera nunca envia a vontade; o broker o faz, porque a essa altura a câmera já se foi.
9.18.6. Keepalive e reconexão¶
O CONNECT carrega um intervalo de keepalive em segundos. Se o cliente ficar em silêncio por esse tempo, o broker o considera morto. Para evitar isso, o cliente envia periodicamente um PINGREQ (um byte: 0xC0) e recebe de volta um PINGRESP (0xD0) – o heartbeat menor e mais barato que o protocolo pode transportar. A maioria dos aplicativos de câmera define o keepalive em 30 ou 60 segundos.
Se a conexão TCP cair, ambos os lados percebem e reconectam do zero. As assinaturas feitas antes da queda são perdidas, a menos que o cliente tenha usado uma sessão persistente na conexão; para aplicativos de câmera simples, o padrão de reassinar na reconexão é mais curto e igualmente bom.
Isto é suficiente para ler a especificação do MQTT ou montar à mão um cliente sobre um socket.socket. O cliente incluído em mqtt faz exatamente isso, além de oferecer uma API sensata para o código da aplicação.