Документація/Як/Створити модуль - OpenSCADAWiki

# Документація/Як/Створити модуль

This page is a translated version of the page Documents/How to/Create module and the translation is 100% complete.

English • ‎mRussian • ‎Українська

- Автор: Роман Савоченко
- Спонсорування, повної ревізії на 1.8 ЛД[!]: SINGE SOFTWARE
- Початково створено: у старій Wiki

Цей посібник первинно покликано допомогти у створені модулів для OpenSCADA. Створення модуля може знадобитися за бажання додати підтримку нового джерела даних або іншого розширення до OpenSCADA. Оскільки OpenSCADA є гранично модульною, то всі інтерфейси взаємодії із зовнішнім середовищем здійснюється за посередництвом розширення модулями типів:

- бази даних;
- комунікаційні інтерфейси, транспорти;
- протоколи комунікаційних інтерфейсів;
- джерела даних та збір даних;
- архіви-історія (повідомлень та значень);
- інтерфейси користувача (GUI, TUI, WebGUI, speach, signal ...);
- додаткові модулі, спеціальні.

Але оскільки транспортні інтерфейси, реалізовані модулями, дозволяють зв'язуватися із різними пристроями на них, то цей документ розширено короткою інструкцією з посиланнями щодо створення внутрішніх Функцій, DAQ-Шаблонів, UI-Віджетів, а також оформлення Бібліотек із ними для розташування і публікації у офіційній колекції бібліотек OpenSCADA.

## 1 Створення нового Модуля

Для подальшого розміщення розробленого модуля до репозиторію дерева вихідних текстів OpenSCADA, маєте виконати наступне і дотримуватися вимог:

- бути правовласником або автором коду і розповсюджувати його за вільною ліцензією, віддаючи перевагу GPL;
- приготувати і зберігати код модуля як архів окремої теки модуля будь якої з підсистем OpenSCADA із вимогами до вмісту:
  - вихідні тексти модуля на початку кожного файлу мають включати коректну інформацію прав копіювання, бути написаними і відформатованими згідно до якоїсь системи де перевагу треба надавати стилю форматуванню основних модулів OpenSCADA;
  - файли локалізації модулів також мають бути коректними, актуальними та відповідно відформатованими.
- для документування і розташування цього модуля написати прямий запит у темі форуму "Розробка OpenSCADA", включаючи доказ працездатності від розробника OpenSCADA або коротке демонстраційне відео — вам буде надано статус Розробника і ваш обліковий запис із тим самим паролем з'явиться у OpenSCADA Wiki;
- написати коротку інформаційну сторінку модуля безпосередньо у Wiki таким-же чином, як і інші модулі поряд.

Модулі в OpenSCADA представляють із себе поділювані бібліотеки, які підключаються до ядра OpenSCADA динамічно при запуску або під час функціювання програми. Багато модулів у процесі функціювання можуть бути відключені, підключені та оновлені із менеджера модулів. Модулі також можуть бути вбудовані-включені до ядра OpenSCADA під час складання за посередництвом аргументу --enable-{ModName}=incl до скрипту конфігурації configure, про що можна дізнатися із посібника по збірці. Модулі OpenSCADA можуть бути семи типів згідно присутнім модульним підсистемам. Наразі модулі до OpenSCADA пишуться на мові програмування "C++", хоча у подальшому можлива поява біндінгів на інші мови.

Для створення модулів до OpenSCADA необхідні знання програмування на мові C/C++, складальної системи AutoTools, а також базові знання ОС Linux та використовуваного дистрибутиву Linux.

У дереві вихідних текстів, у гілці кожної підсистеми, для спрощення створення нових модулів передбачено теку "=Tmpl=" із шаблоном модуля відповідної підсистеми. Розробник нового модуля може взяти цю теку та скопіювати її з ім'ям свого нового модуля, хоча він також завжди може використати у якості зразка будь який реальний функціонуючий модуль якщо його новий близький за структурою. Передбачено можливість створення модулів у дереві вихідних текстів проєкту OpenSCADA або як незалежного проєкту зовнішнього модуля до OpenSCADA.

### 1.1 Створення у дереві вихідних текстів проєкту OpenSCADA

Створювати нові модулі у дереві вихідних текстів проєкту OpenSCADA має сенс у випадку подальших планів передачі нового модуля проєкту OpenSCADA. Оскільки модуль не має суперечити духу відкритого проєкту та ліцензії на основі якої розробляється та розповсюджується OpenSCADA то ліцензією нового модуля вочевидь має бути одна із вільних ліцензій.

