Мы часто видим, как компании заказывают мобильное приложение для управления 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 дня и подготовим прозрачное коммерческое предложение. Получите консультацию уже сегодня.







