Add support for signature on brother printers
This commit is contained in:
24
src/main.py
24
src/main.py
@@ -1,7 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Welcome to the LittlePrynter's source code.
|
Welcome to the LittlePrynter's source code.
|
||||||
This program expose a web interface, with user authentification,
|
This program expose a web interface,
|
||||||
that makes it possible to print messages from the web.
|
that makes it possible to print messages from the web.
|
||||||
|
|
||||||
It also exposes a API, making it possible to print and interface
|
It also exposes a API, making it possible to print and interface
|
||||||
with much of the printer's abilities.
|
with much of the printer's abilities.
|
||||||
|
|
||||||
@@ -10,12 +11,14 @@ Then we build the API around Flask,
|
|||||||
Then we build the web interface, using the simple Jinja2 templating.
|
Then we build the web interface, using the simple Jinja2 templating.
|
||||||
|
|
||||||
We support two modes :
|
We support two modes :
|
||||||
|
|
||||||
The first is a simple mode, where a computer, connected to a thermal printer,
|
The first is a simple mode, where a computer, connected to a thermal printer,
|
||||||
runs this program and exposes a web interface that makes use of the client's camera
|
runs this program and exposes a web interface that makes use of the client's camera
|
||||||
The seconde is booth mode, where a Raspberry Pi is connected to a thermal printer,
|
|
||||||
a button and a flash.
|
The seconde is booth mode, where a Raspberry Pi is connected to
|
||||||
The web interface exists but may not be used, as the press of the button with take
|
a thermal printer, a button and a flash.
|
||||||
a picture and activate the flash while simply informing the web page.
|
The web interface exists but it's not needed, as the press of the button
|
||||||
|
takes a picture and activates the flash while simply informing the web page.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Following are the librairies we import,
|
# Following are the librairies we import,
|
||||||
@@ -98,11 +101,7 @@ app.config["TEMPLATES_AUTO_RELOAD"] = True
|
|||||||
print_queue = PrintQueue(app)
|
print_queue = PrintQueue(app)
|
||||||
|
|
||||||
# Find out if we are running on a Raspberry Pi
|
# Find out if we are running on a Raspberry Pi
|
||||||
rpi = Raspberry(
|
rpi = Raspberry(print_queue, app, configuration_file)
|
||||||
print_queue,
|
|
||||||
app,
|
|
||||||
configuration_file
|
|
||||||
)
|
|
||||||
RASPBERRY_PI_CONNECTED = rpi.is_raspberry_pi()
|
RASPBERRY_PI_CONNECTED = rpi.is_raspberry_pi()
|
||||||
|
|
||||||
|
|
||||||
@@ -134,6 +133,7 @@ limiter = Limiter(
|
|||||||
|
|
||||||
app.logger.info("🖶 Welcome to LittlePrynter !")
|
app.logger.info("🖶 Welcome to LittlePrynter !")
|
||||||
|
|
||||||
|
|
||||||
# General routes
|
# General routes
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
@limiter.limit("1/second", override_defaults=False)
|
@limiter.limit("1/second", override_defaults=False)
|
||||||
@@ -233,7 +233,6 @@ def web_print_img():
|
|||||||
except werkzeug.exceptions.BadRequestKeyError:
|
except werkzeug.exceptions.BadRequestKeyError:
|
||||||
process_image = False
|
process_image = False
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -287,7 +286,6 @@ def api_print_sms():
|
|||||||
except werkzeug.exceptions.BadRequestKeyError:
|
except werkzeug.exceptions.BadRequestKeyError:
|
||||||
do_cut = False
|
do_cut = False
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -390,11 +388,13 @@ def api_worker_stop():
|
|||||||
worker.stop_worker()
|
worker.stop_worker()
|
||||||
return jsonify(worker.current_state())
|
return jsonify(worker.current_state())
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/printers")
|
@app.route("/api/printers")
|
||||||
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())
|
||||||
|
|
||||||
|
|
||||||
## Authentification
|
## Authentification
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,12 @@ class PrintQueue:
|
|||||||
with self._lock:
|
with self._lock:
|
||||||
self.app.logger.debug("Return current queue state")
|
self.app.logger.debug("Return current queue state")
|
||||||
return [
|
return [
|
||||||
{"task_id": t.task_id, "status": t.status, "type": str(t.task_type), "content": str(t.get_print_data())}
|
{
|
||||||
|
"task_id": t.task_id,
|
||||||
|
"status": t.status,
|
||||||
|
"type": str(t.task_type),
|
||||||
|
"content": str(t.get_print_data()),
|
||||||
|
}
|
||||||
for t in self._queue
|
for t in self._queue
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
152
src/printer.py
152
src/printer.py
@@ -16,7 +16,6 @@ from uuid import uuid4
|
|||||||
from PIL import Image, ImageEnhance, ImageDraw, ImageFont
|
from PIL import Image, ImageEnhance, ImageDraw, ImageFont
|
||||||
import numpy as np
|
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
|
||||||
from brother_ql.models import ModelsManager
|
from brother_ql.models import ModelsManager
|
||||||
@@ -42,6 +41,7 @@ class PrinterInfo:
|
|||||||
"""
|
"""
|
||||||
Brother-QL printer information
|
Brother-QL printer information
|
||||||
"""
|
"""
|
||||||
|
|
||||||
identifier: str
|
identifier: str
|
||||||
backend: str
|
backend: str
|
||||||
protocol: str
|
protocol: str
|
||||||
@@ -94,13 +94,14 @@ class Printer(ABC):
|
|||||||
"""Takes a PrintTask and executes it"""
|
"""Takes a PrintTask and executes it"""
|
||||||
|
|
||||||
def use_printer(self) -> None:
|
def use_printer(self) -> None:
|
||||||
"""Marks the printer as being used """
|
"""Marks the printer as being used"""
|
||||||
self.used = True
|
self.used = True
|
||||||
|
|
||||||
def free_printer(self) -> None:
|
def free_printer(self) -> None:
|
||||||
"""Marks the printer as being not used, it's available."""
|
"""Marks the printer as being not used, it's available."""
|
||||||
self.used = False
|
self.used = False
|
||||||
|
|
||||||
|
|
||||||
class EscPosPrinter(Printer):
|
class EscPosPrinter(Printer):
|
||||||
"""
|
"""
|
||||||
Create a new ESC/POS based printer.
|
Create a new ESC/POS based printer.
|
||||||
@@ -128,8 +129,10 @@ class EscPosPrinter(Printer):
|
|||||||
"The USB device is not plugged in : %s",
|
"The USB device is not plugged in : %s",
|
||||||
str(e),
|
str(e),
|
||||||
)
|
)
|
||||||
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.app.logger.error("Printer could not be connected : %s ", str(e))
|
self.app.logger.error("Printer could not be connected : %s ", str(e))
|
||||||
|
raise e
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if p.is_online():
|
if p.is_online():
|
||||||
@@ -187,7 +190,6 @@ class EscPosPrinter(Printer):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _print_txt(self, msg, signature="", bold=False):
|
def _print_txt(self, msg, signature="", bold=False):
|
||||||
self.ready = False
|
|
||||||
if not isinstance(msg, str):
|
if not isinstance(msg, str):
|
||||||
self.app.logger.error(
|
self.app.logger.error(
|
||||||
"It is not possible to print a " + str(type(msg)) + ", only strings."
|
"It is not possible to print a " + str(type(msg)) + ", only strings."
|
||||||
@@ -199,6 +201,7 @@ 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) > 4096:
|
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))
|
||||||
@@ -208,7 +211,7 @@ class EscPosPrinter(Printer):
|
|||||||
+ str(len(clean_msg))
|
+ str(len(clean_msg))
|
||||||
+ ", needs to be below 4096 caracters long."
|
+ ", needs to be below 4096 caracters long."
|
||||||
)
|
)
|
||||||
|
# TODO: make the max length configurable
|
||||||
if len(signature) > 256:
|
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))
|
||||||
@@ -239,9 +242,9 @@ class EscPosPrinter(Printer):
|
|||||||
self.ready = True
|
self.ready = True
|
||||||
|
|
||||||
def _print_img(self, path, signature="", center=True, process=False):
|
def _print_img(self, path, signature="", center=True, process=False):
|
||||||
self.ready = False
|
|
||||||
clean_signature = str(signature)
|
clean_signature = str(signature)
|
||||||
|
|
||||||
|
# TODO: make the max length configurable
|
||||||
if len(signature) > 256:
|
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))
|
||||||
@@ -306,7 +309,6 @@ class EscPosPrinter(Printer):
|
|||||||
self.ready = True
|
self.ready = True
|
||||||
|
|
||||||
def _qr(self, content):
|
def _qr(self, content):
|
||||||
self.ready = False
|
|
||||||
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)
|
||||||
@@ -321,7 +323,6 @@ class EscPosPrinter(Printer):
|
|||||||
self.ready = True
|
self.ready = True
|
||||||
|
|
||||||
def _cut(self):
|
def _cut(self):
|
||||||
self.ready = False
|
|
||||||
try:
|
try:
|
||||||
self.printer.open(self.usb_args)
|
self.printer.open(self.usb_args)
|
||||||
self.printer.cut()
|
self.printer.cut()
|
||||||
@@ -338,21 +339,23 @@ class EscPosPrinter(Printer):
|
|||||||
has_paper = self._has_paper()
|
has_paper = self._has_paper()
|
||||||
is_ready = self.ready
|
is_ready = self.ready
|
||||||
|
|
||||||
self.app.logger.debug("Has paper : %s " , has_paper )
|
self.app.logger.debug("Has paper : %s ", has_paper)
|
||||||
self.app.logger.debug("Ready : %s " , is_ready )
|
self.app.logger.debug("Ready : %s ", is_ready)
|
||||||
|
|
||||||
return is_ready and has_paper # and is_online
|
return is_ready and has_paper # and is_online
|
||||||
|
|
||||||
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:
|
||||||
self.app.logger.debug("Acquired lock to start print")
|
self.app.logger.debug("Acquired lock to start print")
|
||||||
|
|
||||||
i_m_ready = self._state()
|
i_m_ready = self._state()
|
||||||
while not i_m_ready:
|
while not i_m_ready:
|
||||||
self.app.logger.debug("Waiting for the printer to become ready, current state %s ", str(i_m_ready))
|
self.app.logger.debug(
|
||||||
|
"Waiting for the printer to become ready, current state %s ",
|
||||||
|
str(i_m_ready),
|
||||||
|
)
|
||||||
i_m_ready = self._state()
|
i_m_ready = self._state()
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
|
|
||||||
@@ -391,9 +394,7 @@ 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.vendor_id
|
self.usb_args["idVendor"] = self.vendor_id
|
||||||
@@ -431,7 +432,7 @@ class BrotherPrinter(Printer):
|
|||||||
|
|
||||||
self.app.logger.debug(f"Matched printer model: {model}")
|
self.app.logger.debug(f"Matched printer model: {model}")
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
self.app.logger.warning("Invalid product ID format:"+ str(ve))
|
self.app.logger.warning("Invalid product ID format:" + str(ve))
|
||||||
raise ve
|
raise ve
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.app.logger.error("Error while finding a model ? : " + str(e))
|
self.app.logger.error("Error while finding a model ? : " + str(e))
|
||||||
@@ -459,13 +460,12 @@ class BrotherPrinter(Printer):
|
|||||||
def _print_txt(self, msg, signature="", bold=False):
|
def _print_txt(self, msg, signature="", bold=False):
|
||||||
"""Print text after converting it to an image"""
|
"""Print text after converting it to an image"""
|
||||||
self._print_img(text_to_image(msg))
|
self._print_img(text_to_image(msg))
|
||||||
|
self._print_img(text_to_image(signature))
|
||||||
|
|
||||||
def _print_img(self, data, 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.ready = False
|
|
||||||
|
|
||||||
if process:
|
if process:
|
||||||
try:
|
try:
|
||||||
self.app.logger.debug("Proccessing the image")
|
self.app.logger.debug("Proccessing the image")
|
||||||
@@ -480,10 +480,13 @@ class BrotherPrinter(Printer):
|
|||||||
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"]
|
||||||
BACKEND_STRING_DESCR = self.printer_info.identifier
|
BACKEND_STRING_DESCR = self.printer_info.identifier
|
||||||
except Exception:
|
except Exception as e:
|
||||||
self.app.logger.error("Couldn't guess the backend to use from the printer string descriptor")
|
self.app.logger.error(
|
||||||
|
"Couldn't guess the backend to use from the printer string descriptor"
|
||||||
|
)
|
||||||
|
raise e
|
||||||
|
|
||||||
try:
|
try:
|
||||||
im = Image.open(processed_path)
|
im = Image.open(processed_path)
|
||||||
@@ -498,20 +501,40 @@ class BrotherPrinter(Printer):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
if signature != "":
|
||||||
|
try:
|
||||||
|
im = Image.open(text_to_image(signature))
|
||||||
|
qlr = BrotherQLRaster(self.printer_info.model)
|
||||||
|
create_label(
|
||||||
|
qlr, im, self.printer_info.label_type, cut=False, dither=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# noinspection PyCallingNonCallable
|
||||||
|
be = BACKEND_CLASS(BACKEND_STRING_DESCR)
|
||||||
|
be.write(qlr.data)
|
||||||
|
be.dispose()
|
||||||
|
del be
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
def _cut(self):
|
def _cut(self):
|
||||||
self.ready = False
|
|
||||||
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"]
|
||||||
BACKEND_STRING_DESCR = self.printer_info.identifier
|
BACKEND_STRING_DESCR = self.printer_info.identifier
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.app.logger.error("Couldn't guess the backend to use from the printer string descriptor")
|
self.app.logger.error(
|
||||||
|
"Couldn't guess the backend to use from the printer string descriptor"
|
||||||
|
)
|
||||||
|
raise e
|
||||||
|
|
||||||
data = "src/static/images/1x1.png"
|
data = "src/static/images/1x1.png"
|
||||||
try:
|
try:
|
||||||
im = Image.open(data)
|
im = Image.open(data)
|
||||||
qlr = BrotherQLRaster(self.printer_info.model)
|
qlr = BrotherQLRaster(self.printer_info.model)
|
||||||
create_label(qlr, im, self.printer_info.label_type, threshold=70, cut=True, rotate=0)
|
create_label(
|
||||||
|
qlr, im, self.printer_info.label_type, threshold=70, cut=True, rotate=0
|
||||||
|
)
|
||||||
|
|
||||||
# noinspection PyCallingNonCallable
|
# noinspection PyCallingNonCallable
|
||||||
be = BACKEND_CLASS(BACKEND_STRING_DESCR)
|
be = BACKEND_CLASS(BACKEND_STRING_DESCR)
|
||||||
@@ -521,32 +544,41 @@ class BrotherPrinter(Printer):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
def _qr(self):
|
||||||
|
# This should not be reached by a print on a BrotherQL
|
||||||
|
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"""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
if self.ready:
|
self.app.logger.debug("Acquired lock to start print")
|
||||||
try:
|
i_m_ready = self._state()
|
||||||
match (task_type.value):
|
while not i_m_ready:
|
||||||
case "image":
|
self.app.logger.debug(
|
||||||
self._print_img(data["img"])
|
"Waiting for the printer to become ready, current state %s ",
|
||||||
self.ready = True
|
str(i_m_ready),
|
||||||
case "cut":
|
)
|
||||||
self._cut()
|
i_m_ready = self._state()
|
||||||
self.ready = True
|
time.sleep(0.3)
|
||||||
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:
|
|
||||||
raise e
|
|
||||||
# end try
|
|
||||||
else:
|
|
||||||
raise RuntimeError("The printer is not ready to print yet !")
|
|
||||||
|
|
||||||
# These values are by default for now
|
self.app.logger.debug("Checked state to start printing : %s", self._state())
|
||||||
|
self.ready = False
|
||||||
|
|
||||||
# raise NotImplementedError("This printer type is not implemented yet")
|
try:
|
||||||
|
match (task_type.value):
|
||||||
|
case "image":
|
||||||
|
self._print_img(data["img"], signature=data["sign"])
|
||||||
|
self.ready = True
|
||||||
|
case "cut":
|
||||||
|
self._cut()
|
||||||
|
self.ready = True
|
||||||
|
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:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def _process_image(self, path, max_width=575, max_height=1000):
|
def _process_image(self, path, max_width=575, max_height=1000):
|
||||||
@@ -563,15 +595,12 @@ def _process_image(self, path, max_width=575, max_height=1000):
|
|||||||
grayscale = img.convert("L")
|
grayscale = img.convert("L")
|
||||||
|
|
||||||
avg_brightness = np.asarray(grayscale, dtype=np.float32).mean()
|
avg_brightness = np.asarray(grayscale, dtype=np.float32).mean()
|
||||||
self.app.logger.debug(
|
self.app.logger.debug("Average brightness of the image: %.2f", avg_brightness)
|
||||||
"Average brightness of the image: %.2f", avg_brightness
|
|
||||||
)
|
|
||||||
|
|
||||||
if avg_brightness < brightness_threshold:
|
if avg_brightness < brightness_threshold:
|
||||||
brightness_factor = min(
|
brightness_factor = min(
|
||||||
max(
|
max(
|
||||||
1 + (brightness_threshold - avg_brightness)
|
1 + (brightness_threshold - avg_brightness) / brightness_threshold,
|
||||||
/ brightness_threshold,
|
|
||||||
1.1,
|
1.1,
|
||||||
),
|
),
|
||||||
2.5,
|
2.5,
|
||||||
@@ -580,9 +609,7 @@ def _process_image(self, path, max_width=575, max_height=1000):
|
|||||||
"Image is dark; increasing brightness by %.2f",
|
"Image is dark; increasing brightness by %.2f",
|
||||||
brightness_factor,
|
brightness_factor,
|
||||||
)
|
)
|
||||||
grayscale = ImageEnhance.Brightness(grayscale).enhance(
|
grayscale = ImageEnhance.Brightness(grayscale).enhance(brightness_factor)
|
||||||
brightness_factor
|
|
||||||
)
|
|
||||||
|
|
||||||
contrast = float(np.asarray(grayscale, dtype=np.float32).std())
|
contrast = float(np.asarray(grayscale, dtype=np.float32).std())
|
||||||
self.app.logger.debug("Grayscale standard deviation: %.2f", contrast)
|
self.app.logger.debug("Grayscale standard deviation: %.2f", contrast)
|
||||||
@@ -596,6 +623,7 @@ def _process_image(self, path, max_width=575, max_height=1000):
|
|||||||
self.app.logger.debug("Processed and saved image: %s", jpeg_path)
|
self.app.logger.debug("Processed and saved image: %s", jpeg_path)
|
||||||
return jpeg_path
|
return jpeg_path
|
||||||
|
|
||||||
|
|
||||||
def text_to_image(
|
def text_to_image(
|
||||||
text,
|
text,
|
||||||
width=400,
|
width=400,
|
||||||
@@ -639,7 +667,11 @@ def text_to_image(
|
|||||||
line = ""
|
line = ""
|
||||||
piece = ""
|
piece = ""
|
||||||
for char in word:
|
for char in word:
|
||||||
if piece and draw.textbbox((0, 0), piece + char, font=font)[2] > max_text_width:
|
if (
|
||||||
|
piece
|
||||||
|
and draw.textbbox((0, 0), piece + char, font=font)[2]
|
||||||
|
> max_text_width
|
||||||
|
):
|
||||||
lines.append(piece)
|
lines.append(piece)
|
||||||
piece = char
|
piece = char
|
||||||
else:
|
else:
|
||||||
@@ -654,11 +686,7 @@ def text_to_image(
|
|||||||
|
|
||||||
ascent, descent = font.getmetrics()
|
ascent, descent = font.getmetrics()
|
||||||
line_height = ascent + descent
|
line_height = ascent + descent
|
||||||
height = (
|
height = 2 * padding + len(lines) * line_height + (len(lines) - 1) * line_spacing
|
||||||
2 * padding
|
|
||||||
+ len(lines) * line_height
|
|
||||||
+ (len(lines) - 1) * line_spacing
|
|
||||||
)
|
|
||||||
|
|
||||||
image = Image.new("RGB", (width, height), background)
|
image = Image.new("RGB", (width, height), background)
|
||||||
draw = ImageDraw.Draw(image)
|
draw = ImageDraw.Draw(image)
|
||||||
@@ -674,4 +702,4 @@ def text_to_image(
|
|||||||
image_path = upload_dir / f"{uuid4().hex}.png"
|
image_path = upload_dir / f"{uuid4().hex}.png"
|
||||||
image.save(image_path, format="PNG")
|
image.save(image_path, format="PNG")
|
||||||
|
|
||||||
return str(image_path)
|
return str(image_path)
|
||||||
|
|||||||
@@ -49,10 +49,7 @@ class Printers:
|
|||||||
# Find all connected USB devices
|
# Find all connected USB devices
|
||||||
# the find() fnc returns a generator
|
# the find() fnc returns a generator
|
||||||
devices = list(
|
devices = list(
|
||||||
usb.core.find(
|
usb.core.find(find_all=True, custom_match=_FindClass(self.app, 7))
|
||||||
find_all=True,
|
|
||||||
custom_match=_FindClass(self.app,7)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if devices:
|
if devices:
|
||||||
@@ -60,15 +57,15 @@ class Printers:
|
|||||||
|
|
||||||
if waiting_time_for_printer <= 0:
|
if waiting_time_for_printer <= 0:
|
||||||
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."
|
||||||
)
|
)
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"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."
|
||||||
)
|
)
|
||||||
|
|
||||||
self.app.logger.warning(
|
self.app.logger.warning(
|
||||||
"No printers found; waiting for a printer to connect..."
|
"No printers found; waiting for a printer to connect..."
|
||||||
)
|
)
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
waiting_time_for_printer -= 1
|
waiting_time_for_printer -= 1
|
||||||
@@ -120,10 +117,12 @@ class Printers:
|
|||||||
self.app.logger.debug("Trying to create a new BROTHER printer")
|
self.app.logger.debug("Trying to create a new BROTHER printer")
|
||||||
|
|
||||||
brother_printer = BrotherPrinter(
|
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:
|
except Exception as e:
|
||||||
self.app.logger.error(
|
self.app.logger.error(
|
||||||
"Could not create a %s printer class with %s:%s" , product,
|
"Could not create a %s printer class with %s:%s",
|
||||||
|
product,
|
||||||
dev.idVendor,
|
dev.idVendor,
|
||||||
dev.idProduct,
|
dev.idProduct,
|
||||||
)
|
)
|
||||||
@@ -131,12 +130,14 @@ class Printers:
|
|||||||
|
|
||||||
# If the object creation is successfull, we add it to the list of Printers
|
# If the object creation is successfull, we add it to the list of Printers
|
||||||
printers.add(brother_printer)
|
printers.add(brother_printer)
|
||||||
self.app.logger.debug("Found a %s printer" , manufacturer)
|
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))
|
||||||
|
|
||||||
if len(printers) < 1:
|
if len(printers) < 1:
|
||||||
self.app.logger.warning("Not printers found ! Please plug in a Printer and restart the program.")
|
self.app.logger.warning(
|
||||||
|
"Not printers found ! Please plug in a Printer and restart the program."
|
||||||
|
)
|
||||||
raise RuntimeError("No printers found")
|
raise RuntimeError("No printers found")
|
||||||
|
|
||||||
return printers
|
return printers
|
||||||
@@ -163,26 +164,32 @@ class Printers:
|
|||||||
"""Return the current list of printers"""
|
"""Return the current list of printers"""
|
||||||
return self.printers
|
return self.printers
|
||||||
|
|
||||||
|
|
||||||
class _FindClass:
|
class _FindClass:
|
||||||
"""
|
"""
|
||||||
Use by usb.core to modify the way USB devices are found
|
Use by usb.core to modify the way USB devices are found
|
||||||
Taken from pyUSB documentation on Github
|
Taken from pyUSB documentation on Github
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app, class_):
|
def __init__(self, app, class_):
|
||||||
self._class = class_
|
self._class = class_
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
def __call__(self, device):
|
def __call__(self, device):
|
||||||
# first, let's check the device
|
# first, let's check the device
|
||||||
self.app.logger.debug("Found a USB device of class %s", str(device.bDeviceClass))
|
self.app.logger.debug(
|
||||||
|
"Found a USB device of class %s", str(device.bDeviceClass)
|
||||||
|
)
|
||||||
if device.bDeviceClass == self._class:
|
if device.bDeviceClass == self._class:
|
||||||
return True
|
return True
|
||||||
# ok, transverse all devices to find an
|
# ok, transverse all devices to find an
|
||||||
# interface that matches our class
|
# interface that matches our class
|
||||||
for configuration in device:
|
for configuration in device:
|
||||||
# Every device can have one or more configuration,
|
# Every device can have one or more configuration,
|
||||||
interface = usb.util.find_descriptor(configuration, bInterfaceClass=self._class)
|
interface = usb.util.find_descriptor(
|
||||||
# each config can have several interfaces
|
configuration, bInterfaceClass=self._class
|
||||||
|
)
|
||||||
|
# each config can have several interfaces
|
||||||
if interface is not None:
|
if interface is not None:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class PrintWorker(threading.Thread):
|
|||||||
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. TODO : Change that
|
tasks. TODO : Change that
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app, print_queue, printers_pool):
|
def __init__(self, app, print_queue, printers_pool):
|
||||||
super().__init__(daemon=True)
|
super().__init__(daemon=True)
|
||||||
self.app = app
|
self.app = app
|
||||||
@@ -51,12 +52,11 @@ class PrintWorker(threading.Thread):
|
|||||||
self._printer_index = (index + 1) % len(self.printers_obj)
|
self._printer_index = (index + 1) % len(self.printers_obj)
|
||||||
return printer
|
return printer
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Background thread that processes queue items"""
|
"""Background thread that processes queue items"""
|
||||||
self.app.logger.debug("Worker %s started working.", threading.get_ident())
|
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("Current threads : %s", threading.active_count())
|
||||||
self.app.logger.debug("Threads actives : %s " , threading.enumerate())
|
self.app.logger.debug("Threads actives : %s ", threading.enumerate())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
@@ -73,7 +73,9 @@ class PrintWorker(threading.Thread):
|
|||||||
# If we do not have a printer, we try to find one
|
# If we do not have a printer, we try to find one
|
||||||
|
|
||||||
while self.running and self.printer is None:
|
while self.running and self.printer is None:
|
||||||
self.app.logger.debug("Trying to find a available printer for the worker.")
|
self.app.logger.debug(
|
||||||
|
"Trying to find a available printer for the worker."
|
||||||
|
)
|
||||||
self.printer = self._acquire_available_printer()
|
self.printer = self._acquire_available_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",
|
||||||
@@ -81,14 +83,11 @@ class PrintWorker(threading.Thread):
|
|||||||
"Ready" if self.printer.ready else "Not ready",
|
"Ready" if self.printer.ready else "Not ready",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if self.printer is None:
|
if self.printer is None:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
self.printer.use_printer()
|
self.printer.use_printer()
|
||||||
|
|
||||||
|
|
||||||
# If the printer is not ready, we wait for it to become ready.
|
# If the printer is not ready, we wait for it to become ready.
|
||||||
while not self.printer.ready:
|
while not self.printer.ready:
|
||||||
self.app.debug("Waiting for our printer to become ready...")
|
self.app.debug("Waiting for our printer to become ready...")
|
||||||
@@ -115,7 +114,9 @@ class PrintWorker(threading.Thread):
|
|||||||
if task:
|
if task:
|
||||||
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 %s", task.task_type, task.task_id)
|
self.app.logger.debug(
|
||||||
|
"Got task %s %s", task.task_type, task.task_id
|
||||||
|
)
|
||||||
task.status = "processing"
|
task.status = "processing"
|
||||||
|
|
||||||
print_data = task.get_print_data()
|
print_data = task.get_print_data()
|
||||||
@@ -132,7 +133,7 @@ class PrintWorker(threading.Thread):
|
|||||||
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.app.logger.debug(
|
self.app.logger.debug(
|
||||||
"Finished printing task %s " , task.task_id
|
"Finished printing task %s ", task.task_id
|
||||||
)
|
)
|
||||||
self.state = "idle"
|
self.state = "idle"
|
||||||
|
|
||||||
@@ -141,7 +142,7 @@ class PrintWorker(threading.Thread):
|
|||||||
self.state = "idle"
|
self.state = "idle"
|
||||||
self.print_queue.mark_completed(task.task_id, "failed")
|
self.print_queue.mark_completed(task.task_id, "failed")
|
||||||
self.app.logger.error(
|
self.app.logger.error(
|
||||||
"Could not print task %s because %s " , task.task_id, str(e)
|
"Could not print task %s because %s ", task.task_id, str(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user