Pull to refresh
1862.51

Терминалы Lipman Nurit и их программирование

Level of difficulty Medium
Reading time 22 min
Views 4.2K
Приветствую всех!

Не так давно я уже рассказывал о разработке приложений для банковских терминалов Ingenico. И тогда я сказал, что платформа Telium была выбрана из-за того, что она единственная, под которую в открытом доступе есть SDK.

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



Итак, в ходе данной статьи поговорим о несколько более древних и экзотических терминалах. Узнаем, как писать под них, где взять софт и что со всем этим делать. Традиционно будет много интересного.

Суть такова


Несмотря на то, что я уже попробовал писать под Ingenico, хотелось чего-то большего. Как минимум, потому что SDK Telium был лишён подробной документации, а приложенного в комплекте CHM'а для старта разработки явно достаточно не было, также были некоторые нюансы с персонализацией терминалов (которую нельзя осуществить средствами SDK) и сопутствующими моментами. Заодно и просто было интересно узнать, как обстоят дела у других производителей.

И вот, наконец, я получил всё необходимое, а это значит, что теперь можно попробовать это в работе и рассказать о впечатлениях вам.

Обзор оборудования


Итак, для начала разберёмся с тем, под что мы вообще будем писать. Терминалы эти производились израильской компанией Lipman Electronics Engineering, позже поглощённой VeriFone, под линейкой Nurit. Устройства работали на процессорах MC68000 или ARM. В ходе данной статьи рассмотрим последний ввиду наличия у меня софта под него. Тем не менее, для устройств на MC68000 во многом всё идентично, так что если вам удастся раздобыть библиотеки и компилятор под них, то эта статья тоже пригодится.

Итак, взглянем на типичных представителей терминалов данной фирмы.



Nurit 2085. Старый терминал на MC68000. Из косяков моего экземпляра — отсутствует крышка принтера.





Аккумулятор внутри потёк, но выкусил я его до того, как дорожки позеленели.





Nurit 3020. Один из самых навороченных экземпляров на MC68000. Была также модель 3010 с GSM-модемом.









Внутренности. Терминал не оснащён хранилищем ключей, поэтому тамперов тоже нет. Память поддерживает всё тот же аккумулятор (на этот раз он в идеальном состоянии). Из необычного — принтер имеет рычаг, отводящий головку от прижимного валика, чтобы можно было подсунуть новую ленту, но он тут жёстко зафиксирован в защёлкнутом положении, а бумага заправляется при прокрутке (для чего есть комбинация кнопочек). По идее, износ принтера при этом выше, ну да ладно.



Процессор MC68000 и микросхема Flash внутри терминала 3020. В глаза бросается также некий разъём «DEBUG CARD». Скорее всего, использовался он на заводе для проверки платы на работоспособность.





Nurit 8320. По виду он очень напоминает предыдущий, но построен он на процессоре ARM, также он оснащён хранилищем ключей. GSM-модем теперь полностью встроенный (у аппаратов вроде Nurit 3010 была внешняя антенна). Два слота под SAM-модули и никель-металлгидридный аккумулятор никуда не делись.









Разбираем.



Электроника, разумеется, сильно отличается. Большинство чипов находятся на плате клавиатуры и закрыты металлическим экраном, который я для фото снял. Документация на процессор нигде не находится, по-видимому, он тут заказной.



GSM-модем Siemens MC35i.







Nurit 8000. Портативный терминал с тачскрином.









Ну и внутренности, как же без них. Аккумулятор теперь литий-ионный. Разъёмы для зарядки и подключения к компьютеру проприетарные, их распиновка мне неизвестна. Процессор и память залиты смолой. Как и в прошлой модели, в нём присутствуют тамперы.

Сейчас всё это уже давно ушло в историю. Время терминалов без бесконтактной оплаты прошло. Тем не менее, такие аппараты всё ещё реально найти на вторичке (и цена вполне молодёжная), так что раздобыть экземпляр для препарирования и опытов сложно не будет.

Также у данных устройств есть один нюанс с блоком питания: он тут на шестнадцать вольт и с инверсной полярностью (центральный контакт разъёма — минус!).

Пин-пады


Помимо самих терминалов ко мне в руки попали и пин-пады. Увы, ни на один из них у меня нет ни софта, ни документации. Но это не так страшно, так как по системе команд они идентичны таковым у VeriFone, которым у меня был посвящён отдельный пост. Единственное, у Nurit пакет данных, содержащий в себе PIN-block, короче на один байт. У VeriFone этот байт отведён для состояния трёх программируемых кнопок под экраном (которые в некоторых версиях прошивки пин-пада даже не использовались).



Пин-пад Nurit 272. Распиновка идентична таковой у VeriFone PP1000SE.



Дисплей на HD44780, «из коробки» поддерживает и русский шрифт.

