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.
Mock tradicional
- responde uma chamada e para
- não mantém estado
- não tem tempo
- não erra de propósito
Simulador de comportamento
- aceita conexão e mantém estado
- respeita tempo e ordem
- reage a heartbeat e queda
- erra de propósito
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
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.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.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.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.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.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
- Simular dispositivo é simular comportamento, não só payload. Conexão, estado, heartbeat e ordem fazem parte do protocolo.
- Tempo é dado. Contador que dá a volta, relógio diferente e atraso a mais mudam o resultado.
- Testar o formato dos dados com casos conhecidos pega erro que um teste de integração levaria horas para encontrar.
- Aleatoriedade determinística é o melhor dos dois mundos: variação sem perder reprodutibilidade.
- A interface é parte da ferramenta. Ver estado, contadores e logs economiza console.
- 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.