Процедура створення нового модуля з включенням до дерева вихідних текстів на основі шаблону в цілому є простішою за процедуру для зовнішнього модуля та включає в себе кроки:

  1. Отримати дерево вихідних текстів проєкту OpenSCADA для:
  - Робочої (Work) гілки:
    svn co svn://oscada.org/trunk/OpenSCADA
  - гілки стабільного релізу — НЕБАЖАНО, оскільки до стабільних LTS релізів приймаються лише виправлення та ця інструкція потребує версії 0.9 або вище:
    svn co svn://oscada.org/tags/openscada_0.9
  2. Скопіювати теку шаблону з назвою нового модуля "NewMod", наприклад, для підсистеми "БД":
    cd OpenSCADA/src/moduls/bd; cp -r =Tmpl= NewMod; cd NewMod; rm -f configure.ac
      для підсистеми "Збір Даних" шлях наступний — "OpenSCADA/src/moduls/daq"
      для підсистеми "Архіви-Історія" шлях наступний — "OpenSCADA/src/moduls/arhiv"
      для підсистеми "Транспорти" шлях наступний — "OpenSCADA/src/moduls/transport"
      для підсистеми "Транспортні Протоколи" шлях наступний — "OpenSCADA/src/moduls/protocol"
      для підсистеми "Користувацькі Інтерфейси" шлях наступний — "OpenSCADA/src/moduls/ui"
      для підсистеми "Спеціальні" шлях наступний — "OpenSCADA/src/moduls/special"
  3. Редагувати файл "module.cpp" щодо:
      також може здійснюватися автоматично за допомогою: sed -i "s/Tmpl/NewMod/g" *.{cpp,h}
  - зміни назви функцій вбудування-включення модуля згідно назви нового модуля:
    "TModule::SAt bd_Tmpl_module( int n_mod )" —> "TModule::SAt bd_NewMod_module( int n_mod )"
    "TModule *bd_Tmpl_attach( const TModule::SAt &AtMod, const string &source )" —> "TModule *bd_NewMod_attach( const TModule::SAt &AtMod, const string &source )"
  - інформації про модуль у файлі "module.cpp", а саме ділянка:

    //************************************************
    //* Modul info!                                  *
    #define MOD_ID          "NewMod"
    #define MOD_NAME        _("DB NewMod")
    #define MOD_TYPE        SDB_ID
    #define VER_TYPE        SDB_VER
    #define MOD_VER         "0.0.1"
    #define AUTHORS         _("MyName MyFamily")
    #define DESCRIPTION     _("BD NewMod description.")
    #define MOD_LICENSE     "GPL2"

  4. Відредагувати конфігурацію складання модуля у файлі "Makefile.am" щодо:
      також може здійснюватися автоматично за допомогою: sed -i "s/Tmpl/NewMod/g" Makefile.am

    EXTRA_DIST = *.h po/*

    if NewModIncl
    noinst_LTLIBRARIES = db_NewMod.la
    db_NewMod_la_CXXFLAGS = -DMOD_INCL -fpic
    db_NewMod_la_LIBTOOLFLAGS = --tag=disable-shared
    db_NewMod_la_LDFLAGS = -module
    else
    oscd_modul_LTLIBRARIES = db_NewMod.la
    db_NewMod_la_CXXFLAGS =
    db_NewMod_la_LIBTOOLFLAGS = --tag=disable-static
    db_NewMod_la_LDFLAGS = -module -avoid-version $(top_builddir)/src/liboscada.la
    endif

    db_NewMod_la_CXXFLAGS += $(NewMod_CFLAGS)
    db_NewMod_la_LDFLAGS += $(NewMod_LDLAGS)
    db_NewMod_la_SOURCES = module.cpp

    I18N_mod = $(oscd_modulpref)NewMod
    include ../../../../I18N.mk

  5. Додати запис нового модуля в кінець секції підсистеми конфігураційного файлу "OpenSCADA/configure.ac" складальної системи OpenSCADA:
  - в кінець секції "DB modules" для підсистеми "БД":

    AX_MOD_DB_EN(NewMod, [disable or enable[=incl] compilation module DB.NewMod], disable, incl, [
        # Код перевірки зовнішніх бібліотек модуля
    ])

  - в кінець секції "DAQ modules" для підсистеми "Збір Даних":

    AX_MOD_DAQ_EN(NewMod, [disable or enable[=incl] compilation module DAQ.NewMod], disable, incl, [
        # Код перевірки зовнішніх бібліотек модуля
    ])

  - в кінець секції "Archive modules" для підсистеми "Архіви-Історія":

    AX_MOD_Archive_EN(NewMod, [disable or enable[=incl] compilation module Archive.NewMod], disable, incl, [
        # Код перевірки зовнішніх бібліотек модуля
    ])

  - в кінець секції "Transport modules" для підсистеми "Транспорти":

    AX_MOD_Transport_EN(NewMod, [disable or enable[=incl] compilation module Transport.NewMod], disable, incl, [
        # Код перевірки зовнішніх бібліотек модуля
    ])

  - в кінець секції "Transport protocol modules" для підсистеми "Транспортні Протоколи":

    AX_MOD_TrProt_EN(NewMod, [disable or enable[=incl] compilation module Protocol.NewMod], disable, incl, [
        # Код перевірки зовнішніх бібліотек модуля
    ])

  - в кінець секції "UI modules" для підсистеми "Користувацькі Інтерфейси":

    AX_MOD_UI_EN(NewMod, [disable or enable[=incl] compilation module UI.NewMod], disable, incl, [
        # Код перевірки зовнішніх бібліотек модуля
    ])

  - в кінець секції "Special modules" для підсистеми "Спеціальні":

    AX_MOD_Special_EN(NewMod, [disable or enable[=incl] compilation module Special.NewMod], disable, incl, [
        # Код перевірки зовнішніх бібліотек модуля
    ])

  6. Тепер новий модуль можна скласти у складі OpenSCADA після переформування складальної системи:
    autoreconf -if; ./configure --enable-NewMod; make
  7. Опублікувати — сформувати латку із вашим модулем та надіслати її розробникам OpenSCADA:
    cd OpenSCADA; make distclean; rm -rf src/moduls/bd/NewMod/{Makefile.in,.deps}
    svn add src/moduls/bd/NewMod; svn diff > NewMod.patch

### 1.2 Створення зовнішнього модуля до OpenSCADA

Створення зовнішнього модуля до OpenSCADA може мати сенс у випадку розробки інтерфейсу інтеграції з комерційними системами, які вимагають закриття коду взаємодії, а також у випадку інших реалізацій комерційних інтерфейсів при яких модуль до OpenSCADA отримує статус окремого проєкту, розповсюджується та підтримується незалежно, часто у вигляді бінарних збірок під конкретну платформу та версію OpenSCADA. Ліцензія таких модулів відповідно може бути будь якою.

Процедура створення нового зовнішнього модуля на основі шаблону багато в чому схожа на попередню процедуру та включає в себе кроки:

  1. Отримати вихідні текстів проєкту OpenSCADA — для зовнішнього модуля у якості джерела шаблону можна використати будь які файли OpenSCADA версії більш 0.9, оскільки із них потрібно скопіювати лише теку "=Tmpl=" та декілька файлів для збірки.
  2. Скопіювати теку шаблону з ім'ям нового модуля "NewMod", наприклад, для підсистеми "БД"; та вже в ній створити та скопіювати потрібні файли зовнішнього модуля. В подальшому інформаційні файли проєкту "COPYING", "NEWS", "README", "AUTHORS" та "ChangeLog" потрібно заповнити згідно сутності нового модуля:
    cp -r OpenSCADA/src/moduls/bd/=Tmpl= NewMod; touch NewMod/{NEWS,README,AUTHORS,ChangeLog}; cp OpenSCADA/I18N.mk NewMod/
      для підсистеми "Збір Даних" шлях наступний — "OpenSCADA/src/moduls/daq/=Tmpl="
      для підсистеми "Архіви-Історія" шлях наступний — "OpenSCADA/src/moduls/arhiv/=Tmpl="
      для підсистеми "Транспорти" шлях наступний — "OpenSCADA/src/moduls/transport/=Tmpl="
      для підсистеми "Транспортні Протоколи" шлях наступний — "OpenSCADA/src/moduls/protocol/=Tmpl="
      для підсистеми "Користувацькі Інтерфейси" шлях наступний — "OpenSCADA/src/moduls/ui/=Tmpl="
      для підсистеми "Спеціальні" шлях наступний — "OpenSCADA/src/moduls/special/=Tmpl="
  3. Відредагувати інформацію модуля у файлі "module.cpp" аналогічно цьому пункту попереднього розділу.
  4. Відредагувати конфігурацію збірки модуля у файлі "Makefile.am" аналогічно цьому пункту попереднього розділу, окрім:
  - замість "db_NewMod_la_LDFLAGS = -module -avoid-version $(top_builddir)/src/liboscada.la" записати "db_NewMod_la_LDFLAGS = -module -avoid-version", тобто видалити "$(top_builddir)/src/liboscada.la"
  - замість "include ../../../../I18N.mk" записати "include I18N.mk", тобто видалити шлях "../../../../"
  5. Відредагувати файл конфігурації складальної системи "configure.ac" щодо:
      також може здійснюватися автоматично за допомогою: sed -i "s/Tmpl/NewMod/g" configure.ac
  - "AC_INIT([Tmpl],[0.0.1],[my@email.org])" — інформація про модуль: ім'я, версія та Ел.Пошта проєкту
  - "AM_CONDITIONAL([TmplIncl],[test])" — "AM_CONDITIONAL([NewModIncl],[test])"
  6. Встановити пакет розробки OpenSCADA "openscada-dev" або "openscada-devel" — у зв'язку з тим, що модуль зовнішній та вихідні файли OpenSCADA потрібні лише на першому етапі його створення, то необхідно встановлювати пакет розробки OpenSCADA, який містить заголовні файли та бібліотеки.
  7. Тепер новий модуль можна скласти, після формування складальної системи:
    autoreconf -if; ./configure; make

### 1.3 API модуля

API OpenSCADA для розробника OpenSCADA та модулів до неї описано у відповідному документі "API OpenSCADA", який завжди має бути під рукою при розробці для OpenSCADA. В цьому ж документі наголос здійснено на детальному роз'яснені основних моментів модульного API.

Модулі в OpenSCADA реалізуються як поділювані бібліотеки та одна така бібліотека може містити багато модулів підсистем OpenSCADA, фактично виступаючи як контейнер. Такі контейнери також можуть бути включені-вбудовані до Бібліотеки Ядра OpenSCADA якщо ви будуєте дуже компактні рішення.

Першим кроком підключення поділюваних бібліотек (SO — Поділювані Об'єкти) є підключення функцій ініціалізації. Такі функції мають бути визначені як звичайні "C" функції для запобігання спотворенню їх назв. Зазвичай це здійснюється наступним чином:

    //================== CUT =========================
    extern "C"
    {
    #ifdef MOD_INCL
        TModule::SAt bd_Tmpl_module( int n_mod )
    #else
        TModule::SAt module( int n_mod )
    #endif
        {
            if(n_mod == 0) return TModule::SAt(MOD_ID, MOD_TYPE, VER_TYPE);
            return TModule::SAt("");
        }

    <!--T:348-->
    #ifdef MOD_INCL
        TModule *bd_Tmpl_attach( const TModule::SAt &AtMod, const string &source )
    #else
        TModule *attach( const TModule::SAt &AtMod, const string &source )
    #endif
        {
            if(AtMod == TModule::SAt(MOD_ID,MOD_TYPE,VER_TYPE)) return new BDTmpl::BDMod(source);
            return NULL;
        }
    }
    //================== CUT =========================

Точкою входу будь якого модуля є функції:

- TModule::SAt module( int n_mod ), TModule::SAt {modTp}_{modNm}_module( int n_mod ) — використовуються для сканування переліку та інформації з усіх модулів у бібліотеці. Перша функція використовується під час реалізації модулів у зовнішній поділюваній бібліотеці, а друга під час включення-вбудування їх до ядра OpenSCADA, де modTp відповідає типу модуля, а modNm його ІД.
- TModule *attach( const TModule::SAt &AtMod, const string &source ), TModule *{modTp}_{modNm}_attach( const TModule::SAt &AtMod, const string &source ) — використовується для безпосереднього підключення-відкриття обраного модуля шляхом створення кореневого об'єкта модуля успадкованого від TModule. Перша функція використовується під час реалізації модулів у зовнішній поділюваній бібліотеці, а друга під час включення-вбудування їх до ядра OpenSCADA, де modTp та modNm відповідають попередній функції.

Загальним для всіх модулів є наслідування кореневого об'єкта-класу модуля від класу модульної підсистеми TModule, що вказує на наявність спільної частини інтерфейсу модуля який розглянемо далі. Для отримання уявлення про архітектуру модулів у контексті загальної архітектури OpenSCADA наполегливо рекомендується мати перед очима загальну діаграму класів OpenSCADA!

Всі інтерфейсні об'єкти модулів успадковують клас вузла TCntrNode, який надає механізм інтерфейсу управління. Одним із завдань цього механізму є надання інтерфейсу конфігурації об'єкту у будь якому конфігураторі OpenSCADA.

Загальне API
TCntrNode — Вузол OpenSCADA: void preEnable( int flag );, void postEnable( int flag ); — підключення модуля до динамічного дерева об'єктів, викликається перед та після фактичного увімкнення модуля відповідно. void preDisable( int flag );, void postDisable( int flag ); — виключення модуля із динамічного дерева об'єктів перед звільненням об'єкту, викликається перед та після фактичного виключення модуля відповідно. void load_( TConfig *cfg );, void load_( ); — завантаження модуля із контексту сховища cfg та загалом, викликається на стадії завантаження конфігурації модуля зі сховку. void save_( ); — збереження модуля, викликається на стадії збереження конфігурації модуля до сховку, зазвичай за ініціативою користувача.
TModule — Модуль OpenSCADA: void modStart( ); — запуск модуля, викликається на стадії запуску завдань виконання фонових функцій модуля, якщо такі модулем надаються. void modStop( ); — зупинка модуля, викликається на стадії зупинки завдань виконання фонових функцій модуля, якщо такі модулем надаються. void modInfo( vector<string> &list ); — запит переліку інформаційних властивостей модуля, яким надається стандартний набір властивостей "Module", "Name", "Type", "Source", "Version", "Author", "Description", "License", та що може бути розширено власними-специфічними властивостями. string modInfo( const string &name ); — запит елементу інформації name за якого здійснюється обробка запитів і до власних-специфічних властивостей модуля. void modFuncReg( ExpFunc *func ); — реєстрація експортованої функції модуля, яка є частиною механізму міжмодульної взаємодії що реєструє внутрішню функцію модуля для зовнішнього виклику за назвою-символом функції та її вказівником відносно об'єкта модуля. Наразі цей механізм мало якими модулями використовується! void perSYSCall( unsigned int cnt ); — виклик із системного-сервісного потоку-завдання з періодичністю 10 секунд та секундним лічильником cnt, може використовуватися для виконання періодичних-рідких сервісних процедур.
API модулів підсистеми "Бази Даних (БД)"
Призначено для інтеграції OpenSCADA із БД чи СУБД, яка реалізується модулем. Надає два загальних підходи до реалізації модулів: Режим ANSI SQL — є найпростішим шляхом який передбачає безпосереднє використання функцій ядра fieldSQLSeek(), fieldSQLGet(), fieldSQLSet(), fieldSQLDel() у fieldSeek(), fieldGet(), fieldSet(), fieldDel() відповідно; всі SQL-модулі використовують наразі цей підхід. Повна Реалізація — є найскладнішим шляхом який передбачає повну реалізацію; модулі, що використовують такий підхід є або старими або специфічними: DBF, LDAP.
TTypeBD->TModule — кореневий об'єкт модуля підсистеми "БД": string features( ); — перелік ключових слів підтримуваних властивостей БД. int lsPr( ); — базовий приорітет БД [0...9] у загальному переліку сховків. TBD *openBD( const string &id ); — викликається при відкритті або створені нового об'єкта БД даним модулем з ідентифікатором id.
TBD — об'єкт бази даних: void enable( ); — включення БД. void disable( ); — відключення БД. void allowList( vector<string> &list ) const; — запит переліку list таблиць у БД. void sqlReq( const string &req, vector< vector<string> > *tbl = NULL, char intoTrans = EVAL_BOOL ); — обробка SQL-запиту req до БД та отримання результату у вигляді таблиці tbl, якщо запит вибірки та вказівник ненульовий. При встановлені intoTrans у TRUE для запиту мусить бути відкрита транзакція, у FALSE закрита. Ця функція має реалізовуватися для СУБД, які підтримують SQL-запити. void transCloseCheck( ); — періодично викликається для перевірки транзакцій та закриття старих або які містять багато запитів. TTable *openTable( const string &name, bool create ); — викликається при відкритті або створені нового об'єкта таблиці.
TTable — об'єкт таблиці у базі даних: void fieldStruct( TConfig &cfg ); — отримання поточної структури таблиці у об'єкті cfg. bool fieldSeek( int row, TConfig &cfg, const string &cacheKey = "" ); — послідовне сканування записів таблиці перебором row за об'єктом cfg та повернення FALSE по закінченню, з адресацією за активними keyUse() ключовими полями. Ключ кешу cacheKey вказується для предзавантаженням повної відповіді до кешу, із витягненням наступних записів звідти. void fieldGet( TConfig &cfg ); — запит вказаного у об'єкті cfg запису із адресацією за ключовими полями. void fieldSet( TConfig &cfg ); — передача вказаного у об'єкті cfg запису з адресацією за ключовими полями. void fieldDel( TConfig &cfg ); — видалення вказаного запису за ключовими полями об'єкту cfg. Специфічне для SQL Баз Даних void fieldFix( TConfig &cfg, const string &langLs = "" ); — виправлення структури таблиці БД до cfg та для мов перекладу langLs, зазвичай після невдалого запису. string getSQLVal( TCfg &cf, uint8_t RqFlg = 0 ); — повернення специфічно до SQL обгорненого значення cf для прапорців ReqFlg звернення RqFlg. void setSQLVal( TCfg &cf, const string &vl, bool tr = false ); — розбір SQL-значення vl для перекладу tr та із записом до cf.
API of the modules of the "Transports" subsystem
Provides OpenSCADA communications through the interface, often it is the network one which is implemented by the module.
TTypeTransport->TModule — the root module object of the "Transports" subsystem: virtual bool isNetwork( ); — the sign of network implementation by this module. virtual string outAddrHelp( ); — address format help of the output transports. virtual TTransportIn *In( const string &id, const string &stor ); — called when a new input transport object is opened or created by this module with the identifier id and the storage stor. virtual TTransportOut *Out( const string &name, const string &stor ); — called when a new output transport object is opened or created by this module with the identifier id and the storage stor.
TTransportIn — the input transport object: virtual unsigned keepAliveReqs( ); — maximum Keep Alive requests. virtual unsigned keepAliveTm( ); — keep Alive time. virtual string getStatus( ); — getting the status of the transport. virtual void start( ); — starting the transport. virtual void stop( ); — stopping the transport. virtual int writeTo( const string &sender, const string &data ); — sending data backward to the sender.
TTransportOut — the output transport object: virtual string timings( ); — transport timeouts. virtual unsigned short attempts( ); — connection attempts. virtual string getStatus( ); — getting the status of the transport. virtual void setTimings( const string &vl, bool isDef = false ); — setting the transport timeouts, as default one for isDef. virtual void setAttempts( unsigned short vl ); — setting the connection attempts. virtual void start( int time = 0 ); — starting the transport with the connection timeout time. When you start the output transport the actual connection to the remote station is established for the interfaces that works by the connection. At this time the errors can occur if the connection is impossible and the transport should return to the stopped state. virtual void stop( ); — stopping the transport. virtual int messIO( const char *oBuf, int oLen, char *iBuf = NULL, int iLen = 0, int time = 0 ); — sending of the data over the transport. The waiting timeout time of the connection in milliseconds. The time in negative disables the transport's request/respond mode and allows for the independently reading/writing to a buffer IO, with the reading timeout time in absolute.
API модулів підсистеми "Транспортні протоколи"
Забезпечує OpenSCADA комунікаціями рівня протоколу, які реалізуються модулем, щодо доступу до даних із зовнішніх систем та надання даних OpenSCADA для зовнішніх систем.
TProtocol->TModule — кореневий об'єкт модуля підсистеми "Транспортні протоколи": virtual void itemListIn( vector<string> &ls, const string &curIt = "" ); — перелік ls під'елементів вхідного протоколу від поточного елементу curIt, якщо протокол їх надає. Використовується при обранні об'єкта у конфігурації вхідного транспорту. virtual void outMess( XMLNode &io, TTransportOut &tro ); — передавання даних об'єктами ядра OpenSCADA у дереві XML io до віддаленої системи через транспорт tro та поточний вихідний протокол. Представлення даних у дереві XML не стандартизоване та специфічне до логічної структури протоколу. Ці дані серіалізуються — перетворюються у послідовність байтів відповідно до протоколу, та надсилаються через визначений вихідний транспорт tro функцією messIO(). virtual TProtocolIn *in_open( const string &id ); — викликається модулем за відкриття або створення нового об'єкту транспортного протоколу із ідентифікатором id.
TProtocolIn — вхідний об'єкт транспортного протоколу з опрацювання вхідних запитів від вхідного транспортного об'єкту TTransportIn. Для кожного сеансу вхідного запиту створюється асоційований об'єкт вхідного протоколу, який залишається живим до завершення повного сеансу "запит->відповідь". Адреса транспорту, який відкриває входження протоколу, визначається у srcTr(): virtual unsigned waitReqTm( ) — час очікування запиту на вхідному транспорті у мілісекундах, після якого звертається до протоколу із порожнім повідомленням — режим опитування. Встановлення його у нуль вимикає режим опитування. virtual void setSrcTr( TTransportIn *vl ) — встановлення транспорту-джерела відкритого сеансу вхідного протоколу. virtual void setSrcAddr( const string &vl ); — встановлення адреси відправника. virtual bool mess( const string &request, string &answer ); — передавання послідовності даних запиту request до об'єкту протоколу для її розбору відповідно до реалізації протоколу. Ця функція протоколу має опрацювати запит request, згенерувати відповідь у answer та повернути FALSE у випадку повноти запиту. Якщо запит request не повний, необхідно повернути транспорту TRUE для індикації "очікування завершення", попередні частини запиту повинні зберігатися у контексті об'єкту протоколу.
API модулів підсистеми "Збір Даних"
Надає збір даних реального часу із зовнішніх систем або їх формування у обчислювачах, які реалізуються модулем. Це основна підсистема, оскільки SCADA це первинно про Збір Даних. Як основна підсистема, вона забезпечує декілька підходів до реалізації модулів, які здебільшого стосуються формування та зберігання структури атрибутів: Статичне формування через визначення набору типів параметрів, успадкованих від TTypeParam, тобто застосування структур виконується як набір атрибутів зі зміною типу параметра. Цей метод є найменш гнучким і його використовують такі модулі: GPIO, SMH2Gi, AMRDevs. Динамічне формування з керуванням структури контейнеру TElem в об’єкті параметра TParamContr. Цей метод є найбільш гнучким і використовується в більшості модулів, структуру яких можна конфігурувати. Як розширення динамічного формування існує тип параметра Логічного Рівня, який можна додати до будь-якого модуля, але він використовується переважно в універсальних джерелах даних: LogicLev, ModBus, Siemens, OPC_UA.
TTypeDAQ->TModule — кореневий об'єкт модуля підсистеми "Збір Даних": virtual bool compileFuncLangs( vector<string> *ls = NULL ); — запитує перелік мов ls для яких реалізується можливість формування користувацьких процедур у цьому модулі, та перевірка факту підтримки цього. virtual void compileFuncSnthHgl( const string &lang, XMLNode &shgl ); — запит прав підсвітлення синтаксису shgl щодо визначеної мови lang. virtual string compileFunc( const string &lang, TFunction &fnc_cfg, const string &prog_text, const string &usings = "", int maxCalcTm = 0 ); — компіляція-реєстрація користувацької функції на підтримуваній мові програмування lang та на вихідному коді процедури prog_text, заснованій на параметрах процедури fnc_cfg. Повертає адресу об'єкту скомпільованої функції, готового до виконання. virtual bool redntAllow( ); — стан підтримки механізму резервування цим модулем. Має бути перевизначено та повертати TRUE за підтримки, інакше FALSE. virtual TController *ContrAttach( const string &id, const string &daq_db ); — викликається модулем за відкриття або створення нового об'єкту контролеру із ідентифікатором id.
TController — об'єкт контролеру джерела даних. У контексті об'єкту зазвичай виконується завдання періодичного або планового опитування даних реального часу фізичного контролеру або фізично відокремленого блоку даних. У випадку отримання даних пакетами, вони розташовуються безпосередньо у архіві, пов'язаному із атрибутом параметра TVAl::arch(), та поточне значення встановлюється функцією TVAl::set() із атрибутом "sys"=TRUE: virtual string getStatus( ); — функція запиту статусу контролеру. virtual void enable_( ); — увімкнення об'єкту контролеру. На цій стадії зазвичай відбувається ініціалізація об'єктів параметрів та їх інтерфейсів у формі атрибутів, атрибути інколи можуть запитуватися у пов'язаного віддаленого джерела. virtual void disable_( ); — вимкнення об'єкту контролеру. virtual void start_( ); — запуск об'єкту контролеру. На цій стадії зазвичай створюється та запускається завдання опитування. virtual void stop_( ); — зупинка об'єкту контролеру. virtual void redntDataUpdate( ); — операція отримання даних із резервної станції, автоматично викликається сервісною процедурою схеми резервування підсистеми. virtual string catsPat( ); — перелік правил регулярних виразів, поділених '|', для виокремлення за категорією щодо повідомлень згенерованих об'єктом. virtual void messSet( const string &mess, int lev, const string &type2Code = "OP", const string &prm = "", const string &cat = "" ); — формування повідомлення Джерела Даних від об'єкту параметру prm (PrmId) або об'єкту контролеру взагалі якщо об'єкту параметру не визначено, для повідомлення mess, рівня lev та коду типу type2Code. Ця функція генерує повідомлення із уніфікованою категорією прозорості Джерела Даних "{type2Code}{ModId}:{CntrId}[.{prm}][:{cat}]". virtual TParamContr *ParamAttach( const string &id, int type ); — викликається модулем за відкриття або створення нового об'єкту параметру контролера із ідентифікатором id.
TParamContr->TValue — об'єкт параметру контролера джерела даних. Містить атрибути з реальними даними у наборі визначеному фізично доступними даними. Значення до атрибутів потрапляють із завдання опитування контролера, при асинхронному режимі, або запитуються під час звернення, при синхронному режимі, та за посередництвом методів успадкованого типу TValue: virtual TElem *dynElCntr( ); — контейнер динамічних елементів атрибутів Збору Даних. Переважно визначається джерелами логічного рівня які і надають контейнери такого характеру. virtual void enable( ); — увімкнення об'єкту параметру, здійснюється формування набору атрибутів та заповнення їх значеннями недостовірності. virtual void disable( ); — вимкнення об'єкту параметру. virtual void setType( const string &tpId ); — викликається для зміни типу параметру у tpId та може бути опрацьований у об'єкті модуля для зміни власних даних. virtual TVal* vlNew( ); — викликається на стадії створення нового атрибуту. Може бути перевизначено для реалізації особливої поведінки у межах свого об'єкту, успадкованого від TVal, при доступі до атрибуту. virtual void vlGet( TVal &vo ); — викликається для атрибуту vo із режимом прямого читання TVal::DirRead при читанні значення задля прямого-синхронного читання із фізичного джерела або буферу об'єкта. virtual void vlSet( TVal &vo, const TVariant &vl, const TVariant &pvl ); — викликається для атрибуту vo із режимом прямого запису TVal::DirWrite при запису значення vl задля прямого-синхронного запису до фізичного джерела або буферу об'єкта, із попереднім значенням pvl. virtual void vlArchMake( TVal &val ); — викликається на стадії створення архіву значень із атрибутом val як джерело щодо ініціалізації характеристик якості буферу архіву у відповідності до характеристик джерела даних та опитування.
API модулів підсистеми "Архіви-Історія"
Використовується для архівування та ведення історії повідомлень і значень реального часу отриманих у підсистемі "Збір Даних", та у засіб реалізований модулем.
TTypeArchivator->TModule — кореневий об'єкт модуля підсистеми "Архіви-Історія": virtual TMArchivator *AMess( const string &id, const string &stor ); — викликається модулем за відкриття або створення нового об'єкту архіватору повідомлень із ідентифікатором id та у сховку stor. virtual TVArchivator *AVal( const string &id, const string &stor ); — викликається модулем за відкриття або створення нового об'єкту архіватору значень із ідентифікатором id та у сховку stor.
TMArchivator — об'єкт архіватору повідомлень. virtual void redntDataUpdate( ); — операція отримання даних із резервної станції, автоматично викликається сервісною процедурою схеми резервування підсистеми. virtual void start( ); — запуск об'єкту архіватору, архіватор запускається для отримання повідомлень та розміщення їх до сховку. virtual void stop( ); — зупинка об'єкту архіватору. virtual time_t begin( ); — час початку даних архіватору у відповідності до поточного стану сховку. virtual time_t end( ); — час закінчення даних архіватору у відповідності до поточного стану сховку. virtual bool put( vector<TMess::SRec> &mess, bool force = false ); — розташування групи повідомлень mess до архіватору. Повертає TRUE за успішної операції. Встановити force для прямого запису до архіву оминаючи резервування. virtual time_t get( time_t bTm, time_t eTm, vector<TMess::SRec> &mess, const string &category = "", char level = 0, time_t upTo = 0 ); — отримання повідомлень до mess із архіватору для визначених параметрів фільтрації. Повертає час зупинки запиту, корисно для продовження від цієї позиції як часу закінчення, тобто ітераційно заглиблюючись в історію. Фільтр визначається діапазоном часу [bTm...eTm], правилами категорії category, рівнем level та обмежено до часу upTo. За відсутності прямого визначення обмежувального часу upTo, це обмеження встановлюється у prmInterf_TM — 7 секунд.
TVArchivator — об'єкт архіватору значень. virtual void start( ); — запуск об'єкту архіватору, архіватор запускається для отримання значень та розміщення їх у сховок. virtual void stop( bool full_del = false ); — зупинка об'єкту архіватору із можливістю повного видалення його даних зі сховку за full_del. virtual TVArchEl *getArchEl( TVArchive &arch ); — отримання об'єкту елемента архіву значень для визначеного архіву arch. virtual void pushAccumVals( ); — виштовхування накопичених завданням архівації значень, для акумулювальних архіваторів.
TVArchEl — об'єкт елементу архіватору значень. virtual void fullErase( ); — викликається для цілковитого видалення архівної частини архіватору. virtual int64_t end( ); — час завершення у мікросекундах щодо наявних значень у архіві архіватору. virtual int64_t begin( ); — час початку у мікросекундах щодо наявних значень у архіві архіватору. virtual TVariant getValProc( int64_t *tm, bool up_ord ); — запит одного значення із архіву на час tm та із підтягненням до верхнього значення у ґратці вимірювання up_ord. virtual void getValsProc( TValBuf &buf, int64_t beg, int64_t end ); — запит групи значень до buf із архіву та для діапазону часу [beg...end]. virtual void setValsProc( TValBuf &buf, int64_t beg, int64_t end, bool toAccum ); — встановлення групи значень buf до архіву, для діапазону часу [beg...end] та через акумуляцію toAccum.
API модулів підсистеми "Користувацькі Інтерфейси"
Користувацький інтерфейс формується згідно до концепції та механізмів зовнішніх розповсюджених стандартів та бібліотек.
TUI->TModule — кореневий об'єкт модуля підсистеми "Користувацькі Інтерфейси": Не містить специфічних функцій!
API модулів підсистеми "Спеціальне"
Реалізує специфічні функції, які не увійшли до жодної з вищеперелічених підсистем. Специфічні функції формуються згідно їх власних вимог та із використанням всіх можливостей API OpenSCADA.
TSpecial->TModule — кореневий об'єкт модуля підсистеми "Спеціальне": Не містить специфічних функцій!

Для зручності прямої адресації до кореневого об'єкта модуля із будь якого об'єкта нижче за ієрархією, рекомендується визначити глобальну змінну "mod" у області імен модуля, з ініціалізацією її у конструкторі кореневого об'єкта. Також, для прозорого перекладу текстових повідомлень модуля рекомендується визначити шаблони функцій виклику перекладу повідомлень модуля "_({Повідомлення})" та "trS({Повідомлення})" як:

    #undef _
    #define _(mess) mod->I18N(mess).c_str()
    #undef trS
    #define trS(mess) mod->I18N(mess,mess_PreSave)

У конструкторі кореневого об'єкту модуля успадкованого від TModule необхідно встановити основну інформацію модуля викликом функції void modInfoMainSet({Ім'я}, {Тип}, {Версія}, {Автори}, {Опис}, {Ліцензія}, {Джерело}) після ініціалізації швидкого посилання "mod" на кореневий об'єкт цього модуля.

Подальше отримання файлу шаблону перекладів "po/oscd_NewMod.pot" текстових повідомлень "_({Повідомлення})" та "trS({Повідомлення})", а також оновлення-актуалізація файлів вже існуючих перекладів "po/{uk|de|ru|...}.po" здійснюється командою у теці модуля make messages.

При вирішенні завдань нового модуля може знадобитися розширення параметрів конфігурації, що здійснюється у віртуальній функції void cntrCmdProc( XMLNode *req );. Вміст цієї функції, який додає властивості, у модулі SQLite має вигляд:

    void MBD::cntrCmdProc( XMLNode *opt )
    {
        //Getting the page info
        if(opt->name() == "info") {
            TBD::cntrCmdProc(opt);
            ctrMkNode("fld",opt,-1,"/prm/cfg/ADDR",EVAL_STR,enableStat()?R_R___:RWRW__,"root",SDB_ID,3,
                "dest","sel_ed","select","/prm/cfg/dbFsList","help",
                        _("SQLite DB address must be written as: \"{FileDBPath}\".\n"
                          "Where:\n"
                          "  FileDBPath - full path to DB file (./oscada/Main.db).\n"
                          "               Use the empty path to create a temporary database on the disk.\n"
                          "               Use \":memory:\" to create a temporary database in memory."));
            if(reqCnt)
                ctrMkNode("comm",opt,-1,"/prm/st/end_tr",_("Close opened transaction"),RWRW__,"root",SDB_ID);
        }
        //Processing for commands to the page
        string a_path = opt->attr("path");
        if(a_path == "/prm/cfg/dbFsList" && ctrChkNode(opt)) {
            opt->childAdd("el")->setText(":memory:");
            TSYS::ctrListFS(opt, addr(), "db;");
        }
        else if(a_path == "/prm/st/end_tr" && ctrChkNode(opt,"set",RWRW__,"root",SDB_ID,SEC_WR) && reqCnt) transCommit();
        else TBD::cntrCmdProc(opt);
    }

Перша половина цієї функції обслуговує інформаційні запити "info" з переліком та властивостями полів конфігурації. Друга половина обслуговує решту команд на отримання, встановлення значення та інше. Виклик TBD::cntrCmdProc(opt); використовується для отримання успадкованого інтерфейсу. Детальніше про призначення використаних функцій дивіться у інтерфейсі управління, а також у вихідних текстах існуючих модулів.

Об'єкт TCntrNode окрім функції інтерфейсу управління надає уніфіковані механізми контролю за модифікацією конфігурації об'єкта, завантаження, збереження та видалення дублікатів конфігурації у сховищі. Для встановлення прапорця модифікації даних об'єкта можна використовувати функції modif() та modifG(), а специфічні до модуля дії із завантаження та збереження можна розташовувати у віртуальні функції:

- void load_( TConfig *cfg );, void load_( ); — завантаження об'єкта зі сховища.
- void save_( ); — збереження об'єкта у сховищі.

Дії із конфігурацією типово відбуваються за посередництвом об'єкта TConfig, яких містить набір визначених властивостей зі структурою та значеннями. Для прямого відображення властивостей об'єкта модуля він успадковується від TConfig, а нові властивості додаються командою:

    fldAdd(new TFld("MOD_PRMS",trS("Module addition parameters"),TFld::String,TFld::FullText|TCfg::NoVal,"100000"));

Завантаження/збереження/видалення властивостей, вказаних у об'єкті TConfig, із/у/в сховище здійснюється командами:

    TBDS::dataGet(fullDB(), owner().nodePath()+tbl(), *this);
    TBDS::dataSet(fullDB(), owner().nodePath()+tbl(), *this);
    TBDS::dataDel(fullDB(flag&NodeRemoveOnlyStor), owner().nodePath()+tbl(), *this, TBDS::UseAllKeys);

Де:

- fullDB() — повна назва сховища у загальній формі;
- owner().nodePath()+tbl() — загальний шлях до вузла об'єкта у конфігураційному файлі, представлений таблицею;
- *this — цей об'єкт, успадкований від TConfig.

Для генерації налагоджувальних повідомлень відповідно до загальної концепції налаштувань необхідно використовувати функцію mess_debug() з умовою виклику залежно від ділянки вихідного тексту програми:

- рідко викликувана ділянка — прямий виклик функції mess_debug(...);;
- часто викликувана ділянка — умовний виклик if(mess_lev() == TMess::Debug) mess_debug(...);;
- критична до продуктивності ділянка коду — обгортання у визначення OSC_DEBUG:

    #ifdef OSC_DEBUG
      mess_debug(...);
    #endif

## 2 Створення нових Внутрішніх Елементів і Бібліотек із ними

Для подальшого розміщення бібліотеки розробленого внутрішнього коду до репозиторію дерева вихідних текстів OpenSCADA, маєте виконати наступне і дотримуватися вимог:

- бути правовласником або автором коду і розповсюджувати його за вільною ліцензією, віддаючи перевагу GPL;
- створити окреме експортне сховище у файлі модуля БД SQLite і бібліотеку у ньому (залежно від Елементу), куди розташувати ваші Елементи; якщо-ж це нова бібліотека, то назвати її відповідним чином, із описом як зазначено нижче; більше щодо маніпуляцій зі сховищами можете прочитати у Як Перенести конфігурації проєкту OpenSCADA;
- приготувати внутрішній код із вимогами до вмісту:

  - Функції, DAQ-Шаблони, UI-Віджети, а також їх бібліотека, мають бути написані міжнародною мовою і у полі опису містити сам опис, та загальну інформацію із правами копіювання у кінці:

    Author: Name Family <nick@email.org>
    Total complexity: N.N HD
    Version: N.N.N
    License: GPLvN

  - вихідні тексти Функцій, DAQ-Шаблонів, UI-Віджетів мають бути написаними і відформатованими згідно до якоїсь системи, де перевагу треба надавати стилю форматуванню основних модулів OpenSCADA, не мають містити локалізованих текстів, а лише тексти міжнародною мовою із обгортанням функцією перекладу tr();
  - ІД та ім'я ВВ мають також бути міжнародною мовою;
  - усі зазначені тексти можуть перекладатися локальною мовою автора, або і декількома на його розсуд, та на сторінці "Переклади".

- для документування і розташування бібліотеки написати прямий запит у темі форуму "Розробка OpenSCADA", включаючи доказ працездатності від розробника OpenSCADA або коротке демонстраційне відео — вам буде надано статус Розробника і ваш обліковий запис із тим самим паролем з'явиться у OpenSCADA Wiki;
- написати коротку інформаційну сторінку для нової бібліотеки, або частину до існуючої, безпосередньо у Wiki таким-же чином, як і інші бібліотеки поряд; у інформаційному полі в кінці опису нової бібліотеки додати поле для переходу на цю документацію у якості контекстної:

    DOC: Libs_MyLib|Libs/MyLib

Окрім стандартних контекстів виконання, описаних нижче, надаються декілька із власною інтеграцією контексту виконання безпосередньо JavaLikeCalc Функціями із можливістю обрання DAQ-Шаблонів: JavaLikeCalc Контролери, Вузли Даних ModBus Протоколу, Користувацький протокол, WWW-сторінка користувача. І вони використовують власний об'єкт зберігання для JavaLikeCalc Функцій, щодо чого можете ознайомитися на їх власних сторінках.

JavaLikeCalc Функції
JavaLikeCalc Функції не мають специфічного застосування, тобто вони можуть викликатися із інших контекстів виконання OpenSCADA, із інших Функцій або незалежно користувачем у сервісних викликах. Тож, функції не мають посилань і можуть використовувати усе Користувацьке API.

Функції розташовуються і конфігуруються у бібліотеках JavaLikeCalc модуля та кожна бібліотека може бути збережена у окремому сховищі.

DAQ-Шаблони
DAQ-Шаблони є частиною концепції Логічного Рівня підсистеми Збору Даних де необхідне обчислення М'якої Логіки щодо отриманих даних. Але наразі DAQ-Шаблони також можуть використовуватися низкою специфічних частин OpenSCADA із інтеграцією користувацького контексту виконання: JavaLikeCalc Контролери, Вузли Даних ModBus Протоколу, Користувацький протокол, WWW-сторінка користувача. DAQ-Шаблони мають посилання на DAQ-Атрибути і можуть використовувати усе Користувацьке API.

Шаблони розташовуються і конфігуруються у Бібліотеках Шаблонів підсистеми Збору Даних та кожна бібліотека може бути збережена у окремому сховищі.

UI-Віджети
UI-Віджети є частиною VCAEngine модуля, які інтегрують контекст виконання. UI-Віджети мають посилання на DAQ-Атрибути, Віджет-Атрибути і можуть використовувати усе Користувацьке API із власним Користувацьким VCAEngine API. Окрім стандартних змінних, до контексту виконання можуть прямо поміщатися атрибути Віджету і дочірніх Віджетів через позначення "Обробка".

Віджети розташовуються і конфігуруються у Бібліотеках Віджетів і Проєктах модуля VCAEngine та кожна бібліотека-проєкт може бути збережено у окремому сховищі.

Documents/How_to/Create_module/uk - GFDL | May 2026 | OpenSCADA 0.9.9