А вот так выглядит ввод PIN.
Разбирать экземпляр я не стал, так как внутренняя батарейка всё ещё жива, а разбираться лишний раз со сбросом тампера не хотелось.



А вот другой пин-пад, Nurit 292. Он один из самых поздних.



Обратная сторона. Под крышкой слоты под SAM-модули.



Инновационная израильская технология «RS-232 over USB». По контактам разъёма miniUSB гонятся уровни обычного RS-232 и двенадцать вольт питания.



Внутренности. Пин-пады Nurit, в отличие от большинства таковых у VeriFone, имеют в себе считыватель смарт-карт. Батарейка, как водится, дохлая.



Обратная сторона маленькой платы. Виден разъём miniUSB.



Вид со снятой платой. Внутренности залиты смолой.

image

Пин-пад VeriFone PP1000SE v2. Он тут упомянут неспроста — устройство использует ряд наработок Lipman, в частности, операционную систему. Девайс имеет режим работы «Nurit», позволяющий использовать его вместе с терминалами данной фирмы. Собственно, всё, что можно сказать про данную штуку, сказано в посте про пин-пады, так что тут повторяться не буду.

Немного про применение


Хотя эти устройства создавались для банковских операций, у нас они использовались по большей части в ряде других мест (аналогично тому, как на терминалах VeriFone OMNI-395 поднимали обслуживание карт лояльности).

Nurit 8010 использовались как транспортные терминалы. Интересно, кстати: что это за считыватель и как он подключается к терминалу?



Также они нашли своё применение как мобильные платёжные терминалы в системах наподобие ОСМП (он же QIWI). Карты они не принимали, а лишь служили для связи с сервером платёжной системы. По сути они были полными аналогами привычных нам «ящиков», с которых мы некогда пополняли счета телефонов, только управлялись они не нами, а операторами в точках обслуживания. Софт этот и поныне лежит здесь. В архивной копии сайта «Киберплат» есть и другой вариант подобного софта.

Где взять ПО


SDK для этих терминалов искать пришлось долго. И очень долгое время он не попадался нигде. Был, правда, некий сайт possoftware.ucoz.com невразумительного содержания, где вроде как можно было купить данное ПО. Цена на сайте указана не была, но после некоторых поисков я нашёл на каком-то заплесневевшем форуме пост этого же товарища, где нужная цифра таки была — несколько тысяч долларов за один SDK. После выяснения всего этого стало понятно, что ищу я не в том направлении.

И вот в комментариях к моему посту про Ingenico товарищ vladkorotnev таки подкинул заветную ссылку. Как мне было объяснено, валялся архив на каком-то китайском сайте вроде CodeForge.com, откуда и был скачан много лет назад.

Архитектура


Ну и для начала поговорим немного об устройстве программной части этих устройств. Все терминалы Nurit работают на своей проприетарной ОС — Nurit OS (NOS). Терминал оснащён Flash, где находятся загрузчик, сама NOS, а также пользовательское приложение.

Загрузчик осуществляет запуск NOS, а также её перезапись в случае таковой необходимости (например, при обновлении ОС). Перепрошивка осуществляется при помощи софта OSP Loader и *.osp-файла, представляющего собой запароленный ZIP-архив, содержащий в себе все версии ОС для всех терминалов, сертифицированных для страны, для которой этот пакет предназначен. Переписать можно также и сам загрузчик (но если что-то пойдёт не так, восстановить терминал выйдет только при помощи программатора).



Загрузчик также обладает небольшой пасхалкой: пароль на запуск NOS — 1947, знаковый год для Израиля, где и выпускались данные аппараты.

NOS отвечает за загрузку приложения, все служебные функции по типу обмена данными при загрузке, а также работу с оборудованием на низком уровне. Именно через неё осуществляется взаимодействие приложения с железом терминала. Существует два режима работы приложения — Single и Multi. В первом режиме приложение запускается после загрузки NOS и взаимодействует с её API всё время работы вплоть до завершения, во втором же при наступлении некого события NOS вызывает его обработчик, который затем возвращает управление операционной системе терминала. Кроме этого, переключение Single->Multi в меню NOS служит для очищения Flash.

Тем не менее, даже в Single-режиме приложение должно периодически отдавать управление NOS, чтобы фоновые процессы были завершены. Для этого необходимо периодически вызывать функции Mte_Wait(), Kb_Read(), Kb_WaitForKey(). Если это не будет выполняться больше, чем десять секунд, терминал автоматически перезагрузится. То есть, например, если ваше приложение начнёт тупить в бесконечном цикле, без вызова этих функций ОС посчитает, что оно зависло, и вырубит его. Если же этот самый цикл предусмотрен вами (например, терминал ждёт, пока придут данные из порта), то необходимо не просто висеть, а вызывать Mte_Wait(1).



Собственно, вот такой отчёт печатается при аварийной перезагрузке.



