From ed787b626679e945e1f5bc9a1b164f943629fa2e Mon Sep 17 00:00:00 2001 From: Nicola Giancecchi Date: Sun, 5 Feb 2023 19:20:15 +0100 Subject: [PATCH 001/105] Fixed issues with PHP8 --- esc2html.php | 10 +++++----- src/Parser/Command/Printout.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esc2html.php b/esc2html.php index 1204486..2ad3e52 100644 --- a/esc2html.php +++ b/esc2html.php @@ -49,7 +49,7 @@ } // Block-level formatting such as text justification $classes = getBlockClasses($formatting); - $classesStr = implode($classes, " "); + $classesStr = implode(" ", $classes); $outp[] = wrapInline("
", "
", $lineHtml); $lineHtml = ""; } @@ -60,14 +60,14 @@ } else if ($sub -> isAvailableAs('PrintBufferredDataGraphicsSubCmd') && $bufferedImg !== null) { // Append and flush buffer $classes = getBlockClasses($formatting); - $classesStr = implode($classes, " "); + $classesStr = implode(" ", $classes); $outp[] = wrapInline("
", "
", imgAsDataUrl($bufferedImg)); $lineHtml = ""; } } else if ($cmd -> isAvailableAs('ImageContainer')) { // Append and flush buffer $classes = getBlockClasses($formatting); - $classesStr = implode($classes, " "); + $classesStr = implode(" ", $classes); $outp[] = wrapInline("
", "
", imgAsDataUrl($cmd)); $lineHtml = ""; // Should load into print buffer and print next line break, but we print immediately, so need to skip the next line break. @@ -101,7 +101,7 @@ function imgAsDataUrl($bufferedImg) $imgSrc = "data:image/png;base64," . base64_encode($bufferedImg -> asPng()); $imgWidth = $bufferedImg -> getWidth() / 2; // scaling, images are quite high res and dwarf the text $bufferedImg = null; - return "\"$imgAlt\""; + return "\"$imgAlt\""; } function wrapInline($tag, $closeTag, $content) @@ -169,7 +169,7 @@ function span(InlineFormatting $formatting, $spanContentText = false) if (count($classes) == 0) { return $spanContentHtml; } - return "" . $spanContentHtml . ""; + return "" . $spanContentHtml . ""; } function getBlockClasses($formatting) diff --git a/src/Parser/Command/Printout.php b/src/Parser/Command/Printout.php index e5e5dd0..cdc67a2 100755 --- a/src/Parser/Command/Printout.php +++ b/src/Parser/Command/Printout.php @@ -186,6 +186,6 @@ public function logUnknownCommand(array $searchStack) $cmdStack[] = $s; } } - fwrite(STDERR, "WARNING: Unknown command " . implode($cmdStack, ' ') . "\n"); + fwrite(STDERR, "WARNING: Unknown command " . implode(' ', $cmdStack) . "\n"); } } From 696d4b3774f4874d4050e3dada6446ab21a0ad83 Mon Sep 17 00:00:00 2001 From: Nicola Giancecchi Date: Thu, 9 Feb 2023 15:00:22 +0100 Subject: [PATCH 002/105] Updated css --- src/resources/esc2html.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/resources/esc2html.css b/src/resources/esc2html.css index 46cfadb..88a3b7b 100644 --- a/src/resources/esc2html.css +++ b/src/resources/esc2html.css @@ -1,7 +1,10 @@ +body { + margin: 0; +} + .esc-receipt { border: 1px solid #888; font-family: monospace; - margin: 1em; padding: 1em; min-width: 28em; display: inline-block; From 4447deaa0b1c573249b10a766790171669f12204 Mon Sep 17 00:00:00 2001 From: Nicola Giancecchi Date: Thu, 9 Feb 2023 15:10:38 +0100 Subject: [PATCH 003/105] Removed min-width --- src/resources/esc2html.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resources/esc2html.css b/src/resources/esc2html.css index 88a3b7b..d259b8f 100644 --- a/src/resources/esc2html.css +++ b/src/resources/esc2html.css @@ -5,8 +5,9 @@ body { .esc-receipt { border: 1px solid #888; font-family: monospace; + /*margin: 1em;*/ padding: 1em; - min-width: 28em; + /*min-width: 28em;*/ display: inline-block; } From c9fad6945382b04e3eda861cd00982bcca260685 Mon Sep 17 00:00:00 2001 From: Nicola Giancecchi Date: Thu, 9 Feb 2023 16:16:23 +0100 Subject: [PATCH 004/105] Updated CSS --- src/resources/esc2html.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/resources/esc2html.css b/src/resources/esc2html.css index d259b8f..0244001 100644 --- a/src/resources/esc2html.css +++ b/src/resources/esc2html.css @@ -8,6 +8,8 @@ body { /*margin: 1em;*/ padding: 1em; /*min-width: 28em;*/ + transform: scale(1.25); + transform-origin: top left; display: inline-block; } From 899a237be4a58da5344a100d89113ae13e20489e Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 11 Sep 2023 17:47:40 -0400 Subject: [PATCH 005/105] Environnement de base: Container docker avec - flask-socketio - php et escpos-tools --- .dockerignore | 2 ++ dockerfile | 21 +++++++++++++++++++++ miniflask.py | 10 ++++++++++ 3 files changed, 33 insertions(+) create mode 100644 .dockerignore create mode 100644 dockerfile create mode 100644 miniflask.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5bc776e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +dockerfile +doc/** diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..7ae9360 --- /dev/null +++ b/dockerfile @@ -0,0 +1,21 @@ +#On part de l'image Debian de php +FROM php:8.1-cli + +#On va utiliser l'utilitaire "install-php-extensions" au lieu de PECL car il marche mieux. +ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ +RUN chmod +x /usr/local/bin/install-php-extensions +RUN install-php-extensions imagick @composer mbstring + +#Note: utiliser "." au lieu de * permet de garder la structure et envoyer tous les sous-répertoires +ADD . /home/escpos-emu/ +WORKDIR /home/escpos-emu/ + +#Installation de Flask et Flask-socketio +RUN apt-get update +RUN apt-get install -y python3-flask-socketio + +RUN composer install + +EXPOSE 5000 +CMD [ "python3", "miniflask.py"] +#ENTRYPOINT [ "/bin/bash" ] \ No newline at end of file diff --git a/miniflask.py b/miniflask.py new file mode 100644 index 0000000..8214cfa --- /dev/null +++ b/miniflask.py @@ -0,0 +1,10 @@ +from flask import Flask + +app = Flask(__name__) + +@app.route("/") +def hello_world(): + return "

Hello, World FLG!

" + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file From 5fee14abb09cbb3a6f004d64a4bcd9885967cc1f Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 12 Sep 2023 16:09:32 -0400 Subject: [PATCH 006/105] =?UTF-8?q?Cr=C3=A9ation=20d'un=20serveur=20TCP=20?= =?UTF-8?q?=C3=A0=20c=C3=B4t=C3=A9=20de=20Flask?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dockerfile | 6 ++++-- miniflask.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/dockerfile b/dockerfile index 7ae9360..45dc6ab 100644 --- a/dockerfile +++ b/dockerfile @@ -2,6 +2,7 @@ FROM php:8.1-cli #On va utiliser l'utilitaire "install-php-extensions" au lieu de PECL car il marche mieux. +#Voir: https://github.com/mlocati/docker-php-extension-installer ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ RUN chmod +x /usr/local/bin/install-php-extensions RUN install-php-extensions imagick @composer mbstring @@ -10,12 +11,13 @@ RUN install-php-extensions imagick @composer mbstring ADD . /home/escpos-emu/ WORKDIR /home/escpos-emu/ -#Installation de Flask et Flask-socketio +#Installation de Flask RUN apt-get update -RUN apt-get install -y python3-flask-socketio +RUN apt-get install -y python3-flask RUN composer install EXPOSE 5000 +# Démarrer le serveur Flask avec le script miniflask CMD [ "python3", "miniflask.py"] #ENTRYPOINT [ "/bin/bash" ] \ No newline at end of file diff --git a/miniflask.py b/miniflask.py index 8214cfa..e3330fa 100644 --- a/miniflask.py +++ b/miniflask.py @@ -1,4 +1,32 @@ from flask import Flask +import random, socket, threading + +#tcp server +HOST = '' #Empty string accepts all origins +TCP_PORT = 9100 + +def launchServer(): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((HOST, TCP_PORT)) + s.listen() + + while True: #Recevoir des connexions, une à la fois, pour l'éternité. + print('waiting for connection') + conn, addr = s.accept() + with conn: + print (f"Adress connected: {addr}") + taille = 0 + while True: + data = conn.recv(1024) # receive 1024 bytes (or less) + taille += 1 + if not data: + break #pour arrêter quand tout est reçu + conn.sendall(b"All done!") + print (f"Received {taille} packets", flush=True) + + + + app = Flask(__name__) @@ -7,4 +35,10 @@ def hello_world(): return "

Hello, World FLG!

" if __name__ == "__main__": + #Lancer le service TCP + t = threading.Thread(target=launchServer) + t.daemon = True + t.start() + + #Lancer l'application Flask app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file From 22d31826f11dbc528191cc5523363400b2da58d7 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 12 Sep 2023 16:55:18 -0400 Subject: [PATCH 007/105] =?UTF-8?q?R=C3=A9ception=20de=20l'impression=20fo?= =?UTF-8?q?nctionne,=20sans=20en=20faire=20une=20visualisation=20HTML?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniflask.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/miniflask.py b/miniflask.py index e3330fa..4d0c6cb 100644 --- a/miniflask.py +++ b/miniflask.py @@ -15,14 +15,21 @@ def launchServer(): conn, addr = s.accept() with conn: print (f"Adress connected: {addr}") - taille = 0 + binfile = open("reception.bin", "wb") + while True: data = conn.recv(1024) # receive 1024 bytes (or less) - taille += 1 - if not data: - break #pour arrêter quand tout est reçu - conn.sendall(b"All done!") - print (f"Received {taille} packets", flush=True) + if not data: #Quand tout a été reçu + binfile.close() #Écrire le fichier et le fermer + break #puis fermer la réception + else: + binfile.write(data) + + conn.sendall(b"All done!") #A enlever plus tard? On dit au client qu'on a fini. + + #TODO: traiter le fichier reception.bin pour en faire un HTML, plus possiblement informer Flask? + + print (f"Receipt received", flush=True) From b5ad6c2be528fb915b2b4bb41318bbe5d0fb8d4f Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 12 Sep 2023 17:07:05 -0400 Subject: [PATCH 008/105] Notes et TODO pour la suite. --- miniflask.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/miniflask.py b/miniflask.py index 4d0c6cb..258ad89 100644 --- a/miniflask.py +++ b/miniflask.py @@ -10,7 +10,9 @@ def launchServer(): s.bind((HOST, TCP_PORT)) s.listen() - while True: #Recevoir des connexions, une à la fois, pour l'éternité. + while True: #Recevoir des connexions, une à la fois, pour l'éternité. + # NOTE: on a volontaire pris la version bloquante pour s'assurer que chaque reçu va être sauvegardé puis converti avant d'en accepter un autre. + # TODO: peut-être implémenter le signal BUSY plus tard? print('waiting for connection') conn, addr = s.accept() with conn: @@ -39,8 +41,13 @@ def launchServer(): @app.route("/") def hello_world(): + + #TODO: faire une interface qui présente tous les reçus qu'on a gardé. + return "

Hello, World FLG!

" + + if __name__ == "__main__": #Lancer le service TCP t = threading.Thread(target=launchServer) From 313e2ee8852f823b0330b3efa060b2634198d776 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Thu, 14 Sep 2023 11:37:05 -0400 Subject: [PATCH 009/105] Rename main script and document printer specs --- dockerfile | 2 +- miniflask.py => escpos-netprinter.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) rename miniflask.py => escpos-netprinter.py (71%) diff --git a/dockerfile b/dockerfile index 45dc6ab..6d4c6ba 100644 --- a/dockerfile +++ b/dockerfile @@ -19,5 +19,5 @@ RUN composer install EXPOSE 5000 # Démarrer le serveur Flask avec le script miniflask -CMD [ "python3", "miniflask.py"] +CMD [ "python3", "escpos-netprinter.py"] #ENTRYPOINT [ "/bin/bash" ] \ No newline at end of file diff --git a/miniflask.py b/escpos-netprinter.py similarity index 71% rename from miniflask.py rename to escpos-netprinter.py index 258ad89..0bfb46c 100644 --- a/miniflask.py +++ b/escpos-netprinter.py @@ -11,8 +11,11 @@ def launchServer(): s.listen() while True: #Recevoir des connexions, une à la fois, pour l'éternité. - # NOTE: on a volontaire pris la version bloquante pour s'assurer que chaque reçu va être sauvegardé puis converti avant d'en accepter un autre. - # TODO: peut-être implémenter le signal BUSY plus tard? + """ NOTE: On a volontairement pris la version bloquante pour s'assurer que chaque reçu va être sauvegardé puis converti avant d'en accepter un autre. + NOTE: il est possible que ce soit le comportement attendu soit de n'accepter qu'une connection à la fois. Voir p.6 de la spécification d'un module Ethernet + à l'adresse suivante: https://files.cyberdata.net/assets/010748/ETHERNET_IV_Product_Guide_Rev_D.pdf + TODO: peut-être implémenter certains codes de statut plus tard. Voir l'APG Epson section "Processing the Data Received from the Printer" + """ print('waiting for connection') conn, addr = s.accept() with conn: From 4aeea6f31dc5e44bd5a1f5f2e131a55bfffc5b43 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Thu, 14 Sep 2023 16:30:53 -0400 Subject: [PATCH 010/105] Variables d'environnement et mode debug fonctionnels. --- dockerfile | 16 ++++++++++++---- escpos-netprinter.py | 33 ++++++++++++++++++++++----------- web/receipt_view.py | 2 ++ 3 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 web/receipt_view.py diff --git a/dockerfile b/dockerfile index 6d4c6ba..5f2470f 100644 --- a/dockerfile +++ b/dockerfile @@ -17,7 +17,15 @@ RUN apt-get install -y python3-flask RUN composer install -EXPOSE 5000 -# Démarrer le serveur Flask avec le script miniflask -CMD [ "python3", "escpos-netprinter.py"] -#ENTRYPOINT [ "/bin/bash" ] \ No newline at end of file +#Configurer l'environnement d'exécution +ENV FLASK_APP=escpos-netprinter.py +ENV FLASK_RUN_HOST=0.0.0.0 +ENV FLASK_RUN_PORT=5000 +# To activate the Flask debug mode, set at True (case-sensitive) +ENV FLASK_RUN_DEBUG=false + +EXPOSE 9100 +EXPOSE ${FLASK_RUN_PORT} + +# Démarrer le serveur Flask et le serveur d'impression +CMD python3 ${FLASK_APP} diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 0bfb46c..c3de1f1 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -1,14 +1,14 @@ from flask import Flask +from web import receipt_view +from os import getenv import random, socket, threading -#tcp server -HOST = '' #Empty string accepts all origins -TCP_PORT = 9100 - -def launchServer(): +#Network esc-pos printer server +def launchPrintServer(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind((HOST, TCP_PORT)) - s.listen() + HOST = getenv('FLASK_RUN_HOST', '0.0.0.0') #The print service will respond to the same adresses as Flask + s.bind(('', 9100)) #Always use port 9100 + s.listen(1) #Accept only one connection at a time. while True: #Recevoir des connexions, une à la fois, pour l'éternité. """ NOTE: On a volontairement pris la version bloquante pour s'assurer que chaque reçu va être sauvegardé puis converti avant d'en accepter un autre. @@ -49,13 +49,24 @@ def hello_world(): return "

Hello, World FLG!

" - +@app.route("/recu") +def show_receipt(): + return if __name__ == "__main__": - #Lancer le service TCP - t = threading.Thread(target=launchServer) + #Lancer le service d'impression TCP + t = threading.Thread(target=launchPrintServer) t.daemon = True t.start() + print (f"Print port open", flush=True) #Lancer l'application Flask - app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file + host = getenv('FLASK_RUN_HOST', '0.0.0.0') + port = getenv('FLASK_RUN_PORT', '5000') + debugmode = getenv('FLASK_RUN_DEBUG', "false") + if debugmode == 'True': + startDebug:bool = True + else: + startDebug:bool = False + + app.run(host=host, port=int(port), debug=startDebug, use_reloader=False) \ No newline at end of file diff --git a/web/receipt_view.py b/web/receipt_view.py new file mode 100644 index 0000000..65d04bb --- /dev/null +++ b/web/receipt_view.py @@ -0,0 +1,2 @@ +from flask import Flask + From edb8f07773827fe318a874238fce922ec1b7566c Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 15 Sep 2023 09:03:34 -0400 Subject: [PATCH 011/105] Add printer port config + documenting comments. --- dockerfile | 4 +++- escpos-netprinter.py | 13 ++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dockerfile b/dockerfile index 5f2470f..ef2716f 100644 --- a/dockerfile +++ b/dockerfile @@ -15,16 +15,18 @@ WORKDIR /home/escpos-emu/ RUN apt-get update RUN apt-get install -y python3-flask +#Installation de escpos-tools RUN composer install #Configurer l'environnement d'exécution ENV FLASK_APP=escpos-netprinter.py ENV FLASK_RUN_HOST=0.0.0.0 ENV FLASK_RUN_PORT=5000 +ENV PRINTER_PORT=9100 # To activate the Flask debug mode, set at True (case-sensitive) ENV FLASK_RUN_DEBUG=false -EXPOSE 9100 +EXPOSE ${PRINTER_PORT} EXPOSE ${FLASK_RUN_PORT} # Démarrer le serveur Flask et le serveur d'impression diff --git a/escpos-netprinter.py b/escpos-netprinter.py index c3de1f1..da06952 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -1,13 +1,15 @@ from flask import Flask from web import receipt_view from os import getenv +import subprocess import random, socket, threading #Network esc-pos printer server def launchPrintServer(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: HOST = getenv('FLASK_RUN_HOST', '0.0.0.0') #The print service will respond to the same adresses as Flask - s.bind(('', 9100)) #Always use port 9100 + PORT = getenv('PRINTER_PORT', '9100') #A printer should always listen to port 9100, but the Epson printers can be configured so also will we. + s.bind((HOST, int(PORT))) s.listen(1) #Accept only one connection at a time. while True: #Recevoir des connexions, une à la fois, pour l'éternité. @@ -19,21 +21,22 @@ def launchPrintServer(): print('waiting for connection') conn, addr = s.accept() with conn: - print (f"Adress connected: {addr}") + print (f"Adress connected: {addr}", flush=True) binfile = open("reception.bin", "wb") while True: data = conn.recv(1024) # receive 1024 bytes (or less) - if not data: #Quand tout a été reçu + if not data: #Quand on a reçu le signal de fin de transmission binfile.close() #Écrire le fichier et le fermer break #puis fermer la réception else: + #Écrire les données reçues dans le fichier. binfile.write(data) conn.sendall(b"All done!") #A enlever plus tard? On dit au client qu'on a fini. #TODO: traiter le fichier reception.bin pour en faire un HTML, plus possiblement informer Flask? - + print (f"Receipt received", flush=True) @@ -69,4 +72,4 @@ def show_receipt(): else: startDebug:bool = False - app.run(host=host, port=int(port), debug=startDebug, use_reloader=False) \ No newline at end of file + app.run(host=host, port=int(port), debug=startDebug, use_reloader=False) #On empêche le reloader parce qu'il repart "main" au complet et le service d'imprimante n'est pas conçu pour ça. \ No newline at end of file From c66c6504ad2c5ceb93019b2d7c5b865dfc984d48 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 15 Sep 2023 10:26:02 -0400 Subject: [PATCH 012/105] The minimum viable service: show the latest received receipt. --- escpos-netprinter.py | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/escpos-netprinter.py b/escpos-netprinter.py index da06952..4921499 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -1,10 +1,12 @@ -from flask import Flask +from flask import Flask, send_file from web import receipt_view from os import getenv import subprocess +from subprocess import CompletedProcess +from pathlib import PurePath import random, socket, threading -#Network esc-pos printer server +#Network esc-pos printer service def launchPrintServer(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: HOST = getenv('FLASK_RUN_HOST', '0.0.0.0') #The print service will respond to the same adresses as Flask @@ -14,7 +16,7 @@ def launchPrintServer(): while True: #Recevoir des connexions, une à la fois, pour l'éternité. """ NOTE: On a volontairement pris la version bloquante pour s'assurer que chaque reçu va être sauvegardé puis converti avant d'en accepter un autre. - NOTE: il est possible que ce soit le comportement attendu soit de n'accepter qu'une connection à la fois. Voir p.6 de la spécification d'un module Ethernet + NOTE: il est possible que ce soit le comportement attendu de n'accepter qu'une connection à la fois. Voir p.6 de la spécification d'un module Ethernet à l'adresse suivante: https://files.cyberdata.net/assets/010748/ETHERNET_IV_Product_Guide_Rev_D.pdf TODO: peut-être implémenter certains codes de statut plus tard. Voir l'APG Epson section "Processing the Data Received from the Printer" """ @@ -33,15 +35,32 @@ def launchPrintServer(): #Écrire les données reçues dans le fichier. binfile.write(data) - conn.sendall(b"All done!") #A enlever plus tard? On dit au client qu'on a fini. + conn.sendall(b"Virtual printer: All done!") #A enlever plus tard? On dit au client qu'on a fini. + conn.close() + print ("Data received, client disconnected.", flush=True) #TODO: traiter le fichier reception.bin pour en faire un HTML, plus possiblement informer Flask? + recu:CompletedProcess = subprocess.run(["php", "esc2html.php", "reception.bin"], capture_output=True, text=True ) + if recu.returncode != 0: + print(f"Error while converting receipt: {recu.returncode}") + print("Error output:") + print(recu.stderr, flush=True) - print (f"Receipt received", flush=True) - - - - + else: + #Si la conversion s'est bien passée, on devrait avoir le HTML + + print (f"Receipt received") + #print(recu.stdout, flush=True) + try: + nouveauRecu = open(PurePath('web', 'receipts', 'myreceipt.html'), mode='wt') + #Écrire le reçu dans le fichier. + nouveauRecu.write(recu.stdout) + nouveauRecu.close() + + except OSError as err: + print("File creation error:", err.errno, flush=True) + + app = Flask(__name__) @@ -54,7 +73,7 @@ def hello_world(): @app.route("/recu") def show_receipt(): - return + return send_file(PurePath('web', 'receipts', 'myreceipt.html')) if __name__ == "__main__": #Lancer le service d'impression TCP From 7ac4124506e0afc39abdfc116a8bfd9e4b13d517 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 15 Sep 2023 12:53:44 -0400 Subject: [PATCH 013/105] MVP+: manipuler le HTML --- dockerfile | 3 ++- escpos-netprinter.py | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/dockerfile b/dockerfile index ef2716f..1d46de9 100644 --- a/dockerfile +++ b/dockerfile @@ -13,7 +13,8 @@ WORKDIR /home/escpos-emu/ #Installation de Flask RUN apt-get update -RUN apt-get install -y python3-flask +RUN apt-get install -y python3-flask +RUN apt-get install -y python3-lxml #Installation de escpos-tools RUN composer install diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 4921499..b7d2d76 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -4,9 +4,10 @@ import subprocess from subprocess import CompletedProcess from pathlib import PurePath +from lxml import html, etree import random, socket, threading -#Network esc-pos printer service +#Network ESC/pos printer service def launchPrintServer(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: HOST = getenv('FLASK_RUN_HOST', '0.0.0.0') #The print service will respond to the same adresses as Flask @@ -51,10 +52,20 @@ def launchPrintServer(): print (f"Receipt received") #print(recu.stdout, flush=True) + + recuConvert:etree.ElementTree = html.fromstring(recu.stdout) + + theHead:etree.Element = recuConvert.head + newTitle = etree.Element("title") + newTitle.text = "Ce qui vient d'être reçu" + theHead.append(newTitle) + + #print(etree.tostring(theHead), flush=True) + try: nouveauRecu = open(PurePath('web', 'receipts', 'myreceipt.html'), mode='wt') #Écrire le reçu dans le fichier. - nouveauRecu.write(recu.stdout) + nouveauRecu.write(html.tostring(recuConvert).decode()) nouveauRecu.close() except OSError as err: From 27d61d15b354dd24be7655fc0a9de25c851ae60b Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 15 Sep 2023 15:34:28 -0400 Subject: [PATCH 014/105] Add a full web interface plus cleanup of the docker container. --- .dockerignore | 8 +++++++ dockerfile | 1 + escpos-netprinter.py | 39 +++++++++++++++++++++-------------- templates/accueil.html.j2 | 20 ++++++++++++++++++ templates/receiptList.html.j2 | 20 ++++++++++++++++++ web/receipt_view.py | 2 -- 6 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 templates/accueil.html.j2 create mode 100644 templates/receiptList.html.j2 delete mode 100644 web/receipt_view.py diff --git a/.dockerignore b/.dockerignore index 5bc776e..22c832b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,10 @@ +.dockerignore dockerfile +.git +.git/** +.gitignore +doc doc/** +*.md +.travis.yml +*.bin diff --git a/dockerfile b/dockerfile index 1d46de9..6d8b967 100644 --- a/dockerfile +++ b/dockerfile @@ -18,6 +18,7 @@ RUN apt-get install -y python3-lxml #Installation de escpos-tools RUN composer install +RUN rm composer.json && rm composer.lock #Configurer l'environnement d'exécution ENV FLASK_APP=escpos-netprinter.py diff --git a/escpos-netprinter.py b/escpos-netprinter.py index b7d2d76..d2726f1 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -1,11 +1,12 @@ -from flask import Flask, send_file -from web import receipt_view -from os import getenv +from flask import Flask, send_file, render_template +from os import getenv, listdir +from os.path import splitext import subprocess from subprocess import CompletedProcess from pathlib import PurePath from lxml import html, etree -import random, socket, threading +from datetime import datetime +import socket, threading #Network ESC/pos printer service def launchPrintServer(): @@ -53,17 +54,19 @@ def launchPrintServer(): print (f"Receipt received") #print(recu.stdout, flush=True) + heureRecept = datetime.now() + recuConvert:etree.ElementTree = html.fromstring(recu.stdout) theHead:etree.Element = recuConvert.head newTitle = etree.Element("title") - newTitle.text = "Ce qui vient d'être reçu" + newTitle.text = "Reçu imprimé le {}".format(heureRecept.strftime('%d %b %Y @ %X%Z')) theHead.append(newTitle) #print(etree.tostring(theHead), flush=True) try: - nouveauRecu = open(PurePath('web', 'receipts', 'myreceipt.html'), mode='wt') + nouveauRecu = open(PurePath('web', 'receipts', 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d%X%Z'))), mode='wt') #Écrire le reçu dans le fichier. nouveauRecu.write(html.tostring(recuConvert).decode()) nouveauRecu.close() @@ -76,15 +79,21 @@ def launchPrintServer(): app = Flask(__name__) @app.route("/") -def hello_world(): - - #TODO: faire une interface qui présente tous les reçus qu'on a gardé. - - return "

Hello, World FLG!

" - -@app.route("/recu") -def show_receipt(): - return send_file(PurePath('web', 'receipts', 'myreceipt.html')) +def accueil(): + return render_template('accueil.html.j2', host=getenv('FLASK_RUN_HOST', '0.0.0.0'), + port=getenv('PRINTER_PORT', '9100'), + debug=getenv('FLASK_RUN_DEBUG', "false") ) + +@app.route("/recus") +def list_receipts(): + fichiers = listdir(PurePath('web', 'receipts')) + noms = [ splitext(filename)[0] for filename in fichiers ] + return render_template('receiptList.html.j2', receiptlist=noms) + +@app.route("/recus/") +def show_receipt(filename): + return send_file(PurePath('web', 'receipts', filename)) + if __name__ == "__main__": #Lancer le service d'impression TCP diff --git a/templates/accueil.html.j2 b/templates/accueil.html.j2 new file mode 100644 index 0000000..c2bc906 --- /dev/null +++ b/templates/accueil.html.j2 @@ -0,0 +1,20 @@ + + + + Imprimante virtuelle + + +

État de l'imprimante:

+
    +
  • En ligne
  • +
  • Adresses source acceptées: {{host}}
  • +
  • Port d'impression: {{port}}
  • + {%if debug == 'True'%} +
  • Mode débogage activé
  • + {%endif%} +
+ + Consulter les reçus imprimés + + + \ No newline at end of file diff --git a/templates/receiptList.html.j2 b/templates/receiptList.html.j2 new file mode 100644 index 0000000..45b1bb0 --- /dev/null +++ b/templates/receiptList.html.j2 @@ -0,0 +1,20 @@ + + + + Tous les reçus imprimés + + + + {%if receiptlist|length > 0 %} +

{{receiptlist|length}} recu{%if receiptlist|length > 1%}s{%endif%} disponible{%if receiptlist|length > 1%}s{%endif%}

+
    + {%for receipt in receiptlist%} +
  • {{receipt}}
  • + {%endfor%} +
+ {% else %} +

Aucun reçu trouvé!

+ {% endif %} + {# a comment #} + + \ No newline at end of file diff --git a/web/receipt_view.py b/web/receipt_view.py deleted file mode 100644 index 65d04bb..0000000 --- a/web/receipt_view.py +++ /dev/null @@ -1,2 +0,0 @@ -from flask import Flask - From ffe4517270b727df2532ca3b9ceb0132692ea442 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 15 Sep 2023 15:50:02 -0400 Subject: [PATCH 015/105] Move the escpos-tools licence and readme, relicence the software under GNU AGPLv3 --- LICENCE.md | 661 +++++++++++++++++++++++++++ LICENSE.md => LICENSE-escpostools.md | 2 +- README.md => README-escpostools.md | 0 3 files changed, 662 insertions(+), 1 deletion(-) create mode 100644 LICENCE.md rename LICENSE.md => LICENSE-escpostools.md (91%) rename README.md => README-escpostools.md (100%) diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENCE.md @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/LICENSE.md b/LICENSE-escpostools.md similarity index 91% rename from LICENSE.md rename to LICENSE-escpostools.md index 0ae7e24..0227398 100644 --- a/LICENSE.md +++ b/LICENSE-escpostools.md @@ -1,4 +1,4 @@ -Copyright (c) 2014-17 Michael Billington `< michael.billington@gmail.com >` and +ESCPOS-tools Copyright (c) 2014-17 Michael Billington `< michael.billington@gmail.com >` and others. Permission is hereby granted, free of charge, to any person diff --git a/README.md b/README-escpostools.md similarity index 100% rename from README.md rename to README-escpostools.md From bb60829b719c7de207f09aa0381c6b3d6ea11574 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 15 Sep 2023 16:26:50 -0400 Subject: [PATCH 016/105] Added print port number to startup log. --- escpos-netprinter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/escpos-netprinter.py b/escpos-netprinter.py index d2726f1..f1aa8e6 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -15,6 +15,7 @@ def launchPrintServer(): PORT = getenv('PRINTER_PORT', '9100') #A printer should always listen to port 9100, but the Epson printers can be configured so also will we. s.bind((HOST, int(PORT))) s.listen(1) #Accept only one connection at a time. + print (f"Printer port {PORT} open", flush=True) while True: #Recevoir des connexions, une à la fois, pour l'éternité. """ NOTE: On a volontairement pris la version bloquante pour s'assurer que chaque reçu va être sauvegardé puis converti avant d'en accepter un autre. @@ -100,8 +101,7 @@ def show_receipt(filename): t = threading.Thread(target=launchPrintServer) t.daemon = True t.start() - print (f"Print port open", flush=True) - + #Lancer l'application Flask host = getenv('FLASK_RUN_HOST', '0.0.0.0') port = getenv('FLASK_RUN_PORT', '5000') From e1e6b18d1e40e5367f64e4c9ccfb3f280aac322c Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 15 Sep 2023 16:41:01 -0400 Subject: [PATCH 017/105] Add a dummy file to ensure that the receipts directory is in git. --- .dockerignore | 1 + web/receipts/dummy.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 web/receipts/dummy.txt diff --git a/.dockerignore b/.dockerignore index 22c832b..416ab18 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,3 +8,4 @@ doc/** *.md .travis.yml *.bin +web/receipts/** \ No newline at end of file diff --git a/web/receipts/dummy.txt b/web/receipts/dummy.txt new file mode 100644 index 0000000..f964db5 --- /dev/null +++ b/web/receipts/dummy.txt @@ -0,0 +1 @@ +this file should not be in the container. \ No newline at end of file From df3c773e586cf739fb47bac396754d97ca4ea572 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 15 Sep 2023 16:55:14 -0400 Subject: [PATCH 018/105] New basic README. --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..7aedfb4 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +ESC/POS virtual network printer +---------- + +This is a very simple container-based ESC/POS network printer, that transforms the printed material in HTML pages and makes them avaliable in a web interface. + +## Quick start + +This project requires: +- A Docker installation (kubernetes should work, but is untested.) + +To install from source: + +```bash +git clone https://github.com/gilbertfl/escpos-netprinter.git +cd escpos-netprinter +docker docker build -t escpos-netprinter:beta . +docker run -d -p 5000:5000/tcp -p 9100:9100/tcp escpos-netprinter:beta +``` + +TODO: for some reason, the .dockerignore is not executed on build. + From ccb6fe685ec1493d8f3933cc6beafe8ba1545178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-L=C3=A9onard=20Gilbert?= <83510612+gilbertfl@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:29:24 -0400 Subject: [PATCH 019/105] Update README.md Installation instructions --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7aedfb4..b2df865 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ This project requires: To install from source: ```bash -git clone https://github.com/gilbertfl/escpos-netprinter.git -cd escpos-netprinter -docker docker build -t escpos-netprinter:beta . +wget --show-progress +unzip master.zip +cd escpos-netprinter-master +docker build -t escpos-netprinter:beta . docker run -d -p 5000:5000/tcp -p 9100:9100/tcp escpos-netprinter:beta ``` -TODO: for some reason, the .dockerignore is not executed on build. From 0ee6c4ded2a51abcae128569a2143a77600cf0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-L=C3=A9onard=20Gilbert?= <83510612+gilbertfl@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:31:16 -0400 Subject: [PATCH 020/105] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2df865..ed81779 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This project requires: To install from source: ```bash -wget --show-progress +wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/heads/master.zip unzip master.zip cd escpos-netprinter-master docker build -t escpos-netprinter:beta . From 5006908fc52a821d7ea834fa204b17b8282f2d08 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 18 Sep 2023 13:59:34 -0400 Subject: [PATCH 021/105] Update install and start instructions, plus known issues. --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ed81779..6915b07 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,15 @@ wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs unzip master.zip cd escpos-netprinter-master docker build -t escpos-netprinter:beta . -docker run -d -p 5000:5000/tcp -p 9100:9100/tcp escpos-netprinter:beta ``` +To run the resulting container: +```bash +docker run -d --cpus 1.8 -p 5000:5000/tcp -p 9100:9100/tcp escpos-netprinter:beta +``` +It should now accept prints on the default port(9100), and you can visualize it with the web application at port 5000. I have put a CPU usage limit as a safety, because it completely locked up my two-core PhotonOS host twice. +## Known issues +This is very deep beta software for now, so it has known major defects: +- There seems to be an infinite loop which eats up CPU time somewhere, but it's location is unknown. +- It still uses the Flask development server, so it is unsafe for public networks. \ No newline at end of file From 929e375b4ef22547a770e38ebc1aab47dbef58bc Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 18 Sep 2023 14:09:02 -0400 Subject: [PATCH 022/105] Remove the contributing file. --- CONTRIBUTING.md | 51 ------------------------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index fd9ef3f..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,51 +0,0 @@ -# How to contribute - -This project is open to many different types of contribution. You can help with improving the documentation and examples, sharing your insights on the issue tracker, adding fixes to the code, or providing test cases. - -## Issue tracker - -Open issues of all sorts are tracked on the [issue tracker](https://github.com/receipt-print-hq/escpos-tools/issues). Please check [the docs](https://github.com/receipt-print-hq/escpos-tools/blob/master/README.md) before you post, and practice good [bug tracker etiquette](https://bugzilla.mozilla.org/page.cgi?id=etiquette.html) to keep it running smoothly. - -Issues are [loosely categorised](https://github.com/receipt-print-hq/escpos-tools/labels), and will stay open while there is still something that can be resolved. - -Anybody may add to the discussion on the bug tracker. Just be sure to add new questions as separate issues, and to avoid commenting on closed issues. - -## Submitting changes - -Code changes may be submitted as a "[pull request](https://help.github.com/en/articles/about-pull-requests)" at [receipt-print-hq/escpos-tools](https://github.com/receipt-print-hq/escpos-tools). The description should include some information about how the change improves the library. - -The project is MIT-licensed (see [LICENSE.md](https://github.com/receipt-print-hq/escpos-tools/blob/master/LICENSE.md) for details). You are not required to assign copyright in order to submit changes, but you do need to agree for your code to be distributed under this license in order for it to be accepted. - -### Documentation changes - -The official documentaton is also located in the main repository, under the [doc/](https://github.com/receipt-print-hq/escpos-tools/tree/master/doc) folder. - -You are welcome to post any suggested improvements as pull requests. - -### Release process - -This project is still quite new, and does not have a formalised release process. - -Changes should be submitted via pull request directly to the shared "master" branch. - -## Code style - -This project uses the [PSR-2 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) for all PHP source code. - -## Testing and CI - -The tests are executed on [Travis CI](https://travis-ci.org/receipt-print-hq/escpos-tools) over PHP 5.6 and 7.0. Earlier versions of PHP are not supported. - -For development, you will require the `imagick` and `Xdebug` PHP exensions, the `composer` dependency manager, and acces to a `sass` compiler. - -Fetch a copy of this code and load dependencies with composer: - - git clone https://github.com/receipt-print-hq/escpos-tools - cd escpos-tools/ - composer install - -Code style can be checked via [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer): - - php vendor/bin/phpcs --standard=psr2 src/ -n - -The CI scripts currently just render a few receipts to check for obvious errors. You can find the commands to run locally in `travis.yml`. From 62f8858e517c867f1f918611368ec7e0ce6281e6 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 18 Sep 2023 14:41:23 -0400 Subject: [PATCH 023/105] Add time zone info in file name --- escpos-netprinter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/escpos-netprinter.py b/escpos-netprinter.py index f1aa8e6..2a595f5 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -6,6 +6,7 @@ from pathlib import PurePath from lxml import html, etree from datetime import datetime +from zoneinfo import ZoneInfo import socket, threading #Network ESC/pos printer service @@ -55,7 +56,7 @@ def launchPrintServer(): print (f"Receipt received") #print(recu.stdout, flush=True) - heureRecept = datetime.now() + heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) recuConvert:etree.ElementTree = html.fromstring(recu.stdout) From 89d740b43d77dcb50b7fe09daf4a4a1391ac6b5d Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 18 Sep 2023 15:54:10 -0400 Subject: [PATCH 024/105] Add printer IP to printer info page. --- templates/accueil.html.j2 | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/accueil.html.j2 b/templates/accueil.html.j2 index c2bc906..ce46695 100644 --- a/templates/accueil.html.j2 +++ b/templates/accueil.html.j2 @@ -8,6 +8,7 @@
  • En ligne
  • Adresses source acceptées: {{host}}
  • +
  • Adresse de cette imprimante: {{request.environ['SERVER_ADDR']}}
  • Port d'impression: {{port}}
  • {%if debug == 'True'%}
  • Mode débogage activé
  • From e256608fb4153e3ba84c09ef36934a6feabdff74 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 19 Sep 2023 15:34:14 -0400 Subject: [PATCH 025/105] Completed conversion to SocketServer instead of raw socket. Adds niceties like timeouts. --- escpos-netprinter.py | 189 +++++++++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 79 deletions(-) diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 2a595f5..314d54b 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -8,74 +8,91 @@ from datetime import datetime from zoneinfo import ZoneInfo import socket, threading +import socketserver -#Network ESC/pos printer service -def launchPrintServer(): - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - HOST = getenv('FLASK_RUN_HOST', '0.0.0.0') #The print service will respond to the same adresses as Flask - PORT = getenv('PRINTER_PORT', '9100') #A printer should always listen to port 9100, but the Epson printers can be configured so also will we. - s.bind((HOST, int(PORT))) - s.listen(1) #Accept only one connection at a time. - print (f"Printer port {PORT} open", flush=True) - - while True: #Recevoir des connexions, une à la fois, pour l'éternité. - """ NOTE: On a volontairement pris la version bloquante pour s'assurer que chaque reçu va être sauvegardé puis converti avant d'en accepter un autre. - NOTE: il est possible que ce soit le comportement attendu de n'accepter qu'une connection à la fois. Voir p.6 de la spécification d'un module Ethernet - à l'adresse suivante: https://files.cyberdata.net/assets/010748/ETHERNET_IV_Product_Guide_Rev_D.pdf - TODO: peut-être implémenter certains codes de statut plus tard. Voir l'APG Epson section "Processing the Data Received from the Printer" - """ - print('waiting for connection') - conn, addr = s.accept() - with conn: - print (f"Adress connected: {addr}", flush=True) - binfile = open("reception.bin", "wb") - - while True: - data = conn.recv(1024) # receive 1024 bytes (or less) - if not data: #Quand on a reçu le signal de fin de transmission - binfile.close() #Écrire le fichier et le fermer - break #puis fermer la réception - else: - #Écrire les données reçues dans le fichier. - binfile.write(data) - - conn.sendall(b"Virtual printer: All done!") #A enlever plus tard? On dit au client qu'on a fini. - conn.close() - print ("Data received, client disconnected.", flush=True) - - #TODO: traiter le fichier reception.bin pour en faire un HTML, plus possiblement informer Flask? - recu:CompletedProcess = subprocess.run(["php", "esc2html.php", "reception.bin"], capture_output=True, text=True ) - if recu.returncode != 0: - print(f"Error while converting receipt: {recu.returncode}") - print("Error output:") - print(recu.stderr, flush=True) - - else: - #Si la conversion s'est bien passée, on devrait avoir le HTML - - print (f"Receipt received") - #print(recu.stdout, flush=True) - - heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) - - recuConvert:etree.ElementTree = html.fromstring(recu.stdout) - - theHead:etree.Element = recuConvert.head - newTitle = etree.Element("title") - newTitle.text = "Reçu imprimé le {}".format(heureRecept.strftime('%d %b %Y @ %X%Z')) - theHead.append(newTitle) - - #print(etree.tostring(theHead), flush=True) - - try: - nouveauRecu = open(PurePath('web', 'receipts', 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d%X%Z'))), mode='wt') - #Écrire le reçu dans le fichier. - nouveauRecu.write(html.tostring(recuConvert).decode()) - nouveauRecu.close() - - except OSError as err: - print("File creation error:", err.errno, flush=True) - +#Network ESC/pos printer server +class ESCPOSServer(socketserver.TCPServer): + + def handle_timeout(self) -> None: + print ('Print service timeout!', flush=True) + return super().handle_timeout() + + + +#Network ESC/pos printer request handling +class ESCPOSHandler(socketserver.StreamRequestHandler): + + """ + TODO: peut-être implémenter certains codes de statut plus tard. Voir l'APG Epson section "Processing the Data Received from the Printer" + """ + timeout = 30 #On abandonne une réception après 30 secondes. + + # Receive the print data and dump it in a file. + def handle(self): + print (f"Adress connected: {self.client_address}", flush=True) + binfile = open("reception.bin", "wb") + + #Lire tout jusqu'à ce qu'on ait EOF + try: + indata:bytes = self.rfile.read() + + print(f"{len(indata)} bytes received.", flush=True) + #Écrire les données reçues dans le fichier. + binfile.write(indata) + + #Quand on a reçu le signal de fin de transmission + binfile.close() #Écrire le fichier et le fermer + + self.wfile.write(b"Virtual printer: All done!") #A enlever plus tard? On dit au client qu'on a fini. + + print ("Data received, ACK sent.", flush=True) + + #traiter le fichier reception.bin pour en faire un HTML + self.print_toHTML("reception.bin") + + except TimeoutError: + print("Timeout while reading") + if len(indata) > 0: + print(f"{len(indata)} bytes received.") + print(indata, flush=True) + else: + print("Nothing received!") + + #Convertir l'impression recue en HTML et la rendre disponible à Flask + def print_toHTML(self, binfilename:str): + + print("Impression de ", binfilename) + recu:CompletedProcess = subprocess.run(["php", "esc2html.php", binfilename], capture_output=True, text=True ) + if recu.returncode != 0: + print(f"Error while converting receipt: {recu.returncode}") + print("Error output:") + print(recu.stderr, flush=True) + + else: + #Si la conversion s'est bien passée, on devrait avoir le HTML + print (f"Receipt decoded", flush=True) + #print(recu.stdout, flush=True) + + heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) + + recuConvert:etree.ElementTree = html.fromstring(recu.stdout) + + theHead:etree.Element = recuConvert.head + newTitle = etree.Element("title") + newTitle.text = "Reçu imprimé le {}".format(heureRecept.strftime('%d %b %Y @ %X%Z')) + theHead.append(newTitle) + + #print(etree.tostring(theHead), flush=True) + + try: + nouveauRecu = open(PurePath('web', 'receipts', 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d%X%Z'))), mode='wt') + #Écrire le reçu dans le fichier. + nouveauRecu.write(html.tostring(recuConvert).decode()) + nouveauRecu.close() + + except OSError as err: + print("File creation error:", err.errno, flush=True) + app = Flask(__name__) @@ -97,19 +114,33 @@ def show_receipt(filename): return send_file(PurePath('web', 'receipts', filename)) +def launchPrintServer(printServ:ESCPOSServer): + #Recevoir des connexions, une à la fois, pour l'éternité. + """ NOTE: On a volontairement pris la version bloquante pour s'assurer que chaque reçu va être sauvegardé puis converti avant d'en accepter un autre. + NOTE: il est possible que ce soit le comportement attendu de n'accepter qu'une connection à la fois. Voir p.6 de la spécification d'un module Ethernet + à l'adresse suivante: https://files.cyberdata.net/assets/010748/ETHERNET_IV_Product_Guide_Rev_D.pdf """ + print (f"Printer port open", flush=True) + printServ.serve_forever() + + if __name__ == "__main__": - #Lancer le service d'impression TCP - t = threading.Thread(target=launchPrintServer) - t.daemon = True - t.start() - - #Lancer l'application Flask - host = getenv('FLASK_RUN_HOST', '0.0.0.0') + + #Obtenir les variables d'environnement + host = getenv('FLASK_RUN_HOST', '0.0.0.0') #By default, listen to all source adresses port = getenv('FLASK_RUN_PORT', '5000') debugmode = getenv('FLASK_RUN_DEBUG', "false") - if debugmode == 'True': - startDebug:bool = True - else: - startDebug:bool = False + printPort = getenv('PRINTER_PORT', '9100') + + #Lancer le service d'impression TCP + with ESCPOSServer((host, int(printPort)), ESCPOSHandler) as printServer: + t = threading.Thread(target=launchPrintServer, args=[printServer]) + t.daemon = True + t.start() + + #Lancer l'application Flask + if debugmode == 'True': + startDebug:bool = True + else: + startDebug:bool = False - app.run(host=host, port=int(port), debug=startDebug, use_reloader=False) #On empêche le reloader parce qu'il repart "main" au complet et le service d'imprimante n'est pas conçu pour ça. \ No newline at end of file + app.run(host=host, port=int(port), debug=startDebug, use_reloader=False) #On empêche le reloader parce qu'il repart "main" au complet et le service d'imprimante n'est pas conçu pour ça. \ No newline at end of file From fe942a68647698bd9764df47bc761a9b71d0a186 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 19 Sep 2023 16:04:17 -0400 Subject: [PATCH 026/105] Change install and run commands --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6915b07..29df312 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This project requires: To install from source: ```bash -wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/heads/master.zip +wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/heads/moveToSocketServer.zip unzip master.zip cd escpos-netprinter-master docker build -t escpos-netprinter:beta . @@ -19,7 +19,7 @@ docker build -t escpos-netprinter:beta . To run the resulting container: ```bash -docker run -d --cpus 1.8 -p 5000:5000/tcp -p 9100:9100/tcp escpos-netprinter:beta +docker run -d --rm --cpus 1.8 -p 5000:5000/tcp -p 9100:9100/tcp escpos-netprinter:beta ``` It should now accept prints on the default port(9100), and you can visualize it with the web application at port 5000. I have put a CPU usage limit as a safety, because it completely locked up my two-core PhotonOS host twice. From 770b55a9c0c53f5c77d576ce670cd954e8dbd9ac Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 19 Sep 2023 16:04:17 -0400 Subject: [PATCH 027/105] Change install and run commands --- README.md | 4 ++-- escpos-netprinter.py | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 6915b07..29df312 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This project requires: To install from source: ```bash -wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/heads/master.zip +wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/heads/moveToSocketServer.zip unzip master.zip cd escpos-netprinter-master docker build -t escpos-netprinter:beta . @@ -19,7 +19,7 @@ docker build -t escpos-netprinter:beta . To run the resulting container: ```bash -docker run -d --cpus 1.8 -p 5000:5000/tcp -p 9100:9100/tcp escpos-netprinter:beta +docker run -d --rm --cpus 1.8 -p 5000:5000/tcp -p 9100:9100/tcp escpos-netprinter:beta ``` It should now accept prints on the default port(9100), and you can visualize it with the web application at port 5000. I have put a CPU usage limit as a safety, because it completely locked up my two-core PhotonOS host twice. diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 314d54b..d0b8ec2 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -33,9 +33,19 @@ def handle(self): binfile = open("reception.bin", "wb") #Lire tout jusqu'à ce qu'on ait EOF + indata:bytes = b'' try: - indata:bytes = self.rfile.read() + indata = self.rfile.read() + except TimeoutError: + print("Timeout while reading") + if len(indata) > 0: + print(f"{len(indata)} bytes received.") + print(indata, flush=True) + else: + print("Nothing received!") + + else: print(f"{len(indata)} bytes received.", flush=True) #Écrire les données reçues dans le fichier. binfile.write(indata) @@ -50,14 +60,6 @@ def handle(self): #traiter le fichier reception.bin pour en faire un HTML self.print_toHTML("reception.bin") - except TimeoutError: - print("Timeout while reading") - if len(indata) > 0: - print(f"{len(indata)} bytes received.") - print(indata, flush=True) - else: - print("Nothing received!") - #Convertir l'impression recue en HTML et la rendre disponible à Flask def print_toHTML(self, binfilename:str): From 33ff7c9205208524a0c877a8ecfa991d183bb6d7 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Thu, 21 Sep 2023 14:04:59 -0400 Subject: [PATCH 028/105] Add known issues with printing to readme. --- README.md | 12 ++++++++++-- escpos-netprinter.py | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 29df312..71b54f6 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ ESC/POS virtual network printer This is a very simple container-based ESC/POS network printer, that transforms the printed material in HTML pages and makes them avaliable in a web interface. +## Limits +This docker image is not to be exposed on a public network (see [known issues](#known-issues)) + +A print cannot last longer than 30 seconds. The timeout could be changed at some point, or made configurable. + ## Quick start This project requires: @@ -25,5 +30,8 @@ It should now accept prints on the default port(9100), and you can visualize it ## Known issues This is very deep beta software for now, so it has known major defects: -- There seems to be an infinite loop which eats up CPU time somewhere, but it's location is unknown. -- It still uses the Flask development server, so it is unsafe for public networks. \ No newline at end of file +- ~~There seems to be an infinite loop which eats up CPU time somewhere, but it's location is unknown.~~ Seems to have been resolved by SocketServer +- It still uses the Flask development server, so it is unsafe for public networks. +- The conversion to HTML does not do QR codes (see [issue #59](https://github.com/receipt-print-hq/escpos-tools/issues/59)) +- This still has not been tested with success with a regular POS program (like the [Epson utilities](https://download.epson-biz.com/modules/pos/)) + diff --git a/escpos-netprinter.py b/escpos-netprinter.py index d0b8ec2..f28387b 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -54,6 +54,7 @@ def handle(self): binfile.close() #Écrire le fichier et le fermer self.wfile.write(b"Virtual printer: All done!") #A enlever plus tard? On dit au client qu'on a fini. + self.wfile.flush() print ("Data received, ACK sent.", flush=True) From 822dfe0bc4b7e84eeb2c9dc005e9ae8e565604c4 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Thu, 21 Sep 2023 14:04:59 -0400 Subject: [PATCH 029/105] Add known issues with printing to readme. --- README.md | 12 ++++++++++-- escpos-netprinter.py | 7 ++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 29df312..71b54f6 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ ESC/POS virtual network printer This is a very simple container-based ESC/POS network printer, that transforms the printed material in HTML pages and makes them avaliable in a web interface. +## Limits +This docker image is not to be exposed on a public network (see [known issues](#known-issues)) + +A print cannot last longer than 30 seconds. The timeout could be changed at some point, or made configurable. + ## Quick start This project requires: @@ -25,5 +30,8 @@ It should now accept prints on the default port(9100), and you can visualize it ## Known issues This is very deep beta software for now, so it has known major defects: -- There seems to be an infinite loop which eats up CPU time somewhere, but it's location is unknown. -- It still uses the Flask development server, so it is unsafe for public networks. \ No newline at end of file +- ~~There seems to be an infinite loop which eats up CPU time somewhere, but it's location is unknown.~~ Seems to have been resolved by SocketServer +- It still uses the Flask development server, so it is unsafe for public networks. +- The conversion to HTML does not do QR codes (see [issue #59](https://github.com/receipt-print-hq/escpos-tools/issues/59)) +- This still has not been tested with success with a regular POS program (like the [Epson utilities](https://download.epson-biz.com/modules/pos/)) + diff --git a/escpos-netprinter.py b/escpos-netprinter.py index d0b8ec2..eff6087 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -29,7 +29,7 @@ class ESCPOSHandler(socketserver.StreamRequestHandler): # Receive the print data and dump it in a file. def handle(self): - print (f"Adress connected: {self.client_address}", flush=True) + print (f"Address connected: {self.client_address}", flush=True) binfile = open("reception.bin", "wb") #Lire tout jusqu'à ce qu'on ait EOF @@ -54,6 +54,7 @@ def handle(self): binfile.close() #Écrire le fichier et le fermer self.wfile.write(b"Virtual printer: All done!") #A enlever plus tard? On dit au client qu'on a fini. + self.wfile.flush() print ("Data received, ACK sent.", flush=True) @@ -87,7 +88,7 @@ def print_toHTML(self, binfilename:str): #print(etree.tostring(theHead), flush=True) try: - nouveauRecu = open(PurePath('web', 'receipts', 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d%X%Z'))), mode='wt') + nouveauRecu = open(PurePath('web', 'receipts', 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d_%X%Z'))), mode='wt') #Écrire le reçu dans le fichier. nouveauRecu.write(html.tostring(recuConvert).decode()) nouveauRecu.close() @@ -128,7 +129,7 @@ def launchPrintServer(printServ:ESCPOSServer): if __name__ == "__main__": #Obtenir les variables d'environnement - host = getenv('FLASK_RUN_HOST', '0.0.0.0') #By default, listen to all source adresses + host = getenv('FLASK_RUN_HOST', '0.0.0.0') #By default, listen to all source addresses port = getenv('FLASK_RUN_PORT', '5000') debugmode = getenv('FLASK_RUN_DEBUG', "false") printPort = getenv('PRINTER_PORT', '9100') From f4747db593c5024b260e67ca18c1814942187a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-L=C3=A9onard=20Gilbert?= <83510612+gilbertfl@users.noreply.github.com> Date: Thu, 21 Sep 2023 16:52:35 -0400 Subject: [PATCH 030/105] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71b54f6..446873d 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This project requires: To install from source: ```bash -wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/heads/moveToSocketServer.zip +wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/heads/master.zip unzip master.zip cd escpos-netprinter-master docker build -t escpos-netprinter:beta . From a2d505a4d30b1d76aa4fd70ba0badf57b0de49ae Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 22 Sep 2023 13:21:46 -0400 Subject: [PATCH 031/105] Timeout test --- escpos-netprinter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/escpos-netprinter.py b/escpos-netprinter.py index eff6087..0c8e243 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -25,7 +25,7 @@ class ESCPOSHandler(socketserver.StreamRequestHandler): """ TODO: peut-être implémenter certains codes de statut plus tard. Voir l'APG Epson section "Processing the Data Received from the Printer" """ - timeout = 30 #On abandonne une réception après 30 secondes. + timeout = 5 #On abandonne une réception après 30 secondes. # Receive the print data and dump it in a file. def handle(self): @@ -39,11 +39,13 @@ def handle(self): except TimeoutError: print("Timeout while reading") + self.connection.close() if len(indata) > 0: print(f"{len(indata)} bytes received.") print(indata, flush=True) else: - print("Nothing received!") + print("Nothing received!", flush=True) + else: print(f"{len(indata)} bytes received.", flush=True) From 9ecae2807f09e3246dbd8f4ec77d6804abc9c499 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 22 Sep 2023 16:36:19 -0400 Subject: [PATCH 032/105] 1.0beta version published --- README.md | 7 +++---- escpos-netprinter.py | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 446873d..343ced3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This is a very simple container-based ESC/POS network printer, that transforms t ## Limits This docker image is not to be exposed on a public network (see [known issues](#known-issues)) -A print cannot last longer than 30 seconds. The timeout could be changed at some point, or made configurable. +A print cannot last longer than 10 seconds. This timeout could be changed at some point, or made configurable. ## Quick start @@ -29,9 +29,8 @@ docker run -d --rm --cpus 1.8 -p 5000:5000/tcp -p 9100:9100/tcp escpos-netprinte It should now accept prints on the default port(9100), and you can visualize it with the web application at port 5000. I have put a CPU usage limit as a safety, because it completely locked up my two-core PhotonOS host twice. ## Known issues -This is very deep beta software for now, so it has known major defects: -- ~~There seems to be an infinite loop which eats up CPU time somewhere, but it's location is unknown.~~ Seems to have been resolved by SocketServer +This is still beta software for now, so it has known major defects: - It still uses the Flask development server, so it is unsafe for public networks. - The conversion to HTML does not do QR codes (see [issue #59](https://github.com/receipt-print-hq/escpos-tools/issues/59)) -- This still has not been tested with success with a regular POS program (like the [Epson utilities](https://download.epson-biz.com/modules/pos/)) +- ~~This still has not been tested with success with a regular POS program (like the [Epson utilities](https://download.epson-biz.com/modules/pos/))~~ It works with simpler drivers, for example for the MUNBYN ITPP047 printers. diff --git a/escpos-netprinter.py b/escpos-netprinter.py index c1ee051..91b1808 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -25,14 +25,14 @@ class ESCPOSHandler(socketserver.StreamRequestHandler): """ TODO: peut-être implémenter certains codes de statut plus tard. Voir l'APG Epson section "Processing the Data Received from the Printer" """ - timeout = 5 #On abandonne une réception après 30 secondes. + timeout = 10 #On abandonne une réception après 10 secondes - un compromis pour assurer que tout passe sans se bourrer de connections zombies. # Receive the print data and dump it in a file. def handle(self): print (f"Address connected: {self.client_address}", flush=True) binfile = open("reception.bin", "wb") - #Lire tout jusqu'à ce qu'on ait EOF + #Read everything until we get EOF indata:bytes = b'' try: indata = self.rfile.read() @@ -55,11 +55,11 @@ def handle(self): #Quand on a reçu le signal de fin de transmission binfile.close() #Écrire le fichier et le fermer - self.wfile.write(b"Virtual printer: All done!") #A enlever plus tard? On dit au client qu'on a fini. + self.wfile.write(b"ESCPOS-netprinter: All done!") #A enlever plus tard? On dit au client qu'on a fini. self.wfile.flush() self.connection.close() - print ("Data received, ACK sent.", flush=True) + print ("Data received, signature sent.", flush=True) #traiter le fichier reception.bin pour en faire un HTML self.print_toHTML("reception.bin") From 1cac3ea24ef3e3d36973c2916f3508e573b63eb5 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 25 Sep 2023 16:39:51 -0400 Subject: [PATCH 033/105] Basic QR code parsing structure --- src/Parser/Command/Code2DDataCmd.php | 58 ++++++++++++++++- src/Parser/Command/QRCodeSubCommand.php | 82 +++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/Parser/Command/QRCodeSubCommand.php diff --git a/src/Parser/Command/Code2DDataCmd.php b/src/Parser/Command/Code2DDataCmd.php index e04566d..6ff1c13 100644 --- a/src/Parser/Command/Code2DDataCmd.php +++ b/src/Parser/Command/Code2DDataCmd.php @@ -3,7 +3,63 @@ use ReceiptPrintHq\EscposTools\Parser\Command\DataCmd; +// This interprets the "GS ( k" commands. +// Official Description: Performs data processing related to 2-dimensional codes +// (PDF417, QR Code, MaxiCode, 2-dimensional GS1 DataBar, Composite Symbology). class Code2DDataCmd extends DataCmd { - + private $pL = null; + private $pH = null; + private $cn = null; + + //Process one command byte. Return true if the byte is interpreted without error + /*This symbol has the following format: GS ( k pL pH cn fn [parameters] + Symbol type is specified by cn + Function code fn specifies the function + pL and pH specify the number of bytes following cn as (pL + pH × 256) + The [parameters] are described in each function. + (ref: Epson ESC/POS Command Reference for TM Printers) + */ + public function addChar($char) + { + //Lets begin by getting the size from the first 4 bytes + if ($this -> pL === null){ + $this -> pL = ord($char); + return true; + } + elseif ($this -> pH === null){ + $this -> pH = ord($char); + //Calculate the length of fn+[parameters] - the spec starts counting AFTER cn + $this -> dataSize = $this -> pL + $this -> pH * 256; + return true; + } + //Now interpret the subcommand + elseif ($this -> cn === null) { + $this -> cn = ord($char); + + //If the command is known, assign subCommand with the interpreter class + if($this->cn == 48){ + //this is a PDF417 code command + } + elseif($this->cn == 49){ + //this is a QR code command + $this->subCommand = new QRcodeSubCommand($this->datasize); + } + elseif(50 <= $this->cn <= 54) { + //this is one of the other valid codes + } + else return false; //This code is not a valid function. Stop all processing. + return true; + } + else { //Process everything after cn + if ($this -> subCommand === null){ + //If subCommand is null, the command is not implemented. Stop all processing. + return false; + } + else { + //Send the fn and parameter data to the subcommand + return $this -> subCommand -> addChar($char); + } + } + } } diff --git a/src/Parser/Command/QRCodeSubCommand.php b/src/Parser/Command/QRCodeSubCommand.php new file mode 100644 index 0000000..337db50 --- /dev/null +++ b/src/Parser/Command/QRCodeSubCommand.php @@ -0,0 +1,82 @@ + None: + + # snip, snip: I removed the qr->image conversion which is not useful + + # Native 2D code printing + cn = b"1" # Code type for QR code + # Select model: 1, 2 or micro. + self._send_2d_code_data( + six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0) + ) + # Set dot size. + self._send_2d_code_data(six.int2byte(67), cn, six.int2byte(size)) + # Set error correction level: L, M, Q, or H + self._send_2d_code_data(six.int2byte(69), cn, six.int2byte(48 + ec)) + # Send content & print + self._send_2d_code_data(six.int2byte(80), cn, content.encode("utf-8"), b"0") + self._send_2d_code_data(six.int2byte(81), cn, b"", b"0") + + def _send_2d_code_data(self, fn, cn, data, m=b"") -> None: + """Calculate and send correct data length for`GS ( k`. + + :param fn: Function to use. + :param cn: Output code type. Affects available data. + :param data: Data to send. + :param m: Modifier/variant for function. Often '0' where used. + """ + if len(m) > 1 or len(cn) != 1 or len(fn) != 1: + raise ValueError("cn and fn must be one byte each.") + header = self._int_low_high(len(data) + len(m) + 2, 2) + self._raw(GS + b"(k" + header + cn + fn + m + data) + + So we need to keep everything organized to build a QR visual. + + After that, by using endroid/qr-code, we shoud be able to make an image out of it: + + $qrCode = QrCode::create('Life is too short to be generating QR codes') + ->setEncoding(new Encoding('UTF-8')) + ->setErrorCorrectionLevel(new ErrorCorrectionLevelLow()) + ->setSize(300) + ->setMargin(10) + ->setRoundBlockSizeMode(new RoundBlockSizeModeMargin()) + ->setForegroundColor(new Color(0, 0, 0)) + ->setBackgroundColor(new Color(255, 255, 255)); + +*/ + +class QRcodeSubCommand extends DataSubCmd +{ + + private $fn = null; + + public function addChar($char) + { + if ($this->fn === null){ + //First extract the QR function + $this -> fn = ord($char); + return true; + } + else{ + //then get [parameters] + return parent::addChar($char); + } + } + +} \ No newline at end of file From e9c69bf6ad3ee706dcb00976a8e3f7cf64f0106e Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 29 Sep 2023 16:57:38 -0400 Subject: [PATCH 034/105] QR command parsing complete, begin implementing the printing. --- src/Parser/Command/Code2DDataCmd.php | 14 +-- src/Parser/Command/QRCodeSubCommand.php | 64 +---------- src/Parser/Context/Code2DStateStorage.php | 131 ++++++++++++++++++++++ 3 files changed, 144 insertions(+), 65 deletions(-) create mode 100644 src/Parser/Context/Code2DStateStorage.php diff --git a/src/Parser/Command/Code2DDataCmd.php b/src/Parser/Command/Code2DDataCmd.php index 6ff1c13..4550eb4 100644 --- a/src/Parser/Command/Code2DDataCmd.php +++ b/src/Parser/Command/Code2DDataCmd.php @@ -8,18 +8,18 @@ // (PDF417, QR Code, MaxiCode, 2-dimensional GS1 DataBar, Composite Symbology). class Code2DDataCmd extends DataCmd { + /*This symbol has the following format: GS ( k pL pH cn fn [parameters] + Symbol type is specified by cn + Function code fn specifies the function + pL and pH specify the number of bytes following cn as (pL + pH × 256) + The [parameters] are described in each function. + (ref: Epson ESC/POS Command Reference for TM Printers) + */ private $pL = null; private $pH = null; private $cn = null; //Process one command byte. Return true if the byte is interpreted without error - /*This symbol has the following format: GS ( k pL pH cn fn [parameters] - Symbol type is specified by cn - Function code fn specifies the function - pL and pH specify the number of bytes following cn as (pL + pH × 256) - The [parameters] are described in each function. - (ref: Epson ESC/POS Command Reference for TM Printers) - */ public function addChar($char) { //Lets begin by getting the size from the first 4 bytes diff --git a/src/Parser/Command/QRCodeSubCommand.php b/src/Parser/Command/QRCodeSubCommand.php index 337db50..1b8683d 100644 --- a/src/Parser/Command/QRCodeSubCommand.php +++ b/src/Parser/Command/QRCodeSubCommand.php @@ -4,68 +4,16 @@ use ReceiptPrintHq\EscposTools\Parser\Command\DataSubCmd; -/* A QR code is sent over a few commands. Here is the code from python-escpos: - - def qr( - self, - content, - ec=QR_ECLEVEL_L, - size=3, - model=QR_MODEL_2, - native=False, - center=False, - impl="bitImageRaster", - ) -> None: - - # snip, snip: I removed the qr->image conversion which is not useful - - # Native 2D code printing - cn = b"1" # Code type for QR code - # Select model: 1, 2 or micro. - self._send_2d_code_data( - six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0) - ) - # Set dot size. - self._send_2d_code_data(six.int2byte(67), cn, six.int2byte(size)) - # Set error correction level: L, M, Q, or H - self._send_2d_code_data(six.int2byte(69), cn, six.int2byte(48 + ec)) - # Send content & print - self._send_2d_code_data(six.int2byte(80), cn, content.encode("utf-8"), b"0") - self._send_2d_code_data(six.int2byte(81), cn, b"", b"0") - - def _send_2d_code_data(self, fn, cn, data, m=b"") -> None: - """Calculate and send correct data length for`GS ( k`. - - :param fn: Function to use. - :param cn: Output code type. Affects available data. - :param data: Data to send. - :param m: Modifier/variant for function. Often '0' where used. - """ - if len(m) > 1 or len(cn) != 1 or len(fn) != 1: - raise ValueError("cn and fn must be one byte each.") - header = self._int_low_high(len(data) + len(m) + 2, 2) - self._raw(GS + b"(k" + header + cn + fn + m + data) - - So we need to keep everything organized to build a QR visual. - - After that, by using endroid/qr-code, we shoud be able to make an image out of it: - - $qrCode = QrCode::create('Life is too short to be generating QR codes') - ->setEncoding(new Encoding('UTF-8')) - ->setErrorCorrectionLevel(new ErrorCorrectionLevelLow()) - ->setSize(300) - ->setMargin(10) - ->setRoundBlockSizeMode(new RoundBlockSizeModeMargin()) - ->setForegroundColor(new Color(0, 0, 0)) - ->setBackgroundColor(new Color(255, 255, 255)); - -*/ - class QRcodeSubCommand extends DataSubCmd { private $fn = null; + public function __construct($dataSize) + { + $this -> dataSize = $dataSize - 1; //$dataSize is the size of [parameters], so we exclude the fn byte + } + public function addChar($char) { if ($this->fn === null){ @@ -74,7 +22,7 @@ public function addChar($char) return true; } else{ - //then get [parameters] + //then send [parameters] into $data return parent::addChar($char); } } diff --git a/src/Parser/Context/Code2DStateStorage.php b/src/Parser/Context/Code2DStateStorage.php new file mode 100644 index 0000000..b83f342 --- /dev/null +++ b/src/Parser/Context/Code2DStateStorage.php @@ -0,0 +1,131 @@ + None: + + # snip, snip: I removed the qr->image conversion which is not useful + + # Native 2D code printing + cn = b"1" # Code type for QR code + # Select model: 1, 2 or micro. + self._send_2d_code_data( + six.int2byte(65), cn, six.int2byte(48 + model) + six.int2byte(0) + ) + # Set dot size. + self._send_2d_code_data(six.int2byte(67), cn, six.int2byte(size)) + # Set error correction level: L, M, Q, or H + self._send_2d_code_data(six.int2byte(69), cn, six.int2byte(48 + ec)) + # Send content & print + self._send_2d_code_data(six.int2byte(80), cn, content.encode("utf-8"), b"0") + self._send_2d_code_data(six.int2byte(81), cn, b"", b"0") + + def _send_2d_code_data(self, fn, cn, data, m=b"") -> None: + """Calculate and send correct data length for`GS ( k`. + + :param fn: Function to use. + :param cn: Output code type. Affects available data. + :param data: Data to send. + :param m: Modifier/variant for function. Often '0' where used. + """ + if len(m) > 1 or len(cn) != 1 or len(fn) != 1: + raise ValueError("cn and fn must be one byte each.") + header = self._int_low_high(len(data) + len(m) + 2, 2) + self._raw(GS + b"(k" + header + cn + fn + m + data) + + So we need to keep everything organized to build a QR visual. + + After that, by using endroid/qr-code, we shoud be able to make an image out of it. + +*/ +class Code2DStateStorage +{ + + public function __construct(private int $qrCodeModel = 50, + private int $qrModuleSize = 0, + private int $qrErrorCorrectionLevel = 48, + private string $symbolStorage = null ){ $this->reset();} + + //To implement the ESC @ reset. + public function reset(){ + $this->$qrCodeModel = 50; //50 is the default. + $this->$qrModuleSize = 0; //TODO: The specs calls for a printer default. Choose a sane one. + $this->qrErrorCorrectionLevel = 48; + $this->symbolStorage = null; + } + + //To implement GS ( k , this sets the QR code model: 49, 50 or 51. + public function setQRModel($model){ + $x = ord($model); + if($x === 49 || $x === 50 || $x === 51){ + $this->qrCodeModel = $x; + } + //TODO: We should probably return an error if another value is sent. + } + + //To implement GS ( k , this sets the modulus size + public function setModuleSize($n){ + $this->qrModuleSize = ord($n); + } + + //To implement GS ( k , this sets the error correction level + public function setErrorCorrectLevel($level){ + $x=ord($level); + if($x === 48 || $x === 49 || $x === 50 || $x === 51){ + $this->qrErrorCorrectionLevel = $x; + } + //TODO: We should probably return an error if another value is sent. + } + + //To implement GS ( k , this stores the QR code data + public function fillSymbolStorage($data){ + //TODO: We should probably do some bounds-checking to prevent overflowing the data size. + $this->symbolStorage = $data; + } + + //To implement GS ( k , Transmit the size information of the symbol data in the symbol storage area + public function printQRCodeStateInfo(){ + //TODO: implement the status info - this is probably unnecessary for the ESCPOS-netprinter project. + //Of special interest here is the "Other information" data which states if printing is possible. + } + + //To implement GS ( k , this outputs the QR code in png format + public function printQRCode(){ + //TODO: implement the printing with endroid/qr-code + /* $qrCode = Builder::create() + ->writer(new PngWriter()) + ->writerOptions([]) + ->data('Custom QR code contents') + ->encoding(new Encoding('UTF-8')) + ->errorCorrectionLevel(new ErrorCorrectionLevelHigh()) + ->size(300) + ->margin(10) + ->roundBlockSizeMode(new RoundBlockSizeModeMargin()) + ->logoPath(__DIR__.'/assets/symfony.png') + ->logoResizeToWidth(50) + ->logoPunchoutBackground(true) + ->labelText('This is the label') + ->labelFont(new NotoSans(20)) + ->labelAlignment(new LabelAlignmentCenter()) + ->validateResult(false) + ->build(); + */ + + } +} \ No newline at end of file From d20daaddfec4ae03707c9c92e990c5b3bda55a51 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 10 Oct 2023 14:10:03 -0400 Subject: [PATCH 035/105] Add qrcode library to dockerfile. --- .gitignore | 1 + dockerfile | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3b4df9e..d6cf701 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ vendor/ # other build files build/* *.phar +.devcontainer/devcontainer.json diff --git a/dockerfile b/dockerfile index 6d8b967..7024874 100644 --- a/dockerfile +++ b/dockerfile @@ -16,7 +16,9 @@ RUN apt-get update RUN apt-get install -y python3-flask RUN apt-get install -y python3-lxml -#Installation de escpos-tools +#Installation de php-qrcode :requiert seulement imagick +RUN composer require chillerlan/php-qrcode:"^4.3.4" --prefer-stable +#Installation de escpos-tools: requiert gd en plus de imagick RUN composer install RUN rm composer.json && rm composer.lock From f3a66db2a0dd9354be70671209f413fc6485618c Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 27 Oct 2023 12:14:11 -0400 Subject: [PATCH 036/105] First implementation of QR code printing with chillerlan/qrcode --- composer.json | 3 +- composer.lock | 181 ++++++++++++++++++++-- dockerfile | 1 - esc2html.php | 42 +++++ src/Parser/Command/Code2DDataCmd.php | 8 +- src/Parser/Command/QRCodeSubCommand.php | 10 +- src/Parser/Context/Code2DStateStorage.php | 162 ++++++++++++++----- 7 files changed, 343 insertions(+), 64 deletions(-) diff --git a/composer.json b/composer.json index ba0eaea..ae91c67 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,8 @@ { "require" : { "squizlabs/php_codesniffer" : "^2.8", - "mike42/escpos-php": "^1.5" + "mike42/escpos-php": "^1.5", + "chillerlan/php-qrcode":"^4.3.4" }, "autoload" : { "psr-4" : { diff --git a/composer.lock b/composer.lock index 068e473..08bc6c7 100644 --- a/composer.lock +++ b/composer.lock @@ -1,24 +1,165 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "hash": "b5630260eb622bbc042c2d420c2abe1e", - "content-hash": "ea62982abf2ff4d1f3c1f9246556fbb2", + "content-hash": "f9aac8b902c9dc2e55fd5510310ca949", "packages": [ + { + "name": "chillerlan/php-qrcode", + "version": "4.3.4", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-qrcode.git", + "reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d", + "reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d", + "shasum": "" + }, + "require": { + "chillerlan/php-settings-container": "^2.1.4", + "ext-mbstring": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phan/phan": "^5.3", + "phpunit/phpunit": "^9.5", + "setasign/fpdf": "^1.8.2" + }, + "suggest": { + "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", + "setasign/fpdf": "Required to use the QR FPDF output." + }, + "type": "library", + "autoload": { + "psr-4": { + "chillerlan\\QRCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kazuhiko Arase", + "homepage": "https://github.com/kazuhikoarase" + }, + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + }, + { + "name": "Contributors", + "homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors" + } + ], + "description": "A QR code generator. PHP 7.4+", + "homepage": "https://github.com/chillerlan/php-qrcode", + "keywords": [ + "phpqrcode", + "qr", + "qr code", + "qrcode", + "qrcode-generator" + ], + "support": { + "issues": "https://github.com/chillerlan/php-qrcode/issues", + "source": "https://github.com/chillerlan/php-qrcode/tree/4.3.4" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "time": "2022-07-25T09:12:45+00:00" + }, + { + "name": "chillerlan/php-settings-container", + "version": "2.1.4", + "source": { + "type": "git", + "url": "https://github.com/chillerlan/php-settings-container.git", + "reference": "1beb7df3c14346d4344b0b2e12f6f9a74feabd4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/1beb7df3c14346d4344b0b2e12f6f9a74feabd4a", + "reference": "1beb7df3c14346d4344b0b2e12f6f9a74feabd4a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phan/phan": "^5.3", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "chillerlan\\Settings\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Smiley", + "email": "smiley@chillerlan.net", + "homepage": "https://github.com/codemasher" + } + ], + "description": "A container class for immutable settings objects. Not a DI container. PHP 7.4+", + "homepage": "https://github.com/chillerlan/php-settings-container", + "keywords": [ + "PHP7", + "Settings", + "configuration", + "container", + "helper" + ], + "support": { + "issues": "https://github.com/chillerlan/php-settings-container/issues", + "source": "https://github.com/chillerlan/php-settings-container" + }, + "funding": [ + { + "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", + "type": "custom" + }, + { + "url": "https://ko-fi.com/codemasher", + "type": "ko_fi" + } + ], + "time": "2022-07-05T22:32:14+00:00" + }, { "name": "mike42/escpos-php", - "version": "v1.5.1", + "version": "v1.6", "source": { "type": "git", "url": "https://github.com/mike42/escpos-php.git", - "reference": "11d8ed8950b775c6f120722de10ee2ef6fb90f18" + "reference": "995d9a74d4fec9e0b99ddaf308bac4fec05efc40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mike42/escpos-php/zipball/11d8ed8950b775c6f120722de10ee2ef6fb90f18", - "reference": "11d8ed8950b775c6f120722de10ee2ef6fb90f18", + "url": "https://api.github.com/repos/mike42/escpos-php/zipball/995d9a74d4fec9e0b99ddaf308bac4fec05efc40", + "reference": "995d9a74d4fec9e0b99ddaf308bac4fec05efc40", "shasum": "" }, "require": { @@ -60,20 +201,24 @@ "print", "receipt" ], - "time": "2017-03-12 12:17:11" + "support": { + "issues": "https://github.com/mike42/escpos-php/issues", + "source": "https://github.com/mike42/escpos-php/tree/master" + }, + "time": "2017-11-05T10:18:27+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "2.8.1", + "version": "2.9.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d" + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d", - "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", "shasum": "" }, "require": { @@ -138,15 +283,21 @@ "phpcs", "standards" ], - "time": "2017-03-01 22:17:45" + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2018-11-07T22:31:41+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": [], - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.6.0" } diff --git a/dockerfile b/dockerfile index 7024874..97f71f9 100644 --- a/dockerfile +++ b/dockerfile @@ -18,7 +18,6 @@ RUN apt-get install -y python3-lxml #Installation de php-qrcode :requiert seulement imagick RUN composer require chillerlan/php-qrcode:"^4.3.4" --prefer-stable -#Installation de escpos-tools: requiert gd en plus de imagick RUN composer install RUN rm composer.json && rm composer.lock diff --git a/esc2html.php b/esc2html.php index 2ad3e52..ef5a9fc 100644 --- a/esc2html.php +++ b/esc2html.php @@ -4,6 +4,7 @@ */ require_once __DIR__ . '/vendor/autoload.php'; +use ReceiptPrintHq\EscposTools\Parser\Context\Code2DStateStorage; use ReceiptPrintHq\EscposTools\Parser\Parser; use ReceiptPrintHq\EscposTools\Parser\Context\InlineFormatting; @@ -27,6 +28,8 @@ $bufferedImg = null; $imgNo = 0; $skipLineBreak = false; +$code2dStorage = new Code2DStateStorage(); + foreach ($commands as $cmd) { if ($cmd -> isAvailableAs('InitializeCmd')) { $formatting = InlineFormatting::getDefault(); @@ -73,6 +76,35 @@ // Should load into print buffer and print next line break, but we print immediately, so need to skip the next line break. $skipLineBreak = true; } + if ($cmd -> isAvailableAs('Code2DDataCmd')){ + $sub = $cmd -> subCommand(); + if($sub->isAvaliableAs('QRcodeSubCommand')){ + switch ($sub->get_fn()) { + case 65: //set model + $code2dStorage->setQRModel($sub->get_data()); + break; + case 67: //set module size + $code2dStorage->setModuleSize($sub->get_data()); + break; + case 69: //select error correction level + $code2dStorage->setErrorCorrectLevel($sub->get_data()); + break; + case 80: //Store QR data + $code2dStorage->fillSymbolStorage($sub->get_data()); + break; + case 81: //Print the QR code + // TODO: what to do if the QR code data has not yet been sent? + $qrcodeURI = $code2dStorage->getQRCodeURI(); + $qrcodeData = $code2dStorage->getQRCodeData(); + + $outp[] = qrCodeAsDataUrl($qrcodeURI, $qrcodeData); + break; + case 82: //Transmit size information of symbol storage data. + # TODO: maybe implement by printing the info? + break; + } + } + } } // Stuff we need in the HTML header @@ -104,6 +136,16 @@ function imgAsDataUrl($bufferedImg) return "\"$imgAlt\""; } +/* Creates the HTML image of a QR code + Args: + bufferedQRImg: the Base64 encoded PNG image of the QR code + qrcodeData: the data encoded in the QR code, to be put in the alt tag as an accessibility measure */ +function qrCodeAsDataUrl($bufferedQRImg, $qrcodeData) +{ + $imgSrc = "data:image/png;base64," . $bufferedQRImg; + return "\"$qrcodeData\""; +} + function wrapInline($tag, $closeTag, $content) { return $tag . $content . $closeTag; diff --git a/src/Parser/Command/Code2DDataCmd.php b/src/Parser/Command/Code2DDataCmd.php index 4550eb4..bb3578c 100644 --- a/src/Parser/Command/Code2DDataCmd.php +++ b/src/Parser/Command/Code2DDataCmd.php @@ -30,11 +30,11 @@ public function addChar($char) elseif ($this -> pH === null){ $this -> pH = ord($char); //Calculate the length of fn+[parameters] - the spec starts counting AFTER cn - $this -> dataSize = $this -> pL + $this -> pH * 256; + $this->dataSize = $this->pL + $this->pH * 256; return true; } //Now interpret the subcommand - elseif ($this -> cn === null) { + elseif ($this->cn === null) { $this -> cn = ord($char); //If the command is known, assign subCommand with the interpreter class @@ -52,13 +52,13 @@ public function addChar($char) return true; } else { //Process everything after cn - if ($this -> subCommand === null){ + if ($this->subCommand === null){ //If subCommand is null, the command is not implemented. Stop all processing. return false; } else { //Send the fn and parameter data to the subcommand - return $this -> subCommand -> addChar($char); + return $this->subCommand->addChar($char); } } } diff --git a/src/Parser/Command/QRCodeSubCommand.php b/src/Parser/Command/QRCodeSubCommand.php index 1b8683d..8312357 100644 --- a/src/Parser/Command/QRCodeSubCommand.php +++ b/src/Parser/Command/QRCodeSubCommand.php @@ -11,7 +11,7 @@ class QRcodeSubCommand extends DataSubCmd public function __construct($dataSize) { - $this -> dataSize = $dataSize - 1; //$dataSize is the size of [parameters], so we exclude the fn byte + $this->dataSize = $dataSize - 1; //$dataSize is the size of [parameters], so we exclude the fn byte } public function addChar($char) @@ -27,4 +27,12 @@ public function addChar($char) } } + public function get_fn(){ + return $this->fn; + } + + public function get_data(){ + return $this->data; + } + } \ No newline at end of file diff --git a/src/Parser/Context/Code2DStateStorage.php b/src/Parser/Context/Code2DStateStorage.php index b83f342..0d485fb 100644 --- a/src/Parser/Context/Code2DStateStorage.php +++ b/src/Parser/Context/Code2DStateStorage.php @@ -1,13 +1,20 @@ reset();} + public function __construct(){ $this->reset();} //To implement the ESC @ reset. public function reset(){ $this->$qrCodeModel = 50; //50 is the default. - $this->$qrModuleSize = 0; //TODO: The specs calls for a printer default. Choose a sane one. - $this->qrErrorCorrectionLevel = 48; + $this->$qrModuleSize = 4; //TODO: The specs call for a printer default. Denso says 4 in their guide: https://www.qrcode.com/en/howto/cell.html + $this->qrErrorCorrectionLevel = EccLevel::L; //48 (low) is the default. $this->symbolStorage = null; } - //To implement GS ( k , this sets the QR code model: 49, 50 or 51. + //To implement GS ( k , this sets the QR code model. + /* 49 Selects model 1 + 50 Selects model 2 + 51 Selects Micro QR Code + This limits the data size.*/ public function setQRModel($model){ $x = ord($model); if($x === 49 || $x === 50 || $x === 51){ @@ -79,24 +91,73 @@ public function setQRModel($model){ //TODO: We should probably return an error if another value is sent. } - //To implement GS ( k , this sets the modulus size + //To implement GS ( k , this sets the module size public function setModuleSize($n){ $this->qrModuleSize = ord($n); } //To implement GS ( k , this sets the error correction level - public function setErrorCorrectLevel($level){ - $x=ord($level); - if($x === 48 || $x === 49 || $x === 50 || $x === 51){ - $this->qrErrorCorrectionLevel = $x; - } - //TODO: We should probably return an error if another value is sent. + /* 48 Selects Error correction level L + 49 Selects Error correction level M + 50 Selects Error correction level Q + 51 Selects Error correction level H */ + public function setErrorCorrectLevel($levelByte){ + $x=ord($levelByte); + // chillerlan/php-qrcode version + switch ($x) { //EccLevel::X where X is: L M Q H + case 48: + $this->qrErrorCorrectionLevel = ECCLevel::L; + break; + case 49: + $this->qrErrorCorrectionLevel = ECCLevel::M; + break; + case 50: + $this->qrErrorCorrectionLevel = ECCLevel::Q; + break; + case 51: + $this->qrErrorCorrectionLevel = ECCLevel::H; + break; + default: + //TODO: We should probably return an error if another value is sent. + break; + } + } //To implement GS ( k , this stores the QR code data + // The acceptable size of this data is limited by the model: https://www.qrcode.com/en/codes/model12.html + // Model 1: 667 alphanumeric chars (V14 at low correction level) + // Model 2: 4,296 alphanumeric chars (V40 at low correction level) + // Micro-QR: 21 alphanumeric chars https://www.qrcode.com/en/codes/microqr.html public function fillSymbolStorage($data){ - //TODO: We should probably do some bounds-checking to prevent overflowing the data size. - $this->symbolStorage = $data; + /*$maxDataBits = 0; + switch ($this->qrCodeModel) { + case 49: + $maxDataBits = $this->qrErrorCorrectionLevel.getMaxBitsForVersion(new Version(14)); + break; + case 50: + $maxDataBits = $this->qrErrorCorrectionLevel.getMaxBitsForVersion(new Version(40)); + break; + case 51: + $maxDataBits = 21*8; //TODO: check the length of a character in the standard. I assume that each char is 1 byte. + break; + } + //TODO: measure the $data size, depending on it's type. + $dataSize = 0; //The data size in bits. + //Hint: + QRDataModeInterface $x = new Byte($data) + //QRCode\Data\Byte.getLengthBits() -->> c'Est une fonction protected!! + + + if ($dataSize > $maxDataBits) { + # TODO: decide if we truncate or reject in case of overflow. + + + } else {*/ + $this->symbolStorage = $data; + //} + + } //To implement GS ( k , Transmit the size information of the symbol data in the symbol storage area @@ -105,27 +166,44 @@ public function printQRCodeStateInfo(){ //Of special interest here is the "Other information" data which states if printing is possible. } - //To implement GS ( k , this outputs the QR code in png format - public function printQRCode(){ - //TODO: implement the printing with endroid/qr-code - /* $qrCode = Builder::create() - ->writer(new PngWriter()) - ->writerOptions([]) - ->data('Custom QR code contents') - ->encoding(new Encoding('UTF-8')) - ->errorCorrectionLevel(new ErrorCorrectionLevelHigh()) - ->size(300) - ->margin(10) - ->roundBlockSizeMode(new RoundBlockSizeModeMargin()) - ->logoPath(__DIR__.'/assets/symfony.png') - ->logoResizeToWidth(50) - ->logoPunchoutBackground(true) - ->labelText('This is the label') - ->labelFont(new NotoSans(20)) - ->labelAlignment(new LabelAlignmentCenter()) - ->validateResult(false) - ->build(); - */ - + //To implement GS ( k , this outputs the QR code in png format as a base64 URI string + public function getQRCodeURI(){ + // TODO: if( $this->symbolStorage) is empty, do nothing?? + // chillerlan/PHP-QRCode version + // this library can only output Model 2 QR codes, but we will abuse it by setting the version + // 1) set the options + $qroptions = new QROptions; + $qroptions->outputType = 'imagick'; + $qroptions->imagickFormat = 'png32'; // e.g. png32, jpeg, webp + $qroptions->quality = 100; + $qroptions->outputBase64 = true; //To make render() output the URI string directly + + //Set ecc level + $qroptions->eccLevel = $this->qrErrorCorrectionLevel ; + + //Set the versions from the expected model + $qroptions->versionMin = 1; + switch ($this->qrCodeModel) { + case 49: + //49 Selects model 1 + $qroptions->versionMax = 14; + break; + case 50: + # 50 Selects model 2*/ + $qroptions->versionMax = 40; + break; + case 51: + # TODO: decide what to do if someone wants a MicroQR code + break; + } + // 2) generate the QR + $qroutput = (new QRCode($qroptions))->render($this->symbolStorage); + + // 3) retourner l'URI contenant l'image PNG du code avec imagick + return $qroutput; } + + public function getQRCodeData(){ + return $this->symbolStorage; + } } \ No newline at end of file From 7d52e164d192b5e9cd3a568edeb946442457f93b Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 31 Oct 2023 09:00:32 -0400 Subject: [PATCH 037/105] Basic config cleanup --- .gitignore | 4 +- composer.json | 2 +- composer.lock | 303 -------------------------------------------------- dockerfile | 4 +- 4 files changed, 6 insertions(+), 307 deletions(-) delete mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index d6cf701..d1b4d59 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,6 @@ vendor/ # other build files build/* *.phar -.devcontainer/devcontainer.json +.devcontainer/** +.vscode/** +composer.lock \ No newline at end of file diff --git a/composer.json b/composer.json index ae91c67..f86821e 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "require" : { "squizlabs/php_codesniffer" : "^2.8", "mike42/escpos-php": "^1.5", - "chillerlan/php-qrcode":"^4.3.4" + "chillerlan/php-qrcode": "5.0-beta" }, "autoload" : { "psr-4" : { diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 08bc6c7..0000000 --- a/composer.lock +++ /dev/null @@ -1,303 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "f9aac8b902c9dc2e55fd5510310ca949", - "packages": [ - { - "name": "chillerlan/php-qrcode", - "version": "4.3.4", - "source": { - "type": "git", - "url": "https://github.com/chillerlan/php-qrcode.git", - "reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d", - "reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d", - "shasum": "" - }, - "require": { - "chillerlan/php-settings-container": "^2.1.4", - "ext-mbstring": "*", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phan/phan": "^5.3", - "phpunit/phpunit": "^9.5", - "setasign/fpdf": "^1.8.2" - }, - "suggest": { - "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", - "setasign/fpdf": "Required to use the QR FPDF output." - }, - "type": "library", - "autoload": { - "psr-4": { - "chillerlan\\QRCode\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kazuhiko Arase", - "homepage": "https://github.com/kazuhikoarase" - }, - { - "name": "Smiley", - "email": "smiley@chillerlan.net", - "homepage": "https://github.com/codemasher" - }, - { - "name": "Contributors", - "homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors" - } - ], - "description": "A QR code generator. PHP 7.4+", - "homepage": "https://github.com/chillerlan/php-qrcode", - "keywords": [ - "phpqrcode", - "qr", - "qr code", - "qrcode", - "qrcode-generator" - ], - "support": { - "issues": "https://github.com/chillerlan/php-qrcode/issues", - "source": "https://github.com/chillerlan/php-qrcode/tree/4.3.4" - }, - "funding": [ - { - "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", - "type": "custom" - }, - { - "url": "https://ko-fi.com/codemasher", - "type": "ko_fi" - } - ], - "time": "2022-07-25T09:12:45+00:00" - }, - { - "name": "chillerlan/php-settings-container", - "version": "2.1.4", - "source": { - "type": "git", - "url": "https://github.com/chillerlan/php-settings-container.git", - "reference": "1beb7df3c14346d4344b0b2e12f6f9a74feabd4a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/1beb7df3c14346d4344b0b2e12f6f9a74feabd4a", - "reference": "1beb7df3c14346d4344b0b2e12f6f9a74feabd4a", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phan/phan": "^5.3", - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "chillerlan\\Settings\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Smiley", - "email": "smiley@chillerlan.net", - "homepage": "https://github.com/codemasher" - } - ], - "description": "A container class for immutable settings objects. Not a DI container. PHP 7.4+", - "homepage": "https://github.com/chillerlan/php-settings-container", - "keywords": [ - "PHP7", - "Settings", - "configuration", - "container", - "helper" - ], - "support": { - "issues": "https://github.com/chillerlan/php-settings-container/issues", - "source": "https://github.com/chillerlan/php-settings-container" - }, - "funding": [ - { - "url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4", - "type": "custom" - }, - { - "url": "https://ko-fi.com/codemasher", - "type": "ko_fi" - } - ], - "time": "2022-07-05T22:32:14+00:00" - }, - { - "name": "mike42/escpos-php", - "version": "v1.6", - "source": { - "type": "git", - "url": "https://github.com/mike42/escpos-php.git", - "reference": "995d9a74d4fec9e0b99ddaf308bac4fec05efc40" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mike42/escpos-php/zipball/995d9a74d4fec9e0b99ddaf308bac4fec05efc40", - "reference": "995d9a74d4fec9e0b99ddaf308bac4fec05efc40", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=5.3.9" - }, - "require-dev": { - "guzzlehttp/guzzle": "~3.0|~4.0|~5.0|~6.0", - "phpunit/phpunit": "4.8.*", - "squizlabs/php_codesniffer": "2.*" - }, - "suggest": { - "ext-gd": "Used for image printing if present.", - "ext-imagick": "Will be used for image printing if present. Required for PDF printing or use of custom fonts.", - "guzzlehttp/guzzle": "Allows the use of the ApiConnector to send print jobs over HTTP." - }, - "type": "library", - "autoload": { - "psr-4": { - "Mike42\\": "src/Mike42" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Billington", - "email": "michael.billington@gmail.com" - } - ], - "description": "PHP receipt printer library for use with ESC/POS-compatible thermal and impact printers", - "homepage": "https://github.com/mike42/escpos-php", - "keywords": [ - "ESC-POS", - "driver", - "escpos", - "print", - "receipt" - ], - "support": { - "issues": "https://github.com/mike42/escpos-php/issues", - "source": "https://github.com/mike42/escpos-php/tree/master" - }, - "time": "2017-11-05T10:18:27+00:00" - }, - { - "name": "squizlabs/php_codesniffer", - "version": "2.9.2", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "2acf168de78487db620ab4bc524135a13cfe6745" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", - "reference": "2acf168de78487db620ab4bc524135a13cfe6745", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "bin": [ - "scripts/phpcs", - "scripts/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "classmap": [ - "CodeSniffer.php", - "CodeSniffer/CLI.php", - "CodeSniffer/Exception.php", - "CodeSniffer/File.php", - "CodeSniffer/Fixer.php", - "CodeSniffer/Report.php", - "CodeSniffer/Reporting.php", - "CodeSniffer/Sniff.php", - "CodeSniffer/Tokens.php", - "CodeSniffer/Reports/", - "CodeSniffer/Tokenizers/", - "CodeSniffer/DocGenerators/", - "CodeSniffer/Standards/AbstractPatternSniff.php", - "CodeSniffer/Standards/AbstractScopeSniff.php", - "CodeSniffer/Standards/AbstractVariableSniff.php", - "CodeSniffer/Standards/IncorrectPatternException.php", - "CodeSniffer/Standards/Generic/Sniffs/", - "CodeSniffer/Standards/MySource/Sniffs/", - "CodeSniffer/Standards/PEAR/Sniffs/", - "CodeSniffer/Standards/PSR1/Sniffs/", - "CodeSniffer/Standards/PSR2/Sniffs/", - "CodeSniffer/Standards/Squiz/Sniffs/", - "CodeSniffer/Standards/Zend/Sniffs/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", - "keywords": [ - "phpcs", - "standards" - ], - "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" - }, - "time": "2018-11-07T22:31:41+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": true, - "prefer-lowest": false, - "platform": [], - "platform-dev": [], - "plugin-api-version": "2.6.0" -} diff --git a/dockerfile b/dockerfile index 97f71f9..f6d0867 100644 --- a/dockerfile +++ b/dockerfile @@ -5,7 +5,7 @@ FROM php:8.1-cli #Voir: https://github.com/mlocati/docker-php-extension-installer ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ RUN chmod +x /usr/local/bin/install-php-extensions -RUN install-php-extensions imagick @composer mbstring +RUN install-php-extensions mbstring @composer imagick #Note: utiliser "." au lieu de * permet de garder la structure et envoyer tous les sous-répertoires ADD . /home/escpos-emu/ @@ -17,7 +17,7 @@ RUN apt-get install -y python3-flask RUN apt-get install -y python3-lxml #Installation de php-qrcode :requiert seulement imagick -RUN composer require chillerlan/php-qrcode:"^4.3.4" --prefer-stable +#RUN composer require chillerlan/php-qrcode:"^4.3.4" --prefer-stable RUN composer install RUN rm composer.json && rm composer.lock From fa9297e70b0dffaf3c32eb6508f35a55162320f6 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 31 Oct 2023 11:41:48 -0400 Subject: [PATCH 038/105] All QR code processing OK. Parsing error remaining: some QR functions are output as TextCommands --- esc2html.php | 41 ++++++++++- src/Parser/Command/Code2DDataCmd.php | 26 ++++--- src/Parser/Command/Code2DSubCommand.php | 17 +++++ src/Parser/Command/DataSubCmd.php | 4 +- src/Parser/Command/QRCodeSubCommand.php | 9 +-- .../Command/UnimplementedCode2DSubCommand.php | 9 +++ src/Parser/Context/Code2DStateStorage.php | 73 ++++++++++--------- 7 files changed, 125 insertions(+), 54 deletions(-) create mode 100644 src/Parser/Command/Code2DSubCommand.php create mode 100644 src/Parser/Command/UnimplementedCode2DSubCommand.php diff --git a/esc2html.php b/esc2html.php index ef5a9fc..1cbd7fc 100644 --- a/esc2html.php +++ b/esc2html.php @@ -8,14 +8,37 @@ use ReceiptPrintHq\EscposTools\Parser\Parser; use ReceiptPrintHq\EscposTools\Parser\Context\InlineFormatting; +$debugMode = false; +$targetFilename = ""; + +error_log("esc2html starting", 0); // Usage -if (!isset($argv[1])) { - print("Usage: " . $argv[0] . " filename\n"); +if ($argc < 2) { + print("Usage: " . $argv[0] . " [--debug] filename \n"."zéro args"); die(); } +else { + if ($argv[1]=='--debug'){ + $debugMode = true; + if (!isset($argv[2])) { + print("Usage: " . $argv[0] . " [--debug] filename ". $argc-1 . " arguments received\n"); + die(); + } + else $targetFilename = $argv[2]; + error_log("Debug mode enabled", 0); + } + else { //First argument is not '--debug' + if(isset($argv[2])) { // But there is at least 2 args + print("Usage: " . $argv[0] . " [--debug] filename \n". $argc-1 . " arguments received\n"); + die(); + } + else $targetFilename = $argv[1]; //The only argument is the filename. + } +} +error_log("Target filename: " . $targetFilename . "", 0); // Load in a file -$fp = fopen($argv[1], 'rb'); +$fp = fopen($targetFilename, 'rb'); $parser = new Parser(); $parser -> addFile($fp); @@ -31,6 +54,8 @@ $code2dStorage = new Code2DStateStorage(); foreach ($commands as $cmd) { + if ($debugMode) error_log("". get_class($cmd) ."", 0); //Output the command class in the debug console + if ($cmd -> isAvailableAs('InitializeCmd')) { $formatting = InlineFormatting::getDefault(); } @@ -40,6 +65,7 @@ if ($cmd -> isAvailableAs('TextContainer')) { // Add text to line // TODO could decode text properly from legacy code page to UTF-8 here. + #if ($debugMode) error_log("Text or unidentified command: '". $cmd->get_data() ."' ", 0); $spanContentText = $cmd -> getText(); $lineHtml .= span($formatting, $spanContentText); } @@ -78,7 +104,13 @@ } if ($cmd -> isAvailableAs('Code2DDataCmd')){ $sub = $cmd -> subCommand(); - if($sub->isAvaliableAs('QRcodeSubCommand')){ + if ($debugMode) { + error_log("Subcommand ". get_class($sub) ."", 0); //Output the subcommand class in the debug console + error_log("Function " . $sub->get_fn() ."",0); + error_log("Data size:". $sub->getDataSize() ."",0); + error_log("Data: '" . $sub->get_data() ."",0); + } + if($sub->isAvailableAs('QRcodeSubCommand')){ switch ($sub->get_fn()) { case 65: //set model $code2dStorage->setQRModel($sub->get_data()); @@ -126,6 +158,7 @@ $body = wrapBlock("", "", $receipt); $html = wrapBlock("", "", array_merge($head, $body), false); echo "\n" . implode("\n", $html) . "\n"; +error_log("asd". $targetFilename . " converted to HTML",0); function imgAsDataUrl($bufferedImg) { diff --git a/src/Parser/Command/Code2DDataCmd.php b/src/Parser/Command/Code2DDataCmd.php index bb3578c..916f060 100644 --- a/src/Parser/Command/Code2DDataCmd.php +++ b/src/Parser/Command/Code2DDataCmd.php @@ -2,6 +2,7 @@ namespace ReceiptPrintHq\EscposTools\Parser\Command; use ReceiptPrintHq\EscposTools\Parser\Command\DataCmd; +use ReceiptPrintHq\EscposTools\Parser\Command\{QRcodeSubCommand, UnimplementedCode2DSubCommand,Code2DSubCommand} ; // This interprets the "GS ( k" commands. // Official Description: Performs data processing related to 2-dimensional codes @@ -18,6 +19,7 @@ class Code2DDataCmd extends DataCmd private $pL = null; private $pH = null; private $cn = null; + private ?Code2DSubCommand $subCommand = null; //Process one command byte. Return true if the byte is interpreted without error public function addChar($char) @@ -40,26 +42,32 @@ public function addChar($char) //If the command is known, assign subCommand with the interpreter class if($this->cn == 48){ //this is a PDF417 code command + $this->subCommand = new UnimplementedCode2DSubCommand($this->dataSize) ; } elseif($this->cn == 49){ //this is a QR code command - $this->subCommand = new QRcodeSubCommand($this->datasize); + $this->subCommand = new QRcodeSubCommand($this->dataSize); } - elseif(50 <= $this->cn <= 54) { + elseif($this->cn >= 50 && $this->cn <= 54) { //this is one of the other valid codes + $this->subCommand = new UnimplementedCode2DSubCommand($this->dataSize) ; + } + else{ + error_log("Invalid QR code subfunction received: " . ord($char) . "",0); + $this->subCommand = new Code2DSubCommand($this->dataSize); //fill sub with a placeholder } - else return false; //This code is not a valid function. Stop all processing. return true; } else { //Process everything after cn - if ($this->subCommand === null){ - //If subCommand is null, the command is not implemented. Stop all processing. - return false; - } - else { //Send the fn and parameter data to the subcommand return $this->subCommand->addChar($char); - } } } + + public function subCommand() + { + // TODO rename and take getSubCommand() name. + return $this -> subCommand; + } + } diff --git a/src/Parser/Command/Code2DSubCommand.php b/src/Parser/Command/Code2DSubCommand.php new file mode 100644 index 0000000..fd69821 --- /dev/null +++ b/src/Parser/Command/Code2DSubCommand.php @@ -0,0 +1,17 @@ +dataSize; + } + + public function get_data(){ + return $this->data; + } + +} \ No newline at end of file diff --git a/src/Parser/Command/DataSubCmd.php b/src/Parser/Command/DataSubCmd.php index 5793cfb..63726cd 100644 --- a/src/Parser/Command/DataSubCmd.php +++ b/src/Parser/Command/DataSubCmd.php @@ -5,8 +5,8 @@ class DataSubCmd extends Command { - private $data = ""; - private $dataSize; + protected string $data = ""; + protected int $dataSize; public function __construct($dataSize) { diff --git a/src/Parser/Command/QRCodeSubCommand.php b/src/Parser/Command/QRCodeSubCommand.php index 8312357..1701644 100644 --- a/src/Parser/Command/QRCodeSubCommand.php +++ b/src/Parser/Command/QRCodeSubCommand.php @@ -1,10 +1,10 @@ fn; } - public function get_data(){ - return $this->data; + public function isAvailableAs($interface){ + return parent::isAvailableAs($interface); } - } \ No newline at end of file diff --git a/src/Parser/Command/UnimplementedCode2DSubCommand.php b/src/Parser/Command/UnimplementedCode2DSubCommand.php new file mode 100644 index 0000000..e7c8bbe --- /dev/null +++ b/src/Parser/Command/UnimplementedCode2DSubCommand.php @@ -0,0 +1,9 @@ +reset();} //To implement the ESC @ reset. public function reset(){ - $this->$qrCodeModel = 50; //50 is the default. - $this->$qrModuleSize = 4; //TODO: The specs call for a printer default. Denso says 4 in their guide: https://www.qrcode.com/en/howto/cell.html + $this->qrCodeModel = 50; //50 is the default. + $this->qrModuleSize = 4; //TODO: The specs call for a printer default. Denso says 4 in their guide: https://www.qrcode.com/en/howto/cell.html $this->qrErrorCorrectionLevel = EccLevel::L; //48 (low) is the default. - $this->symbolStorage = null; + $this->symbolStorage = ''; } //To implement GS ( k , this sets the QR code model. @@ -168,40 +168,45 @@ public function printQRCodeStateInfo(){ //To implement GS ( k , this outputs the QR code in png format as a base64 URI string public function getQRCodeURI(){ - // TODO: if( $this->symbolStorage) is empty, do nothing?? // chillerlan/PHP-QRCode version // this library can only output Model 2 QR codes, but we will abuse it by setting the version - // 1) set the options - $qroptions = new QROptions; - $qroptions->outputType = 'imagick'; - $qroptions->imagickFormat = 'png32'; // e.g. png32, jpeg, webp - $qroptions->quality = 100; - $qroptions->outputBase64 = true; //To make render() output the URI string directly - - //Set ecc level - $qroptions->eccLevel = $this->qrErrorCorrectionLevel ; - - //Set the versions from the expected model - $qroptions->versionMin = 1; - switch ($this->qrCodeModel) { - case 49: - //49 Selects model 1 - $qroptions->versionMax = 14; - break; - case 50: - # 50 Selects model 2*/ - $qroptions->versionMax = 40; - break; - case 51: - # TODO: decide what to do if someone wants a MicroQR code - break; - } - // 2) generate the QR - $qroutput = (new QRCode($qroptions))->render($this->symbolStorage); - // 3) retourner l'URI contenant l'image PNG du code avec imagick - return $qroutput; + if(strlen($this->symbolStorage) > 0){ + // 1) set the options + $qroptions = new QROptions; + $qroptions->outputType = 'imagick'; + $qroptions->imagickFormat = 'png32'; // e.g. png32, jpeg, webp + $qroptions->quality = 100; + $qroptions->outputBase64 = true; //To make render() output the URI string directly + + //Set ecc level + $qroptions->eccLevel = $this->qrErrorCorrectionLevel ; + + //Set the versions from the expected model + $qroptions->versionMin = 1; + switch ($this->qrCodeModel) { + case 49: + //49 Selects model 1 + $qroptions->versionMax = 14; + break; + case 50: + # 50 Selects model 2*/ + $qroptions->versionMax = 40; + break; + case 51: + # TODO: decide what to do if someone wants a MicroQR code + break; + } + // 2) generate the QR + $qroutput = (new QRCode($qroptions))->render($this->symbolStorage); + + // 3) retourner l'URI contenant l'image PNG du code avec imagick + return $qroutput; } + else { + return "Cannot print 2D code: no data stored."; + } + } public function getQRCodeData(){ return $this->symbolStorage; From c550a530058464d1294b16393ce372a481b51fa6 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 31 Oct 2023 13:48:14 -0400 Subject: [PATCH 039/105] Deal with QR print command before data sent. --- NoQR.JPG | Bin 0 -> 16639 bytes esc2html.php | 15 ++++++++++++--- src/Parser/Context/Code2DStateStorage.php | 4 +++- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 NoQR.JPG diff --git a/NoQR.JPG b/NoQR.JPG new file mode 100644 index 0000000000000000000000000000000000000000..cf2b387c3d2f407e124ccde1fa94329a597189bc GIT binary patch literal 16639 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 break; case 81: //Print the QR code - // TODO: what to do if the QR code data has not yet been sent? $qrcodeURI = $code2dStorage->getQRCodeURI(); - $qrcodeData = $code2dStorage->getQRCodeData(); + + if ($qrcodeURI == Code2DStatestorage::NO_DATA_ERROR){ + error_log("QR code print ordered before contents stored.",0); + $imageData = base64_encode(file_get_contents('NoQR.JPG')); + $imgSrc = 'data: '.mime_content_type('NoQR.JPG').';base64,'.$imageData; + $qrcodeData = Code2dStatestorage::NO_DATA_ERROR; + $outp[] = "\"$qrcodeData\""; + } + else { + $qrcodeData = $code2dStorage->getQRCodeData(); + $outp[] = qrCodeAsDataUrl($qrcodeURI, $qrcodeData); + } - $outp[] = qrCodeAsDataUrl($qrcodeURI, $qrcodeData); break; case 82: //Transmit size information of symbol storage data. # TODO: maybe implement by printing the info? diff --git a/src/Parser/Context/Code2DStateStorage.php b/src/Parser/Context/Code2DStateStorage.php index 32bc4fa..ebfcc5e 100644 --- a/src/Parser/Context/Code2DStateStorage.php +++ b/src/Parser/Context/Code2DStateStorage.php @@ -68,6 +68,8 @@ class Code2DStateStorage private int $qrErrorCorrectionLevel = EccLevel::L; private string $symbolStorage = '' ; + const NO_DATA_ERROR = "Cannot print 2D code: no data stored."; + public function __construct(){ $this->reset();} //To implement the ESC @ reset. @@ -204,7 +206,7 @@ public function getQRCodeURI(){ return $qroutput; } else { - return "Cannot print 2D code: no data stored."; + return self::NO_DATA_ERROR; } } From 247ba2c6e647ff35f3e2b3199d5b3294df39933f Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 31 Oct 2023 15:07:56 -0400 Subject: [PATCH 040/105] Almost complete. --- .dockerignore | 3 ++- dockerfile | 5 ++--- esc2html.php | 2 +- receipt-with-qrcode.bin | Bin 0 -> 16516 bytes 4 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 receipt-with-qrcode.bin diff --git a/.dockerignore b/.dockerignore index 416ab18..d4df030 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,5 @@ doc/** *.md .travis.yml *.bin -web/receipts/** \ No newline at end of file +web/receipts/** +composer.lock \ No newline at end of file diff --git a/dockerfile b/dockerfile index f6d0867..c23dee8 100644 --- a/dockerfile +++ b/dockerfile @@ -16,10 +16,9 @@ RUN apt-get update RUN apt-get install -y python3-flask RUN apt-get install -y python3-lxml -#Installation de php-qrcode :requiert seulement imagick -#RUN composer require chillerlan/php-qrcode:"^4.3.4" --prefer-stable +#Installation HTML printer RUN composer install -RUN rm composer.json && rm composer.lock +#RUN rm composer.json && rm composer.lock #Configurer l'environnement d'exécution ENV FLASK_APP=escpos-netprinter.py diff --git a/esc2html.php b/esc2html.php index d484e17..964314f 100644 --- a/esc2html.php +++ b/esc2html.php @@ -167,7 +167,7 @@ $body = wrapBlock("", "", $receipt); $html = wrapBlock("", "", array_merge($head, $body), false); echo "\n" . implode("\n", $html) . "\n"; -error_log("asd". $targetFilename . " converted to HTML",0); +error_log("'". $targetFilename . "' converted to HTML",0); function imgAsDataUrl($bufferedImg) { diff --git a/receipt-with-qrcode.bin b/receipt-with-qrcode.bin new file mode 100644 index 0000000000000000000000000000000000000000..35c255d3484c154dbef21aebaccd0841499af509 GIT binary patch literal 16516 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 NZO-he%`ADD{|HxQ7ES;F literal 0 HcmV?d00001 From c08fcded9de334bd799e5219335ca2aa0f38078c Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 31 Oct 2023 15:07:56 -0400 Subject: [PATCH 041/105] Almost complete. --- .dockerignore | 4 ++-- dockerfile | 5 ++--- esc2html.php | 2 +- receipt-with-qrcode.bin | Bin 0 -> 16516 bytes src/Parser/Command/Code2DDataCmd.php | 4 +++- 5 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 receipt-with-qrcode.bin diff --git a/.dockerignore b/.dockerignore index 416ab18..6b9aff3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,5 +7,5 @@ doc doc/** *.md .travis.yml -*.bin -web/receipts/** \ No newline at end of file +web/receipts/** +composer.lock \ No newline at end of file diff --git a/dockerfile b/dockerfile index f6d0867..c23dee8 100644 --- a/dockerfile +++ b/dockerfile @@ -16,10 +16,9 @@ RUN apt-get update RUN apt-get install -y python3-flask RUN apt-get install -y python3-lxml -#Installation de php-qrcode :requiert seulement imagick -#RUN composer require chillerlan/php-qrcode:"^4.3.4" --prefer-stable +#Installation HTML printer RUN composer install -RUN rm composer.json && rm composer.lock +#RUN rm composer.json && rm composer.lock #Configurer l'environnement d'exécution ENV FLASK_APP=escpos-netprinter.py diff --git a/esc2html.php b/esc2html.php index d484e17..964314f 100644 --- a/esc2html.php +++ b/esc2html.php @@ -167,7 +167,7 @@ $body = wrapBlock("", "", $receipt); $html = wrapBlock("", "", array_merge($head, $body), false); echo "\n" . implode("\n", $html) . "\n"; -error_log("asd". $targetFilename . " converted to HTML",0); +error_log("'". $targetFilename . "' converted to HTML",0); function imgAsDataUrl($bufferedImg) { diff --git a/receipt-with-qrcode.bin b/receipt-with-qrcode.bin new file mode 100644 index 0000000000000000000000000000000000000000..35c255d3484c154dbef21aebaccd0841499af509 GIT binary patch literal 16516 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 NZO-he%`ADD{|HxQ7ES;F literal 0 HcmV?d00001 diff --git a/src/Parser/Command/Code2DDataCmd.php b/src/Parser/Command/Code2DDataCmd.php index 916f060..2321c55 100644 --- a/src/Parser/Command/Code2DDataCmd.php +++ b/src/Parser/Command/Code2DDataCmd.php @@ -2,7 +2,9 @@ namespace ReceiptPrintHq\EscposTools\Parser\Command; use ReceiptPrintHq\EscposTools\Parser\Command\DataCmd; -use ReceiptPrintHq\EscposTools\Parser\Command\{QRcodeSubCommand, UnimplementedCode2DSubCommand,Code2DSubCommand} ; +use ReceiptPrintHq\EscposTools\Parser\Command\QRcodeSubCommand; +use ReceiptPrintHq\EscposTools\Parser\Command\UnimplementedCode2DSubCommand; +use ReceiptPrintHq\EscposTools\Parser\Command\Code2DSubCommand; // This interprets the "GS ( k" commands. // Official Description: Performs data processing related to 2-dimensional codes From d1a6903a45a0f785033c4449fe97b82a0e24da8c Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 31 Oct 2023 17:26:40 -0400 Subject: [PATCH 042/105] QR code render works Cleanup deprecations Still parses QR code data commands as TextCommands --- dockerfile | 6 +++--- esc2html.php | 4 ++++ src/Parser/Command/Code2DDataCmd.php | 3 ++- src/Parser/Command/DataCmd.php | 4 ++-- src/Parser/Command/SelectBitImageModeCmd.php | 2 ++ .../StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php | 3 --- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/dockerfile b/dockerfile index c23dee8..a89c356 100644 --- a/dockerfile +++ b/dockerfile @@ -1,5 +1,5 @@ -#On part de l'image Debian de php -FROM php:8.1-cli +#On part de l'image php-cli "latest" sur Debian +FROM php:cli #On va utiliser l'utilitaire "install-php-extensions" au lieu de PECL car il marche mieux. #Voir: https://github.com/mlocati/docker-php-extension-installer @@ -18,7 +18,7 @@ RUN apt-get install -y python3-lxml #Installation HTML printer RUN composer install -#RUN rm composer.json && rm composer.lock +RUN rm composer.json && rm composer.lock #Configurer l'environnement d'exécution ENV FLASK_APP=escpos-netprinter.py diff --git a/esc2html.php b/esc2html.php index 964314f..ca2a65d 100644 --- a/esc2html.php +++ b/esc2html.php @@ -37,6 +37,10 @@ } error_log("Target filename: " . $targetFilename . "", 0); +if(!$debugMode) { + error_reporting(E_ERROR | E_PARSE); //Deprecation warnings are unwanted except for debugging +} + // Load in a file $fp = fopen($targetFilename, 'rb'); diff --git a/src/Parser/Command/Code2DDataCmd.php b/src/Parser/Command/Code2DDataCmd.php index 2321c55..2982488 100644 --- a/src/Parser/Command/Code2DDataCmd.php +++ b/src/Parser/Command/Code2DDataCmd.php @@ -1,10 +1,10 @@ cn == 49){ //this is a QR code command $this->subCommand = new QRcodeSubCommand($this->dataSize); + //$this->subCommand = new UnimplementedCode2DSubCommand($this->dataSize) ; } elseif($this->cn >= 50 && $this->cn <= 54) { //this is one of the other valid codes diff --git a/src/Parser/Command/DataCmd.php b/src/Parser/Command/DataCmd.php index 5a79621..9941144 100644 --- a/src/Parser/Command/DataCmd.php +++ b/src/Parser/Command/DataCmd.php @@ -9,8 +9,8 @@ abstract class DataCmd extends EscposCommand private $p2 = null; private $arg1 = null; private $arg2 = null; - private $data = null; - private $dataSize = null; + protected $data = null; + protected $dataSize = null; private $subCommand = null; public function addChar($char) diff --git a/src/Parser/Command/SelectBitImageModeCmd.php b/src/Parser/Command/SelectBitImageModeCmd.php index ff01d3d..5865029 100644 --- a/src/Parser/Command/SelectBitImageModeCmd.php +++ b/src/Parser/Command/SelectBitImageModeCmd.php @@ -13,6 +13,8 @@ class SelectBitImageModeCmd extends EscposCommand implements ImageContainer private $p2 = null; + private $height, $width; + private $data = ""; private $dataSize = null; diff --git a/src/Parser/Command/StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php b/src/Parser/Command/StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php index dcf9e47..6c6e378 100644 --- a/src/Parser/Command/StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php +++ b/src/Parser/Command/StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php @@ -18,9 +18,6 @@ class StoreRasterFmtDataToPrintBufferGraphicsSubCmd extends DataSubCmd implement private $y1 = null; private $y2 = null; - private $data = ""; - private $dataSize; - public function __construct($dataSize) { $this -> dataSize = $dataSize - 8; From b81774deaa202c1e2ad63747d2e6baed440cb650 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 13 Nov 2023 11:27:15 -0500 Subject: [PATCH 043/105] Update Known issues --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 343ced3..2cfa494 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,6 @@ It should now accept prints on the default port(9100), and you can visualize it ## Known issues This is still beta software for now, so it has known major defects: - It still uses the Flask development server, so it is unsafe for public networks. -- The conversion to HTML does not do QR codes (see [issue #59](https://github.com/receipt-print-hq/escpos-tools/issues/59)) +- The conversion to HTML does not do QR codes correctly (see the 1.0-beta.2 release notes) - ~~This still has not been tested with success with a regular POS program (like the [Epson utilities](https://download.epson-biz.com/modules/pos/))~~ It works with simpler drivers, for example for the MUNBYN ITPP047 printers. From be6f367e890ae012715804400e8c6523cf40a8c2 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 13 Nov 2023 17:08:53 -0500 Subject: [PATCH 044/105] First steps integrating CUPS for robust Jetdirect and lpd service. --- .dockerignore | 1 + cups/esc2file.sh | 81 +++++++++ dockerfile | 24 ++- start.sh | 3 + test.html | 427 ---------------------------------------------- web/tmp/dummy.txt | 1 + 6 files changed, 109 insertions(+), 428 deletions(-) create mode 100644 cups/esc2file.sh create mode 100644 start.sh delete mode 100644 test.html create mode 100644 web/tmp/dummy.txt diff --git a/.dockerignore b/.dockerignore index 6b9aff3..947159a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,4 +8,5 @@ doc/** *.md .travis.yml web/receipts/** +web/tmp/** composer.lock \ No newline at end of file diff --git a/cups/esc2file.sh b/cups/esc2file.sh new file mode 100644 index 0000000..1fe9774 --- /dev/null +++ b/cups/esc2file.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# +# /usr/lib/cups/backend/esc2file +# +# (c) November 2023 Francois-Leonard Gilbert +# +# Derived from "2file" CUPS backend script from the KDE printing project +# https://community.kde.org/Printing/Developer_Tools +# +# License: AGPL-3.0 +# + +backend=${0} +jobid=${1} +cupsuser=${2} +jobtitle=${3} +jobcopies=${4} +joboptions=${5} +jobfile=${6} + +# the following messages should appear in /var/log/cups/error_log, +# depending on what "LogLevel" setting your cupsd.conf carries: +echo "INFO: backend=${backend}" 1>&2 +echo "INFO: jobid=${jobid}" 1>&2 +echo "INFO: cupsuser=${cupsuser}" 1>&2 +echo "INFO: jobtitle=${jobtitle}" 1>&2 +echo "INFO: jobcopies=${jobcopies}" 1>&2 +echo "INFO: joboptions=${joboptions}" 1>&2 +echo "INFO: jobfile=${jobfile}" 1>&2 +echo "INFO: printtime=${printtime}" 1>&2 + +#echo "EMERG: This is a \"emergency\" level log message" 1>&2 +#echo "ALERT: This is a \"alert\" level log message" 1>&2 +#echo "CRIT: This is a \"critical\" level log message" 1>&2 +#echo "ERROR: This is a \"error\" level log message" 1>&2 +#echo "WARN: This is a \"warn\" level log message" 1>&2 +#echo "NOTICE: This is a \"notice\" level log message" 1>&2 +#echo "INFO: This is a \"info\" level log message" 1>&2 +#echo "INFO: This is a 2nd \"info\" level log message" 1>&2 +#echo "INFO: This is a 3rd \"info\" level log message" 1>&2 +#echo "DEBUG: This is a \"debug\" level log message" 1>&2 + + +# we will read the output filename from the printers $DEVICE_URI environment +# variable that should look like "esc2file:/path/to/a/filename.suffix" + +# Now do the real work: +case ${#} in + 0) + # this case is for "backend discovery mode" + echo "file esc2file \"ESCPOS-netprinter\" \"2file-style backend to print ESC-POS to HTML\"" + exit 0 + ;; + 5) + # backend needs to read from stdin if number of arguments is 5 + # NOTE: the CUPS backend programming directives state that temporary files should be created in the directory specified by the "TMPDIR" environment variable + cat - > ${TMPDIR}receipt.bin + php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${DEVICE_URI#esc2file:} + ;; + 6) + # backend needs to read from file if number of arguments is 6 + php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} + ;; + 1|2|3|4|*) + # these cases are unsupported + echo " " + echo " Usage: esc2file job-id user title copies options [file]" + echo " " + echo " (Install as CUPS backend in /usr/lib/cups/backend/esc2file)" + echo " (Use as 'device URI' like \"esc2file:/path/to/writeable/jobfile.suffix\" for printer installation.)" + exit 0 +esac + +echo 1>&2 + +# we reach this line only if we actually "printed something" +echo "NOTICE: processed Job ${jobid} to file ${DEVICE_URI#esc2file:}" 1>&2 +echo "NOTICE: End of \"${0}\" run...." 1>&2 +echo "NOTICE: ---------------------------------------------------" 1>&2 +echo 1>&2 +exit 0 \ No newline at end of file diff --git a/dockerfile b/dockerfile index a89c356..1167766 100644 --- a/dockerfile +++ b/dockerfile @@ -9,6 +9,7 @@ RUN install-php-extensions mbstring @composer imagick #Note: utiliser "." au lieu de * permet de garder la structure et envoyer tous les sous-répertoires ADD . /home/escpos-emu/ +ADD --chmod=0555 cups/esc2file.sh /usr/lib/cups/backend/esc2file WORKDIR /home/escpos-emu/ #Installation de Flask @@ -16,6 +17,17 @@ RUN apt-get update RUN apt-get install -y python3-flask RUN apt-get install -y python3-lxml +#Installation de CUPS +RUN apt-get install -y cups + +#Configuration de CUPS ATTENTION: on rend CUPS complètement ouvert de cette façon! +RUN /usr/sbin/cupsd \ + && while [ ! -f /var/run/cups/cupsd.pid ]; do sleep 1; done \ + && cupsctl --remote-admin --remote-any --share-printers \ + && kill $(cat /var/run/cups/cupsd.pid) +RUN rm /etc/cups/snmp.conf + + #Installation HTML printer RUN composer install RUN rm composer.json && rm composer.lock @@ -25,11 +37,21 @@ ENV FLASK_APP=escpos-netprinter.py ENV FLASK_RUN_HOST=0.0.0.0 ENV FLASK_RUN_PORT=5000 ENV PRINTER_PORT=9100 + +# "Device URI" for CUPS +ENV DEVICE_URI=esc2file:/home/escpos-emu/web/tmp/receipt.bin +# Temporary directory for CUPS +ENV TMPDIR=/home/escpos-emu/web/tmp/ + # To activate the Flask debug mode, set at True (case-sensitive) ENV FLASK_RUN_DEBUG=false EXPOSE ${PRINTER_PORT} EXPOSE ${FLASK_RUN_PORT} +#Expose the CUPS admin port (temporary?) +EXPOSE 631 # Démarrer le serveur Flask et le serveur d'impression -CMD python3 ${FLASK_APP} +CMD ["/usr/sbin/cupsd", "-f"] +#CMD python3 ${FLASK_APP} +#CMD ["/bin/bash","-c","./start.sh"] diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..f70dfed --- /dev/null +++ b/start.sh @@ -0,0 +1,3 @@ +#!/bin/bash +/usr/sbin/cupsd +python3 $FLASK_APP diff --git a/test.html b/test.html deleted file mode 100644 index 886143f..0000000 --- a/test.html +++ /dev/null @@ -1,427 +0,0 @@ - - - - - - - -
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    Image 360x24
    -
    L'assiette fiscale
    -
    2020 rue du Finfin
    -
    Québec, G1G 1G1
    -
     
    -
    27 Oct 2023 @ 15:35:41EDT
    - Cannot print 2D code: no data stored. -
    - - diff --git a/web/tmp/dummy.txt b/web/tmp/dummy.txt new file mode 100644 index 0000000..f964db5 --- /dev/null +++ b/web/tmp/dummy.txt @@ -0,0 +1 @@ +this file should not be in the container. \ No newline at end of file From 54e42fe1ac4dc84b9fcb66f2cbef95e7d53b9937 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 17 Nov 2023 15:41:46 -0500 Subject: [PATCH 045/105] CUPS integration continues --- cups/cups-files.conf | 88 +++++++++++++++++++++++++++ cups/cupsd.conf | 138 +++++++++++++++++++++++++++++++++++++++++++ cups/esc2file.sh | 18 +++++- cups/test_setup.sh | 20 +++++++ dockerfile | 27 ++++++--- esc2html.php | 11 +++- 6 files changed, 290 insertions(+), 12 deletions(-) create mode 100644 cups/cups-files.conf create mode 100644 cups/cupsd.conf create mode 100644 cups/test_setup.sh diff --git a/cups/cups-files.conf b/cups/cups-files.conf new file mode 100644 index 0000000..8d37718 --- /dev/null +++ b/cups/cups-files.conf @@ -0,0 +1,88 @@ +# +# File/directory/user/group configuration file for the CUPS scheduler. +# See "man cups-files.conf" for a complete description of this file. +# + +# List of events that are considered fatal errors for the scheduler... +#FatalErrors config + +# Do we call fsync() after writing configuration or status files? +#SyncOnClose Yes + +# Default user and group for filters/backends/helper programs; this cannot be +# any user or group that resolves to ID 0 for security reasons... +User lp +Group lp + +# Administrator user group, used to match @SYSTEM in cupsd.conf policy rules... +# This cannot contain the Group value for security reasons... +SystemGroup root lpadmin cups-admins + + +# User that is substituted for unauthenticated (remote) root accesses... +#RemoteRoot remroot + +# Do we allow file: device URIs other than to /dev/null? +#FileDevice No + +# Permissions for configuration and log files... +#ConfigFilePerm 0640 +#LogFilePerm 00640 + +# Specifies the group name or ID that will be used for log files. +# The default group in Debian is "adm". +LogFileGroup adm + +# Location of the file logging all access to the scheduler; may be the name +# "syslog". If not an absolute path, the value of ServerRoot is used as the +# root directory. Also see the "AccessLogLevel" directive in cupsd.conf. +AccessLog /var/log/cups/access_log + +# Location of cache files used by the scheduler... +#CacheDir /var/cache/cups + +# Location of data files used by the scheduler... +#DataDir /usr/share/cups + +# Location of the static web content served by the scheduler... +#DocumentRoot /usr/share/cups/doc-root + +# Location of the file logging all messages produced by the scheduler and any +# helper programs; may be the name "syslog". If not an absolute path, the value +# of ServerRoot is used as the root directory. Also see the "LogLevel" +# directive in cupsd.conf. +ErrorLog /var/log/cups/error_log + +# Location of the file logging all pages printed by the scheduler and any +# helper programs; may be the name "syslog". If not an absolute path, the value +# of ServerRoot is used as the root directory. Also see the "PageLogFormat" +# directive in cupsd.conf. +PageLog /var/log/cups/page_log + +# Location of the file listing all of the local printers... +#Printcap /run/cups/printcap + +# Format of the Printcap file... +#PrintcapFormat bsd +#PrintcapFormat plist +#PrintcapFormat solaris + +# Location of all spool files... +#RequestRoot /var/spool/cups + +# Location of helper programs... +#ServerBin /usr/lib/cups + +# SSL/TLS keychain for the scheduler... +#ServerKeychain ssl + +# Location of other configuration files... +#ServerRoot /etc/cups + +# Location of scheduler state files... +#StateDir /run/cups + +# Location of scheduler/helper temporary files. This directory is emptied on +# scheduler startup and cannot be one of the standard (public) temporary +# directory locations for security reasons... +TempDir /var/spool/cups/tmp diff --git a/cups/cupsd.conf b/cups/cupsd.conf new file mode 100644 index 0000000..fe52e12 --- /dev/null +++ b/cups/cupsd.conf @@ -0,0 +1,138 @@ +LogLevel INFO +PageLogFormat +MaxLogSize 0 +ErrorPolicy abort-job #Retrying won't make this right +# Allow remote access +Port 631 +Listen /run/cups/cups.sock +# Share local printers on the local network. +Browsing On +BrowseLocalProtocols dnssd +DefaultAuthType Basic +WebInterface Yes +IdleExitTimeout 60 + + # Allow shared printing and remote administration... + Order allow,deny + Allow all + + + # Allow remote administration... + Order allow,deny + Allow all + + + AuthType Default + Require user @SYSTEM + # Allow remote access to the configuration files... + Order allow,deny + Allow all + + + AuthType Default + Require user @SYSTEM + # Allow remote access to the log files... + Order allow,deny + Allow all + + + JobPrivateAccess default + JobPrivateValues default + SubscriptionPrivateAccess default + SubscriptionPrivateValues default + + Order deny,allow + + + Require user @OWNER @SYSTEM + Order deny,allow + + + AuthType Default + Require user @OWNER @SYSTEM + Order deny,allow + + + AuthType Default + Require user @SYSTEM + Order deny,allow + + + AuthType Default + Require user @SYSTEM + Order deny,allow + + + Require user @OWNER @SYSTEM + Order deny,allow + + + Order deny,allow + + + + JobPrivateAccess default + JobPrivateValues default + SubscriptionPrivateAccess default + SubscriptionPrivateValues default + + AuthType Default + Order deny,allow + + + AuthType Default + Require user @OWNER @SYSTEM + Order deny,allow + + + AuthType Default + Require user @SYSTEM + Order deny,allow + + + AuthType Default + Require user @SYSTEM + Order deny,allow + + + AuthType Default + Require user @OWNER @SYSTEM + Order deny,allow + + + Order deny,allow + + + + JobPrivateAccess default + JobPrivateValues default + SubscriptionPrivateAccess default + SubscriptionPrivateValues default + + AuthType Negotiate + Order deny,allow + + + AuthType Negotiate + Require user @OWNER @SYSTEM + Order deny,allow + + + AuthType Default + Require user @SYSTEM + Order deny,allow + + + AuthType Default + Require user @SYSTEM + Order deny,allow + + + AuthType Negotiate + Require user @OWNER @SYSTEM + Order deny,allow + + + Order deny,allow + + diff --git a/cups/esc2file.sh b/cups/esc2file.sh index 1fe9774..a5b86f3 100644 --- a/cups/esc2file.sh +++ b/cups/esc2file.sh @@ -54,12 +54,26 @@ case ${#} in 5) # backend needs to read from stdin if number of arguments is 5 # NOTE: the CUPS backend programming directives state that temporary files should be created in the directory specified by the "TMPDIR" environment variable + echo "DEBUG: Printing from stdin" 1>&2 cat - > ${TMPDIR}receipt.bin - php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${DEVICE_URI#esc2file:} + if [ "$?" -ne "0" ]; + then + echo "CRIT: Cannot write to ${TMPDIR}receipt.bin" 1>&2 + else + #php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${DEVICE_URI#esc2file:} + php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${TMPDIR}/test.html + if [ "$?" != "0" ]; then + echo "ERROR: Error while printing ${TMPDIR}receipt.bin" 1>&2 + fi + fi ;; 6) # backend needs to read from file if number of arguments is 6 - php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} + #php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} + php /home/escpos-emu/esc2html.php ${6} 1>/home/escpos-emu/web/receipts/test.html + if [ "$?" != "0" ]; then + echo "ERROR: Error $? while printing ${6} to ${DEVICE_URI#esc2file:}" 1>&2 + fi ;; 1|2|3|4|*) # these cases are unsupported diff --git a/cups/test_setup.sh b/cups/test_setup.sh new file mode 100644 index 0000000..44c8df1 --- /dev/null +++ b/cups/test_setup.sh @@ -0,0 +1,20 @@ +#!/bin/bash +shopt -s extglob +shopt -s nullglob #Quand un patron ne retourne rien, ignorer la commande. Pour continuer avec le patron lui-même, mettre "shopt -u nullglob" + +cupsd +lpadmin -p test -v $DEVICE_URI -E +echo "Allo!"|lp -d test + +x=0; +for FILE in /home/escpos-emu/web/receipts/* ; do + echo "$FILE" + ((x=x+1)) +done + +if [ "$x" = "0" ]; then + echo "ERREUR: Aucun fichier produit par l'impression" + exit 1; +fi + +#A partir d'ici, retirer l'imprimante-test ? \ No newline at end of file diff --git a/dockerfile b/dockerfile index 1167766..7c2ac1b 100644 --- a/dockerfile +++ b/dockerfile @@ -9,6 +9,7 @@ RUN install-php-extensions mbstring @composer imagick #Note: utiliser "." au lieu de * permet de garder la structure et envoyer tous les sous-répertoires ADD . /home/escpos-emu/ +RUN rm -rf web ADD --chmod=0555 cups/esc2file.sh /usr/lib/cups/backend/esc2file WORKDIR /home/escpos-emu/ @@ -19,6 +20,20 @@ RUN apt-get install -y python3-lxml #Installation de CUPS RUN apt-get install -y cups +ADD cups/cups-files.conf /etc/cups/cups-files.conf +# On passe en loglevel debug2! +ADD cups/cupsd.conf /etc/cups/cupsd.conf +RUN groupadd cups-admins +RUN useradd -d /home/escpos-emu -g cups-admins -s /sbin/nologin cupsadmin +RUN echo "cupsadmin:123456" | chpasswd +COPY --chown=lp:lp --chmod=666 web/. /home/escpos-emu/web + +# "Device URI" for CUPS +ENV DEVICE_URI=esc2file:/home/escpos-emu/web/receipts/test.html +# Temporary directory for CUPS +#ENV TMPDIR=/home/escpos-emu/web/tmp +# Give CUPS access to directories + #Configuration de CUPS ATTENTION: on rend CUPS complètement ouvert de cette façon! RUN /usr/sbin/cupsd \ @@ -26,7 +41,7 @@ RUN /usr/sbin/cupsd \ && cupsctl --remote-admin --remote-any --share-printers \ && kill $(cat /var/run/cups/cupsd.pid) RUN rm /etc/cups/snmp.conf - +#RUN rm /home/escpos-emu/cups/cups-files.conf #Installation HTML printer RUN composer install @@ -35,19 +50,17 @@ RUN rm composer.json && rm composer.lock #Configurer l'environnement d'exécution ENV FLASK_APP=escpos-netprinter.py ENV FLASK_RUN_HOST=0.0.0.0 -ENV FLASK_RUN_PORT=5000 +ENV FLASK_RUN_PORT=80 +#This port is for the Jetdirect protocol. ENV PRINTER_PORT=9100 -# "Device URI" for CUPS -ENV DEVICE_URI=esc2file:/home/escpos-emu/web/tmp/receipt.bin -# Temporary directory for CUPS -ENV TMPDIR=/home/escpos-emu/web/tmp/ - # To activate the Flask debug mode, set at True (case-sensitive) ENV FLASK_RUN_DEBUG=false EXPOSE ${PRINTER_PORT} EXPOSE ${FLASK_RUN_PORT} +#Expose the lpd port +EXPOSE 515 #Expose the CUPS admin port (temporary?) EXPOSE 631 diff --git a/esc2html.php b/esc2html.php index ca2a65d..a0a90f4 100644 --- a/esc2html.php +++ b/esc2html.php @@ -15,14 +15,14 @@ // Usage if ($argc < 2) { print("Usage: " . $argv[0] . " [--debug] filename \n"."zéro args"); - die(); + exit(1); } else { if ($argv[1]=='--debug'){ $debugMode = true; if (!isset($argv[2])) { print("Usage: " . $argv[0] . " [--debug] filename ". $argc-1 . " arguments received\n"); - die(); + exit(1); } else $targetFilename = $argv[2]; error_log("Debug mode enabled", 0); @@ -30,7 +30,7 @@ else { //First argument is not '--debug' if(isset($argv[2])) { // But there is at least 2 args print("Usage: " . $argv[0] . " [--debug] filename \n". $argc-1 . " arguments received\n"); - die(); + exit(1); } else $targetFilename = $argv[1]; //The only argument is the filename. } @@ -43,6 +43,10 @@ // Load in a file $fp = fopen($targetFilename, 'rb'); +if ( !$fp ) { + error_log("File ". $targetFilename . "not found."); + exit(1); +} $parser = new Parser(); $parser -> addFile($fp); @@ -173,6 +177,7 @@ echo "\n" . implode("\n", $html) . "\n"; error_log("'". $targetFilename . "' converted to HTML",0); + function imgAsDataUrl($bufferedImg) { $imgAlt = "Image " . $bufferedImg -> getWidth() . 'x' . $bufferedImg -> getHeight(); From 3738227c637375bc6c51c47f8e9e44f7a1aa9393 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 20 Nov 2023 16:45:30 -0500 Subject: [PATCH 046/105] More debugging power --- cups/cupsd.conf | 2 +- cups/esc2file.sh | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cups/cupsd.conf b/cups/cupsd.conf index fe52e12..65ca304 100644 --- a/cups/cupsd.conf +++ b/cups/cupsd.conf @@ -1,4 +1,4 @@ -LogLevel INFO +LogLevel debug PageLogFormat MaxLogSize 0 ErrorPolicy abort-job #Retrying won't make this right diff --git a/cups/esc2file.sh b/cups/esc2file.sh index a5b86f3..186974b 100644 --- a/cups/esc2file.sh +++ b/cups/esc2file.sh @@ -28,6 +28,7 @@ echo "INFO: jobcopies=${jobcopies}" 1>&2 echo "INFO: joboptions=${joboptions}" 1>&2 echo "INFO: jobfile=${jobfile}" 1>&2 echo "INFO: printtime=${printtime}" 1>&2 +echo "DEBUG: Executing as ${USER}" 1>&2 #echo "EMERG: This is a \"emergency\" level log message" 1>&2 #echo "ALERT: This is a \"alert\" level log message" 1>&2 @@ -58,19 +59,18 @@ case ${#} in cat - > ${TMPDIR}receipt.bin if [ "$?" -ne "0" ]; then - echo "CRIT: Cannot write to ${TMPDIR}receipt.bin" 1>&2 + echo "ERROR: Cannot write to ${TMPDIR}receipt.bin" 1>&2 else - #php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${DEVICE_URI#esc2file:} - php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${TMPDIR}/test.html + php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log if [ "$?" != "0" ]; then - echo "ERROR: Error while printing ${TMPDIR}receipt.bin" 1>&2 + echo "ERROR: Error $? while printing ${TMPDIR}receipt.bin to ${DEVICE_URI#esc2file:}" 1>&2 fi fi ;; 6) # backend needs to read from file if number of arguments is 6 - #php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} - php /home/escpos-emu/esc2html.php ${6} 1>/home/escpos-emu/web/receipts/test.html + echo "DEBUG: Printing from file ${6}" 1>&2 + php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log if [ "$?" != "0" ]; then echo "ERROR: Error $? while printing ${6} to ${DEVICE_URI#esc2file:}" 1>&2 fi From 174a561343697da87bd3953f48c9112ddc94d030 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 1 Dec 2023 14:46:29 -0500 Subject: [PATCH 047/105] First CUPS print! --- cups/esc2file.sh | 11 ++++++++--- dockerfile | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cups/esc2file.sh b/cups/esc2file.sh index 186974b..3ead29e 100644 --- a/cups/esc2file.sh +++ b/cups/esc2file.sh @@ -60,19 +60,24 @@ case ${#} in if [ "$?" -ne "0" ]; then echo "ERROR: Cannot write to ${TMPDIR}receipt.bin" 1>&2 + exit 1 #Send an error to CUPS to signal printing failure else php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log if [ "$?" != "0" ]; then echo "ERROR: Error $? while printing ${TMPDIR}receipt.bin to ${DEVICE_URI#esc2file:}" 1>&2 + exit 1 #Send an error to CUPS to signal printing failure fi fi ;; 6) # backend needs to read from file if number of arguments is 6 echo "DEBUG: Printing from file ${6}" 1>&2 - php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log + # php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log + /usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/test.html 2>>${TMPDIR}/esc2html_log if [ "$?" != "0" ]; then - echo "ERROR: Error $? while printing ${6} to ${DEVICE_URI#esc2file:}" 1>&2 + #echo "ERROR: Error $? while printing ${6} to ${DEVICE_URI#esc2file:}" 1>&2 + echo "ERROR: Error $? while printing ${6} to ${TMPDIR}test.html" 1>&2 + exit 1 #Send an error to CUPS to signal printing failure fi ;; 1|2|3|4|*) @@ -89,7 +94,7 @@ echo 1>&2 # we reach this line only if we actually "printed something" echo "NOTICE: processed Job ${jobid} to file ${DEVICE_URI#esc2file:}" 1>&2 -echo "NOTICE: End of \"${0}\" run...." 1>&2 +echo "NOTICE: End of \"${0}\" run." 1>&2 echo "NOTICE: ---------------------------------------------------" 1>&2 echo 1>&2 exit 0 \ No newline at end of file diff --git a/dockerfile b/dockerfile index 7c2ac1b..919411e 100644 --- a/dockerfile +++ b/dockerfile @@ -21,8 +21,10 @@ RUN apt-get install -y python3-lxml #Installation de CUPS RUN apt-get install -y cups ADD cups/cups-files.conf /etc/cups/cups-files.conf -# On passe en loglevel debug2! + +#Configure CUPS ADD cups/cupsd.conf /etc/cups/cupsd.conf +#Manage CUPS-specific users and permissions RUN groupadd cups-admins RUN useradd -d /home/escpos-emu -g cups-admins -s /sbin/nologin cupsadmin RUN echo "cupsadmin:123456" | chpasswd From 35cf4675345bdf154e7bbbfc40e176fe8d38d4ac Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 1 Dec 2023 16:45:19 -0500 Subject: [PATCH 048/105] HTML file creation works, but cannot be deposited outside of the CUPS temp directory. --- cups/esc2file.sh | 22 +++++++++++----------- dockerfile | 13 +++++-------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/cups/esc2file.sh b/cups/esc2file.sh index 3ead29e..cb2bb03 100644 --- a/cups/esc2file.sh +++ b/cups/esc2file.sh @@ -56,28 +56,28 @@ case ${#} in # backend needs to read from stdin if number of arguments is 5 # NOTE: the CUPS backend programming directives state that temporary files should be created in the directory specified by the "TMPDIR" environment variable echo "DEBUG: Printing from stdin" 1>&2 - cat - > ${TMPDIR}receipt.bin + cat - > ${TMPDIR}/receipt.bin if [ "$?" -ne "0" ]; then - echo "ERROR: Cannot write to ${TMPDIR}receipt.bin" 1>&2 - exit 1 #Send an error to CUPS to signal printing failure + echo "ERROR: Cannot write to ${TMPDIR}/receipt.bin" 1>&2 + exit 51 #Send an error to CUPS to signal printing failure else - php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log - if [ "$?" != "0" ]; then + /usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log + if [ "$?" -ne "0" ]; then echo "ERROR: Error $? while printing ${TMPDIR}receipt.bin to ${DEVICE_URI#esc2file:}" 1>&2 - exit 1 #Send an error to CUPS to signal printing failure + exit 52 #Send an error to CUPS to signal printing failure fi fi ;; 6) # backend needs to read from file if number of arguments is 6 echo "DEBUG: Printing from file ${6}" 1>&2 - # php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log + #/usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log /usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/test.html 2>>${TMPDIR}/esc2html_log - if [ "$?" != "0" ]; then - #echo "ERROR: Error $? while printing ${6} to ${DEVICE_URI#esc2file:}" 1>&2 - echo "ERROR: Error $? while printing ${6} to ${TMPDIR}test.html" 1>&2 - exit 1 #Send an error to CUPS to signal printing failure + if [ "$?" -ne "0" ]; then + echo "ERROR: Error $? while printing ${6} to ${DEVICE_URI#esc2file:}" 1>&2 + #echo "ERROR: Error $? while printing ${6} to ${TMPDIR}/test.html" 1>&2 + exit 61 #Send an error to CUPS to signal printing failure fi ;; 1|2|3|4|*) diff --git a/dockerfile b/dockerfile index 919411e..ad2ed2f 100644 --- a/dockerfile +++ b/dockerfile @@ -24,6 +24,7 @@ ADD cups/cups-files.conf /etc/cups/cups-files.conf #Configure CUPS ADD cups/cupsd.conf /etc/cups/cupsd.conf + #Manage CUPS-specific users and permissions RUN groupadd cups-admins RUN useradd -d /home/escpos-emu -g cups-admins -s /sbin/nologin cupsadmin @@ -32,16 +33,12 @@ COPY --chown=lp:lp --chmod=666 web/. /home/escpos-emu/web # "Device URI" for CUPS ENV DEVICE_URI=esc2file:/home/escpos-emu/web/receipts/test.html -# Temporary directory for CUPS -#ENV TMPDIR=/home/escpos-emu/web/tmp -# Give CUPS access to directories - #Configuration de CUPS ATTENTION: on rend CUPS complètement ouvert de cette façon! -RUN /usr/sbin/cupsd \ - && while [ ! -f /var/run/cups/cupsd.pid ]; do sleep 1; done \ - && cupsctl --remote-admin --remote-any --share-printers \ - && kill $(cat /var/run/cups/cupsd.pid) +# RUN /usr/sbin/cupsd \ +# && while [ ! -f /var/run/cups/cupsd.pid ]; do sleep 1; done \ +# && cupsctl --remote-admin --remote-any --share-printers \ +# && kill $(cat /var/run/cups/cupsd.pid) RUN rm /etc/cups/snmp.conf #RUN rm /home/escpos-emu/cups/cups-files.conf From e61dbfbd04fc4ef5e16ba0b32fe37750818033e5 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 19 Dec 2023 14:38:53 -0500 Subject: [PATCH 049/105] First working version with CUPS frontend --- cups/esc2file.sh | 17 +++++++++-- dockerfile | 10 ++++--- escpos-netprinter.py | 70 ++++++++++++++++++++++++++++++++++++-------- start.sh | 6 +++- 4 files changed, 83 insertions(+), 20 deletions(-) diff --git a/cups/esc2file.sh b/cups/esc2file.sh index cb2bb03..567e652 100644 --- a/cups/esc2file.sh +++ b/cups/esc2file.sh @@ -29,6 +29,7 @@ echo "INFO: joboptions=${joboptions}" 1>&2 echo "INFO: jobfile=${jobfile}" 1>&2 echo "INFO: printtime=${printtime}" 1>&2 echo "DEBUG: Executing as ${USER}" 1>&2 +echo "NOTICE: processing Job ${jobid}" 1>&2 #echo "EMERG: This is a \"emergency\" level log message" 1>&2 #echo "ALERT: This is a \"alert\" level log message" 1>&2 @@ -62,7 +63,8 @@ case ${#} in echo "ERROR: Cannot write to ${TMPDIR}/receipt.bin" 1>&2 exit 51 #Send an error to CUPS to signal printing failure else - /usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}receipt.bin 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log + #/usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}/receipt.bin 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log + /usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}/receipt.bin 1>${TMPDIR}/esc2html.html 2>>${TMPDIR}/esc2html_log if [ "$?" -ne "0" ]; then echo "ERROR: Error $? while printing ${TMPDIR}receipt.bin to ${DEVICE_URI#esc2file:}" 1>&2 exit 52 #Send an error to CUPS to signal printing failure @@ -73,12 +75,23 @@ case ${#} in # backend needs to read from file if number of arguments is 6 echo "DEBUG: Printing from file ${6}" 1>&2 #/usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log - /usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/test.html 2>>${TMPDIR}/esc2html_log + /usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/esc2html.html 2>>${TMPDIR}/esc2html_log if [ "$?" -ne "0" ]; then echo "ERROR: Error $? while printing ${6} to ${DEVICE_URI#esc2file:}" 1>&2 #echo "ERROR: Error $? while printing ${6} to ${TMPDIR}/test.html" 1>&2 exit 61 #Send an error to CUPS to signal printing failure fi + #Call the web site to move the html file to the web directory + response=$(/usr/bin/curl -s http://localhost:${FLASK_RUN_PORT}/newReceipt) + if [ "$?" -eq "0" ]; then + if echo "$response" | grep -q "OK"; then + echo "File copy returned OK" + else + echo "File copy did not return OK" + fi + else + echo "ERROR: Error $? while calling the web site to move the ${TMPDIR}/esc2html.html to the web directory" 1>&2 + fi ;; 1|2|3|4|*) # these cases are unsupported diff --git a/dockerfile b/dockerfile index ad2ed2f..23b92db 100644 --- a/dockerfile +++ b/dockerfile @@ -1,5 +1,7 @@ #On part de l'image php-cli "latest" sur Debian -FROM php:cli +#FROM php:cli +#Contournement temporaire: imagick a un problème, mais pas sur php8.2 +FROM php:8.2-cli #On va utiliser l'utilitaire "install-php-extensions" au lieu de PECL car il marche mieux. #Voir: https://github.com/mlocati/docker-php-extension-installer @@ -29,7 +31,7 @@ ADD cups/cupsd.conf /etc/cups/cupsd.conf RUN groupadd cups-admins RUN useradd -d /home/escpos-emu -g cups-admins -s /sbin/nologin cupsadmin RUN echo "cupsadmin:123456" | chpasswd -COPY --chown=lp:lp --chmod=666 web/. /home/escpos-emu/web +#COPY --chown=lp:lp --chmod=666 web/. /home/escpos-emu/web # "Device URI" for CUPS ENV DEVICE_URI=esc2file:/home/escpos-emu/web/receipts/test.html @@ -64,6 +66,6 @@ EXPOSE 515 EXPOSE 631 # Démarrer le serveur Flask et le serveur d'impression -CMD ["/usr/sbin/cupsd", "-f"] +#CMD ["/usr/sbin/cupsd", "-f"] #CMD python3 ${FLASK_APP} -#CMD ["/bin/bash","-c","./start.sh"] +CMD ["/bin/bash","-c","./start.sh"] diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 91b1808..9e01904 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -7,9 +7,12 @@ from lxml import html, etree from datetime import datetime from zoneinfo import ZoneInfo +import shutil + import socket, threading import socketserver + #Network ESC/pos printer server class ESCPOSServer(socketserver.TCPServer): @@ -80,24 +83,29 @@ def print_toHTML(self, binfilename:str): #print(recu.stdout, flush=True) heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) - - recuConvert:etree.ElementTree = html.fromstring(recu.stdout) - - theHead:etree.Element = recuConvert.head - newTitle = etree.Element("title") - newTitle.text = "Reçu imprimé le {}".format(heureRecept.strftime('%d %b %Y @ %X%Z')) - theHead.append(newTitle) + recuConvert = self.add_html_title(heureRecept, recu.stdout) #print(etree.tostring(theHead), flush=True) try: nouveauRecu = open(PurePath('web', 'receipts', 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d_%X%Z'))), mode='wt') #Écrire le reçu dans le fichier. - nouveauRecu.write(html.tostring(recuConvert).decode()) + nouveauRecu.write(recuConvert) nouveauRecu.close() except OSError as err: print("File creation error:", err.errno, flush=True) + + def add_html_title(self,heureRecept:datetime, recu:str)->str: + + recuConvert:etree.ElementTree = html.fromstring(recu) + + theHead:etree.Element = recuConvert.head + newTitle = etree.Element("title") + newTitle.text = "Reçu imprimé le {}".format(heureRecept.strftime('%d %b %Y @ %X%Z')) + theHead.append(newTitle) + + return html.tostring(recuConvert).decode() @@ -118,10 +126,46 @@ def list_receipts(): @app.route("/recus/") def show_receipt(filename): return send_file(PurePath('web', 'receipts', filename)) + +@app.route("/newReceipt") +def publish_receipt(): + """ Get the receipt from the CUPS temp directory and publish it in the web/receipts directory and add the corresponding log to our permanent logfile""" + heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) + #NOTE: on set dans cups-files.conf le répertoire TempDir: + #obtenir le répertoire temporaire de CUPS de cups-files.conf + source_dir=PurePath('/var', 'spool', 'cups', 'tmp') + + # specify your source file and destination file paths + source_file = source_dir.joinpath('esc2html.html') + destination_dir = PurePath('web', 'receipts') + + # specify your new filename + new_filename = 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d_%X%Z')) + + # create the full destination path with the new filename + destination_file = destination_dir / new_filename + + # use shutil.copy2() to copy the file + shutil.copy2(source_file, destination_file) + + #Load the log from /var/spool/cups/tmp/esc2html_log and append it in web/tmp/esc2html_log + log = open(PurePath('web','tmp', 'esc2html_log'), mode='at') + source_log = open(source_dir.joinpath('esc2html_log'), mode='rt') + log.write(source_log.read()) + log.close() + #remove the contents from the source log + source_log.close() + source_log = open(source_dir.joinpath('esc2html_log'), mode='wt') + source_log.write('') + source_log.close() + + #send an http acknowledgement + return "OK" + def launchPrintServer(printServ:ESCPOSServer): - #Recevoir des connexions, une à la fois, pour l'éternité. + #Recevoir des connexions, une à la fois, pour l'éternité. Émule le protocle HP JetDirect """ NOTE: On a volontairement pris la version bloquante pour s'assurer que chaque reçu va être sauvegardé puis converti avant d'en accepter un autre. NOTE: il est possible que ce soit le comportement attendu de n'accepter qu'une connection à la fois. Voir p.6 de la spécification d'un module Ethernet à l'adresse suivante: https://files.cyberdata.net/assets/010748/ETHERNET_IV_Product_Guide_Rev_D.pdf """ @@ -139,9 +183,9 @@ def launchPrintServer(printServ:ESCPOSServer): #Lancer le service d'impression TCP with ESCPOSServer((host, int(printPort)), ESCPOSHandler) as printServer: - t = threading.Thread(target=launchPrintServer, args=[printServer]) - t.daemon = True - t.start() + # t = threading.Thread(target=launchPrintServer, args=[printServer]) + # t.daemon = True + # t.start() #Lancer l'application Flask if debugmode == 'True': @@ -149,4 +193,4 @@ def launchPrintServer(printServ:ESCPOSServer): else: startDebug:bool = False - app.run(host=host, port=int(port), debug=startDebug, use_reloader=False) #On empêche le reloader parce qu'il repart "main" au complet et le service d'imprimante n'est pas conçu pour ça. \ No newline at end of file + app.run(host=host, port=int(port), debug=startDebug, use_reloader=False) #On empêche le reloader parce qu'il repart "main" au complet et le service d'imprimante n'est pas conçue pour ça. \ No newline at end of file diff --git a/start.sh b/start.sh index f70dfed..bb48812 100644 --- a/start.sh +++ b/start.sh @@ -1,3 +1,7 @@ #!/bin/bash + +# Start CUPS /usr/sbin/cupsd -python3 $FLASK_APP + +# Start Flask +python3 ${FLASK_APP} From 5ce356bd953ecb6475bf8c3275738592ae62eb91 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 19 Dec 2023 16:44:22 -0500 Subject: [PATCH 050/105] Auto-create and share the printer on container start. --- .gitignore | 3 ++- cups/cups-files.conf | 1 - cups/cupsd.conf | 9 ++++++--- cups/test_jetdirect.py | 23 +++++++++++++++++++++++ cups/test_lpd.sh | 4 ++++ dockerfile | 4 ++-- start.sh | 1 + 7 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 cups/test_jetdirect.py create mode 100644 cups/test_lpd.sh diff --git a/.gitignore b/.gitignore index d1b4d59..df5d62e 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ build/* *.phar .devcontainer/** .vscode/** -composer.lock \ No newline at end of file +composer.lock +cups/*.tar.gz diff --git a/cups/cups-files.conf b/cups/cups-files.conf index 8d37718..b3e0281 100644 --- a/cups/cups-files.conf +++ b/cups/cups-files.conf @@ -18,7 +18,6 @@ Group lp # This cannot contain the Group value for security reasons... SystemGroup root lpadmin cups-admins - # User that is substituted for unauthenticated (remote) root accesses... #RemoteRoot remroot diff --git a/cups/cupsd.conf b/cups/cupsd.conf index 65ca304..461ecca 100644 --- a/cups/cupsd.conf +++ b/cups/cupsd.conf @@ -2,15 +2,18 @@ LogLevel debug PageLogFormat MaxLogSize 0 ErrorPolicy abort-job #Retrying won't make this right -# Allow remote access +# Allow web admin access Port 631 Listen /run/cups/cups.sock +WebInterface Yes +ServerAlias * +IdleExitTimeout 60 + # Share local printers on the local network. Browsing On BrowseLocalProtocols dnssd DefaultAuthType Basic -WebInterface Yes -IdleExitTimeout 60 + # Allow shared printing and remote administration... Order allow,deny diff --git a/cups/test_jetdirect.py b/cups/test_jetdirect.py new file mode 100644 index 0000000..096e104 --- /dev/null +++ b/cups/test_jetdirect.py @@ -0,0 +1,23 @@ +import socket +import argparse + +#This is a simple test script to test that the JetDirect protocol works. + +#get printer host and port from command line +parser = argparse.ArgumentParser() +parser.add_argument('--host', help='IP adress or hostname of the printer', default='localhost') +parser.add_argument('--port', help='Port of the printer', default=9100) +args = parser.parse_args() + +HOST = args.host #The IP adress or hostname of the printer +PORT = args.port #A printer should always listen to port 9100, but the Epson printers can be configured so also will we. + +print(f"Print to: {HOST}:{PORT}") + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((HOST, int(PORT))) + s.sendall(b"Hello, world\n") + s.shutdown(socket.SHUT_WR) #Indiquer qu'on a fini de transmettre, et qu'on est prêt à recevoir. + data = s.recv(1024) + +print(f"Received {data!r}") \ No newline at end of file diff --git a/cups/test_lpd.sh b/cups/test_lpd.sh new file mode 100644 index 0000000..d8d2b5c --- /dev/null +++ b/cups/test_lpd.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# This is a script to test the lpd backend +lp -d lpd_escpos ../receipt-with-logo.bin +lp -d lpd_escpos ../receipt-with-qrcode.bin \ No newline at end of file diff --git a/dockerfile b/dockerfile index 23b92db..83000b5 100644 --- a/dockerfile +++ b/dockerfile @@ -42,9 +42,9 @@ ENV DEVICE_URI=esc2file:/home/escpos-emu/web/receipts/test.html # && cupsctl --remote-admin --remote-any --share-printers \ # && kill $(cat /var/run/cups/cupsd.pid) RUN rm /etc/cups/snmp.conf -#RUN rm /home/escpos-emu/cups/cups-files.conf +RUN rm /home/escpos-emu/cups/cups-files.conf -#Installation HTML printer +#Installation HTML converter RUN composer install RUN rm composer.json && rm composer.lock diff --git a/start.sh b/start.sh index bb48812..b9230c1 100644 --- a/start.sh +++ b/start.sh @@ -2,6 +2,7 @@ # Start CUPS /usr/sbin/cupsd +lpadmin -p lpd_escpos -v $DEVICE_URI -E -o printer-is-shared=true # Start Flask python3 ${FLASK_APP} From e170091a9f1e406e8249307010038f69bd98ee48 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Thu, 21 Dec 2023 15:52:12 -0500 Subject: [PATCH 051/105] Reactivate the Jetdirect Python service. Update access permissions and file paths, add debug logs --- cups/cupsd.conf | 8 ++--- esc2html.php | 17 +++++++++-- escpos-netprinter.py | 36 ++++++++++++++++------- src/Parser/Context/Code2DStateStorage.php | 1 + 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/cups/cupsd.conf b/cups/cupsd.conf index 461ecca..4349aa5 100644 --- a/cups/cupsd.conf +++ b/cups/cupsd.conf @@ -17,26 +17,26 @@ DefaultAuthType Basic # Allow shared printing and remote administration... Order allow,deny - Allow all + Allow all #Allow access from any IP address # Allow remote administration... Order allow,deny - Allow all + Allow all #Allow access from any IP address AuthType Default Require user @SYSTEM # Allow remote access to the configuration files... Order allow,deny - Allow all + Allow all #Allow access from any IP address AuthType Default Require user @SYSTEM # Allow remote access to the log files... Order allow,deny - Allow all + Allow all #Allow access from any IP address JobPrivateAccess default diff --git a/esc2html.php b/esc2html.php index a0a90f4..fa9b32d 100644 --- a/esc2html.php +++ b/esc2html.php @@ -137,8 +137,21 @@ if ($qrcodeURI == Code2DStatestorage::NO_DATA_ERROR){ error_log("QR code print ordered before contents stored.",0); - $imageData = base64_encode(file_get_contents('NoQR.JPG')); - $imgSrc = 'data: '.mime_content_type('NoQR.JPG').';base64,'.$imageData; + $imagefile = file_get_contents('NoQR.JPG'); + if ($imagefile === false) { + #To make the netprinter work, provide a full path to the image file + $imagefile = file_get_contents('/home/escpos-emu/NoQR.JPG'); + if ($imagefile === false) { + error_log("NoQR.JPG not found.",0); + } + else { + $imageData = base64_encode($imagefile); + $imgSrc = 'data: '.mime_content_type('NoQR.JPG').';base64,'.$imageData;} + } + else { + $imageData = base64_encode($imagefile); + $imgSrc = 'data: '.mime_content_type('NoQR.JPG').';base64,'.$imageData; + } $qrcodeData = Code2dStatestorage::NO_DATA_ERROR; $outp[] = "\"$qrcodeData\""; } diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 9e01904..b6aca4e 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -1,6 +1,7 @@ from flask import Flask, send_file, render_template from os import getenv, listdir from os.path import splitext +from io import BufferedWriter import subprocess from subprocess import CompletedProcess from pathlib import PurePath @@ -33,7 +34,8 @@ class ESCPOSHandler(socketserver.StreamRequestHandler): # Receive the print data and dump it in a file. def handle(self): print (f"Address connected: {self.client_address}", flush=True) - binfile = open("reception.bin", "wb") + bin_filename = PurePath('web', 'tmp', "reception.bin") + binfile = open(bin_filename, "wb") #Read everything until we get EOF indata:bytes = b'' @@ -65,13 +67,13 @@ def handle(self): print ("Data received, signature sent.", flush=True) #traiter le fichier reception.bin pour en faire un HTML - self.print_toHTML("reception.bin") + self.print_toHTML(binfile, bin_filename) #Convertir l'impression recue en HTML et la rendre disponible à Flask - def print_toHTML(self, binfilename:str): + def print_toHTML(self, binfile:BufferedWriter, filename:PurePath): - print("Impression de ", binfilename) - recu:CompletedProcess = subprocess.run(["php", "esc2html.php", binfilename], capture_output=True, text=True ) + print("Printing ", binfile.name) + recu:CompletedProcess = subprocess.run(["php", "esc2html.php", filename.as_posix()], capture_output=True, text=True ) if recu.returncode != 0: print(f"Error while converting receipt: {recu.returncode}") print("Error output:") @@ -82,9 +84,13 @@ def print_toHTML(self, binfilename:str): print (f"Receipt decoded", flush=True) #print(recu.stdout, flush=True) + #Ajouter un titre au reçu heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) recuConvert = self.add_html_title(heureRecept, recu.stdout) + #Ajouter un pied de page au reçu + recuConvert = self.add_html_footer(recuConvert) + #print(etree.tostring(theHead), flush=True) try: @@ -97,7 +103,6 @@ def print_toHTML(self, binfilename:str): print("File creation error:", err.errno, flush=True) def add_html_title(self,heureRecept:datetime, recu:str)->str: - recuConvert:etree.ElementTree = html.fromstring(recu) theHead:etree.Element = recuConvert.head @@ -106,7 +111,16 @@ def add_html_title(self,heureRecept:datetime, recu:str)->str: theHead.append(newTitle) return html.tostring(recuConvert).decode() - + + def add_html_footer(self, recu:str)->str: + recuConvert:etree.ElementTree = html.fromstring(recu) + theBody:etree.Element = recuConvert.body + + newFooter = etree.Element("footer") + newFooter.text = "Retour à la Liste des reçus" + theBody.append(newFooter) + + return html.tostring(recuConvert).decode() app = Flask(__name__) @@ -132,7 +146,7 @@ def publish_receipt(): """ Get the receipt from the CUPS temp directory and publish it in the web/receipts directory and add the corresponding log to our permanent logfile""" heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) #NOTE: on set dans cups-files.conf le répertoire TempDir: - #obtenir le répertoire temporaire de CUPS de cups-files.conf + #Extraire le répertoire temporaire de CUPS de cups-files.conf source_dir=PurePath('/var', 'spool', 'cups', 'tmp') # specify your source file and destination file paths @@ -183,9 +197,9 @@ def launchPrintServer(printServ:ESCPOSServer): #Lancer le service d'impression TCP with ESCPOSServer((host, int(printPort)), ESCPOSHandler) as printServer: - # t = threading.Thread(target=launchPrintServer, args=[printServer]) - # t.daemon = True - # t.start() + t = threading.Thread(target=launchPrintServer, args=[printServer]) + t.daemon = True + t.start() #Lancer l'application Flask if debugmode == 'True': diff --git a/src/Parser/Context/Code2DStateStorage.php b/src/Parser/Context/Code2DStateStorage.php index ebfcc5e..d9ed7b0 100644 --- a/src/Parser/Context/Code2DStateStorage.php +++ b/src/Parser/Context/Code2DStateStorage.php @@ -132,6 +132,7 @@ public function setErrorCorrectLevel($levelByte){ // Model 2: 4,296 alphanumeric chars (V40 at low correction level) // Micro-QR: 21 alphanumeric chars https://www.qrcode.com/en/codes/microqr.html public function fillSymbolStorage($data){ + error_log("Filling symbol storage with data",0); #TODO: remove this debug line /*$maxDataBits = 0; switch ($this->qrCodeModel) { case 49: From 79b10dd01f5043e3dce2d0b244f85bfc4f80f29a Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 8 Jan 2024 09:42:00 -0500 Subject: [PATCH 052/105] Reorder dockerfile for speed Replace kludge for NoQR file with real solution --- dockerfile | 33 ++++++++++------------- esc2html.php | 16 +++++------ src/Parser/Context/Code2DStateStorage.php | 2 +- 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/dockerfile b/dockerfile index 83000b5..e74e18f 100644 --- a/dockerfile +++ b/dockerfile @@ -9,38 +9,32 @@ ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/do RUN chmod +x /usr/local/bin/install-php-extensions RUN install-php-extensions mbstring @composer imagick -#Note: utiliser "." au lieu de * permet de garder la structure et envoyer tous les sous-répertoires -ADD . /home/escpos-emu/ -RUN rm -rf web -ADD --chmod=0555 cups/esc2file.sh /usr/lib/cups/backend/esc2file -WORKDIR /home/escpos-emu/ - -#Installation de Flask +#Install Flask RUN apt-get update RUN apt-get install -y python3-flask RUN apt-get install -y python3-lxml -#Installation de CUPS +#Install CUPS RUN apt-get install -y cups -ADD cups/cups-files.conf /etc/cups/cups-files.conf - -#Configure CUPS -ADD cups/cupsd.conf /etc/cups/cupsd.conf #Manage CUPS-specific users and permissions RUN groupadd cups-admins RUN useradd -d /home/escpos-emu -g cups-admins -s /sbin/nologin cupsadmin RUN echo "cupsadmin:123456" | chpasswd -#COPY --chown=lp:lp --chmod=666 web/. /home/escpos-emu/web # "Device URI" for CUPS ENV DEVICE_URI=esc2file:/home/escpos-emu/web/receipts/test.html -#Configuration de CUPS ATTENTION: on rend CUPS complètement ouvert de cette façon! -# RUN /usr/sbin/cupsd \ -# && while [ ! -f /var/run/cups/cupsd.pid ]; do sleep 1; done \ -# && cupsctl --remote-admin --remote-any --share-printers \ -# && kill $(cat /var/run/cups/cupsd.pid) +#Installation de l'émulateur d'imprimante +#Note: utiliser "." au lieu de * permet de garder la structure et envoyer tous les sous-répertoires +ADD . /home/escpos-emu/ +RUN rm -rf web +ADD --chmod=0555 cups/esc2file.sh /usr/lib/cups/backend/esc2file +WORKDIR /home/escpos-emu/ + +#Configure CUPS +ADD cups/cupsd.conf /etc/cups/cupsd.conf +ADD cups/cups-files.conf /etc/cups/cups-files.conf RUN rm /etc/cups/snmp.conf RUN rm /home/escpos-emu/cups/cups-files.conf @@ -51,7 +45,8 @@ RUN rm composer.json && rm composer.lock #Configurer l'environnement d'exécution ENV FLASK_APP=escpos-netprinter.py ENV FLASK_RUN_HOST=0.0.0.0 -ENV FLASK_RUN_PORT=80 +#Web interface port +ENV FLASK_RUN_PORT=80 #This port is for the Jetdirect protocol. ENV PRINTER_PORT=9100 diff --git a/esc2html.php b/esc2html.php index fa9b32d..a9bba65 100644 --- a/esc2html.php +++ b/esc2html.php @@ -136,21 +136,17 @@ $qrcodeURI = $code2dStorage->getQRCodeURI(); if ($qrcodeURI == Code2DStatestorage::NO_DATA_ERROR){ - error_log("QR code print ordered before contents stored.",0); - $imagefile = file_get_contents('NoQR.JPG'); + error_log("DEBUG: QR code print ordered before contents stored.",0); + $imagefile = file_get_contents(__DIR__.'/NoQR.JPG'); if ($imagefile === false) { #To make the netprinter work, provide a full path to the image file - $imagefile = file_get_contents('/home/escpos-emu/NoQR.JPG'); - if ($imagefile === false) { - error_log("NoQR.JPG not found.",0); - } - else { - $imageData = base64_encode($imagefile); - $imgSrc = 'data: '.mime_content_type('NoQR.JPG').';base64,'.$imageData;} + error_log("ERROR: NoQR image not found in ".__DIR__, 0); + $imageData = ''; + $imgSrc = ''; } else { $imageData = base64_encode($imagefile); - $imgSrc = 'data: '.mime_content_type('NoQR.JPG').';base64,'.$imageData; + $imgSrc = 'data:image/jpeg;base64,' . $imageData; } $qrcodeData = Code2dStatestorage::NO_DATA_ERROR; $outp[] = "\"$qrcodeData\""; diff --git a/src/Parser/Context/Code2DStateStorage.php b/src/Parser/Context/Code2DStateStorage.php index d9ed7b0..0bd5225 100644 --- a/src/Parser/Context/Code2DStateStorage.php +++ b/src/Parser/Context/Code2DStateStorage.php @@ -132,7 +132,7 @@ public function setErrorCorrectLevel($levelByte){ // Model 2: 4,296 alphanumeric chars (V40 at low correction level) // Micro-QR: 21 alphanumeric chars https://www.qrcode.com/en/codes/microqr.html public function fillSymbolStorage($data){ - error_log("Filling symbol storage with data",0); #TODO: remove this debug line + error_log("Filling symbol storage with QR data",0); /*$maxDataBits = 0; switch ($this->qrCodeModel) { case 49: From 457a02346fcdb0b5f5d05320ff25f01daf67eede Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 8 Jan 2024 14:25:41 -0500 Subject: [PATCH 053/105] Remove direct file access and replace with database of files with ID's Add a navigation footer --- escpos-netprinter.py | 137 ++++++++++++++++++++++++---------- templates/accueil.html.j2 | 8 +- templates/footer.html | 6 ++ templates/receiptList.html.j2 | 4 +- 4 files changed, 110 insertions(+), 45 deletions(-) create mode 100644 templates/footer.html diff --git a/escpos-netprinter.py b/escpos-netprinter.py index b6aca4e..6615859 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -1,16 +1,15 @@ -from flask import Flask, send_file, render_template -from os import getenv, listdir -from os.path import splitext +from flask import Flask, redirect, render_template, request, url_for +from os import getenv from io import BufferedWriter +import csv import subprocess from subprocess import CompletedProcess from pathlib import PurePath from lxml import html, etree from datetime import datetime from zoneinfo import ZoneInfo -import shutil -import socket, threading +import threading import socketserver @@ -70,10 +69,10 @@ def handle(self): self.print_toHTML(binfile, bin_filename) #Convertir l'impression recue en HTML et la rendre disponible à Flask - def print_toHTML(self, binfile:BufferedWriter, filename:PurePath): + def print_toHTML(self, binfile:BufferedWriter, bin_filename:PurePath): print("Printing ", binfile.name) - recu:CompletedProcess = subprocess.run(["php", "esc2html.php", filename.as_posix()], capture_output=True, text=True ) + recu:CompletedProcess = subprocess.run(["php", "esc2html.php", bin_filename.as_posix()], capture_output=True, text=True ) if recu.returncode != 0: print(f"Error while converting receipt: {recu.returncode}") print("Error output:") @@ -88,21 +87,24 @@ def print_toHTML(self, binfile:BufferedWriter, filename:PurePath): heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) recuConvert = self.add_html_title(heureRecept, recu.stdout) - #Ajouter un pied de page au reçu - recuConvert = self.add_html_footer(recuConvert) - #print(etree.tostring(theHead), flush=True) try: - nouveauRecu = open(PurePath('web', 'receipts', 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d_%X%Z'))), mode='wt') - #Écrire le reçu dans le fichier. - nouveauRecu.write(recuConvert) - nouveauRecu.close() + #Créer un nouveau fichier avec le nom du reçu + html_filename = 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d_%X.%f%Z')) + with open(PurePath('web', 'receipts', html_filename), mode='wt') as nouveauRecu: + #Écrire le reçu dans le fichier. + nouveauRecu.write(recuConvert) + nouveauRecu.close() + #Ajouter le reçu à la liste des reçus + self.add_receipt_to_directory(html_filename) except OSError as err: print("File creation error:", err.errno, flush=True) - def add_html_title(self,heureRecept:datetime, recu:str)->str: + @staticmethod + def add_html_title(heureRecept:datetime, recu:str, self=None)->str: + """ Ajouter un titre au reçu """ recuConvert:etree.ElementTree = html.fromstring(recu) theHead:etree.Element = recuConvert.head @@ -112,37 +114,86 @@ def add_html_title(self,heureRecept:datetime, recu:str)->str: return html.tostring(recuConvert).decode() - def add_html_footer(self, recu:str)->str: - recuConvert:etree.ElementTree = html.fromstring(recu) - theBody:etree.Element = recuConvert.body - - newFooter = etree.Element("footer") - newFooter.text = "Retour à la Liste des reçus" - theBody.append(newFooter) - - return html.tostring(recuConvert).decode() - + @staticmethod + def add_receipt_to_directory(new_filename: str, self=None) -> None: + # Add an entry in the reference file with the new filename and an unique ID. + # Open the CSV file in read mode to count the existing rows + try: + with open(PurePath('web', 'receipt_list.csv'), mode='r') as fileDirectory: + reader = csv.reader(fileDirectory) + # Count the number of rows, starting from 1 (to include the header) + next_fileID = sum(1 for row in reader) + 1 + fileDirectory.close() + except FileNotFoundError: + # Create the CSV file with the headers + with open(PurePath('web', 'receipt_list.csv'), mode='w', newline='') as fileDirectory: + writer = csv.writer(fileDirectory) + writer.writerow(['next_fileID', 'filename']) + fileDirectory.close() + next_fileID = 1 # If the file does not exist, start IDs is 1 + # Now, id holds the next sequential ID + + # Open the CSV file in append mode to add a new row + with open(PurePath('web', 'receipt_list.csv'), mode='a', newline='') as fileDirectory: + writer = csv.writer(fileDirectory) + # Append a new line to the CSV file with the new ID and filename + writer.writerow([next_fileID, new_filename]) + app = Flask(__name__) @app.route("/") def accueil(): - return render_template('accueil.html.j2', host=getenv('FLASK_RUN_HOST', '0.0.0.0'), - port=getenv('PRINTER_PORT', '9100'), + return render_template('accueil.html.j2', host = request.host.split(':')[0], + jetDirectPort=getenv('PRINTER_PORT', '9100'), debug=getenv('FLASK_RUN_DEBUG', "false") ) @app.route("/recus") def list_receipts(): - fichiers = listdir(PurePath('web', 'receipts')) - noms = [ splitext(filename)[0] for filename in fichiers ] - return render_template('receiptList.html.j2', receiptlist=noms) + """ List all the receipts available """ + try: + with open(PurePath('web', 'receipt_list.csv'), mode='r') as fileDirectory: + # Skip the header and get all the filenames in a list + reader = csv.reader(fileDirectory) + noms = list() + for row in reader: + if row[0] == 'next_fileID': + continue # Skip the header + else: + # Add the file id and filename to the list + noms.append([row[0], row[1]]) + # Since the file is found, render the template with the list of filenames + return render_template('receiptList.html.j2', receiptlist=noms) + except FileNotFoundError: + return redirect(url_for('accueil')) + -@app.route("/recus/") -def show_receipt(filename): - return send_file(PurePath('web', 'receipts', filename)) +@app.route("/recus/") +def show_receipt(fileID:int): + """ Show the receipt with the given ID """ + # Open the CSV file in read mode + with open(PurePath('web', 'receipt_list.csv'), mode='r') as fileDirectory: + reader = csv.reader(fileDirectory) + # Find the row with the given ID + for row in reader: + if row[0] == 'next_fileID': + continue # Skip the header + elif int(row[0]) == fileID: + filename = row[1] + break + else: + # If the ID is not found, return a 404 error + return "Not found", 404 + + # If the ID is found, open the file to append the footer from templates/footer.html + with open(PurePath('web', 'receipts', filename), mode='rt') as receipt: + receipt_html = receipt.read() # Read the file content + receipt_html = receipt_html.replace('', render_template('footer.html') + '') # Append the footer + return receipt_html + @app.route("/newReceipt") -def publish_receipt(): +def publish_receipt_from_CUPS(): """ Get the receipt from the CUPS temp directory and publish it in the web/receipts directory and add the corresponding log to our permanent logfile""" heureRecept = datetime.now(tz=ZoneInfo("Canada/Eastern")) #NOTE: on set dans cups-files.conf le répertoire TempDir: @@ -151,16 +202,23 @@ def publish_receipt(): # specify your source file and destination file paths source_file = source_dir.joinpath('esc2html.html') - destination_dir = PurePath('web', 'receipts') # specify your new filename - new_filename = 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d_%X%Z')) + new_filename = 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d_%X.%f%Z')) + # create the full destination path with the new filename - destination_file = destination_dir / new_filename + destination_file = PurePath('web', 'receipts', new_filename) + + #read the file, add the title and write it in the destination file + with open(source_file, mode='rt') as receipt: + receipt_html = receipt.read() + receipt_html = ESCPOSHandler.add_html_title(heureRecept, receipt_html) + with open(destination_file, mode='wt') as newReceipt: + newReceipt.write(receipt_html) + newReceipt.close() - # use shutil.copy2() to copy the file - shutil.copy2(source_file, destination_file) + ESCPOSHandler.add_receipt_to_directory(new_filename) #Load the log from /var/spool/cups/tmp/esc2html_log and append it in web/tmp/esc2html_log log = open(PurePath('web','tmp', 'esc2html_log'), mode='at') @@ -176,7 +234,6 @@ def publish_receipt(): #send an http acknowledgement return "OK" - def launchPrintServer(printServ:ESCPOSServer): #Recevoir des connexions, une à la fois, pour l'éternité. Émule le protocle HP JetDirect diff --git a/templates/accueil.html.j2 b/templates/accueil.html.j2 index ce46695..dfa65de 100644 --- a/templates/accueil.html.j2 +++ b/templates/accueil.html.j2 @@ -7,15 +7,15 @@

    État de l'imprimante:

    • En ligne
    • -
    • Adresses source acceptées: {{host}}
    • -
    • Adresse de cette imprimante: {{request.environ['SERVER_ADDR']}}
    • -
    • Port d'impression: {{port}}
    • +
    • Adresse de cette imprimante: {{host}}
    • +
    • Ports d'impression: {{jetDirectPort}} (Jetdirect), 515 (lpd)
    • {%if debug == 'True'%}
    • Mode débogage activé
    • {%endif%}
    - Consulter les reçus imprimés + Consulter la liste des reçus imprimés +{% include 'footer.html' %} \ No newline at end of file diff --git a/templates/footer.html b/templates/footer.html new file mode 100644 index 0000000..b0e85d2 --- /dev/null +++ b/templates/footer.html @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/templates/receiptList.html.j2 b/templates/receiptList.html.j2 index 45b1bb0..ce5b1f0 100644 --- a/templates/receiptList.html.j2 +++ b/templates/receiptList.html.j2 @@ -9,12 +9,14 @@

    {{receiptlist|length}} recu{%if receiptlist|length > 1%}s{%endif%} disponible{%if receiptlist|length > 1%}s{%endif%}

    {% else %}

    Aucun reçu trouvé!

    {% endif %} {# a comment #} + +{% include 'footer.html' %} \ No newline at end of file From b6d7e4b2e54bf4a586b9f1e8182aafc64ca4bd9f Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 8 Jan 2024 15:03:37 -0500 Subject: [PATCH 054/105] Readd debug for bug: QR interpreted as text --- esc2html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esc2html.php b/esc2html.php index a9bba65..dae5246 100644 --- a/esc2html.php +++ b/esc2html.php @@ -73,7 +73,7 @@ if ($cmd -> isAvailableAs('TextContainer')) { // Add text to line // TODO could decode text properly from legacy code page to UTF-8 here. - #if ($debugMode) error_log("Text or unidentified command: '". $cmd->get_data() ."' ", 0); + if ($debugMode) error_log("Text or unidentified command: '". $cmd->getText() ."' ", 0); $spanContentText = $cmd -> getText(); $lineHtml .= span($formatting, $spanContentText); } From 2c09115f65205fc9a5829e366a24b7d3273f1e98 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 8 Jan 2024 15:07:38 -0500 Subject: [PATCH 055/105] Cleaning up the resulting container --- .dockerignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 947159a..61f1a1c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,4 +9,8 @@ doc/** .travis.yml web/receipts/** web/tmp/** -composer.lock \ No newline at end of file +composer.lock +esc2text.php +escimages.php +LICENSE** +README** \ No newline at end of file From d28fc6a55a89ff4ec8402399ed1b57f673e38aef Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 8 Jan 2024 15:07:38 -0500 Subject: [PATCH 056/105] Cleaning up the resulting container and updated the README.md file --- .dockerignore | 6 +++++- README.md | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.dockerignore b/.dockerignore index 947159a..61f1a1c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,4 +9,8 @@ doc/** .travis.yml web/receipts/** web/tmp/** -composer.lock \ No newline at end of file +composer.lock +esc2text.php +escimages.php +LICENSE** +README** \ No newline at end of file diff --git a/README.md b/README.md index 2cfa494..0e282f0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ESC/POS virtual network printer ---------- -This is a very simple container-based ESC/POS network printer, that transforms the printed material in HTML pages and makes them avaliable in a web interface. +This is a container-based ESC/POS network printer, that transforms the printed material in HTML pages and makes them avaliable in a web interface. ## Limits This docker image is not to be exposed on a public network (see [known issues](#known-issues)) @@ -24,9 +24,9 @@ docker build -t escpos-netprinter:beta . To run the resulting container: ```bash -docker run -d --rm --cpus 1.8 -p 5000:5000/tcp -p 9100:9100/tcp escpos-netprinter:beta +docker run -d --rm --cpus 1.8 -p 515:515/tcp -p 631:631/tcp -p 80:80/tcp -p 9100:9100/tcp escpos-netprinter:beta ``` -It should now accept prints on the default port(9100), and you can visualize it with the web application at port 5000. I have put a CPU usage limit as a safety, because it completely locked up my two-core PhotonOS host twice. +It should now accept prints by JetDirect on the default port(9100) and by lpd on the default port(515), and you can visualize it with the web application at port 80. ## Known issues This is still beta software for now, so it has known major defects: From 2c3c5d2420ed948d6f13ae83239f1525231e1b99 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Tue, 9 Jan 2024 16:46:50 -0500 Subject: [PATCH 057/105] esc2file cleanup: make the CUPS file names configurable in the device URI --- cups/cupsd.conf | 12 +++++++++--- cups/esc2file.sh | 36 +++++++++++++++++++++++------------- cups/test_jetdirect.sh | 3 +++ dockerfile | 7 +++++-- esc2html.php | 4 ++-- escpos-netprinter.py | 20 ++++++++++++-------- 6 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 cups/test_jetdirect.sh diff --git a/cups/cupsd.conf b/cups/cupsd.conf index 4349aa5..2f6f33b 100644 --- a/cups/cupsd.conf +++ b/cups/cupsd.conf @@ -1,7 +1,13 @@ -LogLevel debug +#Set the log level for the ErrorLog file - default is warn +#Values: none, emerg, alert, crit, error, warn, notice, info, debug, debug2 +LogLevel warn + +#Disable printed page logging PageLogFormat -MaxLogSize 0 -ErrorPolicy abort-job #Retrying won't make this right + +MaxLogSize 32m #Max log file size +ErrorPolicy abort-job #Retrying won't make errors right + # Allow web admin access Port 631 Listen /run/cups/cups.sock diff --git a/cups/esc2file.sh b/cups/esc2file.sh index 567e652..abd0b8e 100644 --- a/cups/esc2file.sh +++ b/cups/esc2file.sh @@ -44,7 +44,14 @@ echo "NOTICE: processing Job ${jobid}" 1>&2 # we will read the output filename from the printers $DEVICE_URI environment -# variable that should look like "esc2file:/path/to/a/filename.suffix" +# variable that should look like "esc2file:/dest_filename.suffix/log_filename.suffix" + +# Extract the file names from the DEVICE_URI +FILENAMES=${DEVICE_URI#esc2file:/} +DEST_FILENAME=${FILENAMES%/*} +LOG_FILENAME=${FILENAMES#*/} +echo "DEBUG: DEST_FILENAME=${DEST_FILENAME}" 1>&2 +echo "DEBUG: LOG_FILENAME=${LOG_FILENAME}" 1>&2 # Now do the real work: case ${#} in @@ -57,16 +64,17 @@ case ${#} in # backend needs to read from stdin if number of arguments is 5 # NOTE: the CUPS backend programming directives state that temporary files should be created in the directory specified by the "TMPDIR" environment variable echo "DEBUG: Printing from stdin" 1>&2 + # read from stdin and write to receipt.bin cat - > ${TMPDIR}/receipt.bin if [ "$?" -ne "0" ]; then echo "ERROR: Cannot write to ${TMPDIR}/receipt.bin" 1>&2 exit 51 #Send an error to CUPS to signal printing failure else - #/usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}/receipt.bin 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log - /usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}/receipt.bin 1>${TMPDIR}/esc2html.html 2>>${TMPDIR}/esc2html_log + #/usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}/receipt.bin 1>${TMPDIR}/esc2html.html 2>>${TMPDIR}/esc2html_log + /usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}/receipt.bin 1>${TMPDIR}/${DEST_FILENAME} 2>>${TMPDIR}/${LOG_FILENAME} if [ "$?" -ne "0" ]; then - echo "ERROR: Error $? while printing ${TMPDIR}receipt.bin to ${DEVICE_URI#esc2file:}" 1>&2 + echo "ERROR: Error $? while printing ${TMPDIR}/receipt.bin to ${TMPDIR}/${DEST_FILENAME}" 1>&2 exit 52 #Send an error to CUPS to signal printing failure fi fi @@ -74,23 +82,25 @@ case ${#} in 6) # backend needs to read from file if number of arguments is 6 echo "DEBUG: Printing from file ${6}" 1>&2 - #/usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${DEVICE_URI#esc2file:} 2>>/home/escpos-emu/web/tmp/esc2html_log - /usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/esc2html.html 2>>${TMPDIR}/esc2html_log + #/usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/esc2html.html 2>>${TMPDIR}/esc2html_log + /usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/${DEST_FILENAME} 2>>${TMPDIR}/${LOG_FILENAME} if [ "$?" -ne "0" ]; then - echo "ERROR: Error $? while printing ${6} to ${DEVICE_URI#esc2file:}" 1>&2 + echo "ERROR: Error $? while printing ${6} to ${TMPDIR}/${DEST_FILENAME}" 1>&2 #echo "ERROR: Error $? while printing ${6} to ${TMPDIR}/test.html" 1>&2 exit 61 #Send an error to CUPS to signal printing failure fi - #Call the web site to move the html file to the web directory + #Call the web app to move the html file to the web directory response=$(/usr/bin/curl -s http://localhost:${FLASK_RUN_PORT}/newReceipt) if [ "$?" -eq "0" ]; then if echo "$response" | grep -q "OK"; then - echo "File copy returned OK" + echo "DEBUG: File copy returned OK" 1>&2 else - echo "File copy did not return OK" + echo "ERROR: File copy did not return OK" 1>&2 + exit 62 #Send an error to CUPS to signal printing failure fi else - echo "ERROR: Error $? while calling the web site to move the ${TMPDIR}/esc2html.html to the web directory" 1>&2 + echo "ERROR: Error $? while calling the web app to move ${TMPDIR}/${DEST_FILENAME} to the web directory" 1>&2 + exit 63 #Send an error to CUPS to signal printing failure fi ;; 1|2|3|4|*) @@ -99,14 +109,14 @@ case ${#} in echo " Usage: esc2file job-id user title copies options [file]" echo " " echo " (Install as CUPS backend in /usr/lib/cups/backend/esc2file)" - echo " (Use as 'device URI' like \"esc2file:/path/to/writeable/jobfile.suffix\" for printer installation.)" + echo " (Use as 'device URI' like \"esc2file:/dest_filename.suffix/log_filename.suffix\" for printer installation.)" exit 0 esac echo 1>&2 # we reach this line only if we actually "printed something" -echo "NOTICE: processed Job ${jobid} to file ${DEVICE_URI#esc2file:}" 1>&2 +echo "NOTICE: processed Job ${jobid} to file ${TMPDIR}/${DEST_FILENAME}" 1>&2 echo "NOTICE: End of \"${0}\" run." 1>&2 echo "NOTICE: ---------------------------------------------------" 1>&2 echo 1>&2 diff --git a/cups/test_jetdirect.sh b/cups/test_jetdirect.sh new file mode 100644 index 0000000..d20909e --- /dev/null +++ b/cups/test_jetdirect.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# This is a script to test the jetdirect backend +python3 ./test_jetdirect.py \ No newline at end of file diff --git a/dockerfile b/dockerfile index e74e18f..7e58b16 100644 --- a/dockerfile +++ b/dockerfile @@ -22,8 +22,10 @@ RUN groupadd cups-admins RUN useradd -d /home/escpos-emu -g cups-admins -s /sbin/nologin cupsadmin RUN echo "cupsadmin:123456" | chpasswd -# "Device URI" for CUPS -ENV DEVICE_URI=esc2file:/home/escpos-emu/web/receipts/test.html +# Compose the "Device URI" for CUPS. +ENV DEST_FILENAME=esc2html.html +ENV LOG_FILENAME=esc2html_log +ENV DEVICE_URI=esc2file:/${DEST_FILENAME}/${LOG_FILENAME} #Installation de l'émulateur d'imprimante #Note: utiliser "." au lieu de * permet de garder la structure et envoyer tous les sous-répertoires @@ -44,6 +46,7 @@ RUN rm composer.json && rm composer.lock #Configurer l'environnement d'exécution ENV FLASK_APP=escpos-netprinter.py +#Accepted source IP addresses ENV FLASK_RUN_HOST=0.0.0.0 #Web interface port ENV FLASK_RUN_PORT=80 diff --git a/esc2html.php b/esc2html.php index dae5246..32473ce 100644 --- a/esc2html.php +++ b/esc2html.php @@ -136,11 +136,11 @@ $qrcodeURI = $code2dStorage->getQRCodeURI(); if ($qrcodeURI == Code2DStatestorage::NO_DATA_ERROR){ - error_log("DEBUG: QR code print ordered before contents stored.",0); + error_log("Warning: QR code print ordered before contents stored.",0); $imagefile = file_get_contents(__DIR__.'/NoQR.JPG'); if ($imagefile === false) { #To make the netprinter work, provide a full path to the image file - error_log("ERROR: NoQR image not found in ".__DIR__, 0); + error_log("ERROR: NoQR.JPG image not found in ".__DIR__, 0); $imageData = ''; $imgSrc = ''; } diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 6615859..6b9b82b 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -1,3 +1,4 @@ +import os from flask import Flask, redirect, render_template, request, url_for from os import getenv from io import BufferedWriter @@ -200,17 +201,17 @@ def publish_receipt_from_CUPS(): #Extraire le répertoire temporaire de CUPS de cups-files.conf source_dir=PurePath('/var', 'spool', 'cups', 'tmp') - # specify your source file and destination file paths - source_file = source_dir.joinpath('esc2html.html') + # Get the source filename from the environment variable and create the full path + source_filename = os.environ['DEST_FILENAME'] + source_file = source_dir.joinpath(source_filename) - # specify your new filename + # specify the destination filename new_filename = 'receipt{}.html'.format(heureRecept.strftime('%Y%b%d_%X.%f%Z')) - - # create the full destination path with the new filename + # Create the full destination path with the new filename destination_file = PurePath('web', 'receipts', new_filename) - #read the file, add the title and write it in the destination file + # Read the source file, add the title and write it in the destination file with open(source_file, mode='rt') as receipt: receipt_html = receipt.read() receipt_html = ESCPOSHandler.add_html_title(heureRecept, receipt_html) @@ -218,11 +219,14 @@ def publish_receipt_from_CUPS(): newReceipt.write(receipt_html) newReceipt.close() + # Add the new receipt to the directory ESCPOSHandler.add_receipt_to_directory(new_filename) - #Load the log from /var/spool/cups/tmp/esc2html_log and append it in web/tmp/esc2html_log + #Load the log file from /var/spool/cups/tmp/ and append it in web/tmp/esc2html_log + logfile_filename = os.environ['LOG_FILENAME'] + print(logfile_filename) log = open(PurePath('web','tmp', 'esc2html_log'), mode='at') - source_log = open(source_dir.joinpath('esc2html_log'), mode='rt') + source_log = open(source_dir.joinpath(logfile_filename), mode='rt') log.write(source_log.read()) log.close() #remove the contents from the source log From 0b2a386aada07034072f99e6b54a01f09baf0fe1 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 12 Jan 2024 15:21:46 -0500 Subject: [PATCH 058/105] Add VSCode dev environment to version control, without polluting the resulting container. --- .devcontainer/devcontainer.json | 42 +++++++++++++++++++++++++++++++++ .dockerignore | 10 +++++++- .gitignore | 9 +++++-- .vscode/launch.json | 38 +++++++++++++++++++++++++++++ .vscode/start_debug.sh | 5 ++++ .vscode/stop_debug.sh | 4 ++++ .vscode/tasks.json | 20 ++++++++++++++++ 7 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/start_debug.sh create mode 100644 .vscode/stop_debug.sh create mode 100644 .vscode/tasks.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..553cbf6 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,42 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile +{ + "name": "Existing Dockerfile", + "build": { + // Sets the run context to one level up instead of the .devcontainer folder. + "context": "..", + // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. + "dockerfile": "../dockerfile" + }, + // Set *default* container specific settings.json values on container create. + "customizations/vscode/settings": { + "terminal.integrated.shell.linux": "/bin/bash", + "python.pythonPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", + "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", + "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", + "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", + "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", + "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python" + ], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [80, 9100, 515, 631] + + // Use the docker arguments instead to make ports avaliable on the network the container is running on. + // "runArgs": [ + // "-p", "192.168.1.21:80:80", + // "-p", "192.168.1.21:9100:9100", + // "-p", "192.168.1.21:515:515", + // "-p", "192.168.1.21:631:631" + // ] +} diff --git a/.dockerignore b/.dockerignore index 61f1a1c..84f4ba4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -13,4 +13,12 @@ composer.lock esc2text.php escimages.php LICENSE** -README** \ No newline at end of file +README** + +# Remove all traces of Visual Studio Code devcontainer extension from the container - does not affect the actual devcontainer +.devcontainer/** +.devcontainer +.vscode/** +.vscode +__pycache__/** +__pycache__ \ No newline at end of file diff --git a/.gitignore b/.gitignore index df5d62e..f129e00 100644 --- a/.gitignore +++ b/.gitignore @@ -16,8 +16,13 @@ vendor/ # other build files build/* +/__pycache__/** *.phar -.devcontainer/** -.vscode/** composer.lock cups/*.tar.gz + +# Test and runtime-generated files +web/receipts/*.html +web/tmp/reception.bin +web/receipt_list.csv +web/tmp/*_log diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e514434 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,38 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Python: netprinter", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/escpos-netprinter.py", + "preLaunchTask": "Start Session", + "postDebugTask": "Stop Session", + "jinja": true, + "justMyCode": true + }, + { + "name": "Python: Flask without print server", + "type": "python", + "request": "launch", + "module": "flask", + "preLaunchTask": "Start Session", + "postDebugTask": "Stop Session", + "env": { + "FLASK_APP": "escpos-netprinter.py", + "FLASK_DEBUG": "1" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "jinja": true, + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/.vscode/start_debug.sh b/.vscode/start_debug.sh new file mode 100644 index 0000000..ed38a52 --- /dev/null +++ b/.vscode/start_debug.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Start CUPS +/usr/sbin/cupsd +lpadmin -p lpd_escpos -v $DEVICE_URI -E -o printer-is-shared=true diff --git a/.vscode/stop_debug.sh b/.vscode/stop_debug.sh new file mode 100644 index 0000000..4a686c9 --- /dev/null +++ b/.vscode/stop_debug.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Stop the CUPS daemon +service cups stop \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..bf5c516 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,20 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + + "version": "2.0.0", + "tasks": [ + { + "label": "Start Session", + "type": "shell", + "command": "./.vscode/start_debug.sh", + "problemMatcher": [] + }, + { + "label": "Stop Session", + "type": "shell", + "command": "./.vscode/stop_debug.sh", + "problemMatcher": [] + } + ] +} \ No newline at end of file From 69e9bcf4895ea1c8de79cfddedd8162dd71e0871 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Fri, 12 Jan 2024 16:24:36 -0500 Subject: [PATCH 059/105] Now the JetDirect service deals with the undocumented handshake. --- .dockerignore | 6 ++++++ escpos-netprinter.py | 32 ++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/.dockerignore b/.dockerignore index 84f4ba4..c984202 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,6 +15,12 @@ escimages.php LICENSE** README** +# Test and runtime-generated files +web/receipts/** +web/tmp/** +web/receipt_list.csv +web/tmp/** + # Remove all traces of Visual Studio Code devcontainer extension from the container - does not affect the actual devcontainer .devcontainer/** .devcontainer diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 6b9b82b..57d4f36 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -40,20 +40,44 @@ def handle(self): #Read everything until we get EOF indata:bytes = b'' try: - indata = self.rfile.read() + # Read the handshake first to signal that we are a printer if needed + # NOTE: since this is an undocumented feature, we will keep going even if we don't get the handshake + + indata_handshake = self.rfile.read(8) #Read the first 8 bytes + print(f"{len(indata_handshake)} bytes received.") + print("-----start of data-----", flush=True) + print(indata_handshake, flush=True) + print("-----end of data-----", flush=True) + if(indata_handshake == b'\x1b\x40\x1b\x3d\x01\x10\x04\x01'): + self.wfile.write(b'\x16') + self.wfile.flush() + print("Handshake done", flush=True) + + #Read the rest of the data and append it to the handshake + after_handshake = self.rfile.read() + indata = indata_handshake + after_handshake + except TimeoutError: print("Timeout while reading") self.connection.close() if len(indata) > 0: print(f"{len(indata)} bytes received.") + print("-----start of data-----", flush=True) print(indata, flush=True) + print("-----end of data-----", flush=True) else: - print("Nothing received!", flush=True) + print("No data received!", flush=True) else: print(f"{len(indata)} bytes received.", flush=True) + + #Debug: afficher les données reçues + print("-----start of data-----", flush=True) + print(indata, flush=True) + print("-----end of data-----", flush=True) + #Écrire les données reçues dans le fichier. binfile.write(indata) @@ -224,7 +248,7 @@ def publish_receipt_from_CUPS(): #Load the log file from /var/spool/cups/tmp/ and append it in web/tmp/esc2html_log logfile_filename = os.environ['LOG_FILENAME'] - print(logfile_filename) + # print(logfile_filename) log = open(PurePath('web','tmp', 'esc2html_log'), mode='at') source_log = open(source_dir.joinpath(logfile_filename), mode='rt') log.write(source_log.read()) @@ -244,7 +268,7 @@ def launchPrintServer(printServ:ESCPOSServer): """ NOTE: On a volontairement pris la version bloquante pour s'assurer que chaque reçu va être sauvegardé puis converti avant d'en accepter un autre. NOTE: il est possible que ce soit le comportement attendu de n'accepter qu'une connection à la fois. Voir p.6 de la spécification d'un module Ethernet à l'adresse suivante: https://files.cyberdata.net/assets/010748/ETHERNET_IV_Product_Guide_Rev_D.pdf """ - print (f"Printer port open", flush=True) + print (f"JetDirect port open", flush=True) printServ.serve_forever() From d5e9d2b77d798b99a06be8554c0da2aef48008e2 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 15 Jan 2024 10:22:54 -0500 Subject: [PATCH 060/105] Visual tweaks: emulate a 80mm paper receipt and center all QR codes printed. --- esc2html.php | 5 +++-- src/resources/esc2html.css | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esc2html.php b/esc2html.php index 32473ce..341c2a7 100644 --- a/esc2html.php +++ b/esc2html.php @@ -196,14 +196,15 @@ function imgAsDataUrl($bufferedImg) return "\"$imgAlt\""; } -/* Creates the HTML image of a QR code +/* Creates the HTML image of a QR code. +The image is encased in a div which is used to center the image. Args: bufferedQRImg: the Base64 encoded PNG image of the QR code qrcodeData: the data encoded in the QR code, to be put in the alt tag as an accessibility measure */ function qrCodeAsDataUrl($bufferedQRImg, $qrcodeData) { $imgSrc = "data:image/png;base64," . $bufferedQRImg; - return "\"$qrcodeData\""; + return "
    \"$qrcodeData\"
    "; } function wrapInline($tag, $closeTag, $content) diff --git a/src/resources/esc2html.css b/src/resources/esc2html.css index 0244001..31253cf 100644 --- a/src/resources/esc2html.css +++ b/src/resources/esc2html.css @@ -8,6 +8,7 @@ body { /*margin: 1em;*/ padding: 1em; /*min-width: 28em;*/ + width: 80mm; /* 80mm is the width of a paper receipt. Other sizes include 52mm */ transform: scale(1.25); transform-origin: top left; display: inline-block; From 9514a51d0afc051a577ac5c9b15948a17e3c9595 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 15 Jan 2024 10:45:21 -0500 Subject: [PATCH 061/105] Readme updates for version 2.0-beta --- README.md | 11 ++++++++--- escpos-netprinter.py | 2 ++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e282f0..bc86fac 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ ESC/POS virtual network printer This is a container-based ESC/POS network printer, that transforms the printed material in HTML pages and makes them avaliable in a web interface. +The printer emulates a 80mm roll of paper. + ## Limits This docker image is not to be exposed on a public network (see [known issues](#known-issues)) @@ -19,18 +21,21 @@ To install from source: wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/heads/master.zip unzip master.zip cd escpos-netprinter-master -docker build -t escpos-netprinter:beta . +docker build -t escpos-netprinter:2.0-beta . ``` To run the resulting container: ```bash -docker run -d --rm --cpus 1.8 -p 515:515/tcp -p 631:631/tcp -p 80:80/tcp -p 9100:9100/tcp escpos-netprinter:beta +docker run -d --rm --name escpos_netprinter -p 515:515/tcp -p 80:80/tcp -p 9100:9100/tcp escpos-netprinter:2.0-beta ``` It should now accept prints by JetDirect on the default port(9100) and by lpd on the default port(515), and you can visualize it with the web application at port 80. +For debugging, you can add port 631 to access the CUPS interface. The CUPS administrative username is `cupsadmin` and the password is `123456`. + +As of version 2.0, this has been tested to work with a regular POS program without adapting it. ## Known issues This is still beta software for now, so it has known major defects: - It still uses the Flask development server, so it is unsafe for public networks. - The conversion to HTML does not do QR codes correctly (see the 1.0-beta.2 release notes) -- ~~This still has not been tested with success with a regular POS program (like the [Epson utilities](https://download.epson-biz.com/modules/pos/))~~ It works with simpler drivers, for example for the MUNBYN ITPP047 printers. +- While it works with simple drivers, for example for the MUNBYN ITPP047 printers, the [Epson utilities](https://download.epson-biz.com/modules/pos/) refuse to speak to it. diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 57d4f36..d9fc54b 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -280,6 +280,8 @@ def launchPrintServer(printServ:ESCPOSServer): debugmode = getenv('FLASK_RUN_DEBUG', "false") printPort = getenv('PRINTER_PORT', '9100') + print("Starting ESCPOS-netprinter", flush=True) + #Lancer le service d'impression TCP with ESCPOSServer((host, int(printPort)), ESCPOSHandler) as printServer: t = threading.Thread(target=launchPrintServer, args=[printServer]) From 59f27b5722a8714db85495f4c1920a8a5d9aabed Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 15 Jan 2024 10:45:21 -0500 Subject: [PATCH 062/105] Readme updates for version 2.0-beta Corrected layout of QR code image when there is no data --- README.md | 11 ++++++++--- esc2html.php | 2 +- escpos-netprinter.py | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0e282f0..bc86fac 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ ESC/POS virtual network printer This is a container-based ESC/POS network printer, that transforms the printed material in HTML pages and makes them avaliable in a web interface. +The printer emulates a 80mm roll of paper. + ## Limits This docker image is not to be exposed on a public network (see [known issues](#known-issues)) @@ -19,18 +21,21 @@ To install from source: wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/heads/master.zip unzip master.zip cd escpos-netprinter-master -docker build -t escpos-netprinter:beta . +docker build -t escpos-netprinter:2.0-beta . ``` To run the resulting container: ```bash -docker run -d --rm --cpus 1.8 -p 515:515/tcp -p 631:631/tcp -p 80:80/tcp -p 9100:9100/tcp escpos-netprinter:beta +docker run -d --rm --name escpos_netprinter -p 515:515/tcp -p 80:80/tcp -p 9100:9100/tcp escpos-netprinter:2.0-beta ``` It should now accept prints by JetDirect on the default port(9100) and by lpd on the default port(515), and you can visualize it with the web application at port 80. +For debugging, you can add port 631 to access the CUPS interface. The CUPS administrative username is `cupsadmin` and the password is `123456`. + +As of version 2.0, this has been tested to work with a regular POS program without adapting it. ## Known issues This is still beta software for now, so it has known major defects: - It still uses the Flask development server, so it is unsafe for public networks. - The conversion to HTML does not do QR codes correctly (see the 1.0-beta.2 release notes) -- ~~This still has not been tested with success with a regular POS program (like the [Epson utilities](https://download.epson-biz.com/modules/pos/))~~ It works with simpler drivers, for example for the MUNBYN ITPP047 printers. +- While it works with simple drivers, for example for the MUNBYN ITPP047 printers, the [Epson utilities](https://download.epson-biz.com/modules/pos/) refuse to speak to it. diff --git a/esc2html.php b/esc2html.php index 341c2a7..58aae02 100644 --- a/esc2html.php +++ b/esc2html.php @@ -149,7 +149,7 @@ $imgSrc = 'data:image/jpeg;base64,' . $imageData; } $qrcodeData = Code2dStatestorage::NO_DATA_ERROR; - $outp[] = "\"$qrcodeData\""; + $outp[] = "
    \"$qrcodeData\"
    "; } else { $qrcodeData = $code2dStorage->getQRCodeData(); diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 57d4f36..d9fc54b 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -280,6 +280,8 @@ def launchPrintServer(printServ:ESCPOSServer): debugmode = getenv('FLASK_RUN_DEBUG', "false") printPort = getenv('PRINTER_PORT', '9100') + print("Starting ESCPOS-netprinter", flush=True) + #Lancer le service d'impression TCP with ESCPOSServer((host, int(printPort)), ESCPOSHandler) as printServer: t = threading.Thread(target=launchPrintServer, args=[printServer]) From c58c63c075c3d179164c4acc83a1308a6cf876ee Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Wed, 17 Jan 2024 10:21:34 -0500 Subject: [PATCH 063/105] Consolidate esc2html logs for all sources --- escpos-netprinter.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/escpos-netprinter.py b/escpos-netprinter.py index d9fc54b..cc1c82c 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -97,15 +97,29 @@ def handle(self): def print_toHTML(self, binfile:BufferedWriter, bin_filename:PurePath): print("Printing ", binfile.name) - recu:CompletedProcess = subprocess.run(["php", "esc2html.php", bin_filename.as_posix()], capture_output=True, text=True ) - if recu.returncode != 0: - print(f"Error while converting receipt: {recu.returncode}") + try: + recu:CompletedProcess = subprocess.run(["php", "esc2html.php", bin_filename.as_posix()], capture_output=True, text=True, check=True) + + except subprocess.CalledProcessError as err: + print(f"Error while converting receipt: {err.returncode}") + # append the error output to the log file + with open(PurePath('web','tmp', 'esc2html_log'), mode='at') as log: + log.write(f"Error while converting a JetDirect print: {err.returncode}") + log.write(datetime.now(tz=ZoneInfo("Canada/Eastern")).strftime('%Y%b%d %X.%f %Z')) + log.write(err.stderr) + log.close() + print("Error output:") - print(recu.stderr, flush=True) + print(err.stderr, flush=True) else: #Si la conversion s'est bien passée, on devrait avoir le HTML print (f"Receipt decoded", flush=True) + with open(PurePath('web','tmp', 'esc2html_log'), mode='at') as log: + log.write("Successful JetDirect print") + log.write(datetime.now(tz=ZoneInfo("Canada/Eastern")).strftime('%Y%b%d %X.%f %Z')) + log.write(recu.stderr) + log.close() #print(recu.stdout, flush=True) #Ajouter un titre au reçu @@ -251,6 +265,7 @@ def publish_receipt_from_CUPS(): # print(logfile_filename) log = open(PurePath('web','tmp', 'esc2html_log'), mode='at') source_log = open(source_dir.joinpath(logfile_filename), mode='rt') + log.write(f"CUPS print received at {datetime.now(tz=ZoneInfo("Canada/Eastern")).strftime('%Y%b%d %X.%f %Z')}") log.write(source_log.read()) log.close() #remove the contents from the source log From fa943d796be84cd1c0785f3c0a1598949d5c559b Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Wed, 17 Jan 2024 10:21:34 -0500 Subject: [PATCH 064/105] Consolidate esc2html logs for all sources --- escpos-netprinter.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/escpos-netprinter.py b/escpos-netprinter.py index d9fc54b..a41dc99 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -97,15 +97,29 @@ def handle(self): def print_toHTML(self, binfile:BufferedWriter, bin_filename:PurePath): print("Printing ", binfile.name) - recu:CompletedProcess = subprocess.run(["php", "esc2html.php", bin_filename.as_posix()], capture_output=True, text=True ) - if recu.returncode != 0: - print(f"Error while converting receipt: {recu.returncode}") + try: + recu:CompletedProcess = subprocess.run(["php", "esc2html.php", bin_filename.as_posix()], capture_output=True, text=True, check=True) + + except subprocess.CalledProcessError as err: + print(f"Error while converting receipt: {err.returncode}") + # append the error output to the log file + with open(PurePath('web','tmp', 'esc2html_log'), mode='at') as log: + log.write(f"Error while converting a JetDirect print: {err.returncode}") + log.write(datetime.now(tz=ZoneInfo("Canada/Eastern")).strftime('%Y%b%d %X.%f %Z')) + log.write(err.stderr) + log.close() + print("Error output:") - print(recu.stderr, flush=True) + print(err.stderr, flush=True) else: #Si la conversion s'est bien passée, on devrait avoir le HTML print (f"Receipt decoded", flush=True) + with open(PurePath('web','tmp', 'esc2html_log'), mode='at') as log: + log.write("Successful JetDirect print") + log.write(datetime.now(tz=ZoneInfo("Canada/Eastern")).strftime('%Y%b%d %X.%f %Z')) + log.write(recu.stderr) + log.close() #print(recu.stdout, flush=True) #Ajouter un titre au reçu @@ -251,6 +265,7 @@ def publish_receipt_from_CUPS(): # print(logfile_filename) log = open(PurePath('web','tmp', 'esc2html_log'), mode='at') source_log = open(source_dir.joinpath(logfile_filename), mode='rt') + log.write(f"CUPS print received at {datetime.now(tz=ZoneInfo('Canada/Eastern')).strftime('%Y%b%d %X.%f %Z')}") log.write(source_log.read()) log.close() #remove the contents from the source log From 13d7c4d8235f94dd0f9f06aeca0a1659a0932379 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Wed, 17 Jan 2024 13:51:46 -0500 Subject: [PATCH 065/105] Add debugging for JetDirect --- dockerfile | 6 +++--- escpos-netprinter.py | 27 +++++++++++++++------------ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/dockerfile b/dockerfile index 7e58b16..f28de67 100644 --- a/dockerfile +++ b/dockerfile @@ -55,6 +55,8 @@ ENV PRINTER_PORT=9100 # To activate the Flask debug mode, set at True (case-sensitive) ENV FLASK_RUN_DEBUG=false +# To activate the netprinter debug mode, set at True (case-sensitive) +ENV ESCPOS_DEBUG=True EXPOSE ${PRINTER_PORT} EXPOSE ${FLASK_RUN_PORT} @@ -63,7 +65,5 @@ EXPOSE 515 #Expose the CUPS admin port (temporary?) EXPOSE 631 -# Démarrer le serveur Flask et le serveur d'impression -#CMD ["/usr/sbin/cupsd", "-f"] -#CMD python3 ${FLASK_APP} +# Start Flask and all printing services CMD ["/bin/bash","-c","./start.sh"] diff --git a/escpos-netprinter.py b/escpos-netprinter.py index a41dc99..af1a7c3 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -34,6 +34,7 @@ class ESCPOSHandler(socketserver.StreamRequestHandler): # Receive the print data and dump it in a file. def handle(self): print (f"Address connected: {self.client_address}", flush=True) + netprinter_debugmode = getenv('ESCPOS_DEBUG', "false") bin_filename = PurePath('web', 'tmp', "reception.bin") binfile = open(bin_filename, "wb") @@ -45,9 +46,10 @@ def handle(self): indata_handshake = self.rfile.read(8) #Read the first 8 bytes print(f"{len(indata_handshake)} bytes received.") - print("-----start of data-----", flush=True) - print(indata_handshake, flush=True) - print("-----end of data-----", flush=True) + if netprinter_debugmode == 'True': + print("-----start of data-----", flush=True) + print(indata_handshake, flush=True) + print("-----end of data-----", flush=True) if(indata_handshake == b'\x1b\x40\x1b\x3d\x01\x10\x04\x01'): self.wfile.write(b'\x16') self.wfile.flush() @@ -63,9 +65,10 @@ def handle(self): self.connection.close() if len(indata) > 0: print(f"{len(indata)} bytes received.") - print("-----start of data-----", flush=True) - print(indata, flush=True) - print("-----end of data-----", flush=True) + if netprinter_debugmode == 'True': + print("-----start of data-----", flush=True) + print(indata, flush=True) + print("-----end of data-----", flush=True) else: print("No data received!", flush=True) @@ -73,10 +76,10 @@ def handle(self): else: print(f"{len(indata)} bytes received.", flush=True) - #Debug: afficher les données reçues - print("-----start of data-----", flush=True) - print(indata, flush=True) - print("-----end of data-----", flush=True) + if netprinter_debugmode == 'True': + print("-----start of data-----", flush=True) + print(indata, flush=True) + print("-----end of data-----", flush=True) #Écrire les données reçues dans le fichier. binfile.write(indata) @@ -292,7 +295,7 @@ def launchPrintServer(printServ:ESCPOSServer): #Obtenir les variables d'environnement host = getenv('FLASK_RUN_HOST', '0.0.0.0') #By default, listen to all source addresses port = getenv('FLASK_RUN_PORT', '5000') - debugmode = getenv('FLASK_RUN_DEBUG', "false") + flask_debugmode = getenv('FLASK_RUN_DEBUG', "false") printPort = getenv('PRINTER_PORT', '9100') print("Starting ESCPOS-netprinter", flush=True) @@ -304,7 +307,7 @@ def launchPrintServer(printServ:ESCPOSServer): t.start() #Lancer l'application Flask - if debugmode == 'True': + if flask_debugmode == 'True': startDebug:bool = True else: startDebug:bool = False From de9acf0385253061f646152c1351ae25f01316b4 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Wed, 17 Jan 2024 16:08:29 -0500 Subject: [PATCH 066/105] QR code parsing corrected. Parsing now interprets DLE codes as null-commands --- esc2html.php | 2 +- src/Parser/Command/Code2DDataCmd.php | 4 +-- src/Parser/Command/CommandFiveArgs.php | 34 +++++++++++++++++++++++++ src/Parser/Command/Printout.php | 13 ++++++++-- src/Parser/Command/QRCodeSubCommand.php | 10 +++++++- 5 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 src/Parser/Command/CommandFiveArgs.php diff --git a/esc2html.php b/esc2html.php index 58aae02..f7a73b9 100644 --- a/esc2html.php +++ b/esc2html.php @@ -116,7 +116,7 @@ error_log("Subcommand ". get_class($sub) ."", 0); //Output the subcommand class in the debug console error_log("Function " . $sub->get_fn() ."",0); error_log("Data size:". $sub->getDataSize() ."",0); - error_log("Data: '" . $sub->get_data() ."",0); + error_log("Data: " . $sub->get_data() ."",0); } if($sub->isAvailableAs('QRcodeSubCommand')){ switch ($sub->get_fn()) { diff --git a/src/Parser/Command/Code2DDataCmd.php b/src/Parser/Command/Code2DDataCmd.php index 2982488..6e8a7e6 100644 --- a/src/Parser/Command/Code2DDataCmd.php +++ b/src/Parser/Command/Code2DDataCmd.php @@ -33,8 +33,8 @@ public function addChar($char) } elseif ($this -> pH === null){ $this -> pH = ord($char); - //Calculate the length of fn+[parameters] - the spec starts counting AFTER cn - $this->dataSize = $this->pL + $this->pH * 256; + //Calculate the length of fn+[parameters] - the spec counts cn, so we remove its size + $this->dataSize = ($this->pL + $this->pH * 256) - 1; return true; } //Now interpret the subcommand diff --git a/src/Parser/Command/CommandFiveArgs.php b/src/Parser/Command/CommandFiveArgs.php new file mode 100644 index 0000000..8b0c54c --- /dev/null +++ b/src/Parser/Command/CommandFiveArgs.php @@ -0,0 +1,34 @@ + arg1 === null) { + $this -> arg1 = ord($char); + return true; + } elseif ($this -> arg2 === null) { + $this -> arg2 = ord($char); + return true; + } elseif ($this -> arg3 === null) { + $this -> arg3 = ord($char); + return true; + } elseif ($this -> arg4 === null) { + $this -> arg4 = ord($char); + return true; + } elseif ($this -> arg5 === null) { + $this -> arg5 = ord($char); + return true; + } + return false; + } +} diff --git a/src/Parser/Command/Printout.php b/src/Parser/Command/Printout.php index cdc67a2..d0a96f9 100755 --- a/src/Parser/Command/Printout.php +++ b/src/Parser/Command/Printout.php @@ -93,8 +93,17 @@ class Printout extends Command '.' => 'CancelKanjiCharacterMode', 'C' => 'SelectKanjiCharacterCode' ), - DLE => array( - + DLE => array( //DLE groups "real-time" commands like "sound buzzer" or "feed paper". + //Since none of those change the printed result, we parse them as generics. + '\x04' => 'CommandTwoArgs', //EOT + '\x05' => 'CommandOneArg', //ENQ + '\x14' => array( //DC4 + '\x01' => 'CommandTwoArgs', //Generate pulse + '\x02' => 'CommandTwoArgs', //Printer power-off + '\x03' => 'CommandFiveArgs', //Real-time buzzer + '\x07' => 'CommandOneArg', //Real-time status transmission + '\x08\x01\x03\x14\x01\x06\x02' => 'CommandOneArg' //Clear buffers. NOTE the only possible arg here is '\x08' + ), ), CAN => 'CancelCmd' ); diff --git a/src/Parser/Command/QRCodeSubCommand.php b/src/Parser/Command/QRCodeSubCommand.php index 1701644..17a38ee 100644 --- a/src/Parser/Command/QRCodeSubCommand.php +++ b/src/Parser/Command/QRCodeSubCommand.php @@ -8,10 +8,11 @@ class QRcodeSubCommand extends Code2DSubCommand { private $fn = null; + private $m = null; public function __construct($dataSize) { - $this->dataSize = $dataSize - 1; //$dataSize is the size of [parameters], so we exclude the fn byte + $this->dataSize = $dataSize; //$dataSize is the size of fn+[parameters], so we exclude the fn byte } public function addChar($char) @@ -19,8 +20,15 @@ public function addChar($char) if ($this->fn === null){ //First extract the QR function $this -> fn = ord($char); + $this->dataSize = $this->dataSize - 1; //subtract the size of fn from the size of the QR contents return true; } + elseif ($this->fn == 80 && $this->m === null){ + //If this is the data storage function, extract the m value + $this->m = ord($char); + $this->dataSize = $this->dataSize - 1; //subtract the size of m from the size of the QR contents + return true; + } else{ //then send [parameters] into $data return parent::addChar($char); From cfdfcfe1aac2792c1dbb611c05f8affeaa285a90 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Thu, 18 Jan 2024 16:20:33 -0500 Subject: [PATCH 067/105] QR code parsing AND rendering works Moved to the 5.0 release of php-qrcode --- composer.json | 2 +- esc2html.php | 15 ++------------- src/Parser/Context/Code2DStateStorage.php | 10 ++++------ 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index f86821e..738f38b 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "require" : { "squizlabs/php_codesniffer" : "^2.8", "mike42/escpos-php": "^1.5", - "chillerlan/php-qrcode": "5.0-beta" + "chillerlan/php-qrcode": "^5.0" }, "autoload" : { "psr-4" : { diff --git a/esc2html.php b/esc2html.php index f7a73b9..2d264f7 100644 --- a/esc2html.php +++ b/esc2html.php @@ -133,7 +133,7 @@ $code2dStorage->fillSymbolStorage($sub->get_data()); break; case 81: //Print the QR code - $qrcodeURI = $code2dStorage->getQRCodeURI(); + $qrcodeURI = $code2dStorage->getQRCodeBase64URI(); if ($qrcodeURI == Code2DStatestorage::NO_DATA_ERROR){ error_log("Warning: QR code print ordered before contents stored.",0); @@ -153,7 +153,7 @@ } else { $qrcodeData = $code2dStorage->getQRCodeData(); - $outp[] = qrCodeAsDataUrl($qrcodeURI, $qrcodeData); + $outp[] = "
    \"$qrcodeData\"
    "; } break; @@ -196,17 +196,6 @@ function imgAsDataUrl($bufferedImg) return "\"$imgAlt\""; } -/* Creates the HTML image of a QR code. -The image is encased in a div which is used to center the image. - Args: - bufferedQRImg: the Base64 encoded PNG image of the QR code - qrcodeData: the data encoded in the QR code, to be put in the alt tag as an accessibility measure */ -function qrCodeAsDataUrl($bufferedQRImg, $qrcodeData) -{ - $imgSrc = "data:image/png;base64," . $bufferedQRImg; - return "
    \"$qrcodeData\"
    "; -} - function wrapInline($tag, $closeTag, $content) { return $tag . $content . $closeTag; diff --git a/src/Parser/Context/Code2DStateStorage.php b/src/Parser/Context/Code2DStateStorage.php index 0bd5225..d5d2638 100644 --- a/src/Parser/Context/Code2DStateStorage.php +++ b/src/Parser/Context/Code2DStateStorage.php @@ -170,15 +170,13 @@ public function printQRCodeStateInfo(){ } //To implement GS ( k , this outputs the QR code in png format as a base64 URI string - public function getQRCodeURI(){ + public function getQRCodeBase64URI(){ // chillerlan/PHP-QRCode version // this library can only output Model 2 QR codes, but we will abuse it by setting the version if(strlen($this->symbolStorage) > 0){ // 1) set the options $qroptions = new QROptions; - $qroptions->outputType = 'imagick'; - $qroptions->imagickFormat = 'png32'; // e.g. png32, jpeg, webp $qroptions->quality = 100; $qroptions->outputBase64 = true; //To make render() output the URI string directly @@ -201,10 +199,10 @@ public function getQRCodeURI(){ break; } // 2) generate the QR - $qroutput = (new QRCode($qroptions))->render($this->symbolStorage); + $qrCodeBase64 = (new QRCode($qroptions))->render($this->symbolStorage); - // 3) retourner l'URI contenant l'image PNG du code avec imagick - return $qroutput; + // 3) return the base64 URI for the rendered QR code + return $qrCodeBase64; } else { return self::NO_DATA_ERROR; From 341d748c55592a1f4eb4bf273d4d45c9ae460ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-L=C3=A9onard=20Gilbert?= <83510612+gilbertfl@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:27:12 -0500 Subject: [PATCH 068/105] Update README.md --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bc86fac..afbcf18 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ESC/POS virtual network printer ---------- -This is a container-based ESC/POS network printer, that transforms the printed material in HTML pages and makes them avaliable in a web interface. +This is a container-based ESC/POS network printer, that replaces paper rolls with HTML pages and a web interface. The printer emulates a 80mm roll of paper. @@ -21,12 +21,12 @@ To install from source: wget --show-progress https://github.com/gilbertfl/escpos-netprinter/archive/refs/heads/master.zip unzip master.zip cd escpos-netprinter-master -docker build -t escpos-netprinter:2.0-beta . +docker build -t escpos-netprinter:2.0 . ``` To run the resulting container: ```bash -docker run -d --rm --name escpos_netprinter -p 515:515/tcp -p 80:80/tcp -p 9100:9100/tcp escpos-netprinter:2.0-beta +docker run -d --rm --name escpos_netprinter -p 515:515/tcp -p 80:80/tcp -p 9100:9100/tcp escpos-netprinter:2.0 ``` It should now accept prints by JetDirect on the default port(9100) and by lpd on the default port(515), and you can visualize it with the web application at port 80. For debugging, you can add port 631 to access the CUPS interface. The CUPS administrative username is `cupsadmin` and the password is `123456`. @@ -34,8 +34,7 @@ For debugging, you can add port 631 to access the CUPS interface. The CUPS adm As of version 2.0, this has been tested to work with a regular POS program without adapting it. ## Known issues -This is still beta software for now, so it has known major defects: +While version 2.0 is no longer a beta version, it has known defects: - It still uses the Flask development server, so it is unsafe for public networks. -- The conversion to HTML does not do QR codes correctly (see the 1.0-beta.2 release notes) -- While it works with simple drivers, for example for the MUNBYN ITPP047 printers, the [Epson utilities](https://download.epson-biz.com/modules/pos/) refuse to speak to it. +- While it works with simple drivers, for example the one for the MUNBYN ITPP047 printers, the [Epson utilities](https://download.epson-biz.com/modules/pos/) refuse to speak to it. From c4526121dbb4c6dc4d6efa4b07a9aeb9e4382da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-L=C3=A9onard=20Gilbert?= <83510612+gilbertfl@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:40:17 -0500 Subject: [PATCH 069/105] Change dockerfile to set debugging to false --- dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfile b/dockerfile index f28de67..2daaa1c 100644 --- a/dockerfile +++ b/dockerfile @@ -56,7 +56,7 @@ ENV PRINTER_PORT=9100 # To activate the Flask debug mode, set at True (case-sensitive) ENV FLASK_RUN_DEBUG=false # To activate the netprinter debug mode, set at True (case-sensitive) -ENV ESCPOS_DEBUG=True +ENV ESCPOS_DEBUG=false EXPOSE ${PRINTER_PORT} EXPOSE ${FLASK_RUN_PORT} From 6da83abf36cea78524d2a2ef16e4d2bb472b06b5 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 22 Jan 2024 14:24:11 -0500 Subject: [PATCH 070/105] Explicitly make the entrypoint executable. --- dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/dockerfile b/dockerfile index 2daaa1c..7ab42ee 100644 --- a/dockerfile +++ b/dockerfile @@ -30,6 +30,7 @@ ENV DEVICE_URI=esc2file:/${DEST_FILENAME}/${LOG_FILENAME} #Installation de l'émulateur d'imprimante #Note: utiliser "." au lieu de * permet de garder la structure et envoyer tous les sous-répertoires ADD . /home/escpos-emu/ +RUN chmod +x start.sh RUN rm -rf web ADD --chmod=0555 cups/esc2file.sh /usr/lib/cups/backend/esc2file WORKDIR /home/escpos-emu/ From b692139df0ed5740468f3a56ffb091f466644123 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 22 Jan 2024 14:24:11 -0500 Subject: [PATCH 071/105] Explicitly make the entrypoint executable. --- dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/dockerfile b/dockerfile index 2daaa1c..90503f0 100644 --- a/dockerfile +++ b/dockerfile @@ -30,6 +30,7 @@ ENV DEVICE_URI=esc2file:/${DEST_FILENAME}/${LOG_FILENAME} #Installation de l'émulateur d'imprimante #Note: utiliser "." au lieu de * permet de garder la structure et envoyer tous les sous-répertoires ADD . /home/escpos-emu/ +ADD --chmod=0555 ./start.sh /home/escpos-emu/start.sh RUN rm -rf web ADD --chmod=0555 cups/esc2file.sh /usr/lib/cups/backend/esc2file WORKDIR /home/escpos-emu/ From 02255e9ce7ebe72a46f4d0d8be1cc4e6e0053ab8 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 22 Jan 2024 16:38:12 -0500 Subject: [PATCH 072/105] Add some debugging and deal with empty prints. Solve syntax errors when decoding DLE commands --- escpos-netprinter.py | 106 +++++++++++++++++--------------- src/Parser/Command/Printout.php | 16 ++--- 2 files changed, 64 insertions(+), 58 deletions(-) diff --git a/escpos-netprinter.py b/escpos-netprinter.py index af1a7c3..f2e24bb 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -36,65 +36,71 @@ def handle(self): print (f"Address connected: {self.client_address}", flush=True) netprinter_debugmode = getenv('ESCPOS_DEBUG', "false") bin_filename = PurePath('web', 'tmp', "reception.bin") - binfile = open(bin_filename, "wb") + with open(bin_filename, "wb") as binfile: - #Read everything until we get EOF - indata:bytes = b'' - try: - # Read the handshake first to signal that we are a printer if needed - # NOTE: since this is an undocumented feature, we will keep going even if we don't get the handshake - - indata_handshake = self.rfile.read(8) #Read the first 8 bytes - print(f"{len(indata_handshake)} bytes received.") - if netprinter_debugmode == 'True': - print("-----start of data-----", flush=True) - print(indata_handshake, flush=True) - print("-----end of data-----", flush=True) - if(indata_handshake == b'\x1b\x40\x1b\x3d\x01\x10\x04\x01'): - self.wfile.write(b'\x16') - self.wfile.flush() - print("Handshake done", flush=True) - - #Read the rest of the data and append it to the handshake - after_handshake = self.rfile.read() - indata = indata_handshake + after_handshake - - - except TimeoutError: - print("Timeout while reading") - self.connection.close() - if len(indata) > 0: - print(f"{len(indata)} bytes received.") + #Read everything until we get EOF + indata:bytes = b'' + after_handshake:bytes = b'' + try: + # Read the handshake first to signal that we are a printer if needed + # NOTE: since this is an undocumented feature, we will keep going even if we don't get the handshake + + indata_handshake = self.rfile.read(8) #Read the first 8 bytes + print(f"{len(indata_handshake)} bytes received.") if netprinter_debugmode == 'True': print("-----start of data-----", flush=True) - print(indata, flush=True) + print(indata_handshake, flush=True) print("-----end of data-----", flush=True) - else: - print("No data received!", flush=True) - + if(indata_handshake == b'\x1b\x40\x1b\x3d\x01\x10\x04\x01'): + self.wfile.write(b'\x16') + self.wfile.flush() + print("Handshake done", flush=True) + + #Read the rest of the data and append it to the handshake + after_handshake = self.rfile.read() + indata = indata_handshake + after_handshake + + + except TimeoutError: + print("Timeout while reading") + self.connection.close() + if len(indata) > 0: + print(f"{len(indata)} bytes received.") + if netprinter_debugmode == 'True': + print("-----start of data-----", flush=True) + print(indata, flush=True) + print("-----end of data-----", flush=True) + else: + print("No data received!", flush=True) - else: - print(f"{len(indata)} bytes received.", flush=True) + + else: + #Quand on a reçu le signal de fin de transmission + print(f"{len(indata)} bytes received.", flush=True) - if netprinter_debugmode == 'True': - print("-----start of data-----", flush=True) - print(indata, flush=True) - print("-----end of data-----", flush=True) + if netprinter_debugmode == 'True': + print("-----start of complete data-----", flush=True) + print(indata, flush=True) + print("-----end of data-----", flush=True) - #Écrire les données reçues dans le fichier. - binfile.write(indata) + #Écrire les données reçues dans le fichier, sauf si on a seulement eu le handshake. + if len(after_handshake) > 0: + binfile.write(indata) + binfile.close() #Écrire le fichier et le fermer + #traiter le fichier reception.bin pour en faire un HTML + self.print_toHTML(binfile, bin_filename) + elif netprinter_debugmode == 'True': + print("Nothing after the handshake: nothing will be printed.", flush=True) - #Quand on a reçu le signal de fin de transmission - binfile.close() #Écrire le fichier et le fermer + #The binfile should auto-close here. - self.wfile.write(b"ESCPOS-netprinter: All done!") #A enlever plus tard? On dit au client qu'on a fini. - self.wfile.flush() - self.connection.close() + self.wfile.write(b"ESCPOS-netprinter: All done!") #A enlever plus tard? On dit au client qu'on a fini. + self.wfile.flush() + self.connection.close() - print ("Data received, signature sent.", flush=True) + print ("Data received, signature sent.", flush=True) + - #traiter le fichier reception.bin pour en faire un HTML - self.print_toHTML(binfile, bin_filename) #Convertir l'impression recue en HTML et la rendre disponible à Flask def print_toHTML(self, binfile:BufferedWriter, bin_filename:PurePath): @@ -119,8 +125,8 @@ def print_toHTML(self, binfile:BufferedWriter, bin_filename:PurePath): #Si la conversion s'est bien passée, on devrait avoir le HTML print (f"Receipt decoded", flush=True) with open(PurePath('web','tmp', 'esc2html_log'), mode='at') as log: - log.write("Successful JetDirect print") - log.write(datetime.now(tz=ZoneInfo("Canada/Eastern")).strftime('%Y%b%d %X.%f %Z')) + log.write("Successful JetDirect print\n") + log.write(datetime.now(tz=ZoneInfo("Canada/Eastern")).strftime('%Y%b%d %X.%f %Z\n\n')) log.write(recu.stderr) log.close() #print(recu.stdout, flush=True) diff --git a/src/Parser/Command/Printout.php b/src/Parser/Command/Printout.php index d0a96f9..78d7247 100755 --- a/src/Parser/Command/Printout.php +++ b/src/Parser/Command/Printout.php @@ -95,14 +95,14 @@ class Printout extends Command ), DLE => array( //DLE groups "real-time" commands like "sound buzzer" or "feed paper". //Since none of those change the printed result, we parse them as generics. - '\x04' => 'CommandTwoArgs', //EOT - '\x05' => 'CommandOneArg', //ENQ - '\x14' => array( //DC4 - '\x01' => 'CommandTwoArgs', //Generate pulse - '\x02' => 'CommandTwoArgs', //Printer power-off - '\x03' => 'CommandFiveArgs', //Real-time buzzer - '\x07' => 'CommandOneArg', //Real-time status transmission - '\x08\x01\x03\x14\x01\x06\x02' => 'CommandOneArg' //Clear buffers. NOTE the only possible arg here is '\x08' + "\x04" => 'CommandTwoArgs', //EOT + "\x05" => 'CommandOneArg', //ENQ + "\x14" => array( //DC4 + "\x01" => 'CommandTwoArgs', //Generate pulse + "\x02" => 'CommandTwoArgs', //Printer power-off + "\x03" => 'CommandFiveArgs', //Real-time buzzer + "\x07" => 'CommandOneArg', //Real-time status transmission + "\x08\x01\x03\x14\x01\x06\x02" => 'CommandOneArg' //Clear buffers. NOTE the only possible arg here is '\x08' ), ), CAN => 'CancelCmd' From 2303d07b4bf96930d22bbd9437294c042a8df561 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Wed, 24 Jan 2024 13:42:13 -0500 Subject: [PATCH 073/105] Correct PSR-4 case-sensitivity issue. --- esc2html.php | 2 +- src/Parser/Command/Code2DDataCmd.php | 4 ++-- src/Parser/Command/QRCodeSubCommand.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esc2html.php b/esc2html.php index 2d264f7..da2dd1a 100644 --- a/esc2html.php +++ b/esc2html.php @@ -118,7 +118,7 @@ error_log("Data size:". $sub->getDataSize() ."",0); error_log("Data: " . $sub->get_data() ."",0); } - if($sub->isAvailableAs('QRcodeSubCommand')){ + if($sub->isAvailableAs('QRCodeSubCommand')){ switch ($sub->get_fn()) { case 65: //set model $code2dStorage->setQRModel($sub->get_data()); diff --git a/src/Parser/Command/Code2DDataCmd.php b/src/Parser/Command/Code2DDataCmd.php index 6e8a7e6..9310a48 100644 --- a/src/Parser/Command/Code2DDataCmd.php +++ b/src/Parser/Command/Code2DDataCmd.php @@ -3,7 +3,7 @@ use ReceiptPrintHq\EscposTools\Parser\Command\Code2DSubCommand; use ReceiptPrintHq\EscposTools\Parser\Command\DataCmd; -use ReceiptPrintHq\EscposTools\Parser\Command\QRcodeSubCommand; +use ReceiptPrintHq\EscposTools\Parser\Command\QRCodeSubCommand; use ReceiptPrintHq\EscposTools\Parser\Command\UnimplementedCode2DSubCommand; // This interprets the "GS ( k" commands. @@ -48,7 +48,7 @@ public function addChar($char) } elseif($this->cn == 49){ //this is a QR code command - $this->subCommand = new QRcodeSubCommand($this->dataSize); + $this->subCommand = new QRCodeSubCommand($this->dataSize); //$this->subCommand = new UnimplementedCode2DSubCommand($this->dataSize) ; } elseif($this->cn >= 50 && $this->cn <= 54) { diff --git a/src/Parser/Command/QRCodeSubCommand.php b/src/Parser/Command/QRCodeSubCommand.php index 17a38ee..827c0e9 100644 --- a/src/Parser/Command/QRCodeSubCommand.php +++ b/src/Parser/Command/QRCodeSubCommand.php @@ -4,7 +4,7 @@ use ReceiptPrintHq\EscposTools\Parser\Command\Code2DSubCommand; -class QRcodeSubCommand extends Code2DSubCommand +class QRCodeSubCommand extends Code2DSubCommand { private $fn = null; From f29797f229e29afe4065695fd64204fdb7597336 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Wed, 24 Jan 2024 13:47:24 -0500 Subject: [PATCH 074/105] Sidestep permission problems by copying the whole CUPS log every time instead of append-then-truncate-source. --- escpos-netprinter.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/escpos-netprinter.py b/escpos-netprinter.py index f2e24bb..5ccecfa 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -272,15 +272,11 @@ def publish_receipt_from_CUPS(): #Load the log file from /var/spool/cups/tmp/ and append it in web/tmp/esc2html_log logfile_filename = os.environ['LOG_FILENAME'] # print(logfile_filename) - log = open(PurePath('web','tmp', 'esc2html_log'), mode='at') + log = open(PurePath('web','tmp', 'esc2html_log'), mode='wt') source_log = open(source_dir.joinpath(logfile_filename), mode='rt') log.write(f"CUPS print received at {datetime.now(tz=ZoneInfo('Canada/Eastern')).strftime('%Y%b%d %X.%f %Z')}") log.write(source_log.read()) log.close() - #remove the contents from the source log - source_log.close() - source_log = open(source_dir.joinpath('esc2html_log'), mode='wt') - source_log.write('') source_log.close() #send an http acknowledgement From 047b551bde42fd486281cd5d1b9c9715e15ba342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-L=C3=A9onard=20Gilbert?= <83510612+gilbertfl@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:59:43 -0500 Subject: [PATCH 075/105] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index afbcf18..027cc3a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ This is a container-based ESC/POS network printer, that replaces paper rolls wit The printer emulates a 80mm roll of paper. +![sample print](https://github.com/gilbertfl/escpos-netprinter/assets/83510612/8aefc8c5-01ab-45f3-a992-e2850bef70f6) + ## Limits This docker image is not to be exposed on a public network (see [known issues](#known-issues)) From ee2aa8dc1702550a26f98be34ecabe4bd975657d Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Mon, 29 Jan 2024 12:01:36 -0500 Subject: [PATCH 076/105] Ensure that ESCPOS_DEBUG enables debugging logs for all parts of the netprinter. --- cups/esc2file.sh | 29 +++++++++++++++++++++-------- dockerfile | 12 ++++++------ esc2html.php | 8 ++++---- escpos-netprinter.py | 18 +++++++++++------- 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/cups/esc2file.sh b/cups/esc2file.sh index abd0b8e..5467559 100644 --- a/cups/esc2file.sh +++ b/cups/esc2file.sh @@ -43,15 +43,20 @@ echo "NOTICE: processing Job ${jobid}" 1>&2 #echo "DEBUG: This is a \"debug\" level log message" 1>&2 -# we will read the output filename from the printers $DEVICE_URI environment -# variable that should look like "esc2file:/dest_filename.suffix/log_filename.suffix" +# We will read the output filename from the printers and set the debug mode from the $DEVICE_URI +# environment variable that should look like "esc2file:/dest_filename.suffix/log_filename.suffix/isdebug" # Extract the file names from the DEVICE_URI FILENAMES=${DEVICE_URI#esc2file:/} -DEST_FILENAME=${FILENAMES%/*} -LOG_FILENAME=${FILENAMES#*/} +DEST_FILENAME=${FILENAMES%/*/*} +TEMP=${FILENAMES%/*} +LOG_FILENAME=${TEMP#*/} +# Extract the debug flag from the DEVICE_URI +ESCPOS_DEBUG=${FILENAMES##*/} + echo "DEBUG: DEST_FILENAME=${DEST_FILENAME}" 1>&2 echo "DEBUG: LOG_FILENAME=${LOG_FILENAME}" 1>&2 +echo "esc2file - Debug mode: ${ESCPOS_DEBUG}" 1>${TMPDIR}/${LOG_FILENAME} # Now do the real work: case ${#} in @@ -71,8 +76,12 @@ case ${#} in echo "ERROR: Cannot write to ${TMPDIR}/receipt.bin" 1>&2 exit 51 #Send an error to CUPS to signal printing failure else - #/usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}/receipt.bin 1>${TMPDIR}/esc2html.html 2>>${TMPDIR}/esc2html_log - /usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}/receipt.bin 1>${TMPDIR}/${DEST_FILENAME} 2>>${TMPDIR}/${LOG_FILENAME} + if [ "${ESCPOS_DEBUG}" == "True" ]; then + /usr/local/bin/php /home/escpos-emu/esc2html.php --debug ${TMPDIR}/receipt.bin 1>${TMPDIR}/${DEST_FILENAME} 2>>${TMPDIR}/${LOG_FILENAME} + else + #/usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}/receipt.bin 1>${TMPDIR}/esc2html.html 2>>${TMPDIR}/esc2html_log + /usr/local/bin/php /home/escpos-emu/esc2html.php ${TMPDIR}/receipt.bin 1>${TMPDIR}/${DEST_FILENAME} 2>>${TMPDIR}/${LOG_FILENAME} + fi if [ "$?" -ne "0" ]; then echo "ERROR: Error $? while printing ${TMPDIR}/receipt.bin to ${TMPDIR}/${DEST_FILENAME}" 1>&2 exit 52 #Send an error to CUPS to signal printing failure @@ -82,8 +91,12 @@ case ${#} in 6) # backend needs to read from file if number of arguments is 6 echo "DEBUG: Printing from file ${6}" 1>&2 - #/usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/esc2html.html 2>>${TMPDIR}/esc2html_log - /usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/${DEST_FILENAME} 2>>${TMPDIR}/${LOG_FILENAME} + if [ "${ESCPOS_DEBUG}" == "True" ]; then + /usr/local/bin/php /home/escpos-emu/esc2html.php --debug ${6} 1>${TMPDIR}/${DEST_FILENAME} 2>>${TMPDIR}/${LOG_FILENAME} + else + #/usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/esc2html.html 2>>${TMPDIR}/esc2html_log + /usr/local/bin/php /home/escpos-emu/esc2html.php ${6} 1>${TMPDIR}/${DEST_FILENAME} 2>>${TMPDIR}/${LOG_FILENAME} + fi if [ "$?" -ne "0" ]; then echo "ERROR: Error $? while printing ${6} to ${TMPDIR}/${DEST_FILENAME}" 1>&2 #echo "ERROR: Error $? while printing ${6} to ${TMPDIR}/test.html" 1>&2 diff --git a/dockerfile b/dockerfile index 90503f0..030588f 100644 --- a/dockerfile +++ b/dockerfile @@ -22,11 +22,6 @@ RUN groupadd cups-admins RUN useradd -d /home/escpos-emu -g cups-admins -s /sbin/nologin cupsadmin RUN echo "cupsadmin:123456" | chpasswd -# Compose the "Device URI" for CUPS. -ENV DEST_FILENAME=esc2html.html -ENV LOG_FILENAME=esc2html_log -ENV DEVICE_URI=esc2file:/${DEST_FILENAME}/${LOG_FILENAME} - #Installation de l'émulateur d'imprimante #Note: utiliser "." au lieu de * permet de garder la structure et envoyer tous les sous-répertoires ADD . /home/escpos-emu/ @@ -63,8 +58,13 @@ EXPOSE ${PRINTER_PORT} EXPOSE ${FLASK_RUN_PORT} #Expose the lpd port EXPOSE 515 -#Expose the CUPS admin port (temporary?) +#Expose the CUPS admin port EXPOSE 631 +# Compose the "Device URI" for CUPS "esc2file:/dest_filename.suffix/log_filename.suffix/isdebug" +ENV DEST_FILENAME=esc2html.html +ENV LOG_FILENAME=esc2html_log +ENV DEVICE_URI=esc2file:/${DEST_FILENAME}/${LOG_FILENAME}/${ESCPOS_DEBUG} + # Start Flask and all printing services CMD ["/bin/bash","-c","./start.sh"] diff --git a/esc2html.php b/esc2html.php index da2dd1a..fac61b4 100644 --- a/esc2html.php +++ b/esc2html.php @@ -14,22 +14,22 @@ error_log("esc2html starting", 0); // Usage if ($argc < 2) { - print("Usage: " . $argv[0] . " [--debug] filename \n"."zéro args"); + print("Usage: php " . $argv[0] . " [--debug] filename \n"."zéro args"); exit(1); } else { if ($argv[1]=='--debug'){ $debugMode = true; if (!isset($argv[2])) { - print("Usage: " . $argv[0] . " [--debug] filename ". $argc-1 . " arguments received\n"); + print("Usage: php " . $argv[0] . " [--debug] filename ". $argc-1 . " arguments received\n"); exit(1); } else $targetFilename = $argv[2]; - error_log("Debug mode enabled", 0); + error_log("\nDebug mode enabled\n", 0); } else { //First argument is not '--debug' if(isset($argv[2])) { // But there is at least 2 args - print("Usage: " . $argv[0] . " [--debug] filename \n". $argc-1 . " arguments received\n"); + print("Usage: php " . $argv[0] . " [--debug] filename \n". $argc-1 . " arguments received\n"); exit(1); } else $targetFilename = $argv[1]; //The only argument is the filename. diff --git a/escpos-netprinter.py b/escpos-netprinter.py index 5ccecfa..b6485ff 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -30,11 +30,12 @@ class ESCPOSHandler(socketserver.StreamRequestHandler): TODO: peut-être implémenter certains codes de statut plus tard. Voir l'APG Epson section "Processing the Data Received from the Printer" """ timeout = 10 #On abandonne une réception après 10 secondes - un compromis pour assurer que tout passe sans se bourrer de connections zombies. + netprinter_debugmode = "false" # Receive the print data and dump it in a file. def handle(self): print (f"Address connected: {self.client_address}", flush=True) - netprinter_debugmode = getenv('ESCPOS_DEBUG', "false") + self.netprinter_debugmode = getenv('ESCPOS_DEBUG', "false") bin_filename = PurePath('web', 'tmp', "reception.bin") with open(bin_filename, "wb") as binfile: @@ -47,7 +48,7 @@ def handle(self): indata_handshake = self.rfile.read(8) #Read the first 8 bytes print(f"{len(indata_handshake)} bytes received.") - if netprinter_debugmode == 'True': + if self.netprinter_debugmode == 'True': print("-----start of data-----", flush=True) print(indata_handshake, flush=True) print("-----end of data-----", flush=True) @@ -66,7 +67,7 @@ def handle(self): self.connection.close() if len(indata) > 0: print(f"{len(indata)} bytes received.") - if netprinter_debugmode == 'True': + if self.netprinter_debugmode == 'True': print("-----start of data-----", flush=True) print(indata, flush=True) print("-----end of data-----", flush=True) @@ -78,7 +79,7 @@ def handle(self): #Quand on a reçu le signal de fin de transmission print(f"{len(indata)} bytes received.", flush=True) - if netprinter_debugmode == 'True': + if self.netprinter_debugmode == 'True': print("-----start of complete data-----", flush=True) print(indata, flush=True) print("-----end of data-----", flush=True) @@ -89,7 +90,7 @@ def handle(self): binfile.close() #Écrire le fichier et le fermer #traiter le fichier reception.bin pour en faire un HTML self.print_toHTML(binfile, bin_filename) - elif netprinter_debugmode == 'True': + elif self.netprinter_debugmode == 'True': print("Nothing after the handshake: nothing will be printed.", flush=True) #The binfile should auto-close here. @@ -107,7 +108,10 @@ def print_toHTML(self, binfile:BufferedWriter, bin_filename:PurePath): print("Printing ", binfile.name) try: - recu:CompletedProcess = subprocess.run(["php", "esc2html.php", bin_filename.as_posix()], capture_output=True, text=True, check=True) + if self.netprinter_debugmode == 'True': + recu:CompletedProcess = subprocess.run(["php", "esc2html.php", "--debug", bin_filename.as_posix()], capture_output=True, text=True, check=True) + else: + recu:CompletedProcess = subprocess.run(["php", "esc2html.php", bin_filename.as_posix()], capture_output=True, text=True, check=True) except subprocess.CalledProcessError as err: print(f"Error while converting receipt: {err.returncode}") @@ -274,7 +278,7 @@ def publish_receipt_from_CUPS(): # print(logfile_filename) log = open(PurePath('web','tmp', 'esc2html_log'), mode='wt') source_log = open(source_dir.joinpath(logfile_filename), mode='rt') - log.write(f"CUPS print received at {datetime.now(tz=ZoneInfo('Canada/Eastern')).strftime('%Y%b%d %X.%f %Z')}") + log.write(f"CUPS print received at {datetime.now(tz=ZoneInfo('Canada/Eastern')).strftime('%Y%b%d %X.%f %Z')}\n") log.write(source_log.read()) log.close() source_log.close() From e944ac00d294b3db24f7b1a3455c29afbff8a1e8 Mon Sep 17 00:00:00 2001 From: Francois-Leonard Gilbert Date: Thu, 28 Mar 2024 15:58:53 -0400 Subject: [PATCH 077/105] Prevent footer from overlapping the receipt list by making it sticky with CSS. --- escpos-netprinter.py | 5 +++-- templates/accueil.html.j2 | 27 ++++++++++++++------------- templates/footer.html | 2 +- templates/receiptList.html.j2 | 6 +++--- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/escpos-netprinter.py b/escpos-netprinter.py index b6485ff..ce40882 100644 --- a/escpos-netprinter.py +++ b/escpos-netprinter.py @@ -237,10 +237,11 @@ def show_receipt(fileID:int): # If the ID is not found, return a 404 error return "Not found", 404 - # If the ID is found, open the file to append the footer from templates/footer.html + # If the ID is found, open the html rendering of the receipt and add the footer from templates/footer.html with open(PurePath('web', 'receipts', filename), mode='rt') as receipt: receipt_html = receipt.read() # Read the file content - receipt_html = receipt_html.replace('', render_template('footer.html') + '') # Append the footer + receipt_html = receipt_html.replace('', '
    ') + receipt_html = receipt_html.replace('', '
    ' + render_template('footer.html') + '') # Append the footer return receipt_html diff --git a/templates/accueil.html.j2 b/templates/accueil.html.j2 index dfa65de..4b85020 100644 --- a/templates/accueil.html.j2 +++ b/templates/accueil.html.j2 @@ -3,19 +3,20 @@ Imprimante virtuelle - -

    État de l'imprimante:

    -
      -
    • En ligne
    • -
    • Adresse de cette imprimante: {{host}}
    • -
    • Ports d'impression: {{jetDirectPort}} (Jetdirect), 515 (lpd)
    • - {%if debug == 'True'%} -
    • Mode débogage activé
    • - {%endif%} -
    - - Consulter la liste des reçus imprimés - + +
    +

    État de l'imprimante:

    +
      +
    • En ligne
    • +
    • Adresse de cette imprimante: {{host}}
    • +
    • Ports d'impression: {{jetDirectPort}} (Jetdirect), 515 (lpd)
    • + {%if debug == 'True'%} +
    • Mode débogage activé
    • + {%endif%} +
    + + Consulter la liste des reçus imprimés +
    {% include 'footer.html' %} \ No newline at end of file diff --git a/templates/footer.html b/templates/footer.html index b0e85d2..2d02e20 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -1,6 +1,6 @@ -