Як я зробив офлайн голосовий асистент для розумного будинку на Raspberry Pi — і чому відмовився від хмари
Мене звати Іван, я solo-розробник і вже понад рік будую SelenaCore — open-source хаб для розумного будинку, який працює повністю офлайн, підтримує українську мову та не відправляє жодних даних у хмару. Ця стаття — для тих, хто хоче зрозуміти, як влаштований такий проєкт зсередини, які технічні рішення я приймав і з якими реальними проблемами зіткнувся.


Чому я взагалі за це взявся
Все почалося з простого питання: чому мій розумний будинок не розуміє мене українською?
Google Home і Amazon Alexa — хороші продукти, але вони вимагають постійного інтернету, відправляють голосові запити на сервери компанії і не дуже добре розуміють українську. Home Assistant вирішує частину проблем, але голосове управління там — окрема складна тема, і теж переважно через хмарні сервіси.
Я почав думати: а що якщо зробити все локально? STT на пристрої, LLM на пристрої, TTS на пристрої. Без хмари взагалі. Щоб навіть при відключенні інтернету все працювало.
Так з’явився SelenaCore.
Архітектура: як це влаштовано
Загальна схема
Система складається з кількох шарів:
- Офлайн голосовий pipeline — wake-word детекція → STT → обробка → відповідь через TTS
- Pluggable LLM layer — підтримує Ollama (локальні моделі) та хмарні провайдери як опцію
- Модульна система — 21 вбудований модуль для управління пристроями, таймерами, погодою тощо
- React SPA фронтенд — веб-інтерфейс для управління та моніторингу
- Docker-based деплоймент — все запускається в контейнерах
Апаратна база — Raspberry Pi 4/5 або NVIDIA Jetson Orin Nano. Вибір залежить від того, чи потрібен LLM inference на борту.
STT: чому Vosk, а не Whisper
Коли я вибирав між Vosk і Whisper для офлайн розпізнавання мовлення, виграв Vosk — і не через якість, а через швидкість і пам’ять.
Whisper дає кращу точність, особливо для нестандартних фраз. Але на Raspberry Pi 4 він занадто повільний для realtime-інтерфейсу. Vosk з моделлю vosk-model-uk дає прийнятну точність і відповідає за 300—500мс, що цілком комфортно для голосового асистента.
Є один важливий нюанс: STT недосконалий. Vosk може розпізнати «включи лампочку» як «включи лампу» або «ввімкни лапку». Це реальна проблема, яка вплинула на всю архітектуру Intent Recognition — але про це нижче.
TTS: Piper і локальні голоси
Для синтезу мови використовую Piper — швидкий офлайн TTS з непоганою якістю Ukrainian голосів. Він працює на CPU без GPU-прискорення і дає затримку ~150—300мс на Pi 4, що прийнятно.
Спочатку я планував просто передавати всі команди напряму в LLM і отримувати відповіді. Але виявилось, що це занадто дорого по латентності і токенах, коли кожен «увімкни світло» йде на LLM з повним контекстом.
Intent Recognition: від простого до three-tier pipeline
Це виявилось найскладнішою частиною проєкту.
Перша версія — regex-based матчинг. Просто: якщо фраза містить «увімкни» і «лампу» — це device.on. Спрацьовувало для простих випадків, але повністю ламалось при будь-яких варіаціях.
Друга версія — LLM для всього. Кожен запит йде в модель. Точність чудова, але затримка
Поточна версія — три рівні:
- Tier 0: Fuzzy phrase cache — база вже оброблених фраз з fuzzy matching. Якщо «включи лампочку у вітальні» вже зустрічалась і вирішена — відповідь миттєва.
- Tier 1: Embedding classifier —
all-MiniLM-L6-v2через ONNX (без Python ML залежностей). Класифікує intent по векторній схожості. Працює за ~200мс. - Tier 2: LLM — тільки для складних або невизначених запитів, де embedding не впевнений.
Результат: 92.9% точності на тестовому корпусі з 70 українських голосових команд, середня затримка embedding-класифікації ~200мс.
Багатомовність: архітектурне рішення
Одна з ключових фічей — підтримка кількох мов. Але реалізована вона нестандартно.
Вся внутрішня обробка відбувається лише англійською. Переклад відбувається тільки на двох межах:
- Vosk output (будь-яка мова → англійська) через Helsinki-NLP
opus-mt-mul-enз CTranslate2 int8 квантизацією (~300MB) - Перед Piper TTS (англійська → мова користувача)
Чому так? Тому що
На практиці: «увімкни лампу у вітальні» → переклад → «Turn on the lamp in the living room» → embedding classifier → device.on, entity=light, location=living room → виконання → «Лампу у вітальні увімкнено» через TTS.
Реальні проблеми, з якими я зіткнувся
Проблема 1: «turn the tape on» vs «turn on the light»
Helsinki-NLP перекладає «увімкни стрічку в кабінеті» як «Turn the tape on in your office».
Слово «tape» замість «LED strip» або «light strip» — і embedding classifier плутається між device.on і device.off з margin лише 0.003. Ця крихкість перекладу — реальна проблема офлайн pipeline без LLM семантичного згладжування.
Вирішення поки що часткове: phrase_cache + накопичення правильних перекладів через LLM Tier 2 в базу.
Проблема 2: ambiguous entity names
Якщо в базі є «Кондиціонер» з type=air_conditioner, модель embedding бачить «Turn on the air conditioning» і класифікує це як device.set_mode, бо в тренувальних даних «air conditioning» часто асоційоване з режимами, а не вмиканням.
Рішення: для типів пристроїв (thermostat, air_conditioner) додав explicit override: якщо немає value у параметрах і є базова команда «turn on/off» — форсую device.on/off.
Проблема 3: RAM на Raspberry Pi 4
Pi 4 з 4GB RAM тримає:
- Vosk STT model: ~200MB
- ONNX embedding model: ~90MB
- Helsinki translator: ~300MB
- Piper TTS: ~80MB
- FastAPI сервер: ~150MB
- React SPA: ~50MB
Разом ~870MB тільки для core stack. Ollama з llama3.1:8b ще ~5GB. Тому на Pi 4 Ollama = тільки якщо є swap і ви готові до 3+ секунд inference.
Поточне рішення: Tier 2 LLM може бути хмарним (Claude API) — це опція для тих, хто хоче кращу якість без потужного заліза. Або NVIDIA Jetson Orin Nano — там NVMe + LPDDR5 дозволяє комфортно запускати 8B моделі.
Проблема 4: display stack
Хотів мати гарний fullscreen дашборд. Chromium kiosk mode споживає ~300MB RAM і важко оптимізується на Pi.
Зараз переходжу на WPE WebKit через cog — це ~50MB і набагато легший footprint. Але є нюанс: на NVIDIA Jetson Tegra DRM несумісний з wlroots, тому fallback до Xorg + Chromium там залишається.
Що зараз вміє система
- Управління пристроями через голос: увімкнути/вимкнути світло, регулювати яскравість, колір, температуру
- Підтримка Tuya-сумісних пристроїв через local protocol (без хмари)
- Інтеграція з Home Assistant через WebSocket
- Таймери, будильники, нагадування
- Запит температури з датчиків
- Ідентифікація хто вдома через presence detection
- Радіо (online stream)
- Повністю офлайн режим — все працює без інтернету
Що в планах
Основна мета на наступний квартал — довести translation pipeline до production-ready стану і зробити перший публічний реліз з повноцінними docker-образами для Pi 4, Pi 5 і Jetson.
Також думаю над spell correction через symspellpy для компенсації STT помилок — але це після накопичення достатнього логу реальних фраз.
Висновок
SelenaCore — це доказ того, що повністю офлайн голосовий асистент для розумного будинку з підтримкою української мови реальний на доступному залізі. Це не комерційний продукт і не спроба замінити Home Assistant — це інструмент для тих, кому важлива приватність і хто хоче розуміти, що відбувається всередині.
Проєкт open-source, MIT ліцензія. Якщо вам цікаво — заходьте на GitHub (github.com/dotradepro/SelenaCore) і зірочка буде дуже доречна 🙂
Якщо маєте питання про архітектуру, конкретні рішення чи хочете обговорити — пишіть у коментарях, постараюсь відповісти на все
17 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівДуже хороша ініціатива, дякую!
а взагалі розуміють? у Вас є досвід налаштування? було б цікаво
Я поки зупинився на хмарних Gemini моделях. Безкоштовних лімітів наразі вистачає на «побавитися».
Маю ще ollama на ігровому десктопі, який прокидається Wake-on-Wlan пакетом, коли хтось стукає в Ollama API, але то не дуже зручно чекати поки воно прокинеться в повсякденному використанні.
Іване, крута стаття — читав і впізнавав себе на кожному кроці 😄
Я теж нещодавно зробив voice dictation, тільки з іншого боку: замість розумного будинку — прямо в VS Code/Cursor. Мікрофон у статус-барі, Gemini транскрибує, текст летить у чат з AI-асистентом. Шлях хмарний (протилежність до твого), але задача та сама — прибрати клавіатуру між мозком і інструментом.
Найбільший інсайт збігається: найскладніше не «зробити щоб працювало», а зробити щоб latency не вбивала UX. У тебе 150-300мс на Pi — повага, це реально непогано для edge inference.
Зірочку поставив, слідкуватиму за релізом 🚀
Дякую за зірочку!
Згоден, затримка — це найбільший біль. Якщо асистент тупить, юзер експірієнс одразу в мінус. Тому я так і заморочився з локалкою на «малинці», щоб витиснути максимум швидкості.
Кейс із диктуванням коду в Cursor — вогонь. Хоч підходи й різні (хмара vs локалка), але мета одна — звільнити руки. Успіхів з проєктом!
Ollama з інференсом на CPU чи GPU?
Можливо варто розглянути llama.cpp, він дає кращій перформанс.
Доволі суттєвий скачок до jetson nano. Не розглядалась та сама Raspberry Pi 4 з 8Gb або RaspberryPi 8/16Gb? Більше пам’яті дасть кращій перформанс, а 5та ще й значно потужніша. З переваг все те саме оточення і ціна суттєво нижча.
В будь якому разі чудовий проєкт. Успіхів.
Дякую, Serhii!
Щодо llama.cpp — теоретично на CPU він може давати кращий результат завдяки більш агресивній оптимізації під конкретне залізо, але ми це окремо не бенчмаркали. Якщо у вас є реальні цифри порівняння на ARM — було б дуже цікаво подивитись, можливо варто розглянути як альтернативний бекенд.
Щодо продуктивності — поділюсь реальними результатами наших тестів:
Канонічний бенч (40 голосових команд, uk/en):
• Точність розпізнавання інтентів: 94.3%
• Embedding класифікатор (MiniLM-L6-v2, 22 MB): classify p50 = 41ms
• E2E з перекладом Helsinki-NLP: p50 = 155ms
Stress-бенч (70 команд, включно з мусорними, заїканнями, сленгом):
• Точність: 82.5% на Pi 5
• Filler words (ну, um, like): 100% — модель їх просто ігнорує
• Абракадабра / нерозбірливе: коректно йде в unknown
Ключовий момент: GPU потрібен тільки дляLLM-інференсу (складний діалог, нові фрази). Розпізнавання команд через embedding-класифікатор працює блискавично на будь-якому залізі — 22 MB модель, CPU-only, без GPU взагалі. Більшість домашніх команд обробляється за 100-200ms end-to-end.
Для звичайного розумного будинку Pi 5 8GB більш ніж достатньо. Pi 5 16GB дає змогу запускати більшу LLM для діалогу — але на розпізнаванні команд це не відображається.
Доповню: Ollama вже не вбудований провайдер у SelenaCore. Винесли його нарівні з Claude, OpenAI та іншими — як зовнішній LLM через REST API. Користувач вказує endpoint у налаштуваннях і підключає той інференс, що йому зручно: Ollama, llama.cpp у server mode, LocalAI, чи хмару.
Причина та, про яку ви пишете: різне залізо і ОС не дають єдиної оптимізації. Хтось витисне максимум із llama.cpp під свій CPU, хтось — із Ollama + CUDA на Jetson. Тому ядро бекенд-агностичне — кожен обирає під себе.
Я в захваті від локальних голосових помічників, особливо українською мовою. Для мене LMM що працюють локально це досі щось нове та дике. Та і сама ідея україномовних голосових помічників ще три чотири роки тому також вважалось чимось з сфери фантастики. Дякую вам за роботу і статтю з нетерпінням чекаю docker образ
Богдане, дякую за теплі слова! Так, попередня бета вже доступна на GitHub — github.com/dotradepro/SelenaCore. Вона сирувата і ще не обкатана, але я намагаюсь рухатись максимально швидко.
Паралельно треную голосові моделі Piper для україномовних відповідей. Зараз, наприклад, іде тренування голосу Леся Подерев’янського — ще трохи кульгає, але вже є впізнаваний характерний голос 😄
Docker-образ буде — слідкуйте за репозиторієм!
А голоси можна тренувати? Це як? В мене знову вибух мозку. До речі про локальні LLM встановив собі програму Ollama та локальну LLM gemma4:e2b
Так, тренування голосу — реально! Я навіть зробив окремий інструмент для цього: github.com/dotradepro/Piper-Master-Trainer2–3 години навчання, причому відео брав просто з публічних виступів — і модель дуже швидко почала говорити його голосом
Принцип простий: берете аудіо з виступів потрібної людини, запускаєте тренування — і модель вчиться відтворювати голос. На прикладі Трампа мені вистачило
Ollama + gemma4b — хороший старт для локального LLM! Для розумного будинку з простими командами навіть невелика модель справляється непогано.
Так, при бажанні можна навіть відтворити голос улюбленого персонажа з ігор/фільмів, якщо достатньо звукових семплів назбирати. Так наприклад, мій асистент розмовляє голосом GLaDOS з Portal. Мопед не мій, але звучить круто) Ще й «характер» можна відповідний налаштувати, відредагувавши системний промпт.
github.com/...uan-binh/glados-respeaker
Ваша glados україномовна чи англомовна? Дуже люблю glados і portal, навіть озвучку українську голосом glados зробив youtu.be/Gk78p5PVVB8, але мені озвучувала акторка дубляжу github.com/sverdlyuk/glados_ukr у вас respeaker lite? Я не зміг його примусити працювати і купив voice assistant preview edition
В мене англомовна, як в оригіналі. В цілому, наразі багато готових тулів та автоматизацій для Home Assistant Voice Pipeline, який я використовую, зроблено з розрахунком саме на англійську, тож використовую що є. Геть усе переписувати нажаль немає часу)
Я так розумію розмовний агент в pipeline це LLM, а Text-to-Speech це озвучка голосом. Що у вас використовується? У мене ось так
res.cloudinary.com/.../ahof0rxt4bukgoiianhd.jpg
Whisper -> Gemini (aka Google Generative AI) -> Piper
Все це (звісно, за винятком LLM) крутиться на Mini PC з Intel N100, без GPU.