NOS также имеет своё собственное меню, использующееся для загрузки данных и диагностики устройства.

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



А вот схема организации памяти (из официальной документации). Девайс оснащён RAM-диском, где можно хранить данные приложений, защищённые модели имеют хранилище ключей.

Входим в NOS


В отличие от Ingenico Telium, где вход в менеджер осуществлялся при помощи выбора пункта меню, местное служебное меню открывается комбинацией кнопочек.



Самой главной является четвёрка 4 + Menu + -> + Enter, позволяющая войти в меню NOS (что придётся делать при каждой загрузке приложения). Иногда нужен и загрузчик, например, если вы случайно загнали терминал в бесконечное ожидание и на клавиатуру он не реагирует. В таком случае необходимо зайти в NOS Loader, выбрать пункт «Start NOS» и ввести пароль: 1947



То же самое для Nurit 8000. Увы, в полной мере запустить его у меня нет возможности, так как разъём для подключения к ПК проприетарный, а аналога ему, чтобы спаять кабель, я так и не нашёл.



А вот для Nurit 8400. Такого терминала у меня нет, но считаю, что лишним тут это не будет.

Ставим софт


Приступим к установке и запуску. Из скачанного архива понадобится примерно следующее:

  • ARM-toolchain, которым, собственно, и будем собирать программу
  • Библиотеки
  • Nurit Software Development tools — упаковщик приложений и загрузчик
  • ADE — примеры проектов
  • LipHelp — справочник разработчика

Для загрузки в терминал понадобится Fast Loader, скачать его можно тут. OSP Loader не подойдёт, а Smart Loader очень громоздкий и в большинстве случаев не нужен.
Также для запуска будет нужен ПК с Windows 2000 или XP или виртуалка с ней же (очень желательно именно её).

Итак, для начала накатываем ADE_ARM_1_7.49.00A.EXE из папки ADE, далее — Nurit Software Development tools и Nurit Help. Установка всего этого очень проста и проблем не вызывает.

Теперь очередь компилятора. Он имеет триальную лицензию, которая конкретно у экземпляра из архива истекла ещё в незапамятные годы. Поэтому переводим дату на первое января двухтысячного, чтобы обеспечить его работоспособность. Именно для этого и желательно установить Windows на виртуальную машину, чтобы таким переводом даты не поломать работу другого софта.

В папке Compilador (архив, судя по всему, откуда-то из Бразилии) лежит hcarm_4.5a. Перемещаем эту папку в корень системного диска. В свойствах системы находим переменные среды, к Path добавляем C:\hcarm_4.5a;C:\hcarm_4.5a\bin;hcarm_4.5a\lib. Из корня архива берём CLKERN.DLL и закидываем в папку bin компилятора.
В общем-то, на этом всё. Можно пробовать запускать.

Пишем первую программу


Ну что же, время пробовать что-то написать. Вообще, традиционной «тестовой» проги в SDK нет. Есть что-то в папке ApplicationExample из архива, но при всей простоте проекта собрать его у меня так и не получилось. Поэтому воспользуемся тем, что есть.
Идём по адресу C:\ADE_ARM\ADE_ARM7\SNGL_APL\SRC, там открываем DEMO.C, удаляем там весь текст, а взамен вставляем примерно следующее:

#include "project.def"
#include liptypes_def
#include nurit_def
#include kb_h
#include kbhw_h
#include mte_h
#include display_h
#include grphdisp_h
#include grphtext_h
#include grphtext_def
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
 
 
void main (void){
	Display_SetFont(FONT_10X7);
	Display_Cls(DSPL1);
	Display_FormatWrite(DSPL1,1,LEFT_JST ,"Hello, Habr!");
	for(;;){
		Kb_WaitForKey();
	}
}

Помимо этого файла там лежат ещё два — в DLMDECLR.C загружаются динамически подключаемые библиотеки, а в APPLHEAD.C указывается заголовок приложения. Выглядит это примерно так:

#include "project.def"
#include nurit_def
#include liptypes_def
#include applhead_def
#include dlmlinkr_def

extern void static_data_size(void);
extern DLMEntryData BaseDlmIndex;

static const application_header id =

              {
                APPL_SIGNATURE,                     /* Signatrue              */
                ((unsigned long)static_data_size),  /* End of Static data     */
                {'0','1','.','0','0'},              /* minimum NOS Version    */
                {"demo"},                           /* Application Name       */
                {'0','1','.','0','0'},              /* Application Version    */
                {"demo"},                           /* Appl Description       */
                {"06/06/01"},                       /* Date                   */
                0,                                  /* New header format      */
                0,                                  /* Number of DLMs         */
                DLMS_FIRST_DLM(BaseDlmIndex),       /* Pointer to DLM list    */
                0,                                  /* Industry type          */
                0,                                  /* Protocol type          */
                0,0                                 /* Spare bytes            */

              };

