Північні Плагіни
Північні плагіни використовуються в північних завданнях і мікросервісах для вилучення даних, буферизованих у Fledge, і відправки їх на північ, тобто на сервер або сервіс у хмарі або в корпоративний центр обробки даних. Північні плагіни можуть бути написані на Python або C/C++, кілька різних північних плагінів доступні як приклади, які можна вик ористовувати при створенні нових плагінів.
Північний плагін має обмежену кількість точок входу, які він підтримує, ці точки входу однакові для північних плагінів на Python та C/C++.
| Вхідні точки | Опис |
|---|---|
| plugin_info | Повертає інформацію про плагін, включаючи його конфігурацію. Це те саме, що і plugin_info у всіх інших типах плагінів і є частиною стандартного інтерфейсу плагіна. |
| plugin_init | Також є частиною стандартного інтерфейсу плагіна. Цьому виклику передається запит конфігурації плагіна і його слід використовувати для будь-якої ініціалізації плагіна. |
| plugin_send | Ця точка входу є специфічною для північного плагіна точкою входу, яка використовується для надсилання даних з Fledge. Ця точка буде викликатися багаторазово з блоками зчитування. |
| plugin_shutdown | Частина стандартного інтерфейсу плагіна, яка буде викликана, коли плагін більше не потрібен, і буде останнім викликом плагіна. |
| plugin_register | Регістр функцію зворотного виклику, яка використовується для контрольних записів та операцій. |
Життєвий цикл плагіна дуже схожий, незалежно від того, написаний він на Python чи C/C++, спочатку виконується виклик plugin_info для визначення даних про плагін. Потім плагін ініціалізується викликом точки входу plugin_init. Точка входу plugin_send буде викликана кілька разів для надсилання фактичних даних і, нарешті, буде викликана точка входу plugin_shutdown.
У наступних розділах буде детально описано кожен з цих викликів та наведено приклади як на C/C++, так і на Python.
Плагіни на Python
Плагіни Python завантажуються динамічно і виконуються або в рамках завдання, відомого як sending_task, або як завдання north. Цей код реалізовано на C++ та вбудовано інтерпретатор Python, який використовується для запуску плагіна Python.
Виклик plugin_info
Виклик plugin_info - це перший виклик, який буде зроблено до плагіна, і він викликається лише один раз. Він є частиною стандартного інтерфейсу плагіна, який реалі зовано у північному, південному, фільтрі, правилі сповіщень та плагінах доставки сповіщень. До цього виклику не передається жодних аргументів, і він має повернути інформаційну структуру плагіна у вигляді дикту Python.
Типова реалізація для простого північного плагіна просто повертає DICT наступним чином
def plugin_info():
""" Used only once when call will be made to a plugin.
Args:
Returns:
Information about the plugin including the configuration for the plugin
"""
return {
'name': 'http',
'version': '1.9.1',
'type': 'north',
'interface': '1.0',
'config': _DEFAULT_CONFIG
}
Елементи в структурі, які повертає plugin_info, є
| Назва | Опис |
|---|---|
| name | Назва плагіну |
| version | Версія плагіна. Зазвичай це те саме, що версія Fledge, для роботи з якою вона призначена, але не обмежена тим самим. |
| type | Тип плагіна, у цьому випадку тип завжди буде північ |
| interface | Версія інтерфейсу плагіна, яку підтримує плагін. У цьому випадку версія 1.0 |
| config | DICT, який визначає конфігурацію плагіна за замовчуванням. |
У наведеному вище випадку _DEFAULT_CONFIG - це ще один Python DICT, який містить значення за замовчуванням для конфігурації плагіна і буде розглянутий у розділі "Конфігурація".
Конфігурація
Конфігурація у Fledge представлена у вигляді JSON-структури, яка визначає ім'я, значення, значення за замовчуванням, тип та ряд інших необов'язкових параметрів. Процес конфігурації працює за допомогою плагінів, що мають конфігурацію за замовчуванням, яку вони повертають з виклику plugin_init. Потім код конфігурації Fledge об'єднає його з копією цієї конфігурації, яку він зберігає. При першому створенні сервісу, без попередньої конфігурації, менеджер конфігурації візьме значення за замовчуванням і зробить їх фактичними значеннями. Потім користувач може оновити їх, щоб встановити значення не за замовчуванням. Під час наступних запусків плагіна ці значення будуть об'єднані зі значеннями за замовчуванням для створення конфігурації, що використовується, яка передається до точки входу plugin_init. Механізм призначений для забезпечення початкового запуску плагіна, а також для оновлення плагіна з метою створення нових елементів конфігурації для плагінів зі збереженням попередніх значень конфігурації, встановлених користувачем.
Приклад конфігурації за замовчуванням для http плагіна на основі північного python показано нижче.
{
"plugin": {
"description": "HTTP North Plugin",
"type": "string",
"default": "http_north",
"readonly": "true"
},
"url": {
"description": "Destination URL",
"type": "string",
"default": "http://localhost:6683/sensor-reading",
"order": "1",
"displayName": "URL"
},
"source": {
"description": "Source of data to be sent on the stream. May be either readings or statistics.",
"type": "enumeration",
"default": "readings",
"options": ["readings", "statistics"],
"order": "2",
"displayName": "Source"
},
"verifySSL": {
"description": "Verify SSL certificate",
"type": "boolean",
"default": "false",
"order": "3",
"displayName": "Verify SSL"
}
}
Елементи, позначені як "readonly" : "true ", не будуть показані користувачеві. Властивості displayName та order використовуються інтерфейсом користувача лише для відображення елемента конфігурації. Опис, тип і значення за замовчуванням використовуються API для перевірки введених даних, а також для встановлення початкових значень при створенні нового елемента конфігурації.
Користувацькому інтерфейсу також можна надати правила для визначення дійсності елементів конфігурації на основі значень інших елементів, наприклад
{
"applyFilter": {
"description": "Should filter be applied before processing data",
"type": "boolean",
"default": "false",
"order": "4",
"displayName": "Apply Filter"
},
"filterRule": {
"description": "JQ formatted filter to apply (only applicable if applyFilter is True)",
"type": "string",
"default": ".[]",
"order": "5",
"displayName": "Filter Rule",
"validity": "applyFilter == \"true\""
}
}
Це дозволить лише входити до елемента конфігурації filterRule, якщо для елемента applyFilter встановлено значення true.
Виклик plugin_init
Виклик plugin_init буде викликано після виклику plugin_info для отримання інформації про плагін. Цей виклик призначений для того, щоб дозволити плагіну виконати будь-яку необхідну ініціалізацію, а також створити дескриптор, який буде використовуватися у всіх наступних викликах для ідентифікаці ї екземпляра плагіна.
До plugin_init передається Python DICT як єдиний аргумент, цей DICT містить змінену конфігурацію плагіна, яка створюється шляхом взяття конфігурації плагіна за замовчуванням, що повертається plugin_info, і додавання до неї значень, які користувач налаштував раніше. Це робоча конфігурація, яку має використовувати плагін.
Типова реалізація виклику plugin_init створить екземпляр класу Python, який є основною частиною плагіна. Потім буде повернуто об'єкт, який є дескриптором, що передаватиметься у наступних викликах. Цей дескриптор у простому плагіні, як правило, є Python DICT, тобто конфігурацією плагіна, однак можуть бути повернуті будь-які значення. Викликувач розглядає хендл як непрозорі дані, які він зберігає і передає наступним викликам плагіна, він ніколи не зазирає всередину цього об'єкта і не має жодних очікувань щодо того, що зберігається в цьому об'єкті.
Реалізація плагіна fledge-north-http для plugin_init показана нижче як приклад
def plugin_init(data):
""" Used for initialization of a plugin.
Args:
data - Plugin configuration
Returns:
Dictionary of a Plugin configuration
"""
global http_north, config
http_north = HttpNorthPlugin()
config = data
return config
У цьому випадку плагін створює об'єкт, який реалізує функціональність, і зберігає цей об'єкт у глобальній змінній. Це можна зробити, оскільки в межах одного процесу існує лише один екземпляр північного плагіна. Однак, можливо, краще повертати екземпляр класу в дескрипторі, а не використовувати глобальну змінну. Використання глобальної змінної не рекомендується для плагінів-фільтрів, оскільки у межах одного процесу може існувати декілька екземплярів фільтра. У цьому випадку плагін використовує конфігурацію як дескриптор, що повертається.
Виклик plugin_send
Виклик plugin_send є основною точкою входу до північного плагіна, він використовується для надсилання набору зчитувань на північ до системи призначення. Він відповідає як за зв'язок з цією системою, так і за перетворення внутрішнього представлення даних зчитування у представлення, необхідне для зовнішньої системи.
Для зв'язку, що виконується процедурою plugin_send, слід використовувати примітиви асинхронного вводу/виводу Python 3, визначення точки входу plugin_send також повинно використовувати ключове слово async.
Точці входу plugin_send передається 3 аргументи: дескриптор плагіна, дані для надсилання та ідентифікатор потоку.
async def plugin_send(handle, payload, stream_id):
Дескриптор - це непрозорі дані, що повертаються викликом plugin_init і можуть використовуватися плагіном для зберігання даних між викликами. Корисне навантаження - це набір даних, які слід надіслати, див. нижче для більш детальної інформації про обробку корисного навантаження. Ідентифікатор потоку - це ціле число, яке унікально ідентифікує з'єднання від цього екземпляра Fledge до системи призначення. Цей ідентифікатор можна використовувати, якщо плагіну потрібен унікальний ідентифікатор, але у більшості випадків його можна ігнорувати.
Виклик plugin_send повертає три значення: логічне значення, яке вказує, чи були надіслані дані, ідентифікатор об'єкта останнього надісланого зчитування та кількість надісланих зчитувань.
Нижче наведено код виклику plugin_send для плагіна http north.
async def plugin_send(handle, payload, stream_id):
""" Used to send the readings block from north to the configured destination.
Args:
handle - An object which is returned by plugin_init
payload - A List of readings block
stream_id - An Integer that uniquely identifies the connection from Fledge instance to the destination system
Returns:
Tuple which consists of
- A Boolean that indicates if any data has been sent
- The object id of the last reading which has been sent
- Total number of readings which has been sent to the configured destination
"""
try:
is_data_sent, new_last_object_id, num_sent = await http_north.send_payloads(payload)
except asyncio.CancelledError:
pass
else:
return is_data_sent, new_last_object_id, num_sent
Виклик plugin_shutdown
Виклик plugin_shutdown є останнім записом, необхідним для північного плагіна Python, він викликається північним сервісом або завданням безпосередньо перед завершенням завдання або у північному сервісі, якщо це дозволено конфігурацією, див. реконфігурацію нижче. Виклику plugin_shutdown передається дескриптор плагіна, і він має виконати будь-як е очищення, що вимагається плагіном.
def plugin_shutdown(handle):
""" Used when plugin is no longer required and will be final call to shutdown the plugin. It should do any necessary cleanup if required.
Args:
handle - Plugin handle which is returned by plugin_init
Returns:
"""
Виклик не повинен повертати жодних даних. Після виклику дескриптор більше не повинен вважатися дійсним, і подальші виклики плагіна за допомогою цього дескриптора не здійснюватимуться.
Реконфігурація
На відміну від інших плагінів у Fledge, північні плагіни не мають точки входу для реконфігурації, це пов'язано з оригінальним характером реалізації північної частини Fledge, яка використовувала короткочасні завдання для надсилання даних на північ. Кожне нове виконання створювало нове завдання з новою конфігурацією, тому вважалося, що реконфігурація додавала складності північним плагінам, якої можна було б уникнути.
Однак, після впровадження функції, яка дозволяє запускати північ як постійно увімкнений сервіс, це стало проблемою. Вона вирішується закриттям плагіна, викликом plugin_shutdown і подальшим перезапуском за допомогою plugin_init для передачі нової конфігурації і отримання нового дескриптора плагіна з цією новою конфігурацією.
Обробка корисного навантаження
Корисне навантаження, яке передається в процедуру plugin_send, — це список читань Python, кожне читання кодується як Python DICT. Властивості DICT для читання:
| Ключ | Опис |
|---|---|
| id | Ідентифікатор зчитування. Кожному зчитуванню присвоюється цілочисельний ідентифікатор, який є зростаючим значенням, саме ці значення ідентифікаторів використовуються для відстеження обсягу даних, що надсилаються через плагін north. Одним з результатів, що повертаються з процедури plugin_send, є ідентифікатор останнього успішно надісланого зчитування. |
| asset_code | Код ак тиву для зчитування. Як правило, південний сервіс генерує показники для одного або декількох кодів активів. Ці коди активів використовуються для ідентифікації джерела даних. В одному блоці зчитувань, що передається до процедури plugin_send, може з'явитися декілька кодів активів. |
| reading | Вкладений DICT Python, який зберігає фактичні точки даних, пов'язані зі зчитуванням. Ці DICT для зчитування містять пару ключ/значення для кожної точки даних в активі. Значення цієї пари є значенням точки даних і може бути числом, рядком, масивом або вкладеним об'єктом. |
| ts | Мітка часу, коли зчитування було вперше побачено системою. |
| user_ts | Мітка часу даних у зчитуванні. Вона може збігатися з ts вище або в деяких випадках може бути міткою часу, отриманою від самого джерела даних. Саме цю мітку часу слід вважати такою, що найточніше відображає часову мітку даних. |
Зразок корисного навантаження наведено нижче.
[{'reading': {'sinusoid': 0.0}, 'asset_code': 'sinusoid', 'id': 1, 'ts': '2021-09-27 06:55:52.692000+00:00', 'user_ts': '2021-09-27 06:55:49.947058+00:00'},
{'reading': {'sinusoid': 0.104528463}, 'asset_code': 'sinusoid', 'id': 2, 'ts': '2021-09-27 06:55:52.692000+00:00', 'user_ts': '2021-09-27 06:55:50.947110+00:00'}]
Плагіни на C/C++
The flow of a C/C++ plugin is very similar to that of a Python plugin, the entry points vary slightly compared to Python, mostly for language reasons.
Точка входу plugin_info
Plugin_info знову є першою точкою входу, яка буде викликана, у випадку плагіна C/C++ він повертатиме вказівник на структуру PLUGIN_INFORMATION, ця структура містить ті самі елементи, що й у Python DICT, які повертає Плагіни Python.
static PLUGIN_INFORMATION info = {
PLUGIN_NAME, // Name
VERSION, // Version
0, // Flags
PLUGIN_TYPE_NORTH, // Type
"1.0.0", // Interface version
default_config // Configuration
}
Слід зазначити, що екземпляр структури PLUGIN_INFORMATION оголошено як статичний. Усі глобальні змінні, оголошені за допомогою C/C++ плагіна, мають бути оголошені як статичні, оскільки механізм завантаження плагінів буде спільно використовувати глобальні змінні між плагінами. Використання істинних глобальних змінних може призвести до непередбачуваної взаємодії між плагінами.
До таких змінних відносяться
| Назва | Опис |
|---|---|
| name | Назва плагіну. |
| version | Версія плагіна, виражена у вигляді рядка. Зазвичай, але не завжди, це відповідає поточній версії Fledge. |
| flags | Растрова карта прапорів, які надають додаткову інформацію про плагін. |
| interface | Версія інтерфейсу, наразі північні плагіни мають інтерфейс версії 1.0.0. |
| config | Стандартна конфігурація плагіна. У плагінах C/C++ це повертається як рядок, що містить структуру JSON. |
Деякі прапори підтримуються плагінами, однак невелика підмножина підтримується північними плагінами, ця підмножина складається з
| Назва | Опис |
|---|---|
| SP_PERSIST_DATA | Плагін зберігає дані та використовує розширення API збереження даних. |
| SP_BUILTIN | Плагін вбудовано в базовий пакет Fledge. Це не слід використовувати для плагінів, доданих користувачем. |
Типова реалізація запису plugin_info просто повертатиме структуру PLUGIN_INFORMATION для плагіна.
PLUGIN_INFORMATION *plugin_info()
{
return &info;
}
Більш складні реалізації можуть адаптувати вміст інформації, що повертається, на основі певних критеріїв, визначених під час виконання. Прикладом такого скрипту може бути адаптація конфігурації за замовчуванням на основі певного елемента виявлення, який відбувається під час виконання. Наприклад, якщо плагін призначено для надсилання даних до іншого сервісу, точка входу plugin_info може виконати певне виявлення сервісу та оновити набір опцій для перелічених типів у конфігурації за замовчуванням. Це дозволить користувацькому інтерфейсу надати користувачеві список для вибору всіх екземплярів сервісів, які було знайдено під час запуску плагіна.
Точка входу plugin_init
Точка входу plugin_init викликається після того, як конфігурація плагіна була побудована шляхом поєднання конфігурації за замовчуванням з будь-якою збереженою конфігурацією, яку користувач встановив для плагіна. Конфігурація передається у вигляді вказівника на об'єкт C++ класу ConfigCategory. Цей об'єкт потім може бути використаний для вилучення даних з конфігурації.
Виклик plugin_init слід використовувати для ініціалізації самого плагіна, а також для вилучення конфігурації для екземпляра ConfigCategory і зберігання в екземплярі плагіна. Детальнішу інформацію про використання класу ConfigCategory можна знайти у розділі "Клас підтримки C++" Посібника для розробників плагінів. Зазвичай північний плагін створює екземпляр класу, який реалізує необхідну функціональність, зберігає конфігурацію у цьому класі і повертає вказівник на цей екземпляр як дескриптор для плагіна. Це гарантує, що наступні виклики зможуть отримати доступ до цього екземпляра класу і пов'язаного з ним стану, оскільки всі майбутні виклики передаватимуть дескриптор як аргумент.
Нижче наведено, мабуть, найбільш загальну форму виклику plugin_init.
PLUGIN_HANDLE plugin_init(ConfigCategory *configData)
{
return (PLUGIN_HANDLE)(new myNorthPlugin(configData));
}
У цьому випадку передбачається, що у нас є клас myNorthPlugin, який реалізує функціональність плагіна. Конструктор отримує в якості аргументу вказівник ConfigCategory і виконує всю необхідну ініціалізацію з цієї категорії конфігурації.
Точка входу plugin_send
Точка входу plugin_send, як і у випадку з вже описаними плагінами Python, є серцем північного плагіна. Вона викликається з дескриптором плагіна і блоком даних зчитування, які мають бути надіслані на північ. Зазвичай plugin_send витягує об'єкт, створений у виклику plugin_init, з дескриптора, а потім викликає функціонал всередині цього об'єкта, щоб виконати будь-яку логіку перекладу і зв'язку, необхідну для надсилання даних зчитування.
uint32_t plugin_send(PLUGIN_HANDLE handle, std::vector<Reading *>& readings)
{
myNorthPlugin *plugin = (myNorthPlugin *)handle;
return plugin->send(readings);
}
Блок зчитувань надсилається як стандартний бібліотечний шаблон C++ вектор вказівників на екземпляр класу Reading, який також описано вище у розділі "Класи підтримки C++".
Поверненням функції plugin_send має бути підрахунок кількості показів, надісланих плагіном.
Точка входу plugin_shutdown
Точка входу plugin_shutdown викликається, коли плагін більше не потрі бен. Вона має виконати будь-яке необхідне очищення. Як і у випадку з іншими точками входу, вона викликається з дескриптором, який було повернуто за допомогою plugin_init. У випадку нашого простого плагіна це може бути просто видалення об'єкта C++, який реалізує функціональність плагіна.
void plugin_shutdown(PLUGIN_HANDLE handle)
{
myNorthPlugin *plugin = (myNorthPlugin *)handle;
delete plugin;
}
Точка входу plugin_register
Точка входу plugin_register використовується для передачі плагіну двох вказівників на функції. Ці вказівники функцій є функціями, які слід викликати, коли потрібно записати задане значення або виконати операцію з заданим значенням. Плагін повинен зберігати ці вказівники функцій для подальшого використання.
void plugin_register(PLUGIN_HANDLE handle, (bool ( *write)(char *name, char *value, ControlDestination destination, ...), int (* operation)(char *operation, int paramCount, char *parameters[], ControlDestination destination, ...))
{
myNorthPlugin *plugin = (myNorthPlugin *)handle;
plugin->setpointCallbacks(write, operation);
}
Цей виклик буде здійснено, лише якщо плагін містить опцію SP_CONTROL у полі прапорців структури PLUGIN_INFORMATION.
Контроль заданої точки
Fledge підтримує декілька шляхів для контролю заданих значень, один з цих шляхів дозволяє північному сервісу бути двонаправленим, коли північний плагін отримує тригер від системи на північ від Fledge, щоб виконати контроль заданого значення. Цим тригером може бути опитування північним плагіном системи або відповідь протоколу з півночі.
Керування за уставкою доступне лише для північних сервісів, воно не підтримується для північних завдань і буде проігноровано.
Коли північному плагіну потрібно виконати операцію запису уставки, він викликає зворотний виклик write, який було передано плагіну у точці входу plugin_register. Цей зворотний виклик приймає декілька аргументів;
Ім'я уставки, яку потрібно записати.
Значення, яке потрібно записати до уставки. Завжди виражається у вигляді рядка.
Місце призначення операції запису. Передається за допомогою перечислюваного типу ControlDestination. Наразі це може бути одне з наступних значень
- DestinationBroadcast: надсилає операцію запису до всіх південних сервісів, які підтримують керування.
- DestinationAsset: надсилає запит на запис до південного сервісу, відповідального за поглинання даного активу. Актив передається як наступний аргумент у виклику write.
- DestinationService: надсилає запит на запис до вказаного південного сервісу.
Наприклад, якщо північний плагін хоче записати уставку під назвою швидкість зі значенням 28 у південний сервіс під назвою Керування двигуном, він зробить виклик наступним чином.
(*m_write)("speed", "28", DestinationService, "Motor Control");
Припустимо, що змінна-член m_write була використана для зберігання вказівника функції зворотного виклику write.
Якщо північному плагіну потрібно виконати операцію, а не запис, то він має викликати operation, який було передано йому у виклику plugin_register. Цей виклик приймає набір аргументів;
- Назва операції, яку потрібно виконати.
- Кількість параметрів, я кі слід передати операції.
- Масив параметрів у вигляді рядків, які потрібно передати операції
- Призначення операції, це той самий набір призначень, що і для виклику запису.