Skip to content

uk Module Bus Protocol

wiki-sync edited this page Apr 15, 2026 · 1 revision

Довідник протоколу WebSocket Module Bus

SelenaCore Module Bus -- це комунікаційний рівень, натхненний CAN-шиною, де ядро виступає головним вузлом, а зовнішні модулі підключаються як рівноправні учасники через єдину точку WebSocket. Цей документ є авторитетним довідником протоколу для розробників модулів.

Точка підключення

ws://<host>/api/v1/bus?token=<module_token>

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


Життєвий цикл з'єднання

Module                                          Core
  |                                               |
  |  WebSocket connect ?token=TOKEN               |
  |---------------------------------------------->|
  |                          token validation      |
  |                          (reject -> close 4001)|
  |                                               |
  |              WebSocket accept()               |
  |<----------------------------------------------|
  |                                               |
  |  announce {...capabilities}                   |
  |---------------------------------------------->|
  |                                               |
  |              announce_ack {bus_id}            |
  |<----------------------------------------------|
  |                                               |
  |       bidirectional message loop              |
  |<--------------------------------------------->|
  |                                               |
  |              ping (every 15s)                 |
  |<----------------------------------------------|
  |  pong                                         |
  |---------------------------------------------->|
  |                                               |
  |              shutdown {drain_ms}              |
  |<----------------------------------------------|
  |  (finish work, close connection)              |
  |---------------------------------------------->|

Кроки

  1. Підключення -- Модуль відкриває WebSocket-з'єднання з ?token=TOKEN як параметром запиту.
  2. Автентифікація -- Ядро перевіряє токен до виклику accept(). Недійсний токен призводить до негайного закриття з кодом 4001.
  3. Оголошення -- Модуль надсилає повідомлення announce, де оголошує свою назву, версію та можливості.
  4. Реєстрація -- Ядро перевіряє оголошення, реєструє модуль та відповідає announce_ack з призначеним bus_id.
  5. Цикл повідомлень -- Починається двонаправлена комунікація. Модуль може надсилати та отримувати всі підтримувані типи повідомлень.
  6. Перевірки працездатності -- Ядро надсилає ping кожні 15 секунд. Модуль повинен відповісти pong. Три послідовні пропущені ping призводять до відключення (код закриття 4004).
  7. Завершення -- Ядро надсилає повідомлення shutdown з вікном drain_ms. Модуль повинен завершити поточну роботу протягом цього вікна та коректно вийти.

Формат повідомлень

Кожне повідомлення -- це JSON-об'єкт з обов'язковим полем type:

{"type": "<message_type>", ...}

Наступні розділи визначають кожен тип повідомлення, його напрямок та схему.


Типи повідомлень

announce

Напрямок: модуль -> ядро

Надсилається одразу після прийняття WebSocket-з'єднання. Оголошує ідентичність модуля та його можливості.

{
  "type": "announce",
  "module": "weather-module",
  "version": "1.0.0",
  "capabilities": {
    "intents": [
      {
        "patterns": {
          "en": ["weather", "forecast"]
        },
        "priority": 50,
        "description": "Weather queries"
      }
    ],
    "subscriptions": ["device.state_changed"],
    "publishes": ["weather.module_started"]
  }
}
Поле Тип Опис
module string Унікальний ідентифікатор модуля, повинен збігатися з назвою в зареєстрованому маніфесті.
version string Версія модуля у форматі semver.
capabilities.intents array Список оголошень інтентів, які модуль може обробляти.
capabilities.intents[].patterns object Відображення коду мови на фрази-тригери. Враховується лише ключ en — мовлення іншими мовами проходить до LLM-рівня, який розпізнає будь-яку мову та повертає назву інтенту англійською. Інші мовні ключі ігноруються при індексуванні.
capabilities.intents[].priority integer Пріоритет маршрутизації. Менші значення обробляються першими.
capabilities.intents[].description string Зрозумілий людині опис групи інтентів.
capabilities.subscriptions array Типи подій, які модуль хоче отримувати. Підтримуються шаблони з підстановкою (наприклад, device.*).
capabilities.publishes array Типи подій, які модуль має право генерувати.

Якщо модуль не надішле announce протягом встановленого тайм-ауту, з'єднання закривається з кодом 4002.


re_announce

Напрямок: модуль -> ядро

Ідентична схема до announce, але з "type": "re_announce". Дозволяє модулю оновити свої можливості "на льоту" (додати/видалити інтенти, змінити підписки) без розриву WebSocket-з'єднання.

