From OpenSCADAWiki
Jump to: navigation, search
This page is a translated version of the page Modules/UserProtocol and the translation is 100% complete.

Other languages:
English • ‎российский • ‎українська
Модуль Имя Версия Лицензия Источник Языки Платформы Тип Автор Описание
UserProtocol Пользовательский протокол 1.1 GPL2 prot_UserProtocol.so en,uk,ru,de x86,x86_64,ARM Протокол Роман Савоченко

Обеспечивает создание собственных пользовательских протоколов на внутреннем языке OpenSCADA.

Модуль предназначен для предоставления пользователю возможности создания реализаций различных протоколов собственными силами на внутреннем языке OpenSCADA, обычно JavaLikeCalc, не прибегая к низкоуровневому программированию.

Основная цель модуля — упростить задачу подключения к OpenSCADA устройств источников данных, которые имеют незначительное распространение и/или предоставляют доступ к собственным данным по специфическому протоколу, обычно достаточно простому для реализации на внутреннем языке OpenSCADA. Для реализации этого предоставляется механизм формирования протокола исходящего запроса.

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

Модуль предоставляет возможность создания реализаций множества различных протоколов в объекте "Пользовательский протокол" (рис.1), а также использовать для этого стандартные шаблоны DAQ. Использование шаблонов DAQ позволяет создавать библиотеки комплексных протоколов и вызов их в этом модуле многократно, как их реализации, а также предоставляет контекст данных исполнения входного шаблона с их связыванием с данными подсистемы "Сбор Данных".

Рис.1. Основная вкладка объекта "Пользовательский протокол".

Главная вкладка содержит основные настройки пользовательского протокола:

  • Раздел "Состояние" — содержит свойства, характеризующие состояние протокола: статус, "Включен", имя БД (содержащей конфигурацию) и дату последней модификации.
  • Идентификатор, имя и описание узла протокола.
  • Состояние "Включен", в которое переводить протокол при загрузке.
  • DAQ шаблон, связанный с протоколом. Выбор шаблона тут выключает режим работы по прямым процедурам и доступ к следующим опциям. Режим DAQ-шаблона может работать во входном, выходном или обоих режимах запроса, в зависимости от наличия необходимых атрибутов.
  • Признак полного перевода текста процедур и языка входной и выходной процедур. Выбор языка для входной и/или выходной процедур включает соответствующую часть протокола и открывает доступ к соответствующим вкладкам конфигурации.

1 Входная часть протокола

Протокол входных запросов работает в кооперации с входным транспортом и отдельный объект "Пользовательского протокола" указывается в поле конфигурации протокола транспорта, вместе с именем модуля UserProtocol. Далее все запросы к транспорту будут направляться к прямой процедуре, или процедуре шаблону, с обработки запроса протокола (рис.2).

Рис.2. Вкладка конфигурации и контроля входных запросов.

Вкладка конфигурации и контроля входных запросов содержит:

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

Для прямой процедуры обработки предопределены, и обязательны или опциональны для создания в шаблоне, следующие атрибуты обмена с входящим транспортом:

  • Входной результат (rez) <Логический> — результат обработки (false — полный запрос; true — неполный, удерживать подключение); в режиме DAQ-шаблона Вы должны писать сюда прямо, не оператором "return";
  • Входной запрос (request) <Строка> — полное-аккумулированное сообщение запроса, которое протокол должен очищать на предмет обработанных данных;
  • Входной ответ (answer) <Строка> — сообщение ответа;
  • Входной отправитель (sender) <Строка> — отправитель запроса;
  • Транспорт (tr) <Объект> — объект входного транспорта.

Общий сценарий обработки входящих запросов:

  • Запрос формируется удалённой станцией и через сеть попадает на транспорт OpenSCADA.
  • OpenSCADA транспорт передаёт запрос этому модулю, выбранному в поле протокола, и объекту пользовательского протокола, в виде значений переменной "request" — для последовательности запроса и "sender" — для адреса отправителя запроса.
  • Запускается выполнение процедуры протокола входящего запроса, в процессе которой анализируется содержимое переменной "request" и формируется ответ в переменной "answer". По окончанию выполнения процедуры формируется переменная "rez", которая указывает транспорту на факт получения полного запроса и формирование корректного ответа (false) или необходимость транспорту ожидать остатка данных (true) — удерживать подключение.
  • Если результат обработки равен "false", и ответ ненулевой, то транспорт отправляет ответ, а сама процедура протокола должна удалить обработанную часть запроса "request", поскольку только она может определить целостность посылки и потенциальное наличие за ней начала следующей.
  • Если результат процедуры обработки равен "true" то транспорт продолжает ожидать данные. При получении следующей порции данных они добавляются к переменной "request" и выполнение процедуры повторяется.

В качестве примера рассмотрим реализацию обработки запросов по протоколу DCON для некоторых запросов к источнику данных с адресом "10":