Тут можно указать, в частности, минимальную версию NOS, а также версию и дату выпуска самого приложения. Позже это можно будет посмотреть в меню «Application info» NOS.
Отдельного внимания также заслуживает файл project.def. А находится там примерно следующее:

/*----  SYSTEM DEFINITIONS  ----*/

#define PACKED _Packed
#define packed _Packed

/*--------------------------*/
/*----  8000 NOS FILES  ----*/
/*--------------------------*/

#define   liptypes_def "..\..\nos_api\liptypes.def"
#define   applhead_def "..\..\nos_api\applhead.def"
#define   dlmlinkr_def "..\..\nos_api\dlmlinkr.def"
#define   dlmhead_def  "..\..\nos_api\dlmhead.def"
#define   evd_hd_def   "..\..\nos_api\evd_hd.def"
#define   nurit_def    "..\..\nos_api\nurit.def"
#define   comhndlr_def "..\..\nos_api\comhndlr.def"
#define   formater_def "..\..\nos_api\formater.def"
#define   gsm_msg_def  "..\..\nos_api\gsm_msg.def"
#define   multiapp_def "..\..\nos_api\multiapp.def"
#define   atmmenu_def  "..\..\nos_api\atmmenu.def"
#define   winform_def  "..\..\nos_api\winform.def"
#define   encryptr_def "..\..\nos_api\encryptr.def"
#define   seckb_def    "..\..\nos_api\seckb.def"
#define   session_def  "..\..\nos_api\session.def"
#define   grphtext_def "..\..\nos_api\grphtext.def"
#define   grphdisp_def "..\..\nos_api\grphdisp.def"
#define   bmp_def "..\..\nos_api\bmp.def"
#define   colors_def "..\..\nos_api\bmp.def"

#define   dialtgts_prm "..\..\nos_api\dial_3.prm"
#define   rdiotgts_prm "..\..\nos_api\rdio_3.prm"
#define   comhndlr_prm "..\..\nos_api\comhnd_3.prm"
#define   modem_prm    "..\..\nos_api\modem_3.prm"
#define   at_radio_prm "..\..\nos_api\at_radio.prm"
#define   gsmradio_prm "..\..\nos_api\gsmradio.prm"

#define   applmngr_h   "..\..\nos_api\applmngr.h"
#define   appload_h    "..\..\nos_api\appload.h"
#define   atmhw_h      "..\..\nos_api\atmhw.h"
#define   atmmenu_h    "..\..\nos_api\atmmenu.h"
#define   barcode_h    "..\..\nos_api\barcode.h"
#define   caller_h     "..\..\nos_api\caller.h"
#define   cardrdr_h    "..\..\nos_api\cardrdr.h"
#define   comhndlr_h   "..\..\nos_api\comhndlr.h"
#define   cpuhw_h      "..\..\nos_api\cpuhw.h"
#define   des_h        "..\..\nos_api\des.h"
#define   display_h    "..\..\nos_api\display.h"
#define   dns_h        "..\..\nos_api\dns.h"
#define   eeprom_h     "..\..\nos_api\eeprom.h"
#define   errlog_h     "..\..\nos_api\errlog.h"
#define   fismem_h     "..\..\nos_api\fismem.h"
#define   formater_h   "..\..\nos_api\formater.h"
#define   ftp_h        "..\..\nos_api\ftp.h"
#define   fstp_h       "..\..\nos_api\fstp.h"
#define   grphtext_h   "..\..\nos_api\grphtext.h"
#define   grphdisp_h   "..\..\nos_api\grphdisp.h"
#define   gsmradio_h   "..\..\nos_api\gsmradio.h"
#define   icctrans_h   "..\..\nos_api\icctrans.h"
#define   iccsyn_h     "..\..\nos_api\iccsyn.h"
#define   kb_h         "..\..\nos_api\kb.h"
#define   kbhw_h       "..\..\nos_api\kbhw.h"
#define   keysldr_h    "..\..\nos_api\keysldr.h"
#define   link_h       "..\..\nos_api\link.h"
#define   modem_h      "..\..\nos_api\modem.h"
#define   mte_h        "..\..\nos_api\mte.h"
#define   multiapp_h   "..\..\nos_api\multiapp.h"
#define   params_h     "..\..\nos_api\params.h"
#define   printer_h    "..\..\nos_api\printer.h"
#define   prntutil_h   "..\..\nos_api\prntutil.h"
#define   protmngr_h   "..\..\nos_api\protmngr.h"
#define   phone_h      "..\..\nos_api\phone.h"
#define   ramdisk_h    "..\..\nos_api\ramdisk.h"
#define   rtc_h        "..\..\nos_api\rtc.h"
#define   rtcutil_h    "..\..\nos_api\rtcutil.h"
#define   sysutil_h    "..\..\nos_api\sysutil.h"
#define   statist_h    "..\..\nos_api\statist.h"
#define   stat_bar_h   "..\..\nos_api\stat_bar.h"
#define   soft_kb_h    "..\..\nos_api\soft_kb.h"
#define   sign_cap_h   "..\..\nos_api\sign_cap.h"
#define   seckbmst_h   "..\..\nos_api\seckbmst.h"
#define   scanner_h    "..\..\nos_api\scanner.h"
#define   safe_h       "..\..\nos_api\safe.h"
#define   touch_h      "..\..\nos_api\touch.h"
#define   test_h       "..\..\nos_api\test.h"
#define   tcpapi_h     "..\..\nos_api\tcpapi.h"
#define   util_h       "..\..\nos_api\util.h"
#define   uart_h       "..\..\nos_api\uart.h"
#define   winform_h    "..\..\nos_api\winform.h"

