5 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
10 changed files with 105 additions and 68 deletions

View File

@@ -3,6 +3,7 @@
[defaults]
signature = ""
# Printer settings
[printer]
upload_folder = "src/static/uploads"
@@ -14,11 +15,14 @@ waiting_time = 5
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
[rpi]

View File

@@ -16,10 +16,14 @@ waiting_time = 5
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
[rpi]

View File

@@ -366,6 +366,7 @@ def api_print_image():
# TODO: This might not depend on the Raspberry Pi
@app.route("/api/camera/picture", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def camera_picture():
"""Returns a picture taken by the camera on a raspberry pi"""
if RASPBERRY_PI_CONNECTED:
@@ -378,24 +379,28 @@ def camera_picture():
@app.route("/api/queue", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def api_queue_status():
"""API endpoint for entire queue"""
return jsonify(web.get_queue_state())
@app.route("/api/queue/completed", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def api_queue_completed():
"""API endpoint that returns the finished tasks"""
return jsonify(web.get_queue_completed())
@app.route("/api/worker", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def api_worker_state():
"""API endpoint to get the worker state"""
return jsonify(worker.current_state())
@app.route("/api/worker/start")
@limiter.limit("6/minute", override_defaults=False)
def api_worker_start():
"""
Enable to worker. This starts to process the print queue.
@@ -405,6 +410,7 @@ def api_worker_start():
@app.route("/api/worker/stop")
@limiter.limit("6/minute", override_defaults=False)
def api_worker_stop():
"""
Stops the print queue. This stops the processing of the print queue.
@@ -414,6 +420,7 @@ def api_worker_stop():
@app.route("/api/printers")
@limiter.limit("6/minute", override_defaults=False)
def api_printers_info():
"""Returns the current state of every printers"""
return jsonify(printers_pool.printers_info())

View File

@@ -10,9 +10,9 @@ import time
from enum import Enum
import uuid
import threading
import usb.core
from pathlib import Path
from uuid import uuid4
import usb.core
from PIL import Image, ImageEnhance, ImageDraw, ImageFont
import numpy as np
@@ -119,11 +119,14 @@ class EscPosPrinter(Printer):
self.usb_args = {}
self.usb_args["idVendor"] = self.vendor_id
self.usb_args["idProduct"] = self.device_id
self.configuration_file = configuration_file
try:
# This also calls open(), which we need to close()
# or else the device will appear as busy.
p = escpos.printer.Usb(self.vendor_id, self.device_id, 0, profile=configuration_file["profile"])
p = escpos.printer.Usb(
self.vendor_id, self.device_id, 0, profile=configuration_file["profile"]
)
except escpos.exceptions.DeviceNotFoundError as e:
self.app.logger.error(
"The USB device is not plugged in : %s",
@@ -148,13 +151,13 @@ class EscPosPrinter(Printer):
vendor_id=self.vendor_id,
product_id=self.device_id,
serial_number="",
label_type=configuration_file["label_type"],
label_type=self.configuration_file["label_type"],
)
# Setting up the printing options.
# TODO : Expose this to the web interface
p.set(
align=configuration_file["align_text"],
align=self.configuration_file["align_text"],
font="a",
bold=False,
underline=0,
@@ -213,25 +216,25 @@ class EscPosPrinter(Printer):
clean_signature = str(signature)
# 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) > int(self.configuration_file["text_max_length"]):
self.app.logger.warning(
"Could not print message of this length: " + str(len(clean_msg))
)
raise RuntimeError(
"Could not print message of this length :"
+ 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) > 256:
if len(signature) > int(self.configuration_file["signature_max_length"]):
self.app.logger.warning(
"Could not print signature of this length: " + str(len(clean_signature))
)
raise RuntimeError(
"Could not print signature of this length :"
+ 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
@@ -256,15 +259,15 @@ class EscPosPrinter(Printer):
def _print_img(self, path, signature="", center=True, process=False):
clean_signature = str(signature)
# TODO: make the max length configurable
if len(signature) > 256:
if len(signature) > int(self.configuration_file["signature_max_length"]):
self.app.logger.warning(
"Could not print signature of this length: " + str(len(clean_signature))
)
raise ValueError(
raise RuntimeError(
"Could not print signature of this length :"
+ 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)):
@@ -311,9 +314,7 @@ class EscPosPrinter(Printer):
try:
self.printer.close()
except Exception as e:
self.app.logger.error(
"Could not close the printer connexion %s", str(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")
@@ -457,7 +458,9 @@ class BrotherPrinter(Printer):
vendor_id=vendor_id,
product_id=self.device_id,
serial_number=serial_number,
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
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.ready = True
@@ -473,7 +476,12 @@ class BrotherPrinter(Printer):
self._print_img(text_to_image(msg))
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
"""
@@ -526,7 +534,7 @@ class BrotherPrinter(Printer):
instructions=instructions,
printer_identifier=BACKEND_STRING_DESCR,
backend_identifier="pyusb",
blocking=False
blocking=False,
)
self.app.logger.debug(str(success))
@@ -570,18 +578,16 @@ class BrotherPrinter(Printer):
cut=False,
)
success = send(
instructions=instructions,
printer_identifier=BACKEND_STRING_DESCR,
backend_identifier="pyusb",
blocking=False
blocking=False,
)
self.app.logger.debug(str(success))
self.app.logger.debug("Printed signature")
time.sleep(2)
except Exception as e:
raise e
finally:
@@ -620,7 +626,6 @@ class BrotherPrinter(Printer):
except Exception as e:
raise e
def _qr(self):
# This should not be reached by a print on a BrotherQL
raise NotImplementedError()

View File

@@ -91,25 +91,35 @@ class Printers:
)
match (manufacturer):
case ("EPSON"):
case "EPSON":
try:
# We create a new EscPosPrinter()
self.app.logger.debug("Trying to create a new EPSON printer")
prid = dev.idProduct
vendir = dev.idVendor
escpos_printer = EscPosPrinter(self.app, vendir, prid, self.configuration_file["printer"]["epson"] )
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"):
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"] )
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",
@@ -122,8 +132,10 @@ class Printers:
# 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)
case _:
self.app.logger.debug(
"Found a printer of unknown manufacturer : %s", manufacturer
)
# end match
self.app.logger.debug("Found %s printers", len(printers))
@@ -156,11 +168,20 @@ class Printers:
return None
# def printers_info(self):
# """Return the current list of printers"""
# printers_info_list = {}
# for i in self.printers
# return self.printers
def printers_info(self):
"""Return the current list of 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:

View File

@@ -5,6 +5,6 @@
<title>LittlePrynter API</title>
</head>
<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>
</html>

View File

@@ -53,7 +53,7 @@
<hr>
<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>
</body>
<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

@@ -41,7 +41,9 @@ class Web:
return True
def print_image(self, image, sign: str, do_cut: bool, process_image: bool, many: int = 1) -> 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.
"""

View File

@@ -45,7 +45,9 @@ class PrintWorker(threading.Thread):
self.app.logger.debug("Ho great, I'm alive... I'm ready to work another day...")
def _acquire_available_printer(self):
result = self.printers.acquire_available_printer(start_index=self._printer_index)
result = self.printers.acquire_available_printer(
start_index=self._printer_index
)
if result is None:
return None
@@ -81,7 +83,6 @@ class PrintWorker(threading.Thread):
)
self.printer = self._acquire_available_printer()
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",
@@ -90,8 +91,6 @@ class PrintWorker(threading.Thread):
if self.printer is None:
time.sleep(1)
self.printer.use_printer()
# If the printer is not ready, we wait for it to become ready.