Общество с ограниченной ответственностью «ОПЛАТ-СЕРВИС»
УТВЕРЖДАЮ
Генеральный директор
ООО «ОПЛАТ-СЕРВИС»
В.Н. Болтиков
Контрольно-кассовая ТЕХНИКА
УМКА-01-ФА
РУКОВОДСТВО ПРОГРАММИСТА
2016
Содержание
-
1. Общие положения
Контрольно-кассовая техника (далее — ККТ) УМКА-01-ФА предназначена для работы в составе автоматических устройств для расчетов (далее – АУР) при осуществлении расчетов на территории Российской Федерации в целях обеспечения интересов граждан и организаций, защиты прав потребителей, а также обеспечения установленного порядка осуществления расчетов, полноты учета выручки в организациях и у индивидуальных предпринимателей. Обмен данными между компьютером платёжного терминала и ПТК ведётся по интерфейсу RS – 232 со скоростью 1200, 2400, 4800, 9600, 14400, 38400, 58600 и 115200 бод и с физическим кадром:
1 стартовый бит;
8 битов данных;
1 стоповый бит;
без проверки на четность;
без управления потоком;
3 линии (TXD, RXD, GND).
Документ состоит из двух основных частей: «Нижний уровень» и «Верхний уровень».
Под командой понимается посылка, состоящая из кода команды и данных (они могут отсутствовать), которые определяют тип действия для ККТ. На каждую команду ККТ присылает ответ, содержащий код ошибки и данные (могут отсутствовать) в зависимости от команды.
Все команды и ответы передаются по единому протоколу нижнего уровня, который обеспечивает достоверность передачи данных с помощью механизма контрольных сумм и повторов. Таким образом, в разделе «Нижний уровень» описан общий для всех команд и ответов алгоритм их передачи и приема.
В разделе «Верхний уровень» описаны конкретные коды команд, передаваемые в них данные и форматы ответов на команды, а также возможные коды ошибок и их описание.
-
2. Нижний уровень
Нижний уровень условно разделен на две части, для реализации каждой из которых используется механизм, реализуемый другой его частью:
транспортная часть отвечает за обмен пакетами по физическому уровню;
буфер заданий осуществляет организацию очереди получаемых заданий.
При описании будем использовать условные обозначения:
"Name" – имя константы (значения констант приведены в подразделе «Константы»).
Name – имя переменной или параметра. При таком формате указания имени параметра учитывается только его наличие и положение относительно других параметров, а не его значение, размерность и т.п.
Name [X] – имя переменной или параметра с указанием его фиксированной длины в байтах. Все параметры двоичные (беззнаковые), если в описании не указано иного. Если в скобках ничего не указано (пустые скобки), то считается, что длина параметра не предопределена (определяется другими параметрами).
Все передаваемые многобайтовые значения передаются по правилу «младший байт первым».
Константы
Транспортный уровень
Обозначение
|
Код
|
STX
|
0xFE
|
ESC
|
0xFD
|
TSTX
|
0xEE
|
TESC
|
0xED
|
Ошибки
Обозначение
|
Код
|
E_Overflow
|
0xB1
|
E_AlreadyExists
|
0xB2
|
E_NotFound
|
0xB3
|
E_IllegalValue
|
0xB4
|
Статус задания
Обозначение
|
Код
|
Pending
|
0xA1
|
InProgress
|
0xA2
|
Result
|
0xA3
|
Error
|
0xA4
|
Stopped
|
0xA5
|
AsyncResult
|
0xA6
|
AsyncError
|
0xA7
|
Waiting
|
0xA8
|
Команды буфера
Обозначение
|
Код
|
Add
|
0xC1
|
Ack
|
0xC2
|
Req
|
0xC3
|
Abort
|
0xC4
|
AckAdd
|
0xC5
|
Транспортная часть
Структура пакета данных при обмене
Обмен в обоих направлениях производится пакетами с единой структурой:
Обозначение
|
Описание
|
Byte stuffing
|
Для того чтобы байт "STX" не встречался в полях Data и CRC, при передаче
байтов этих полей применяется маскирование (Byte stuffing): если очередной
байт не равен "STX" или "ESC", то он передается как есть. Если байт равен
"STX", то вместо него передается пара байтов: "ESC" и "TSTX". Если байт
равен "ESC", то вместо него передается пара байтов: "ESC" и "TESC". Следует
заметить, что байты, равные "TSTX" и "TESC", передаются как есть (без
дополнения лишними байтами).
Получение сочетания "ESC" + XX, где XX не равен "TSTX" или "TESC",
считать нарушением обмена – пакет отбрасывать.
|
Len
|
Количество информационных байтов в поле Data. При передаче производится маскирование, поэтому количество байтов реально передаваемых по физическому каналу может быть больше указанного значения. Чтобы само поле Len не требовало
маскирования нужно учитывать:
‒ допустимые значения 0x0000..0x7E7F,
‒ в младшем байте следует передавать младшие 7! (а не 8) битов значения
длины (бит 7 содержит 0);
‒ а в старшем передавать остальные 8 бит.
unsigned char* Buf;
Buf [1] = (unsigned char) Len & 0x7F;
Buf [2] = Len >> 7;
//младший байт длины
//старший байт длины
|
Id
|
По номеру Id можно определить, ответом на какой из пакетов данных ПК является этот пакет. То есть ПК нумерует отсылаемые пакеты по своему усмотрению, а ККТ в ответных пакетах указывает, что «это ответ на такой-то пакет ПК». Допустимые значения для id, используемых ПК, 0x00..0xDF.
Остальные значения зарезервированы.
|
Data
|
Это информационная часть пакета, которую транспортная часть протокола никак не интерпретирует (это делает вторая часть протокола – буфер заданий, подробнее смотрите в разделе «Буфер заданий»).
|
CRC
|
Контрольная сумма CRC8 по полям Id и Data (с учетом маскирования). Начальное значение: 0xFF (Полином: x 8+x5+x4+1 / 0x31 / “CRC-8-Dallas/Maxim”).
|
Логика работы
1. Первым по каналу передается байт с индексом 0 (то есть "STX").
2. ПК при формировании пакета может назначить любое допустимое значение Id.
Рекомендуется – монотонно нарастающее.
3. ККТ, отвечая на пакет ПК, использует тот же Id.
4. Если CRC не совпадает, то пакет отбрасывается.
5. Если в середине пакета встретилось "STX", то предыдущая полученная часть пакета
игнорируется (отбрасывается) и начинается получение нового пакета.
6. Если Id или Len имеют некорректное значение или ПК прислал неизвестную команду, то
ККТ шлет пакет без информационной части ("STX", 0x0000, Id, CRC) для сигнализации
об ошибке.
7. Транспортный уровень получает пакеты в прерывании, там же их обрабатывает (не путать с «исполняет команды ККТ»).
8. Так как команда верхнего уровня в ККТ может выполняться достаточно длительное время, то во время исполнения одной команды можно передать следующую команду. Реализация транспортного уровня ведется в прерываниях (UART), а исполнение команд верхнего уровня – в основном цикле MCU ККТ. То есть считается, что в ККТ есть два потока: транспортный уровень (прием пакетов) и верхний уровень (поток ККТ).
9. Назовем команды верхнего уровня, доставленные в пакете транспортным уровнем из ПК, заданием.
10. Транспортный уровень имеет команды Добавить задание в буфер, Получить результат исполнения задания, Очистить буфер заданий, которые исполняет в прерывании и сразу же отправляет ответный пакет (синхронно с точки зрения ПК).
11. Полученные задания транспортный уровень складывает в буфер FIFO (в прерывании).
12. Основной поток MCU ККТ, реализующий функции ККТ, берет очередное задание из буфера, исполняет команду ККТ и складывает результат ее исполнения на место задания в буфер.
13. ПК может командой (по транспортному уровню) запросить результат исполнения задания или указать, чтобы ККТ сразу после исполнения задания отправил в ПК (асинхронно с точки зрения ПК) пакет с результатом задания.
Транспортный уровень позволяет настроить «конвейер»:
Диаграммы состояний обмена транспортного уровня
Со стороны ККТ
Со стороны ПК
Буфер заданий
1. В ККТ есть буфер заданий FIFO. Его емкость не регламентируется (зависит от модели
ККТ, динамического распределения памяти ККТ и т.п.).
2. Буфер может быть в одном из состояний:
-
"Complete" – в буфере нет заданий или все задания из буфера исполнены потоком ККТ.
-
"Running" – одно из заданий, находящихся в буфере, исполняется потоком ККТ.
-
"Error" – при исполнении одного из заданий возникла ошибка.
3. Все получаемые транспортным уровнем задания помещаются в буфер.
4. Исполнение заданий из буфера производится потоком ККТ до возникновения первой ошибки. При возникновении ошибки буфер переходит в режим "Error", исполнение заданий прекращается. Из состояния Error в состояние Complete буфер можно вывести только командой полной очистки буфера.
У задания есть признак «игнорировать ошибку». При возникновении ошибки в процессе исполнения такого задания, работа буфера не прекращается (ошибка игнорируется).
5. Задание в буфере может иметь статус:
-
"Pending" – задание помещено в буфер, но до него еще не дошла очередь.
-
"InProgress" – задание исполняется потоком ККТ в данный момент.
-
"Waiting" – задание исполняется в фоновом режиме. ККТ ожидает данные от внешнего устройства.
-
"Result – задание исполнено потоком ККТ без ошибки.
-
"Error" – задание исполнено потоком ККТ с ошибкой.
-
"Stopped"– задание было в состоянии Pending на момент возникновения ошибки при исполнении одного из предшествующих в буфере заданий.
6. При добавлении задания в буфер ПК передает с заданием его однобайтовый идентификатор TId (не путать с Id транспортного уровня). По этому идентификатору ПК различает задания в буфере (при запросе статуса задания и т.п.). Допустимые значения для TId 0x00..0xDF.
7. Если задание еще не исполнилось ККТ (состояния "Pending", "InProgress", "Waiting" или "Stopped"), то в буфере хранится само задание (пакет с командой верхнего уровня).
8. Когда задание исполнилась ККТ (состояния "Result" или "Error"), то в буфере в задании уже вместо пакета с командой верхнего уровня хранится пакет с ответом верхнего уровня.
9. ПК имеет возможность запросить статус любого задания, находящегося в буфере (по идентификатору задания TId). Если задание исполнено ("Result" или "Error"), то вместе со статусом в ПК отсылается результат исполнения задания, сформированный ККТ (ответ верхнего уровня).
10. Каждое задание в буфере имеет флаг NeedResult. После исполнения задания с возведенным этим флагом поток ККТ отсылает в ПК асинхронно пакет с идентификатором задания и его результатом.
11. Каждое задание в буфере имеет флаг IgnoreError. Если этот флаг установлен и задание исполнилось с ошибкой, то буфер на это реагирует так же, как если бы задание исполнилось без ошибки.
12. Каждое задание в буфере имеет флаг WaitAsyncData. Если флаг установлен, то задание может находится в буфере в состоянии Waiting сколь угодно долго. При добавлении нового задания, задание с установленным флагом WaitAsyncData в начале буфера не препятствует добавлению нового задания (подробнее смотрите описание команды Add). В ККТ вместо хранения флага для всех заданий в буфере можно реализовать хранение задания с установленном флагом WaitAsyncData в отдельной переменной – это несколько упрощает реализацию.
13. Если при исполнении задания ККТ возникает ошибка, то ККТ отсылает в ПК асинхронно пакет с идентификатором этого задания TId и его результатом вне зависимости от флага NeedResult.
14. Все команды в первую очередь проверяют корректность значений параметров (команд работы с буфером, но не параметров самого задания). Если значение параметра недопустимое, то в ответ на команду отсылается код ошибки "E_IllegalValue" и индекс параметра, в котором обнаружена ошибка (параметры нумеруются, начиная с 0).
Добавить задание в буфер
Команда добавления нового задания в буфер. Команда имеет битовое поле Флаги – битовая маска:
0-й бит – флаг NeedResult, настройка передачи результата задания (0 – результат не передается, 1 – результат передается);
1-й бит – флаг IgnoreError, настройка работы с ошибками (0 – не игнорировать, 1 – игнорировать);
2-й бит – флаг WaitAsyncData, настройка ожидания выполнения задания (0 – сразу выполнять задание, 1 – ожидать выполнения задания сколь угодно долго, не препятствовать добавлению нового задания).
Биты 3..7 зарезервированы, для совместимости будущими версиями должны содержать 0, в случае если бит3=бит4=..= бит7≠0, то вернется "E_IllegalValue".
Команда
|
|
Ответ
|
Описание
|
"Add", Flags [1], TId [1], Data[]
|
=>
|
|
|
|
<=
|
"Pending"
|
Задание помещено в буфер, но пока не исполняется.
|
<=
|
"InProgress"
|
Задание помещено в буфер и уже исполняется.
|
<=
|
"Waiting"
|
Задание помещено в буфер и уже ожидает данных от внешнего устройства.
|
<=
|
"Result", Data []
|
Задание уже находилось в буфере и ККТ успела его успешно выполнить.
Data – результат, возвращенный ККТ.
|
<=
|
"Error", Data []
|
Задание уже находилось в буфере, и ККТ успела его выполнить, но возникла ошибка.
Data – результат, возвращенный ККТ.
|
<=
|
"Stopped", TIdErr
[1]
|
Задание добавлено в буфер, но буфер находится в состоянии "Error" из-за ошибки, возникшей при исполнении ККТ задания TIdErr.
|
<=
|
"E_IllegalValue",
PIdx [1]
|
Задание не может быть помещено в буфер – недопустимое значение параметра. Pidx= 0 (для Flags), PIdx = 1 (для TId).
|
<=
|
"E_AlreadyExists"
|
Задание не может быть помещено в буфер – в буфере уже есть задание с таким Tid (и это не может быть повтором).
|
<=
|
"E_Overflow",
TId2 [1]
|
Задание не может быть помещено в буфер – не достаточно места в буфере, так как не удается удалить задание с TId2 (можно затереть только удачно завершенное задание с не установленным NeedResult).
|
Логика работы
1. Задание добавляется в конец буфера.
2. Если нет свободного места, то затираются уже исполненные задания со сброшенным флагом NeedResult (для ПК результат не важен).
3. Затирать исполненные задания с установленным флагом NeedResult нельзя (для ПК важен результат).
4. Задание, при исполнении которого произошла ошибка, если у него установлен флаг IgnoreError (игнорировать ошибку), затирать можно. В случае если флаг не установлен, затирать нельзя (ПК может понадобиться код возникшей ошибки). Правило «Если NeedResult установлен, то затирать нельзя» тут тоже действует (см. пункт 2).
5. Затирать исполняющееся задание нельзя (возможно, что в процессе его исполнения возникнет ошибка, а TId стерт).
6. Если в начале буфера находится задание в состоянии Waiting, то оно перемещается в конец буфера. Поскольку буфер кольцевой, это означает только изменение указателя (или указателей). В случае хранения задания с установленным флагом WaitAsyncData в отдельной переменной, данный механизм не требуется.
7. Понятия "Complete" и "Running" очень похожи. С точки зрения WinAPI "Complete" эквивалентно WaitForSingleObject в «живом» потоке.
Механизм переповторов базируется на том факте, что при нарушении обмена в момент добавления задания i ПК не начинает добавлять задание i+1 до тех пор, пока он не завершит работу с результатом добавления задания i. Дополнительно существует возможность запросить состояние задания по TId
(если у задания «есть» статус, то это задание уже добавилось в буфер). Но при этом нужно учитывать то, что если будет потерян ответ E_AlreadyExists, то запрос статуса с этим TId будет неверен. Рекомендуется уделять особое внимание назначению TId и «неполучению» ответа.
Нужно учитывать, что добавление задания в буфер, находящийся в состоянии "Error", не имеет практического смысла (это задание никогда не будет исполнено), но служит для информирования ПК о возникновении ошибки при исполнении другого задания с указанием, какого именно.
Получить состояние задания
Запрашивает статус задания и, если есть, результат его исполнения ККТ.
Команда
|
|
Ответ
|
Описание
|
"Req", TId [1]
|
=>
|
|
|
|
<=
|
"Pending"
|
Задание еще не начали исполнять.
|
<=
|
"InProgress"
|
Задание в данный момент исполняется.
|
<=
|
"Waiting"
|
Задание ожидает данных от внешнего устройства.
|
<=
|
"Result", Data []
|
Задание исполнено успешно.
Data – результат, возвращенный ККТ.
|
<=
|
"Error", Data []
|
Задание исполнено с ошибкой.
Data – результат, возвращенный ККТ.
|
<=
|
"Stopped", TIdErr
[1]
|
Задание отменено из-за ошибки, возникшей при исполнении задания TIdErr.
|
<=
|
"E_IllegalValue",
PIdx [1]
|
Недопустимое значение TId (PIdx = 0).
|
<=
|
"E_NotFound"
|
Задания с таким TId нет в буфере.
|
Если задание i исполнено (статус "Result" или "Error") и у него установлен флаг NeedResult, то флаг NeedResult у задания i сбрасывается. Это позволит при следующих добавлениях заданий в буфер затереть это задание. При этом нужно учитывать ограничение: не получив (потеряв) ответ на команду "Req", нельзя подать команду "Add", а потом опять пытаться получить "Req" этого задания – задание может быть уже стерто командой "Add".
Подтвердить получение результата
Этой командой ПК информирует ККТ о том, что ККТ может более не запрашивать данные о сохранности результатов текущего задания и всех ему предшествующих в буфере, кроме запросов с установленным флагом WaitAsyncData (нужно снять флаг NeedResult для текущего задания и всех ему предшествующих в буфере, кроме запросов с установленным флагом WaitAsyncData).
Команда
|
|
Ответ
|
Описание
|
"Ack", TId [1]
|
=>
|
|
|
|
<=
|
"Pending"
|
Задание еще не начали исполнять – команда не выполнена.
|
<=
|
"InProgress"
|
Задание в данный момент исполняется –
команда не выполнена.
|
<=
|
"Waiting"
|
ККТ сняла флаг для задания и прекращает ждать данные от внешнего устройства. Состояние задание меняется на Result или Error на усмотрение прошивки.
|
<=
|
"Result"
|
ККТ сняла флаг для успешно исполненного задания.
|
<=
|
"Error"
|
ККТ сняла флаг для исполненного с ошибкой задания.
|
<=
|
"Stopped"
|
Команда не выполнена, так как задание отменено из-за ошибки, возникшей при исполнении другого задания.
|
<=
|
"E_IllegalValue",
PIdx [1]
|
Недопустимое значение TId (PIdx = 0).
|
<=
|
"E_NotFound"
|
Задания с таким TId нет в буфере.
|
Если указанное задание не исполнено (состояние "Pending", "InProgress", "Stopped"), то флаг не сбрасывается ни у одного из заданий, даже если предыдущие уже исполнены. Так же флаг не сбрасывается, если указан неверный или несуществующий в буфере Tid. Команда, фактически возвращает текущий статус указанного задания, но не возвращает данные (при "Result" и "Error") или TId (при "Stopped"), как это делают "Add" и "Req".
Очистить буфер
Команда не просто чистит буфер, но и пытается прервать текущее исполняющееся задание. Команда удаляет все задания, вне зависимости от того, успешно оно или нет и установлен ли флаг NeedResult.
Команда
|
|
Ответ
|
Описание
|
"Abort"
|
=>
|
|
|
|
<=
|
"Result"
|
Все задания удалены и прерваны.
|
<=
|
"InProgress", TId [1]
|
Все задания удалены, кроме того TId, которое исполняется и не может быть прервано.
|
При текущей реализации верхнего уровня можно говорить, что "Abort" стирает из буфера все задания, имеющие любой статус, кроме "InProgress" (то есть будут стерты все выполненные и ожидающие исполнения задания, но останется исполняемое в данный момент).
Асинхронный ответ
Пакет посылается на транспортном уровне с Id = 0xF0.
Команда
|
|
Ответ
|
Описание
|
Отсутствует
|
|
|
|
|
<=
|
"AsyncResult", TId [1], Data []
|
Задание TId исполнено успешно.
Data – результат, возвращенный ККТ.
|
<=
|
"AsyncError", TId [1], Data []
|
Задание TId исполнено с ошибкой.
Data – результат, возвращенный ККТ.
|
ККТ не ждет никаких пакетов от ПК в ответ на этот пакет. То есть, если ПК не получил этот пакет в течение времени ожидания, то ПК должен сам запросить статус интересующих заданий командой "Req".
Добавить задание в буфер с одновременным подтверждением
Это комбинация двух команд: "Ack" и "Add". То есть одним пакетом ПК подтверждает ККТ получение результатов выполнение предыдущих команд (чтобы ККТ больше не запрашивала результат) и добавляет новое задание в буфер.
Команда
|
|
Ответ
|
Описание
|
"AckAdd", TIdAck [1], FlagsTIdAdd [1], Data []
|
=>
|
|
|
|
<=
|
"E_IllegalValue",PIdx [1]
|
Ack и Add не выполнены –недопустимое значение параметра. PIdx = 0 (для TIdAck), PIdx = 1 (для Flags), PIdx = 2 (для TIdAdd).
|
<=
|
"E_NotFound"
|
Ack не выполнен (задания с таким TIdAck нет в буфере). Добавление тоже не проводилось.
|
<=
|
"Pending"
|
Ack не выполнился, задание не помещено в буфер.
|
<=
|
"InProgress"
|
Ack не выполнился, задание не помещено в буфер.
|
<=
|
"Stopped"
|
Ack не выполнился, задание не помещено в буфер.
|
<=
|
StateAck [1],
"Pending"
|
StateAck = {"Result" | "Error"}. Ack выполнен удачно, задание помещено в буфер, но пока не исполняется.
|
<=
|
StateAck [1],
"InProgress"
|
StateAck = {"Result" | "Error"}. Ack выполнен удачно, задание помещено в буфер и уже исполняется.
|
<=
|
StateAck [1],
"Waiting"
|
StateAck = {"Result" | "Error"}. Ack выполнен удачно, задание помещено в буфер и ожидает данные от внешнего устройства.
|
<=
|
StateAck [1],
"Result", Data []
|
StateAck = {"Result" | "Error"}. Ack выполнен удачно, задание уже находилось в буфере и ККТ успела
его успешно выполнить. Data – результат, возвращенный ККТ.
|
<=
|
StateAck [1],
"Error", Data []
|
StateAck = {"Result" | "Error"}. Ack выполнен удачно, задание уже находилось в буфере и ККТ успела его выполнить, но возникла ошибка.
Data – результат, возвращенный ККТ.
|
<=
|
StateAck [1],
"Stopped", TIdErr [1]
|
StateAck = {"Result" | "Error"}. Ack выполнен удачно, задание добавлено в буфер, но буфер находится в состоянии "Error" из-за ошибки, возникшей при исполнении ККТ задания TIdErr.
|
<=
|
StateAck [1],
"E_AlreadyExists"
|
StateAck = {"Result" | "Error"}. Ack выполнен удачно, задание не может быть помещено в буфер – в
буфере уже есть задание с таким TId (и это не может быть повтором).
|
<=
|
StateAck [1],
"E_Overflow",
TId2 [1]
|
StateAck = {"Result" | "Error"}. Ack выполнен удачно, задание не может быть помещено в буфер –
недостаточно места в буфере, так как не удается удалить задание с TId2 (можно затереть только
удачно завершенное задание со снятым флагом NeedResult).
|
Ответ команды – комбинация ответов команд Ack и Add. Если ошибка возникла при выполнении Ack, то Add даже не начинает исполняться и ее ответ отсутствует, если же Ack выполнен удачно, то его ответ всегда передается вместе с результатом исполнения Add (пусть это будет даже ошибка).
Команда удобна при получении асинхронных данных от внешнего устройства (установлен флаг WaitAsyncData). То есть ПК одной командой подтверждает получение данных и сообщает ККТ, что она должна продолжать ждать.
Типичные сценарии
Успешное выполнение команды
При выполнении команды возникла ошибка
Переполнение очереди
Утерян пакет с данными об успешном исполнении команды
|