Site menu MQTT, um protocolo idIoTa

MQTT, um protocolo idIoTa

O título deste texto tem duas motivações. Uma é o trocadilho com IoT, uma área em que o MQTT brilha, embora não tenha sido sua aplicação original.

A outra motivação é que MQTT é um protocolo muito simples, um ovo de Colombo. Poderia mesmo ter sido inventado por qualquer idiota. Muitos de nossos colegas de profissão já inventaram e reinventaram protocolos semelhantes ao longo da carreira, só esqueceram de promover a ideia.

O MQTT foi inventado pela IBM no contexto de controle e monitoramento de um oleoduto. É um protocolo M2M (machine-to-machine), concebido para comunicação entre computadores. Por ser muito simples e leve, é fácil implementá-lo em qualquer controlador, e de fato encontram-se implementações para todas as linguagens e plataformas imagináveis. Por suas qualidades, desbancou muitos protocolos mais antigos e mais conhecidos como XMPP.

O modelo básico do MQTT é o PubSub ou publisher-subscriber. Neste modelo, remetentes e destinatários de mensagens estão completamente desacoplados. As mensagens MQTT possuem apenas tópico e conteúdo, cujo formato é commpletamente livre, embora o costume seja usar mensagens legíveis por humanos.

Demonstração do MQTT na linha de comando, usando um broker local. Não poderia ser mais fácil: publica-se uma mensagem ao vento. Quem subscreveu ao tópico, recebe a mensagem. Os coringas de tópicos aparecem mais para o final do vídeo.

As trocas de mensagens MQTT são sempre intermediadas por um servidor ou broker MQTT. O protocolo não tem nenhuma pretensão de ser P2P, nem faz uso de recursos como broadcast.

O tópico de uma mensagem MQTT costuma ser uma string segmentada por barras, por exemplo stat/Switch0001/State. É interessante seguir este formato porque o MQTT permite a subscrição usando coringas, por exemplo stat/Switch0001/# ou stat/+/State. Uma vez que a mensagem não contém nem o endereço IP nem nenhuma outra informação auxiliar sobre o remetente, o tópico é quem deve identificá-lo de alguma forma.

O tópico precisa ser único apenas na rede local; o MQTT não tem escopo global, então não é necessário prefixar com nomes de domínio ou coisas assim, como às vezes se vê em outros protocolos. O fato da identificação do remetente depender unicamente do tópico facilita e.g. a troca de um equipamento defeituoso por outro equivalente.

O fato do MQTT centralizar a comunicação no broker e não usar endereços IP para identificar clientes resolve de plano muitos problemas que normalmente enfrentamos quando o protocolo tenta ser "inteligente demais". Não importa se sua rede possui roteadores NAT, se usamos diversas LANs, se o access point não implementa multicast corretamente, ou se os clientes MQTT ficam mudando de endereço IP. O MQTT simplesmente funciona.

Como se isso não bastasse, o MQTT pode ainda usar WebSockets como transporte, o que permite ao protocolo funcionar numa situação de rede ainda mais restritiva, onde apenas conexões Web são permitidas.

Existem diversas implementações de broker ou servidor MQTT. De longe, a mais utilizada é a Mosquitto, disponível em qualquer distro Linux. O Mosquitto tem alguns recursos extras interessantes, como a possibilidade de repassar mensagens MQTT entre brokers.

Existem ainda brokers públicos na Internet para fazer testes simples. E também há brokers pagos, se não quiser ou não puder rodar um broker localmente.

Igualmente populares são os utilitários de linha de comando mosquitto_pub e mosquitto_sub, que servem tanto para depuração quanto para alguns usos amadores de MQTT (eu uso no crontab para monitorar uptime de alguns serviços).

Cabelinhos