/*-----------------------------*/
/*------   DLM API files  -----*/
/*-----------------------------*/

#define   demoapi_api  "..\..\dlm1\src\demoapi.api"
#define   demo2api_api "..\..\dlm2\src\demo2api.api"

Зачем такие сложности с сотней define'ов и столь странным переименованием файлов, мне решительно неведомо. Чтобы при перемещении папки в другое место не пришлось бы переписывать пути во всём коде? Так можно просто указать путь ко всему NOS_API в переменной среды, как это делалось в большинстве других SDK, что я видел. Ну да ладно.
Далее идём в RELEASE и в папке OBJ удаляем всё, что там лежит. Запускаем батник BUILD.BAT. Если всё было сделано правильно, то demo.log так и останется пустым, а в папке RELEASE появится demo.hex (или же у него просто станет другой дата изменения).

Но demo.hex — ещё не приложение. Чтобы сделать его таковым, нужен Application Integrator из состава Nurit Software Development Tools. Запускаем его.



В открывшемся окне жмякаем крайнюю левую кнопку на панели, в открывшемся окне выбираем наш *.hex-файл. Перед сборкой загружаемого компонента можно посмотреть сведения о приложении (те самые, что были прописаны в APPLHEAD.C) или задать ограничения.



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

Подписывание приложений




По уровню защиты терминалы Nurit делятся на два вида — Secured и NOT Secured. Отличия между ними в том, что в «незащищённом» аппарате не используются тамперы (то есть его можно разобрать, и ему ничего не сделается), разрешена загрузка приложения из одного терминала в другой, установлены некоторые ограничения на криптографию. Есть и аппаратные отличия, например, защищённые терминалы внутри залиты смолой. Узнать, к какому типу относится ваш терминал, можно, если открыть NOS, выбрать пункт «Terminal ID», а затем выйти из него. При этом на пару секунд на экране загорится Terminal is Secured или Terminal is NOT Secured. Тампер с защищённого терминала сносится перепрошивкой ОС при помощи OSP Loader.

Если приложение загружается в «защищённый» терминал, оно должно быть подписано (увы, мне неведомо, каким именно софтом). Под эту подпись выделено пятьсот двенадцать байт в соответствующем поле в заголовке.

Скорее всего, вам достанется именно «простой» вариант, так как ОСМП и подобные организации не использовали криптографические функции.

Fast Loader


Теперь можно загрузить приложение в терминал. Вообще, Lipman предоставляла эмулятор терминала для удобства отладки, но он до меня не дошёл, увы.
Для этого нам понадобится кабель для COM-порта и, собственно, download tool.

Схема кабеля здесь вот такая:


Для сборки надо всего-то лишь огрызок патч-корда и разъём DB9F. К терминалу от компьютера идёт семь проводов, но для загрузки на деле достаточно всего трёх: RX, TX, GND. Также подойдёт загрузочный кабель для VeriFone, распиновка тут идентичная.
Порядок контактов виден тут:


(если что, видео вообще не про Nurit, а про VeriFone VX520. Скриншот этот был мною сделан больше трёх лет назад ради распиновки кабеля)
На этом фото красный провод — земля, белый и чёрный — RX и TX.
В терминале установлен разъём 10P10C, но крайние контакты не задействованы, так что можно воткнуть туда обычный сетевой 8P8C, работать тоже будет.



Далее открываем Fast Loader (или просто открываем собранное приложение, и он запустится сам). Тут настраиваем параметры порта.



Теперь очередь терминала. Заходим в NOS, выбираем «Download». Далее жмякаем Enter и набираем любое число (хоть «111»). Набрать что-то надо обязательно, иначе при попытке загрузить что-то будет ошибка.



Выбираем «Comm Prm», чтобы выставить параметры соединения.



Указываем порт COM1 и ту же скорость, что настраивали в Fast Loader.



Далее выбираем «Manual load» и подтверждаем своё действие.



