from flask import Flask, request
from escpos.printer import Usb
app = Flask(__name__)
# https://python-escpos.readthedocs.io/en ... cpos-class
@app.route("/", methods=["GET"])
def print():
codigo = request.args.get("codigo", "")
ref = request.args.get("ref", "")
pedido = request.args.get("pedido", "")
operario = request.args.get("oper", "")
mez = request.args.get("mez", "")
if not codigo:
return error("missing codigo", 400)
mensaje = (
"Codigo: "
+ codigo
+ "\nReferencia: "
+ ref
+ "\nMezcla: "
+ mez
+ "\nOperador: "
+ operario
+ "\nPedido: "
+ pedido
)
p = None
try:
p = Usb(0x04B8, 0x0E15)
p.set(double_height=True, double_width=True)
p.text(mensaje)
p.qr(codigo, size=13)
p.cut()
p.close()
except Exception as exc:
if p:
p.cut()
p.close()
return error(str(exc), 500)
return (
{},
200,
{"Content-Type": "application/json"},
)
def error(msg, code):
return ({"err": msg}, code, {"Content-Type": "application/json"})
Чтобы сделать USB-принтер доступным, я добавил следующее правило udev:
SUBSYSTEMS=="usb", ATTRS{idVendor}=="04b8", ATTRS{idProduct}=="0e15", MODE="0666"` in file `/lib/udev/rules.d/99-myusb.rules
...чтобы он не был запрещенным ресурсом.
В установке использовались Python 3.11 и python-escpos 3.0a8, и все прошло гладко.
Новая настройка
Годы спустя я решил упростить работу — запуск полноценной настольной ОС и Apache показался мне излишним. Итак, я перешел на Raspberry Pi OS Lite, обслуживая приложение Flask напрямую с Gunicorn на порту 5000.
Те же правила udev, но теперь я управляю приложением как сервисом systemd:
[Unit]
Description=EPOS Print Service
After=network.target
[Service]
User=cc
WorkingDirectory=/home/cc/rasp
ExecStart=/home/cc/rasp/venv/bin/gunicorn -w 1 -b 0.0.0.0:5000 app:app
Restart=always
[Install]
WantedBy=multi-user.target
Сейчас я использую Python 3.13, python-escpos 3.1 и pyusb 1.3.1.
Код Flask превратился в немного более структурированную версию с базовым ведением журнала и небольшим классом-оболочкой принтера:
from flask import Flask, request
from escpos.printer import Usb
import os
from datetime import datetime
import random
import string
class Log:
def __init__(self):
self.script_dir = os.path.dirname(os.path.abspath(__file__))
self.log_path = os.path.join(self.script_dir, "printer.log")
def log(self, message: str):
"""Simple homemade logger that appends messages to printer.log."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
line = f"{timestamp} | {message}\n"
try:
with open(self.log_path, "a", encoding="utf-8") as f:
f.write(line)
except Exception as e:
print(f"Logging failed: {e}")
class Printer:
id_vendor = 0x04B8
id_product = 0x0E15
usb_printer = None
def __init__(self):
self.usb_printer = None
def loadUsbPrinter(self):
try:
logging.log("Attempting to connect to printer...")
self.usb_printer = Usb(self.id_vendor, self.id_product)
self.usb_printer.set(double_height=True, double_width=True)
logging.log("Connected to printer successfully.")
except Exception as e:
logging.log(f"Printer connection failed: {e}")
def getUsb(self):
if not self.usb_printer:
self.loadUsbPrinter()
return self.usb_printer
def http_response():
return ({}, 200, {"Content-Type": "application/json"})
def generateUID():
return "".join(
random.choices(
string.ascii_uppercase + string.ascii_lowercase + string.digits, k=10
)
)
app = Flask(__name__)
logging = Log()
printer = Printer()
logging.log(f"--- App started ---")
@app.after_request
def after_request(response):
response.headers.add("Access-Control-Allow-Origin", "*")
response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization")
response.headers.add("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
return response
@app.route("/", methods=["POST"])
def print_codigo():
data = request.get_json(force=True, silent=True) or {}
mensaje = data.get("mensaje", "Test")
codigo = data.get("codigo", "Test")
uid = generateUID()
logging.log(f"Print request received: {codigo} | UID: {uid}")
p = printer.getUsb()
if not p:
logging.log("Error: Printer not connected.")
return http_response()
try:
p.text(f"{uid}\n")
p.qr(codigo, size=11)
p._raw(b"\n")
p.text(mensaje)
p._raw(b"\n")
p.cut()
except Exception as exc:
logging.log(f"Error during printing: {str(exc)}")
return http_response()
Проблема
В большинстве случаев все работает, но иногда принтер останавливается на полпути выполнения задания — печатает QR-код, а затем зависает. Поле codigo обычно имеет вид «a123456789».
Иногда вместо правильного QR-кода перед печатью текста сообщения печатается длинный поток случайных символов. Я предполагаю, что ему не удается правильно закодировать или отправить данные QR-кода на принтер.
Вот что я пробовал до сих пор:
- Изменение количества рабочих Gunicorn
- Повторная инициализация или сброс USB-принтера при каждом запросе
- Создание нового экземпляра Usb() для каждого запроса и немедленное его закрытие
- Использование с USB(...): чтобы гарантировать, что функция close() всегда вызывается
- Возвращаемся к Apache
Самое интересное: когда возникает проблема (принтер зависает в qr), в журнал не выдается никаких ошибок, я также пробовал добавить time.sleep прямо перед возвратом, но то же самое.
После этого, если принтер вызывается снова, он печатает правильно, с QR-кодом предыдущего билета вверху.
Кто-нибудь сталкивался с подобным поведением с python-escpos и Gunicorn (или Flask) в Raspberry Pi OS Lite?
Любые идеи, обходные пути или советы по отладке?
CURL обычных запросов:
curl 'http://localhost:5000/' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Accept-Language: es-ES,es;q=0.9,en;q=0.8' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/json' \
-H 'Origin: http://10.0.0.9:8080' \
-H 'Referer: http://10.0.0.9:8080' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-site' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36' \
-H 'sec-ch-ua: "Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
--data-raw '{"mensaje":"a13023\nVN150E-G\nEPDM-008/1\nOper: \nPedido: 961","codigo":"a13023"}'
Подробнее здесь: https://stackoverflow.com/questions/798 ... spberry-pi
Мобильная версия