У меня есть много хобби-проектов в GitHub. Некоторые из них довольно популярны, поэтому к ним время от времени постят issues. Проблема в том, что они теряются в куче моих электронных писем или я забываю пройтись по своим репозиториям и добавить новые пункты в список дел.

Иногда я записывал новые issues на стикеры, когда видел уведомления, но всегда хотел найти предлог, чтобы упростить этот процесс. Однажды в кафе я увидел, как принтер чеков выплёвывает заказы, и задался вопросом, можно ли использовать его для печати тикетов каждый раз, когда в один из моих репозиториев добавляют issue.

Спойлер: у меня получилось!


Вот зачем я купил принтер чеков: каждый раз, когда в одном из моих репозиториев GitHub появляется новый issue, физический тикет печатается у меня на столе. pic.twitter.com/g6uYtGP9J7 — Andrew Schmelyun (@aschmelyun) 24 марта 2022 года

Давайте разберёмся, что же я использовал для этого и как настроил систему!

Список оборудования


Чтобы приступить к работе, мне нужен был термопринтер чеков и способ передачи данных в него. В конечном итоге я использовал следующие комплектующие:

  • Epson TM-T88IV;
  • Raspberry Pi Zero W;
  • Адаптер Micro USB — USB;
  • Кабель USB Type-B.

Термопринтер Epson я выбрал потому, что в нём используется набор команд ESC/POS, для которого есть надёжные библиотеки на множестве языков программирования. Плюс эти принтеры довольно часто продают с рук, и мне за вполне умеренную цену удалось найти его на Ebay с набором бумаги для чеков.

Также мне нужно было какое-то оборудование для подключения между Интернетом и принтером, упрощающее передачу данных. Можно было бы подключить принтер к моему PC, но мне хотелось, чтобы это была полностью автономная система, которую можно было бы просто поставить в углу. У меня завалялась старая неиспользуемая Raspberry Pi Zero W, поэтому я выбрал её.

Так как у RPi Zero есть лишь один разъём micro USB, для подключения к принтеру чеков я использую адаптер и кабель USB Type-B.

Передача данных в принтер


Итак, мы подключили принтер, Raspberry Pi готова к работе, но нам нужно как-то передавать данные в принтер из Raspberry Pi. Это легко можно сделать с помощью Node или Python, но поскольку я PHP-разработчик и мне нравится преодолевать ограничения языка, выберем его. К счастью, существует довольно качественная библиотека для работы с командами ESC/POS на PHP.

Однако прежде чем писать код, мне нужно убедиться, что принтер доступен для создаваемой мной программы. Так как я использую Ubuntu в Raspberry Pi, доступ можно получить через /dev/usb/lp0 (или другой lp#). Но для начала, возможно, придётся немного подготовиться.

Сначала я открою терминал в устройстве, к которому подключён принтер (в нашем случае это Raspberry Pi). Выполню команду lsusb чтобы получить Product ID и Vendor ID от соединения с принтером. Она вернёт нечто подобное:

Bus 002 Device 001: ID 04b2:0202 Epson TM-T888IV Device Details

Далее я создам правило udev, позволяющее пользователям, принадлежащим к группе dialout, пользоваться принтером. Создаю файл /etc/udev/rules.d/99-escpos.rules и добавляю в него следующее:

SUBSYSTEM=="usb", ATTRS{idVendor}=="04b2", ATTRS{idProduct}=="0202", MODE="0664", GROUP="dialout"

Не забудем заменить шестнадцатеричные значения на vendor ID и product ID, возвращённые из lsusb.

Если пользователи не относятся к группе dialout, попытаемся их туда добавить:

sudo usermod -a -G dialout pi && sudo usermod -a -G dialout root

А в конце нужно перезапустить udev:

sudo service udev restart

Подключение готово и теперь можно начинать писать код, чтобы его тестировать. Для начала я запрошу нужную библиотеку с помощью Composer:

composer require mike42/escpos-php

После её установки мне нужно написать код для отправки данных в принтер. Создаём файл index.php и добавляем в него следующее:

<?php

require __DIR__ . '/vendor/autoload.php';

use Mike42\Escpos\PrintConnectors\FilePrintConnector;
use Mike42\Escpos\Printer;

$connector = new FilePrintConnector('/dev/usb/lp0');
$printer = new Printer($connector);

$printer->text('Hello, world!');
$printer->feed(2);
$printer->cut();

Чтобы запустить его, мне достаточно выполнить скрипт при помощи PHP и root-доступа:

sudo php index.php

Если всё получилось, то на чеке распечатается Hello, world! с двумя пропущенными строками, после чего чек будет отрезан. Всё это работает довольно просто.

Создан connector печати для «файла» /dev/usb/lp0, который является USB-адаптером, к которому подключен принтер. Последующие команды принтера (text(), feed(), cut()) потоково передают по этому соединению сырые команды, связанные с соответствующими действиями принтера.

Примечание: если вы получаете ошибку о допуске при отправке на /dev/usb/lp0 или что-то подобное, то попробуйте выполнить sudo chmod +777 /dev/usb/lp0, и проверьте, устранило ли это проблему.

Теперь можно подключиться к GitHub и заполнить чеки реальными данными.

Подключение к GitHub


В GitHub можно легко прослушивать события в репозиториях при помощи webhooks. Зайдя на страницу параметров одного из моих репозиториев и перейдя в раздел webhooks, я могу создать хук, который будет выполнять POST на определённый URL при выбранном действии. В данном случае я хочу печатать тикет при создании нового issue, поэтому выбрал раздел «Issues». В качестве типа данных я выбрал JSON, потому что мне нравится с ним работать.

Но прежде чем двигаться дальше, мне нужен URL, на который GitHub мог бы отправить POST-запрос. Сначала я подключусь к Raspberry Pi по ssh и запущу локальный PHP-сервер, использовав флаг -S в папке с моим проектом:

sudo php -S 127.0.0.1:8000

После запуска сервера мне нужен способ получить доступ к этому порту на Raspberry Pi, когда она находится в локальной сети. Я не хочу раскрывать мой домашний IP-адрес или создавать pass-through через роутер. Поэтому я просто воспользовался ngrok для создания туннеля через открытый порт.

ngrok http 8000

После загрузки я копирую выданный URL https и вставляю его в поле URL для webhook в GitHub, а потом сохраняю webhook. Сразу после сохранения должен отправиться тестовый запрос, ngrok принимает запрос, передаёт его по туннелю на локальный PHP-сервер, и на принтере печатается ещё один Hello, world!.

Теперь можно использовать входящий запрос от GitHub для создания тикета.

Готовый код


Теперь внесём изменения в приведённый выше код. Сначала мне нужно отвергать всё, что не является POST-запросом. Поэтому перед инициализацией FilePrintConnection я добавляю такие строки:

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    return 'Error: Expecting POST request';
}

А после инициализации FilePrintConnection и Printer я декодирую весь JSON-запрос от GitHub как ассоциативный массив:

$data = json_decode(file_get_contents('php://input'), true);

Теперь можно использовать предыдущие методы принтера и массив данных с GitHub для создания нужного мне чека! При работе с библиотекой Escpos для форматирования текста требуется куча повторяющегося кода. Например, вот как выглядит заголовок issue жирным подчёркнутым текстом вместе с телом, написанным обычным текстом:

$printer->setUnderline(true); // start underlined text
$printer->setEmphasis(true); // start bolded text
$printer->text($data['issue']['title']);
$printer->setEmphasis(false); // stop bolded text
$printer->setUnderline(false); // stop underlined text

$printer->text($data['issue']['body']);

Полный код, который я использовал для форматирования тикета в показанном выше твите, можно посмотреть в репозитории GitHub.

Теперь чтобы протестировать код, мне достаточно перейти в репозиторий, для которого я настроил webhook, создать новый issue и подождать, пока принтер выдаст тикет.

Завершение и дальнейшие шаги


Итак, что же можно ещё сделать с этой системой? Пока она определённо является лишь доказательством работоспособности идеи, но мы можем расширить её в разных направлениях.

Например, в сам тикет можно добавить QR-код с ссылкой на issue в GitHub. Также можно добавить больше подробностей об issue, например, метки и степень опасности.

Кроме того, можно использовать эту концепцию для обработки практически любых данных, поступающих от webhook или через запрос API. Например, для печати тикетов из приложений наподобие Jira и Bugsnag, исключений, выброшенных приложениями в продакшене, или даже повседневных пунктов todo и списков покупок!

Комментарии (11)


  1. iliasam
    08.07.2022 13:21
    +2

    «Raspberry Pi Zero W» — содержит полноценный процессор с Linux, так что хаб «Программирование микроконтроллеров» тут лишний.
    Бумагу автору, видимо, не жалко)


    1. boris-the-blade Автор
      08.07.2022 13:22
      +1

      Привет! Ваша правда, спасибо, убрал хаб.


  1. radioxoma
    08.07.2022 14:46
    +14

    А когде чек выцветает, well, wontfix.


  1. danilovmy
    08.07.2022 21:07
    +1

    странно. вариантов же много:

    wifi принер с SMTP Client (email print ) подключается к почте, настраиваем емайл получать инфо только с гитхаба. Epson, например, TM-T88V-iHub

    wifi принер с подключением к api (Epson connect) - подключается к гитхаб напрямую например TM-L90-i

    Мы решали такую задачу с банком, никаких доп устройств не надо было, так еще и без проводов. Или тут именно цимес, что он еще и rapsberry использовал?

    p. s. Написал автору, спросил зачем так сложно :)


  1. xSVPx
    09.07.2022 12:02
    +1

    Т.е. собирать это все в виде почты-тасков в одном месте это неудобнее, чем иметь дело с кучей бумажек ?

    Чеки были бы удобны, если бы он по утрам выходил в коридор и раздавал их рандомным сотрудникам, и то с большим натягом. Учитывая их временность это вообще лишняя бумага.


  1. serafims
    09.07.2022 12:53
    +3

    Эта статья полезна не результатом, а инструментами, и их можно применить более полезно. А если проблемы печатаются очень редко, типа 2 в неделю, то почему бы и нет.


    1. 402d
      10.07.2022 10:20
      +2

      Плюсую. Я узнал про ngrok.


    1. isden
      11.07.2022 11:35

      Кмк, эта статья полезна только более-менее интересной идеей.
      Техническая реализация, если честно, так себе.


  1. nenvoy
    10.07.2022 18:53

    Пока все отказываеются от бумаги, ради природы...


  1. Slon48
    10.07.2022 18:53

    Нечто подобное внедрил у себя дома писатель Леонид Каганов ))
    https://lleo.me/dnevnik/2021/12/02


  1. dimnsk
    10.07.2022 21:01

    а это какая рубрика ?