12 Commits

Author SHA1 Message Date
n07070
070f037449 Apply formating, light linting 2026-11-13 19:51:32 +01:00
n07070
adb5e9422d Add limits to API calls 2026-11-13 19:48:05 +01:00
n07070
d348acd88c Add Printers information via the API 2026-11-13 19:47:52 +01:00
n07070
e8568795b2 Configuration max text length via config file 2026-11-13 19:47:36 +01:00
n07070
086a7ae640 Make max text length configurable 2026-11-13 19:46:16 +01:00
n07070
836d6b0be9 Update configuration options in the web methods 2026-11-13 19:16:56 +01:00
n07070
8309399827 Update web interface for configuration options 2026-11-13 19:16:46 +01:00
n07070
b50b9abc22 Add more configuration options to the printers 2026-11-13 19:16:13 +01:00
n07070
d0f4ba8f01 Add more configuration and printing options 2026-11-13 19:15:59 +01:00
n07070
3fc41f806a Update configuration file to add printer specifics 2026-11-13 19:15:32 +01:00
n07070
9830917b1a Remove image after printing for brother-ql 2026-11-13 03:05:17 +01:00
n07070
ba7e854a6f Match case for printer type discovery 2026-11-13 03:01:21 +01:00
11 changed files with 318 additions and 135 deletions

View File

@@ -1,11 +1,28 @@
# Configuration file the LittlePrynter # Configuration file the LittlePrynter
[defaults] [defaults]
signature = "Anonymous" signature = ""
# Printer settings # Printer settings
[printer] [printer]
upload_folder = "src/static/uploads" upload_folder = "src/static/uploads"
# How long do we wait before giving up on waiting for a printer connexion
waiting_time = 5
[printer.epson]
# Configurations for EPSON printers
profile = "TM-T20III"
label_type = 80 # 80 or 58 ( label width in mm )
align_text = "center"
text_max_length = 4096
signature_max_length = 256
[printer.brother]
# Configurations for Brother printers
label_type = "62" # See https://github.com/pklaus/brother_ql#usage for label types
text_max_length = 4096
signature_max_length = 256
# Raspberry Pi Configuration # Raspberry Pi Configuration
[rpi] [rpi]

View File

@@ -5,8 +5,25 @@ signature = "Anonymous"
# Printer settings # Printer settings
[printer] [printer]
vendor_id = "0x04b8" # Where do we upload the pictures and such
device_id = "0x0e28" upload_folder = "src/static/uploads"
# How long do we wait before giving up on waiting for a printer connexion
waiting_time = 5
[printer.epson]
# Configurations for EPSON printers
profile = "TM-T20III"
label_type = 80 # 80 or 58 ( label width in mm )
align_text = "center"
text_max_length = 4096
signature_max_length = 256
[printer.brother]
# Configurations for Brother printers
label_type = 62 # See https://github.com/pklaus/brother_ql#usage for label types
text_max_length = 4096
signature_max_length = 256
# Raspberry Pi Configuration # Raspberry Pi Configuration
[rpi] [rpi]

View File

