Монитор заказов вместо кухонного термопринтера

Небольшой квест о замене кухонного принтера заказов в ресторане на табло заказов 24″ монитор с raspberryPi за вечер. Это актуально практически для любой системы erp (все современные 1С системы в торговом оборудовании поддерживают чековые принтеры, аналогично и с другими системами).
Ремарка
В ресторанах и кафе для печати заказов на кухне чаще всего используют принтеры заказов (принтеры «марок»). Это небольшие термопринтеры (родственники контрольно-кассовых машин), но без фискальных накопителей, и кнопка у них чаще всего одна — промотка ленты. Раньше термопринтеры были преимущественно связаны с системами типа FrontOffice по COM порту, но около 10 лет назад ситуация изменилась, в принтерах появилась поддержка Ethernet.
Опыт
Принтеры, которые встречались в работе производителей Штрих-М, Posiflex, Sam4s, однотипны, используют для печати протокол RAW (Протокол односторонний). У них есть небольшие веб-серверы с настройками скорости печати, указания порта, кодировки, дополнительные функциональные возможности и настройки сети. Некоторые модели имеют возможность подключения сканера штрихкодов для уведомлений о готовности блюд(пересылают штрихкод в сеть). Стоимость на текущий день для бюджетных моделей начинается от 10 т.р. и может доходить до 30 т.р на Epson. Срок жизни при интенсивной эксплуатации от пары лет. Основные причины выхода из строя — поломка отрезчика бумаги, жир (покрывает принтер снаружи и частично механизмы внутри), отказ термоголовки, высыхание пластмассы роликов и шестеренок, залитие принтеров жидкостями. Ремонт и замена элементов составляет от 50% стоимости принтера, плюс, конечно же, расходный материал — термобумага.
Задача
Итак, по согласованию с кухней и администрацией взамен очередного вышедшего из строя термопринтера был смонтирован монитор с raspberry pi 3 B c sd-картой на 2 Гб.
Основная задача не вносить изменений в FrontOffice систему, и для ПО не отличаться от принтера чеков/заказов.
ПО официантов FrontOffice Штрих-М, в качестве принтера заказов указан Штрих-600. Ранее, когда менялись российские принтеры на корейские, выяснилось, что кодовая страница, в которой передаются пакеты, — это Windows-1251 порт 9100.
Выбор и настройка ОС
В качестве мини ПК будет Raspberry Pi 3 Model B, развернем а нем легковесную систему Raspbian Stretch Lite.
Проведем небольшой тюнинг: доставим в систему менеджер окон openbox, менеджер входа в систему LightDM, настроим автологин, скроем лог загрузки.
Немного анализа
Далее построим простенький сокет-сервер, чтобы узнать, как информация кодируется в пакете, и что там вообще отправляется на термопринтер.
#!/usr/bin/env python # -*- coding: utf-8 -*- import socket sock = socket.socket() sock.bind(('', 9100)) sock.listen(1) while True: conn, addr = sock.accept() data = conn.recv(16384) print(data) # print(((data.rsplit('*',1)[1]).rsplit('- -',9)[0]).decode('cp1251').encode('utf8')) # clear_data = ((data.rsplit('*',1)[1]).rsplit('- -',9)[0]).decode('cp1251').encode('utf8') conn.close()
ПО FrontOffice отправляет данные одним пакетом в котором летит пачка спец. символов перед основной частью и после неё. Справочная информация о шрифтах и их размере кодирована символами, которых нет в кодировке utf8. После каждой строки указан перенос /r/n. Можно было написать функцию, фильтрующую спец.символы, но у нас один вечер, а в «марке» очень удачно отделено начало строкой звездочек, конец строкой символов минус. Добавим костыль, отбросим спец символы в начале и конце, декодируем в utf8. В окне консоли получим чек, как он есть при печати на «марке» из принтера.
Архитектура будущего приложения
Прикинем немного архитектуру приложения.
- Сокет-сервер, постоянно ожидающий прием.
- Веб-сервер.
- Приложение просмотра — браузер с fullscreen.
- Система обмена сообщениями между сокет-сервером и веб-сервером.
Продакшн
Первый и четвертый пункт решим, дополнив выше написанный сокет-сервер — redis — хранилищем ключ-значение, с прицелом на будущую доработку( каналы — подписки), попутно снизим износ sd-карты. И добавим сигнал — уведомление о приходе нового заказа, воспроизводить будем через hdmi на колонках монитора. Вывод звука активируем через raspi-config.
#!/usr/bin/env python # -*- coding: utf-8 -*- import socket import redis import pygame sock = socket.socket() sock.bind(('', 9100)) sock.listen(1) pygame.mixer.pre_init(frequency=44100, size=-16, channels=2, buffer=4096) pygame.mixer.init(44100, -16, 2, 4096) sound = pygame.mixer.Sound("icq.wav") #print(sound.get_num_channels()) r = redis.StrictRedis(host='localhost', port=6379, db=0) n=0 while True: conn, addr = sock.accept() data = conn.recv(16384) print(((data.rsplit('*',1)[1]).rsplit('- -',9)[0]).decode('cp1251').encode('utf8')) sound.play() clear_data = ((data.rsplit('*',1)[1]).rsplit('- -',9)[0]).decode('cp1251').encode('utf8') r.set('data'+str(n), clear_data) n=n+1 conn.close()
По второму пункту накидаем веб-сервер на flask с автообновление каждые 15 секунд (пока это самый простой вариант), в таск-лист пометим socketio и очередь возможно celery или на redis. Переберем все доступные пары ключ — значение и отобразим на страничке. По клику на «марке» удалим из redis и с рабочего стола соответственно.
# -*- coding: utf-8 -*- from flask import Flask, render_template, redirect import os import redis r = redis.StrictRedis(host='localhost', port=6379, db=0) app = Flask(__name__) def kernel_ver(): try: f = open(os.path.dirname(os.path.abspath(__file__)) + '/release.txt') lines = f.readlines() f.close() return lines[0] except IOError as e: return "--" @app.route('/') def index(): d = {} for item in r.keys(): d[item] = (r.get(item)).decode('utf8') return render_template("index.html", release=kernel_ver(), di = d) @app.route('/del/<key>') def delstamp(key): r.delete(key) return redirect("http://192.168.1.80:5000/", code=302) if __name__ == "__main__": app.run(host='0.0.0.0')
Добавим jinja шаблон
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="refresh" content="30"/> <title> Монитор заказов</title> <link href="http://fonts.googleapis.com/css?family=Reenie+Beanie:regular" rel="stylesheet" type="text/css"> </head> <body> <ul> {% for key in di %} <li> <a href="/del/{{key}}"> <!-- h2>Title #1</h2 --> {% for item in di[key].splitlines() %} <p>{{ item }}</p> {% endfor %} </a> </li> {% endfor %} </ul> </body> </html>
Остался пункт 3, сделаем самый минимальный браузер без кнопок из 13 строк.
import sys from PySide import QtCore, QtGui, QtWebKit class MainWindow(QtGui.QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.showFullScreen() self.web = QtWebKit.QWebView(self) self.web.load(QtCore.QUrl('http://127.0.0.1:5000')) self.setCentralWidget(self.web) app = QtGui.QApplication(sys.argv) main_window = MainWindow() main_window.show() sys.exit(app.exec_())
Далее необходимо создать сервисы для запуска всех выше написанных скриптов.
Или по-быстрому их прописать в autostart файл openbox.


