Skip to content

Repository files navigation

pdv-device-bridge (Python 3.11+)

Bridge serial para Raspberry Pi que atende balanças e impressoras ESC/POS para vários caixas via HTTP na LAN.

Requisitos

  • Python 3.11, 3.12 ou 3.13
  • Linux com /dev/serial/by-id
  • Permissão de acesso serial (grupos dialout e lp conforme hardware)

Instalação

cd utils/pdv-device-bridge
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -e .

Atualização de uma instalação via Git

No Raspberry Pi, execute como o usuário dono do checkout em /opt/pdv-device-bridge:

cd /opt/pdv-device-bridge
./scripts/update.sh

O 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.

Configuração

  1. Copie config.example.toml para /etc/pdv-device-bridge/config.toml.
  2. Ajuste id, path e parâmetros seriais de cada dispositivo.
  3. O bind HTTP deve permanecer na LAN (ex.: 0.0.0.0:8787 em rede interna).
  4. Defina server.cors_allowed_origins com as origens do PDV web (ex.: http://localhost:8080 no 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.

Execução local

pdv-device-bridge --config ./config.example.toml

Endpoints

  • GET /health
  • GET /v1/devices
  • GET /v1/scales/{scale_id}/read?max_age_ms=1500
  • GET /v1/scales/{scale_id}/readings?limit=50 (leituras recentes em memória)
  • GET /v1/scales/{scale_id}/events (SSE; evento scale com state=weight|empty|error)
  • POST /v1/printers/{printer_id}/jobs
  • GET /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.

Diagnostico da balanca no Raspberry Pi

Execute no diretorio do projeto, com o ambiente virtual instalado:

.venv/bin/python scripts/troubleshoot_scale.py --config /etc/pdv-device-bridge/config.toml

O 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-api

Para 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-bridge

O 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.

Exemplo de job ESC/POS bruto

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\"}"

systemd

Arquivo de unidade pronto em:

  • systemd/pdv-device-bridge.service

Config padrão aplicada:

  • Restart=always
  • RestartSec=2
  • WatchdogSec=20

O processo envia READY=1 e WATCHDOG=1 automaticamente quando executado com Type=notify.

Testes

cd utils/pdv-device-bridge
source .venv/bin/activate
python -m pip install -e .[dev]
pytest

Políticas operacionais implementadas

Leitura da balança pelo terminal

Com 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 --watch

Sem --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-bridge

Ausê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 800ms e limite da operação 2500ms, comando 0x04 0x05, até 200 bytes; encerra em CR/LF ou após 30ms sem novos bytes. Apenas um quadro serial válido com peso zero retorna state=empty, grams=0 e HTTP 200. Por padrão, ausência de bytes, payload não reconhecido e falhas seriais retornam HTTP 502 e degradam /health. Na Urano POP-Z, o perfil scale-urano-pop-z-9600-8n2 configura no_response_state=no_reading, pois a balança pode não responder com peso negativo ou instável; esse estado retorna HTTP 200 sem 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.toml manual, adicione no_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-api para ver a duração HTTP e filme a colocação do item junto com a tela do PDV. Ajuste scale.read_timeout_ms no 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 por max_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 512 bytes, pausa 15ms entre chunks, settle final 1000ms, timeout de escrita 3000ms.

About

Bridge serial para Raspberry Pi que atende balanças e impressoras ESC/POS para vários caixas via HTTP na LAN.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages