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

Монитор заказов

Небольшой квест о замене кухонного принтера заказов в ресторане на табло заказов 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. В окне консоли получим чек, как он есть при печати на «марке» из принтера.

Архитектура будущего приложения


Прикинем немного архитектуру приложения.

  1. Сокет-сервер, постоянно ожидающий прием.
  2. Веб-сервер.
  3. Приложение просмотра — браузер с fullscreen.
  4. Система обмена сообщениями между сокет-сервером и веб-сервером.


Продакшн


Первый и четвертый пункт решим, дополнив выше написанный сокет-сервер — 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/')
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 шаблон






 Монитор заказов



  















Остался пункт 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.

Результат
Кухонный монитор заказов

© Habrahabr.ru