[ PROJETO / 013 / tr_serialProxy ]

DO WIRESHARK
AO PROXY SERIAL

Do Wireshark a uma ferramenta menor: um proxy serial transparente para registrar e retransmitir somente o tráfego das portas COM escolhidas.

VERSÃO0.1.0 / DESENVOLVIMENTO
PLATAFORMAWINDOWS / PYTHON 3.10+
NÚCLEOPYTHON + PYSERIAL
Diagrama histórico da evolução: primeiro, a prova com conversores físicos; depois, a arquitetura com COM virtual. A parte inferior ainda mostra COM20/COM21 e chama a solução de desejada, mas ela já foi validada posteriormente com COM13/COM14. O success_count pertence ao material ilustrativo e não existe no proxy 0.1.0 atual.

O problema começou no Wireshark.

O experimento Do movimento aos bytes — explorando USB HID com Wireshark mostrou que informações normalmente invisíveis entre software e hardware podiam ser observadas. Endpoints, relatórios HID e payloads deixaram de ser abstrações e apareceram como bytes.

WIRESHARK↓TRÁFEGO INVISÍVEL SE TORNA OBSERVÁVEL↓COMUNICAÇÃO DE EQUIPAMENTOS REAIS↓tr_serialProxy

A descoberta permaneceu, mas a necessidade seguinte era bem mais estreita: quais bytes este software envia para esta porta COM e quais bytes o equipamento devolve?

Uma ferramenta excelente podia ser grande demais para a tarefa.

O Wireshark não foi aprovado para uso normal no computador de trabalho. A preocupação fazia sentido: captura de rede e USB em baixo nível possui alcance amplo, pode envolver drivers próprios e determinadas operações podem exigir privilégios administrativos.

Uma máquina offline dedicada à análise continuava sendo uma alternativa válida. Mas a restrição ajudou a formular uma pergunta melhor: se o objetivo era observar somente portas COM, por que capturar USB bruto, rede ou protocolos sem relação com o equipamento?

[ ESCOPO COMO DECISÃO DE PROJETO ]

A solução não tenta contornar políticas. Ela reduz a ferramenta ao recurso necessário: duas portas seriais explicitamente escolhidas pelo usuário.

Python entrou no caminho dos bytes.

SOFTWAREabre COM virtual A
↔
COM VIRTUALA ↔ B
↔
tr_serialProxyregistra e encaminha
↔
COM FÍSICAadaptador real
↔
DISPOSITIVOequipamento

O projeto é um proxy serial transparente. Ele não captura o barramento USB, a rede ou outras aplicações. Duas threads leem simultaneamente os dois sentidos; cada bloco recebido é registrado e então escrito integralmente no destino.

TXsoftware → dispositivo
RXdispositivo → software

Antes da porta virtual, havia fios sobre a bancada.

Primeiro foi estabelecida uma referência sem o proxy: o software se conectava diretamente à COM7, correspondente ao adaptador USB específico da moto elétrica, e comunicava com o controlador VOTOL.

SOFTWARE / COM7↔ADAPTADOR USB DA MOTO↔CONTROLADOR VOTOL

Para colocar o Python no caminho foram acrescentados dois conversores USB–RS485. A COM6 e a COM8 pertenciam a esses conversores e estavam conectadas eletricamente pelo par A/B. Nenhuma delas era o adaptador da moto.

SOFTWARE / COM6↔USB–RS485 / COM6↔ A/B ↔USB–RS485 / COM8↔tr_serialProxy↔COM7 / ADAPTADOR USB / VOTOL

Antes de iniciar o Python, selecionar COM6 no software ainda produzia Communication abnormal, no response: os dois conversores formavam a ligação COM6 ↔ COM8, mas o caminho entre COM8 e o adaptador da moto em COM7 ainda não existia. O proxy completava justamente esse trecho.

O proxy abriu COM7 como --physical-port e COM8 como --virtual-port. Apesar do nome do argumento, COM8 ainda era uma porta física nesse protótipo: “virtual” descrevia o lado voltado ao software. Depois que o Python entrava em execução, o software deixava de usar COM7 e passava a abrir COM6 com comunicação bem-sucedida. Tentar abrir COM8 diretamente no software também não representava a montagem e resultava em ausência de resposta.

