Некоторое время назад я увлекался тем, то писал самодельные прошивки для различных готовых устройств. Так, например, сделал пульт для управления солярием из пульта от охранной сигнализации. А что, смотрите сами:
корпус есть отличный;
уже встроена клавиатура и не просто, а более-менее надежная;
есть светодиодные индикаторы и динамик (пищалка);
корпус штатно крепится к стене;
все собрано красиво и на вид надежно.
Внутри есть платка с AVR микроконтроллером, разъемом внутрисхемного программирования. Что осталось:
вывести наружу com порт для подключения к серверу;
приделать реле для включения пускателя солярия;
подключить блок питания;
и главное – написать саму прошивку.

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

После переделок касс, я начал работу по написанию утилиты для красивой печати. Дело в том, что я также разработчик на 1с, с++ и мне приходилось что-то печатать из кучи разных программ на кучу разных устройств. Поэтому я решил сделать суперуниверсальную программу, которая бы могла все:
печатать на разные устройства;
работать с 1 и 2 мерными штрих кодами;
уметь печатать кучей различных шрифтов;
иметь возможности по редактированию отдельных символов и создания целиком новых шрифтов;
должна быть возможность работы в режиме командной строки (т.е. программа на 1с готовит заранее текстовый файл, потом дает команду на печать);
показывать на экране как будет выглядеть документ после печати;
уметь работать со шрифтами двойной ширины/высоты, жирными, подчеркнутыми и т.п.;
двигать бумагу вперед и назад на любые требуемые интервалы;
и еще все что потребуется.


Постепенно я создал эту программу, и она уже умеет очень много всего, но вот я подхожу к основной теме статьи. Дело в том, что мне попадались на работе, у заказчиков аппараты АТОЛ. Обычно они назывались Fprint-22, Fprint-55 и еще куча вариантов. Это были совершенно разные ккм и общая черта была только та, что все они работали через драйвера Атол. В аппаратах были разные микроконтроллеры, разные термоголовки, и ширина бумаги была от 45 до 80 мм. Но все они работали через атоловские драйвера.
И часто заказчики хотели помимо основных функций (печать чеков) сэкономить на покупке термопринтера и использовать уже имеющуюся кассу для печати различных не фискальных документов. Это могли быть счета в барах, временные пароли для Wi‑Fi, талоны для въезда на территорию, те же коды для включения солярия на 5 или 10 мин. И я понял, что пришла пора для еще одной модернизации программы. Естественно, так просто на кассе не попечатаешь — это же не принтер.


Для начала я выделил три основных ветки драйверов. Это была 6…8 версии, 9 ая версия и 10 ая. Все они имели свои особенности.
Чем старше был аппарат, тем больше вероятность, что на самой новой 10-й версии он не заработает. Поскольку я пишу на с++, то взаимодействовать с атоловским драйвером пр��шлось из с++. Сразу скажу, это был еще тот квест.

6…8 версии
Тут как раз оказалось все просто. Сначала объявляем переменную
IFprnM45 ECR;
Потом создаем объект:
COleException *e = new COleException; try { if (!ECR.CreateDispatch("AddIn.FprnM45", e)) throw e; }
Дальше включаем устройство:
ECR.SetDeviceEnabled(true);
И готово, можно делать с кассой все что хочешь. Например,
ECR.FullCut();
Это полная отрезка ленты. В конце вызываем
ECR.ReleaseDispatch();
И все делов. т. е. ничего сложного. Конечно, перед этим надо было установить сами драйвера и зарегистрировать нужные библиотеки.
Потом к проекту на с++ я добавлял файл с заголовками функций fprnm1c.h.

10 версия
Для нее я уже не смог повторить тот фокус что для 6ой. Файл с заголовками я нашел, а дальше был затык. Может быть конечно я что‑то не допер, но создать объект fptr10 у меня не получилось. Пришлось подключать на лету dll от 10ой версии fptr10.dll и извлекать из нее функции. Делал я это так:
hDll10 = LoadLibraryA("fptr10.dll"); // загружаем typedef int (WINAPI *PFN_libfptr_create)(libfptr_handle); PFN_libfptr_create p_libfptr_create; // описываю импортируемую функцию libfptr_handle fptr10; // описываю дескриптор p_libfptr_create = (PFN_libfptr_create)GetProcAddress(hDll10, "libfptr_create"); // получаю ссылку на функцию iRes = (*p_libfptr_create)(&fptr10); // вызываю саму функцию
Вот так удалось подружиться с 10ой версией драйвера.

