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.3 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());