{
  "type": "re_announce",
  "module": "weather-module",
  "version": "1.1.0",
  "capabilities": { ... }
}

Ядро атомарно замінює зареєстровані можливості модуля та відповідає новим announce_ack.


announce_ack

Напрямок: ядро -> модуль

Підтверджує успішну реєстрацію або перереєстрацію.

{
  "type": "announce_ack",
  "bus_id": "uuid-1234",
  "warnings": []
}
Поле Тип Опис
bus_id string UUID, призначений ядром. Використовується внутрішньо для маршрутизації.
warnings array Список нефатальних попереджень (наприклад, невідомі шаблони підписки).

intent

Напрямок: ядро -> модуль

Відправляється, коли запит користувача збігається з одним із зареєстрованих шаблонів інтентів модуля.

{
  "type": "intent",
  "id": "uuid-request",
  "payload": {
    "text": "what's the weather?",
    "lang": "en",
    "context": {}
  }
}
Поле Тип Опис
id string Унікальний ідентифікатор запиту. Повинен бути повернутий у intent_response.
payload.text string Необроблений текст запиту користувача.
payload.lang string Визначений код мови (en, uk тощо).
payload.context object Довільний контекст з вихідної сесії.

Модуль повинен відповісти протягом 10 секунд, інакше запит вважається таким, що вичерпав час очікування.


intent_response

Напрямок: модуль -> ядро

Відповідь на повідомлення intent. Поле id повинно збігатися з оригінальним запитом.

{
  "type": "intent_response",
  "id": "uuid-request",
  "payload": {
    "handled": true,
    "tts_text": "It's currently 12°C and cloudy",
    "data": {
      "temperature": 12,
      "condition": "cloudy"
    }
  }
}
Поле Тип Опис
id string Повинен збігатися з id відповідного повідомлення intent.
payload.handled boolean true, якщо модуль успішно обробив інтент. false запускає передачу до наступного відповідного модуля.
payload.tts_text string Текст для синтезу мовлення у відповідь користувачу.
payload.data object Структуровані дані, що супроводжують відповідь. Схема залежить від модуля.

Якщо handled дорівнює false, ядро перенаправляє інтент до наступного придатного модуля (максимум 3 спроби передачі загалом).


event

Напрямок: двонаправлений

Використовується для розсилки подій за моделлю публікація/підписка.

{
  "type": "event",
  "payload": {
    "event_type": "device.state_changed",
    "data": {
      "device_id": "xxx",
      "state": {"power": true}
    }
  }
}
Поле Тип Опис
payload.event_type string Ідентифікатор типу події, розділений крапками.
payload.data object Довільне тіло події.

Модуль -> ядро: event_type перевіряється на відповідність оголошеному списку publishes модуля. Події, яких немає у списку, відхиляються.

Ядро -> модуль: Доставляється лише якщо event_type збігається з одним із шаблонів subscriptions модуля. Підтримується збіг за шаблоном -- device.* збігається з device.state_changed, device.added тощо.


ping / pong

Напрямок: двонаправлений

Механізм перевірки працездатності.

{"type": "ping", "ts": 1711900000}
{"type": "pong", "ts": 1711900000}
Поле Тип Опис
ts integer Unix-мітка часу (секунди) джерела ping. Повертається у pong.

Ядро надсилає ping кожні 15 секунд. Модуль повинен відповісти pong з тим самим значенням ts. Після 3 послідовних пропущених ping ядро закриває з'єднання з кодом 4004.


api_request

Напрямок: модуль -> ядро

Дозволяє модулю викликати REST API SelenaCore через шину без окремого HTTP-з'єднання. Дозволи контролюються системою ACL.

{
  "type": "api_request",
  "id": "req-uuid",
  "payload": {
    "method": "GET",
    "path": "/devices",
    "body": null
  }
}
Поле Тип Опис
id string Унікальний ідентифікатор запиту. Повертається у відповідному api_response.
payload.method string HTTP-метод: GET, POST, PATCH, DELETE.
payload.path string Шлях API (без префікса /api/v1).
payload.body object або null Тіло JSON-запиту. null для GET/DELETE.

api_response

Напрямок: ядро -> модуль

Відповідь на api_request.

{
  "type": "api_response",
  "id": "req-uuid",
  "payload": {
    "status": 200,
    "body": [
      {"device_id": "...", "name": "Kitchen Light"}
    ]
  }
}
Поле Тип Опис
id string Збігається з id вихідного api_request.
payload.status integer HTTP-еквівалентний код стану.
payload.body any Тіло відповіді. Структура відповідає відповідній точці REST API.

