From 704b7b83cdc683c06ac6cce7616ec2835a76d652 Mon Sep 17 00:00:00 2001 From: n07070 Date: Wed, 11 Nov 2026 16:58:44 +0100 Subject: [PATCH] Update the way a printer is found, update the configuration of Brother Printers --- src/main.py | 15 +- src/printer.py | 176 +++++++++++++++++------- src/printers.py | 92 +++++++------ src/static/font/MonospaceTypewriter.ttf | Bin 0 -> 38748 bytes src/worker.py | 73 +++++----- 5 files changed, 231 insertions(+), 125 deletions(-) create mode 100644 src/static/font/MonospaceTypewriter.ttf diff --git a/src/main.py b/src/main.py index 4a0645b..b9f4cdb 100644 --- a/src/main.py +++ b/src/main.py @@ -40,6 +40,7 @@ from raspberry import Raspberry # The Raspberry pi control Class from web import Web # Wrapper for the web routes and API from print_queue import PrintQueue from worker import PrintWorker +from printers import Printers # We create the main Flask object app = Flask(__name__) @@ -108,10 +109,18 @@ RASPBERRY_PI_CONNECTED = rpi.is_raspberry_pi() # Web & API management web = Web(app, print_queue) -# Start worker thread -# When created, the worker will try to find printers connected to the system +# Create a pool of printers try: - worker = PrintWorker(app, print_queue) + # comment: Find all printers connected to the system + printers_pool = Printers(app) +except Exception as e: + app.logger.error("Could not create a pool of printers because %s", str(e)) + sys.exit(-1) +# end try + +# Start worker thread +try: + worker = PrintWorker(app, print_queue, printers_pool=printers_pool) worker.start() except Exception as e: app.logger.error("Could not start the worker because %s ", str(e)) diff --git a/src/printer.py b/src/printer.py index b357850..bd7eeac 100644 --- a/src/printer.py +++ b/src/printer.py @@ -11,9 +11,12 @@ from enum import Enum import uuid import threading import usb.core -from PIL import Image, ImageEnhance +from pathlib import Path +from uuid import uuid4 +from PIL import Image, ImageEnhance, ImageDraw, ImageFont import numpy as np + # Importing the modules needed for each supported printer Type import escpos.printer from brother_ql.models import ModelsManager @@ -386,8 +389,9 @@ class BrotherPrinter(Printer): """ def __init__(self, app, vendor_id, device_id): + super().__init__( - app, vendor_id, device_id, printer_type=PrinterType.BROTHER + app, vendor_id, device_id, printer_type=PrinterType.BROTHER ) self.printer = None self.usb_args = {} @@ -450,16 +454,21 @@ class BrotherPrinter(Printer): def _state(self): return self.ready + def _print_txt(self, msg, signature="", bold=False): + """Print text after converting it to an image""" + self._print_img(text_to_image(msg)) + def _print_img(self, data): """ Print a raster image via a Brother QL printer """ - label_type = "102" + # TODO : make this label type go in the configuration + label_type = "62" rotate = 0 dither = False data = _process_image(self, path=data, max_width=720, max_height=720) - + self.app.logger.debug("Printing image %s", data) try: # Prepare the image for printing qlr = BrotherQLRaster(self.printer_info["model"]) @@ -545,6 +554,9 @@ class BrotherPrinter(Printer): case "cut": # The cut happens by default on Brother QL printers. pass + case "text": + self._print_txt(data["txt"], signature=data["sign"]) + self.ready = True case _: raise RuntimeError("This task type is not supported") except Exception as e: @@ -559,56 +571,128 @@ class BrotherPrinter(Printer): def _process_image(self, path, max_width=575, max_height=1000): - brightness_factor = 1.5 # Used only if image is too dark - brightness_threshold = 100 # Brightness threshold (0–255) - contrast_factor = 2 # Less than 1.0 = lower contrast + brightness_threshold = 100 + contrast_factor = 2.0 - with Image.open(path) as original_img: - # Convert to RGB if needed (JPEG doesn't support alpha) - if original_img.mode in ("RGBA", "P"): - self.app.logger.debug("Converting the image from RGBA to RGBA") - original_img = original_img.convert("RGB") - - # Resize while maintaining aspect ratio - original_img = original_img.resize((max_width, max_height), Image.Resampling.LANCZOS) - self.app.logger.debug("Resized the image") - - # # Convert to grayscale for dithering - # dithered_img = original_img.convert("L").convert("1") - # Dithering using default method (Floyd–Steinberg) - # self.app.logger.debug("Dithered the image") - - # Compute brightness of original image (grayscale average) - grayscale = original_img.convert("L") - avg_brightness = np.array(grayscale).mean() - self.app.logger.debug( - "Average brightness of the image : " + str(avg_brightness) + with Image.open(path) as img: + # Preserve aspect ratio while fitting within the maximum dimensions. + img.thumbnail( + (max_width, max_height), + Image.Resampling.LANCZOS, + ) + + grayscale = img.convert("L") + + avg_brightness = np.asarray(grayscale, dtype=np.float32).mean() + self.app.logger.debug( + "Average brightness of the image: %.2f", avg_brightness ) - # Dynamically compute brightness factor if too dark if avg_brightness < brightness_threshold: - brightness_factor = ( - 1 + (brightness_threshold - avg_brightness) / brightness_threshold - ) brightness_factor = min( - max(brightness_factor, 1.1), 2.5 - ) # Clamp between 1.1 and 2.5 - self.app.logger.debug( - f"Image too dark, increasing brightness by a factor of {brightness_factor:.2f}" + max( + 1 + (brightness_threshold - avg_brightness) + / brightness_threshold, + 1.1, + ), + 2.5, + ) + self.app.logger.debug( + "Image is dark; increasing brightness by %.2f", + brightness_factor, + ) + grayscale = ImageEnhance.Brightness(grayscale).enhance( + brightness_factor ) - enhancer = ImageEnhance.Brightness(grayscale) - grayscale = enhancer.enhance(brightness_factor) - # Computer current contrast of grayscale image - contrast = np.clip(np.std(np.array(grayscale)), 0, 255) - self.app.logger.debug("Standard deviation of the contrast : %s", contrast) - # # Enhance contrast - contrast_enhancer = ImageEnhance.Contrast(grayscale) - original_img = contrast_enhancer.enhance(contrast_factor) + contrast = float(np.asarray(grayscale, dtype=np.float32).std()) + self.app.logger.debug("Grayscale standard deviation: %.2f", contrast) + + # Save the enhanced result. + processed = ImageEnhance.Contrast(grayscale).enhance(contrast_factor) - # Convert to JPEG and save jpeg_path = os.path.splitext(path)[0] + "_processed.jpg" - grayscale.save(jpeg_path, format="JPEG", quality=95, optimize=True) - self.app.logger.debug("Processed and saved image.") + processed.save(jpeg_path, format="JPEG", quality=95, optimize=True) + self.app.logger.debug("Processed and saved image: %s", jpeg_path) return jpeg_path + +def text_to_image( + text, + width=400, + font_path="src/static/font/MonospaceTypewriter.ttf", + font_size=24, + padding=10, + line_spacing=4, + background="white", + fill="black", + output_dir="src/static/uploads", +): + """Converts a string to an image""" + + font = ImageFont.truetype(font_path, font_size) + + # Measure and wrap text by pixel width. + measure_img = Image.new("RGB", (1, 1)) + draw = ImageDraw.Draw(measure_img) + max_text_width = width - 2 * padding + if max_text_width <= 0: + raise ValueError("width must be greater than twice the padding") + + lines = [] + for paragraph in text.splitlines() or [""]: + words = paragraph.split() + if not words: + lines.append("") + continue + + line = "" + for word in words: + candidate = f"{line} {word}".strip() + if draw.textbbox((0, 0), candidate, font=font)[2] <= max_text_width: + line = candidate + continue + + if line: + lines.append(line) + + # Split a word that is wider than the available width. + line = "" + piece = "" + for char in word: + if piece and draw.textbbox((0, 0), piece + char, font=font)[2] > max_text_width: + lines.append(piece) + piece = char + else: + piece += char + line = piece + + if line: + lines.append(line) + + if not lines: + lines = [""] + + ascent, descent = font.getmetrics() + line_height = ascent + descent + height = ( + 2 * padding + + len(lines) * line_height + + (len(lines) - 1) * line_spacing + ) + + image = Image.new("RGB", (width, height), background) + draw = ImageDraw.Draw(image) + + y = padding + for line in lines: + draw.text((padding, y), line, font=font, fill=fill) + y += line_height + line_spacing + + upload_dir = Path(output_dir) + upload_dir.mkdir(parents=True, exist_ok=True) + + image_path = upload_dir / f"{uuid4().hex}.png" + image.save(image_path, format="PNG") + + return str(image_path) \ No newline at end of file diff --git a/src/printers.py b/src/printers.py index 41e3750..af6bc5c 100644 --- a/src/printers.py +++ b/src/printers.py @@ -7,6 +7,7 @@ the methods expected from printers. from collections.abc import Set import time +import threading import usb.core import usb.util from printer import Printer, EscPosPrinter, BrotherPrinter @@ -19,10 +20,11 @@ class Printers: def __init__(self, app): """ - Discover printers connected to the computer and return a Collection of Printer() + Discovers printers connected to the computer """ self.app = app - self.printers = self._discover_printers() + self.printers = list(self._discover_printers()) + self._lock = threading.Lock() def _discover_printers(self) -> Set[Printer]: """ @@ -40,25 +42,36 @@ class Printers: printers = set() - # Find all connected USB devices - devices = usb.core.find(find_all=True, custom_match=_FindClass(7)) - 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: + # TODO : this could go in the config ? + waiting_time_for_printer = 5 + + while True: + # Find all connected USB devices + # the find() fnc returns a generator + devices = list( + usb.core.find( + find_all=True, + custom_match=_FindClass(self.app,7) + ) + ) + + if devices: break + if waiting_time_for_printer <= 0: + self.app.logger.warning( + "No USB devices of class 7 ( printers ) found or pyusb could not access the bus." + ) + raise RuntimeError( + "No USB devices of class 7 ( printers ) found or pyusb could not access the bus." + ) - if not list(devices): self.app.logger.warning( - "No USB devices of class 7 ( printers ) found or pyusb could not access the bus." - ) - raise RuntimeError( - "No USB devices of class 7 ( printers ) found or pyusb could not access the bus." - ) + "No printers found; waiting for a printer to connect..." + ) + + time.sleep(1) + waiting_time_for_printer -= 1 for dev in devices: # Attempt to get the manufacturer and product strings @@ -107,8 +120,7 @@ class Printers: self.app.logger.debug("Trying to create a new BROTHER printer") brother_printer = BrotherPrinter( - self.app, vendor_id=dev.idVendor, device_id=dev.idProduct - ) + 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, @@ -129,43 +141,45 @@ class Printers: return printers - def any(self) -> Printer: - """ - Return a dict key: UUID, value: Printer, with any connected printer. - """ - if len(self.printers) > 0: - for i in self.printers: - return i - else: - raise RuntimeError("No printers available") + def acquire_available_printer(self, start_index=0): + """Atomically acquire and return an available printer, or None.""" + printers = self.printers + if not printers: + self.app.logger.error("No printers available for acquisition") + return None - # def get_printer(self, printer_type): - # """ - # Return a specific printer + with self._lock: + for offset in range(len(printers)): + index = (start_index + offset) % len(printers) + printer = printers[index] - # printer_type -- a printer type - # """ - # return NotImplementedError() + if not printer.used: + printer.use_printer() + return index, printer + return None class _FindClass: """ Use by usb.core to modify the way USB devices are found Taken from pyUSB documentation on Github """ - def __init__(self, class_): + def __init__(self, app, class_): self._class = class_ + self.app = app def __call__(self, device): # first, let's check the device + self.app.logger.debug("Found a USB device of class %s", str(device.bDeviceClass)) if device.bDeviceClass == self._class: return True # ok, transverse all devices to find an # interface that matches our class - for cfg in device: - # find_descriptor: what's it? - intf = usb.util.find_descriptor(cfg, bInterfaceClass=self._class) - if intf is not None: + for configuration in device: + # Every device can have one or more configuration, + interface = usb.util.find_descriptor(configuration, bInterfaceClass=self._class) + # each config can have several interfaces + if interface is not None: return True return False diff --git a/src/static/font/MonospaceTypewriter.ttf b/src/static/font/MonospaceTypewriter.ttf new file mode 100644 index 0000000000000000000000000000000000000000..29e9a6795d2b66abc6ff32b2addd1002cc1eaa5d GIT binary patch literal 38748 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