Реализация мониторинга HVAC-систем через мобильное приложение
HVAC-контроллеры (Danfoss, Honeywell, Daikin VRV) редко говорят на одном протоколе. Один объект — Modbus RTU на RS-485 к приточным установкам, BACnet/IP к чиллерам, проприетарный протокол к фанкойлам Daikin. Мобильное приложение работает с нормализованными данными через API-шлюз, но разработчику нужно понимать откуда берутся числа, чтобы правильно их интерпретировать и отображать.
Ключевые параметры и их источники
Минимальный набор для мониторинга климата:
| Параметр | Источник | Единица | Частота |
|---|---|---|---|
| Температура подачи/обратки | Датчики PT1000/NTC на трубопроводе | °C | 30 сек |
| Температура воздуха в зоне | Комнатный датчик или термостат | °C | 1 мин |
| Влажность | SHT31 или HIH6130 в зоне | %RH | 1 мин |
| Уставка (setpoint) | Контроллер | °C | по изменению |
| Состояние компрессора | Дискретный вход контроллера | on/off | по изменению |
| COP (коэффициент эффективности) | Вычисляемый на бэкенде | — | 5 мин |
Важный нюанс: температура в Modbus Holding Registers часто приходит как знаковое int16 в единицах 0.1°C. Если контроллер отдаёт 0xFF9C, это не 65436°C — это -100, то есть -10.0°C. Неправильная интерпретация — источник классической ошибки «датчик показывает -3200°C».
fun parseModbusTemperature(rawValue: Int): Double {
// Конвертируем unsigned 16-bit в signed
val signed = if (rawValue > 32767) rawValue - 65536 else rawValue
return signed / 10.0
}
Реализация на Android: polling через Retrofit + coroutines
Шлюз (Node-RED или самописный Go-сервис) предоставляет REST API. Polling с адаптивным интервалом — агрессивный когда приложение на переднем плане, редкий в фоне:
class HvacPollingService(
private val api: HvacApi,
private val repository: HvacRepository,
) {
private var pollingJob: Job? = null
fun startPolling(scope: CoroutineScope, foreground: Boolean) {
pollingJob?.cancel()
val interval = if (foreground) 15_000L else 60_000L
pollingJob = scope.launch {
while (isActive) {
try {
val data = api.getHvacStatus()
repository.update(data)
} catch (e: IOException) {
// Логируем, не крэшим — потеря связи с шлюзом штатная ситуация
}
delay(interval)
}
}
}
}
Для объектов с MQTT-шлюзом (Eclipse Mosquitto) используем org.eclipse.paho.client.mqttv3. Топики по зонам: hvac/{buildingId}/{unitId}/temperature, hvac/{buildingId}/{unitId}/setpoint.
Тренды и история
График температуры за неделю — обязательный элемент. Данные за длительный период запрашиваем с агрегацией на сервере (avg/min/max по часовым интервалам), не тащим сырые 30-секундные записи. В приложении рендерим через MPAndroidChart (Android) или fl_chart (Flutter):
LineChartData buildTemperatureChart(List<TemperatureReading> history) {
return LineChartData(
lineBarsData: [
LineChartBarData(
spots: history.asMap().entries.map((e) =>
FlSpot(e.key.toDouble(), e.value.temperature)).toList(),
isCurved: true,
color: Colors.blue,
dotData: FlDotData(show: false),
),
],
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) =>
Text(formatHour(history[value.toInt()].timestamp)),
),
),
),
);
}
Алерты по выходу из диапазона
Температура вышла за пределы — нужно уведомление. На сервере настраиваем правила (например, через Node-RED или TimescaleDB continuous aggregates), push приходит через FCM. В приложении храним историю алертов локально в Room/SQLite — пользователь должен видеть когда и что произошло, даже если уведомление смахнул.
Разработка приложения мониторинга HVAC с real-time данными, историческими трендами и алертами: 4–6 недель. Стоимость рассчитывается индивидуально после анализа протоколов контроллеров и количества точек мониторинга.







