Модуль | Имя | Версия | Лицензия | Источник | Языки | Платформы | Тип | Автор | Описание |
---|---|---|---|---|---|---|---|---|---|
JavaLikeCalc | Вычислитель на Java-подобном языке. | 5.1 | GPL2 | daq_JavaLikeCalc.so | en,uk,ru,de | x86,x86_64,ARM | DAQ | Роман Савоченко | Предоставляет вычислитель и движок библиотек, основанные на Java-подобном языке. Пользователь может создавать и модифицировать функции и их библиотеки. |
Модуль источника данных предоставляет в OpenSCADA механизм создания функций и их библиотек на Java-подобном языке. Описание функции на Java-подобном языке сводится к обвязке параметров функции алгоритмом. Кроме этого модуль наделён функциями непосредственных вычислений путём создания объектов вычислительных контроллеров.
Непосредственные вычисления обеспечиваются созданием объекта контроллера и связыванием его с функцией этого же модуля. Для связанной функции создаётся кадр значений (контекст), над которым и выполняются периодические вычисления.
Модулем реализуются функции горизонтального резервирования, а именно — совместная работа с удалённой станцией этого-же уровня. Кроме синхронизации значений и архивов атрибутов параметров, модулем осуществляется синхронизация значений вычислительной функции, с целью безударного "подхвата" алгоритмов.
Параметры функции могут свободно создаваться, удаляться или модифицироваться. Текущая версия модуля поддерживает до 65535 параметров функции, в сумме с внутренними переменными. Вид редактирования функций в конфигураторе OpenSCADA показан на рисунке 1.
После любого изменения текста программы или конфигурации параметров, выполняется перекомпиляция программы с уведомлением объектов значений TValCfg, связанных с функцией. Компилятор языка построен с использованием известного генератора грамматики "Bison", который совместим с не менее известной утилитой "Yacc".
Язык использует неявное определение локальных переменных, которое заключается в определении новой переменной в случае присваивания ей значения. Причём тип локальной переменной устанавливается в соответствии с типом присваиваемого значения. Например, выражение Qr=Q0*Pi+0.01; определит переменную Qr с типом переменной Q0.
В работе с различными типами данных язык использует механизм автоматического приведения типов в местах, где подобное приведение является целесообразным.
Для комментирования участков кода в языке предусмотрены символы "//" и "/* ... */". Всё, что идёт после "//", до конца строки, и между "/* ... */", игнорируется компилятором.
В процессе генерации кода, компилятор языка производит оптимизацию по константам и приведение типов констант к требуемому типу. Под оптимизацией констант подразумевается вычисление двух констант и вставка результата в код, в процессе построения байт-кода. Например, выражение y=pi*10; свернётся в простое присваивание y=31.4159;. Под приведением типов констант к требуемому типу подразумевается формирования константы в коде, которая исключает приведение типа в процессе исполнения. Например, выражение y=x*"10";, в случае вещественного типа переменной x, преобразуется в y=x*10;.
Выражения присваивания могут записываться через символ ',', например:
var1 = 1, var2 = 3, var4 = var1+var2; for(var1 = 0, var2 = 0, var3 = -1; var1 < 10; var1++, var2++) var3++;
Язык поддерживает вызовы внешних и внутренних функций. Имя любой функции, вообще, воспринимается как символ, проверка которого на принадлежность к той или иной категории производится в следующем порядке:
Вызов внешней функции, как и глобального атрибута параметра DAQ, записывается в виде адреса к узлу объектной модели OpenSCADA: "DAQ.JavaLikeCalc.lib_techApp.klapNotLin". Для статических функций Вы можете следующим образом осуществлять динамическое подключение:
function klapNotLin = "DAQ.JavaLikeCalc.lib_techApp.klapNotLin"; rez = klapNotLin(prm1, prm2, ..., prmN);
Для предоставления возможности написания пользовательских процедур управления различными компонентами OpenSCADA, этим модулем предоставляется реализация API прекомпиляции пользовательских процедур отдельных компонентов OpenSCADA на Java-подобном языке. Такими компонентами, например, являются: шаблоны параметров подсистемы "Сбор данных" и среда визуализации и управления (СВУ).
Ключевые слова: if, else, while, for, break, continue, return, function, using, true, false.
Постоянные:
Типы переменных:
Встроенные константы: pi = 3.14159265..., e = 2.71828182..., EVAL_BOOL(2), EVAL_INT(-9223372036854775807), EVAL_REAL,EVAL(-1.79E308), EVAL_STR("<EVAL>")
Глобальные атрибуты параметров DAQ (начиная с подсистемы "DAQ" и в виде {Тип модуля DAQ}.{Объект контроллера}.{Параметр}.{Атрибут}).
Функции и параметры объектной модели OpenSCADA.
Операции, поддерживаемые языком, представлены в таблице ниже. Приоритет операций уменьшается сверху вниз. Операции с одинаковым приоритетом входят в одну цветовую группу.
Символ | Описание |
() | Вызов функции. |
{} | Программные блоки. |
++ | Инкремент (пост и пре). |
-- | Декремент (пост и пре). |
- | Унарный минус. |
! | Логическое отрицание. |
~ | Побитовое отрицание. |
* | Умножение. |
/ | Деление. |
% | Остаток от целочисленного деления. |
+ | Сложение |
- | Вычитание |
<< | Поразрядный сдвиг влево |
>> | Поразрядный сдвиг вправо |
> | Больше |
>= | Больше или равно |
< | Меньше |
<= | Меньше или равно |
== | Равно |
!= | Не равно |
| | Поразрядное "ИЛИ" |
& | Поразрядное "И" |
^ | Поразрядное "Исключающее ИЛИ" |
&& | Логический "И" |
|| | Логический "ИЛИ" |
?: | Условная операция "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;
Поддерживаются три типа циклов: while, for и for-in. Синтаксис циклов соответствует языкам программирования: C++, Java и JavaScript.
Цикл while, в общем, записывается следующим образом: while({условие}) {тело цикла};
Цикл for записывается следующим образом: for({пре-инициализ};{условие};{пост-вычисление}) {тело цикла};
Цикл for-in записывается следующим образом: for({переменная} in {объект}) {тело цикла};
Где:
Язык поддерживает определение и вызов внутренних функций. Для определения внутренней функции используется ключевое слово "function" и в целом определение имеет синтаксис: function {имяФ} ({пер1}, {пер2}, ... {перN}) { {тело функции} }. Определение внутренней функции внутри другой недопустимо однако допустим вызов ранее определённой.
Вызов внутренней функции осуществляется в типовой способ, как процедура {имяФ}({var1}, {var2}, ... {varN}); или как функция {перРез} = {имяФ}({пер1}, {пер2}, ... {перN});. Вызов внутренних функций допустим только после их декларации выше!
Все переменные, определённые в основном теле, недоступны в середине внутренних функций и могут быть переданы через двухсторонние аргументы вызываемой внутренней функции или через аргументы основной функции. Все переменные, определённые в середине внутренней функции, имеют собственную область имён и недоступны из основного тела, или любой другой внутренней функции, и могут быть переданы в основное тело через двухсторонние аргументы, результат вызываемой внутренней функции или через аргументы основной функции. Переменные внутренней функции регистрируются для сохранения/восстановления их контекста после второго и более входа в функцию, т.е. они целиком поддерживают рекурсивные вызовы!
Оператор "return", в середине внутренней функции, осуществляет контролируемое её завершение и помещение указанной переменной, или результата выражения, как результат вызываемой внутренней функции.
Пример типового определения и использования внутренней функции представлен далее:
function sum(a, b, c, d) { return a + ((b==EVAL)?0:b) + ((c==EVAL)?0:c) + ((d==EVAL)?0:d); } rez = sum(1, 2);
Языком предусмотрена поддержка следующих специальных символов строковых переменных:
JavaLikeCalc предоставляет поддержку типа данных объект "Object". Объект представляет собой ассоциативный контейнер свойств и функций. Свойства могут содержать как данные четырёх базовых типов, так и другие объекты. Доступ к свойствам объекта может осуществляться посредством записи имён свойств к объекту obj.prop, через точку, а также посредством заключения имени свойства в квадратные скобки obj["prop"]. Очевидно, что первый механизм статичен, а второй позволяет указывать имя свойства через переменную. Удалить свойство объекта можно директивой "delete". Имя свойства через точку не должно начинаться с цифры и содержать символы операций, иначе, для первой цифры, должен использоваться префикс объекта — SYS.BD.SQLite.db_1s, или осуществляться запись в квадратных скобках — SYS.BD.SQLite["1+s"], для символов операций в названии. Чтение неопределённого свойства вернёт EVAL. Создание объекта осуществляется посредством ключевого слова new: varO = new Object(). Базовое определение объекта не содержит функций. Операции копирования объекта, на самом деле, делают ссылку на исходный объект. При удалении объекта осуществляется уменьшение счётчика ссылок, а при достижении счётчика нуля, объект удаляется физически.
Разные компоненты OpenSCADA могут доопределять базовый объект особыми свойствами и функциями. Стандартным расширением объекта является массив "Array", который создаётся командой varO = new Array(prm1,prm2,prm3,...,prmN). Перечисленные через запятую параметры помещаются в массив в исходном порядке. Если параметр только один то массив инициируется указанным количеством пустых элементов. Особенностью массива является то, что он работает со свойствами, как с индексами и основным механизмом обращения является заключение индекса в квадратные скобки arr[1]. Массив хранит свойства в собственном контейнере одномерного массива. Цифровые свойства массива используются для доступа непосредственно к массиву, а символьные работают как свойства объекта. Детальнее про свойства и функции массива можно прочитать по ссылке.
Объект регулярного выражения "RegExp" создаётся командой varO = new RegExp(pat, flg), где pat — шаблон регулярного выражения, а flg — флаги поиска. Объект работы с регулярными выражениями основан на библиотеке "PCRE". При глобальном поиске устанавливается атрибут объекта "lastIndex", что позволяет продолжить поиск при следующем вызове функции. В случае неудачного поиска атрибут "lastIndex" сбрасывается в ноль. Детальнее про свойства и функции объекта регулярного выражения можно прочитать по ссылке.
Для произвольного доступа к аргументам функции предусмотрен объект аргументов, обратиться к которому можно посредством символа "arguments". Этот объект содержит свойство "length" с количеством аргументов у функции и позволяет обратиться к значению аргумента посредством его номера или идентификатора. Рассмотрим перебор аргументов по циклу:
args = new Array(); for(var i = 0; i < arguments.length; i++) args[i] = arguments[i];
Частичными свойствами объекта обладают и базовые типы. Свойства и функции базовых типов приведены ниже:
var rez = "Java123Script".search("script","i"); // rez = 7
var rez = "Java123Script".search(new RegExp("script","i")); // rez = 7
var rez = "1 плюс 2 плюс 3".match("\\d+","g"); // rez = [1], [2], [3]
var rez = "1 плюс 2 плюс 3".match(new RegExp("\\d+","g")); // rez = [1], [2], [3]
rez = "1,2, 3 , 4 ,5".split(new RegExp("\\s*,\\s*")); // rez = [1], [2], [3], [4], [5]
rez = "Javascript".replace(4,3,"67"); // rez = "Java67ipt"
rez = "123 321".replace("3","55"); // rez = "1255 5521"
rez = "value = \"123\"".replace(new RegExp("\"([^\"]*)\"","g"),"``$1''")); // rez = "value = ``123''"
Для доступа к системным объектам(узлам) OpenSCADA предусмотрен соответствующий объект, который создаётся путём простого указания точки входа "SYS" корневого объекта OpenSCADA, а затем, через точку, указываются вложенные объекты, в соответствии с иерархией. Например, вызов функции запроса через исходящий транспорт осуществляется следующим образом: SYS.Transport.Sockets.out_testModBus.messIO(Special.FLibSYS.strEnc2Bin("15 01 00 00 00 06 01 03 00 00 00 05"));.
Приведём несколько примеров программ на 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);
Объект контроллера этого модуля, для обеспечения непосредственных вычислений, связывается с функциями из библиотек, построенных с его помощью, или с шаблоном сбора данных. В случае с шаблоном сбора данных, добавляется возможность внешнего связывания, с другими параметрами и атрибутами подсистемы "Сбор данных". Для предоставления вычисленных данных в OpenSCADA, в объекте контроллера могут создаваться параметры. Пример вкладки конфигурации объекта контроллера данного типа изображен на рисунке 2.
С помощью этой вкладки можно установить:
Вкладка "Вычисления" объекта контроллера (Рис. 3) содержит параметры и текст программы, непосредственно выполняемой контроллером. Модулем предусмотрена обработка ряда специальных параметров, доступных в программе контроллера:
Параметр объекта контроллера данного модуля выполняет функцию предоставления доступа к результатам вычисления контроллера в OpenSCADA, посредством атрибутов параметров. Из специфических полей, вкладка конфигурации параметра контроллера содержит только поле перечисления параметров вычисляемой функции, которые необходимо отразить.
Модуль предоставляет механизм для создания библиотек пользовательских функций на Java-подобном языке. Пример вкладки конфигурации библиотеки изображен на рисунке 4. Вкладка содержит базовые поля: доступность, адрес таблицы БД библиотеки, дата и время модификации, идентификатор, имя и описание.
Функция, также как и библиотека, содержит базовую вкладку конфигурации, вкладку формирования программы и параметров функции (Рис.1), а также вкладку исполнения созданной функции.
Некоторые объекты модуля предоставляют функции пользовательского программирования.
Объект "Библиотека функций" (SYS.DAQ.JavaLikeCalc["lib_Lfunc"])
Объект "Пользовательская функция" (SYS.DAQ.JavaLikeCalc["lib_Lfunc"]["func"])
Исходный текст процедур на языке этого модуля компилируются в байт-код, который впоследствии вычисляется виртуальной машиной. Байт-код это не машинный код и достичь производительности самой аппаратной архитектуры в виртуальной машине его исполняющего — теоретически нереально, если конечно код этой виртуальной машины не исполняет сам процессор. Т.е. производительность выполнения байт-кода примерно на порядок ниже аппаратной производительности за счёт накладных расходов команд виртуальной машины, разделения многопоточного доступа к данным, прозрачного приведения типов и отсутствия жёсткой типизации, а так-же динамической природы языка и наличия сложных типов "Строка" и "Объект".
28.01.2006:
Description: Initial estimation of the productivity of the OpenSCADA virtual machine in example of the expression y=x1+x2, where all the variables are global and in the float-point type.
Stage | Action | K7_1G-0, us |
---|---|---|
1 | The registers list initialization | 2.3 |
2 | Entry to the function exec() | 3 |
3 | The commands coming | 4.4 |
4 | Reading | 9 |
5 | Full time | 10.2 |
17.07.2013:
Description: Justification of the current performance evaluation and optimization. The measurements were made by sampling the minimum time from five calls to 1000 executions of the formula a -= b*(a-c) and its abbreviations in each call. All the variables are global and in the float-point type.
Formula | Time on AMDGeode-500 (the operation time), us | Notes |
---|---|---|
a -= b*(a-c) | 4.52 (0.74) | |
a -= b*c | 3.78 (0.72) | |
a -= b | 3.06 (0.56)
|
|
a = b | 2.5 (1.21)
|
Writing to the function IO is longer then reading from the local register for other context call and additional checking for NAN and real modification. |
Empty | 1.29 | the infrastructure and measurement method utilization time. |
24.04.2016:
Reason: Estimate performance of accessing to the low level IO lines on Raspberry Pi GPIO in different ways of the JavaLikeCalc language of OpenSCADA.
Conditions: Raspberry Pi 3, GPIO40, DAQ.GPIO (based on the library bcm2835)
Operation | Result, us |
---|---|
Sleep. Lag on sleep in 1ms measuring, which mostly limited by the realtime reaction about 100us. | |
SYS.sleep(); | +110 |
Special.FLibSYS.fnc_tmSleep(); | +70 |
Sleep. Lag on sleep in 100us measuring, which performs in the measuring cycle. | |
SYS.sleep(); | +17 |
Special.FLibSYS.fnc_tmSleep(); | +2 |
Get a level of the GPIO pin | |
From an attribute res = GPIO.io.pi.gpio17; | 5.4 |
By the static accessing function res = DAQ.GPIO.io.pi.fnc_get(17); | 1.6 |
By the static accessing function with the link preparation function get = "DAQ.GPIO.io.pi.fnc_get"; for(i = 0; i < 10000; i++) res = get(17); | 1.7 |
By the dynamic accessing function res = SYS.DAQ.GPIO.io.pi.fnc_get.call(17); | 80 |
By the dynamic accessing function with the end object preparation tO = SYS.DAQ.GPIO.io.pi.fnc_get; for(i = 0; i < 1000; i++) res = tO.call(17); | 14.3 |
Put a level to the GPIO pin | |
To an attribute GPIO.io.pi.gpio18 = true; | 2.1 |
By the static accessing function DAQ.GPIO.io.pi.fnc_put(18, true); | 1.4 |
By the static accessing function with the link preparation function put = "DAQ.GPIO.io.pi.fnc_put"; for(i = 0; i < 10000; i++) put(17, false); | 1.5 |
By the dynamic accessing function SYS.DAQ.GPIO.io.pi.fnc_put.call(18, true); | 79 |
By the dynamic accessing function with the end object preparation tO = SYS.DAQ.GPIO.io.pi.fnc_put; for(i = 0; i < 1000; i++) tO.call(18, true); | 14.3 |
Modules/JavaLikeCalc/ru - GFDL | July 2021 | OpenSCADA 0.9.4 |