From OpenSCADAWiki
Jump to: navigation, search
(Updating to match new version of source page)
Line 56: Line 56:
 
* Если результат процедуры обработки равен "true" то транспорт продолжает ожидать данные. При получении следующей порции данных они добавляются к переменной "request" и выполнение процедуры повторяется.
 
* Если результат процедуры обработки равен "true" то транспорт продолжает ожидать данные. При получении следующей порции данных они добавляются к переменной "request" и выполнение процедуры повторяется.
  
В качестве примера рассмотрим реализацию обработки запросов по протоколу DCON для некоторых запросов к источнику данных с адресом "10":
+
As an example, let's consider the implementation of DCON request processing for some requests to a data source with the address "10":
<pre style="white-space: pre-wrap;">
+
<syntaxhighlight lang="JavaScript">
 
var enCRC = true;
 
var enCRC = true;
 
//SYS.messDebug("/DCON/in","REQ: "+request);
 
//SYS.messDebug("/DCON/in","REQ: "+request);
//Проверка запроса на полноту
+
//Testing the request for completity
 
if(request.length < 4 || request[request.length-1] != "\r") {
 
if(request.length < 4 || request[request.length-1] != "\r") {
 
   if(request.length > 10) request = "";
 
   if(request.length > 10) request = "";
 
   return true;
 
   return true;
 
}
 
}
//Проверка запроса на целостность (CRC) и адрес
+
//Checking the request for the integrity (CRC) and the address
 
if(enCRC) {
 
if(enCRC) {
 
   CRC = 0;
 
   CRC = 0;
Line 71: Line 71:
 
   if(CRC != request.slice(request.length-3,request.length-1).toInt(16) || request.slice(1,3).toInt(16) != 10) return false;
 
   if(CRC != request.slice(request.length-3,request.length-1).toInt(16) || request.slice(1,3).toInt(16) != 10) return false;
 
}
 
}
//Анализ запроса и подготовка ответа
+
//Analysis the request and the response preparing
 
if(request[0] == "#") answer = ">+05.123+04.153+07.234-02.356+10.000-05.133+02.345+08.234";
 
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 if(request[0] == "@") answer = ">AB3C";
 
else answer = "?";
 
else answer = "?";
//Завершение ответа
+
//Finishing the response
 