Se um de nós meros mortais inventasse algo semelhante ao MQTT, provavelmente não resistiria à tentação de procurar cabelo em ovo, enchendo o protocolo de complicações e recursos para tentar otimizá-lo ou açambarcar mais casos de uso — e assim acabar estragando o que de outra forma seria um sucesso. (O Verde certamente exigiria suporte a expressões regulares em vez dos singelos coringas.)

O MQTT tem seu punhado de "cabelos". É pouca coisa, tudo com bons motivos para existir, mas é suficiente para causar tropeços se não compreendidos corretamente.

As mensagens LWT (Last will and testament) são pré-cadastradas junto ao broker pelo cliente MQTT, e são entregues apenas quando ou se esse cliente desconectar involuntariamente. Um uso óbvio é participar à rede que o dispositivo saiu do ar.

Cada publicação e cada subscrição MQTT são associados a um nível de qualidade de serviço (QoS). Os níveis são três: 0 (no máximo uma entrega), 1 (no mínimo uma entrega) e 2 (exatamente uma entrega).

É importante destacar que uma mensagem pode estar sujeita a diferentes níveis de QoS ao longo de sua trajetória. O QoS atribuído na publicação trata da confiabilidade no trecho remetente-broker. Já o QoS da subscrição refere-se ao trecho broker-destinatário.

Alguém poderia perguntar: se o MQTT usa TCP como transporte, isso não garante automaticamente um "QoS nível 2"? No nível da rede, pode ser que sim. Mas escolher o QoS correto é relevante para dirigir o comportamento extra-rede:

Um cliente MQTT pode requisitar ao broker uma sessão persistente. Nesse tipo de sessão, as subscrições sobrevivem a uma desconexão temporária, evitando o overhead de refazer todas as subscrições a cada reconexão.

Quando há uma sessão persistente, o broker MQTT faz papel de caixa postal, retendo todas as mensagens com QoS 1 ou 2 enquanto o cliente estiver desconectado. Isso é útil tanto pela garantia de entrega quanto por contemplar clientes muito limitados em banda ou energia, que só podem comparecer de forma intermitente.

Por último, o remetente pode marcar uma mensagem como retida. Uma mensagem retida é armazenada em disco, e persiste mesmo que o broker seja reiniciado e/ou que o remetente desapareça da rede. Ela também é reenviada a cada cliente que subscreve o tópico, mesmo que já tenha sido enviada antes.

A única forma de substituir uma mensagem retida é enviando outra com o mesmo tópico, e a única forma de removê-la é enviando outra com conteúdo nulo. (A versão 5.0 do protocolo inclui a possibilidade de atribuir uma data de expiração.)

Ao implementar um cliente MQTT, a tentação é enviar todas as mensagens como retidas para aumentar a confiabilidade. Com o tempo, descobrimos que isto é uma má ideia, quando continuamos a receber mensagens-fantasmas de remetentes que já não existem há semanas ou meses. Ou quando o remetente parou de funcionar, porém as mensagens retidas dão a impressão de que ele continua ativo.

Tanto o cliente como o broker monitoram a conexão usando mensagens de "ping". O ritmo do ping pode ser configurado conforme o caso de uso. Vai depender de quão rápido queremos descobrir que um dispositivo saiu do ar (para emitir a mensagem LWT e/ou tomar outras providências).

Escolha do tópico

Como dito antes, o tópico tem formato livre. Como meu primeiro contato com MQTT foi através do projeto Tasmota, que usei para instalar firmware livre em um par de chaves Sonoff, acabei adotando a convenção default deles:

O MQTT não é um protocolo RPC. Você pode simular uma comunicação bidirecional, publicando num tópico e subscrevendo outro, mas não é confortável. Em MQTT você "atira e esquece", e deve pensar o funcionamento do sistema em torno dessa premissa.

Por exemplo, é razoável supor que uma chave de luz, uma vez comandada, fique naquele estado para sempre. É um caso que vale até marcar a mensagem de comando como retida. Se a chave só publica seu estado quando ele muda, também faz sentido que seja mensagem retida, para que qualquer novo participante da rede seja informado do estado da chave assim que subscreve.

