Реализация мониторинга статуса IoT-устройств в реальном времени
Пользователь открывает список устройств и видит актуальный статус каждого — онлайн, офлайн, ошибка питания, низкий заряд. Не «статус 5 минут назад», а сейчас. Технически это задача двустороннего realtime-соединения с правильным управлением lifecycle на мобильном устройстве.
Транспорты для realtime статуса
MQTT с LWT (Last Will Testament). Устройство при подключении к брокеру регистрирует LWT-сообщение: если соединение оборвётся без явного disconnect, брокер опубликует {"online": false} в топик devices/{id}/status. Мобильное приложение подписывается на devices/+/status и обновляет UI при получении.
Это лучший паттерн для IoT: детектирование offline происходит автоматически, без polling.
WebSocket. Для web-based бэкендов. Home Assistant API, custom backend на Laravel с Pusher или Laravel Echo — все через WebSocket. Подходит когда MQTT-брокер недоступен напрямую с телефона.
Server-Sent Events (SSE). Однонаправленный HTTP-стриминг. Проще WebSocket, работает через любой CDN. На Android — OkHttp с EventSource:
val request = Request.Builder().url("$baseUrl/api/devices/stream").build()
val eventSource = EventSources.createFactory(okHttpClient)
.newEventSource(request, object : EventSourceListener() {
override fun onEvent(source: EventSource, id: String?, type: String?, data: String) {
val update = json.decodeFromString<DeviceStatusUpdate>(data)
repository.updateDeviceStatus(update.deviceId, update.status)
}
override fun onFailure(source: EventSource, t: Throwable?, response: Response?) {
// Переподключение через ExponentialBackoff
scheduleReconnect()
}
})
Long polling — устаревший паттерн, не рекомендуется для мобильных приложений. Держит HTTP-соединение открытым, плохо работает при смене сети.
MQTT на Android: lifecycle management
MQTT-соединение не должно привязываться к Activity или Fragment lifecycle. Правильно — foreground Service:
class MqttService : Service() {
private lateinit var mqttClient: MqttAsyncClient
override fun onCreate() {
val options = MqttConnectOptions().apply {
isAutomaticReconnect = true
isCleanSession = false
keepAliveInterval = 30
}
mqttClient = MqttAsyncClient(brokerUrl, clientId, MqttDefaultFilePersistence())
mqttClient.connect(options).waitForCompletion()
mqttClient.subscribe("devices/+/status", 1) { topic, message ->
val deviceId = topic.split("/")[1]
// Broadcast или через Repository
DeviceRepository.getInstance().updateStatus(deviceId, message.toString())
}
}
}
При переходе в фоновый режим Android может убить обычный Service. startForeground() с уведомлением — защита от kill. На Android 14 foreground service требует явное указание типа: android:foregroundServiceType="connectedDevice".
Обработка переходов online/offline
Три состояния, которые нужно отражать в UI:
-
online— устройство подключено, данные актуальны -
offline— устройство не отвечает (LWT сработал или timeout) -
unknown— нет соединения с брокером/бэкендом, статус неизвестен
unknown — отдельный кейс. Если телефон сам потерял сеть, нельзя показывать все устройства как offline — это неправда. Детектировать состояние сети через ConnectivityManager.NetworkCallback:
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
viewModel.setConnectionState(ConnectionState.CONNECTED)
}
override fun onLost(network: Network) {
viewModel.setConnectionState(ConnectionState.NO_NETWORK)
// Показать баннер "Нет подключения" вместо offline-статусов
}
}
Индикаторы в UI
Простой ● зелёный/красный — читаемо, но не информативно. Лучше:
- Иконка + цвет: зелёный = онлайн, серый = офлайн, красный = ошибка, жёлтый = предупреждение
- Время последней активности для офлайн-устройств: «офлайн · 2 ч назад»
- Для батарейных устройств — уровень заряда рядом со статусом
Время «2 ч назад» — не timestamp из БД напрямую. Форматировать через DateUtils.getRelativeTimeSpanString() на Android или RelativeDateTimeFormatter — иначе при открытии через час будет «3 ч назад» только после перезагрузки экрана.
Реализация realtime-мониторинга статуса с MQTT/WebSocket: 2–4 недели в зависимости от транспорта и числа устройств.







