У меня есть много хобби-проектов в 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 Mike42EscposPrintConnectorsFilePrintConnector;
use Mike42EscposPrinter;
$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 и списков покупок!
Автор:
boris-the-blade