Неавторизовані запити отримують статус 403.


shutdown

Напрямок: ядро -> модуль

Надсилається, коли ядро завершує роботу або явно відключає модуль.

{
  "type": "shutdown",
  "drain_ms": 5000
}
Поле Тип Опис
drain_ms integer Мілісекунди, які модуль має для завершення поточної роботи перед розривом з'єднання.

Модуль повинен завершити всі очікувані операції протягом зазначеного вікна, а потім закрити своє з'єднання.


Система двох каналів

Кожне WebSocket-з'єднання підтримує дві внутрішні черги для розділення трафіку за пріоритетом:

Черга Макс. розмір Політика переповнення Типи повідомлень
Критична 100 Зворотний тиск (блокує відправника) intent, intent_response, api_request, api_response
Подієва 1000 Видалення найстаріших event

Корутина запису завжди спочатку обробляє критичну чергу. Це гарантує, що обробка інтентів та API-виклики ніколи не будуть заблоковані сплеском подієвого трафіку.


Система ACL

Дозволи модулів оголошуються в manifest.json модуля та перевіряються при кожному api_request. Відповідність дозволів до точок доступу:

Дозвіл Дозволені операції
devices.read GET /devices, GET /devices/{id}
devices.write POST /devices, PATCH /devices/{id}/state, DELETE /devices/{id}
events.subscribe Отримання подій, що збігаються з шаблонами підписки
events.publish POST /events/publish, генерація повідомлень event на шині

Запити, що перевищують надані модулю дозволи, отримують статус 403 у api_response.


Автоматичний вимикач (Circuit Breaker)

Автоматичний вимикач для кожного модуля захищає систему від модулів, що не відповідають:

  1. Замкнений (нормальний) -- Інтенти маршрутизуються до модуля як зазвичай.
  2. Розімкнений (спрацьований) -- Модуль виключається з маршрутизації інтентів. Спрацьовує, коли модуль систематично перевищує час очікування на запити інтентів.
  3. Відновлення -- Через 30 секунд у розімкненому стані вимикач дозволяє пробний запит. Успішний intent_response повертає вимикач у замкнений стан.

Автоматичний вимикач впливає лише на маршрутизацію інтентів. Події та API-запити продовжують працювати нормально, поки вимикач розімкнений.


Маршрутизація інтентів

Коли надходить запит користувача, ядро обробляє його через шину:

  1. Викликається route_intent(text, lang, context).
  2. Вхідний текст зіставляється зі скомпільованим індексом регулярних виразів, побудованим з шаблонів інтентів усіх підключених модулів. Зіставлення нечутливе до регістру.
  3. Усі збіги сортуються за priority (менше значення = вищий пріоритет).
  4. Повідомлення intent надсилається до першого модуля, що збігся.
  5. Якщо модуль відповідає з handled: false, пробується наступний збіг.
  6. Максимум 3 спроби передачі до того, як запит вважається необробленим.
  7. Кожен модуль має 10-секундний тайм-аут для відповіді.

Коди закриття

Код Назва Опис
4001 invalid_token Автентифікація не вдалася. Наданий токен відсутній, прострочений або недійсний.
4002 announce_timeout Модуль не надіслав повідомлення announce протягом необхідного тайм-ауту після підключення.
4003 invalid_json / expected_announce Порушення протоколу. Перше повідомлення не було валідним JSON або не було повідомленням announce.
4004 ping_timeout Перевірка працездатності не вдалася. Три послідовні ping залишилися без відповіді.
1001 core_shutdown Ядро коректно завершує роботу.

Приклад швидкого старту

Мінімальна сесія модуля:

1. Connect:    ws://localhost/api/v1/bus?token=abc123
2. Send:       {"type":"announce","module":"my-module","version":"0.1.0","capabilities":{"intents":[],"subscriptions":["device.*"],"publishes":[]}}
3. Receive:    {"type":"announce_ack","bus_id":"550e8400-e29b-41d4-a716-446655440000","warnings":[]}
4. Receive:    {"type":"ping","ts":1711900000}
5. Send:       {"type":"pong","ts":1711900000}
6. Receive:    {"type":"event","payload":{"event_type":"device.state_changed","data":{"device_id":"light-1","state":{"power":true}}}}
7. Send:       {"type":"api_request","id":"r1","payload":{"method":"GET","path":"/devices","body":null}}
8. Receive:    {"type":"api_response","id":"r1","payload":{"status":200,"body":[{"device_id":"light-1","name":"Kitchen Light"}]}}

Clone this wiki locally