Теперь подключаем наш агрегат к компьютеру. Запускаем Fast Loader и жмём на терминале «Load appl». Спустя несколько секунд ПК и наш девайс установят связь, и «Starting DCP» вскоре сменится на «Erasing sectors», а затем и на прогресс-бар загрузки. Выходим из меню, NOS при этом перезагрузится. И, если мы нигде не накосячили, на дисплее должно будет появиться примерно следующее:



Шрифты


Разумеется, одним-единственным шрифтом терминал не ограничивается. Менять их можно так:

    Display_Cls(DSPL1);
    Display_SetFont(FONT_13X7);
    Display_FormatWrite(DSPL1,1,LEFT_JST ,"Hello, Habr!");
    Display_SetFont(FONT_10X7);
    Display_FormatWrite(DSPL1,2,LEFT_JST ,"from MaFrance351");




И вот так это выглядит в работе.

Принтер


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



Итак, берём BMPшку, конвертируем её в монохром и выставляем ширину строго 384 пикселя (это разрешение стоящей в терминале печатающей головки). Далее открываем какую-нибудь программу, позволяющую преобразовать изображение в массив. Я использовал LCD Assistant. Полученный файл переименовываем в myimage.c и кидаем в папку проекта. Сам массив должен иметь построчную ориентацию (один байт — восемь пикселей из горизонтальной линии). Программа для всего этого получилась вот такая:

#include "project.def"
#include "myimage.c"
#include liptypes_def
#include nurit_def
#include printer_h
#include prntutil_h
#include kb_h
#include kbhw_h
#include mte_h
#include display_h
#include grphdisp_h
#include grphtext_h
#include grphtext_def
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
 
    static ExGraphicsDescriptor   egd = { EX_SINGLE_LINEAR, /*graphical mode */
                                          EX_DIRECT,  /*0 - use a pointer to the data - static array*/
                                          48,  /*bytes in one line can be less or equal to the number set in mode*/
                                          403, /*number of lines*/
                                          EX_ALLIGN_CENTRE, /*bmp allignement*/
                                          {0,0,0,0,0,0,0,0} /*must be 0*/
                                        };
 
void main(void)
{
    byte key;
    Display_SetFont(FONT_13X7);
    Display_Cls(DSPL1);
    Display_FormatWrite(DSPL1,1,LEFT_JST ,"Printer test");
    Mte_Wait(2000);
    Printer_LineSpacing (0,FALSE);
 
    while(1)
    {
        key = Kb_WaitForKey();
        if (key == '1')
        {
					while(!Printer_WriteStr("I'm printing\n"));;
					while(!Printer_WriteStr("on Nurit 8320!\n"));;
                                        while(!Printer_WriteStr("\n\n\n"));;
					while(!Printer_PrintBitMap (&egd,(byte*)myBMP));;
        }
    }
}




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

ДСЧ


В качестве примера того, что реально используется в криптографии, рассмотрим работу с генератором случайных чисел.

#define nos_h "..\..\nos_api\nos.h"
#include "project.def"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "project.def"
#include nurit_def
#include random_h
#include kb_h
#include mte_h
#include display_h
#include grphdisp_h
#include grphtext_h
#include grphtext_def
#include cardrdr_h
#include printer_h
#include prntutil_h

 void main(void)
 {

    char   toLCD[64];
    usint  random_num;
 
    Display_Cls(DSPL1);
	Display_WriteStr(DSPL1,"Press any key");
    while(1)
    {
        Kb_WaitForKey(); 
        random_num = Random_GetRnd();
        sprintf( toLCD, "Random No:  %u", random_num );
        Display_WriteStr(DSPL1, toLCD);
        
    }
         }




Производитель заявляет, что терминал оснащён аппаратным ДСЧ, то есть значения, которые отображаются на экране, не являются псевдослучайными.

Магнитный считыватель


Попробуем считать карту с магнитной полосой. Тут это делается достаточно легко:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "project.def"
#include nurit_def
#include kb_h
#include mte_h
#include display_h
#include grphdisp_h
#include grphtext_h
#include grphtext_def
#include cardrdr_h
#include printer_h
#include prntutil_h
 
void main(void)
{
    sint tr1_error, tr2_error;
    char *get_tr2;
    Display_Cls(DSPL1);
    CardRdr_SetBeepTime(200);
    CardRdr_Enable();
        while(1)
        {
            Display_WriteStr(DSPL1,"Swipe card");
            while (!CardRdr_IsCardCompleted(&tr1_error,&tr2_error)) Mte_Wait(1);
            get_tr2 = (char*)CardRdr_GetTrack2() + 1;
			Display_FormatWrite(DSPL1, 1, LEFT_JST, get_tr2);
			Mte_Wait(2000);
        }
}


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

Меню




Ну и напоследок посмотрим, как реализовывается пользовательский интерфейс. Для этого тоже существуют специальные функции, позволяющие отображать меню и диалоговые окна. Конечно, по сравнению с девайсами VeriFone на базе ОС Verix простор тут куда меньший, но всё же это куда удобнее, чем заниматься отображением всего этого вручную.