Já no caso da chave comandar uma bomba de água, é mais razoável que o comando "liga" possua timeout, para evitar o caso do comando "desliga" nunca ser emitido, por conta de alguma falha ou travamento. O controlador da bomba simplesmente repete o comando "liga" periodicamente, enquanto desejar que a bomba fique ligada.

Um terceiro caso seria usar uma chave Sonoff para resetar um computador; neste caso desejamos que o estado "ligado" seja o default, e o estado "desligado" é que deve estar sujeito a timeout. (A propósito, o firmware Tasmota possui configurações para todos os cenários descritos. Eu mesmo uso um Sonoff para resetar diariamente o roteador da Net.)

MQTT sobre transporte não-confirmado

O protocolo MQTT possui alguns, mas não todos, os recursos necessários para funcionar com um transporte não-confirmado como UDP, linha serial, LoRa ou 802.15.4 ("Zigbee"). Também existem casos de uso onde é preciso ser ainda mais econômico no tráfego de dados.

Para estes casos, temos o MQTT-SN.

Realmente não sei quão popular é este protocolo. Para ser franco, fiquei sabendo da existência dele apenas hoje, quando fui procurar por "MQTT sobre UDP" no Google. A especificação é de 2013, o que sugere que o interesse da indústria não é lá muito grande. Por outro lado, encontra-se implementações para diversas linguagens e plataformas, então não é 100% patinho feio.

Também encontrei esta versão apócrifa de MQTT sobre UDP. Sua característica é ser P2P: não usa broker e difunde mensagens usando broadcast. A ideia é elegante, mas ressuscita aqueles problemas misteriosos de rede, principalmente quando há mistura de rede cabeada e Wi-Fi, e o broadcast deixa de fluir de um lado para o outro.

MQTT versão 5

A versão 5 do protocolo MQTT é bastante recente, e adiciona diversos "cabelos" ao MQTT. O primeiro deles é não ser retroativamente compatível com o MQTT 3.1.1, que por ora é o mais amplamente difundido.

Uma novidade bem-vinda é a possibilidade de estabelecer timeout para mensagens e para sessões persistentes. (No MQTT 3, a ausência de timeout poderia obrigar um broker a armazenar informação "para sempre".) Também é possível estabelecer uma contagem máxima de mensagens confirmadas pendentes de entrega, o que permite manter o consumo de memória sob controle.

Outra novidade relacionada a tempo é o atraso configurável entre desconexão e emissão da mensagem LWT: se o cliente reconecta a tempo, a emissão do LWT é suspensa, enquanto no MQTT 3 a emissão é sempre imediata.

O MQTT 5 adiciona algum suporte a comunicações no estilo RPC ou cliente-servidor, mediante os recursos de "tópico de resposta" e "dado de correlação". O tópico de resposta é um anexo da mensagem de requisição, de modo que o servidor possa enviar a resposta sem precisar conhecer o tópico de antemão. O dado de correlação é um identificador opaco da transação, que permite a diversos clientes empregar um mesmo tópico de resposta. Pessoalmente, achei confuso e vai de encontro ao espírito do MQTT.

Outro recurso polêmico, mas inegavelmente interessante, é o tópico compartilhado, a que diversos clientes subscrevem, mas apenas um recebe cada mensagem. O uso mais óbvio é o balanceamento de carga, que não sei se tem caso de uso em IoT, mas certamente sim em outros sistemas de grande porte.

Uma adição definitivamente bem-vinda é a autenticação SASL, que é segura mesmo sem TLS. No MQTT 3, a autenticação transmite a senha às claras, o que obriga o uso de TLS se desejamos manter a senha em segredo. (Usar TLS em microcontroladores pode ser difícil ou impossível, fora o aborrecimento de lidar com certificados X.509.)

A lista completa de novidades pode ser encontada no apêndice da especificação do MQTTv5.

Referências

Especificações oficiais do MQTT