From aad7c67d48ba171256446ebf3c29e61cc68f1872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hubert=20Bry=C5=82kowski?= Date: Sun, 9 Jul 2023 17:32:08 +0200 Subject: [PATCH] added support for printing simple labels --- labeler/adapter/telegram_bot.py | 60 +++++++++++++++++++++++++- labeler/app/labeler.py | 38 +++++++++++++--- labeler/domain/objects.py | 5 ++- labeler/infra/e550w_printer/printer.py | 57 ++++++++++++++++++++++-- labeler/interfaces.py | 4 ++ lbot_readme.md | 1 + 6 files changed, 151 insertions(+), 14 deletions(-) diff --git a/labeler/adapter/telegram_bot.py b/labeler/adapter/telegram_bot.py index a3f8739..ca24fa4 100644 --- a/labeler/adapter/telegram_bot.py +++ b/labeler/adapter/telegram_bot.py @@ -1,6 +1,14 @@ import os -from telegram.ext import CommandHandler, ApplicationBuilder +from telegram import Update +from telegram.ext import ( + CommandHandler, + ApplicationBuilder, + ConversationHandler, + CallbackContext, + filters, + MessageHandler, +) from labeler.app.labeler import Application from labeler.infra.e550w_printer.printer import E550W @@ -8,19 +16,67 @@ from labeler.infra.renderer import PILRenderer class LabelingBot: - def __init__(self, app): + def __init__(self, app: Application): self.app = app async def media_info(self, update, context): media = self.app.get_installed_media() await update.message.reply_text(f"Installed media: {media.description}") + async def label_length(self, update, context): + 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) + + conv_handler = ConversationHandler( + entry_points=[CommandHandler("simple_label", bot.label_length)], + states={ + LABEL_LENGTH: [ + MessageHandler(filters.Text() & ~filters.Command(), bot.label_text) + ], + LABEL_TEXT: [ + MessageHandler(filters.Text() & ~filters.Command(), bot.simple_label) + ], + }, + fallbacks=[CommandHandler("cancel", bot.cancel)], + ) + app = ApplicationBuilder().token(os.environ["TELEGRAM_TOKEN"]).build() app.add_handler(CommandHandler("media_info", bot.media_info)) + app.add_handler(conv_handler) app.run_polling() diff --git a/labeler/app/labeler.py b/labeler/app/labeler.py index 9dea496..fe6d53c 100644 --- a/labeler/app/labeler.py +++ b/labeler/app/labeler.py @@ -1,4 +1,11 @@ -from labeler.domain.objects import Label, LabelRequest, LabelDefinition, MediaDefinition +from labeler.domain.objects import ( + Label, + LabelRequest, + LabelDefinition, + MediaDefinition, + Dimension, + Image, +) from labeler.interfaces import Renderer, Printer @@ -7,16 +14,16 @@ class Application: self.renderer = renderer self.printer = printer - def render_preview(self, label_request: LabelRequest): + def render_preview(self, text: str, length: int = None) -> Label: media = self.printer.get_installed_media() - if label_request.length is not None: - label_length = label_request.length - 2 * media.minimal_margin_horizontal + if length != 0: + label_length = Dimension(mm=length) - 2 * media.minimal_margin_horizontal else: - label_length = media.printable_length + label_length = None label_definition = LabelDefinition( - text=label_request.text, + text=text, length=label_length, width=media.printable_width, dpi=media.dpi, @@ -24,5 +31,24 @@ class Application: self.renderer.render_label(label_definition) + def print_label(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_label(label_definition) + self.printer.print_label(label) + return label + def get_installed_media(self) -> MediaDefinition: return self.printer.get_installed_media() diff --git a/labeler/domain/objects.py b/labeler/domain/objects.py index a9a1cbe..7d4c0c6 100644 --- a/labeler/domain/objects.py +++ b/labeler/domain/objects.py @@ -107,16 +107,17 @@ class MediaDefinition(BaseModel): length: Dimension minimal_margin_vertical: Dimension minimal_margin_horizontal: Dimension + minimum_length: Dimension = Field(default_factory=lambda: Dimension(mm=5)) dpi: int description: str @property def printable_width(self) -> Dimension: - return self.width - 2 * self.minimal_margin_horizontal + return self.width - 2 * self.minimal_margin_vertical @property def printable_length(self) -> Dimension: - return self.length - 2 * self.minimal_margin_vertical + return self.length - 2 * self.minimal_margin_horizontal class Label(BaseModel): diff --git a/labeler/infra/e550w_printer/printer.py b/labeler/infra/e550w_printer/printer.py index a7be38b..affe0f7 100644 --- a/labeler/infra/e550w_printer/printer.py +++ b/labeler/infra/e550w_printer/printer.py @@ -1,10 +1,14 @@ +import io +import logging from math import inf +from brother_ql import BrotherQLRaster, create_label +from brother_ql.backends import guess_backend, backend_factory from pysnmp.entity.engine import SnmpEngine from pysnmp.hlapi import getCmd, CommunityData, UdpTransportTarget, ContextData from pysnmp.smi.rfc1902 import ObjectType, ObjectIdentity -from labeler.domain.objects import MediaDefinition, Dimension +from labeler.domain.objects import MediaDefinition, Dimension, Image from labeler.infra.e550w_printer.media_definitions import ( media_width, tape_color, @@ -16,6 +20,12 @@ from labeler.infra.e550w_printer.media_definitions import ( TYPE_BYTE, ) from labeler.interfaces import Printer +from PIL import Image as PILImage + +PRINTABLE_WIDTH = { + 12: Dimension.from_points(150, 360), + 24: Dimension.from_points(320, 360), +} class E550W(Printer): @@ -26,6 +36,45 @@ class E550W(Printer): def get_installed_media(self) -> MediaDefinition: return self.__get_printer_status() + def print_label(self, label: Image): + im = PILImage.open(io.BytesIO(label.bytes)) + + qlr = BrotherQLRaster("PT-E550W") + create_label( + qlr, + im, + self.__media_width_to_type(label.height), + red=False, + threshold=70, + cut=True, + rotate=270, + compress=True, + dpi_600=True, + hq=True, + ) + + try: + try: + selected_backend = guess_backend(f"tcp://{self.ip_address}:9100") + except ValueError: + logging.error( + "Couln't guess the backend to use from the printer string descriptor" + ) + BACKEND_CLASS = backend_factory(selected_backend)["backend_class"] + be = BACKEND_CLASS(f"tcp://{self.ip_address}:9100") + be.write(qlr.data) + be.dispose() + del be + except Exception as e: + logging.exception("Exception happened: %s", e) + + def __media_width_to_type(self, height: int): + metric_width = Dimension.from_points(height, 360) + if metric_width == Dimension.from_points(150, 360): + return "pt512" + else: + raise ValueError(f"Unsupported media width: {metric_width}") + def __get_printer_status(self): raw_snmp_data = self.__get_snmp_status().asNumbers() width = media_width(raw_snmp_data[WIDTH_BYTE]) @@ -36,9 +85,9 @@ class E550W(Printer): return MediaDefinition( width=Dimension(mm=width), length=Dimension(mm=inf), - minimal_margin_vertical=Dimension(mm=1), - minimal_margin_horizontal=Dimension(mm=2), - dpi=600, + minimal_margin_vertical=(Dimension(mm=width) - PRINTABLE_WIDTH[width]) / 2, + minimal_margin_horizontal=Dimension(mm=1), + dpi=360, description=f"{tape_type} - {width}mm, {media_text_color} on {media_tape_color} background", ) diff --git a/labeler/interfaces.py b/labeler/interfaces.py index fdbc9d5..a88553d 100644 --- a/labeler/interfaces.py +++ b/labeler/interfaces.py @@ -13,3 +13,7 @@ class Printer(abc.ABC): @abc.abstractmethod def get_installed_media(self) -> MediaDefinition: pass + + @abc.abstractmethod + def print_label(self, label: Image): + pass diff --git a/lbot_readme.md b/lbot_readme.md index 838fb5d..0ec7733 100644 --- a/lbot_readme.md +++ b/lbot_readme.md @@ -6,6 +6,7 @@ as provide info about printer status and other useful information. ## Supported commands - `/media_info` - show info about currently installed media +- `/simple_label` - print a simple label ### usage example You need to things: