[Перевод] Я создал принтер чеков для issues в GitHub

cmarkebkyohtkiu8grcpsdzqjms.png


У меня есть много хобби-проектов в 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 и добавляем в него следующее:

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 и списков покупок!

© Habrahabr.ru