feat: Add Web iface with QR codes

This commit is contained in:
Piotr Gaczkowski 2026-03-24 14:27:32 +01:00
parent b4bf6552f6
commit 783d9b6d34
10 changed files with 1293 additions and 739 deletions

75
labeler/adapter/cli.py Normal file
View file

@ -0,0 +1,75 @@
import os
from labeler.app.labeler import Application
from labeler.infra.e550w_printer.printer import E550W
from labeler.infra.renderer import PILRenderer
class LabelingBot:
def __init__(self, app: Application):
self.app = app
def media_info(self):
media = self.app.get_installed_media()
print(f"Installed medium: {media}")
def simple_label(self, label_text, label_length=0):
try:
label = self.app.print_label(text=label_text, length=label_length)
except Exception as e:
print(f"There was an exception: {e}")
def get_qrcode(self, label_text, label_length=0):
label = self.app.render_qrcode_preview(text=label_text, length=label_length)
return label
def print_qrcode(self, label_text, label_length=0):
try:
label = self.app.print_qrcode(text=label_text, length=label_length)
except Exception as e:
print(f"There was an exception: {e}")
# async def label_length(self):
# await update.message.reply_text(
# "Hello! Please tell me the length of the label, enter 0 for auto:"
# )
# return LABEL_LENGTH
#
# async def label_text(self, update: Update, context: CallbackContext) -> int:
# user_input = update.message.text
# context.user_data["length"] = int(user_input)
# await update.message.reply_text("Now, please tell me the text of the label:")
# return LABEL_TEXT
#
# async def simple_label(self, update: Update, context: CallbackContext) -> int:
# user_input = update.message.text
# context.user_data["label"] = user_input
# try:
# label = self.app.print_label(
# text=context.user_data["label"], length=context.user_data["length"]
# )
# except Exception as e:
# await update.message.reply_text(f"There was an exception: {e}")
# return ConversationHandler.END
#
# await update.message.reply_photo(
# label.bytes, f'Your label is: {context.user_data["label"]}'
# )
# return ConversationHandler.END
#
# async def cancel(self, update: Update, context: CallbackContext) -> int:
# await update.message.reply_text("Cancelled.")
# return ConversationHandler.END
if __name__ == "__main__":
application = Application(PILRenderer(), E550W(os.environ.get("PRINTER_IP")))
bot = LabelingBot(application)
LABEL_LENGTH, LABEL_TEXT = range(2)
bot.media_info()
label = bot.get_qrcode("512", 25)
with open("label.png", "wb") as preview:
preview.write(label.bytes)
label = bot.print_qrcode("512", 25)

View file

@ -0,0 +1,80 @@
import os
from labeler.app.labeler import Application
from labeler.infra.e550w_printer.printer import E550W
from labeler.infra.renderer import PILRenderer
from fastapi import FastAPI
app = FastAPI()
class LabelingBot:
def __init__(self, app: Application):
self.app = app
def media_info(self):
media = self.app.get_installed_media()
print(f"Installed medium: {media}")
def simple_label(self, label_text, label_length=0):
try:
label = self.app.print_label(text=label_text, length=label_length)
except Exception as e:
print(f"There was an exception: {e}")
def get_qrcode(self, label_text, label_length=0):
label = self.app.render_qrcode_preview(text=label_text, length=label_length)
return label
def print_qrcode(self, label_text, label_length=0):
try:
label = self.app.print_qrcode(text=label_text, length=label_length)
except Exception as e:
print(f"There was an exception: {e}")
# async def label_length(self):
# await update.message.reply_text(
# "Hello! Please tell me the length of the label, enter 0 for auto:"
# )
# return LABEL_LENGTH
#
# async def label_text(self, update: Update, context: CallbackContext) -> int:
# user_input = update.message.text
# context.user_data["length"] = int(user_input)
# await update.message.reply_text("Now, please tell me the text of the label:")
# return LABEL_TEXT
#
# async def simple_label(self, update: Update, context: CallbackContext) -> int:
# user_input = update.message.text
# context.user_data["label"] = user_input
# try:
# label = self.app.print_label(
# text=context.user_data["label"], length=context.user_data["length"]
# )
# except Exception as e:
# await update.message.reply_text(f"There was an exception: {e}")
# return ConversationHandler.END
#
# await update.message.reply_photo(
# label.bytes, f'Your label is: {context.user_data["label"]}'
# )
# return ConversationHandler.END
#
# async def cancel(self, update: Update, context: CallbackContext) -> int:
# await update.message.reply_text("Cancelled.")
# return ConversationHandler.END
@app.get("/print/{item_id}")
def print_item(item_id: int, q: str | None = None):
application = Application(PILRenderer(), E550W(os.environ.get("PRINTER_IP")))
bot = LabelingBot(application)
LABEL_LENGTH, LABEL_TEXT = range(2)
bot.media_info()
label = bot.get_qrcode(item_id, 25)
with open("label.png", "wb") as preview:
preview.write(label.bytes)
bot.print_qrcode(item_id, 25)