Тут я решил не изобретать собственный костыль, а посмотреть, что на это скажет документация. Вот такой пример там есть.
/*******************************************************************************
 * FILE NAME:    Formater99                                                    *
 * MODULE NAME:  Formater                                                      *
 * PROGRAMMER:   Oren.S                                                        *
 *               [comments: Orna]                                              *
 *                                                                             *
 * DESCRIPTION:  This demo demonstrates the usage of Formater API functions:   *
 *                                                                             *
 * A menu having 6 entries is displayed:                                       *
 * 1. Entry no. 1 - runs a PopUp menu (directly from the menu entries)>        *
 *    the popup menu displays 2 entries> each entry pops-up a message.         *
 * 2. Entry no. 2 - calls a function (from the menu entries), which runs a     *
 *    menu> the menu displays 2 entries> each entry displays a string.         *
 * 3. Entry no. 3 - calls a function with no parameters. The functions         *
 *    displays a message.                                                      *
 * 4. Entry no. 4 - calls a function with parameters. The function displays a  *
 *    message.                                                                 *
 * 5. Entry no. 5 - runs a dialog box (directly from the menu entries), the    *
 *    dialog obtains string input from the user, but does not handle the input *
 * 6. Entry no. 6 - calls a function that runs a dialog box, allowing string   *
 *    input. The input is displayed.                                           *
  * REVISION:    01.00                                                         *
 *******************************************************************************/
/*==========================================*
 *          I N C L U D E S                 *
*==========================================*/
#include "project.def"
#include <string.h>
#include <stdio.h>
#include liptypes_def
#include nurit_def
#include formater_h
#include kb_h
#include display_h
#include util_h
/*=========================================*
*    P R I V A T E   F U N C T I O N S     *
*==========================================*/
void menu_func (void* dummy);
void param_func (void* IsVal);
void dialoge_func (void);
 
/*==========================================*
*        P R I V A T E  D A T A            *
*==========================================*/
/*prompt strings*/
static const char CHOICE1_MSG[] = "you chose 1";
static const char CHOICE2_MSG[] = "you chose 2";
static const char VOID_MSG[] = "void func";
static const char PARAM_MSG[] = "func & param";
 
/*empty string for the dialog box*/
static char my_string[17];
 
/*NOTE: the dialoge box structure is initialized from within
the application so my_string can be changed (not const) */
static dialoge my_dialoge;
 
/* main menu duplicate structure('entry' type), is needed in order that correct address
of my_dialoge, could be sent to Formater_DialogeBox */
static entry main_menu_dup_entries[6];
 
/*entries for popup menu and for menu via function*/
static entry menu_entries[2]=
{
    {"CHOICE 1", 0, param_func, (void*)CHOICE1_MSG},
    {"CHOICE 2", 0, param_func, (void*)CHOICE2_MSG}
};
 
/*the pop up menu structure*/
static menu popup_menu = {
                                    "POPUP MENU",
                                    2,
                                    &menu_entries[0],
                                    DEFAULT_MODE
                               };
/*the menu via function structure*/
static menu function_menu = {
                                    "FUNCTION MENU",
                                    2 ,
                                    &menu_entries[0],
                                    DEFAULT_MODE
                                  };
 
/* The entries of the main menu:
 * NOTE: the main_menu_entries in const, later copied to main_menu_dup_entries for
 * sending the correct address of my_dialoge. you can do without it by initializing all of
 * main_menu_dup_entries inside application, but this may be cumbersome*/
static entry main_menu_entries[6]=
{
    /* 1 - call popup menu, that displays 2 option, each displays a message*/
    {"POPUP MENU", 0, 0,(void *)&popup_menu},
 
    /* 2 - call function that call menu with 2 options, each displays a string*/
    {"MENU VIA FUNCTION", 0, menu_func, 0},
 
    /* 3 - call function with no parameters */
    {"VOID FUNC.", 0, param_func, 0},
 
    /* 4 - call function with parameter casted to void* */
    {"FUNC. WITH PARAM", 0, param_func, (void*)PARAM_MSG},
 
    /* 5 - call dialog box, the user input string will be stored where it's specified
     * in the dialoge structure, the input is not handled */
    {"DIRECT DIALOGE BOX", 0, (void*)Formater_DialogeBox,(void*)&my_dialoge},
 
    /* 6 - activate call to dialoge box via function, which also handles the input results */
    {"DIALOGE BOX VIA FUNC", 0, (void*)dialoge_func, 0}
};
 
