Ми часто бачимо, як компанії замовляють мобільний додаток для керування IoT-пристроями, а через місяць стикаються з гальмами та крашами при додаванні 30-го девайса. Проблема не в «слабкому телефоні», а в архітектурі: кожен транспорт (MQTT, BLE, WebSocket) живе своїм життям, оновлення надходять нарізно, і UI починає смикатися. У цій статті — як ми будуємо єдину шину станів, щоб додаток працював плавно навіть із сотнею пристроїв.
Чому єдина шина станів?
У будь-якому IoT-хабі є кілька джерел даних: MQTT топики, BLE-характеристики, WebSocket-події, REST polling. Без центрального сховища кожен транспорт пише безпосередньо в UI — виходить каша з гонок і зайвих перемальовувань. Ми використовуємо єдиний Map<DeviceId, DeviceState>, що оновлюється атомарно через StateFlow (Android) або CurrentValueSubject (iOS). Це в 10 разів ефективніше, ніж окремі потоки на кожен девайс.
Порівняння підходів до керування станом
| Підхід | Продуктивність (100 пристроїв) | Складність реалізації | Ризик гонок |
|---|---|---|---|
Окремий StateFlow на пристрій |
100 підписок → кожна recomposition | Низька | Високий |
Єдина шина з Map |
1 підписка, diff при оновленні | Середня | Низький (атомарні операції) |
Як ми це робимо: архітектура на Android
Центральний елемент — DeviceRepository з StateFlow<Map<String, DeviceState>>. Кожен транспорт (MqttManager, WebSocketManager, BleManager) лише викликає repository.updateDevice() при отриманні події. ViewModel підписується на devices.collectAsState(), а UI — через LazyColumn з key(device.id). Нижче — типовий код для MQTT:
val mqttClient = MqttAsyncClient(brokerUrl, clientId, MemoryPersistence()) val options = MqttConnectOptions().apply { isAutomaticReconnect = true isCleanSession = false connectionTimeout = 10 keepAliveInterval = 30 } mqttClient.connect(options).waitForCompletion() mqttClient.subscribe("devices/+/state", 1) { topic, message -> val deviceId = topic.split("/")[1] val state = json.decodeFromString<DeviceState>(message.toString()) repository.updateDevice(deviceId, state) } isCleanSession = false відновлює підписки після реконекту, а Last Will Testament (LWT) автоматично позначає пристрій офлайн, якщо він відвалився без disconnect.
Як вибрати транспорт для IoT?
| Протокол | Затримка | Енергоспоживання | Дальність | Використання |
|---|---|---|---|---|
| MQTT | <100 мс | Низьке | Глобальна (через інтернет) | Команди, телеметрія |
| BLE | <10 мс | Дуже низьке | 10–100 м | Датчики, носимі пристрої |
| WebSocket | <50 мс | Середнє | Глобальна | Реалтайм-події |
| REST polling | >1 с | Високе | Глобальна | Резервний канал |
Керування підключеннями: Foreground Service та push
MQTT-з'єднання не повинне помирати при згортанні — інакше пропустите оновлення. На Android використовуємо Foreground Service з постійним сповіщенням. На iOS Background App Refresh ненадійний: правильний шлях — APNS: бекенд отримує подію через MQTT та відправляє push через FCM/APNS, додаток відкриває та синхронізує стан.
Як реалізувати єдину шину: 5 кроків
- Аналіз джерел даних. Визначте, які транспорти будуть використовуватися: MQTT, BLE, WebSocket. Оцініть частоту оновлень — наприклад, датчик температури шле дані кожні 5 секунд, а розумна лампа — раз на хвилину.
-
Проєктування моделі даних. Створіть єдиний інтерфейс
DeviceState, який включаєid,type,status,lastUpdate, та поля для конкретних даних (температура, яскравість). - Реалізація Repository. Використовуйте паттерн Repository з
StateFlow<Map<String, DeviceState>>. Оновлюйте мапу атомарно черезStateFlow.update(). - Інтеграція транспортів. Кожен транспорт (MqttManager, BleManager) отримує дані та викликає
repository.updateDevice(). Переконайтеся, що синхронізація потоків безпечна — використовуйте Dispatchers.Default або корутини. - Тестування з навантаженням. Запустіть 50 симульованих пристроїв, перевірте, що recomposition UI не перевищує 16 мс на кадр.
Типові помилки при розробці IoT-хабу
Розгорніть список
- Окремі Flow на кожен пристрій — кожна підписка викликає recomposition всього списку. Рішення: єдина шина з
key. - Ігнорування LWT — при відвалі пристрою статус залишається «online» до явного таймауту. Рішення: підписуватися на
devices/+/statusз LWT. - Чистий socket без перепідключення — при збої мережі додаток «зависає». Рішення:
isAutomaticReconnect = trueта експоненціальна затримка.
Що входить в роботу
- Архітектура та прототип: вибір протоколів, проєктування шини станів, оцінка навантажень.
- Реалізація: код на Swift/Kotlin, інтеграція з бекендом, налаштування push-сповіщень.
- Тестування: навантажувальне тестування на 50+ пристроях, перевірка граничних випадків (втрата мережі, перемикання протоколів).
- Документація: опис API, інструкція з додавання нового пристрою.
- Підтримка після релізу: 3 місяці гарантії, оновлення під нові версії ОС.
Терміни та вартість
Базова версія з MQTT, списком пристроїв та реалтайм-оновленням — 6–10 тижнів. Повнофункціональне рішення з BLE, push-сповіщеннями, групами та конструктором сценаріїв — 3–5 місяців. Конкретна вартість розраховується індивідуально після аналізу вашого парку пристроїв.
У нас понад 5 років досвіду в IoT-розробці, реалізовано 20+ проєктів для розумного дому, промисловості та ритейлу. MQTT 3.1.1 specification використовується як стандарт передачі. Зв'яжіться з нами — оцінимо ваш проєкт за 1–2 дні та підготуємо прозору комерційну пропозицію. Отримайте консультацію вже сьогодні.