if(enCRC) {
 
if(enCRC) {
 
   CRC = 0;
 
   CRC = 0;
Line 83: Line 83:
 
//SYS.messDebug("/DCON/in","ANSV: "+answer[0]);
 
//SYS.messDebug("/DCON/in","ANSV: "+answer[0]);
 
return 0;
 
return 0;
</pre>
+
</syntaxhighlight>
  
 
== Выходная часть протокола ==
 
== Выходная часть протокола ==
Line 108: Line 108:
 
Суть выделения протокольной части кода в процедуру пользовательского протокола заключается в упрощении и унификации интерфейса клиентского обмена при многократном использовании и предполагает формирование структуры XML-узла обмена в виде атрибутов адресов удалённых станций, адресов читаемых и записываемых переменных, а также значений самих переменных. При этом весь груз непосредственного кодирования запроса и декодирования ответа возлагается на процедуру пользовательского протокола. Если это одноразовая реализация, которая к тому-же не предусматривает реализации входной части, то проще это сделать прямо в исходном шаблоне к источнику данных, в виде встроенной функции.
 
Суть выделения протокольной части кода в процедуру пользовательского протокола заключается в упрощении и унификации интерфейса клиентского обмена при многократном использовании и предполагает формирование структуры XML-узла обмена в виде атрибутов адресов удалённых станций, адресов читаемых и записываемых переменных, а также значений самих переменных. При этом весь груз непосредственного кодирования запроса и декодирования ответа возлагается на процедуру пользовательского протокола. Если это одноразовая реализация, которая к тому-же не предусматривает реализации входной части, то проще это сделать прямо в исходном шаблоне к источнику данных, в виде встроенной функции.
  
В качестве примера рассмотрим реализацию запросов посредством протокола DCON к обработчику, реализованному в предыдущем разделе. Начнём с реализации протокольной части:
+
As an example, consider the implementation of the requests by the protocol DCON to the handler, implemented in the previous section. Let's start with the implementation of the protocol part:
<pre style="white-space: pre-wrap;">
+
<syntaxhighlight lang="JavaScript">
//Формирование конечного запроса
+
//Preparing the result request
 
request = io.name().slice(0,1) + io.attr("addr").toInt().toString(16,2) + io.text();
 
request = io.name().slice(0,1) + io.attr("addr").toInt().toString(16,2) + io.text();
 
if(io.attr("CRC").toInt()) {
 
if(io.attr("CRC").toInt()) {
Line 118: Line 118:
 
}
 
}
 
else request += "\r";
 
else request += "\r";
//Отправка запроса
+
//Sending the request
 
resp = tr.messIO(request);
 
resp = tr.messIO(request);
 
while(resp[resp.length-1] != "\r") {
 
while(resp[resp.length-1] != "\r") {
Line 125: Line 125:
 
   resp += tresp;
 
   resp += tresp;
 
}
 
}
//Анализ ответа
+
//Analysis the response
 
if(io.attr("CRC").toInt()) {
 
if(io.attr("CRC").toInt()) {
 
   if(resp.length < 4 || resp[resp.length-1] != "\r") { io.setAttr("err","10:"+tr("Error or no response.")); return; }
 
   if(resp.length < 4 || resp[resp.length-1] != "\r") { io.setAttr("err","10:"+tr("Error or no response.")); return; }
   //Проверка ответа на целостность (CRC)
+
   //Checking the response to the integrity (CRC)
 
   CRC = 0;
 
   CRC = 0;
 
   for(i = 0; i < (resp.length-3); i++) CRC += resp.charCodeAt(i);
 
   for(i = 0; i < (resp.length-3); i++) CRC += resp.charCodeAt(i);
Line 135: Line 135:
 
else if(resp.length < 2 || resp[resp.length-1] != "\r") { io.setAttr("err","10:"+tr("Error or no response.")); 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; }
 
if(resp[0] != ">") { io.setAttr("err","12:"+resp[0]+":"+tr("DCON error.")); return; }
//Возврат результата
+
//Returning the result
 
io.setAttr("err","");
 
io.setAttr("err","");
 
io.setText(resp.slice(1,resp.length-3));
 
io.setText(resp.slice(1,resp.length-3));
</pre>
+
</syntaxhighlight>
  
И процедура непосредственной отправки DCON запроса, через предыдущую процедуру протокола. Эту процедуру необходимо поместить в нужную задачу или промежуточную функцию OpenSCADA, например, в процедуру объекта контроллера [[Special:MyLanguage/Modules/JavaLikeCalc#Examples|DAQ.JavaLikeCalc]]:
+
And a procedure of sending the DCON request directly, through the previous protocol procedure. This procedure should be placed in the desired task or an interim function of OpenSCADA, for example, in the procedure of the controller object [[Special:MyLanguage/Modules/JavaLikeCalc#Examples|DAQ.JavaLikeCalc]]:
<pre style="white-space: pre-wrap;">
+
<syntaxhighlight lang="JavaScript">
//Подготовка запроса
+
//Preparing the first request
 
req = SYS.XMLNode("#").setAttr("ProtIt","DCON").setAttr("addr",10);
 
req = SYS.XMLNode("#").setAttr("ProtIt","DCON").setAttr("addr",10);
//Отправка запроса
+
//Sending the request
 
SYS.Transport["Serial"]["out_TestDCON"].messIO(req,"UserProtocol");
 
SYS.Transport["Serial"]["out_TestDCON"].messIO(req,"UserProtocol");
 
if(!req.attr("err").length) SYS.messDebug("TEST REQ","RES: "+req.text());
 
if(!req.attr("err").length) SYS.messDebug("TEST REQ","RES: "+req.text());
  
//Подготовка второго запроса
+
//Preparing the second request
 
req = SYS.XMLNode("@").setAttr("ProtIt","DCON").setAttr("addr",10);
 
req = SYS.XMLNode("@").setAttr("ProtIt","DCON").setAttr("addr",10);
//Отправка второго запроса
+
//Sending the request
 
SYS.Transport["Serial"]["out_TestDCON"].messIO(req,"UserProtocol");
 
SYS.Transport["Serial"]["out_TestDCON"].messIO(req,"UserProtocol");
 
if(!req.attr("err").length) SYS.messDebug("TEST REQ","RES: "+req.text());
 
if(!req.attr("err").length) SYS.messDebug("TEST REQ","RES: "+req.text());
</pre>
+
</syntaxhighlight>

Revision as of 09:22, 16 July 2022

Other languages:
English • ‎mRussian • ‎Українська
Модуль Имя Версия Лицензия Источник Языки Платформы Тип Автор Описание
UserProtocol Пользовательский протокол 1.5 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" и выполнение процедуры повторяется.

As an example, let's consider the implementation of DCON request processing for some requests to a data source with the address "10":

var enCRC = true;
//SYS.messDebug("/DCON/in","REQ: "+request);
//Testing the request for completity
if(request.length < 4 || request[request.length-1] != "\r") {
  if(request.length > 10) request = "";
  return true;
}
//Checking the request for the integrity (CRC) and the address
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;
}
//Analysis the request and the response preparing
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 = "?";
//Finishing the response
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-узла обмена в виде атрибутов адресов удалённых станций, адресов читаемых и записываемых переменных, а также значений самих переменных. При этом весь груз непосредственного кодирования запроса и декодирования ответа возлагается на процедуру пользовательского протокола. Если это одноразовая реализация, которая к тому-же не предусматривает реализации входной части, то проще это сделать прямо в исходном шаблоне к источнику данных, в виде встроенной функции.

As an example, consider the implementation of the requests by the protocol DCON to the handler, implemented in the previous section. Let's start with the implementation of the protocol part:

//Preparing the result request
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";
//Sending the request
resp = tr.messIO(request);
while(resp[resp.length-1] != "\r") {
  tresp = tr.messIO("");
  if(!tresp.length) break;
  resp += tresp;
}
//Analysis the response
if(io.attr("CRC").toInt()) {
  if(resp.length < 4 || resp[resp.length-1] != "\r") { io.setAttr("err","10:"+tr("Error or no response.")); return; }
  //Checking the response to the integrity (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; }
//Returning the result
io.setAttr("err","");
io.setText(resp.slice(1,resp.length-3));

And a procedure of sending the DCON request directly, through the previous protocol procedure. This procedure should be placed in the desired task or an interim function of OpenSCADA, for example, in the procedure of the controller object DAQ.JavaLikeCalc:

//Preparing the first request
req = SYS.XMLNode("#").setAttr("ProtIt","DCON").setAttr("addr",10);
//Sending the request
SYS.Transport["Serial"]["out_TestDCON"].messIO(req,"UserProtocol");
if(!req.attr("err").length) SYS.messDebug("TEST REQ","RES: "+req.text());

//Preparing the second request
req = SYS.XMLNode("@").setAttr("ProtIt","DCON").setAttr("addr",10);
//Sending the request
SYS.Transport["Serial"]["out_TestDCON"].messIO(req,"UserProtocol");
if(!req.attr("err").length) SYS.messDebug("TEST REQ","RES: "+req.text());