Приветствую всех!

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

Ссылки



Комментарии (39)


  1. kulhaker478
    00.00.0000 00:00

    Интересно и полезно, спасибо!

    Мне тоже в руки попал девайс из терминальной сферы, правда в виде залитика:

    Бедолага

    Электроэрозия сделала своё дело и сгрызла ногу у LDO 3.3v....

    МК - MAX32550, как бы я не искал но SDK под него не нашлось, а судя по этому твиту - затею и вовсе стоит закопать. Поэтому вопрос, действительно ли всё так плохо или надо знать где искать?


    1. MaFrance351 Автор
      00.00.0000 00:00
      +2

      Найти реально, если действительно знать, где искать. Самым результативным (но и самым сложным) вариантом будет познакомиться с тем, кто варится в этой среде и имеет доступ к материалам. Прямо так и написать — разжился девайсом, хочу поиграться. Если железка не слишком актуальная, могут и поделиться.
      Если знакомств таких нет, то искать на просторах Yahoo (он лучше ищет редкую шнягу, нежели гугл), на публичных FTP (searchftps.net), в DHT-сети (en.btdig.com, в России открывать через VPN). Если не нашлось — попробовать снова через месяц-другой.


      Насчёт вашей железки — судя по всему, это бесконтактный считыватель, который управляется по RS-232. И нужно искать не прошивку, а протокол обмена.


    1. MaFrance351 Автор
      00.00.0000 00:00
      +1

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



      О как. Я одно время даже хотел нарыть такой аппарат на "поиграться", но что-то так и не задалось.
      Это MPOS-terminal, который по Bluetooth подключается к мобиле с банковским приложением. Есть некоторые предположения, что свою прошивку в него и изначально нельзя было загружать, а всё управление при помощи команд, отправляемых по Bluetooth (аналогично тому, как управлялся пин-пад PP1000SE), соответственно, SDK у него только под ведроид/яОсь, без возможности писать софт под его родную архитектуру.
      Если есть желание поковырять — надо узнать его истинного производителя и попробовать найти SDK. Но надо будет для начала восстановить плату и сбросить тампер, что муторно.
      Если нет такого желания — выкинуть штатный МК и использовать только корпус с клавиатурой.


      1. kulhaker478
        00.00.0000 00:00

        ..это остатки

        Никак нет, просто разобрал его дабы отмыть и посмотреть что живо. Судя по всему кто-то из курьеров (или кто ими пользуется) потерял его в луже

        Это MPOS-terminal, который по Bluetooth подключается к мобиле с банковским приложением

        Именно

        Есть некоторые предположения, что свою прошивку в него и изначально нельзя было загружать, а всё управление при помощи команд, отправляемых по Bluetooth (аналогично тому, как управлялся пин-пад PP1000SE), соответственно, SDK у него только под ведроид/яОсь, без возможности писать софт под его родную архитектуру

        Так не сильно интересно (да и ведройдом и жабой я совсем не дружу), хотелось бы именно как с независимой железкой поиграться. Ибо там есть и магнитная, NFC и EMV читалки-писалки. NFC например можно попробовать, для тех карт доступа не покупать же flipper zero в конце-то концов) Поэтому:

        ....выкинуть штатный МК и использовать только корпус с клавиатурой

        оставим пожалуй как крайний вариант..

        Видел упоминание этого камня и какой-то RTOS, возможно получится вкорячить что-то кастомное, однако для этого надо решить вопрос с подписью прошивки

        Если есть желание поковырять — надо узнать его истинного производителя и попробовать найти SDK. Но надо будет для начала восстановить плату и сбросить тампер, что муторно.

        Dspread, а модель девайса на задней крышке и собственно вот https://gitlab.com/dspread/android. К слову метод для обновления прошивки там имеется)

        По тамперу отдельный вопрос, т.к. оба источника питания в 0 высажены и терминал возможно даже и не успел прознать что с ним что-то не так, максимум мог выполнить какие-то действия из-за сброса питаня RTC и то не факт..


        1. MaFrance351 Автор
          00.00.0000 00:00
          +1

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


          Насчёт тампера — отказ внутренней батареи во всех уважающих себя девайсах так-то тоже расценивается как тампер. Аппарат при запуске обнаружит CMOS checksum error и поймёт, что что-то тут нечисто. То есть даже если вы запаяете контакты, а затем подкинете батарею, он не оживёт.


  1. alexei_ovsyannikov
    00.00.0000 00:00

    инстерсно было прочесь. А какие основные различия вы для себя заметили между теминалом LEE и Ingenico?


    1. MaFrance351 Автор
      00.00.0000 00:00
      +1

      Ingenico всё же куда большими ресурсами для разработчика обладает. Больше памяти, мощнее процессор, уровень защиты куда выше. И многие операции для разработчика куда легче, например, если тут работа с хранилищем (EEPROM, RAM-disk) недалеко ушла от таковой на микроконтроллерах, то в Ingenico всё достаточно просто, а возможностей для случайной стрельбы по ногам куда меньше.
      В общем, Ingenico превосходит Nurit практически по всем параметрам. Nurit — он сейчас уже чисто поиграться и просто для истории, время их ушло.


      1. Pav900
        00.00.0000 00:00

        Нурит да - уже история. Особенно доставляло, когда делаешь мультиплатформенный код, который должен работать и на инженико и на нурите. Инженико литл ендиан - нурит - биг ендиан. Много крови это попортило при портации приложений.


        1. MaFrance351 Автор
          00.00.0000 00:00

          Вы, случайно, не для QIWI писали? У них как раз были приложения под Nurit, Ingenico, PAX.


          1. Pav900
            00.00.0000 00:00

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


  1. zikher
    00.00.0000 00:00
    +1

    посвятил несколько лет написанию кода под это чудо

    из запомнившегося и доставившего максимальное количество боли: hcarm (по крайней мере тот, который был у нас) не обнуляет глобальные переменные


    1. MaFrance351 Автор
      00.00.0000 00:00

      Да, тоже заметил...


    1. Pav900
      00.00.0000 00:00

      А он разве должен обнулять? Пройдя школу разработки под терминалы - теперь всегда и везде обнуляю переменные принудительно сам сразу после объявления.


      1. zikher
        00.00.0000 00:00

        C99 6.7.8.10 как бы
        ну и до нуритов писали на verifone 395/3750, там компилятор всё правильно делал


        1. Pav900
          00.00.0000 00:00

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


        1. MaFrance351 Автор
          00.00.0000 00:00

          Да, компилятор был другой там (TXO для 395 и SDS 7.4 для 3750). Потом ещё были ARMовские (VX510, VX670, VX810), там был RVCT.


          1. zikher
            00.00.0000 00:00
            +1

            С 3750-м была ещё одна прекрасная особенность: дебаггер был или триальный или с протухшим ключом, и он работал, если на компьютере была выставлена дата только в 1999 год.

            А системой контроля версий у нас на тот момент была MS SourceSafe, которая использовала в качестве истории дату изменения файла.

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

            Приходилось открывать весь проект из архивов версий и заново коммитить, чтобы иметь возможность копаться в истории


      1. MaFrance351 Автор
        00.00.0000 00:00

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


  1. Pav900
    00.00.0000 00:00
    +1

    Вспоминаю разработку под Ingenico - брр. Проприетарные системы - очень не гибкие. Линукс терминалы - наше всё :)


    1. MaFrance351 Автор
      00.00.0000 00:00

      Типа NewPOS NEW8210?


      1. Pav900
        00.00.0000 00:00
        +1

        Да - 8210


        1. MaFrance351 Автор
          00.00.0000 00:00

          Тогда понятно. А под какие Ingenico доводилось писать? У них же три поколения было...


          1. Pav900
            00.00.0000 00:00

            Под телиум 1 и телиум 2. С самыми последними генерациями уже не пришлось сталкиваться, ушел на линукс и перекрестился.


  1. Pav900
    00.00.0000 00:00
    +1

    Почитал ваши статьи про нурит и инженико - ностальгия взяла. Уже давно подзабытые термины и инструментарии. Можете вспомнить еще старые 16-битные платформы от шлюмбердже-аксальто (терминалы Magic 6000 и 6100). Там подписи не нужно и инструментарий вроде ищется.


    1. MaFrance351 Автор
      00.00.0000 00:00

      Интересно. Попробую поискать.
      Но тут ещё проблема в том, чтобы саму железку нарыть.


      А вот с Hypercom теми же было бы интересно поиграться. Но по ним в интернете вообще всё пусто.


      1. Pav900
        00.00.0000 00:00

        Magic 6100.
        Magic 6100.

        Неубиваемые аппараты были. Флеш память использовалась только как ПЗУ для ОС. Всё файловое хранилище - обычная статическая память с подпиткой от батареи. Хватало года на 2-3, затем замена батареи и дальше работаем. Этим они выгодно отличались от Ingenico, у которых за те же 2-3 года порой ресурс флеш диска вырабатывался в ноль и требовалась перепайка в условиях сервис центра.


        1. MaFrance351 Автор
          00.00.0000 00:00

          О как. Но я таких не застал уже, увы.


  1. nataglushakova81
    00.00.0000 00:00
    +1

    Моя молодость вспомнилась :))) самый крутой фич Ingenico?


    1. Pav900
      00.00.0000 00:00

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


      1. MaFrance351 Автор
        00.00.0000 00:00

        У Hypercom Optimum T4220 такой прикол тоже был. Падали в тампер от малейшего удара, был у них заводской дефект такой. При том, что более старой моделью T2100 можно было забивать гвозди и колоть орехи, и ничего ей не бывало.


  1. vladkorotnev
    00.00.0000 00:00
    +1

    Вот это вы напомнили, конечно, о молодости :-)
    Рад, что у вас всё заработало.



    В своё время терминал откопался на радиорынке как нерабочий, после замены батарейки ожидаемо заработал, хоть и упал в тампер. Сделал такую фиговину с "апплетами", чтоб не перезаливать под каждую задачу новый код, да и апплетом serial printer несколько лет напролёт пользовался — там, список радиодеталей напечатать, или ещё какую-то мелочь (шпоры в универ %)


    Однако, как обычно, с "потыкиванием" у меня вышло лучше, чем с документированием натыканного.


    К сожалению год назад, когда волею случая оказался дома, воткнул его в розетку и услышал изнутри неприятное шипение. На поверку оказалось, что блок питания в моё отсутствие ушёл в разгул и вместо 12 вольт стал выдавать 26, что терминалу не очень понравилось :/


    1. MaFrance351 Автор
      00.00.0000 00:00

      О как. Я так понимаю, у вас был именно защищённый вариант. Он без живой батарейки работать не сможет. В отличие от простого, которому на это всё равно.
      Кстати, а не сохранилось того, что вы тогда писали? Было бы интересно запустить.


      1. vladkorotnev
        00.00.0000 00:00

        Сохранилось, но не сказал бы что там что-то сложнее комплектного сэмпл-кода, ну и ессно недописанное и сырое всё %) https://dropmefiles.com/Kpjbg


        1. MaFrance351 Автор
          00.00.0000 00:00

          Ну, не так всё плохо, как можно подумать...


        1. MaFrance351 Автор
          00.00.0000 00:00
          +1

          Ну, оно даже успешно собралось (только пришлось поменять пути в BUILD.BAT и LINKER.CMD) и запустилось:



          Может, на Github выложите, для истории? Неплохой же пример для старта разработки под эту шнягу…


          1. vladkorotnev
            00.00.0000 00:00

            https://github.com/vladkorotnev/lipman-nurit-hello-world


            Закинул, надеюсь не поимею потом проблем из-за этого, а то там ж в проекте половина сдк лежит %)


            1. MaFrance351 Автор
              00.00.0000 00:00

              Та вряд ли. Это уже самое что ни на есть abandonware…
              К слову, на VeriFone DevNet до сих пор валяется софт для Nurit (в разделе "Legacy"). Но скачать его нельзя, увы.


  1. MaFrance351 Автор
    00.00.0000 00:00

    Немного дополню ранее написанное — для того, чтобы писать по-русски, необходимо выполнить SysUtil_SetLanguage(RUSSIAN,YEAR_MONTH_DAY);
    Первый параметр выбирает системный язык, второй — формат даты. Текст для вывода на экран при этом должен быть в кодировке CP866.


  1. vtrn_dnb
    00.00.0000 00:00
    +2

    But... Can it run DOOM?