Интеграция оборудования с веб-системами часто становится непростой задачей, особенно когда дело касается специфических устройств, таких как кассы. В одном из наших проектов потребовалось настроить взаимодействие кассы АТОЛ 30Ф с веб-приложением, работающим на PHP. Этот кейс стал для нас важным шагом в изучении возможностей автоматизации и оптимизации бизнес-процессов.
Как всё начиналось
Касса АТОЛ 30Ф — это популярное решение для ритейла, но её работа традиционно рассчитана на использование с локальными системами, такими как 1С. Нам же нужно было подключить её к веб-приложению, чтобы обеспечить удалённое управление операциями.
Проблема заключалась в том, что драйверы кассы были написаны на языке C и не имели нативной поддержки PHP. Задача заключалась в том, чтобы связать веб-приложение с физическим устройством напрямую, сохранив его функциональность и производительность. Решение мы нашли в технологии FFI (Foreign Function Interface), позволяющей PHP взаимодействовать с библиотеками на других языках.
FFI: как PHP взаимодействует с C
FFI — это расширение PHP, которое открывает доступ к библиотекам на C. С его помощью можно описать функции библиотеки и вызывать их из PHP-кода, как если бы они были встроенными.
Работа началась с изучения заголовочного файла библиотеки Драйвер контрольно-кассовой техники v.10. Этот файл содержал описание ключевых функций, включая команды для управления устройством, передачи данных и получения ответов. Благодаря FFI мы смогли подключить библиотеку и использовать её функции прямо из веб-приложения.
Например, команда для печати чека выглядела следующим образом:
Код на С для печати чека
#include "libfptr10.h"
libfptr_handle fptr;
libfptr_create(&fptr);
libfptr_set_param_str(fptr, LIBFPTR_PARAM_COMMODITY_NAME, L"Товар");
libfptr_set_param_double(fptr, LIBFPTR_PARAM_PRICE, 100);
libfptr_set_param_double(fptr, LIBFPTR_PARAM_QUANTITY, 5.15);
libfptr_set_param_int(fptr, LIBFPTR_PARAM_TAX_TYPE, LIBFPTR_TAX_VAT10);
libfptr_set_param_double(fptr, LIBFPTR_PARAM_TAX_SUM, 51.5);
libfptr_registration(fptr);
Для упрощения взаимодействия с функциями и переменными из кода на C мы создали PHP-классы-заглушки. Они полностью повторяли структуру заголовочного файла библиотеки, предоставляя описание необходимых функций и переменных на уровне PHP.
Этот подход значительно упростил работу: разработчики могли удобно вызывать методы, а также быстро просматривать их сигнатуры и параметры прямо в IDE, без необходимости заглядывать в исходные файлы на C.
Подключение драйвера ККТ в нашем коде$libfptr = FFI::cdef(file_get_contents('libfptr10.h'), 'libfptr10.so');
$fptr = $libfptr->new('libfptr_handle', false);
$libfptr->libfptr_create(FFI::addr($fptr));
$libfptr->libfptr_set_param_str($fptr, Param::LIBFPTR_PARAM_COMMODITY_NAME, 'Товар');
$libfptr->libfptr_set_param_double($fptr, Param::LIBFPTR_PARAM_PRICE, 100);
$libfptr->libfptr_set_param_double($fptr, Param::LIBFPTR_PARAM_QUANTITY, 5.15);
$libfptr->libfptr_set_param_int($fptr, Param::LIBFPTR_PARAM_TAX_TYPE, TaxType::LIBFPTR_TAX_VAT10);
$libfptr->libfptr_set_param_double($fptr, Param::LIBFPTR_PARAM_TAX_SUM, 51.5);
$libfptr->libfptr_registration($fptr);
PHP вызывал функцию драйвера, которая напрямую передавала команды кассе.
Стек технологий
Для реализации проекта мы использовали следующий стек технологий:
PHP 8.0: основной язык разработки веб-системы.
FFI: для связи с библиотекой на C.
MySQL: для хранения данных о транзакциях и конфигурациях касс.
Nginx: в качестве веб-сервера.
Docker: для упрощения тестирования и развёртывания.
Linux: серверная операционная система, обеспечивающая поддержку необходимых драйверов.
Особенностью проекта стало сочетание гибкости PHP для веб-разработки и производительности библиотек на C.
С какими трудностями мы столкнулись
Этот проект не обошёлся без сложностей, которые потребовали нестандартного подхода. Одной из основных проблем стало различие в форматах данных: PHP использует строки в кодировке UTF-8, тогда как драйвер кассы ожидал 32-битные данные.
Ещё одной сложностью стало управление памятью. В PHP оно происходит автоматически, тогда как в C за это отвечает разработчик. Чтобы избежать утечек и сохранить стабильность работы, мы реализовали дополнительные меры для корректного освобождения памяти. Кроме того, нам нужно было обеспечить поддержку разных способов подключения кассы — USB, Bluetooth и IP. Мы адаптировали систему так, чтобы она могла работать с любым из этих интерфейсов, обеспечивая гибкость использования в зависимости от сценария.
Как это работает в веб-системе
На финальном этапе наша команда завершила настройку интеграции. Теперь взаимодействие с кассой выглядит просто и интуитивно. Например, чтобы напечатать чек, достаточно вызвать PHP-функцию, а всё остальное — от преобразования данных до отправки команды — происходит под капотом.
Это позволило клиенту управлять кассовыми операциями удалённо, что особенно полезно в ситуациях, когда устройство подключено через IP. При необходимости веб-система также поддерживает работу с кассой через локальные интерфейсы, такие как USB или Bluetooth.
Что мы вынесли из этого проекта
Опыт, полученный в процессе реализации задачи, показал, насколько мощным инструментом может быть FFI. Он позволяет интегрировать устройства и системы на разных языках, что открывает широкие возможности для автоматизации и оптимизации бизнес-процессов.
Этот подход оказался универсальным и может быть применён для работы не только с кассами, но и с другими устройствами: от драйверов видеокарт до сетевых адаптеров.
Работа с FFI и драйверами на C расширила наши представления о возможностях PHP и показал, что грамотное использование технологий позволяет находить простые и эффективные решения для сложных задач.
Комментарии (12)
bolk
02.12.2024 13:13PHP использует строки в кодировке UTF-8, тогда как драйвер кассы ожидал 32-битные данные
Что-то я не понял это место. PHP работает с однобайтовой строкой, для остальных кодировок, в том числе UTF-8, нужно использовать внешние функции (iconv, mb* и так далее). Можете раскрыть тему?
FanatPHP
02.12.2024 13:13Да ладно придираться-то :) Понятно что не "РНР", а "наше приложение". Совершенно стандартная конфигурация любого современного сайта.
bolk
02.12.2024 13:13Причём тут придирки?
Понятно что…
Я начал своё сообщение с фразы «…я не понял это место». Если вам понятно — ок. Но прочитайте внимательно что именно я написал.
FanatPHP
02.12.2024 13:13И чем не устраивает моё объяснение? Понятно что я свечку не держал, но оно на мой взгляд совершенно очевидно. Вместо РНР следует читать "наше приложение". И всё сразу встанет на свои места. "Наше приложение, как и любой другой современный сайт, работает с данными в кодировке utf-8, а драйвер кассы ожидал UTF-32".
Ну или как вариант, эту фразу можно толковать как отсылку к default_charset, которая задает умолчания для тех же mbstring и iconv и плюс формирует кодировку для заголовка Content-type. Суть будет та же, но чисто формально ближе к "PHP использует".
И не нужно быть таким серьёзным, не корову мы тут продаём :)
bolk
02.12.2024 13:13Тем, что оно может быть правильным, а может быть и нет.
И не нужно быть таким серьёзным, не корову мы тут продаём :)
Разберусь сам.
Baikonur_2005
02.12.2024 13:13Ваш кассовый аппарат подключен к той же машине где поднят web сервер с php?
samponet
02.12.2024 13:13Я писал управление драйвером для мониторинга этих касс под виндой, может дело в этом, и таких сложностей не испытывал. К этим кассам идёт подробное описание работы с драйвером на С, питоне, С++,С#,go,java, delphi, почему выбрали именно С- не очень понятно.
FanatPHP
02.12.2024 13:13Не очень понятно, что именно вам непонятно. Вы же сами пишете
К этим кассам идёт подробное описание работы с драйвером на С
Ну вот они и взяли этот драйвер на C, и обращаются к нему из РНР. Что здесь непонятного?
samponet
02.12.2024 13:13Т.к. в посте не указана ОС, то не понятно в чем было затруднение. Не знаю как php может работать с библами и драйверами в линукс, в винде через СОМ объект взаимодействие нативно как я вижу, и не важно, на чем написан там драйвер или библиотека.
ArchDemon
02.12.2024 13:13Так же интегрировал CRM на php с кассой Атол. Но использовал веб-сервис Атол для работы с кассами. Пока не очень понятен профит от FFI
FanatPHP
Спасибо, кейс очень интересный. Вот только отчаянно не хватает подробностей. Сейчас материал больше похож на заметки для себя, чем на познавательную статью для читателей.
Фактически, информационная составляющая сводится к "В PHP есть FFI". А те же трудности только упомянуты, но не даётся никаких рекомендаций по их преодолению.
Pardych
Так вся задача и сводится к тому чтобы взять ffi и вперёд. Можно ещё плагином наколхозить. Никаких сложностей. Кодировки строки дежурная задача для интеграции фискальника на си/цпп (там два варианта апи). Я точно так же к флаттеру присобачивал на десктопах.