← voltar ao blog
ACERTOembarcados7min de leituramin

Simulador e relay: o que aprendi testando hardware que não está na bancada

Como foi atuar na construção de simuladores e relays para validar sistemas embarcados sem depender dos equipamentos físicos, e o que essa experiência me ensinou sobre protocolos, testes e compatibilidade.

Matheus Nunes PereiraEngenheiro de Software

Tem uma parte do trabalho com software embarcado que quase ninguém vê: fazer o sistema funcionar quando o hardware não está por perto.

O código compila, os testes unitários passam, mas o sistema só existe por completo quando os sensores, controladores e concentradores de dados estão conectados. E eles quase nunca estão na sua mesa.

Foi nesse contexto que atuei na construção de simuladores e relays. Não vou entrar em detalhes de arquitetura, código ou regras de negócio, porque isso não me pertence. Mas posso contar o que fiz, as competências que desenvolvi e o que aprendi.

Este post é o relato de bastidores do projeto Simulator & Relay, que reúne o simulador e o relay.

O contexto

Os projetos envolviam software que precisava conversar com equipamentos de campo por rede. Cada equipamento tinha o seu protocolo, o seu ciclo de conexão e o seu jeito de falhar. Testar isso com o hardware real era caro, lento e limitado: a bancada não reproduz qualquer cenário, não fica disponível o tempo todo e não produz o caso ruim que você precisa no dia seguinte.

O primeiro impulso foi reaproveitar dados reais de captura. Só que o software em teste não lê um arquivo de captura; ele lê bytes que chegam de um socket. Entre uma coisa e outra existe o protocolo inteiro: cabeçalho, tamanho, ordem dos campos, formato dos números, checksum, heartbeat e a hora certa de abrir e fechar a conexão.

Um mock tradicional não resolve isso. Mock testa função. O que eu precisava era de um dublê de comportamento: algo que aceitasse conexão, mantivesse estado, respondesse a heartbeat e pudesse errar de propósito.

Meu papel

Atuei como pessoa jurídica, em regime de consultoria, com autonomia técnica para conduzir as frentes de desenvolvimento. Na prática, isso significou:

  • entender o comportamento dos equipamentos e traduzir isso em requisitos;
  • implementar os simuladores e os relays;
  • validar com dados reais e com cenários de erro;
  • cuidar do build, do empacotamento e da documentação;
  • apoiar a integração e a depuração quando algo não batia.

Uma parte importante do trabalho foi a comunicação. Eu precisava explicar o que a ferramenta fazia, quais cenários ela cobria e quais ficavam de fora. Documentação e mensagens de erro claras economizavam horas de suporte.

O que construí

Duas frentes complementares: um simulador, que se passava pelos equipamentos e reenviava dados no formato esperado, e um relay, que ficava no meio do caminho para distribuir o tráfego e permitir inspeção sem interferir no produtor.

Em momentos diferentes, usei duas stacks. A primeira foi C++ com SDL2 e ImGui, escolhida por gerar um executável nativo, sem runtime pesado, e rodar em máquinas modestas. A segunda foi Rust no núcleo com Flutter na interface, para uma base mais testável e uma UI mais confortável de configurar.

Não vou detalhar a arquitetura de nenhuma das duas. O que posso dizer é que ambas seguiam a mesma ideia: separar o protocolo da interface, testar o formato dos dados sem abrir socket e deixar a ferramenta observável.

Antes

Mock tradicional

  • responde uma chamada e para
  • não mantém estado
  • não tem tempo
  • não erra de propósito
Depois

Simulador de comportamento

  • aceita conexão e mantém estado
  • respeita tempo e ordem
  • reage a heartbeat e queda
  • erra de propósito
A diferença que muda o tipo de teste

Competências e stack

Camada O que usei
Linguagens C++, Rust, Dart
Interface SDL2 + ImGui, Flutter
Rede TCP, UDP, HTTP
Concorrência threads, async/await, Tokio, ASIO
Integração FFI, flutter_rust_bridge
Build CMake, cross-compile, empacotamento
Práticas testes automatizados, validação de protocolo, documentação, observabilidade

Também trabalhei com compatibilidade com sistemas operacionais antigos, ambientes sem internet, máquinas de 32 bits e hardware sem aceleração gráfica. Isso muda a forma de escolher dependências, de empacotar e de testar. Não é só código: é produto.

