 | Уровень сложности: средний Карл Фреберджер, IT-разработчик,
IBM
Аллен Смит, старший разработчик программного обеспечения,
IBM
01.06.2007 Во второй части данной серии статей вы узнаете, как приступить к реализации адаптера при помощи компонента IBM® WebSphere® RFID Device Kit системы WebSphere RFID Device Infrastructure, начиная с транспортного уровня и используя считыватель Sirit™ INfinity 510 в качестве примера.
Введение
В данной статье продолжается рассмотрение процесса создания адаптеров и агентов для интегрирования RFID-считывателей в RFID-решение IBM Websphere. В первой части описывалось RFID-решение IBM WebSphere и WebSphere RFID Device Infrastructure. Во второй части вы узнаете, как приступить к реализации адаптера, начиная с нижнего уровня Device Kit (транспортного) и используя считыватель Sirit INfinity 510 в качестве примера.
Транспортный уровень в Device Kit
Как говорилось в первой части данной серии статей, при реализации адаптера для считывателя мы используем компонент Device Kit системы WebSphere RFID Device Infrastructure. Device Kit состоит из нескольких уровней, как показано на рисунке 1. Мы начнем реализацию адаптера с создания транспортного уровня.
Рисунок 1. Адаптер Device Kit
В Device Kit транспортный уровень отвечает за преобразование байтов в более осмысленные объекты, называемые сообщениями. Байты, принимаемые от устройства (считывателя), преобразуются в сообщения и передаются прослушивателям. Сообщения, передаваемые приложением в транспортный уровень, преобразуются в соответствующие байты и передаются в устройство.
Транспортный уровень отвечает также за установку соединения с аппаратным устройством, используя соответствующий класс connection, а также за управление всем начальным диалогом с устройством. В начальный диалог может входить установка параметров считывателя, регистрация в считывателе, установка соединений с альтернативными каналами сообщений и т.д.
Транспортный уровень отвечает за передачу тактовых сообщений для поддержки соединения с аппаратным устройством в активном состоянии.
Транспортный уровень не понимает сообщений, которые принимает и передает; он занимается синтаксисом, а не семантикой. Он понимает протокол связи с аппаратным устройством, чего достаточно для формирования сообщений из входящих байтов, проверки контрольных сумм исходящих байтов и т.п. Единственными сообщениями, о которых должен знать транспортный уровень, являются сообщения, используемые для начального диалога, а также тактовые сообщения.
При интегрировании нового устройства в WebSphere RFID Device Infrastructure вы будете создавать начальное определение транспортного уровня при помощи мастера Device Kit Transport Creation и изменять определенные атрибуты транспортного уровня путем редактирования CML-файла (Command Markup Language). Также вы должны будете написать Java™-код для реализации специфических особенностей транспортного уровня для данного устройства. Реализация транспортного уровня часто является наиболее трудоемкой задачей при создании адаптера с новым считывателем.
Транспортные уровни и смежные объекты
Транспортный уровень при выполнении своей работы взаимодействует с несколькими другими объектами. На рисунке 2 показаны основные объекты.
- Один транспортный уровень (экземпляр подтипа
com.ibm.esc.transport.service.TransportService).
- Один уровень соединения (экземпляр подтипа
com.ibm.esc.connection.service.ConnectionService), управляющий взаимодействием с RFID-считывателем.
- Один контроллер (экземпляр
com.ibm.esc.transport.Controller), занимающийся перенаправлением сообщений, обработкой ошибок и изменений состояния прослушивателей транспортного уровня, перезагрузкой транспортного уровня при возникновении ошибки и уведомлениями о передаче тактовых сообщений.
- Ноль или более приложений, являющихся источниками байтов для записи в устройство. Приложения могут быть любого типа.
- Ноль или более прослушивателей, являющихся приемниками сообщений, сформированных на транспортном уровне. Прослушиватели являются экземплярами подтипа
com.ibm.esc.transport.service.TransportListener. Обычно приложение и прослушиватель являются одним и тем же объектом (экземпляром подкласса com.ibm.esc.device.TransportDevice).
Рисунок 2. Смежные объекты транспортного уровня
Для демонстрации взаимодействия между транспортным уровнем и остальными объектами давайте рассмотрим, что происходит, когда приложение запрашивает транспортный уровень передать сообщение в устройство:
- Приложение активизирует метод
send(Messageservice message) транспортного уровня.
- Транспортный уровень форматирует сообщение и активизирует метод
write(byte[] bytes, int offset, int count) уровня соединения.
- Уровень соединения записывает байты в устройство.
Когда транспортный уровень принимает сообщение от устройства, все сильно усложняется (см. рисунок 3):
- Транспортный уровень запускает свой
Thread, выполняя processInput().
-
processInput() активизирует read(byte[], int, int) на уровне соединения (шаг 1 на рисунке).
- Уровень соединения ожидает байты, приходящие от считывателя (шаг 2).
- При поступлении байтов транспортный уровень активизирует
processInput(byte[], int) (шаг 3).
-
processInput(byte[], int) организовывает байты в сообщения. После формирования сообщения он активизирует fireMessageReceived(MessageService, Object) на транспортном уровне (шаг 4).
-
fireMessageReceived(MessageService, Object) уведомляет контроллер о доступности сообщения, активизируя messageReceived(TransportService, Object, MessageService) (шаг 5).
- Контролер проверяет поступившее сообщение и определяет, заинтересован ли в нем транспортный уровень (используя маску interest, определяемую транспортным уровнем).
- Если сообщение не интересно, оно отбрасывается. В противном случае контроллер ставит принятое сообщение в очередь.
- Отдельный
Thread в контроллере выбирает сообщение для обработки. В результате этого происходит разъединение производителей и потребителей сообщений, и устраняются проблемы, связанные с блокировкой потока.
- Сообщение доставляется прослушивателям, зарегистрированным на транспортном уровне, путем активизации
messageReceived(TransportService, Object, MessageService) на них (шаг 6).
К счастью, в нашем случае контроллер уже реализован, и вам не нужно беспокоиться о работе с масками interest, очередью сообщений и потоками.
Рисунок 3. Прием сообщения на транспортном уровне
Классы и интерфейсы транспортного уровня
Все транспортные уровни являются экземплярами подтипа com.ibm.esc.transport.service.TransportService. Фактически, они являются экземплярами подклассов com.ibm.esc.transport.Transport, наследуя уже реализованное поведение. Имеется несколько подклассов com.ibm.esc.transport.Transport; наиболее часто используемым для RFID-считывателей является com.ibm.esc.transport.ResponseTransport, поскольку большинство RFID-считывателей реализуют модель взаимодействия команда/ответ. ResponseTransport блокирует операции записи до прихода ответа на предыдущую операцию записи, либо до истечения времени ожидания ответа. Сообщения, принимаемые со считывателя, подразделяются на ответы на ранее переданное сообщение и на неответы (non-responses). Примером неответа может быть асинхронное сообщение о событии.
ResponseTransport определяет несколько полезных методов, и некоторые из них вы должны реализовать:
-
void
write(MessageService message) отвечает за запись байтов в уровень соединения. Он должен выполнить "предобработку" сообщения для вычисления контрольных сумм, длин и т.д., что необходимо по протоколу. Сообщения должны иметь корректное (окончательное) число байтов для записи. Если протокол не требует изменения сообщений перед записью, вы можете оставить реализацию этого метода по умолчанию; в противном случае вы должны реализовать это.
-
int processInput(byte[]
buffer, int length) реализует разбор байтов в экземпляры подтипа com.ibm.esc.message.service.MessageService. Вы должны заново реализовывать этот метод для каждого типа транспортного уровня, поскольку в нем выполняется синтаксический анализ. Метод должен преобразовывать определенное количество байтов из буфера в сообщения и возвращать количество обработанных байтов (возможно 0, если буфер не содержит полное сообщение). Перед возвратом из метода должно обработаться как можно большее число сообщений.
-
MessageService
noActivityProcessingMessage() возвращает сообщение, которое будет периодически передаваться в устройство в качестве тактового при отсутствии какой-либо другой активности. Мы рассмотрим тактовые сообщения позже. Каждый транспортный уровень должен реализовывать этот метод при использовании тактовых сообщений.
-
int
startup(boolean arg) реализует все необходимые специфические начальные действия (например, команду входа в систему (login) или запуск обработчика асинхронных событий). Для конкретного транспортного уровня, возможно, придется реализовывать этот метод заново.
-
void
startupMessageReceived(TransportService transport, Object timestamp,
MessageService message) реализует обработку сообщений во время начального диалога. Если транспортный уровень реализовывает начальный диалог, вы должны реализовать этот метод. Мы рассмотрим начальный диалог позже.
-
void
fireMessageReceived(Object timestamp, MessageService message) активизируется транспортным уровнем при формировании сообщения из входящих байтов.
-
void
fireMessageReceivedNoResponse(Object timestamp, MessageService message) активизируется транспортным уровнем, сигнализируя, что сформированное сообщение не является ответом на предыдущее исходящее сообщение.
-
MessageService
getSentMessage() возвращает последнее переданное сообщение. Это полезно при создании сообщений для подключения ответа к сообщению, сгенерировавшему его.
-
void
responseReceived() активизируется транспортным уровнем для указания того, что принят ответ на предыдущее исходящее сообщение. Обычно вам не нужно активизировать этот метод, но при наличии ошибки в синтаксическом анализе он может понадобиться.
-
void
handleError(Throwable error, int resourceId, Object[] objects) и его вариации используются для уведомления прослушивателей транспортного уровня о возникновении ошибки. Аргумент int является идентификатором (ID) ресурса; удобнее всего использовать ID, уже определенные в одном из классов Transport.
-
Object
EscObject.getCurrentTimestamp() возвращает текущее время как Object; он полезен при активизации методов, для которых необходима метка времени (timestamp).
Некоторые методы генерируются автоматически из CML-файла, определяющего транспортный уровень:
-
ConnectionService
getDefaultConnection() возвращает экземпляр ConnectionService для подключения к считывателю.
-
long getDefaultResponseTimeout() возвращает таймаут ответа по умолчанию.
-
void setup() инициализирует свойства транспортного уровня и уровня соединения. Мы рассмотрим конфигурационные свойства ниже.
-
getX() - методы для всех пользовательских параметров, определенных вами в CML. Мы рассмотрим пользовательские параметры ниже.
Как можно понять из списка, при создании нового транспортного уровня необходимо реализовать processInput(byte[], int). Возможно, понадобиться реализовать write(MessageService), noActivityProcessingMessage(), startup(boolean) и startupMessageReceived(TransportService, Object, MessageService).
Метод processInput(byte[], int) является самым трудным для реализации. Device Kit не предоставляет какой-либо помощи по синтаксическому разбору, поэтому вы должны написать весь код самостоятельно. Мы предлагаем посмотреть для примера на существующие адаптеры считывателей с протоколом, аналогичным протоколу вашего считывателя. При рассмотрении реализации транспортного уровня для считывателя Sirit INfinity 510 мы представим некоторые вспомогательные классы, которые вы можете использовать для уменьшения количества работы по реализации синтаксического анализа основанных на текстовых сообщениях протоколов.
Метод processInput(byte[], int) должен анализировать байты из буфера (от начала до конца) и "потреблять" как можно большее количество байтов при каждом вызове. Потребление байтов - это их анализ и разбор из буфера (parsing) для формирования объектов message и активизация метода fireMessageReceived(Object, MessageService) или fireMessageReceivedNoResponse(Object, MessageService). Если протокол вашего считывателя использует такое "декорирование", как контрольные суммы или маркеры начала/конца сообщения, метод processInput(byte[], int) должен проверять их корректность и активизировать handleError(Throwable, int, Object[]) при отрицательном результате этой проверки.
Метод write(MessageService) принимает сообщения, имеющие корректную длину в байтах, для записи в устройство и перед записью изменяет байты, связанные с "декорациями" протокола. Это гарантирует, например, что для сообщения контрольные суммы всегда будут верны.
Если на транспортном уровне определенный период времени отсутствует какая-либо активность (прием или запись сообщения), его контроллеру указывается команда передать в устройство тактовое сообщение. Это позволяет транспортному уровню обнаруживать разрыв соединения с устройством. Побочным эффектом является работа в качестве сообщения "не отключаться" ("keep alive") для тех соединений или устройств, которые требуют периодического взаимодействия. Таймаут отсутствия активности является параметром конфигурации (noactivitytimeout) и по умолчанию равен 10 секундам. Сообщение, передаваемое в качестве тактового, определяется в методе noActivityProcessingMessage(). При выборе тактового сообщения желательно выбирать такое сообщение, которое не будет "причинять вред". Например, если ваш считыватель имеет команду для запуска операции чтения RFID-тегов и команду для остановки чтения, должна быть возможность передать тактовое сообщение между этими командами. Тактовое сообщение не должно вызывать прекращение операции чтения тега. Если метод noActivityProcessingMessage() возвращает null, тактовое сообщение не будет передаваться.
Одной из обязанностей транспортного уровня является управление всем начальным диалогом, необходимым для считывателя. Если для работы с устройством нужен начальный диалог, вы должны реализовать метод startupMessageReceived(TransportService, Object, MessageService). Он будет активизироваться всегда, когда транспортный уровень формирует сообщение и обнаруживает, что он вовлечен в начальный диалог. Метод обычно сравнивает принятое сообщение с одним или несколькими шаблонными сообщениями и, если они совпадают, передает другое сообщение или прекращает начальный диалог и начинает нормальную работу. Если транспортный уровень должен передать сообщение для начала диалога, вы должны переопределить метод startup(boolean) для передачи этого сообщения.
Цикл жизни транспортного уровня
В течение своего жизненного цикла транспортный уровень проходит ряд состояний. Большая часть переходов в другое состояние осуществляется автоматически, но иногда управлять состоянием, возможно, придется вам. Все состояния определены как статические константы в TransportService:
- Состояние
CREATED возникает, когда транспортный уровень был создан или остановлен. Транспортный уровень не будет пытаться перейти в другое состояние до тех пор, пока не будет получен начальный запрос.
- Состояние
ALIVE возникает при указании начала работы транспортного уровня.
- Состояние
CONNECTED возникает, когда транспортный уровень подключается к аппаратному устройству.
- Состояние
ACTIVE возникает, когда транспортный уровень пытается начать, но еще не начал работать. Обычно это означает, что выполняется начальный диалог с устройством.
- Состояние
STARTED возникает, когда транспортный уровень начал работу и должен обрабатывать сообщения.
- Состояние
DEAD возникает, когда транспортный уровень завершил работу и не может быть перезапущен.
Реализация Transport.startup(boolean) по умолчанию переводит транспортный уровень в состояние STARTED. Если вам нужно реализовать начальный диалог в транспортном уровне, startup(boolean) переводит транспортный уровень в состояние ACTIVE для указания выполнения начального диалога. После завершения начального диалога транспортный уровень должен быть переведен в состояние STARTED при помощи Transport.setState(int), возможно, в методе startUpMessageReceived(TransportService, Object, MessageService).
CML-файл транспортного уровня
При создании нового транспортного уровня с использованием мастера Device Kit Transport Creation создается проект транспортного уровня, содержащий файл NameTransport.cml, где Name наследуется из имени вашего транспортного уровня. CML-файл содержит определение различных параметров. Device Kit позволяет повторно генерировать скелет определения вашего транспортного уровня (без потери какого-либо кода, который вы, возможно, добавили) на основе содержимого CML-файла.
Некоторыми из типичных субэлементов элемента <transport> являются:
-
<responsetimeout> определяет таймаут ответа. Если этот элемент присутствует, инструментальные средства Device Kit делают транспортный уровень подклассом ResponseTransport.
-
<noactivitytimeout> определяет время не активности перед передачей тактового сообщения.
-
<tcpip>
или <serial> определяет конфигурацию соединения транспортного уровня по умолчанию.
-
Элементы
<message> определяют сообщения, используемые транспортным уровнем. Определение сообщений в CML описано в первой части данной серии статей.
-
<customparameter> определяет пользовательское конфигурационное свойство для транспортного уровня.
Полное описание CML-файла для транспортного уровня приведено в документации по Websphere Studio Device Developer (при установке RFID Tracking Kit).
Настройка транспортного уровня
Транспортный уровень имеет различные конфигурационные свойства. Некоторые из них являются стандартными свойствами, определенными подклассами Transport (например, таймаут ответа). Некоторые из них являются пользовательскими свойствами, определенными в CML для конкретного транспортного уровня. В каждом случае свойство имеет значение по умолчанию. Новые значения свойствам могут быть присвоены путем:
- Установки свойства в Java-коде через метод
setX(), определенный для свойства.
- Установки свойства в Java-коде через метод
putConfigurationInformation(String, Object).
- Установки свойства в файле esc.properties.
Конфигурационные свойства - это свойства с названием name.propertyname, где name наследуется из имени класса транспортного уровня, а propertyname - это имя свойства. Например, sirittransport.readtimeout - это имя свойства, использующееся в файле esc.properties и в методе putConfigurationInformation(String, Object) для примера транспортного уровня, который мы реализуем позже в данной статье. Это же свойство может быть установлено при помощи метода setResponseTimeout(long) и извлечено при помощи метода getResponseTimeout() (обратите внимание на различие в имени свойства).
Для установки свойств в файле esc.properties file измените файл, используя комментарии в качестве модели (комментарии включают все конфигурационные свойства, которые могут быть установлены, и их значения по умолчанию). Поместите файл esc.properties в текущий каталог Java-программы, реализующей транспортный уровень.
Транспортный уровень и сообщения
Главной функцией транспортного уровня является преобразование сообщений в байты и наоборот. Транспортный уровень анализирует байты и создает из них сообщения, а также принимает сообщения и создает из них байты.
Обычно транспортный уровень не должен знать о семантике сообщений. Однако он должен знать о тактовых сообщениях и сообщениях, используемых для начального диалога. Все сообщения, о которых непосредственно знает транспортный уровень, должны быть определены в его CML-файле. Сообщения могут быть либо точными, либо шаблонными (обсуждение сообщений приведено в первой части данной серии статей). Если сообщения для начального диалога формируются "на лету" (например, вывод пароля, при котором символы * замещают символы пароля), такие сообщения не нужно определять в CML.
Когда транспортный уровень анализирует входящие байты для формирования сообщений, он может создать экземпляр любого подтипа MessageService. Доступно несколько подтипов, и выбор правильного подтипа может упростить дальнейшую работу. Если протокол основан на текстовых сообщениях, подумайте о создании подкласса com.ibm.esc.message.AsciiMessage (для ясности). Если протокол использует \r\n в качестве завершающих сообщение символов, используйте подкласс com.ibm.esc.message.AsciiCrlfMessage. Этот класс исключает \r\n из части данных сообщения. Если транспортный уровень является подклассом ResponseTransport, подумайте об использовании подтипа com.ibm.esc.message.service.ResponseMessageService и установите переданное сообщение, если возможно (используйте метод getSentMessage()). Это позволит связать ответное сообщение с сообщением, сгенерировавшим его, упрощая дальнейшее моделирование устройства.
Сообщения, созданные транспортным уровнем, далее используются уровнем устройства Device Kit для взаимодействия и интерпретации результатов, полученных от считывателя (обзор приведен в первой части данной серии статей). Для облегчения моделирования устройства может оказаться полезным реализовать доступ к полям сообщения. Например, если сообщение определяет пары ключ/значение, полезно иметь доступ к значениям по ключу. Для этого метод get(Object) должен найти значение, соответствующее полю. В некоторых случаях полезно использование "виртуальных" ключей. Например, если протокол считывателя определяет ответы на команды "получить атрибут Х" как "OK значение
", может оказаться полезным определить ключ "0" для сообщения до (но не включая) символа пробел, а ключ "1" для всего, что идет после символа пробел до конца данных. Ни один из классов сообщения, предоставляемых Device Kit, не реализует доступ к полям.
Мы рекомендуем определять пользовательский класс сообщений для транспортного уровня и всегда создавать его экземпляры. Класс пользовательских сообщений должен быть подклассом соответствующего класса сообщений (AsciiMessage, AsciiResponseMessage и т.д.) и:
- Переопределить
getDataLength() для возврата количества бит, определяющих фрагмент данных сообщения (обычно, исключая символы завершения сообщения и концевые символы, например, контрольные суммы). Только биты конца сообщения могут быть исключены.
- Реализовать доступ к полям сообщения, если это возможно. Переопределить
get(Object) для синтаксического анализа полей сообщения с целью поиска значения, соответствующего аргументу key.
Транспортный уровень и соединения
Транспортный уровень использует соединения Device в качестве коммуникационного канала к аппаратному устройству. Device Kit предоставляет несколько типов соединений. Наиболее связанными с RFID-считывателями являются:
-
com.ibm.esc.tcpip.connection.TcpipConnection для клиентских соединений по TCP/IP.
-
com.ibm.esc.serial.connection.SerialConnection для соединений по последовательному порту (RS232).
-
com.ibm.esc.tcpip.server.connection.TcpipServerConnection для ситуаций, когда устройство инициирует соединение с транспортным уровнем.
Реализовывать новый тип соединения вы будете крайне редко.
Кроме обычного соединения со считывателем по типу команда/ответ, некоторые считыватели поддерживают альтернативный канал сообщений, по которому могут доставляться некоторые сообщения (например, события чтения тега). Альтернативный канал может быть инициирован транспортным уровнем или устройством. В первом случае транспортный уровень создает еще одно соединение со считывателем (обычно, используя TcpipConnection), а во втором случае в устройстве настраивается хост и порт для подключения. В этом случае транспортный уровень прослушивает соединение от устройства (возможно, используя TcpipServerConnection).
Чтобы использовать альтернативный канал сообщений, в транспортном уровне нужно сделать несколько изменений:
- Транспортный уровень должен начинать соединение в нужное время (обычно во время начального диалога).
- Транспортный уровень должен выполнять синтаксический анализ сообщений, поступающих по альтернативному каналу, и обрабатывать их соответствующим образом (например, они почти всегда не будут ответными сообщениями).
- Транспортный уровень должен закрывать альтернативный канал в нужное время.
- Транспортный уровень должен быть готов справиться с преждевременным закрытием альтернативного канала.
Использование альтернативного канала сообщений требует определения пользовательских конфигурационных параметров транспортного уровня.
Тестирование транспортного уровня
При создании нового транспортного уровня с использованием мастера Device Kit Transport Creation предоставляется возможность создать тестовый проект. Если вы выберете эту возможность, то легко сможете создать контрольные примеры для вашего транспортного уровня.
В отличие от разработки транспортного уровня, создание контрольных примеров не требует написания какого-либо Java-кода; контрольный пример задается при помощи CML. После редактирования CML-файла тестового проекта транспортного уровня сгенерируйте повторно тестовое приложение с использованием инструментальных средств Device Kit, щелкнув правой кнопкой мыши на тестовом CML-файле и выбрав Device Kit => Generate. Затем выполните тестовое приложение, выбрав тестовый проект транспортного уровня Run => Run As => Java
Application.
Тесты транспортного уровня состоят из передачи сообщений в устройство. Если включена трассировка, вы должны увидеть достаточно информации в log-файлах (переданные и принятые сообщения) для определения успешности теста. Автоматическое сравнение ожидаемых и реальных результатов отсутствует.
Для создания контрольного примера в CML необходимо изменить CML-файл тестового проекта и вставить один или несколько элементов <send> после элемента <description>
<transporttest>. Каждый элемент <send> группирует набор элементов <message>, определяющих сообщения для передачи в качестве тестовых. В листинге 1 приведен простой пример.
Листинг 1. Пример тестового CML-файла транспортного уровня
<send id="LoginAndStatus">
<message id="ReaderWhoAmI">
<ascii>reader.who_am_i()\r\n</ascii>
</message>
<message idref="ReaderWhoAmI">
</message>
<message>
<ascii>reader.check_status()\r\n</ascii>
</message>
</send>
|
Вы можете изменить файл esc.properties в тестовом проекте для установки свойств и управления тестом. Самыми распространенными свойствами для переопределения являются свойства host (считыватель для подключения) и testcount (количество выполняемых тестов, обычно 1). Свойство totaltesttime управляет максимальным временем выполнения теста; если время истечет, тест завершается независимо от того, выполнился он или нет. Установите свойство esc.tracelevel в значение 10 для получения самой подробной информации при трассировке вашего теста.
Создание транспортного уровня для считывателя Sirit INfinity 510
В этом примере будут приведены инструкции по созданию и тестированию транспортного уровня для RFID-считывателя Sirit INfinity 510, включая инструкции по разработке всех необходимых фрагментов.
Предварительные требования
Для выполнения данного упражнения вам понадобятся:
- WebSphere Studio Device Developer Version 5.1.7, установленный с WebSphere RFID Tracking Kit 1.1.1.
- Считыватель Sirit INfinity 510.
-
Справочное руководство по протоколу INfinity 510.
Полный пример и некоторые вспомогательные классы можно загрузить по ссылкам, приведенным в разделе "Загрузка".
Освоение протокола считывателя
Перед реализацией транспортного уровня необходимо разобраться в соглашениях протокола работы с вашим считывателем. В "Справочном руководстве по протоколу INfinity 510" утверждается, что:
- Протокол считывателя базируется на ASCII-тексте.
- Для передачи команд и получения ответов от считывателя подключаться нужно к порту 50007.
- Для приема асинхронных сообщений о событиях (включая события чтения тегов) подключаться нужно к порту 50008.
- Все команды заканчиваются символами
\r\n.
- Все ответы и события заканчиваются символами
\r\n\r\n.
Создание транспортного уровня
Мы будем использовать мастер Transport Creation при генерировании скелета транспортного уровня для считывателя Sirit INfinity 510. Выполните следующие действия:
- Выберите File => New => Other.
- Выберите Device Kit, а затем Transport, чтобы открыть мастер.
- Нажмите кнопку Next.
- Заполните следующие поля (см. рисунок 4):
-
Transport Name:
Sirit
-
Package Base:
devworks.example
-
Response Timeout:
1000
-
Connection: TCPIP
- Отметьте все флажки параметров генерирования, а затем нажмите кнопку Next.
Рисунок 4. Указание названия и параметров транспортного уровня
- В следующем диалоговом окне заполните следующие поля (все остальные поля оставьте не заполненными) (см. рисунок 5):
-
Host:
localhost
-
Remote port:
50007
- Нажмите кнопку Finish для генерирования кода транспортного уровня.
Рисунок 5. Указание имени хоста и порта
- Посмотрите на сгенерированные файлы в проектах SiritTransport и SiritTransportTest:
Рисунок 6. Результаты создания транспортного уровня
Тестирование соединения транспортного уровня
Протестируйте возможность подключения вашего транспортного уровня к считывателю путем выполнения следующих действий:
- В проекте SiritTransportTest найдите файл esc.properties. Добавьте следующие свойства в конец этого файла:
esc.tracelevel=10
sirittransporttest.testcount=1
sirittransporttest.host=ip address of reader
|
Эти свойства указывают, что мы: хотим получить максимально подробную информацию трассировки, выполнить тест один раз, подключиться к считывателю.
- Запустите приложение SiritTransportTest. Вы должны увидеть сообщения, указывающие на то, что было выполнено подключение к считывателю и что транспортный уровень начал работать. Ключевым сообщением о начале работы транспортного уровня является:
[INFO] 2006-10-19 14:27:04.110 - TransportTest4005: Received transport
state changed to STARTED from ACTIVE.
|
- Завершите тестовое приложение или подождите одну минуту до его остановки.
Реализация пользовательского класса сообщений
Определите новый класс SiritMessage в том же пакете, в котором находится ваша реализация транспортного уровня. Поскольку считыватель Sirit использует основанный на ASCII-тексте протокол и стиль взаимодействия команда/ответ, вы должны создать суперкласс com.ibm.esc.message.AsciiResponseMessage.
Переопределите метод getDataLength() нового класса для исключения завершающих символов из данных сообщения. Sirit INfinity 510 использует символы \r\n\r\n в качестве символов завершения, поэтому getDataLength() должен возвращать число бит в сообщении без учета последних 4 символов.
public int getDataLength() {
// игнорировать CR+LF+CR+LF в конце сообщения
return (getBytes().length - 4) << 3;
}
|
Реализация метода write(MessageService)
Считыватель Sirit не использует каких-либо декораций протокола; сообщения выводятся точно такими, какими передаются в транспортный уровень для записи, поэтому метод write(MessageService) изменять не нужно.
Реализация метода processInput(byte[], int)
Основной работой транспортного уровня является синтаксический разбор приходящих из устройства байтов в экземпляры MessageService, которые могут обрабатывать другие компоненты Device Kit. Хотя Device Kit не предоставляет какой-либо поддержки написания синтаксического анализатора входящих байтов, имеется несколько вспомогательных классов, немного облегчающих работу.
Загрузите проект DeveloperWorksTransportHelpers из файла transport_helpers_project.zip, ссылка на который приведена в разделе "Загрузка".
Мы будем использовать вспомогательные классы пакета devworks.example.message.parsers:
-
SimpleTerminatedMessageParser реализует простой анализатор, распознающий конец сообщения по предопределенным символам завершения (в Sirit ими будут \r\n\r\n).
-
MessageParserListener определяет прослушиватель, который уведомляет SimpleTerminatedMessageParser при формировании сообщения.
- Прежде всего, определите поле private в транспортном уровня для хранения синтаксического анализатора, который вы создадите:
private SimpleTerminatedMessageParser parser;
|
- Создайте синтаксический анализатор в методе
startup(boolean):
public int startup(boolean output) throws Exception {
parser =
new SimpleTerminatedMessageParser
(createParserListener(),
"\r\n\r\n");
return TransportService.STARTED;
}
|
- Метод
createParserListener() создает экземпляр MessageParserListener, который уведомляется при формировании анализатором сообщения:
protected MessageParserListener createParserListener() {
return new MessageParserListener() {
public void parsedMessage(byte[] bytes, boolean maybeResponse, Object timestamp) {
MessageService m =
new SiritMessage(bytes,
maybeResponse ?
SiritTransport.this.sentMessage :
null);
if (maybeResponse) {
SiritTransport.this.fireMessageReceived(timestamp, m);
}
else {
SiritTransport.this.fireMessageReceivedNoResponsetimestamp, m);
}
}
};
}
|
Используйте здесь анонимный inner-класс вместо определения транспортного уровня, реализующего интерфейс MessageParserListener, поскольку при повторном генерировании транспортного уровня из CML-файла (например, после добавления определений новых сообщений), генератор кода перезапишет ваше определение своим собственным.
- Обратите внимание на то, что когда синтаксический анализатор уведомляет прослушиватель о нахождении им сообщения, вы создаете экземпляр пользовательского класса сообщения и подключаете его к сообщению, сгенерировавшему его (если
maybeResponse равен true).
- Наконец, реализуйте метод
processInput(byte[], int):
protected int processInput(byte[] bytes, int length) {
return parser.parseMessages(bytes, length, true);
}
|
Передайте байты во вспомогательный класс синтаксического анализатора для анализа. Аргумент true указывает анализатору, что принятое сообщение может быть ответом (для считывателя Sirit это ответ, поскольку он пришел от соединения команда/ответ).
Тестирование методов write(MessageService) и processInput(byte[], int)
Для тестирования транспортного уровня создайте тестовое приложение с использованием CML. Тестовое приложение будет передавать различные сообщения в считыватель, и вы сможете проверить выводимые данные для определения корректности формирования ответов.
- Измените файл SiritTransportTest.cml в проекте SiritTransportTest. Вставьте несколько элементов
<message> для указания команд, передаваемых в считыватель. Вы должны сгруппировать элементы <message> в логические тесты при помощи элемента <send>.
- Добавьте следующие строки после элемента
<description>:
<send id="LoginAndStatus">
<message id="ReaderWhoAmI">
<ascii>reader.who_am_i()\r\n</ascii>
</message>
<message>
<ascii>reader.login(login = admin, pwd=readeradmin)\r\n</ascii>
</message>
<message idref="ReaderWhoAmI">
</message>
<message>
<ascii>reader.check_status()\r\n</ascii>
</message>
<message>
<ascii>com.network.1.mac_address\r\n</ascii>
</message>
<message>
<ascii>info.time\r\n</ascii>
</message>
<message>
<ascii>setup.protocols\r\n</ascii>
</send>
<send id="ReaderVersion">
<message>
<ascii>version.hw\r\n</ascii>
</message>
<message>
<ascii>version.sw\r\n</ascii>
</message>
</send>
<send id="ErrorMessages">
<message>
<ascii>foo.bar\r\n</ascii>
</message>
</send>
|
Здесь мы указали разные команды для проверки выводимой информации.
- Сохраните CML-файл и повторно сгенерируйте тестовое приложение, щелкнув правой кнопкой мыши на файле и выбрав Device Kit => Generate. Если после повторного генерирования транспортного уровня возникают ошибки, проверьте, прежде всего, не являются ли они синтаксическими ошибками в Java-коде. Если да, исправьте их. Если ошибки не исчезли, щелкните правой кнопкой мыши на
SiritTransportTest и выберите
SMF => Re-Cache All, then SMF => Validate All.
- Запустите тестовое приложение. Вы должны увидеть в консоли множество сообщений. Просмотрите их и убедитесь в том, что переданные команды и сформированные анализатором ответы корректны. Ответ должен содержать символы завершения (
\r\n\r\n):
[DEBUG] 2006-10-19 17:19:04.260 - Transport2023: Message received
notification.
transport: SiritTransport@34e234e2=STARTED
message: "ok
0.4.4265\r\n\r\n" <- "version.sw\r\n"
|
Вы должны увидеть "спаренные" команды и ответные сообщения в log-файле, например:
[DEBUG] 2006-10-19 15:55:06.476 - Transport2023: Message received notification.
transport: RfidSiritCliTransport@34a034a0=STARTED
message: "ok isoc\r\n\r\n" <- "setup.protocols\r\n"
|
Определение тактового сообщения
Вся функциональность тактирования ("отсутствие активности") реализуется в суперклассах вашего транспортного уровня. Все, что необходимо сделать, - определить передаваемое сообщение. Хорошо подумайте при выборе сообщения для этого; оно должно не иметь побочных результатов, легко выполняться считывателем и давать не большой по размеру ответ. Для данного примера используйте сообщение version.sw.
После выбора желаемого сообщения вы должны переопределить метод noActivityProcessingMessage() в вашем транспортном уровне для возврата этого сообщения. Все сообщения, используемые транспортным уровнем, определяются в CML-файле.
- Измените файл SiritTransport.cml в папке SiritTransportDevelopment проекта SiritTransport.
- Добавьте следующее определение сообщения между элементами
<description>
и <responsetimeout>:
<message id="VersionSwGetMessage">
<ascii>version.sw\r\n</ascii>
</message>
|
- Повторно сгенерируйте определение транспортного уровня из CML, щелкнув правой кнопкой мыши на CML-файле и выбрав Device Kit => Generate.
- Посмотрите на класс
SiritTransportMessages. Вы должны увидеть определение для сообщения. Верните это сообщение из метода noActivityProcessingMessage():
public MessageService noActivityProcessingMessage() {
return SiritTransportMessages.getVersionSwGetMessage();
}
|
- Если вы хотите определить другое время не активности перед отправкой тактового сообщения, измените CML-файл и добавьте следующий элемент
<transport>:
<noactivitytimeout>value</noactivitytimeout>
|
- Вы можете также настроить время не активности путем изменения файла esc.properties в тестовом проекте транспортного уровня и добавления следующего свойства (таймаут в миллисекундах) в конец файла:
sirittransporttest.noactivitytimeout=8000
|
Тестирование тактового сообщения
Повторно запустите предыдущий тест. В конце отображенной информации вы должны увидеть, что примерно каждые 10 секунд (или указанное вами время) передается сообщение version.sw и принимается ответ.
Создание соединения с каналом событий
Sirit INfinity 510 требует подключения транспортного уровня ко второму порту (50008) для получения асинхронных уведомлений о событиях. Кроме того, считыватель использует способ регистрации событий (получением которых вы интересуетесь), требующий от транспортного уровня передачи маркера (token), переданного считывателем при первом подключении к порту 50008.
Device Kit не поддерживает создание альтернативных каналов сообщений, поэтому для облегчения работы мы реализовали некоторые вспомогательные классы. Эти классы находятся в пакете devworks.example.alternate.channel.handler проекта DeveloperWorksTransportHelpers.
Вспомогательными классами для альтернативного канала сообщений являются::
-
AlternateChannelMessageHandler управляет соединением с альтернативным каналом и получением байтов по нему.
-
AlternateChannelMessageListener определяет прослушиватель, который уведомляет AlternateChannelMessageHandler при наличии в нем информации для обработки.
- Номер порта канала событий для соединения (50008) является фиксированным в Sirit, но, по соглашениям, вы должны сделать его конфигурируемым свойством вашего транспортного уровня. Для этого измените CML-файл транспортного уровня, добавив пользовательское свойство после элемента
<description>:
<customparameter name="eventport"
type="int"
defaultvalue="50008"
access="true"/>
|
- Также добавьте другие конфигурационные свойства, необходимые для
AlternateChannelMessageHandler:
<customparameter name="eventChannelRetryTime"
type="int"
defaultvalue="2000"
access="true"/>
<customparameter name="eventChannelRetryAttempts"
type="int"
defaultvalue="2"
access="true"/>
|
- Повторно сгенерируйте транспортный уровень.
- Затем, определите private-поле в транспортном уровне для хранения обработчика сообщений альтернативного канала, который вы создадите:
private AlternateChannelMessageHandler eventChannel = null;
|
- Определите начальный диалог для подключения к каналу событий и зарегистрируйтесь на получение событий. Диалог начинается при подключении транспортного уровня к каналу событий:
public int startup(boolean output) throws Exception {
parser =
new SimpleTerminatedMessageParser(createParserListener(), "\r\n\r\n");
eventChannel =
new AlternateChannelMessageHandler(
createAlternateChannelMessageListener(),
"" + getEventport());
eventChannel.setNumberOfRetries(getEventChannelRetryAttempts());
eventChannel.setRetryWait(getEventChannelRetryTime());
eventChannel.start();
return TransportService.ACTIVE;
}
|
Обратите внимание на то, что теперь startup(boolean) возвращает состояние ACTIVE (вместо STARTED) для указания вовлечения транспортного уровня в начальный диалог.
- После подключения транспортного уровня к порту событий он получит сообщение, содержащее маркер, который в дальнейшем нужен для регистрации на получение событий. Определите три новых сообщения в CML-файле транспортного уровня:
<message id="EventConnectionReportMessage">
<ascii>event.connection id = \r\n\r\n</ascii>
<parameter type="ascii">
<index>22</index>
</parameter>
<filter>
<bytes format="hex">ff, ff, ff, ff, ff, ff, ff, ff,
ff, ff, ff, ff, ff,
ff, ff, ff,
ff, ff, ff, ff</bytes>
</filter>
</message>
<message id="ReaderBindMessage">
<ascii>reader.bind(id=)\r\n</ascii>
<parameter type="ascii">
<insert/>
<index>15</index>
</parameter>
<filter>
<bytes format="hex">ff, ff, ff, ff, ff, ff, ff,
ff, ff, ff, ff, ff</bytes>
</filter>
</message>
<message id="OkReaderBindReportMessage">
<ascii>ok\r\n\r\n</ascii>
<sentmessage idref="ReaderBindMessage"/>
</message>
|
EventConnectionReportMessage - это сообщение, принимаемое по каналу событий после установления соединения. Оно содержит маркер, который применяется в ReaderBindMessage, передаваемом транспортным уровнем для установки в качестве маркера по умолчанию для использования при регистрации на получение событий. Наконец, OkReaderBindReportMessage - это ответ. Когда транспортный уровень получает это сообщение, начальный диалог завершается.
- Для реализации начального диалога переопределите метод
startUpMessageReceived(TransportService, Object, MessageService):
public void startupMessageReceived(
TransportService source,
Object timestamp,
MessageService message) {
if (SiritTransportMessages
.getEventConnectionReportMessage()
.matches(message) != null) {
// получили маркер событий, выполнить команду bind
final Object token =
SiritTransportMessages
.getEventConnectionReportMessage()
.decodeMessage(message);
putConfigurationInformation("event.connection.id", token);
try {
final MessageService bind =
(MessageService) SiritTransportMessages
.getReaderBindMessage()
.clone();
write(bind.encodeMessage(bind, token));
}
catch (CloneNotSupportedException cnse) {
handleError(cnse, CLONE_EXCEPTION_RESOURCE);
}
catch (Exception e) {
handleError(e, WRITE_EXCEPTION_RESOURCE);
}
return;
}
if (SiritTransportMessages.getOkReaderBindReportMessage()
.matches(message) != null) {
// получили OK для reader.bind.
// Транспортный уровень формально начал работу.
setState(TransportService.STARTED);
}
}
|
- Создайте прослушиватель, который будет уведомлен
AlternateChannelMessageHandler, если ввод доступен:
protected AlternateChannelMessageListener createAlternateChannelMessageListener() {
return new AlternateChannelMessageListener() {
public ConnectionService getDefaultAlternateConnection() {
return SiritTransport.this.getDefaultAlternateConnection();
}
public int processAlternateInput(byte[] bytes, int length)
throws Exception {
return
SiritTransport.this.parser.parseMessages(
bytes,
length,
false);
}
public boolean isProcessing() {
return SiritTransport.this.isRunning()
&& SiritTransport.this.getState()
>= TransportService.CONNECTED;
}
public void connectionFailed() {
SiritTransport.this.restart();
}
};
}
|
Метод processAlternateInput(byte[], int) просто обращается к синтаксическому анализатору для создания сообщений из входящих байтов. Аргумент false указывает на то, что сообщение не является ответным сообщением.
- Создайте
ConnectionService для использования обработчиком AlternateChannelMessageHandler:
public ConnectionService getDefaultAlternateConnection() {
return
new TcpipConnection(getString("sirittransport.host",
SiritTransportService.DEFAULT_HOST),
getEventport(),
getInt("sirittransport.localport",
SiritTransportService.DEFAULT_LOCALPORT),
getInt("sirittransport.readtimeout",
SiritTransportService.DEFAULT_READTIMEOUT),
getInt("sirittransport.readsize",
SiritTransportService.DEFAULT_READSIZE),
getInt("sirittransport.writesize",
SiritTransportService.DEFAULT_WRITESIZE),
getInt("sirittransport.linger",
SiritTransportService.DEFAULT_LINGER));
}
|
Уровень соединения моделируется после TCP/IP-соединения транспортного уровня, но использует порт канала событий.
Тестирование обработки событий
Для завершения тестирования вашего транспортного уровня добавьте сообщения в тестовый CML-файл для регистрации и генерирования событий:
<send id="RegisterEvent">
<message id="ReaderRegisterEventMessage">
<ascii>reader.register_event(name=event.debug.trace.iop)\r\n</ascii>
</message>
<message id="ReaderRegisterEventDioMessage">
<ascii>reader.register_event(name=event.dio.all)\r\n</ascii>
</message>
</send>
<send id="Dio">
<message id="DioOutput1ReadMessage">
<ascii>dio.out.1\r\n</ascii>
</message>
<message id="DioOutput1OnMessage">
<ascii>dio.out.1 = 1\r\n</ascii>
</message>
<message idref="DioOutput1ReadMessage">
</message>
<message id="DioOutput1OffMessage">
<ascii>dio.out.1 = 0\r\n</ascii>
</message>
<message id="DioInput1ReadMessage">
<ascii>dio.in.1\r\n</ascii>
</message>
<message id="DioInput1OnMessage">
<ascii>dio.in.1 = 0\r\n</ascii>
</message>
<message id="DioInput1OffMessage">
<ascii>dio.in.1 = 1\r\n</ascii>
</message>
</send>
|
Повторно сгенерируйте тест транспортного уровня и запустите его. Вы должны увидеть сообщения о событии event.dio.all.
Реализация доступа к полям в пользовательском классе сообщений
Уровень устройства, который мы реализуем в следующей статье данной серии, потребует доступа к полям сообщений по ключу. В данном примере обратите внимание на то, что ответы на команды считывателю и события, в получении которых мы заинтересованы, бывают трех типов: ok value\r\n\r\n, ok name = value\r\n\r\n или event.name name1 = value1, name2 = value2\r\n\r\n. Уровню устройства понадобятся два типа полей:
- Часть ответа до первого пробела (ключ "0") и все остальное (ключ "1") для первого типа сообщений.
- Ключи, основанные на части
name
пар key/value для второго и третьего типов сообщений.
Ваше пользовательское сообщение должно заново реализовать метод get(Object key) для предоставления такого доступа. Метод возвращает значение поля, связанное с key. Существует много способов реализации анализа поля. Мы рекомендуем следующий:
- Найдите "0" и "1" полей "по требованию". В реализации вашего уровня устройства вы, возможно, никогда не будете обращаться к любому полю более одного раза.
- Найдите все пары ключ/значение и сохраните их в виде
Map, но только если запрашивается именованный ключ. Таким образом, для многих сообщений вы никогда не будете что-либо анализировать, а, если придется это сделать, то анализ будет выполняться только один раз для создания всех пар ключ/значение.
- Хотя
get(Object) может возвратить любой Object, для основанного на текстовых сообщениях протокола, подобного протоколу для Sirit INfinity 510, имеет больший смысл возвратить поля как экземпляры String. Ключи должны быть экземплярами String.
- Будьте внимательны при анализе и не включайте в значение ведущие и завершающие пробелы, либо завершающую запятую.
- Возвратите
null для неопределенных ключей.
Подходящая реализация пользовательского класса сообщений включена в файл transport_projects.zip, ссылка на который приведена в разделе "Загрузка".
Резюме
В данной статье мы подробно рассмотрели транспортный уровень адаптера Device Kit. Мы обсудили функции транспортного уровня и соглашения по реализации, а также продемонстрировали процесс создания транспортного уровня для RFID-считывателя Sirit INfinity 510. В следующей статье данной серии мы смоделируем уровень устройства.
Загрузка | Описание | Имя | Размер | Метод загрузки |
|---|
| Вспомогательные классы для транспортных уровней | transport_helpers_project.zip | 8KB | HTTP |
|---|
| Полный пример транспортного уровня | transport_projects.zip | 34KB | HTTP |
|---|
Ресурсы Научиться
- Оригинал статьи "Developing a device adapter and agent for the WebSphere RFID solution, Part 2: Building the transport component".
-
Разработка адаптера устройства и агента для решений WebSphere RFID, часть 1: Введение в WebSphere RFID Device Kit (developerWorks, 2007): Описание функций, предоставляемых компонентом WebSphere RFID Device Infrastructure, и процесса создания адаптеров устройств с использованием WebSphere RFID Device Infrastructure Device Development Kit.
-
Разработка адаптера устройства и агента для RFID-решений WebSphere, часть 3: Создание компонента устройства (developerWorks, 2007): Узнайте, как создать новое устройство и смоделировать команды, необходимые для чтения RFID-тегов при помощи RFID-считывателя Sirit™ INfinity 510.
-
Разработка адаптера устройства и агента для RFID-решений WebSphere, часть 4: Создание агента считывателя (developerWorks, 2007): Узнайте, как создать нового агента считывателя, чтобы читать RFID-теги при помощи считывателя Sirit™ INfinity 510.
-
WebSphere RFID Information Center: Документация по Websphere RFID.
-
Sirit INfinity 510 Protocol Reference Guide v1.0
вскоре появится на http://www.sirit.com.
-
Печать простых и сложных RFID-меток при помощи WebSphere RFID (developerWorks, ноябрь 2006): Руководство по печати RFID-меток при помощи Websphere RFID.
- Справочник
IBM WebSphere RFID Handbook: A Solution Guide.
-
На сайте developerWorks, на страницах, посвященных WebSphere имеется множество статей и учебных руководств
Получить продукты и технологии
Обсудить
Об авторах  | |  |
Карл Фреберджер (Karl Freburger) - IT-разработчик в IBM Global Business Services. Является сотрудником Software Group IBM, которая предоставляет бизнес-партнерам возможность проектировать решения, использующие компоненты IBM RFID. |
 | |  |
Аллен Смит (Allen Smith) - старший разработчик программного обеспечения в Pervasive Computing Group в Research Triangle Park, Северная Каролина. Он работает с бизнес-партнерами над проектированием решений, в которых используется всепроникающее программное обеспечение IBM промежуточного уровн |
|