9 версия
Это была для меня самая нужная версия. Дело в том, что разработчики драйверов применяли несколько видов протокола обмена с кассами. Я знал про 2 и 3 версии. 6ая версия драйверов работала, как я понял со 2ым протоколом, 10ые драйвера с 3им протоколом. А 9ые драйвера были переходными! И в них была возможность выбора 2 или 3 версии протокола! Это было круто, т.к. позволяло работать и со старыми кассами и с новыми. Вот тут я и получил большую часть проблем. Но в итоге все заработало. Делал так:
hDll = LoadLibraryA("fptr.dll"); // загружаем DLL TED::Fptr::IFptr *fptr; // описываю указатель на дескриптор typedef TED::Fptr::IFptr * (WINAPI *PFN_CreateFptrInterface)(int); PFN_CreateFptrInterface p_CreateFptrInterface; // описываю импортируемую функцию p_CreateFptrInterface = (PFN_CreateFptrInterface)GetProcAddress(hDll, "CreateFptrInterface"); // получаю ссылку на функцию fptr = (*p_CreateFptrInterface)(i); // вызываю саму функцию
Тут меня ждала еще одна засада. При вызове CreateFptrInterface надо было передавать переменную int. Типа версию драйверов. Я передавал: 9, 0, 1, 6, 8, 10 и вообще хрен знает что, но вызов не срабатывал:‑( В итоге я решил сделать так. Создал цикл от 0 до 10 000 и вызывал эту заразу в цикле со всеми аргументами подряд, пока не вернет нормальное значение. И она вернула! В общем, удалось подобрать число. Если хотите, попробуйте сами подобрать, пишите в коменты что получится.
После этой инициализации можно уже было вызывать разные методы. Но тут встал главный вопрос:
Как печатать?
К драйверу‑то я подключился. Методы вызывались, а дальше?! Как вывести на кассу что‑то нужное, и при этом ее не убить? Для решения этого вопроса, я очень внимательно прочитал руководство программиста по драйверу атол и понял, что единственный шанс — это печатать документ как картинку. Моя программа как раз умела сохранять результат работы в формате BMP, и полдела уже было сделано. Далее я добавил команду для печати уже через драйвер атол и при нажатии соответствующей кнопки сначала формировал BMP файл (конечно же монохром 1 бит на пиксель) и потом уже начинал манипуляции с драйвером. Выглядели они примерно так:
ECR.SetFileName("c:\\tmpbmp.bmp"); ECR.PrintBitMapFromFile();

Конечно, тут тоже было много подводных камней. Для начала, сам временный файл. Если на windows 7 я отлично его записывал в корень диска С, то на windows 10 этот номер не прокатывал. Даже запуская программу под администратором, первый раз новый файл было не создать. Если только я сам его туда копировал и давал на него полные права, то перезаписывался файл нормально. Потом пришлось перенести сохранение файла в другую папку.
Также были сложности с шириной и высотой картинки. Разные аппараты имели разные размеры печатной области. Приходилось подстраиваться.
Еще одна проблема была с невозможностью обратной прокрутки бумаги. Это было нужно для отреза сразу после области печати и для начала печати без полей. Если в аппаратах с моей собственной прошивкой я мог крутить шаговик, как говорится «налево, направо и в другие стороны», то тут вниз бумагу было не протянуть. Может есть тут гуру по атолу, посоветуете в коментах как это можно сделать. Но судя по тому, что даже настоящие чеки касса печатает «особым способом», т. е. без обратной прокрутки, этого не сделать.
«Особый способ» — это когда печатается чек и сразу шапка следующего чека, а потом происходит отрез. Отрезается текущий документ, а первые несколько строк следующего при этом уже напечатаны и скрыты под крышкой аппарата. Следующий документ печатается уже не с начала а, например, с 5ой строки. И тоже после его печати печатаются первые 5 строк следующего. В общем, способ не особо удобный. При нем надо чтобы первые строки везде были одинаковые.
В общем, программа заработала, несколько аппаратов из имеющегося у меня хлама вообще не смогли ничего распечатать, но это были совсем старые модели, которые имели 2 ленты и ширину бумаги 45 мм. У них просто не было вообще команды печать графики.