Referência sem proxy: o software abre COM7, o adaptador USB ligado ao controlador VOTOL.
Antes de abrir o proxy: COM6 já estava selecionada, mas o software ainda não alcançava o controlador pela ponte incompleta.
Depois de iniciar o proxy, o software permanece na COM6 e a comunicação volta a funcionar.
O Python completa o caminho: abre COM7 no lado do controlador e COM8 no lado do software. O caminho local foi removido da captura.
COM8 não era a porta que o software deveria abrir nessa montagem; ela pertencia ao lado do proxy.
python -m tr_serial_proxy --physical-port COM7 --virtual-port COM8 \
  --baud 9600 --raw-log

O Python recebeu, registrou e retransmitiu os bytes; recebeu a resposta, registrou-a e a devolveu ao software. A aplicação e o controlador continuaram se comunicando. Era uma montagem maior do que a solução desejada, mas comprovava o princípio com os recursos disponíveis.

Substituir dois conversores por um null modem virtual.

O com0com cria duas portas COM conectadas entre si. Tudo que é escrito em uma ponta aparece na outra. Na validação, o par foi COM13 ↔ COM14: o software abriu COM13 e o proxy abriu COM14 junto da porta física do equipamento.

SOFTWARE / COM13↔com0com↔COM14 / PROXY↔COM FÍSICA / EQUIPAMENTO

Esses números documentam o teste, não são requisitos. Qualquer par configurado pode ser informado. A instalação do driver é uma operação separada e pode exigir privilégios administrativos; o tr_serialProxy não instala nem configura drivers.

A aplicação continuou funcionando.

A captura abaixo mostra a aplicação de telemetria conectada à COM13 em 9600 baud e, ao lado, o terminal registrando TX e RX. Ela demonstra o resultado que importava: inserir o proxy no caminho sem impedir a comunicação real.

Validação com aplicação real: o software continua comunicando pela COM virtual enquanto o tr_serialProxy registra e retransmite os blocos TX e RX. A captura foi recortada para omitir uma identificação de hardware sem relevância para o projeto.

Os payloads visíveis permanecem apenas como contexto da captura autorizada. Eles não são transcritos, interpretados nem apresentados como documentação do protocolo.

Uma leitura não é necessariamente uma mensagem.

Nos testes, uma sequência podia surgir em blocos de um byte, depois dois, depois vários. Respostas apareceram alternando leituras de 1 e 23 bytes. Isso não significa que cada read() seja um frame, que o equipamento tenha inserido uma quebra ou que os conversores tenham modificado o conteúdo.

TX 1 byteTX 2 bytesRX 1 byteRX 23 bytes

Porta serial é fluxo de bytes. A chamada retorna o que estava disponível naquele momento, dentro do limite configurado. Por isso, os contadores do programa medem blocos registrados, não mensagens de protocolo nem entregas confirmadas.

VER CAPTURA AUTORIZADA DE BYTES

Registro do ensaio com COM7 e COM8. Os bytes são publicados como evidência do encaminhamento, sem interpretação ou descrição do protocolo.

tr_serialProxy

Physical: COM7
Virtual:  COM8
Settings: 9600 8N1
Flow:     none

