La modalità operativa raccomandata è esplicita:
DELIVERY_MODE=transparent_duplexPrintproxy mantiene una connessione client e una connessione stampante attive contemporaneamente. Due coroutine indipendenti trattano i flussi:
CLIENT_TO_PRINTER PRINTER_TO_CLIENT
client read printer read
| |
+--> RAW temporaneo + fsync +--> digest/capture limitata
| |
+--> printer write/drain +--> client write/drain
Il payload non è decodificato nel percorso di relay. DLE EOT, ASB, ACK proprietari, dati server-first e qualsiasi altro byte sono opachi. Parsing, testo pulito e PDF lavorano su una copia archiviata dopo il percorso critico.
Settings.proxy_configs contiene route immutabili. Un MultiProxyService
costruisce una PrintProxyService per route, ciascuna con listener, lock
stampante, task, store, ledger e failure domain propri:
MultiProxyService
|-- proxy-001: 10.1.2.220:9100 <-> 10.1.2.200:9100
|-- proxy-002: 10.1.2.221:9100 <-> 10.1.2.201:9100
`-- proxy-003: 10.1.2.222:9100 <-> 10.1.2.202:9100
Recovery e bind iniziali sono transazionali: se una route non può partire, i listener già aperti vengono chiusi. Dopo startup, un guasto stampante/sessione o un errore fatale route-scoped non interrompe le route sane. Se tutte falliscono, il supervisore termina non-zero.
Archivio e spool multi sono entrambi keyed dall'IPv4 validato della stampante fisica. La chiave non cambia riordinando le liste CSV e impedisce che uno stato operativo venga riaperto da una route diretta a un'altra stampante.
- Ogni byte client viene scritto su storage durevole prima di poter entrare nel send buffer del socket stampante.
- Ogni byte letto dalla stampante viene inoltrato al client senza aggiunte, soppressioni o reinterpretazione.
- Il limite della capture di risposta riguarda solo JSON/log; non limita il relay né il digest completo.
- Non esiste una risposta sintetica: nessun
OK, ACK0x06o status emulato. - Una sola sessione live possiede la stampante. Un secondo client viene resettato fail-fast, senza attesa e senza invio di suoi dati.
- Un job duplex non è replayable.
retry_allowed=falseresta autenticato nei metadata e il worker legacy rifiuta qualsiasi tentativo. SENT_UNCONFIRMEDindica completamento del relay TCP osservato localmente, non conferma stampa fisica.- Un errore di parser,
.PULITO.txto PDF è un errore sidecar: non può cambiare i byte o l'esito della sessione TCP già conclusa.
Il proxy apre l'upstream prima di consumare il payload client. Questo conserva anche protocolli che inviano dati server-first. La proprietà esclusiva è implementata con un lock per la singola stampante fisica:
client A connect -> lock libero -> upstream aperto -> sessione attiva
client B connect -> lock occupato -> RST immediato
Il comportamento del client B non va descritto come una coda: nessun job B è posticipato e nessun byte B deve essere stampato dopo la scadenza del gestionale.
MAX_CONCURRENT_CLIENTS limita le socket applicative, mentre l'esclusività della
stampante limita a uno il relay live effettivo.
Per ogni chunk client:
read client
-> write completo in receiving/<uuid>.raw.tmp
-> fsync del file
-> marker HMAC DUPLEX_ACTIVE prima della prima writer.write()
-> write/drain verso stampante
Il costo di fsync è intenzionale: impedisce il caso in cui carta possa uscire ma il corrispondente prefisso RAW non sia recuperabile. L'I/O bloccante è delegato a un thread; la coroutine inversa resta schedulabile per ricevere status.
Alla frontiera del job, il RAW è pubblicato con move/rename durevole, riletto per SHA-256, associato ai metadata e registrato nella hash chain/HMAC.
RECEIVING
|
| primo invio armato, marker durevole prima di write()
v
DUPLEX_ACTIVE
|
+--> archivio completo + relay completo ------> SENT_UNCONFIRMED
|
+--> write possibile + crash/errore ----------> UNKNOWN_PRINT_STATE
|
+--> recovery dopo DUPLEX_ACTIVE -------------> UNKNOWN_PRINT_STATE
nessuna write possibile + archivio disponibile ---> DUPLEX_ABORTED
input incompleto/limite/storage -------------------> PARTIAL o QUARANTINED
DUPLEX_ABORTED: fallimento live prima di una write possibile;retry_allowed=false. È il gestionale, dopo l'errore TCP, a decidere una nuova sessione.UNKNOWN_PRINT_STATE: la stampante può aver ricevuto un prefisso o tutto; nessun replay, automatico o manuale, è ammesso in modalità duplex.SENT_UNCONFIRMED: tutti i byte del segmento sono stati archiviati e inoltrati senza errore osservato.physical_print_confirmed=falseresta esplicito.PARTIAL: input non completo per policy o errore di storage; conservato come evidenza, non replayato.QUARANTINED: incoerenza di integrità o storage; richiede intervento.
La modalità legacy store_forward conserva la propria state machine
QUEUED -> SEND_ARMED -> SENDING, con retry sicuro solo prima di SENDING.
Questa state machine non deve essere proiettata sui job duplex.
Quando il gestionale invia FIN sul proprio lato TX (shutdown(SHUT_WR)):
- il proxy registra
client_fin_receivedeclient_close_kind=fin; - invia
write_eof()verso la stampante quando supportato; - non chiude il reverse relay;
- continua stampante → client fino a FIN stampante, errore/RST,
PRINTER_RESPONSE_TIMEOUTsenza attività o deadline totaleFORWARD_TIMEOUT; - chiude poi socket e finalizza gli artefatti.
Il timeout del tail è necessario perché molte stampanti RAW non inviano FIN. Deve essere superiore al massimo ritardo reale degli status. Non è una prova che la stampa sia terminata.
Un FIN della stampante viene propagato al lato RX del client quando possibile. Un RST/errore in una direzione resetta conservativamente l'altro lato e rende l'esito incerto se una write era possibile.
TCP è uno stream; i pacchetti non sono job. Le policy accettate sono:
connection_close: FIN client delimita il segmento;idle_timeout: un gap sigilla un segmento non vuoto;hybrid: il primo tra FIN e gap.
In duplex un idle boundary separa solo gli artefatti: la stessa connessione
upstream resta attiva per il segmento successivo. SESSION_IDLE_TIMEOUT limita
l'attesa tra segmenti; MAX_SESSION_DURATION limita l'intera sessione.
SPLIT_ON_ESCPOS_CUT=yes è rifiutato. Un semplice pattern GS V non è un
framing sicuro dentro un linguaggio con raster, barcode e comandi length-prefixed.
Il percorso standard produce:
RAW byte-identico
+--> TXT tecnico
+--> Document Model ESC/POS
+--> PULITO.TXT
+--> PDF 80 mm ad altezza variabile
+--> JSON metadata e hash
I renderer sono limitati da byte, nodi, caratteri, pixel e concorrenza. Scrivono file temporanei nella directory di destinazione, fsyncano file e directory su POSIX e pubblicano con replace atomico. ReportLab è caricato solo dal sidecar. Il RAW non viene mai ricostruito dall'AST.
Le sequenze ESC * omogenee da 8/24 dot separate da ESC J posizionale
vengono prima composte in un unico canvas bounded. ESC * disegna alla
posizione corrente ma non avanza la carta: sommare altezza della banda e feed
separatamente produceva il doppio avanzamento e bande visibili. PDF e OCR usano
lo stesso raster ricostruito.
Un OCR Tesseract bounded viene tentato soltanto sulle bitmap candidate. Un
successo sopra soglia crea RasterTextBlock: il PDF conserva source_bitmap,
mentre .PULITO.txt usa il testo e metadata/log conservano provenienza,
confidence e bounding box senza loggare il contenuto. Fallimento, timeout o
bassa confidenza lasciano il placeholder e non cambiano RAW o relay.
- SHA-256 verifica RAW e sidecar registrati.
- JSON canonico impedisce rappresentazioni ambigue.
manifest.jsonlcollega gli eventi con hash chain.- HMAC-SHA-256 autentica eventi e head con una chiave root-only.
manifest.head.jsonrileva truncation/rollback locale non coerente.printproxyctl verify, a daemon fermo, rilegge l'intera storia e ogni file.
DATA_DIR e SPOOL_DIR devono usare storage Linux locale con semantica di fsync e rename affidabile. NFS/CIFS, FUSE/DrvFS, overlay e tmpfs sono esclusi dal modello di durabilità.
| Evento | Effetto client | Stato archivio | Replay proxy |
|---|---|---|---|
| upstream non apribile prima del payload | RST fail-fast | nessun job o DUPLEX_ABORTED se già iniziato |
no |
| secondo client durante sessione attiva | RST fail-fast | nessun job del secondo client | no |
| client half-close, status ritardato | RX resta aperto entro timeout | SENT_UNCONFIRMED se completo |
no |
| printer RST dopo possibile write | RST al client | UNKNOWN_PRINT_STATE |
no |
crash dopo marker DUPLEX_ACTIVE |
sessione interrotta | recovery UNKNOWN_PRINT_STATE |
no |
| ENOSPC/storage failure | RST/stop conservativo | PARTIAL/QUARANTINED |
no |
| sidecar PDF fallisce | nessuna modifica al relay concluso | errore nel JSON | non applicabile |
Il parser di configurazione mantiene store_forward come default interno solo
per non cambiare in silenzio vecchi file, ma install.sh richiede che
DELIVERY_MODE sia presente. Prima di attivare duplex si deve verificare e
smaltire con la modalità legacy ogni backlog replayable. L'avvio duplex fallisce
se rileva stati legacy ancora inoltrabili.
- POS80BL espone davvero RAW TCP/9100 e non LPR/IPP.
- Il firmware inoltra status DLE EOT/ASB sulla stessa connessione TCP.
PRINTER_RESPONSE_TIMEOUTsupera il massimo ritardo misurato.IDLE_TIMEOUTsupera ogni gap intra-scontrino.- Il gestionale gestisce correttamente un RST fail-fast se la stampante è già occupata.
- 64 MiB e 900 secondi coprono i job/sessioni legittimi.
- Una campagna direct-vs-proxy conferma FIN/RST, timing e byte osservabili.
Queste assunzioni non risultano validate su hardware POS80BL dal solo test automatico con emulatore.