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

IoT: controle remoto de 433MHz (parte 1)

Na automação da casa, tentei algumas heurísticas, do tipo ligar a luz ao desligar o alarme, ou detectar que o celular de alguém está por perto. Mas nem sempre se deseja que evento A provoque efeito B, e às vezes o evento A não acontece (e.g. meu Android resolveu usar privacidade de MAC mesmo quando configurado para não fazer).

Para certas coisas, é melhor e mais simples usar o bom e velho controle remoto keyfob, no estilo dos portões eletrônicos. E calha que o portão eletrônico tem um botão sobrando...

Tecnologias

Em quase todo lugar, os keyfobs usam a freqüência de 433.92MHz, de bom alcance e penetração. Nessa faixa só se permite transmitir em banda estreita, então os keyfobs digitais usam codificação ASK/OOK, ASK ou FSK. Um keyfob Zigbee teria de operar em outra banda (900MHz em diante).

Bem antigamente, os controles eram puramente analógicos, com uma simples regulagem de freqüência para (tentar) distinguir portões próximos. Dava muita confusão e brigas com radioamadores. Mesmo a inauguração de uma nova emissora de rádio ou TV na cidade fazia o portão de alguém ficar maluco.

Os controles digitais comuns enviam um código fixo, que pode ser imutável de fábrica ou "aprendido" de outro controle. Por ser uma informação digital, é resistente a interferências, e como há milhões de códigos possíveis, a chance de colisão é baixa. Os keyfobs são todos baseados em uns poucos chips e.g. EV1527, PT2262, HT12E, HT6P20, etc.

São os controles mais utilizados por ora, porém são inseguros por serem facilmente clonáveis. (A única "segurança" contra um agente hostil é o alcance limitado.) Tampouco são adequados num ambiente com centenas de acessantes, como num condomínio, pois não há como revogar controles individuais.

Os controles com rolling code enviam uma mensagem digital diferente a cada acionamento a fim de dificultar a clonagem. Um chip muito popular é o HCS301, utilizado até em carros de luxo, apesar de já ter tido sua criptografia quebrada. Os melhores sistemas implementam comunicação bidirecional entre estação e keyfob, porém são mais raros e mais caros.

Qual sistema usar?

O ideal seria usar keyfobs "inteligentes" baseados em Zigbee ou ESP-NOW. Porém Zigbee não é simples de usar e não encontrei um keyfob com um ESP32 dentro. Continuarei monitorando essas possibilidades — o alarme usa um keyfob Zigbee e funciona muito bem, e o ESP-NOW parece perfeito para este caso de uso.

O controle digital simples, sem rolling code, é ubíquo, barato, e "hackeável" usando Arduino. É o controle usado no meu portão, e que tem um segundo botão ocioso. Para fazer testes sem ficar abrindo e fechando o o portão, adquiri keyfobs baseados no chip EV1527. Uma vez que os controles só acionam conveniências, não coisas críticas, a resistência a clonagem não é uma preocupação premente.

No mundo Arduino, há diversos módulos para interagir com a banda de 433MHz na codificação OOK, deixando a decodificação a cargo do software. Adquiri o SYN115 para transmissão e o SYN480R para recepção. Não pretendo transmitir usando Arduino, mas como eram vendidos em pares e muito baratos, por que não?

Para esta brincadeira, decidi parar de masoquismos e comprei um analisador lógico. É muito mais barato que um osciloscópio e é perfeitamente suficiente para este caso de uso. Em terra de cego... já sabe né. E adianta bastante o trabalho, sem precisar lidar com microcontrolador.

Figura 1: Receptor 433MHz conectado ao analisador lógico.

Um pouco sobre codificação

A codificação mais comum nesta seara é o ASK/OOK (modulação por amplitude / chaveamento on-off). Ou seja, o transmissor apenas liga e desliga. O "bit 1" é presença de sinal, e o "bit 0" é ausência de sinal. É o equivalente digital do código Morse.

Isto permite que o transmissor seja absurdamente simples. Porém complica as coisas do lado da recepção, pois é difícil distinguir "bit 0" de "ausência de transmissão", que é o problema básico da modulação digital. Os receptores ter controle automático de ganho, então na ausência de sinal eles vão confundir ruídos com bits.

