Compare commits

6 Commits

Author SHA1 Message Date
n07070
a2d1779e2b Update worker to get differents printer types 2026-06-03 23:55:04 +02:00
n07070
f841cd5628 Add queue completion method, add Printer discovery and types 2026-06-03 23:54:15 +02:00
n07070
db7e030a1f Remove printer import, remove vendor&device configuration, update
print_queue
2026-06-03 23:53:02 +02:00
n07070
1218d3fbee Remove unused import, add get_queue_completed method 2026-06-03 23:52:23 +02:00
n07070
ef613b3c10 Remove print, add task Typing 2026-06-03 23:51:38 +02:00
n07070
e549cdc64b Remove vendor and device ID from configuration because it's not needed
anymore
2026-06-03 23:51:13 +02:00
8 changed files with 468 additions and 155 deletions

View File

@@ -5,8 +5,6 @@ signature = "Anonymous"
# Printer settings # Printer settings
[printer] [printer]
vendor_id = 0x04b8
device_id = 0x0e28
upload_folder = "src/static/uploads" upload_folder = "src/static/uploads"
# Raspberry Pi Configuration # Raspberry Pi Configuration

View File

@@ -36,7 +36,6 @@ import werkzeug.exceptions
from flask_socketio import SocketIO from flask_socketio import SocketIO
from flask_limiter import Limiter from flask_limiter import Limiter
from flask_limiter.util import get_remote_address from flask_limiter.util import get_remote_address
from printer import Printer # The wrapper for the printer class
from raspberry import Raspberry # The Raspberry pi control Class from raspberry import Raspberry # The Raspberry pi control Class
from web import Web # Wrapper for the web routes and API from web import Web # Wrapper for the web routes and API
from print_queue import PrintQueue from print_queue import PrintQueue
@@ -73,11 +72,7 @@ except OSError as e:
app.logger.debug("Config file loaded !") app.logger.debug("Config file loaded !")
# Define the USB connections here.
vendor_id = configuration_file["printer"]["vendor_id"]
device_id = configuration_file["printer"]["device_id"]
UPLOAD_FOLDER = str(configuration_file["printer"]["upload_folder"]) UPLOAD_FOLDER = str(configuration_file["printer"]["upload_folder"])
try: try:
os.mkdir(UPLOAD_FOLDER) os.mkdir(UPLOAD_FOLDER)
app.logger.debug("Directory %s created successfully.", UPLOAD_FOLDER) app.logger.debug("Directory %s created successfully.", UPLOAD_FOLDER)
@@ -98,13 +93,12 @@ app.config["ALLOWED_EXTENSIONS"] = ALLOWED_EXTENSIONS
app.config["MAX_CONTENT_LENGTH"] = 10 * 1000 * 1000 # Maximum 3Mb for a file upload app.config["MAX_CONTENT_LENGTH"] = 10 * 1000 * 1000 # Maximum 3Mb for a file upload
app.config["TEMPLATES_AUTO_RELOAD"] = True app.config["TEMPLATES_AUTO_RELOAD"] = True
# Printer connection # Queue creation
# Uses the class defined in the printer.py file print_queue = PrintQueue(app)
printer = Printer(app, 0x04B8, 0x0E28)
# Find out if we are running on a Raspberry Pi # Find out if we are running on a Raspberry Pi
rpi = Raspberry( rpi = Raspberry(
printer, print_queue,
app, app,
socketio, socketio,
configuration_file["rpi"]["button_gpio_port_number"], configuration_file["rpi"]["button_gpio_port_number"],
@@ -112,18 +106,20 @@ rpi = Raspberry(
configuration_file["rpi"]["flash_gpio_port_number"], configuration_file["rpi"]["flash_gpio_port_number"],
configuration_file["rpi"]["flash"], configuration_file["rpi"]["flash"],
) )
RASPBERRY_PI_CONNECTED = rpi.is_raspberry_pi() RASPBERRY_PI_CONNECTED = rpi.is_raspberry_pi()
# Queue creation
print_queue = PrintQueue(app)
# Web & API management # Web & API management
web = Web(app, print_queue) web = Web(app, print_queue)
# Start worker thread # Start worker thread
worker = PrintWorker(app, print_queue, printer, socketio) # When created, the worker will try to find printers connected to the system
try:
worker = PrintWorker(app, print_queue, socketio)
worker.start() worker.start()
except Exception as e:
app.logger.error("Could not start the worker because %s ", str(e))
sys.exit(-1)
# The rate limit # The rate limit
limiter = Limiter( limiter = Limiter(
@@ -303,6 +299,7 @@ def api_print_image():
return "OK", 200 return "OK", 200
# TODO: This might not depend on the Raspberry Pi
@app.route("/api/camera/picture", methods=["GET"]) @app.route("/api/camera/picture", methods=["GET"])
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"""
@@ -320,6 +317,10 @@ 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"])
def api_queue_completed():
"""API endpoint that returns the finished tasks"""
return jsonify(web.get_queue_completed())
@app.route("/api/worker", methods=["GET"]) @app.route("/api/worker", methods=["GET"])
def api_worker_state(): def api_worker_state():

View File

@@ -76,7 +76,13 @@ class PrintQueue:
"""Return current queue state""" """Return current queue state"""
with self._lock: with self._lock:
self.app.logger.debug("Return current queue state") self.app.logger.debug("Return current queue state")
return [{"task_id": t.task_id, "status": t.status} for t in self._queue] return [{"task_id": t.task_id, "status": t.status, "type": str(t.task_type) } for t in self._queue]
def get_queue_completed(self):
"""Return completed queue elements"""
with self._lock:
self.app.logger.debug("Return completed queue elements")
return self._completed_tasks
def get_status(self, task_id): def get_status(self, task_id):
"""Get full status info for a task""" """Get full status info for a task"""

View File

@@ -1,13 +1,15 @@
""" """
This class manages connexion to a Printer This class manages connexion to a Printer
""" """
# import brother_ql
from time import sleep from time import sleep
import os.path import os.path
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum from enum import Enum
import uuid import uuid
import usb.core
import threading
from PIL import Image, ImageEnhance from PIL import Image, ImageEnhance
import numpy as np import numpy as np
@@ -15,20 +17,49 @@ import numpy as np
# Importing the modules needed for each supported printer Type # Importing the modules needed for each supported printer Type
import escpos.printer import escpos.printer
import brother_ql from brother_ql.models import ModelsManager
from brother_ql.backends import backend_factory
from brother_ql import labels
from brother_ql.raster import BrotherQLRaster
from brother_ql.conversion import convert
from brother_ql.backends.helpers import send
class PrinterCapabilities(Enum): class PrinterType(Enum):
""" """
What are the capacities of a Printer ? What are the capacities of a Printer ?
""" """
EPSON = "epson"
BROTHER = "brother"
# For Brother-QL Printers
@dataclass
class PrinterInfo:
identifier: str
backend: str
protocol: str
vendor_id: str
product_id: str
serial_number: str
name: str = "Brother QL Printer"
model: str = "QL-570"
status: str = "unknown"
label_type: str = "unknown"
label_size : str = "unknown"
label_width: int = 0
label_height: int = 0
def __getitem__(self, item):
return getattr(self, item)
def __setitem__(self, key, value):
setattr(self, key, value)
class Printer(ABC): class Printer(ABC):
""" """
If it outputs printed paper and speaks like a printer, then it must be a printer. If it outputs printed paper and speaks like a printer, then it must be a printer.
""" """
def __init__(self, app, vendor_id, device_id): def __init__(self, app, vendor_id, device_id, printer_type: PrinterType):
""" """
We initialize a Printer via it's USB connexion, and generate a unique ID We initialize a Printer via it's USB connexion, and generate a unique ID
""" """
@@ -37,6 +68,8 @@ class Printer(ABC):
self.vendor_id = vendor_id self.vendor_id = vendor_id
self.device_id = device_id self.device_id = device_id
self.ready = False self.ready = False
self.printer_type = printer_type
self._lock = threading.Lock()
@abstractmethod @abstractmethod
def _has_paper(self) -> bool: def _has_paper(self) -> bool:
@@ -53,69 +86,41 @@ class Printer(ABC):
class EscPosPrinter(Printer): class EscPosPrinter(Printer):
""" """
# The connection is based on the ESC/POS library Create a new ESC/POS based printer.
## Connection to the USB printer
## Making sure the printer is alive
## Making sure it has paper
## Define default print settings
## Print starting message, log time of first print, cut.
## Annonce readyness : return a positive pong message.
""" """
def __init__(self, app): def __init__(self, app, vendor_id, device_id):
super().__init__(app, vendor_id="",device_id="") """
Create a connexion to a ESC/POS Printer via USB,
Making sure the printer is alive,
Making sure it has paper,
Define default print settings
"""
super().__init__(app,vendor_id,device_id, printer_type=PrinterType.EPSON)
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
if os.getenv("FLASK_DEBUG"):
waiting_elapsed = 3
else:
waiting_elapsed = 10
self.app.logger.debug("Waiting for printer to get online...")
online = False
while not online:
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( p = escpos.printer.Usb(
self.device_id, self.vendor_id, 0, profile="TM-P80" self.vendor_id, self.device_id, 0, profile="TM-P80"
) )
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, trying again %s : %s", "The USB device is not plugged in : %s",
waiting_elapsed,
str(e), str(e),
) )
except Exception as e:
self.app.logger.error("Printer could not be connected : %s ", str(e))
try: try:
if p.is_online(): if p.is_online():
online = True
self.app.logger.debug("Printer online !") self.app.logger.debug("Printer online !")
except escpos.exceptions.DeviceNotFoundError as e: except Exception as e:
self.app.logger.error( raise e
"Error while getting the printer online %s : %s",
waiting_elapsed,
str(e),
)
sleep(1)
waiting_elapsed -= 1
if waiting_elapsed < 1:
self.app.logger.error(
"Printer took more than 30 seconds to get online, aborting..."
)
waiting_elapsed = 1 # Reset the waiting time for the next print.
raise RuntimeError("Could not get Printer %s online" % self.id)
# Setting up the printing options. # Setting up the printing options.
p.set( p.set(
@@ -284,7 +289,7 @@ class EscPosPrinter(Printer):
try: try:
self.printer.open(self.usb_args) self.printer.open(self.usb_args)
self.printer.qr(content, center=True) self.printer.qr(content, center=True)
self.printer.textln(content, center=True) self.printer.textln(content)
self.printer.close() self.printer.close()
except RuntimeError as e: except RuntimeError as e:
self.printer.close() self.printer.close()
@@ -311,21 +316,28 @@ class EscPosPrinter(Printer):
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"""
if self._state(): with self._lock:
if self._state:
self._state = False
match (task_type.value): match (task_type.value):
case "text": case "text":
self._print_txt(data["txt"], signature=data["sign"]) self._print_txt(data["txt"], signature=data["sign"])
self._state = True
case "image": case "image":
self._print_img( self._print_img(
data["img"], signature=data["sign"], process=data["process"] data["img"], signature=data["sign"], process=data["process"]
) )
self._state = True
case "cut": case "cut":
self._cut() self._cut()
self._state = True
case "qr": case "qr":
self._qr(data["txt"]) self._qr(data["txt"])
self._state = True
case _: case _:
raise RuntimeError("This task type is not supported") raise RuntimeError("This task type is not supported")
else:
raise RuntimeError("The printer is not ready to print yet !")
class BrotherPrinter(Printer): class BrotherPrinter(Printer):
""" """
@@ -333,17 +345,156 @@ class BrotherPrinter(Printer):
""" """
def __init__(self, app, vendor_id, device_id): def __init__(self, app, vendor_id, device_id):
super().__init__(app, vendor_id="",device_id="") super().__init__(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.device_id
self.usb_args["idProduct"] = self.vendor_id self.usb_args["idProduct"] = self.vendor_id
self.model_manager = ModelsManager()
def _has_paper(): # Code taken from https://github.com/5shekel/printit/blob/master/printer_utils.py
def _state(): backend = backend_factory("pyusb")
available_devices = backend["list_available_devices"]()
def print_task(): for printer in available_devices:
self.app.logger.debug(f"Found device: {printer}")
identifier = printer["identifier"]
parts = identifier.split("/")
if len(parts) < 4:
self.app.logger.warning(f"Skipping device with invalid identifier format: {identifier}")
continue
protocol = parts[0]
device_info = parts[2]
serial_number = parts[3]
try:
product_id_int = int(self.device_id, 16)
for m in self.model_manager.iter_elements():
if m.product_id == product_id_int:
model = m.identifier
break
self.app.logger.debug(f"Matched printer model: {model}")
except ValueError:
self.app.logger.warning(f"Invalid product ID format: {product_id}")
self.printer_info = PrinterInfo(
identifier=identifier,
backend="pyusb",
model=model,
protocol=protocol,
vendor_id=vendor_id,
product_id=self.device_id,
serial_number=serial_number,
)
self.ready = True
def _has_paper(self):
raise NotImplementedError("This printer model does not support this.")
def _state(self):
return self.ready
def _print_img(self,data):
"""
Print a raster image via a Brother QL printer
"""
self.ready = False
label_type = "102"
rotate = 0
dither = False
try:
# Prepare the image for printing
qlr = BrotherQLRaster(self.printer_info["model"])
instructions = convert(
qlr=qlr,
images=[data["img"]],
label=label_type,
rotate=rotate,
threshold=70,
dither=dither,
compress=True,
red=False,
dpi_600=False,
hq=False,
cut=True,
)
# Debug logging
if FLASK_DEBUG:
self.app.logger.debug(f"""
Print parameters:
- Label type: {label_type}
- Rotate: {rotate}
- Dither: {dither}
- Model: {self.printer_info['model']}
- Backend: {self.printer_info['backend']}
- Identifier: {self.printer_info['identifier']}
""")
# Try to print using Python API
# send() = status = {
# 'instructions_sent': True, # The instructions were sent to the printer.
# 'outcome': 'unknown', # String description of the outcome of the sending operation like: 'unknown', 'sent', 'printed', 'error'
# 'printer_state': None, # If the selected backend supports reading back the printer state, this key will contain it.
# 'did_print': False, # If True, a print was produced. It defaults to False if the outcome is uncertain (due to a backend without read-back capability).
# 'ready_for_next_job': False, # If True, the printer is ready to receive the next instructions. It defaults to False if the state is unknown.
# }
status = send(
instructions=instructions,
printer_identifier= self.printer_info["identifier"],
backend_identifier="pyusb"
)
if not status["did_print"] or status["outcome"] == "error" or status["outcome"] == "unknown":
raise RuntimeError("Failed to print using Python API")
if status["printer_state"]:
self.ready = bool(status["printer_state"])
else:
self.ready = True
except usb.core.USBError as e:
# Treat timeout errors as successful since they often occur after print completion
if e.errno == 110: # Operation timed out
self.app.logger.debug("USB timeout occurred - this is normal and the print likely completed")
self.app.logger.debug("Print completed (timeout is normal)")
self.ready = True
error_msg = f"USBError encountered: {e}"
self.app.logger.debug(error_msg)
raise RuntimeError from e
except Exception as e:
error_msg = f"Unexpected error during printing: {str(e)}"
self.app.logger.debug(error_msg)
raise RuntimeError from e
def print_task(self, task_type, data):
"""Execute actual print based on task type"""
with self._lock:
if self._state:
self._state = False
match (task_type.value):
case "image":
self._print_img(data["img"])
self._state = True
case "cut":
# The cut happens by default on Brother QL printers.
self._state = True
case _:
raise RuntimeError("This task type is not supported")
else:
raise RuntimeError("The printer is not ready to print yet !")
# These values are by default for now
# raise NotImplementedError("This printer type is not implemented yet")
def _process_image(self, path): def _process_image(self, path):
brightness_factor = 1.5 # Used only if image is too dark brightness_factor = 1.5 # Used only if image is too dark

View File

@@ -3,16 +3,129 @@ A collection of Printers.
It has methods to discover printers, and provides an interface for the methods expected from printers. It has methods to discover printers, and provides an interface for the methods expected from printers.
""" """
from collections.abc import Mapping, Set
from printer import Printer import usb.core
import usb.util
from printer import Printer, EscPosPrinter, BrotherPrinter, PrinterType
class Printers(): class Printers():
""" """
A collection of Printers Finds and creates a set of Printer that can be used by the Workers to print.
""" """
def __init__(self, name, age): def __init__(self, app):
self.name = name """
self.age = age Discover printers connected to the computer and return a Collection of Printer()
"""
self.app = app
self.printers = self._discover_printers()
def _discover_printers(self): def _discover_printers(self) -> Set[Printer]:
pass """
Gets connected USB printer devices using the pyusb library.
We analyse the USB devices, get the ones that match
the printer class ( 7 ) and for Brother and EPSON printers,
try to create a Printer object that can be used by the Worker class
to execute prints.
Returns a set of Printer
"""
self.app.logger.debug("Discovering USB Devices connected to this system")
printers = set()
# Find all connected USB devices
devices = usb.core.find(find_all=True,custom_match=_FindClass(7))
if not 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.")
for dev in devices:
# Attempt to get the manufacturer and product strings
try:
manufacturer = usb.util.get_string(dev, dev.iManufacturer)
except Exception:
manufacturer = "Unknown"
try:
product = usb.util.get_string(dev, dev.iProduct)
except Exception:
product = "Unknown"
self.app.logger.debug("Looking at %s %s (%s:%s)", manufacturer, product, hex(dev.idVendor), hex(dev.idProduct))
if manufacturer == "EPSON":
try:
# We create a new EscPosPrinter()
self.app.logger.debug("Trying to creat a new EPSON printer")
prid = dev.idProduct
vendir = dev.idVendor
escpos_printer = EscPosPrinter(self.app, vendor_id=vendir, device_id=prid)
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 )
# 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 creat a new BROTHER printer")
prid = dev.idProduct
vendir = dev.idVendor
brother_printer = BrotherPrinter(self.app, vendor_id=vendir,device_id=prid)
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))
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 get_printer(self, printer_type):
"""
Return a specific printer
printer_type -- a printer type
"""
return NotImplementedError()
class _FindClass():
def __init__(self, class_):
self._class = class_
def __call__(self, device):
# first, let's check the device
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:
return True
return False

View File

@@ -38,13 +38,11 @@ class PrintTask(ABC):
A print task holds information about what we are looking to print. A print task holds information about what we are looking to print.
""" """
def __init__(self, task_type): def __init__(self, task_type: TaskType):
self.task_id = self._generate_id() self.task_id = self._generate_id()
self.task_type = task_type self.task_type = task_type
self.status = "pending" # pending, processing, completed, failed self.status = "pending" # pending, processing, completed, failed
print("Created a new " + str(self.task_type) + " with ID " + self.task_id)
@abstractmethod @abstractmethod
def get_print_data(self): def get_print_data(self):
"""Return data formatted for printer""" """Return data formatted for printer"""

View File

@@ -1,7 +1,6 @@
import os
from flask import flash from flask import flash
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import time
import os
from task import TextTask, ImageTask, CutTask from task import TextTask, ImageTask, CutTask
@@ -99,11 +98,12 @@ class Web(object):
+ str(os.path.join(self.app.config["UPLOAD_FOLDER"], filename)) + str(os.path.join(self.app.config["UPLOAD_FOLDER"], filename))
) )
return True return True
else:
self.app.logger.error( self.app.logger.error(
"Could not save file because the filename is forbidden" "Could not save file because the filename is forbidden"
) )
return False return False
else: else:
self.app.logger.error( self.app.logger.error(
"Could not save file, it seems to be null ? : " + str(filename) "Could not save file, it seems to be null ? : " + str(filename)
@@ -113,3 +113,7 @@ class Web(object):
def get_queue_state(self): def get_queue_state(self):
"""Return current queue state""" """Return current queue state"""
return self.print_queue.get_queue_state() return self.print_queue.get_queue_state()
def get_queue_completed(self):
"""Return completed queue elements"""
return self.print_queue.get_queue_completed()

View File

@@ -4,32 +4,68 @@
import threading import threading
import time import time
from printers import Printers
class PrintWorker(threading.Thread): class PrintWorker(threading.Thread):
def __init__(self, app, print_queue, printer, socketio=None): def __init__(self, app, print_queue, socketio=None):
super().__init__(daemon=True) super().__init__(daemon=True)
self.app = app self.app = app
self.print_queue = print_queue self.print_queue = print_queue
self.printer = printer self.printer = None
self._lock = threading.Lock()
self.socketio = socketio # Optional self.socketio = socketio # Optional
self.running = True self.running = True
self.state = "idle" # idle, printing, dead, drinking-a-beer self.state = "idle" # idle, printing, dead, drinking-a-beer
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...")
try:
self.printers = Printers(self.app)
self.printers_obj = self.printers.printers
self.printers = iter(self.printers.printers)
except RuntimeError as e:
self.app.logger.warning("Could not get any Printers")
raise e
def run(self): def run(self):
"""Background thread that processes queue items""" """Background thread that processes queue items"""
self.app.logger.info("Worker started working.") self.app.logger.debug("Worker %s started working.", threading.get_ident())
self.app.logger.debug("Current threads : %s" % threading.active_count())
self.app.logger.debug("Threads actives : %s " % threading.enumerate())
while True: while True:
if not self.running or not self.printer.ready:
# If the printer is dead or asleep, it can't work.
if not self.running:
time.sleep(0.2) time.sleep(0.2)
continue continue
# If we have no available printer, we look at the list printers we know about, and try to find one that is available.
# When we find a printer, we acquire it
# When we are finished with a printer, we release it to the world.
while not self.printer or not self.printer.ready:
time.sleep(1)
try:
self.app.logger.debug("Changing printers")
self.printer = next(self.printers)
self.app.logger.debug("The worker got a %s printer and it's %s", self.printer.printer_type, "Ready" if self.printer.ready else "Not ready")
except Exception as e:
self.app.logger.error(str(e))
self.printer = None
if self.state != "idle":
self.app.logger("We are not idle, waiting...")
time.sleep(1)
continue
self.state = "printing"
with self._lock:
try: try:
task = self.print_queue.dequeue() task = self.print_queue.dequeue()
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"
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
@@ -38,26 +74,31 @@ class PrintWorker(threading.Thread):
try: try:
self.app.logger.info("Got a new task") self.app.logger.info("Got a new task")
self.app.logger.debug("Got task %s", task.task_id) self.app.logger.debug("Got task %s", task.task_id)
self.state = "printing"
task.status = "processing" task.status = "processing"
self._emit_status(task.task_id, "processing") self._emit_status(task.task_id, "processing")
print_data = task.get_print_data() print_data = task.get_print_data()
try: try:
self.printer.print_task(task.task_type, print_data) self.printer.print_task(task.task_type, print_data)
except RuntimeError as e: except RuntimeError as e:
self.state = "idle"
self.app.logger.error("Could not print : %s", str(e)) self.app.logger.error("Could not print : %s", str(e))
raise e 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")
self._emit_status(task.task_id, "completed") self._emit_status(task.task_id, "completed")
self.app.logger.debug("Finished printing task %s " % task.task_id)
self.state = "idle"
except RuntimeError as e: except RuntimeError as e:
task.status = "failed" task.status = "failed"
self.state = "idle"
self.print_queue.mark_completed(task.task_id, "failed") self.print_queue.mark_completed(task.task_id, "failed")
self._emit_status(task.task_id, "failed", error=str(e)) self._emit_status(task.task_id, "failed", error=str(e))
print(f"Print task {task.task_id} failed: {e}") self.app.logger.error("Could not print task %s because %s " % task.task_id, str(e))
else: else:
# When they are no new tasks to handle, we put the thread to sleep. # When they are no new tasks to handle, we put the thread to sleep.
self.state = "idle" self.state = "idle"
@@ -104,4 +145,5 @@ class PrintWorker(threading.Thread):
"is_running": self.running, "is_running": self.running,
"queue_size": len(self.print_queue), "queue_size": len(self.print_queue),
"state": self.state, "state": self.state,
"printers": len(self.printers_obj),
} }