Bridge serial para Raspberry Pi que atende balanças e impressoras ESC/POS para vários caixas via HTTP na LAN.
- Python 3.11, 3.12 ou 3.13
- Linux com
/dev/serial/by-id - Permissão de acesso serial (grupos
dialoutelpconforme hardware)
cd utils/pdv-device-bridge
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -e .No Raspberry Pi, execute como o usuário dono do checkout em /opt/pdv-device-bridge:
cd /opt/pdv-device-bridge
./scripts/update.shO script exige uma branch com upstream e checkout sem alterações locais. Ele faz
git pull --ff-only, instala a revisão recebida na .venv, confere o comando
do bridge, reinicia pdv-device-bridge.service com sudo quando necessário e
confere a resposta de /health (inclusive degraded quando um dispositivo
está desconectado).
Se o Git ou a instalação falhar, o serviço não é reiniciado. Para atualizar uma
instalação de desenvolvimento sem systemd, use ./scripts/update.sh --no-restart.
Execute o script novamente se precisar reinstalar as dependências sem haver
novos commits.
- Copie
config.example.tomlpara/etc/pdv-device-bridge/config.toml. - Ajuste
id,pathe parâmetros seriais de cada dispositivo. - O bind HTTP deve permanecer na LAN (ex.:
0.0.0.0:8787em rede interna). - Defina
server.cors_allowed_originscom as origens do PDV web (ex.:http://localhost:8080no desenvolvimento).
Para registrar o Raspberry no Device Control, instale systemd/pdv-device-agent.service, ajustando o ExecStart para o caminho real da .venv no aparelho, configure agent.example.toml em /etc/pdv-device-bridge/agent.toml e use o JSON de pré-vínculo fornecido pelo serviço central como arquivo de provisionamento. O agente mantém UUID e credenciais em /var/lib/pdv-device-bridge e envia heartbeat e eventos pelo canal de saída. Durante a migração de terminais com URL manual, mantenha local.enforce_lan_auth = false; ative a autenticação LAN só depois de migrá-los para o UUID do bridge. Essa mudança reinicia o bridge e deve ser feita com os periféricos ociosos.
pdv-device-bridge --config ./config.example.tomlGET /healthGET /v1/devicesGET /v1/scales/{scale_id}/read?max_age_ms=1500GET /v1/scales/{scale_id}/readings?limit=50(leituras recentes em memória)GET /v1/scales/{scale_id}/events(SSE; eventoscalecomstate=weight|empty|error)POST /v1/printers/{printer_id}/jobsGET /v1/printers/{printer_id}/jobs/{job_id}GET /v1/printers/{printer_id}/jobs?limit=50(fila e jobs recentes)POST /v1/printers/{printer_id}/jobs/{job_id}/retry(reenvia um job com falha)
O histórico de pesagens mantém até 500 leituras por balança e o de impressões,
até 500 jobs por impressora enquanto o processo do bridge estiver ativo. Ambos
são voláteis e são limpos ao reiniciar o serviço. /health e /v1/status
informam a versão instalada. Um reenvio cria um novo job_id,
aponta para o job original em retry_of e pode imprimir duplicado se a falha
original ocorreu depois de os dados chegarem à impressora. Jobs em andamento ou
já impressos não podem ser reenviados por essa operação.
Execute no diretorio do projeto, com o ambiente virtual instalado:
.venv/bin/python scripts/troubleshoot_scale.py --config /etc/pdv-device-bridge/config.tomlO relatorio confere o caminho /dev/serial/by-id, a porta real, permissao do usuario
atual, enumeracao USB, estado do servico e eventos recentes do kernel. Ele nao
envia comandos a balanca por padrao. Para conferir uma leitura real pelo bridge:
.venv/bin/python scripts/troubleshoot_scale.py --read-apiPara isolar erros de abertura como cp210x_open - Unable to enable UART, pare
temporariamente o servico e abra a porta diretamente, sem enviar bytes:
sudo systemctl stop pdv-device-bridge
.venv/bin/python scripts/troubleshoot_scale.py --open-port
sudo systemctl start pdv-device-bridgeO teste direto se recusa a abrir a porta enquanto o servico esta ativo. A abertura
serial pode alterar as linhas de controle DTR/RTS, mesmo sem enviar bytes. Se houver
mais de uma balanca, indique --scale-id ID. Erros antigos do kernel aparecem
como historico; uma abertura ou leitura nova confirma o estado atual.
Com --read-api, o diagnostico tenta ate tres leituras novas. Se uma falhar e a
seguinte funcionar, informa a falha intermitente como aviso. Nesse modo, os eventos
USB mostrados sao apenas os registrados durante a execucao do teste.
PAYLOAD_BASE64=$(printf '\x1b@Teste bridge\n\x1dVA\x10' | base64)
curl -X POST "http://127.0.0.1:8787/v1/printers/printer-caixa-1/jobs" \
-H "Content-Type: application/json" \
-d "{\"payload_base64\":\"${PAYLOAD_BASE64}\",\"content_type\":\"escpos_raw\",\"request_id\":\"sale-123\"}"Arquivo de unidade pronto em:
systemd/pdv-device-bridge.service
Config padrão aplicada:
Restart=alwaysRestartSec=2WatchdogSec=20
O processo envia READY=1 e WATCHDOG=1 automaticamente quando executado com Type=notify.
cd utils/pdv-device-bridge
source .venv/bin/activate
python -m pip install -e .[dev]
pytestCom o bridge ativo, leia pela API local para não disputar a porta serial. Cada amostra pede uma leitura nova (max_age_ms=0) e mostra hora, duração, estado e resposta bruta:
./.venv/bin/python scripts/read_scale.py --scale-id scale-horti-1 --watchSem --watch, o script faz uma única leitura. Encerre o monitor com Ctrl+C. O comando scripts/troubleshoot_scale.py --read-api continua disponível para três tentativas acompanhadas de eventos USB do kernel.
Para isolar o bridge e ler a serial diretamente, faça isso somente em uma janela sem pesagem, com o serviço parado. O script recusa --direct quando o serviço está ativo:
sudo systemctl stop pdv-device-bridge
sudo ./.venv/bin/python scripts/read_scale.py --scale-id scale-horti-1 --direct --watch
# Ctrl+C para encerrar
sudo systemctl start pdv-device-bridgeAusência de bytes nunca vira prato vazio. No perfil padrão, aparece como erro de comunicação. Para a Urano POP-Z configurada com no_response_state = "no_reading", aparece como state=no_reading, sem valor numérico; isso não comprova que o visor está negativo e não libera lançamento. Um quadro válido com peso zero aparece como state=empty, e um quadro negativo válido aparece como state=negative.
- Leitura da balança: timeout serial
800mse limite da operação2500ms, comando0x04 0x05, até200bytes; encerra emCR/LFou após30mssem novos bytes. Apenas um quadro serial válido com peso zero retornastate=empty,grams=0e HTTP200. Por padrão, ausência de bytes, payload não reconhecido e falhas seriais retornam HTTP502e degradam/health. Na Urano POP-Z, o perfilscale-urano-pop-z-9600-8n2configurano_response_state=no_reading, pois a balança pode não responder com peso negativo ou instável; esse estado retorna HTTP200sem peso e não limpa uma falha anterior. Falha de porta, timeout da operação e quadro inválido continuam erros. Uma leitura válida posterior recupera a saúde. - Para uma POP-Z mantida por
config.tomlmanual, adicioneno_response_state = "no_reading"apenas à seção[[devices.scales]]dessa balança. A alteração exige reinício do bridge em janela de manutenção. A seleção do perfil pelo assistente gera a mesma configuração quando o agente gerencia o bridge. - O stream SSE faz leituras novas enquanto houver assinantes, com uma única rotina por balança. Para avaliar a meta de 500 ms, use
scripts/troubleshoot_scale.py --read-apipara ver a duração HTTP e filme a colocação do item junto com a tela do PDV. Ajustescale.read_timeout_msno Raspberry somente após medir as respostas reais; o padrão de 800 ms pode impedir essa meta quando a balança não responde. - A porta da balança permanece aberta entre consultas e é reaberta se o caminho USB mudar ou uma operação serial falhar. Assim o adaptador não precisa ser aberto a cada atualização da tela.
- Cache da última leitura, inclusive peso zero:
1500ms(ajustável pormax_age_ms; as telas ao vivo pedem leitura nova). - Fila por impressora: tamanho máximo
100. - Retry de impressão: backoff
200ms,500ms,1000ms(1 envio inicial + 3 retries). - Escrita da impressora: chunks de
512bytes, pausa15msentre chunks, settle final1000ms, timeout de escrita3000ms.