Site menu IoT: controle remoto de 433MHz (parte 4)

IoT: controle remoto de 433MHz (parte 4)

(Este texto é uma continuação desta série.)

Como melhorar a recepção (a ponto de espionar vizinhos distantes)

O receptor por software que desenvolvemos nos capítulos anteriores procura distinguir claramente sinal de ruído, e visa mais a correção do que a sensibilidade.

Porque, como ruído é aleatório, se começarmos a interpretar ruído como mensagem, e a mensagem só tem 24 ou 28 bits, não demora muito para "receber" todas as mensagens possíveis. (Tipo aquele pessoal que deixa um gravador rodando no cemitério, depois diz que captou vozes do além. Se você é geração Z e não sabe do que estou falando, procure por EVP ou "electronic voice phenomenon" no YouTube.)

Um primeiro teste foi sair andando com o keyfob e ir acionando para ver até onde pega. Constatamos que

Para testar os limites de forma mais sistemática, montei o "radiofarol" (mencionado antes) e posicionei num canto da casa onde a recepção ficasse limítrofe. (Não foi fácil; mesmo aterrando o pino de antena de TX com um resistor, a comunicação ainda acontece se estiver meio perto.) Além de confirmar os pontos anteriores, descobrimos que

Tínhamos optado quase desde o início por verificar a largura dos bits mas não dos chirps, apenas testando se um pulso é mais longo que o outro. Sem querer, acabamos desenvolvendo um RX mais sensível. Muitas outras implementações e.g. a biblioteca rc-switch eo Arduino verificam os chirps, então ficava a dúvida se nós estávamos fazendo certo ou não.

É possível que outras implementações verifiquem os chirps porque precisam distinguir entre muitos protocolos e modelos de keyfobs. Aqui, só tenho dois tipos, e elas apresentam outras diferenças além dos chirps, tornando-os facilmente distinguíveis, então para nós esse procedimento não adiciona valor.

A próxima pergunta é se podemos fazer uma recepção ainda mais sensível, ainda que imprecisa.

Infelizmente, parece que isto não é possível devido a uma limitação do hardware de RX: quando o sinal é fraco demais, o controle de ganho simplesmente não se ajusta. A forma de onda registrada pelo analisador de sinal parece ruído puro. Por outro lado, quando o controle de ganho atua, a recepção é quase sempre clara. Esta é a causa da pouca faixa limítrofe.

Essa característica do RX impede, por exemplo, que façamos uma média móvel do sinal para estimar se existe portadora ou não. Também não há muita esperança de capturar "pedacinhos" de código aqui e ali para montar os fragmentos depois.

Para contornar essa dificuldade, temos duas saídas: usar uma antena direcional (Yagi ou parabólica) apontada para o alvo, e/ou usar um SDR como receptor, o que nos permitiria analisar o sinal ainda no domínio analógico. Naturalmente, o software decodificador fica mais complicado. É um buraco de coelho em que por enquanto não quero entrar. (Existe um módulo para Home Assistant que interfaceia com keyfobs usando SDR e uma sofisticada biblioteca em C para decodificação.)

No fim do dia, se eu quisesse mesmo copiar o código de algum portão eletrônico próximo, seria mais fácil largar uma caixinha preta por perto com bateria suficiente para 1 dia de operação, na linha deste quadrinho do XKCD. Mas meu objetivo final não era esse. A ideia era brincar com codificações banda-base mais robustas. Mesmo para isso, precisaria dum SDR pelo menos na recepção (um SDR capaz de transmitir abriria ainda mais possibilidades, mas é um brinquedo ainda mais caro.)

Surpresas ao integrar o receptor com MQTT

Minhas incursões em automação residencial podem ser encontradas aqui. É um frameworkzinho MicroPython sobre o qual desenvolvi diversos "perfis": controle de iluminação, controle de caixa de água, estação metereológica, etc.

O perfil do receptor 433MHz tem apelido "rx433" e o coração da implementação está aqui. O histórico de commits reflete inclusive a história contada a seguir.

Para interagir com os keyfobs 433MHz, o plano era desenvolver um perfil bem burro, basicamente um bridge que publica tópicos MQTT com os códigos que recebe dos keyfobs. (Outra peça de software, que no momento chamo de "automator", é quem vai comandar o efeito colateral baseado nessa mensagem MQTT. Esse "automator" você não vai encontrar ainda no meu GitHub porque está muito feio para ser publicado.)

Para minha surpresa, o receptor 433MHz não funcionou bem após a integração no framework. Apenas uma em dez recepções era saudável, se tanto. E agora José?

A causa logo ficou evidente: o tratador de interrupção não coexiste bem com rede WiFi. Assim que a rede é ativada, o jitter do tratador aumenta muito, ou seja, ele não é chamado exatamente quando ocorre uma transição de nível, mas sim com um atraso muito variável. Esse jitter (da ordem de 500µs) é suficientemente alto para impedir a decodificação.

Este é um problema específico do porte do MicroPython — o ESP32 possui dois núcleos e no framework Arduino o WiFi fica a cargo do 2º núcleo. Achei que o mesmo acontecia no MicroPython, mas pelo jeito não é o caso. Mesmo sem WiFi rodando em paralelo, a implementação de interrupções no MicroPython ESP8266/ESP32 possui um jitter naturalmente alto. Recomenda-se usar o PyBoard (hardware de referência do MicroPython) se você faz questão de alta precisão e MicroPython ao mesmo tempo. Há tantas menções na Internet a respeito de jitter e atraso em interrupções ESP32 que até estou surpreso que o protótipo inicial funcionou.

Bem, o problema está posto e chorar não resolve nada. Soluções de curto prazo:

A última solução é mais feia que bater na mãe e roubar bolacha de velho. E não havia garantias que produziria resultados melhores. Mas recomendaram isso para um usuário de MicroPython ESP32 com problema semelhante ao nosso, então valia a tentativa.

E de fato o busy loop funcionou! O jitter voltou a ser o mesmo de antes (ou seja, não melhorou; o jitter de +/-25µs observado no tratador de interrupção parece ser inerente ao interpretador).

Nossa tática é executar o busy loop por no máximo 500ms de cada vez, para dar chance do resto do framework rodar. Quando um código válido é recebido, o busy loop encerra mais cedo para a publicação MQTT acontecer logo, mesmo correndo o risco de perder uma recepção em seguida. Pode acontecer dessa interrupção periódica do busy loop deixar o receptor meio "surdo"? Pode, mas como os keyfobs costumam enviar o código diversas vezes, na prática parece que não prejudica.

Estou feliz? É claro que não, usar um busy loop é porco. A solução mais escorreita seria usar RMT para recepção, que é suportado no ESP32 mas não no MicroPython. É algo que talvez eu tente no médio prazo, para manter válida a carteirinha do sindicato de kolaboradores do software livre, e mais útil que reinventar a roda com SDR.