@@ -111,7 +111,7 @@ web = Web(app, print_queue)
# Create a pool of printers # Create a pool of printers
try: try:
# comment: Find all printers connected to the system # comment: Find all printers connected to the system
printers_pool = Printers(app) printers_pool = Printers(app, configuration_file)
except Exception as e: except Exception as e:
app.logger.error("Could not create a pool of printers because %s", str(e)) app.logger.error("Could not create a pool of printers because %s", str(e))
sys.exit(-1) sys.exit(-1)
@@ -179,7 +179,13 @@ def web_print_sms():
do_cut = False do_cut = False
try: try:
web.print_sms(txt, sign, do_cut) # Clamp value between 1 and
many = max(1, min(int(request.form["many"]), 20))
except werkzeug.exceptions.BadRequestKeyError:
many = 1
try:
web.print_sms(txt, sign, do_cut, many)
except RuntimeError as e: except RuntimeError as e:
app.logger.error("Whoops, we could not print an SMS because : %s ", str(e)) app.logger.error("Whoops, we could not print an SMS because : %s ", str(e))
flash("Whoops, we could not print an SMS because :" + str(e), "error") flash("Whoops, we could not print an SMS because :" + str(e), "error")
@@ -233,9 +239,15 @@ def web_print_img():
except werkzeug.exceptions.BadRequestKeyError: except werkzeug.exceptions.BadRequestKeyError:
process_image = False process_image = False
try:
# Clamp value between 1 and
many = max(1, min(int(request.form["many"]), 20))
except werkzeug.exceptions.BadRequestKeyError:
many = 1
try: try:
app.logger.debug("Sending the image to the printer.") app.logger.debug("Sending the image to the printer.")
web.print_image(file, sign, do_cut, process_image) web.print_image(file, sign, do_cut, process_image, many)
except RuntimeError as e: except RuntimeError as e:
app.logger.error("The image could not be printed because : %s ", str(e)) app.logger.error("The image could not be printed because : %s ", str(e))
flash("The image could not be printed because : " + str(e), "error") flash("The image could not be printed because : " + str(e), "error")
@@ -286,9 +298,15 @@ def api_print_sms():
except werkzeug.exceptions.BadRequestKeyError: except werkzeug.exceptions.BadRequestKeyError:
do_cut = False do_cut = False
try:
# Clamp value between 1 and
many = max(1, min(int(request.form["many"]), 20))
except werkzeug.exceptions.BadRequestKeyError:
many = 1
try: try:
# comment: We try to print the SMS # comment: We try to print the SMS
web.print_sms(txt, sign, do_cut) web.print_sms(txt, sign, do_cut, many)
except RuntimeError as e: except RuntimeError as e:
return str(e), 500 return str(e), 500
# end try # end try
@@ -331,9 +349,15 @@ def api_print_image():
except werkzeug.exceptions.BadRequestKeyError: except werkzeug.exceptions.BadRequestKeyError:
process_image = False process_image = False
try:
# Clamp value between 1 and
many = max(1, min(int(request.form["many"]), 20))
except werkzeug.exceptions.BadRequestKeyError:
many = 1
try: try:
app.logger.debug("Sending the image to the printer.") app.logger.debug("Sending the image to the printer.")
web.print_image(file, sign, do_cut, process_image) web.print_image(file, sign, do_cut, process_image, many)
except RuntimeError as e: except RuntimeError as e:
return str(e), 500 return str(e), 500
@@ -342,6 +366,7 @@ def api_print_image():
# TODO: This might not depend on the Raspberry Pi # TODO: This might not depend on the Raspberry Pi
@app.route("/api/camera/picture", methods=["GET"]) @app.route("/api/camera/picture", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def camera_picture(): def camera_picture():
"""Returns a picture taken by the camera on a raspberry pi""" """Returns a picture taken by the camera on a raspberry pi"""
if RASPBERRY_PI_CONNECTED: if RASPBERRY_PI_CONNECTED:
@@ -354,24 +379,28 @@ def camera_picture():
@app.route("/api/queue", methods=["GET"]) @app.route("/api/queue", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def api_queue_status(): def api_queue_status():
"""API endpoint for entire queue""" """API endpoint for entire queue"""
return jsonify(web.get_queue_state()) return jsonify(web.get_queue_state())
@app.route("/api/queue/completed", methods=["GET"]) @app.route("/api/queue/completed", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def api_queue_completed(): def api_queue_completed():
"""API endpoint that returns the finished tasks""" """API endpoint that returns the finished tasks"""
return jsonify(web.get_queue_completed()) return jsonify(web.get_queue_completed())
@app.route("/api/worker", methods=["GET"]) @app.route("/api/worker", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def api_worker_state(): def api_worker_state():
"""API endpoint to get the worker state""" """API endpoint to get the worker state"""
return jsonify(worker.current_state()) return jsonify(worker.current_state())
@app.route("/api/worker/start") @app.route("/api/worker/start")
@limiter.limit("6/minute", override_defaults=False)
def api_worker_start(): def api_worker_start():
""" """
Enable to worker. This starts to process the print queue. Enable to worker. This starts to process the print queue.
@@ -381,6 +410,7 @@ def api_worker_start():
@app.route("/api/worker/stop") @app.route("/api/worker/stop")
@limiter.limit("6/minute", override_defaults=False)
def api_worker_stop(): def api_worker_stop():
""" """
Stops the print queue. This stops the processing of the print queue. Stops the print queue. This stops the processing of the print queue.
@@ -390,6 +420,7 @@ def api_worker_stop():
@app.route("/api/printers") @app.route("/api/printers")
@limiter.limit("6/minute", override_defaults=False)
def api_printers_info(): def api_printers_info():
"""Returns the current state of every printers""" """Returns the current state of every printers"""
return jsonify(printers_pool.printers_info()) return jsonify(printers_pool.printers_info())

View File

@@ -10,9 +10,9 @@ import time
from enum import Enum from enum import Enum
import uuid import uuid
import threading import threading
import usb.core
from pathlib import Path from pathlib import Path
from uuid import uuid4 from uuid import uuid4
import usb.core
from PIL import Image, ImageEnhance, ImageDraw, ImageFont from PIL import Image, ImageEnhance, ImageDraw, ImageFont
import numpy as np import numpy as np
@@ -107,7 +107,7 @@ class EscPosPrinter(Printer):
Create a new ESC/POS based printer. Create a new ESC/POS based printer.
""" """
def __init__(self, app, vendor_id, device_id): def __init__(self, app, vendor_id, device_id, configuration_file):
""" """
Create a connexion to a ESC/POS Printer via USB, Create a connexion to a ESC/POS Printer via USB,
Making sure the printer is alive, Making sure the printer is alive,
@@ -119,11 +119,14 @@ class EscPosPrinter(Printer):
self.usb_args = {} self.usb_args = {}
self.usb_args["idVendor"] = self.vendor_id self.usb_args["idVendor"] = self.vendor_id
self.usb_args["idProduct"] = self.device_id self.usb_args["idProduct"] = self.device_id
self.configuration_file = configuration_file
try: try:
# This also calls open(), which we need to close() # This also calls open(), which we need to close()
# or else the device will appear as busy. # or else the device will appear as busy.
p = escpos.printer.Usb(self.vendor_id, self.device_id, 0, profile="TM-P80") p = escpos.printer.Usb(
self.vendor_id, self.device_id, 0, profile=configuration_file["profile"]
)
except escpos.exceptions.DeviceNotFoundError as e: except escpos.exceptions.DeviceNotFoundError as e:
self.app.logger.error( self.app.logger.error(
"The USB device is not plugged in : %s", "The USB device is not plugged in : %s",
@@ -140,9 +143,21 @@ class EscPosPrinter(Printer):
except Exception as e: except Exception as e:
raise e raise e
self.printer_info = PrinterInfo(
identifier=configuration_file["profile"],
backend="pyusb",
model=configuration_file["profile"],
protocol="ESC/POS",
vendor_id=self.vendor_id,
product_id=self.device_id,
serial_number="",
label_type=self.configuration_file["label_type"],
)
# Setting up the printing options. # Setting up the printing options.
# TODO : Expose this to the web interface
p.set( p.set(
align="center", align=self.configuration_file["align_text"],
font="a", font="a",
bold=False, bold=False,
underline=0, underline=0,
@@ -201,25 +216,25 @@ class EscPosPrinter(Printer):
clean_signature = str(signature) clean_signature = str(signature)
# Make checks on the size of the message being printed # Make checks on the size of the message being printed
# TODO: make the max length configurable if len(clean_msg) > int(self.configuration_file["text_max_length"]):
if len(clean_msg) > 4096:
self.app.logger.warning( self.app.logger.warning(
"Could not print message of this length: " + str(len(clean_msg)) "Could not print message of this length: " + str(len(clean_msg))
) )
raise RuntimeError( raise RuntimeError(
"Could not print message of this length :" "Could not print message of this length :"
+ str(len(clean_msg)) + str(len(clean_msg))
+ ", needs to be below 4096 caracters long." + ", needs to be below %s caracters long.",
str(self.configuration_file["text_max_length"]),
) )
# TODO: make the max length configurable if len(signature) > int(self.configuration_file["signature_max_length"]):
if len(signature) > 256:
self.app.logger.warning( self.app.logger.warning(
"Could not print signature of this length: " + str(len(clean_signature)) "Could not print signature of this length: " + str(len(clean_signature))
) )
raise RuntimeError( raise RuntimeError(
"Could not print signature of this length :" "Could not print signature of this length :"
+ str(len(clean_signature)) + str(len(clean_signature))
+ ", needs to be below 256 caracters long." + ", needs to be below %s caracters long.",
str(self.configuration_file["signature_max_length"]),
) )
# Do the actual printing # Do the actual printing
@@ -244,15 +259,15 @@ class EscPosPrinter(Printer):
def _print_img(self, path, signature="", center=True, process=False): def _print_img(self, path, signature="", center=True, process=False):
clean_signature = str(signature) clean_signature = str(signature)
# TODO: make the max length configurable if len(signature) > int(self.configuration_file["signature_max_length"]):
if len(signature) > 256:
self.app.logger.warning( self.app.logger.warning(
"Could not print signature of this length: " + str(len(clean_signature)) "Could not print signature of this length: " + str(len(clean_signature))
) )
raise ValueError( raise RuntimeError(
"Could not print signature of this length :" "Could not print signature of this length :"
+ str(len(clean_signature)) + str(len(clean_signature))
+ ", needs to be below 256 caracters long." + ", needs to be below %s caracters long.",
str(self.configuration_file["signature_max_length"]),
) )
if not os.path.isfile(str(path)): if not os.path.isfile(str(path)):
@@ -287,23 +302,20 @@ class EscPosPrinter(Printer):
except Exception as e: except Exception as e:
self.app.logger.error(str(e)) self.app.logger.error(str(e))
raise RuntimeError("Could not print the picture") from e raise RuntimeError("Could not print the picture") from e
finally: # finally:
try: # try:
os.remove(path) # os.remove(path)
os.remove(processed_path) # os.remove(processed_path)
except OSError as e: # self.app.logger.debug("Removed image : " + str(processed_path))
raise e # self.app.logger.debug("Removed image : " + str(path))
# except OSError as e:
# raise e
self.app.logger.debug("Removed image : " + str(processed_path)) try:
self.app.logger.debug("Removed image : " + str(path)) self.printer.close()
except Exception as e:
try: self.app.logger.error("Could not close the printer connexion %s", str(e))
self.printer.close() raise RuntimeError("Could not close the printer connexion. ") from e
except Exception as e:
self.app.logger.error(
"Could not close the printer connexion %s", str(e)
)
raise RuntimeError("Could not close the printer connexion. ") from e
self.app.logger.info("Printed a picture") self.app.logger.info("Printed a picture")
self.ready = True self.ready = True
@@ -392,7 +404,7 @@ class BrotherPrinter(Printer):
Manages connexion and capabilities of a BrotherQL Printer Manages connexion and capabilities of a BrotherQL Printer
""" """
def __init__(self, app, vendor_id, device_id): def __init__(self, app, vendor_id, device_id, configuration_file):
super().__init__(app, vendor_id, device_id, printer_type=PrinterType.BROTHER) super().__init__(app, vendor_id, device_id, printer_type=PrinterType.BROTHER)
self.printer = None self.printer = None
@@ -446,7 +458,9 @@ class BrotherPrinter(Printer):
vendor_id=vendor_id, vendor_id=vendor_id,
product_id=self.device_id, product_id=self.device_id,
serial_number=serial_number, serial_number=serial_number,
label_type="62", label_type=configuration_file[
"label_type"
], # TODO: Implement automatic label type detection : https://framagit.org/stickoeur/diagnostickoeur/-/blob/no-masters/printit.py?ref_type=heads#L239
) )
self.app.logger.debug("Created a Brother printer !") self.app.logger.debug("Created a Brother printer !")
self.ready = True self.ready = True
@@ -462,10 +476,16 @@ class BrotherPrinter(Printer):
self._print_img(text_to_image(msg)) self._print_img(text_to_image(msg))
self._print_img(text_to_image(signature)) self._print_img(text_to_image(signature))
def _print_img(self, data, signature="", process=False): def _print_img(
self,
data,
signature="",
process=False,
):
""" """
Print a raster image via a Brother QL printer Print a raster image via a Brother QL printer
""" """
self.app.logger.debug("Printing an image %s via Brother printer", data)
if process: if process:
try: try:
self.app.logger.debug("Proccessing the image") self.app.logger.debug("Proccessing the image")
@@ -478,6 +498,7 @@ class BrotherPrinter(Printer):
else: else:
processed_path = data processed_path = data
self.app.logger.warning("Not proccessing the image") self.app.logger.warning("Not proccessing the image")
try: try:
selected_backend = guess_backend(self.printer_info.identifier) selected_backend = guess_backend(self.printer_info.identifier)
BACKEND_CLASS = backend_factory(selected_backend)["backend_class"] BACKEND_CLASS = backend_factory(selected_backend)["backend_class"]
@@ -491,33 +512,93 @@ class BrotherPrinter(Printer):
try: try:
im = Image.open(processed_path) im = Image.open(processed_path)
qlr = BrotherQLRaster(self.printer_info.model) qlr = BrotherQLRaster(self.printer_info.model)
create_label(qlr, im, self.printer_info.label_type, cut=False, dither=True) # create_label(qlr, im, self.printer_info.label_type, cut=False, dither=True)
# noinspection PyCallingNonCallable # noinspection PyCallingNonCallable
be = BACKEND_CLASS(BACKEND_STRING_DESCR) # be = BACKEND_CLASS(BACKEND_STRING_DESCR)
be.write(qlr.data) # be.write(qlr.data)
be.dispose() # be.dispose()
del be # del be
instructions = convert(
qlr=qlr,
images=[im],
label=self.printer_info.label_type,
dither=True,
compress=False,
dpi_600=False,
cut=False,
)
success = send(
instructions=instructions,
printer_identifier=BACKEND_STRING_DESCR,
backend_identifier="pyusb",
blocking=False,
)
self.app.logger.debug(str(success))
self.app.logger.debug("Printed image")
time.sleep(2)
except usb.core.USBError as e:
if "timeout error" in str(e):
self.app.logger.error("USB Timeout Error %s", str(e))
except Exception as e: except Exception as e:
raise e raise e
# finally:
# try:
# os.remove(processed_path)
# self.app.logger.debug("Removed image : " + str(processed_path))
# except OSError as e:
# raise e
if signature != "": if signature != "":
self.app.logger.debug("Printing signature for the image %s", data)
try: try:
im = Image.open(text_to_image(signature)) signature_file = text_to_image(signature)
im = Image.open(signature_file)
qlr = BrotherQLRaster(self.printer_info.model) qlr = BrotherQLRaster(self.printer_info.model)
create_label( # create_label(
qlr, im, self.printer_info.label_type, cut=False, dither=True # qlr, im, self.printer_info.label_type, cut=False, dither=True
) # )
# noinspection PyCallingNonCallable # noinspection PyCallingNonCallable
be = BACKEND_CLASS(BACKEND_STRING_DESCR) # be = BACKEND_CLASS(BACKEND_STRING_DESCR)
be.write(qlr.data) # be.write(qlr.data)
be.dispose() # be.dispose()
del be # del be
instructions = convert(
qlr=qlr,
images=[signature_file],
label=self.printer_info.label_type,
dither=True,
compress=False,
dpi_600=False,
cut=False,
)
success = send(
instructions=instructions,
printer_identifier=BACKEND_STRING_DESCR,
backend_identifier="pyusb",
blocking=False,
)
self.app.logger.debug(str(success))
self.app.logger.debug("Printed signature")
time.sleep(2)
except Exception as e: except Exception as e:
raise e raise e
finally:
try:
# os.remove(signature_file)
self.app.logger.debug("Removed image : " + str(signature_file))
except OSError as e:
raise e
def _cut(self): def _cut(self):
self.app.logger.debug("Cutting via Brother Printer")
try: try:
selected_backend = guess_backend(self.printer_info.identifier) selected_backend = guess_backend(self.printer_info.identifier)
BACKEND_CLASS = backend_factory(selected_backend)["backend_class"] BACKEND_CLASS = backend_factory(selected_backend)["backend_class"]
@@ -541,12 +622,13 @@ class BrotherPrinter(Printer):
be.write(qlr.data) be.write(qlr.data)
be.dispose() be.dispose()
del be del be
self.app.logger.debug("Finished cutting via Brother Printer")
except Exception as e: except Exception as e:
raise e raise e
def _qr(self): def _qr(self):
# This should not be reached by a print on a BrotherQL # This should not be reached by a print on a BrotherQL
raise NotImplementedError raise NotImplementedError()
def print_task(self, task_type, data): def print_task(self, task_type, data):
"""Execute actual print based on task type""" """Execute actual print based on task type"""

View File

@@ -18,11 +18,12 @@ class Printers:
Finds and creates a set of Printer that can be used by the Workers to print. Finds and creates a set of Printer that can be used by the Workers to print.
""" """
def __init__(self, app): def __init__(self, app, configuration_file):
""" """
Discovers printers connected to the computer Discovers printers connected to the computer
""" """
self.app = app self.app = app
self.configuration_file = configuration_file
self.printers = list(self._discover_printers()) self.printers = list(self._discover_printers())
self._lock = threading.Lock() self._lock = threading.Lock()
@@ -42,8 +43,7 @@ class Printers:
printers = set() printers = set()
# TODO : this could go in the config ? waiting_time_for_printer = self.configuration_file["printer"]["waiting_time"]
waiting_time_for_printer = 5
while True: while True:
# Find all connected USB devices # Find all connected USB devices
@@ -90,47 +90,53 @@ class Printers:
hex(dev.idProduct), hex(dev.idProduct),
) )
if manufacturer == "EPSON": match (manufacturer):
try: case "EPSON":
# We create a new EscPosPrinter() try:
self.app.logger.debug("Trying to create a new EPSON printer") # We create a new EscPosPrinter()
prid = dev.idProduct self.app.logger.debug("Trying to create a new EPSON printer")
vendir = dev.idVendor prid = dev.idProduct
escpos_printer = EscPosPrinter( vendir = dev.idVendor
self.app, vendor_id=vendir, device_id=prid escpos_printer = EscPosPrinter(
self.app,
vendir,
prid,
self.configuration_file["printer"]["epson"],
)
except Exception as e:
raise e
# If the object creation is successfull, we add it to the list of Printers
printers.add(escpos_printer)
self.app.logger.debug("Found a %s printer", manufacturer)
case "Brother":
try:
# We create a new BrotherPrinter()
self.app.logger.debug("Trying to create a new BROTHER printer")
brother_printer = BrotherPrinter(
self.app,
dev.idVendor,
dev.idProduct,
self.configuration_file["printer"]["brother"],
)
except Exception as e:
self.app.logger.error(
"Could not create a %s printer class with %s:%s",
product,
dev.idVendor,
dev.idProduct,
)
raise e
# If the object creation is successfull, we add it to the list of Printers
printers.add(brother_printer)
self.app.logger.debug("Found a %s printer", manufacturer)
case _:
self.app.logger.debug(
"Found a printer of unknown manufacturer : %s", manufacturer
) )
except Exception as e: # end match
raise e
# If the object creation is successfull, we add it to the list of Printers
printers.add(escpos_printer)
self.app.logger.debug("Found a %s printer", manufacturer)
# We already found the type of printer,
# we don't need an extra comparaison.
continue
# or a Brother Printer
if manufacturer == "Brother":
try:
# We create a new BrotherPrinter()
self.app.logger.debug("Trying to create a new BROTHER printer")
brother_printer = BrotherPrinter(
self.app, vendor_id=dev.idVendor, device_id=dev.idProduct
)
except Exception as e:
self.app.logger.error(
"Could not create a %s printer class with %s:%s",
product,
dev.idVendor,
dev.idProduct,
)
raise e
# If the object creation is successfull, we add it to the list of Printers
printers.add(brother_printer)
self.app.logger.debug("Found a %s printer", manufacturer)
self.app.logger.debug("Found %s printers", len(printers)) self.app.logger.debug("Found %s printers", len(printers))
@@ -150,6 +156,8 @@ class Printers:
return None return None
with self._lock: with self._lock:
# So don't always look at the first printer available if need be
# this allows for a round-robin lookup
for offset in range(len(printers)): for offset in range(len(printers)):
index = (start_index + offset) % len(printers) index = (start_index + offset) % len(printers)
printer = printers[index] printer = printers[index]
@@ -162,7 +170,18 @@ class Printers:
def printers_info(self): def printers_info(self):
"""Return the current list of printers""" """Return the current list of printers"""
return self.printers printers_info_list = []
for i in self.printers:
printers_info_list.append(
[
i.printer_type.name,
"Used: " + str(i.used),
"Ready: " + str(i.ready),
i.printer_info,
]
)
return printers_info_list
class _FindClass: class _FindClass:

View File

@@ -5,6 +5,6 @@
<title>LittlePrynter API</title> <title>LittlePrynter API</title>
</head> </head>
<body> <body>
"Welcome to the printing software's API. Please see the index page to get started. Welcome to the printing software's API. Please see the index page to get started.
</body> </body>
</html> </html>

View File

@@ -53,7 +53,7 @@
<hr> <hr>
<footer class="row"> <footer class="row">
<p class=" text-center">Little Prynter is built by <a href="https://n07070.xyz/about-me/">n07070</a> because it's fun :) | <a href="https://git.n07070.xyz/n07070/littleprynter">Source code - AGPLv3</a></p> <p class=" text-center">Little Prynter is built by <a href="https://n07070.xyz/about-me/">n07070</a> because it's fun :) | <a href="/api">API interface</a> | <a href="https://git.n07070.xyz/n07070/littleprynter">Source code - AGPLv3</a></p>
</footer> </footer>
</body> </body>
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script> <script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>

View File

@@ -1,5 +0,0 @@
<hr>
<footer>Little Prynter is built by <a href="https://n07070.xyz/about-me/">n07070</a> because it's fun :) | <a href="https://git.n07070.xyz/n07070/littleprynter/src/branch/epson-tm-t20iii">Source code - AGPLv3</a></footer>
</div>
</body>
</html>

View File

@@ -9,14 +9,20 @@
<textarea class="form-control" type="text" name="txt" placeholder="4096 caractères maximum " maxlength="4096"></textarea> <textarea class="form-control" type="text" name="txt" placeholder="4096 caractères maximum " maxlength="4096"></textarea>
<br> <br>
<input class="form-control" type="text" name="signature" placeholder="Signature ou pseudo" maxlength="200"><br> <input class="form-control" type="text" name="signature" placeholder="Signature ou pseudo" maxlength="200"><br>
<div class="input-group">
<span class="input-group-text">Combien ?</span>
<input type="number" aria-label="how-many" class="form-control" name="many" max="20" min="1" value="1">
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
</div>
<br>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" value="True" id="flexCheckDefault" name="cut" checked> <input class="form-check-input" type="checkbox" value="True" id="flexCheckDefault" name="cut" checked>
<label class="form-check-label" for="flexCheckDefault"> <label class="form-check-label" for="flexCheckDefault">
Couper après impression ? Couper après impression ?
</label> </label>
</div> </div>
<br>
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
</form> </form>
</div> </div>
</div> </div>
@@ -29,6 +35,12 @@
<form enctype="multipart/form-data" class="form-group" action="/web/print/img" method="post"> <form enctype="multipart/form-data" class="form-group" action="/web/print/img" method="post">
<input class="form-control" type="file" name="img" required><br> <input class="form-control" type="file" name="img" required><br>
<input class="form-control" type="text" name="signature" placeholder="Signature ou pseudo" maxlength="200"><br> <input class="form-control" type="text" name="signature" placeholder="Signature ou pseudo" maxlength="200"><br>
<div class="input-group">
<span class="input-group-text">Combien ?</span>
<input type="number" aria-label="how-many" class="form-control" name="many" max="20" min="1" value="1">
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
</div>
<br>
<div class="form-check .form-check-inline"> <div class="form-check .form-check-inline">
<input class="form-check-input" type="checkbox" value="True" id="flexCheckDefault" name="cut" checked> <input class="form-check-input" type="checkbox" value="True" id="flexCheckDefault" name="cut" checked>
<label class="form-check-label" for="flexCheckDefault"> <label class="form-check-label" for="flexCheckDefault">
@@ -41,8 +53,7 @@
Traiter l'image ? Traiter l'image ?
</label> </label>
</div> </div>
<br>
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
</form> </form>
</div> </div>
</div> </div>

View File

@@ -16,7 +16,7 @@ class Web:
self.print_queue = print_queue self.print_queue = print_queue
self.app = app self.app = app
def print_sms(self, texte, sign: str, do_cut: bool) -> bool: def print_sms(self, texte, sign: str, do_cut: bool, many: int = 1) -> bool:
""" """
Get text and a signature, prints the text and cuts after that. Get text and a signature, prints the text and cuts after that.
""" """
@@ -28,19 +28,22 @@ class Web:
# !! there is no garantee !! that both the SMS task and the Cut task # !! there is no garantee !! that both the SMS task and the Cut task
# are added back to back, another task could be # are added back to back, another task could be
# inserted between the two. # inserted between the two.
sms = self.print_queue.enqueue(TextTask(content=texte, signature=sign)) for i in range(many):
self.app.logger.info("Added a SMS task at position %s", sms) sms = self.print_queue.enqueue(TextTask(content=texte, signature=sign))
self.app.logger.info("Added a SMS task at position %s", sms)
if do_cut:
cut = self.print_queue.enqueue(CutTask())
self.app.logger.info("Added a CUT task at position %s", cut)
if do_cut:
cut = self.print_queue.enqueue(CutTask())
self.app.logger.info("Added a CUT task at position %s", cut)
except Exception as e: except Exception as e:
self.app.logger.error(e) self.app.logger.error(e)
raise RuntimeError("Could not add SMS to queue, " + str(e)) from e raise RuntimeError("Could not add SMS to queue, " + str(e)) from e
return True return True
def print_image(self, image, sign: str, do_cut: bool, process_image: bool) -> bool: def print_image(
self, image, sign: str, do_cut: bool, process_image: bool, many: int = 1
) -> bool:
""" """
Get an image and a signature, prints the image and cuts after that. Get an image and a signature, prints the image and cuts after that.
""" """
@@ -53,21 +56,22 @@ class Web:
if file_uploaded: if file_uploaded:
self.app.logger.debug("File has been uploaded, printing...") self.app.logger.debug("File has been uploaded, printing...")
try: try:
img = self.print_queue.enqueue( for i in range(many):
ImageTask( img = self.print_queue.enqueue(
os.path.join( ImageTask(
self.app.config["UPLOAD_FOLDER"], os.path.join(
secure_filename(image.filename), self.app.config["UPLOAD_FOLDER"],
), secure_filename(image.filename),
signature=sign, ),
process=process_image, signature=sign,
process=process_image,
)
) )
) self.app.logger.info("Added a IMG task at position %s", img)
self.app.logger.info("Added a IMG task at position %s", img)
if do_cut: if do_cut:
cut = self.print_queue.enqueue(CutTask()) cut = self.print_queue.enqueue(CutTask())
self.app.logger.info("Added a CUT task at position %s", cut) self.app.logger.info("Added a CUT task at position %s", cut)
except Exception as e: except Exception as e:
raise RuntimeError("Could not add IMG to queue" + str(e)) from e raise RuntimeError("Could not add IMG to queue" + str(e)) from e

View File

@@ -6,7 +6,6 @@ This is the main printing thread. A worker thread consums Tasks from
import threading import threading
import time import time
import traceback import traceback
from printers import Printers
class PrintWorker(threading.Thread): class PrintWorker(threading.Thread):
@@ -21,7 +20,7 @@ class PrintWorker(threading.Thread):
tasks. TODO : Change that tasks. TODO : Change that
""" """
def __init__(self, app, print_queue, printers_pool): def __init__(self, app, print_queue, printers_pool, printer_index=0):
super().__init__(daemon=True) super().__init__(daemon=True)
self.app = app self.app = app
self.print_queue = print_queue self.print_queue = print_queue
@@ -33,7 +32,9 @@ class PrintWorker(threading.Thread):
# All workers share the same pool of printers. # All workers share the same pool of printers.
self.printers = printers_pool self.printers = printers_pool
self.printers_obj = self.printers.printers self.printers_obj = self.printers.printers
self._printer_index = 0 # If we create several workers, we can assign an index
# so that we lookup the nth printer for the nth worker
self._printer_index = printer_index
self.app.logger.debug("Worker initialized.") self.app.logger.debug("Worker initialized.")
self.app.logger.debug("Printers connected:") self.app.logger.debug("Printers connected:")
@@ -44,11 +45,15 @@ class PrintWorker(threading.Thread):
self.app.logger.debug("Ho great, I'm alive... I'm ready to work another day...") self.app.logger.debug("Ho great, I'm alive... I'm ready to work another day...")
def _acquire_available_printer(self): def _acquire_available_printer(self):
result = self.printers.acquire_available_printer(self._printer_index) result = self.printers.acquire_available_printer(
start_index=self._printer_index
)
if result is None: if result is None:
return None return None
index, printer = result index, printer = result
# In case we need to lookup another printer, we don't
# look at the same printer in the printer list
self._printer_index = (index + 1) % len(self.printers_obj) self._printer_index = (index + 1) % len(self.printers_obj)
return printer return printer
@@ -128,7 +133,6 @@ class PrintWorker(threading.Thread):
self.app.logger.error("Could not print : %s", str(e)) self.app.logger.error("Could not print : %s", str(e))
self.app.logger.error(traceback.format_exc()) self.app.logger.error(traceback.format_exc())
self.printer.free_printer() self.printer.free_printer()
raise e
task.status = "completed" task.status = "completed"
self.print_queue.mark_completed(task.task_id, "completed") self.print_queue.mark_completed(task.task_id, "completed")
@@ -156,7 +160,10 @@ class PrintWorker(threading.Thread):
""" """
self.app.logger.debug("Giving the worker a break") self.app.logger.debug("Giving the worker a break")
self.state = "drinking-a-beer" self.state = "drinking-a-beer"
# Free the printer during the worker break
# so it can be used by someone else
self.printer.free_printer() self.printer.free_printer()
self.printer = None
self.running = False self.running = False
def start_worker(self): def start_worker(self):