Модуль | Ім'я | Версія | Ліцензія | Джерело | Мови | Платформи | Тип | Автор | Опис |
---|---|---|---|---|---|---|---|---|---|
JavaLikeCalc | Обчислювач на мові подібній до Java | 5.6 | 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, in, break, continue, return, function, using.
Постійні:
Типи змінних:
Вбудовані константи: pi = 3.14159265..., e = 2.71828182..., EVAL_BOOL(2), EVAL_INT(-9223372036854775807), null,EVAL,EVAL_REAL(-1.79E308), EVAL_STR("<EVAL>")
Глобальні атрибути параметрів DAQ (починаючи з підсистеми DAQ та у вигляді {Тип модуля DAQ}.{Об'єкт контролеру}.{Параметр}.{Атрибут}).
Функції та параметри об'єктної моделі OpenSCADA.
Варіанти EVAL (Error VALue) і null опрацьовуються особливо через перетворення одного у інше залежно від використаного базового типу, тобто ви вільні у використанні лише null або EVAL у будь яких випадках.
Операції, підтримувані мовою, представлено у таблиці нижче. Пріоритет операцій зменшується зверху донизу. Операції з однаковим пріоритетом входять до однієї групи кольору.
Символ | Опис |
() | Виклик функції. |
{} | Програмні блоки. |
++ | Інкремент (пост та пре). |
-- | Декремент (пост та пре). |
- | Унарний мінус. |
! | Логічне заперечення. |
~ | Побітове заперечення. |
* | Множення. |
/ | Ділення. |
% | Залишок від цілочисельного ділення. |
+ | Складання |
- | Віднімання |
<< | Порозрядний зсув ліворуч |
>> | Порозрядний зсув праворуч |
> | Більше |
>= | Більше або дорівнює |
< | Менше |
<= | Менше або дорівнює |
== | Дорівнює |
!= | Не дорівнює |
| | Порозрядне "АБО" |
& | Порозрядне "ТАК" |
^ | Порозрядне "Виключне АБО" |
&& | Логічне "ТАК" |
|| | Логічне "АБО" |
?: | Умовна операція "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==null)?0:b) + ((c==null)?0:c) + ((d==null)?0:d); }
rez = sum(1, 2);
Мовою передбачено підтримку наступних спеціальних символів строкових змінних:
JavaLikeCalc надає підтримку типу даних об'єкт "Object". Об'єкт представляє собою асоціативний контейнер властивостей та функцій. Властивості можуть містити як дані чотирьох базових типів, так і інші об'єкти. Доступ до властивостей об'єкту може здійснюватися за посередництвом запису імен властивостей до об'єкту obj.prop, через крапку, а також за посередництвом включення імені властивості у квадратні дужки obj["prop"]. Очевидно, що перший механізм статичний, а другий дозволяє вказувати ім'я властивості через змінну. Ім'я властивості через крапку не має починатися на цифру та містити символи операцій, інакше, для першої цифри, має використовуватися префікс об'єкту — SYS.BD.SQLite.db_1s, або здійснюватися запис у квадратних дужках — SYS.BD.SQLite["1+s"], для символів операцій у назві. Видалити властивість об'єкту можна директивою "delete". Читання невизначеної властивості поверне null-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) містить параметри та текст програми, яка безпосередньо виконується контролером. Модулем передбачена обробка низки спеціальних параметрів, доступних у програмі контролеру:
Модуль надає тільки один "Стандартний (std)" тип параметрів із назвою таблиці параметрів "JavaLikePrm_{CntrId}".
Параметр об'єкту контролера даного модуля виконує функцію надання доступу до результатів обчислення контролера у OpenSCADA, за посередництвом атрибутів параметрів. Із специфічних полів, вкладка конфігурації параметра об'єкта контролера містить тільки поле переліку параметрів обчислювальної функції, які треба відобразити.
Модуль надає механізм для створення бібліотек користувацьких функцій на мові подібній до Java. Приклад вкладки конфігурації бібліотеки зображено на рисунку 4. Вкладка містить базові поля: доступність, адреса таблиці БД бібліотеки (з відстеженням наявності даних у різних сховищах та наданням послідовного видалення дублікатів), дата та час модифікації, ідентифікатор, ім'я та опис.
Функція, також як і бібліотека, містить базову вкладку конфігурації, вкладку формування програми та параметрів функції (Рис.1), а також вкладку виконання створеної функції.
Деякі об'єкти модуля надають функції користувацького програмування.
Об'єкт "Бібліотека функцій" (SYS.DAQ.JavaLikeCalc["lib_Lfunc"])
Об'єкт "Користувацька функція" (SYS.DAQ.JavaLikeCalc["lib_Lfunc"]["func"])
Сервісні функції — це інтерфейс доступу до OpenSCADA із зовнішніх систем посередництвом Інтерфейсу Управління. Цей механізм покладено в основу усього обміну всередині OpenSCADA, реалізованого шляхом слабких зв'язків та власного протоколу обміну OpenSCADA.
Отримання значень ВВ функції об'єкту контролеру
ЗАП: <get path="/DAQ/JavaLikeCalc/{CNTR}/%2fserv%2ffncAttr" />
ВІДП: <get path="/DAQ/JavaLikeCalc/{CNTR}/%2fserv%2ffncAttr" rez="0">{IOs}</get>
<get path="/DAQ/JavaLikeCalc/testCalc/%2fserv%2ffncAttr" rez="0" user="roman">
<a id="f_frq">0.1</a>
<a id="f_start">0</a>
<a id="f_stop">0</a>
<a id="this"><TCntrNodeObj path="/sub_DAQ/mod_JavaLikeCalc/cntr_testCalc/"/></a>
<a id="offset">100</a>
<a id="out">50</a>
<a id="test">1</a>
<a id="rez" />
<a id="inFarg">3</a>
</get>
Встановлення значень ВВ функції об'єкту контролеру
ЗАП[root-DAQ]: <set path="/DAQ/JavaLikeCalc/{CNTR}/%2fserv%2ffncAttr">{IOs}</set>
<set path="/DAQ/JavaLikeCalc/testCalc/%2fserv%2ffncAttr">
<a id="out">50</a>
<a id="test">1</a>
</set>
Початковий текст процедур на мові цього модуля компілюється у байт-код, який надалі обчислюється віртуальною машиною. Байт-код це не машинний код і досягнути продуктивності самої апаратної архітектури у віртуальній машині його виконуючої — теоретично нереально, якщо звісно код цієї віртуальної машини не виконує сам процесор. Тобто продуктивність виконання байт-коду приблизно на порядок нижче апаратної продуктивності за рахунок накладених витрат команд віртуальної машини, розподілу багатопотокового доступу до даних, прозорого приведення типів та відсутності жорсткої типізації, а також динамічної природи мови та наявності складних типів "Рядок" та "Об'єкт".
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/uk - GFDL | May 2024 | OpenSCADA 0.9.7 |