Автор: Роман Савоченко
Открытая SCADA система OpenSCADA принадлежит к классу SCADA(Supervisory Control and Data Aquisition) систем. Системы данного класса используются как элемент систем автоматизации технологических процессов(АСУ-ТП).
В отдельных случаях SCADA системы могут использоваться на уровне контроллеров, при этом совмещая функции контролера с функциями SCADA системы.
Для организации пользовательских вычислений в среде промышленных контроллеров и SCADA систем используются различные языки программирования. В случае с контроллерами в качестве таких языков чаще всего используются языки низкого уровня (ассемблеры), однако в последнее время всё чаще используются языки высокого уровня (C, Pascal и другие), а также формальные языки (блочные схемы, релейные схемы, логические схемы и другие). В случае с SCADA системами вычисления чаще обеспечиваются языками программирования высокого уровня и формальными языками.
Предоставление среды пользовательского программирования любой программной системой вообще является важным показателем качества и развитости системы.
Данный проект предназначен для разработки среды программирования системы OpenSCADA.
Функционально разработка предназначена для создания в системе OpenSCADA компонентов среды программирования, с помощью которых можно реализовывать различные вычисления и управляющие алгоритмы внутри системы OpenSCADA.
Эксплуатационным назначением разработки является:
Под архитектурными требованиями подразумевается то, какой структуре должна следовать разработка.
Особенность архитектуры системы OpenSCADA накладывает соответствующие требования к архитектуре разработки. Так, учитывая модульность, гибкость и масштабируемость системы OpenSCADA, нужно обеспечить такими же характеристиками данную разработку.
С такой целью среду программирования архитектурно можно разделить на три части:
Объекты функций должны содержать только структуру параметров функций и алгоритм вычисления. Реальные значения объектам функций должны передаваться в виде кадра значений различными вычислительными контролерам и узлами.
Декларирующие объекты должны содержаться в ядре системы OpenSCADA для предоставления доступа всем компонентам среды программирования.
Кадры значений, которые передаются объектам функций в процессе вычислений, должны храниться в узлах, требующих вычислений. Такими узлами могут быть:
Сформулируем требования к среде программирования, исходя из архитектурных требований, которые требуют разделения среды программирования на три части.
Ядро системы OpenSCADA должно обеспечить:
В рамках проекта нужно реализовать следующие библиотеки функций и вычислительные контролеры:
Модуль(и) статических библиотек функций должны обеспечить:
Модуль вычислителя на языке программирования высокого уровня и модуль библиотек функций на этом языке по совместительству должны обеспечить:
Модуль вычислителя на языке блоков должен обеспечить:
Для реализации модуля вычислителя на языке программирования высокого уровня и модуля библиотек функций на этом же языке нужно использовать грамматику одного из известных языков программирования высокого уровня.
Программа на выбранном языке должна компилироваться в код внутренней виртуальной машины. Главными требованиями к виртуальной машине являются: высокая надёжность и производительность. Для удовлетворения требований высокой надёжности виртуальная машина должна содержать механизмы учёта и ограничения времени обсчёта.
В обязательном порядке нужно реализовать следующие возможности языка программирования:
Язык блочных схем может строиться на основе функций доступных библиотек функций объектной модели OpenSCADA. Каждый блок может ассоциироваться с функцией и формировать структуру значений в соответствии со структурой функции. Каждый блок должен обеспечивать включение и исключение из процесса обработки, не останавливая вычисления всей схемы.
Для связывания блоков один с другим нужно обеспечить поддержку следующих типов связей:
Связи должны поддерживать их "горячую", в процессе вычисления, установку.
Любой сложный программный комплекс должен содержать среду программирования. Наличие такой среды значительно расширяет гибкость системы и, как следствие, сферу её применения. Среда программирования, которая используется в таких программных комплексах, может основываться на различных языках программирования. Однако чаще всего используются языки, наделённые высоким уровнем формализма, например, языки блоков (блочные схемы, логичные схемы, релейные схемы и другие). Однако, на уровне с ними часто используются языки высокого уровня типа С и С++ в своей упрощённой реинкарнации: Java и JavaScript. Не в последнюю очередь также используются языки логического вывода: Prolog и Lisp.
Система OpenSCADA разрабатывается как гибкая и высоко-масштабируемая SCADA система, поэтому создание собственной среды программирования является важной задачей. Главной целью создания среды пользовательского программирования является предоставление в систему OpenSCADA гибкого и мощного механизма программирования.
Кроме того, среда программирования предназначена для предоставления возможности создания:
Ключевым элементом среды программирования в роли объекта выбрана функция, где функция определена как контейнер, который содержит структуры данных (параметры) и алгоритм их вычисления. При этом параметры могут быть как входами, так и выходами. Такая, несколько упрощённая объектная структура, совмещает объект с механизмом, тем самым упрощая начальное восприятие и создание среды программирования. Кроме того, такой выбор не исключает возможности создания полноценной объектной модели (ОМ) в будущем.
То есть, имеем элементарный модуль рис.1.
Где:
Как видно, такой модуль не содержит состояний, потому как не хранит значений ни входов ни выходов. Добавление состояния, как текущий контекст, модуль получает в так называемых вычислителях. Такая схема позволяет один модуль функции использовать в разных модулях вычислителей (рис. 2).
Для реализации языка программирования высокого уровня нужно определиться с языком, который будет взят за основу. Исходя из принципа, что язык должен быть достаточно известным и, по возможности, использоваться другими проектами в качестве внутреннего языка вычислений, остановимся на следующих языках: С, Java, JavaScript и Phyton. Поскольку языки С, Java и JavaScript практически вышли из языка C, то ограничим круг до языков Java и JavaScript. Язык C++ является несколько сложным, поскольку содержит механизмы работы с адресами и указателями. Эти возможности являются избыточными для задач среды пользовательского программирования.
Остановимся на языках Java и JavaScript, грамматика которых упрощена. Сначала реализуем общую грамматику для языков Java и JavaScript с последующим уклоном к языку JavaScript, поскольку он часто используется внутри других проектов (Web-браузеры, KDE).
Известно, что для построения компилятора или интерпретатора нужно создать лексический анализатор, синтаксический анализатор и генератор кода или интерпретатор.
Лексический анализатор строится достаточно просто.
Синтаксический анализатор основывается на таком понятии, как формальные грамматики и его создание - задание нетривиальное. Поэтому существуют готовые инструменты для построения синтаксических анализаторов по указанной грамматике. Из них можно отметить генератор синтаксических анализаторов Yacc (AT&T и Berkeley). На основе этого известного генератора синтаксических анализаторов создано множество других, например Bison и Zubr. Все они совместимы и могут заменять один другого.
Для разработки языка программирования был выбран генератор синтаксических анализаторов Bison, поскольку он свободно распространяется практически со всеми современными дистрибутивами ОС Linux, развивается и является многоплатформенным.
Любую формальную грамматику можно записать в виде:
G = <A, N, г, P>
где :
Запишем терминальный и нетерминальный алфавиты:
A = {ERR, IF, ELSE, =, ADDEQ, SUBEQ, MULEQ, DIVEQ, FUNC, B_FUNC, B_FUNC1, B_FUNC2, VAR, CONST, ?, :, OR, AND, |, &, ^, >, <, GE, LE, EQ, NE, +, -, *, /, %, UN_MINUS, !, , ',', (, ), '{', '}', ; }
N = {root, solver, solve,assign, expr, prmlst, if, if_expr, end }
Описание терминального алфавита приведено в таблице 1. Описание нетерминального алфавита приведено в таблице 2. Правила P грамматики приведены в таблице 3. В качестве аксиомы грамматики определено: г = root;
Таблица 1: Терминальный алфавит Java-подобного языка (A)
Терминал | Описание |
ERR | Ошибка. Вставляется лексическим анализатором в случае ошибки в нём. |
error | Ошибка. Прерывает работу синтаксического анализатора. |
IF | Ключевое слово условия - “if”. |
ELSE | Ключевое слово условия - “else”. |
= | Операция – присвоить. |
ADDEQ | Операция - “+=” |
SUBEQ | Операция - “-=” |
MULEQ | Операция - “*=” |
DIVEQ | Операция - “/=” |
FUNC | Внешняя функция. |
B_FUNC | Встроенная функция без параметров. |
B_FUNC1 | Встроенная функция с одним параметром. |
B_FUNC2 | Встроенная функция с двумя параметрами. |
VAR | Переменная, автоматическая переменная, атрибут системного параметра или параметр функции. |
CONST | Константа. |
? | Первый символ операции “?:” |
: | Второй символ операции “?:” |
OR | Логическая операция - “||” |
AND | Логическая операция - “&&” |
| | Операция – побитовое “ИЛИ” |
& | Операция – побитовое “И” |
^ | Операция – исключающее “ИЛИ” |
> | Операция – больше. |
< | Операция – меньше. |
GE | Операция – больше или равно. |
LE | Операция – меньше или равно. |
EQ | Операция – эквивалентно. |
NE | Операция – неэквивалентно. |
+ | Операция – сложение. |
- | Операция – вычитание. |
* | Операция – умножение. |
/ | Операция – деление. |
% | Операция – остаток от целочисленного деления. |
UN_MINUS | Операция – унарный минус. |
! | Операция – инверсия. |
~ | Операция – побитовая инверсия. |
, | Разделитель (отделяет параметры в функциях). |
( | Выделение приоритета в выражениях и параметров в функциях. |
) | Выделение приоритета в выражениях и параметров в функциях. |
{ | Выделение блока выражений. |
} | Выделение блока выражений. |
; | Завершение выражения. |
Таблица 2: Нетерминальный алфавит Java-подобного языка (N)
Нетерминал | Описание |
root | Корень. Все выражения должны сворачиваться в него. |
solver | Решение. |
solve | Елемент решения. |
assign | Присвоение. |
expr | Выражение. |
prmlst | Список параметров функции. |
if | Условие. |
if_expr | Выражение условия. |
end | Конец условия или программы. |
Таблица 3: Грамматика Java-подобного языка
Правило | Описание |
root: solver end | error | разрешение и окончание программы; ошибка. |
solver: /*empty*/ | solver solve | пустое решение; рекурсия решения. |
solve: assign ';' | IF '(' expr ')' if solve end | IF '(' expr ')' if solve end ELSE solve end | FUNC '(' prmlst ')' ';' | '{' solver '}' | присвоение; глобальное короткое условие; глобальное полное условие; процедурный вызов внешней функции; блоки кода в скобках “{“ “}”. |
assign: VAR '=' expr | VAR ADDEQ expr | VAR SUBEQ expr | VAR MULEQ expr | VAR DIVEQ expr | простое присвоение; присвоение с добавлением; присвоение с вычитанием; присвоение с умножением; присвоение с делением. |
expr: CONST | VAR | VAR '=' expr | B_FUNC '(' ')' | B_FUNC1 '(' expr ')' | B_FUNC2 '(' expr ',' expr ')' | FUNC '(' prmlst ')' | expr '+' expr | expr '-' expr | expr '*' expr | expr '/' expr | expr '%' expr | expr '|' expr | expr '&' expr | expr '^' expr | '(' expr ')' | expr OR expr | expr AND expr | expr '<' expr | expr '>' expr | expr GE expr | expr LE expr | expr EQ expr | expr NE expr | '!' expr | '~' expr | '-' expr | expr if_expr expr end ':' expr end | константа; переменная; присвоение переменной внутри выражения; встроенная функция без параметров; встроенная функция с одним параметром; встроенная функция с двумя параметрами; внешняя функция; добавление; вычитание; умножение; деление; остаток от целочисленного деления; побитовое “ИЛИ”; побитовое “И”; побитовое исключающее “ИЛИ”; скобки в выражении; логическое “ИЛИ”; логическое “И”; меньше; больше; больше и равно; меньше и равно; равно; не равно; логическое отрицание; побитовая инверсия; унарный минус; условие внутри выражения; |
prmlst: /*empty*/ | expr | prmlst ',' prmlst | пустой список параметров; выражение как параметр; перечень параметров через “,”. |
if: /*empty*/ | Старт условия (для создания кода условия). |
if_expr: '?' | Старт условия внутри выражения. |
end: /*empty*/ | Конец программы или условия. |
На основе этих правил можно построить синтаксический анализатор. Однако, для работы синтаксического анализатора нужен лексический анализатор, который должен выполнять анализ потока входной программы, выделять лексемы и предоставлять их синтаксическому анализатору.
Реализуем лексический анализатор, который должен выполнять следующие функции:
Для упрощения языка будем использовать неявное определение локальных переменных, которое предполагает определение новых переменных во время присвоения ей первого значения. Кроме того, тип локальной переменной должен устанавливаться в соответствии с типом значения, которое впервые было ей присвоено. Например, выражение <Qr=Q0*Pi+0.01;> должно определить переменную Qr типом переменной Q0.
В работе с различными типами данных используем механизм автоматического приведения типов, в местах где такое приведение имеет смысл.
Для комментирования участков кода предусмотрим символы «//». Всё, что идёт после данных символов до конца строки, должно игнорироваться компилятором (лексическим анализатором).
Предусмотрим, чтоб в процессе генерации кода виртуальной машины компилятор выполнял оптимизацию по константам и приведение типов констант к нужному типу.
Под оптимизацией по константам подразумевается выполнение вычислений над двумя константами в процессе построения кода и последующая вставка результата в код. Например, выражение <y=pi*10;> должно свернуться к простому присваиванию <y=31.4159;>.
Под приведением типов констант к необходимому типу подразумевается формирование в коде константы, которая исключает приведение типа в процессе исполнения. Например, выражение <y=x*10> в случае вещественного типа переменной <x>, должно преобразоваться в <y=x*10.0>.
В результате разработки реализуем следующие элементы языка программирования:
Ключевые слова: if, else, true, false.
Постоянные:
Типы переменных:
Встроенные константы: pi = 3.14159265, e = 2.71828182.
Атрибуты параметров системы OpenSCADA.
Функции объектной модели системы OpenSCADA.
Операции, поддержку которых языком программирования мы получим, представлены в таблице 4. Приоритет операций уменьшается сверху вниз. Операции с одинаковым приоритетом входят в одну цветовую группу.
Таблица 4. Операции Java-подобного языка
Символ | Описание |
() | Вызов функции. |
{} | Программные блоки. |
- | Унарный минус. |
! | Логическое отрицание. |
~ | Побитовое отрицание. |
* | Умножение. |
/ | Деление. |
% | Остаток от целочисленного деления. |
+ | Сложение |
- | Вычитание |
> | Больше |
>= | Больше или равно |
< | Меньше |
<= | Меньше или равно |
== | Равно |
!= | Неравно |
| | Поразрядное «ИЛИ» |
& | Поразрядное «И» |
^ | Поразрядное «Исключающее ИЛИ» |
&& | Логический «И» |
|| | Логический «ИЛИ» |
?: | Условная операция (i=(i<0)?0:i;) |
= | Присваивание. |
+= | Присваивание с сложением. |
-= | Присваивание с вычитанием. |
*= | Присваивание с умножением. |
/= | Присваивание с делением. |
Для обеспечения высокой скорости работы в математических вычислениях модуль предоставляет встроенные математические функции, которые вызываются на уровне команд виртуальной машины. Встроенные математические функции:
Языком поддерживаются два типа условных операторов. Первый - это условный оператор для использования внутри выражения, второй - глобальный.
Условный оператор для использования внутри выражения строится на операциях «?» и «:». В качестве примера можно записать следующее практическое выражение <st_open=(pos>=100)?true:false;>, что читается, как «Если переменная <pos> больше или равна 100, то переменной <st_open> присваивается значение "true", иначе "false".
Глобальное условие строится на основе ключевых слов "if" и "else". В качестве примера можно привести то же выражение, но другим способом <if(pos>100) st_open=true; else st_open=false;>. Как видно, выражение записано по-другому, но читается также.
Приведём несколько примеров программ на разработанном Java-подобном языке:
//Модель хода исполнительного механизма шарового крана
if( !(st_close && !com) && !(st_open && com) )
{
tmp_up=(pos>0&&pos<100)?0:(tmp_up>0&&lst_com==com)?tmp_up-1./frq:t_up;
pos+=(tmp_up>0)?0:(100.*(com?1.:-1.))/(t_full*frq);
pos=(pos>100)?100:(pos<0)?0:pos;
st_open=(pos>=100)?true:false;
st_close=(pos<=0)?true:false;
lst_com=com;
}
//Модель клапана
Qr=Q0+Q0*Kpr*(Pi-1)+0.01;
Sr=(S_kl1*l_kl1+S_kl2*l_kl2)/100.;
Ftmp=(Pi>2.*Po)?Pi*pow(Q0*0.75/Ti,0.5):(Po>2.*Pi)?Po*pow(Q0*0.75/To,0.5):pow(abs(Q0*(pow(Pi,2)-pow(Po,2))/Ti),0.5);
Fi-=(Fi-7260.*Sr*sign(Pi-Po)*Ftmp)/(0.01*lo*frq);
Po+=0.27*(Fi-Fo)/(So*lo*Q0*frq);
Po=(Po<0)?0:(Po>100)?100:Po;
To+=(abs(Fi)*(Ti*pow(Po/Pi,0.02)-To)+(Fwind+1)*(Twind-To)/Riz)/(Ct*So*lo*Qr*frq);
Результатом работы синтаксического анализатора вместе с лексическим может быть или непосредственно интерпретация(выполнение программы), или генерация кода внутренней виртуальной машины.
Интерпретация не требует дополнительных усилий на разработку виртуальной машины и является хорошим решением в случае не сильно требовательных вычислений.
Виртуальная машина позволяет реализовывать высоко производительные вычисления за счёт оптимизации кода и исключения лишнего анализа лексического и синтаксического анализаторов, однако требует значительных усилий на её разработку.
Поскольку в условиях разработки есть пункт, требующий обеспечения высокопроизводительных и надёжных вычислений, то остановимся на разработке виртуальной машины.
Существует множество способов построения виртуальной машины, из них можно отметить:
Стековые машины являются удобным способом построения виртуальной машины. Особенностью стековой виртуальной машины является:
Недостатком стековых машин является то, что стек является динамической структурой, на работу с которой затрачивается относительно много времени, кроме того, для реализации памяти на статические даные отдельного вычислительного сеанса нужно предусматривать отдельную структуру данных.
Другим классом виртуальных машин являются виртуальные машины, основанные на так называемых четверках. Суть четвёрок состоит в том, что команда состоит из: <код> <результат> <операнд1> <операнд2>
Где результат и операнды являются ссылками на регистры памяти или структуры. Код виртуальной машины такого класса получается несколько больше, но структура памяти является статичной и может совмещать функции хранения статических и динамических данных вычисления. Поэтому, для построения виртуальной машины выберем схему, основанную на четверках.
Все даные нашей виртуальной машины будем размещать в регистрах. Регистр виртуальной машины должен представлять структуру, которая может сохранять следующие данные:
Команды построенной виртуальной машины приведено в приложении A. Механизм работы виртуальной машины будет следующим:
Синтаксический анализатор выделяет команды и генерирует код в семантических процедурах Bison. Во время генерации кода виртуальной машины формируется перечень регистров. Регистры, которые содержат ссылки на параметры данной функции, атрибуты параметров подсистемы "DAQ" и внутренние переменные, будут инициализироваться во время генерации кода и больше не использоваться на другие цели. Другие регистры (динамические даные промежуточных вычислений) должны резервироваться и использоваться на временные цели. Такие регистры инициируются конкретными значениями во время выполнения программы в виртуальной машине. Полученная конфигурация регистров (структура памяти) и код программы виртуальной машины используются в режиме вычисления виртуальной машины. При этом конфигурация регистров не меняется, что позволяет использовать быстрые механизмы для доступа к регистрам и первичной инициализации кадра регистров.
Для адресации регистров использован один байт, что обеспечивает поддержку до 255 параметров функции (в сумме с внутренними переменными).
Языки блочного программирования основываются на понятии блочных схем. При чем, в зависимости от сущности блока блочные схемы могут быть: логическими схемами, схемами релейной логики, модель технологического процесса и другое. Суть блочной схемы состоит в том, что она содержит список блоков и связи между ними.
С формальной точки зрения блок - это элемент, который имеет входы, выходы и алгоритм вычисления. Исходя из концепции среды программирования и его основы (рис. 2), блок - это кадр значений параметров, ассоциированный с объектом функции.
Разумеется входы и выходы блоков нужно соединять для получения цельной блочной схемы. Предусмотрим следующие типы связей:
Условно соединения блоков можно изобразить как связи между блоками в целом (рис. 3) или детализация связей (рис. 4). В процессе связывания параметров блоков не будем придерживаться строгого соответствия типам параметров. Это означает, что параметры разных типов смогут легко связываться один с другим. Причем в процессе вычисления будет выполняться автоматическое приведение типов.
Поскольку блочный вычислитель основан на объектах функций объектной модели системы OpenSCADA, то и типы параметров блоков будут такими же, то есть: целое, вещественное, логичное и строка.
Исходя из модульной архитектуры системы OpenSCADA и размышлений, приведенных в разделах выше, была разработана архитектура среды программирования (рис.5).
Как можно видеть, архитектура среды программирования состоит из трёх частей:
Такая архитектура среды программирования позволяет распределить процесс создания и использования. То есть, алгоритмы вычислений в виде объектов функций предоставляются одними компонентами, а используются для вычислений другими.
При такой схеме должна существовать прослойка, которая объединяет компоненты, предоставляющие алгоритмы с компонентами, которые их используют, особенно это важно в свете того, что эти компоненты могут быть отделены от системы, то есть являться модулями. Этим слоем и должен выступать API объектной модели системы OpenSCADA.
Исходя из структуры среды программирования, создадим классы объектов. Статическую диаграмму классов с отделением каждого компонента среды программирования приведено на рис.6. Описание классов приведено в таблице 5.
Таблица 5. Классы среды программирования
Класс | Ответственность | Связи |
TFunction | Класс функции. Содержит описание параметров (IO). В предке должен содержать реализацию алгоритма функции. | Используется вычислителями для связывания с кадром значений. Является абстрактным, наследуется компонентами, которые предоставляют собственные библиотеки функций. |
TValFunc | Класс значений функции. Содержит значения функции в соответствии с составом параметров (IO) класса TFunction. | Агрегируется с объектом функции TFunction для совместных вычислений. Может наследоваться классами вычислителя. Экземпляр класса TValFunc передаётся классу TFunction во время вычисления для выполнения алгоритма в TFunction над значениями в TValFunc. |
IO | Класс параметра функции. Содержит описание параметра, его тип и атрибуты. | Используется классом значений TValFunc для определения значений параметров. |
UserLib | Класс библиотеки пользователя. | Может предоставлять инструмент создания функций пользователя. |
UserFunc | Класс функции пользователя. Предоставляет параметры функции и алгоритм вычисления. | Наследует класс функции TFunction. |
Block | Пользовательский класс вычислителя. Содержит ассоциированный с функцией TFunction кадр значений TValFunc. Вызывает процесс вычисления | Наследует класс кадра значений. |
Исходя из структуры среды программирования (рис.5) и её диаграммы классов (рис.6), создадим классы вычислителя на Java-подобном языке (рис.7). Описание классов приведено в таблице 6.
Таблица 6. Классы модуля JavaLikeCalc
Для непосредственного вычисления нужно обеспечить создание и связывание контроллера с функцией этого же модуля. Для связывания с функцией в контроллере создаётся кадр значений TValFunc, над которым и производятся периодические вычисления.
Для экспорта полученных значений из контроллера в систему OpenSCADA и для импорта значений из системы в контроллер нужно использовать параметры контроллера подсистемы "DAQ". Параметры контроллера связываются с параметром вычислительной функции (полем таблицы данных) и должны выполнять отражение значений.
Исходя из структуры среды программирования (рис.5) и её диаграммы классов (рис.6), создадим классы вычислителя на языке блоков (рис.8). Описание классов приведено в таблице 7.
Таблица 6. Классы модуля BlockCalc
Класс | Ответственность | Связи |
TipContr | Коренной класс модуля, главным назначением которого является точка входа в модуль. | Наследует класс интерфейса модулей подсистемы «Источников данных» TTypeDAQ для интеграции в систему OpenSCADA. |
Contr | Класс реализации контроллера. Содержит механизм периодических вычислений над алгоритмом блочной схемы. | Наследует класс интерфейса контроллера TController. Содержит блоки блочной схемы. |
Prm | Класс реализации параметра подсистемы "DAQ". Содержит механизм отражения параметров блоков блочной схемы на структуру параметра подсистемы "DAQ" системы OpenSCADA. | Наследует класс интерфейса параметра TParamСontr. Содержит ссылки на параметр блока блочной схемы. |
Block | Класс блока. Содержит кадр значений в соответствии с ассоциированной функцией. Содержит механизм связей. | Наследует класс интерфейса кадра значений TValFunc. На него ссылается объект класса параметра (Prm). Экземпляры класса этого типа могут содержать связи один на другого. |
Каждый контроллер этого модуля содержит блочную схему, которую он должен вычислять в соответствии с указанной периодичностью.
Сами блоки при этом не содержат структуры входов/выходов(IO), а только содержат значения, исходя из структуры параметров связанной функции. Для соединения с блоком могут использоваться любые функции объектной модели (ОМ) системы OpenSCADA.
Для предоставления возможности экспортирования значений из блочной схемы в систему OpenSCADA предусмотрим возможность отображения атрибутов блоков на параметры контролера системы OpenSCADA. Таким же образом значения из системы OpenSCADA могут попасть в блочную схему контроллера.
Исходя из структуры среды программирования (рис.5) и её диаграммы классов (рис.6), создадим архитектуру классов для библиотек статических функций (рис.9). Описание классов приведено в таблице 8.
Таблица 7. Классы статической библиотеки функций
Класс | Ответственность | Связи |
Lib | Коренной класс модуля, главным назначением которого является точка доступа в модуль. Выполняет функцию библиотеки, а значит содержит объекты статических функций. | Наследует класс интерфейса модулей подсистемы «Специальные» TSpecial для интеграции в систему OpenSCADA. Содержит объекты функций. |
Func | Класc функции. Содержит структуру параметров и алгоритм их вычисления. | Наследует класс интерфейса функции TFunction. |
На основе данной архитектуры построены следующие статические библиотеки функций:
Таблица 8. Функции библиотеки Complex1
Id | Имя | Описание функции. Формулы вычислений функций |
alarm | Сигнал | Сигнал по шкале параметра:
out = if(val>max || val<min) then true; else false;
|
cond < | Условие '<' | Условие '<' по формуле:
out=if(in1<(in2_1*in2_2*in2_3*in2_4)) |
cond > | Условие '>' | Условие '>' по формуле:
out=if(in1>(in2_1*in2_2*in2_3*in2_4)) |
cond_full | Полное условие | Полное условие по формуле:
out = if(in1<(in2_1*in2_2*in2_3*in2_4)) |
digitBlock | Дискретный блок | Сборка дискретных сигналов. |
div | Делитель | Делитель по формуле:
out = (in1_1*in1_2*in1_3*in1_4*in1_5 + in2_1*in2_2*in2_3*in2_4*in2_5 + in3) / |
exp | Экспонента | Экспонента по формуле:
out=exp (in1_1*in1_2*in1_3*in1_4*in1_5 + (in2_1*in2_2*in2_3*in2_4*in2_5+in3) / |
flow | Расход | Расчёт расхода по формуле:
f = K1*((K3+K4*x)^K2);
|
increment | Итератор | Итератор по формуле:
out = if( in1 > in2 ) then in2 + in3*(in1-in2); |
lag | Задержка | Задержка по формуле:
y = y - Klag*( y - x );
|
mult | Простое умножение | Простое умножение по формуле:
out=(in1_1*in1_2*in1_3*in1_4*in1_5*in1_6)/ |
multDiv | Умножение+деление | Умножение+деление по формуле:
out=in1_1*in1_2*in1_3*in1_4*in1_5* |
pid | ПИД регулятор | ПИД регулятор |
pow | Степень | Степень по формуле:
out=(in1_1*in1_2*in1_3*in1_4*in1_5)^ |
select | Выбор | Вибор по формуле:
out = if( sel = 1 ) then in1_1*in1_2*in1_3*in1_4; |
sum | Простой сумматор | Простой сумматор по формуле:
out=in1_1*in1_2+in2_1*in2_2+in3_1*in3_2+in4_1*in4_2 |
sum_div | Сумма с делением | Сумма с делением по формуле:
out = in1_1*in1_2*(in1_3+in1_4/in1_5) + |
sum_mult | Сумма с умножением | Сумма с умножением по формуле:
out = in1_1*in1_2*(in1_3*in1_4+in1_5) + |
Таблица 9. Стандартные математические функции
Id | Имя | Описание |
abs | Модуль | Мат. функция - модуль от числа. |
acos | Арккосинус | Мат. функция - арккосинус. |
asin | Арксинус | Мат. функция - арксинус. |
atan | Арктангенс | Мат. функция - арктангенс. |
ceil | Округл. до большего | Мат. функция - округления до большего целого. |
cos | Косинус | Мат. функция - косинус. |
cosh | Косинус гиперболический | Мат. функция - косинус гиперболический. |
exp | Экспонента | Мат. функция - экспонента. |
floor | Округл. до меньшего | Мат. функция - округления до меньшего целого |
if | Условие Если | Функция - условие "Если". |
lg | Десятичный логарифм | Мат. функция - десятичный логарифм. |
ln | Натуральный логарифм | Мат. функция - натуральный лагорифм. |
pow | Степень | Мат. функция - возведение в степень. |
rand | Случ. число | Мат. функция - генератор случайных чисел. |
sin | Синус | Мат. функция - синус. |
sinh | Синус гиперболический | Мат. функция - синус гипербалический. |
sqrt | Корень квадратный | Мат. функция - корень квадратный. |
tan | Тангенс | Мат. функция - тангенс. |
tanh | Тангенс гиперболический | Мат. функция - тангенс гиперболический. |
Таблица 10. Функции времени
Id | Имя | Описание |
date | Полная дата | Возвращает полную дату в составе: секунда, минута, час, день месяца, месяц, год, день недели, день в году. |
time | Полное время (с 01.01.1970) | Возвращает полное время в виде числа секунд от начала эпохи |
ctime | Полное время в виде строки | Возвращает строку полного времени вида "Wed Jun 30 21:49:08 1993". |
Начальную реализацию среды программирования в коде было выполнено с помощью полученной UML-модели путём генерации C++ кода в программе Umbrello. В результате был разработан интерфейс объектной модели (API) и компоненты в виде модулей к системе OpenSCADA:
Общую структуру всех этих компонентов и API приведено на рис.10.
Таблица. Команды внутренней виртуальной машины модуля “JavaLikeCalc”
Команда | Код | Описание |
End | 01 | Окончание программы или условной команды. |
MviB | 02 R V | Загрузка логического признака [V] в регистр [R]. Производится инициализация регистра. |
MviI | 03 R V V V V | Загрузка целого числа [V V V V] в регистр [R]. Производится инициализация регистра. |
MviR | 04 R V V V V V V | Загрузка вещественного числа [V V V V V V] в регистр [R]. Производится инициализация регистра. |
MviS | 05 R N S . . | Загрузка строки [S . .] длиной [N] в регистр [R]. Производится инициализация регистра. |
AssB | 06 R0 R1 | Присвоение логического признака из регистра [R1] регистру [R0]. |
AssI | 07 R0 R1 | Присвоение целого из регистра [R1] регистру [R0]. |
AssR | 08 R0 R1 | Присвоение вещественного из регистра [R1] регистру [R0]. |
AssS | 09 R0 R1 | Присвоение строки из регистра [R1] регистру [R0]. |
MovB | 0A R0 R1 | Перемещение логического признака из регистра [R1] в регистр [R0]. Производится инициализация регистра. |
MovI | 0B R0 R1 | Перемещение целого из регистра [R1] в регистр [R0]. Производится инициализация регистра. |
MovR | 0C R0 R1 | Перемещение вещественного из регистра [R1] в регистр [R0]. Производится инициализация регистра. |
MovS | 0D R0 R1 | Перемещение строки из регистра [R1] в регистр [R0]. Производится инициализация регистра. |
AddI | OE R0 R1 R2 | Сложение целых: R0 = R1 + R2. |
AddR | OF R0 R1 R2 | Сложение вещественных: R0 = R1 + R2. |
AddS | 1O R0 R1 R2 | Сложение строк: R0 = R1 + R2. |
SubI | 11 R0 R1 R2 | Вычитание целых: R0 = R1 - R2. |
SubR | 12 R0 R1 R2 | Вычитание вещественных: R0 = R1 - R2. |
MultI | 13 R0 R1 R2 | Перемножение целых: R0 = R1 * R2. |
MultR | 14 R0 R1 R2 | Перемножение вещественных: R0 = R1 * R2. |
DivI | 15 R0 R1 R2 | Деление целых: R0 = R1 / R2. |
DivR | 16 R0 R1 R2 | Деление вещественных: R0 = R1 / R2. |
RstI | 17 R0 R1 R2 | Остаток от целочисленного деления: R0 = R1 % R2. |
BitOr | 18 R0 R1 R2 | Побитовое “ИЛИ”: R0 = R1 | R2. |
BitAnd | 19 R0 R1 R2 | Побитовое “И”: R0 = R1 & R2. |
BitXor | 1A R0 R1 R2 | Побитовое исключающее “ИЛИ”: R0 = R1 ^ R2. |
LOr | 1B R0 R1 R2 | Логическое “ИЛИ”: R0 = R1 || R2. |
LAnd | 1C R0 R1 R2 | Логическое “И”: R0 = R1 && R2. |
LTI | 1D R0 R1 R2 | Целое меньше: R0 = R1 < R2. |
LTR | 1E R0 R1 R2 | Вещественое меньше: R0 = R1 < R2. |
GTI | 1F R0 R1 R2 | Целое больше: R0 = R1 > R2. |
GTR | 20 R0 R1 R2 | Вещественное больше: R0 = R1 > R2. |
LEI | 21 R0 R1 R2 | Целое меньше или равно: R0 = R1 <= R2. |
LER | 22 R0 R1 R2 | Вещественное меньше или равно: R0 = R1 <= R2. |
GEI | 23 R0 R1 R2 | Целое больше или равно: R0 = R1 >= R2. |
GER | 24 R0 R1 R2 | Вещественное больше или равно: R0 = R1 >= R2. |
EQI | 25 R0 R1 R2 | Целое равно: R0 = R1 == R2. |
EQR | 26 R0 R1 R2 | Вещественное равно: R0 = R1 == R2. |
EQS | 27 R0 R1 R2 | Строка равно: R0 = R1 == R2. |
NEI | 28 R0 R1 R2 | Целое не равно: R0 = R1 != R2. |
NER | 29 R0 R1 R2 | Вещественное не равно: R0 = R1 != R2. |
NES | 2A R0 R1 R2 | Строка не рано: R0 = R1 != R2. |
Not | 2B R0 R1 | Отрицание: R0 = !R1. |
BitNot | 2С R0 R1 | Побитовая инверсия: R0 = ~R1. |
NegI | 2D R0 R1 | Инверсия целого: R0 = -R1. |
NegR | 2E R0 R1 | Инверсия вещественного: R0 = -R1. |
If | 2F R E E N N | Условие, если [R] действительно, то исполняются команды после этой операции и по завершению выполняется переход по относительному адресу [N N]; иначе выполняются команды по относительному адресу [EE] и по завершению выполняется переход по относительному адресу [N N]. |
FSin | 30 R0 R1 | Функция синус: R0 = sin(R1). |
FCos | 31 R0 R1 | Функция косинус: R0 = cos(R1). |
FTan | 32 R0 R1 | Функция тангенс: R0 = tan(R1). |
FSinh | 33 R0 R1 | Функция гиперболический синус: R0 = sinh(R1). |
FCosh | 34 R0 R1 | Функция гиперболический косинус: R0 = cosh(R1). |
FTanh | 35 R0 R1 | Функция гиперболический тангенс: R0 = tanh(R1). |
FAsin | 36 R0 R1 | Функция арксинус: R0 = asin(R1). |
FAcos | 37 R0 R1 | Функция арккосинус: R0 = acos(R1). |
FAtan | 38 R0 R1 | Функция арктангенс: R0 = atan(R1). |
FRand | 39 R0 R1 | Случайное число: R0 = rand(R1). |
FLg | 3A R0 R1 | Десятичный логарифм: R0 = lg(R1). |
FLn | 3B R0 R1 | Натуральный логарифм: R0 = ln(R1). |
FExp | 3С R0 R1 | Экспонента: R0 = exp(R1). |
FPow | 3D R0 R1 R2 | Возведение в степень: R0 = pow(R1,R2). |
FSqrt | 3E R0 R1 | Корень квадратный: R0 = sqrt(R1). |
FAbs | 3F R0 R1 | Абсолютное значение: R0 = |R1|. |
FSign | 40 R0 R1 | Знак: R0 = sign(R1). |
FCeil | 41 R0 R1 | Округление до большего: R0 = ceil(R1). |
FFloor | 42 R0 R1 | Округление до меньшего: R0 = floor(R1). |
CProc | 43 F N R P P . . | Процедурный вызов внешней функции [F], с параметрами [P P . .], в количестве [N]. [R] - не используется. |
CFunc | 44 F N R P P . . | Вызов внешней функции [F], с параметрами [P P . .], в количестве [N]. Результат функции помещается в [R]. |