View file

@ -31,6 +31,23 @@ class Application:
self.renderer.render_label(label_definition)
def render_qrcode_preview(self, text: str, length: int = None) -> Label:
media = self.printer.get_installed_media()
if length != 0:
label_length = Dimension(mm=length) - 2 * media.minimal_margin_horizontal
else:
label_length = None
label_definition = LabelDefinition(
text=text,
length=label_length,
width=media.printable_width,
dpi=media.dpi,
)
return self.renderer.render_qrcode(label_definition)
def print_label(self, text: str, length: int = None) -> Image:
media = self.printer.get_installed_media()
@ -50,5 +67,24 @@ class Application:
self.printer.print_label(label)
return label
def print_qrcode(self, text: str, length: int = None) -> Image:
media = self.printer.get_installed_media()
if length != 0:
label_length = Dimension(mm=length) - 2 * media.minimal_margin_horizontal
else:
label_length = None
label_definition = LabelDefinition(
text=text,
length=label_length,
width=media.printable_width,
dpi=media.dpi,
)
label = self.renderer.render_qrcode(label_definition)
self.printer.print_label(label)
return label
def get_installed_media(self) -> MediaDefinition:
return self.printer.get_installed_media()

View file

@ -4,6 +4,7 @@ from math import inf
from brother_ql import BrotherQLRaster, create_label
from brother_ql.backends import guess_backend, backend_factory
from brother_ql.conversion import convert
from pysnmp.entity.engine import SnmpEngine
from pysnmp.hlapi import getCmd, CommunityData, UdpTransportTarget, ContextData
from pysnmp.smi.rfc1902 import ObjectType, ObjectIdentity
@ -43,9 +44,9 @@ class E550W(Printer):
im = PILImage.open(io.BytesIO(label.bytes))
qlr = BrotherQLRaster("PT-E550W")
create_label(
convert(
qlr,
im,
[im],
self.__media_width_to_type(label.height),
red=False,
threshold=70,

View file

@ -1,3 +1,4 @@
import qrcode
import textwrap
from string import ascii_letters
@ -7,9 +8,16 @@ from labeler.domain.objects import Image, LabelDefinition
from labeler.interfaces import Renderer
DPI = 360.0
def points_to_pixels(point_size: float):
return int(point_size * (72 / DPI))
class PILRenderer(Renderer):
def __init__(self):
self.font_path = "/Library/Fonts/Arial.ttf"
self.font_path = "/Library/Fonts/Arial Unicode.ttf"
def render_label(self, label_definition: LabelDefinition) -> Image:
if label_definition.length is None:
@ -47,7 +55,7 @@ class PILRenderer(Renderer):
while text_height > 0:
font = ImageFont.truetype(
"/Library/Fonts/Arial.ttf",
"/Library/Fonts/Arial Unicode.ttf",
text_height,
)
if lines_to_print > 1:
@ -74,6 +82,42 @@ class PILRenderer(Renderer):
)
return im
def render_qrcode(self, label_definition: LabelDefinition) -> Image:
width = label_definition.pixel_width
length = label_definition.pixel_length
text = label_definition.text
font = self.__get_font_for_qr()
qr = qrcode.QRCode(box_size=7)
qr.add_data(f"https://hs3.pl/db/{text}")
qr.make(fit=True)
qr_img = qr.make_image()
print(width, length)
pil_image = PILImage.new("1", (width, length), 1)
draw = ImageDraw.Draw(pil_image)
pil_image.paste(qr_img)
draw.text(
(width / 2, 232),
"HS3-DB",
font=font,
fill=0,
anchor="mm",
align="center",
)
draw.text(
(20, 232 + 34),
f"ID: {text}",
font=font,
fill=0,
align="left",
)
return Image.from_pil(pil_image.transpose(PILImage.Transpose.ROTATE_90))
def __get_font_for_qr(self):
font_size = int(360 / 9)
return ImageFont.truetype("fonts/SourceCodePro-SemiBold.ttf", font_size)
def __get_font(self, text: str, max_width: int, max_length: int):
font_size = max_width
step = max_width // 2

View file

@ -8,6 +8,10 @@ class Renderer(abc.ABC):
def render_label(self, label_definition: LabelDefinition) -> Image:
pass
@abc.abstractmethod
def render_qrcode(self, label_definition: LabelDefinition) -> Image:
pass
class Printer(abc.ABC):
@abc.abstractmethod