-
-
Notifications
You must be signed in to change notification settings - Fork 2
uk Module Bus Protocol
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) |
|---------------------------------------------->|
-
Підключення -- Модуль відкриває WebSocket-з'єднання з
?token=TOKENяк параметром запиту. -
Автентифікація -- Ядро перевіряє токен до виклику
accept(). Недійсний токен призводить до негайного закриття з кодом4001. -
Оголошення -- Модуль надсилає повідомлення
announce, де оголошує свою назву, версію та можливості. -
Реєстрація -- Ядро перевіряє оголошення, реєструє модуль та відповідає
announce_ackз призначенимbus_id. - Цикл повідомлень -- Починається двонаправлена комунікація. Модуль може надсилати та отримувати всі підтримувані типи повідомлень.
-
Перевірки працездатності -- Ядро надсилає
pingкожні 15 секунд. Модуль повинен відповістиpong. Три послідовні пропущені ping призводять до відключення (код закриття4004). -
Завершення -- Ядро надсилає повідомлення
shutdownз вікномdrain_ms. Модуль повинен завершити поточну роботу протягом цього вікна та коректно вийти.
Кожне повідомлення -- це JSON-об'єкт з обов'язковим полем type:
{"type": "<message_type>", ...}Наступні розділи визначають кожен тип повідомлення, його напрямок та схему.
Напрямок: модуль -> ядро
Надсилається одразу після прийняття 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.
Напрямок: модуль -> ядро
Ідентична схема до announce, але з "type": "re_announce". Дозволяє модулю оновити свої можливості "на льоту" (додати/видалити інтенти, змінити підписки) без розриву WebSocket-з'єднання.
{
"type": "re_announce",
"module": "weather-module",
"version": "1.1.0",
"capabilities": { ... }
}Ядро атомарно замінює зареєстровані можливості модуля та відповідає новим announce_ack.
Напрямок: ядро -> модуль
Підтверджує успішну реєстрацію або перереєстрацію.
{
"type": "announce_ack",
"bus_id": "uuid-1234",
"warnings": []
}| Поле | Тип | Опис |
|---|---|---|
bus_id |
string | UUID, призначений ядром. Використовується внутрішньо для маршрутизації. |
warnings |
array | Список нефатальних попереджень (наприклад, невідомі шаблони підписки). |
Напрямок: ядро -> модуль
Відправляється, коли запит користувача збігається з одним із зареєстрованих шаблонів інтентів модуля.
{
"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. Поле 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 спроби передачі загалом).
Напрямок: двонаправлений
Використовується для розсилки подій за моделлю публікація/підписка.
{
"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 тощо.
Напрямок: двонаправлений
Механізм перевірки працездатності.
{"type": "ping", "ts": 1711900000}{"type": "pong", "ts": 1711900000}| Поле | Тип | Опис |
|---|---|---|
ts |
integer | Unix-мітка часу (секунди) джерела ping. Повертається у pong. |
Ядро надсилає ping кожні 15 секунд. Модуль повинен відповісти pong з тим самим значенням ts. Після 3 послідовних пропущених ping ядро закриває з'єднання з кодом 4004.
Напрямок: модуль -> ядро
Дозволяє модулю викликати 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_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.
Напрямок: ядро -> модуль
Надсилається, коли ядро завершує роботу або явно відключає модуль.
{
"type": "shutdown",
"drain_ms": 5000
}| Поле | Тип | Опис |
|---|---|---|
drain_ms |
integer | Мілісекунди, які модуль має для завершення поточної роботи перед розривом з'єднання. |
Модуль повинен завершити всі очікувані операції протягом зазначеного вікна, а потім закрити своє з'єднання.
Кожне WebSocket-з'єднання підтримує дві внутрішні черги для розділення трафіку за пріоритетом:
| Черга | Макс. розмір | Політика переповнення | Типи повідомлень |
|---|---|---|---|
| Критична | 100 | Зворотний тиск (блокує відправника) |
intent, intent_response, api_request, api_response
|
| Подієва | 1000 | Видалення найстаріших | event |
Корутина запису завжди спочатку обробляє критичну чергу. Це гарантує, що обробка інтентів та API-виклики ніколи не будуть заблоковані сплеском подієвого трафіку.
Дозволи модулів оголошуються в 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.
Автоматичний вимикач для кожного модуля захищає систему від модулів, що не відповідають:
- Замкнений (нормальний) -- Інтенти маршрутизуються до модуля як зазвичай.
- Розімкнений (спрацьований) -- Модуль виключається з маршрутизації інтентів. Спрацьовує, коли модуль систематично перевищує час очікування на запити інтентів.
-
Відновлення -- Через 30 секунд у розімкненому стані вимикач дозволяє пробний запит. Успішний
intent_responseповертає вимикач у замкнений стан.
Автоматичний вимикач впливає лише на маршрутизацію інтентів. Події та API-запити продовжують працювати нормально, поки вимикач розімкнений.
Коли надходить запит користувача, ядро обробляє його через шину:
- Викликається
route_intent(text, lang, context). - Вхідний текст зіставляється зі скомпільованим індексом регулярних виразів, побудованим з шаблонів інтентів усіх підключених модулів. Зіставлення нечутливе до регістру.
- Усі збіги сортуються за
priority(менше значення = вищий пріоритет). - Повідомлення
intentнадсилається до першого модуля, що збігся. - Якщо модуль відповідає з
handled: false, пробується наступний збіг. - Максимум 3 спроби передачі до того, як запит вважається необробленим.
- Кожен модуль має 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"}]}}
🤖 This wiki is auto-synced from docs/ in the main repo. Hand-edits on the wiki UI get overwritten on the next push. Open a PR against the main repo instead.
MIT License · Sponsor · Ko-fi
SelenaCore
🇬🇧 English
Getting started
Architecture
Voice & translation
Hardware integration
Development
- Modules overview
- Module development
- System module development
- Module API guide
- Module bus protocol
- Widget development
- User manager / auth
Reference
🇺🇦 Українська
Початок
Архітектура
Голос і переклад
Інтеграція заліза
Розробка
- Розробка модулів
- Розробка системних модулів
- Module API
- Module bus
- Widget development
- User manager / auth
Довідник