No código Morse, o operador envia seqüências de pulsos curtos e longos, cuja largura é mais ou menos conhecida pelos ouvintes, para que seja possível distinguir sinal de ruído. Analogamente, os keyfobs fazem uso de um esquema primitivo de chirps (seqüências de bits físicos) para codificar os bits lógicos, a fim de que o receptor tenha uma chance de captar a mensagem. No caso do EV1527, o protocolo é:

O HT6P20, outro chip facilmente encontrado em controles de portões, tem um protocolo parecido:

Outros chips de keyfob implementam variações desse mesmo tema. O código-fonte da biblioteca rc-switch para Arduino é uma referência valiosa para começar a procurar, além dos datasheets dos chips. Alguns keyfobs transmitem o código diversas vezes, mesmo durante um pressionamento curto, a fim de aumentar a chance de ser detectado.

A imagem abaixo mostra o que o receptor SYN480R entrega na ausência de sinal (esquerda) e na presença de sinal de um keyfob EV1527 (direita):

Figura 2: Recepção de sinal OOK 433MHz.

Antes do preâmbulo, o receptor está com ganho máximo e capta todo tipo de ruído (lado esquerdo). O pulso inicial do preâmbulo calibra o ganho, e a partir deste ponto os chirps recebidos correspondem aos transmitidos, supondo que o transmissor esteja suficientemente perto.

O longo preâmbulo "silencioso" é uma forma simples e eficaz de sinalizar início de transmissão. Na ausência de sinal, as transições tendem a ser muito freqüentes, então um período de silêncio de 10.000µs é quase impossível de acontecer por acaso. O preâmbulo de silêncio é mais eficaz que um preâmbulo de sinal, pois este último poderia ser causado por qualquer fonte de QRM, por exemplo o seu vizinho radioamador.

O final da transmissão pode ser detectado tanto pelo período de silêncio pós-transmissão (que dura um pouco mais que o preâmbulo) quanto pela contagem de bits recebidos, se o receptor sabe quantos bits deve receber.

Os chirps de dados também têm largura previsível (entre 200µs e 600µs, dependendo do chip), que é outra coisa que o receptor pode monitorar, discriminando mensagens saudáveis de corrompidas sem ter de lançar mão de técnicas mais sofisticadas.

Sempre existe a possibilidade do ruído aleatório "enganar" o decodificador. Mas deve ser improvável na prática; não vejo o povo reclamando que o portão eletrônico digital abre sozinho. Além do ruído ter de parecer com uma mensagem de exatamente "n" bits, o conteúdo precisa coincidir com um código de keyfob esperado.

Infelizmente os keyfobs mais comuns não enviam CRC, nem mesmo um bit de paridade, para verificar se um código recebido é válido. O PT2262 (de que não posso um espécime) implementa um esqueminha bem primitivo de "tribits", ou seja, 2 bits representam 3 valores possíveis, então se aparecer o 4º valor "proibido", indica corrupção.

A velocidade é baixa o suficiente para permitir a decodificação por software. (Um alvo comum dos Arduinistas é capturar dados de estações metereológicas, que também transmitem em 433MHz OOK.) Além do desafio atraente, o software permite decodificar keyfobs de qualquer padrão (quiçá até os portões dos seus vizinhos).

Para quem não curte masoquismos, existe a opção de usar um decodificador de hardware como o módulo RX480E (aparentemente baseado num microcontrolador PIC, então não é 100% de hardware, é apenas uma caixa-preta). O pessoal que mexe com portões eletrônicos usa esse módulo para fazer retrofit de controle digital. Também adquiri um exemplar deste para testar.

Conectando ao Arduino (ou ESP32)

A conexão do receptor SYN480R com o Arduino é, a princípio, bem simples. Há apenas 3 pinos: positivo (+5V), negativo (GND) e dados (DATA). Para a antena, basta um pedacinho de fio (17cm de tamanho, se quiser caprichar).