Como eu abordava o problema

  1. Entender o comportamento do equipamento.

    Antes de escrever qualquer linha, eu precisava saber o que o equipamento fazia, em que ordem e o que acontecia quando algo saía do previsto.
  2. Reproduzir o protocolo e o ciclo de vida.

    Não bastava montar os dados. Era preciso abrir conexão, manter estado, responder a heartbeat e fechar no momento certo.
  3. Validar com dados reais.

    As capturas de campo eram a referência. Elas mostravam o que o equipamento realmente enviava, não só o que a documentação dizia.
  4. Criar os cenários ruins.

    Dado incompleto, valor fora da faixa, conexão que cai, comportamento inesperado. É aqui que a ferramenta deixa de ser conveniência e vira engenharia.
  5. Deixar a ferramenta observável.

    Status, contadores e logs claros economizam muito tempo de depuração. Quem usa a ferramenta precisa entender o estado sem abrir vários terminais.
  6. Empacotar e documentar.

    Uma ferramenta que só roda na máquina de quem escreveu não resolve o problema do time. Build, pacote e instruções fazem parte da entrega.

Os desafios que mais me marcaram

Simular comportamento, não só dados

O primeiro erro de quem começa é achar que simular é montar o payload. A parte difícil é o ciclo de vida: quando a conexão abre, quanto tempo ela fica aberta, quando fecha, o que acontece se o outro lado cair e como o equipamento reage a um heartbeat. Um pacote perfeito em uma conexão errada não serve para nada.

Tempo e concorrência

Vários canais, delays, eventos simultâneos e a necessidade de reproduzir o mesmo cenário mais de uma vez. Aprendi a preferir aleatoriedade determinística: variação sem perder reprodutibilidade. Se um caso falha, ele precisa falhar de novo.

Também aprendi que tempo é parte do protocolo. Um contador que dá a volta, uma diferença de relógio ou um atraso a mais mudam o comportamento do sistema. Não dá para tratar tempo como detalhe.

Compatibilidade

Uma ferramenta de teste precisa rodar no ambiente em que o time testa, não só no seu. Sistemas antigos, pouca memória, sem GPU, sem internet, às vezes sem permissão de administrador. Build, empacotamento e mensagens de erro fazem parte da entrega.

Validação

Testar o formato dos dados com casos conhecidos foi um dos maiores ganhos. Se você consegue comparar os bytes esperados sem subir servidor, o erro aparece em segundos, não em horas. Também aprendi a separar o protocolo da rede: a rede é uma casca fina em volta de funções puras.

Observabilidade

A ferramenta precisa mostrar o que está acontecendo. Status, contadores, logs por canal e a próxima ação prevista economizam muito console. Quando algo dá errado, você quer olhar para a tela e entender o estado.

Cenários ruins

Simular o caminho feliz é fácil. O valor está em produzir o caso inválido, o dado incompleto, a conexão que cai e o comportamento inesperado. Foi aí que o simulador deixou de ser conveniência e virou ferramenta de engenharia.

Resultado

O ganho foi autonomia. Consegui testar cenários que o hardware real não produz sob demanda, reduzir a dependência de bancada e chegar ao campo com menos surpresa. Também desenvolvi uma visão mais completa de integração: protocolo, rede, interface, build e operação deixam de ser caixas separadas.

Não posso compartilhar os sistemas, os detalhes de implementação ou os resultados específicos. Mas posso compartilhar a abordagem, as tecnologias e o que essa experiência me ensinou. É isso que este post faz.

O que aprendi

  1. Simular dispositivo é simular comportamento, não só payload. Conexão, estado, heartbeat e ordem fazem parte do protocolo.
  2. Tempo é dado. Contador que dá a volta, relógio diferente e atraso a mais mudam o resultado.
  3. Testar o formato dos dados com casos conhecidos pega erro que um teste de integração levaria horas para encontrar.
  4. Aleatoriedade determinística é o melhor dos dois mundos: variação sem perder reprodutibilidade.
  5. A interface é parte da ferramenta. Ver estado, contadores e logs economiza console.
  6. Compatibilidade é parte do produto. O build e o pacote precisam funcionar no ambiente do time.

No fim, a lição que ficou é simples: se o hardware não pode estar na sua mesa, construa um dublê que se comporte como ele. Não para substituir o teste real, mas para chegar nele com menos surpresa.

Neste post
Escrito por Matheus Nunes PereiraEngenheiro de Software. Sistemas distribuídos, telemetria em tempo real e software que conversa com hardware. Se tiver um problema assim, vamos conversar.Engenheiro de Software

Sistemas distribuídos, telemetria em tempo real e software que conversa com hardware. Se tiver um problema assim, vamos conversar.