[2026-09-30T10:26:38.108685-03:00] [+26.351542] TX  1 bytes
C9
[2026-09-30T10:26:38.109822-03:00] [+26.352654] TX  1 bytes
14
[2026-09-30T10:26:38.110886-03:00] [+26.353712] TX  1 bytes
02
[2026-09-30T10:26:38.111882-03:00] [+26.354707] TX  1 bytes
4C
[2026-09-30T10:26:38.112937-03:00] [+26.355763] TX  1 bytes
44
[2026-09-30T10:26:38.115728-03:00] [+26.358559] TX  2 bytes
47 45
[2026-09-30T10:26:38.116139-03:00] [+26.358976] TX  1 bytes
54
[2026-09-30T10:26:38.117854-03:00] [+26.360695] TX  1 bytes
00
[2026-09-30T10:26:38.118809-03:00] [+26.361643] TX  1 bytes
00
[2026-09-30T10:26:38.119853-03:00] [+26.362682] TX  1 bytes
00
[2026-09-30T10:26:38.120893-03:00] [+26.363716] TX  1 bytes
00
[2026-09-30T10:26:38.121939-03:00] [+26.364778] TX  1 bytes
00
[2026-09-30T10:26:38.123066-03:00] [+26.365975] TX  1 bytes
00
[2026-09-30T10:26:38.124065-03:00] [+26.366903] TX  1 bytes
00
[2026-09-30T10:26:38.125100-03:00] [+26.367947] TX  1 bytes
00
[2026-09-30T10:26:38.126122-03:00] [+26.368970] TX  1 bytes
00
[2026-09-30T10:26:38.127150-03:00] [+26.369983] TX  1 bytes
00
[2026-09-30T10:26:38.128209-03:00] [+26.371058] TX  1 bytes
00
[2026-09-30T10:26:38.129197-03:00] [+26.372028] TX  1 bytes
00
[2026-09-30T10:26:38.130332-03:00] [+26.373159] TX  1 bytes
00
[2026-09-30T10:26:38.131329-03:00] [+26.374154] TX  1 bytes
00
[2026-09-30T10:26:38.132332-03:00] [+26.375165] TX  1 bytes
81
[2026-09-30T10:26:38.133395-03:00] [+26.376246] TX  1 bytes
0D

[2026-09-30T10:26:38.164400-03:00] [+26.407245] RX  1 bytes
C0
[2026-09-30T10:26:38.164807-03:00] [+26.407630] RX  23 bytes
14 05 52 07 39 C7 0D E7 50 78 00 00 00 00 28 02 58 05 C2 00 2C 21 0D

[2026-09-30T10:26:38.236722-03:00] [+26.479569] RX  1 bytes
C0
[2026-09-30T10:26:38.237234-03:00] [+26.480067] RX  23 bytes
14 05 52 06 C0 00 C0 8A C0 00 C0 00 C0 01 C8 10 C0 0F CA 92 2C AD 0D

[2026-09-30T10:26:38.308980-03:00] [+26.551831] RX  1 bytes
C0
[2026-09-30T10:26:38.309534-03:00] [+26.552361] RX  23 bytes
14 05 52 05 C0 00 C0 00 C0 05 C0 09 C0 00 C0 00 C0 07 C1 0E 05 87 0D

[2026-09-30T10:26:38.381296-03:00] [+26.624143] RX  1 bytes
C0
[2026-09-30T10:26:38.381777-03:00] [+26.624607] RX  23 bytes
14 05 52 04 0E 0C CC 01 40 03 B6 4B 64 0E 00 C8 19 64 00 23 03 09 0D

[2026-09-30T10:26:38.453570-03:00] [+26.696412] RX  1 bytes
C0
[2026-09-30T10:26:38.453988-03:00] [+26.696822] RX  23 bytes
14 05 52 03 FF C4 50 64 69 00 7D 00 14 24 D4 0A 00 1E 14 E8 71 E6 0D

[2026-09-30T10:26:38.526253-03:00] [+26.769095] RX  1 bytes
C0
[2026-09-30T10:26:38.526663-03:00] [+26.769492] RX  23 bytes
14 05 52 02 00 64 2D 4B 64 0B B8 23 28 17 70 A4 4B 0C 1E 1E 0F D4 0D

[2026-09-30T10:26:38.598807-03:00] [+26.841654] RX  1 bytes
C0
[2026-09-30T10:26:38.599299-03:00] [+26.842133] RX  23 bytes
14 05 52 01 14 02 03 84 02 76 0A 00 1E 23 28 02 58 05 C2 00 2C C9 0D

Primeiro enxergar. Interpretar depois.

A versão inicial não tenta decidir se os dados são Modbus, um protocolo proprietário, JBD, VOTOL ou CAN encapsulado. Seu núcleo é deliberadamente menor:

BYTE RECEBIDO→REGISTRAR→ENCAMINHAR O MESMO BYTE

A análise pode ser feita posteriormente sobre os logs. Essa neutralidade permite usar o proxy com USB-TTL, USB-RS485, CDC/serial e conversores CAN que apareçam no Windows como uma COM. Interfaces CAN que dependam de API própria e não exponham uma porta serial ficam fora do escopo.