/* the main menu structure*/
static menu main_menu;
/*==========================================================
* function :  main - initializes and activates the main menu
* parameters: None
* returns:    None
* notes:      None
*==========================================================*/
void main(void)
{
    /*initializing dialoge structure, must be done here so my_string is
    * both accessible and changeable */
    my_dialoge.header = "STRING INPUT:";
    my_dialoge.format = (void*)my_string;
    my_dialoge.format_flags = __STRING;
    my_dialoge.length = 16;
    my_dialoge.return_value = 0;
 
    /*initialize string for the dialoge structure */
    my_string[0] = 0;
 
    /*copy main_menu_entries to main_menu_dup_entries, as explained above */
    Util_MemCopy ((byte*)main_menu_entries,(byte*)main_menu_dup_entries,sizeof(entry)*6);
 
    /*initialize main_menu fields */
    main_menu.header = "MAIN MENU:";
    main_menu.no_of_entries = 6;
    main_menu.menu_entries = &main_menu_dup_entries[0];
    main_menu.mode_flags = DEFAULT_MODE;
 
    /*replace address of my_dialoge, the reason to all the duplicates,
    *for the compiler to enter the correct address */
    main_menu_dup_entries[4].parameters = (void*)&my_dialoge;
 
        for (; ;)
        {
            /*call main_menu, notice MENU_MODE which allows direct function and menu calls from menu */
            Formater_GoMenu( (menu*)&main_menu, MENU_MODE );
            Kb_Read ();
        }
}
/*=================================================
* function:   menu_func - activates a simple menu
* parameters: void* dummy - not in use
* returns:    None
* notes:      None
*=================================================*/
void menu_func(void* dummy)
{
    /*return-value from the menu */
    ulint ret_val;
 
    /* the msg to be displayed */
    char MSG[20];
 
    /* calls function_menu, notice CHOICE_MODE which disables direct function call and uses a
     * return value (the user choice)*/
    ret_val = Formater_GoMenu((menu*)&function_menu, CHOICE_MODE);
 
    /*switch of the user entry choice*/
    switch (ret_val )
    {
        /*copy string to buffer*/
        case 1 : strcpy(MSG,"1");
            break;
        /*copy string to buffer*/
        case 2 : strcpy(MSG,"2");
            break;
    }
    /*clear screen*/
    Display_ClrDsp ();
 
    /*display string*/
    Display_UpDisplay (MSG);
 
    /*poll KB for user key press*/
    Kb_WaitForKey ();
}
/*========================================================================
* function :   param_func - displays a msg that was sent or a "void" msg
* parameters:  void* IsVal - a msg or NULL casted as void*
* returns:     None
* notes:       used once from entry no.3 (in main menu) to be called without
*             parameter, and once from 4, with a parameter.
*=========================================================================*/
void param_func (void* IsVal)
{
    /*buffer to store message*/
    char MSG [20];
 
    /*determine if parameter was passed to function*/
    /*if no parameter is sent, display void message*/
    if (!((int)IsVal))
        strcpy(MSG, VOID_MSG);
    /*if parameter was sent, display another message*/
    else
        strcpy(MSG,(char*)IsVal);
        Display_ClrDsp ();
        Display_UpDisplay (MSG);
        Kb_WaitForKey ();
}
/*===========================================================================
* function :  dialoge_func - calls simple dialoge box and displaya the data
 *            received
* parameters: none
* returns:    void
* notes:      none
*===========================================================================*/
void dialoge_func(void)
{
    /*calls dialog box */
    Formater_DialogeBox((void*)&my_dialoge);
 
    /*clears screen*/
    Display_ClrDsp ();
 
    /*display the user input*/
    Display_UpDisplay (my_string);
 
    /*poll keyboard for user key press*/
    Kb_WaitForKey ();
}



Вот как-то так


Конечно, практического применения это уже давно не имеет. Эти терминалы навсегда ушли в историю, причём уже достаточно давно. Тем не менее, рассмотренный девайс оказался весьма интересным. А наличие подробного справочника разработчика, где детально описаны все функции и приведены примеры использования, не может не радовать. Также отмечу, что Nurit'ы намного дубовее ранее рассмотренных Telium и куда сильнее защищены от неправильных действий оператора (в отличие от Telium'a, окирпичить такой аппарат надо ещё постараться), отчего такой терминал можно с успехом использовать для каких-то собственных целей (например, как пульт управления или портативный термопринтер).

И, конечно, на этом мои эксперименты с POS-terminal'ами не заканчиваются. В частности, я не теряю надежды найти софт для ещё более реликтовых аппаратов (VeriFone SC 5000, Ingenico Unicapt32, Hypercom, Datacard, Innovatron Terminaux). Можно попробовать написать для него какую-нибудь игру или демку. И, конечно, можно попытаться сделать симулятор платёжного приложения, где бы использовалось большинство функций терминала типа криптографии и хранения данных.
Такие дела.

Ссылки



Tags:
Hubs:
+35
Comments 40
Comments Comments 40

Articles

Information

Website
timeweb.cloud
Registered
Founded
Employees
201–500 employees
Location
Россия
Representative
Timeweb Cloud