Se for usar ESP32 ou Arduino 3.3V, há duas opções: alimentar o receptor com 3.3V ou usar um level shifter para DATA. Preferi usar um level shifter, o primeiro que tentei (bidirecional) funcionava de forma intermitente, como se houvesse mau contato. Tentei um mais antigo, unidirecional (internamente, um simples divisor resistivo) e esse deu bem certo. Ouvi dizer que o "pull down" do SYN480R é muito fraco para level shifters bidirecionais.

No ESP32, conectei ao pino 14. Não é o único que se presta, mas é um que eu sei de cor que suporta interrupções.

Primeira tentativa de decodificação por software

Decodificar OOK por software não é novidade nenhuma. No mundo Arduino temos as veneráveis bibliotecas VirtualWire mais voltada à comunicação entre dois Arduinos; e RC switch que faz exatamente o que pretendemos (re)fazer aqui. Então sim, estamos reinventando a roda.

Para apimentar um pouco, empreguei MicroPython, que é um pouco lento para esta tarefa, mesmo rodando no ESP32. Estamos falando de detectar e tratar umas 3 mil transições por segundo. Para ter alguma chance disso funcionar, precisamos usar interrupções. Isso cria outros problemas: tratadores de interrupção MicroPython têm de trabalhar rápido e não podem alocar memória. Este código é a primeira tentativa. Ele não faz o serviço completo, apenas detecta uma aparente sequência de chrips e joga numa lista, para um futuro decodificador de nível mais alto terminar o serviço, sem pressa.

No geral o código fala por si. Os limites de tempo (6ms a 20ms para preâmbulo, 120µs a 1800µs para chirps) são bem generosos para acomodar diversas tecnologias de keyfobs, mas talvez não funcionem com todos, e os tempos poderiam ser "apertados" para aumentar a seletividade, se temos apenas um keyfob alvo.

Havia uma opção de implementar o tratador de interrupção a) completamente "burro", apenas gerando amostras, deixando toda a interpretação para o código convencional, ou b) com alguma "inteligência" para filtrar um pouco as amostras, deixando passar apenas as que parecem vir de um keyfob.

Por mais que o tratador deva ser rápido, preferi a opção "b", pelo seguinte motivo. A taxa de amostras do sinal é limitada (em torno de 1k/seg) enquanto a taxa do ruído é ilimitada (no meu hardware é 3k/seg, mas poderia ser 10k, 100k, 1M). Prefiro que o programa falhe por perder amostras (o que só vai acontecer quando se trata de ruído) do que falhar por não dar conta de processar as amostras.

Do jeito que está, o código imprime uma lista de transições, que podemos copiar para um arquivo e pensar na próxima fase.

Segunda fase da decodificação

Este é o script que aceita uma lista de transições na forma de arquivo, e tenta fazer a decodificação final. No momento ele implementa os padrões EV1527 e HT6P20. Esse script é Python (para rodar no computador), não MicroPython.

A quem interessar possa, tambem há algumas amostras de keyfobs para você poder testar o script sem depender de hardware. (Não se anime achando que vai abrir minha garagem com isso; essas amostras vieram de um keyfob em desuso.)

A escrita desse código acabou sendo mais fácil do que o esperado. Há muito mais código para fazer a verificação burocrática das transições do que para a decodificação em si. A facilidade vem do fato que temos acesso à lista completa de transições.

Se tivéssemos de fazer a decodificação "em voo", bit por bit, como seria o caso num microcontrolador mais limitado (PIC, talvez AVR), não seria tão fácil. Mas não há necessidade de fazer desse jeito num ESP32. Processar uma lista de 50 itens não é problema, mesmo rodando MicroPython.

Juntando tudo

O script sniffer.py junta as duas metades, realizando a decodificação completa dentro do ESP32. Este script mais 50 reais de hardware é um scanner completo.

Figura 3: Receptor 433MHz no estado atual.

O decodificador contido no sniffer é um pouco mais simples que a versão de teste mencionada antes. Concluímos que não há necessidade de tantas conferências, porque os keyfobs são hardware barato, o receptor é hardware barato, o MicroPython é lento e adiciona imprecisão, então não dá pra ser tão exigente com os "timings" das transições.

O alcance de recepção é o esperado. Não é melhor que o portão eletrônico, mas não parece ser pior.

Se estiver apreciando a história, siga para a parte 2.