Tempo, direção, tamanho e bytes.

Cada execução cria um arquivo textual próprio. O registro inclui horário local com fuso e microssegundos, tempo decorrido medido por contador monotônico, direção, quantidade de bytes e conteúdo hexadecimal.

[2026-09-30T14:35:22.123456-03:00] [+0.381224] TX  4 bytes
00 41 7F FF

Com --raw-log, o mesmo conteúdo também é gravado como JSONL reversível:

{"time":"2026-09-30T14:35:22.123456-03:00","elapsed":0.381224,"direction":"TX","length":4,"data":"00417fff"}

Os valores acima são fictícios e vêm da documentação pública. --ascii acrescenta somente uma visualização dos caracteres imprimíveis; os bytes permanecem representados em hexadecimal.

As portas e os parâmetros são explícitos.

--physical-portCOM física do equipamento; obrigatório
--virtual-portponta aberta pelo proxy; obrigatório
--baudinteiro positivo; obrigatório
--bytesize5, 6, 7 ou 8; padrão 8
--parityN, E, O, M ou S; padrão N
--stopbits1, 1.5 ou 2; padrão 1
--timeout / --write-timeout0,01 s / 1 s por padrão
--xonxoff / --rtscts / --dsrdtrcontroles de fluxo opcionais
--chunk-sizeaté 4096 bytes por leitura
--log-dir / --log-prefixdestino e prefixo dos logs
--quiet / --ascii / --raw-logsaída reduzida, visualização ASCII e JSONL
--list-portslista as portas sem exigir os demais argumentos
tr-serial-proxy --physical-port COM6 --virtual-port COM14 \
  --baud 9600 --bytesize 8 --parity N --stopbits 1 --raw-log

O comando deve ser ajustado às portas e à configuração efetivas. O nome --virtual-port descreve o papel da ponta no fluxo; no primeiro protótipo, esse argumento chegou a apontar para uma COM física.

Menos capacidade genérica, não risco zero.

Em vez de uma ferramenta capaz de observar diversos tipos de tráfego do computador, o proxy acessa apenas as duas portas seriais informadas. Isso torna seu comportamento mais limitado, previsível e auditável.

[ LOGS TAMBÉM SÃO DADOS ]

Uma captura serial pode conter comandos, números de série, parâmetros, respostas proprietárias, tokens ou credenciais. Capturar menos não autoriza publicar automaticamente o que foi capturado.

Os logs ficam locais e são ignorados pelo Git do projeto. Ainda assim, qualquer arquivo ou captura precisa ser revisado antes de sair do ambiente em que foi produzido.

Transparente nos bytes, não no tempo elétrico.

  • Drivers, buffers, terminal e disco acrescentam latência; não há garantia de tempo real.
  • RTS, DTR, CTS, DSR, BREAK e outros sinais de controle não são espelhados.
  • Mudanças de configuração feitas pelo software na COM virtual não são propagadas automaticamente.
  • Não há reconexão automática, reconstrução de frames, reconhecimento de baud ou reenvio após entrega incerta.
  • Não é indicado começar por firmware ou bootloader crítico sem validação específica.

A ferramenta ficou entre duas investigações.

O artigo do Wireshark e USB HID forneceu a origem conceitual: tornar tráfego invisível observável. Já o mapeamento Entre o Greenway e o BMS mostra por que uma captura serial bidirecional pode ser útil antes de qualquer interpretação de protocolo.

O código e toda a documentação técnica estão no repositório público do tr_serialProxy ↗.

O núcleo continua pequeno.

A versão 0.1 entrega bridge bidirecional e logs. O roadmap público reserva filtros de visualização para a 0.2, estatísticas e análise de timing para a 0.3 e, no futuro, replay opcional e decodificadores como plugins.

Nenhum desses itens é apresentado como concluído. Mesmo quando crescer, a base continua sendo a mesma: capturar, registrar e retransmitir sem modificar os bytes.

[ CHECKPOINT / tr_serialProxy 0.1.0 ]

A melhor ferramenta nem sempre é a mais poderosa.

Entender exatamente o que precisava ser observado permitiu trocar uma capacidade ampla por uma solução menor, específica e verificável — e transformar uma restrição real em uma definição melhor do problema.