var enCRC = true;
//SYS.messDebug("/DCON/in","REQ: "+request);
//Проверка запроса на полноту
if(request.length < 4 || request[request.length-1] != "\r") {
  if(request.length > 10) request = "";
  return true;
}
//Проверка запроса на целостность (CRC) и адрес
if(enCRC) {
  CRC = 0;
  for(i = 0; i < (request.length-3); i++) CRC += request.charCodeAt(i);
  if(CRC != request.slice(request.length-3,request.length-1).toInt(16) || request.slice(1,3).toInt(16) != 10) return false;
}
//Анализ запроса и подготовка ответа
if(request[0] == "#") answer = ">+05.123+04.153+07.234-02.356+10.000-05.133+02.345+08.234";
else if(request[0] == "@") answer = ">AB3C";
else answer = "?";
//Завершение ответа
if(enCRC) {
  CRC = 0;
  for(i=0; i < answer.length; i++) CRC += answer.charCodeAt(i);
  answer += (CRC&0xFF).toString(16,2)+"\r";
}
//SYS.messDebug("/DCON/in","ANSV: "+answer[0]);
return 0;

2 Выходная часть протокола

Протокол исходящих запросов работает в кооперации с исходящим транспортом и отдельным объектом "Пользовательского протокола". Источником запроса через протокол может выступать функция общесистемного API пользовательского программирования выходного транспорта "int messIO( XMLNodeObj req, string prt );", в параметрах которой указывается:

  • req — запрос в виде дерева XML со структурой, соответствующей входному формату реализованного протокола;
  • prt — имя этого модуля — "UserProtocol".

Запрос, отправленный вышеуказанным образом, направляться в прямую процедуру обработки запроса протокола (рис.3), или процедуру шаблона, с идентификатором пользовательского протокола, указываемым в атрибуте req.attr("ProtIt").

Рис.3. Вкладка прямой процедуры обслуживания исходящих запросов.

Вкладка процедуры обработки выходных запросов содержит только поле текста прямой процедуры обработки на внутреннем языке программирования OpenSCADA, который указан в предыдущей вкладке. Эта вкладка отсутствует для режима работы по шаблону.

Для процедуры обработки предопределены, и обязательные или опциональные для создания в шаблоне, следующие атрибуты обмена:

  • Выходной ВВ (io) <Объект::XMLNode> — XML узел обмена с клиентом, через который протокол принимает запросы и в который помещается результат в формате, который реализуется процедурой;
  • Транспорт (tr) <Объект> — объект транспорта, предназначено для вызова функции транспорта string messIO( string mess, real timeOut = 1000 );tr.messIO(req).

Общий сценарий формирования выходного запроса:

  • Формирование XML-дерева согласно структуре, которая реализуется протоколом, и указание идентификатора пользовательского протокола в атрибуте "ProtIt".
  • Отправка запроса транспорту через этот протокол — "SYS.Transport["{Modul}"]["out_{OutTransp}"].messIO(req, "UserProtocol");".
  • Выбор пользовательского протокола согласно req.attr("ProtIt") и инициализация переменных выходного транспорта: io — согласно первому аргументу messIO() и tr — объект "OutTransp".
  • Вызов процедуры на исполнение, которая, осуществив обработку структуры io, формирует прямой запрос транспорта tr.messIO(req);, результат которого обрабатывается и помещается назад в io.

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

В качестве примера рассмотрим реализацию запросов посредством протокола DCON к обработчику, реализованному в предыдущем разделе. Начнём с реализации протокольной части:

//Формирование конечного запроса
request = io.name().slice(0,1) + io.attr("addr").toInt().toString(16,2) + io.text();
if(io.attr("CRC").toInt()) {
  CRC = 0;
  for(i = 0; i < request.length; i++) CRC += request.charCodeAt(i);
    request += (CRC&0xFF).toString(16,2) + "\r";
}
else request += "\r";
//Отправка запроса
resp = tr.messIO(request);
while(resp[resp.length-1] != "\r") {
  tresp = tr.messIO("");
  if(!tresp.length) break;
  resp += tresp;
}
//Анализ ответа
if(io.attr("CRC").toInt()) {
  if(resp.length < 4 || resp[resp.length-1] != "\r") { io.setAttr("err","10:"+tr("Error or no response.")); return; }
  //Проверка ответа на целостность (CRC)
  CRC = 0;
  for(i = 0; i < (resp.length-3); i++) CRC += resp.charCodeAt(i);
    if(CRC != resp.slice(resp.length-3,resp.length-1).toInt(16)) { io.setAttr("err","11:"+tr("CRC error.")); return; }
}
else if(resp.length < 2 || resp[resp.length-1] != "\r") { io.setAttr("err","10:"+tr("Error or no response.")); return; }
if(resp[0] != ">") { io.setAttr("err","12:"+resp[0]+":"+tr("DCON error.")); return; }
//Возврат результата
io.setAttr("err","");
io.setText(resp.slice(1,resp.length-3));

И процедура непосредственной отправки DCON запроса, через предыдущую процедуру протокола. Эту процедуру необходимо поместить в нужную задачу или промежуточную функцию OpenSCADA, например, в процедуру объекта контроллера DAQ.JavaLikeCalc:

//Подготовка запроса
req = SYS.XMLNode("#").setAttr("ProtIt","DCON").setAttr("addr",10);
//Отправка запроса
SYS.Transport["Serial"]["out_TestDCON"].messIO(req,"UserProtocol");
if(!req.attr("err").length) SYS.messDebug("TEST REQ","RES: "+req.text());

//Подготовка второго запроса
req = SYS.XMLNode("@").setAttr("ProtIt","DCON").setAttr("addr",10);
//Отправка второго запроса
SYS.Transport["Serial"]["out_TestDCON"].messIO(req,"UserProtocol");
if(!req.attr("err").length) SYS.messDebug("TEST REQ","RES: "+req.text());