Интеграция оборудования с веб-системами часто становится непростой задачей, особенно когда дело касается специфических устройств, таких как кассы. В одном из наших проектов потребовалось настроить взаимодействие кассы АТОЛ 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)


  1. FanatPHP
    02.12.2024 13:13

    Спасибо, кейс очень интересный. Вот только отчаянно не хватает подробностей. Сейчас материал больше похож на заметки для себя, чем на познавательную статью для читателей.

    Фактически, информационная составляющая сводится к "В PHP есть FFI". А те же трудности только упомянуты, но не даётся никаких рекомендаций по их преодолению.


    1. Pardych
      02.12.2024 13:13

      Так вся задача и сводится к тому чтобы взять ffi и вперёд. Можно ещё плагином наколхозить. Никаких сложностей. Кодировки строки дежурная задача для интеграции фискальника на си/цпп (там два варианта апи). Я точно так же к флаттеру присобачивал на десктопах.


  1. bolk
    02.12.2024 13:13

    PHP использует строки в кодировке UTF-8, тогда как драйвер кассы ожидал 32-битные данные

    Что-то я не понял это место. PHP работает с однобайтовой строкой, для остальных кодировок, в том числе UTF-8, нужно использовать внешние функции (iconv, mb* и так далее). Можете раскрыть тему?


    1. FanatPHP
      02.12.2024 13:13

      Да ладно придираться-то :) Понятно что не "РНР", а "наше приложение". Совершенно стандартная конфигурация любого современного сайта.


      1. bolk
        02.12.2024 13:13

        Причём тут придирки?

        Понятно что…

        Я начал своё сообщение с фразы «…я не понял это место». Если вам понятно — ок. Но прочитайте внимательно что именно я написал.


        1. FanatPHP
          02.12.2024 13:13

          И чем не устраивает моё объяснение? Понятно что я свечку не держал, но оно на мой взгляд совершенно очевидно. Вместо РНР следует читать "наше приложение". И всё сразу встанет на свои места. "Наше приложение, как и любой другой современный сайт, работает с данными в кодировке utf-8, а драйвер кассы ожидал UTF-32".

          Ну или как вариант, эту фразу можно толковать как отсылку к default_charset, которая задает умолчания для тех же mbstring и iconv и плюс формирует кодировку для заголовка Content-type. Суть будет та же, но чисто формально ближе к "PHP использует".

          И не нужно быть таким серьёзным, не корову мы тут продаём :)


          1. bolk
            02.12.2024 13:13

            Тем, что оно может быть правильным, а может быть и нет.

            И не нужно быть таким серьёзным, не корову мы тут продаём :)

            Разберусь сам.


  1. Baikonur_2005
    02.12.2024 13:13

    Ваш кассовый аппарат подключен к той же машине где поднят web сервер с php?


  1. samponet
    02.12.2024 13:13

    Я писал управление драйвером для мониторинга этих касс под виндой, может дело в этом, и таких сложностей не испытывал. К этим кассам идёт подробное описание работы с драйвером на С, питоне, С++,С#,go,java, delphi, почему выбрали именно С- не очень понятно.


    1. FanatPHP
      02.12.2024 13:13

      Не очень понятно, что именно вам непонятно. Вы же сами пишете

      К этим кассам идёт подробное описание работы с драйвером на С

      Ну вот они и взяли этот драйвер на C, и обращаются к нему из РНР. Что здесь непонятного?


      1. samponet
        02.12.2024 13:13

        Т.к. в посте не указана ОС, то не понятно в чем было затруднение. Не знаю как php может работать с библами и драйверами в линукс, в винде через СОМ объект взаимодействие нативно как я вижу, и не важно, на чем написан там драйвер или библиотека.


  1. ArchDemon
    02.12.2024 13:13

    Так же интегрировал CRM на php с кассой Атол. Но использовал веб-сервис Атол для работы с кассами. Пока не очень понятен профит от FFI