Compare commits

4 Commits

Author SHA1 Message Date
n07070
d4eb0ed3f9 Change the way a Printer is used by the worker 2026-10-11 21:42:29 +02:00
n07070
960b0e2240 Add more debug and fix brother creation 2026-06-23 11:32:31 +02:00
n07070
60b0325872 Fix inverter variables, adjust Brother creation and image processing 2026-06-23 11:31:47 +02:00
n07070
f20aa2e3e8 Raise final exception to get traceback 2026-06-23 11:31:33 +02:00
3 changed files with 105 additions and 45 deletions

View File

@@ -75,6 +75,7 @@ class Printer(ABC):
self.ready = False self.ready = False
self.printer_type = printer_type self.printer_type = printer_type
self._lock = threading.Lock() self._lock = threading.Lock()
self.used = False
@abstractmethod @abstractmethod
def _has_paper(self) -> bool: def _has_paper(self) -> bool:
@@ -88,6 +89,13 @@ class Printer(ABC):
def print_task(self, task_type, data) -> None: def print_task(self, task_type, data) -> None:
"""Takes a PrintTask and executes it""" """Takes a PrintTask and executes it"""
def use_printer(self) -> None:
"""Marks the printer as being used """
self.used = True
def free_printer(self) -> None:
"""Marks the printer as being not used, it's available."""
self.used = False
class EscPosPrinter(Printer): class EscPosPrinter(Printer):
""" """
@@ -379,17 +387,19 @@ class BrotherPrinter(Printer):
def __init__(self, app, vendor_id, device_id): def __init__(self, app, vendor_id, device_id):
super().__init__( super().__init__(
app, vendor_id="", device_id="", printer_type=PrinterType.BROTHER app, vendor_id, device_id, printer_type=PrinterType.BROTHER
) )
self.printer = None self.printer = None
self.usb_args = {} self.usb_args = {}
self.usb_args["idVendor"] = self.device_id self.usb_args["idVendor"] = self.vendor_id
self.usb_args["idProduct"] = self.vendor_id self.usb_args["idProduct"] = self.device_id
self.model_manager = ModelsManager() self.model_manager = ModelsManager()
# Code taken from https://github.com/5shekel/printit/blob/master/printer_utils.py # Code taken from https://github.com/5shekel/printit/blob/master/printer_utils.py
self.app.logger.debug("Creating backend for Brother")
backend = backend_factory("pyusb") backend = backend_factory("pyusb")
available_devices = backend["list_available_devices"]() available_devices = backend["list_available_devices"]()
for printer in available_devices: for printer in available_devices:
@@ -408,14 +418,19 @@ class BrotherPrinter(Printer):
serial_number = parts[3] serial_number = parts[3]
try: try:
product_id_int = int(self.device_id, 16)
for m in self.model_manager.iter_elements(): for m in self.model_manager.iter_elements():
if m.product_id == product_id_int: if m.product_id == self.device_id:
model = m.identifier model = m.identifier
break break
self.app.logger.debug(f"Matched printer model: {model}") self.app.logger.debug(f"Matched printer model: {model}")
except ValueError: except ValueError as ve:
self.app.logger.warning(f"Invalid product ID format: {m.product_id}") self.app.logger.warning("Invalid product ID format:"+ str(ve))
raise ve
except Exception as e:
self.app.logger.error("Error while finding a model ? : " + str(e))
raise e
self.printer_info = PrinterInfo( self.printer_info = PrinterInfo(
identifier=identifier, identifier=identifier,
@@ -426,7 +441,7 @@ class BrotherPrinter(Printer):
product_id=self.device_id, product_id=self.device_id,
serial_number=serial_number, serial_number=serial_number,
) )
self.app.logger.debug("Created a Brother printer !")
self.ready = True self.ready = True
def _has_paper(self): def _has_paper(self):
@@ -439,17 +454,19 @@ class BrotherPrinter(Printer):
""" """
Print a raster image via a Brother QL printer Print a raster image via a Brother QL printer
""" """
self.ready = False
label_type = "102" label_type = "102"
rotate = 0 rotate = 0
dither = False dither = False
data = _process_image(self, path=data, max_width=720, max_height=720)
try: try:
# Prepare the image for printing # Prepare the image for printing
qlr = BrotherQLRaster(self.printer_info["model"]) qlr = BrotherQLRaster(self.printer_info["model"])
instructions = convert( instructions = convert(
qlr=qlr, qlr=qlr,
images=[data["img"]], images=[data],
label=label_type, label=label_type,
rotate=rotate, rotate=rotate,
threshold=70, threshold=70,
@@ -486,6 +503,7 @@ class BrotherPrinter(Printer):
printer_identifier=self.printer_info["identifier"], printer_identifier=self.printer_info["identifier"],
backend_identifier="pyusb", backend_identifier="pyusb",
) )
self.app.logger.debug("Status: " + str(status))
if ( if (
not status["did_print"] not status["did_print"]
@@ -494,10 +512,11 @@ class BrotherPrinter(Printer):
): ):
raise RuntimeError("Failed to print using Python API") raise RuntimeError("Failed to print using Python API")
if status["printer_state"]: # if status["printer_state"]:
self.ready = bool(status["printer_state"]) # self.ready = bool(status["printer_state"])
else: # self.app.logger.debug("Printer is borky : " + str(self.ready))
self.ready = True # else:
# self.ready = True
except usb.core.USBError as e: except usb.core.USBError as e:
# Treat timeout errors as successful since they often occur after print completion # Treat timeout errors as successful since they often occur after print completion
@@ -508,29 +527,29 @@ class BrotherPrinter(Printer):
self.app.logger.debug("Print completed (timeout is normal)") self.app.logger.debug("Print completed (timeout is normal)")
self.ready = True self.ready = True
error_msg = f"USBError encountered: {e}" self.app.logger.debug(f"USBError encountered: {e}")
self.app.logger.debug(error_msg)
raise RuntimeError from e raise RuntimeError from e
except Exception as e: except Exception as e:
error_msg = f"Unexpected error during printing: {str(e)}" self.app.logger.debug(f"Unexpected error during printing: {str(e)}")
self.app.logger.debug(error_msg)
raise RuntimeError from e raise RuntimeError from e
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"""
with self._lock: with self._lock:
if self._state: if self.ready:
self._state = False try:
match (task_type.value): match (task_type.value):
case "image": case "image":
self._print_img(data["img"]) self._print_img(data["img"])
self._state = True
case "cut": case "cut":
# The cut happens by default on Brother QL printers. # The cut happens by default on Brother QL printers.
self._state = True pass
case _: case _:
raise RuntimeError("This task type is not supported") raise RuntimeError("This task type is not supported")
except Exception as e:
raise e
# end try
else: else:
raise RuntimeError("The printer is not ready to print yet !") raise RuntimeError("The printer is not ready to print yet !")
@@ -539,12 +558,10 @@ class BrotherPrinter(Printer):
# raise NotImplementedError("This printer type is not implemented yet") # raise NotImplementedError("This printer type is not implemented yet")
def _process_image(self, path): def _process_image(self, path, max_width=575, max_height=1000):
brightness_factor = 1.5 # Used only if image is too dark brightness_factor = 1.5 # Used only if image is too dark
brightness_threshold = 100 # Brightness threshold (0–255) brightness_threshold = 100 # Brightness threshold (0–255)
contrast_factor = 2 # Less than 1.0 = lower contrast contrast_factor = 2 # Less than 1.0 = lower contrast
max_width = 575
max_height = 1000
with Image.open(path) as original_img: with Image.open(path) as original_img:
# Convert to RGB if needed (JPEG doesn't support alpha) # Convert to RGB if needed (JPEG doesn't support alpha)
@@ -553,7 +570,7 @@ def _process_image(self, path):
original_img = original_img.convert("RGB") original_img = original_img.convert("RGB")
# Resize while maintaining aspect ratio # Resize while maintaining aspect ratio
original_img.thumbnail((max_width, max_height), Image.Resampling.LANCZOS) original_img = original_img.resize((max_width, max_height), Image.Resampling.LANCZOS)
self.app.logger.debug("Resized the image") self.app.logger.debug("Resized the image")
# # Convert to grayscale for dithering # # Convert to grayscale for dithering

View File

@@ -6,6 +6,7 @@ the methods expected from printers.
""" """
from collections.abc import Set from collections.abc import Set
import time
import usb.core import usb.core
import usb.util import usb.util
from printer import Printer, EscPosPrinter, BrotherPrinter from printer import Printer, EscPosPrinter, BrotherPrinter
@@ -41,7 +42,17 @@ class Printers:
# Find all connected USB devices # Find all connected USB devices
devices = usb.core.find(find_all=True, custom_match=_FindClass(7)) devices = usb.core.find(find_all=True, custom_match=_FindClass(7))
if not devices: waiting_time_for_printer = 10
while not list(devices):
self.app.logger.warning(str(waiting_time_for_printer) + " Found no printers, waiting for a printer to connect to the system...")
time.sleep(1)
waiting_time_for_printer = waiting_time_for_printer - 1
devices = usb.core.find(find_all=True, custom_match=_FindClass(7))
if waiting_time_for_printer == 0:
break
if not list(devices):
self.app.logger.warning( self.app.logger.warning(
"No USB devices of class 7 ( printers ) found or pyusb could not access the bus." "No USB devices of class 7 ( printers ) found or pyusb could not access the bus."
) )
@@ -60,6 +71,7 @@ class Printers:
product = usb.util.get_string(dev, dev.iProduct) product = usb.util.get_string(dev, dev.iProduct)
except Exception: except Exception:
product = "Unknown" product = "Unknown"
self.app.logger.debug( self.app.logger.debug(
"Looking at %s %s (%s:%s)", "Looking at %s %s (%s:%s)",
manufacturer, manufacturer,
@@ -71,7 +83,7 @@ class Printers:
if manufacturer == "EPSON": if manufacturer == "EPSON":
try: try:
# We create a new EscPosPrinter() # We create a new EscPosPrinter()
self.app.logger.debug("Trying to creat a new EPSON printer") self.app.logger.debug("Trying to create a new EPSON printer")
prid = dev.idProduct prid = dev.idProduct
vendir = dev.idVendor vendir = dev.idVendor
escpos_printer = EscPosPrinter( escpos_printer = EscPosPrinter(
@@ -92,11 +104,10 @@ class Printers:
if manufacturer == "Brother": if manufacturer == "Brother":
try: try:
# We create a new BrotherPrinter() # We create a new BrotherPrinter()
self.app.logger.debug("Trying to creat a new BROTHER printer") self.app.logger.debug("Trying to create a new BROTHER printer")
prid = dev.idProduct
vendir = dev.idVendor
brother_printer = BrotherPrinter( brother_printer = BrotherPrinter(
self.app, vendor_id=vendir, device_id=prid self.app, vendor_id=dev.idVendor, device_id=dev.idProduct
) )
except Exception as e: except Exception as e:
self.app.logger.error( self.app.logger.error(

View File

@@ -5,6 +5,7 @@ This is the main printing thread. A worker thread consums Tasks from
import threading import threading
import time import time
import traceback
from printers import Printers from printers import Printers
@@ -13,7 +14,8 @@ class PrintWorker(threading.Thread):
A thread used to consume Tasks added to a Print Queue. A thread used to consume Tasks added to a Print Queue.
On initialisation, the worker will try to find Printers, On initialisation, the worker will try to find Printers,
and on each print, choose an available Printer from a list of Printers. and on each print, if the choosen Printer becomes unavailable, it
will choose an available Printer from a list of Printers.
If a print fails, it's not retried, but will be added to a list of completed If a print fails, it's not retried, but will be added to a list of completed
tasks. tasks.
@@ -30,8 +32,9 @@ class PrintWorker(threading.Thread):
try: try:
self.printers = Printers(self.app) self.printers = Printers(self.app)
self.printers_obj = self.printers.printers self.printers_obj = self.printers.printers # Get the number of printers connected to the system
self.printers = iter(self.printers.printers) self.printers_list = iter(self.printers.printers)
except RuntimeError as e: except RuntimeError as e:
self.app.logger.warning("Could not get any Printers") self.app.logger.warning("Could not get any Printers")
raise e raise e
@@ -53,11 +56,31 @@ class PrintWorker(threading.Thread):
# we know about, and try to find one that is available. # we know about, and try to find one that is available.
# When we find a printer, we acquire it # When we find a printer, we acquire it
# When we are finished with a printer, we release it to the world. # When we are finished with a printer, we release it to the world.
while not self.printer or not self.printer.ready:
# If we do not have a printer, we try to find one
while not self.printer:
time.sleep(1) time.sleep(1)
try: try:
self.app.logger.debug("Changing printers") self.app.logger.debug("Trying to find a available printer for the worker.")
self.printer = next(self.printers)
# From the list of printer, we get one.
# If no printer in the list is avaible, we try again from the start of the loop, that we reset.
while self.printer.used or not self.printer:
self.printer = next(self.printers_list)
try:
self.printer = next(self.printer)
except StopIteration as e:
self.app.error("We do not have any available printers. Resetting our list of printers and trying again. " + str(e))
self.printers_list = iter(self.printers.printers)
time.sleep(1)
# TODO : Here, in case there is really no printer available, we will stay in a loop forever. Maybe after a number of tries, we should die.
# Here, we must have found a Printer() that is
# Printer.used = False, which means it's available.
# Therefore, we mark it used, so no other worker uses it.
self.printer.use_printer()
self.app.logger.debug( self.app.logger.debug(
"The worker got a %s printer and it's %s", "The worker got a %s printer and it's %s",
self.printer.printer_type, self.printer.printer_type,
@@ -67,6 +90,11 @@ class PrintWorker(threading.Thread):
self.app.logger.error("No printer detected" + str(e)) self.app.logger.error("No printer detected" + str(e))
self.printer = None self.printer = None
# If the printer is not ready, we wait for it to become ready.
while not self.printer.ready:
self.app.debug("Waiting for our printer to become ready...")
time.sleep(1)
if self.state != "idle": if self.state != "idle":
self.app.logger("We are not idle, waiting...") self.app.logger("We are not idle, waiting...")
time.sleep(1) time.sleep(1)
@@ -80,6 +108,7 @@ class PrintWorker(threading.Thread):
except Exception as e: except Exception as e:
self.app.logger.error("Could not get a new task ! %s ", str(e)) self.app.logger.error("Could not get a new task ! %s ", str(e))
self.state = "idle" self.state = "idle"
self.printer.free_printer()
raise RuntimeError( raise RuntimeError(
"We could not get a new task because " + str(e) "We could not get a new task because " + str(e)
) from e ) from e
@@ -97,6 +126,8 @@ class PrintWorker(threading.Thread):
except RuntimeError as e: except RuntimeError as e:
self.state = "idle" self.state = "idle"
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.printer.free_printer()
raise e raise e
task.status = "completed" task.status = "completed"
@@ -125,6 +156,7 @@ 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"
self.printer.free_printer()
self.running = False self.running = False
def start_worker(self): def start_worker(self):