Расскажу, как решал задачу принудительного притока воздуха на кухне и немного автоматизировал управление приточным клапаном с помощью MQTT и WirenBoard.
Выбор устройства
Когда встал вопрос с вентиляцией, я начал смотреть в сторону приточных клапанов с автоматическим управлением. В какой-то момент попался один экземпляр от компании Vakio — привлёк прежде всего небольшими размерами. После изучения сайта выяснилось, что у них есть три модели одного устройства:
Vakio KIV Pro |
Vakio KIV SMART |
Vakio OpenAir |
|---|---|---|
Базовая модель |
Управление заслонкой |
Двигатель нагнетания воздуха + управление заслонкой |
Выбор остановился на Vakio OpenAir: хотелось не просто открывать клапан, но и принудительно нагнетать воздух.
Из интересного: модели KIV SMART и OpenAir оснащены датчиками температуры и влажности, а управляются через MQTT. Внутри устройства — контроллер на базе ESP32, который при первом запуске поднимает собственную Wi-Fi точку доступа для настройки. Из периферии — сервопривод заслонки и мотор нагнетания.
У клапана два режима работы: облако Vakio либо локальный MQTT-брокер. Для домашней автоматизации, конечно, интереснее второй вариант.

Первоначальная настройка
После настройки по инструкции устройство подключается к домашней Wi-Fi сети и открывает веб-интерфейс на 80-м порту — можно зайти прямо в браузере и поуправлять клапаном вручную.

Поскольку у меня уже был настроен контроллер WirenBoard с запущенным MQTT-брокером, достаточно было указать параметры подключения в настройках Vakio — и устройство сразу начало публиковать данные.

Протокол и API
Устройство публикует данные в двух форматах одновременно: старый, где каждый параметр в отдельном топике, и новый JSON-формат. Ребята из Vakio открыто описали протокол в репозитории vakio-ru/vakio-public-api — там детально расписаны все топики, параметры и команды управления.
Структура топиков выглядит так:
От устройства (
device/+/openair/...):system(авторизация, ошибки),mode(возможности, настройки), плюс легаси-топики с отдельными значениямиНа устройство (
server/+/openair/...): команды в формате JSON
Почему пришлось писать свой драйвер
Готовые правила для WirenBoard мне не подошли: не хватало нужной логики управления, обработки всех состояний и нормального мониторинга онлайн/офлайн. Поэтому написал драйвер с нуля на wb-rules 2.0.
Что умеет драйвер:
Интерлок заслонки и скорости — при изменении скорости или заслонки команды отправляются в правильном порядке с задержкой 1,5 с, чтобы механика не конфликтовала
Watchdog — периодически проверяет связь с устройством и выставляет статус онлайн/офлайн, подсвечивая красным нужные контролы
Глубокий опрос — раз в N минут запрашивает полное состояние устройства (прошивку, MAC, настройки)
Поддержка смарт-режима — управление параметрами автоматики: пороговая температура, скорость, аварийное закрытие клапана
Отладочный лог — включается кнопкой прямо из веб-интерфейса WirenBoard, без правки кода
Код драйвера
// ============================================================= // Vakio OpenAir Rev2 — драйвер для Wiren Board / wb-rules 2.0 // Основан на официальном API: github.com/vakio-ru/vakio-public-api // Протокол: JSON MQTT (прошивка >= 1.1.0, exchange_type: json) // ============================================================= // // ТОПИКИ ОТ УСТРОЙСТВА (device/+/openair/...): // system → auth{device_mac, version}, device_subtype{exchange_type,series,subtype,xtal_freq} // errors{shutdown} // mode → capabilities{mode,on_off,speed,gate} // settings{temperature_speed[temp,speed], emerg_shunt, gate, smart_speed} // +/temp, +/hud, +/state, +/speed, +/gate, +/workmode (легаси-топики) // // ТОПИКИ К УСТРОЙСТВУ (server/+/openair/...): // system → {"type":"auth"} | {"shutdown":{"limit":N}} | {"firmware":{...}} | {"reset":[...]} // mode → {"capabilities":{mode,on_off,speed,gate}} // {"settings":{gate,smart_speed,emerg_shunt,temperature_speed:[temp,speed]}} // // ============================================================= // УПРАВЛЕНИЕ РЕЖИМОМ // ============================================================= // Поле "Workmode" — редактируемое текстовое поле. // manual — ручной режим // super_auto — смарт-режим // // ============================================================= // ИНТЕРЛОК заслонки и скорости // ============================================================= // Speed = 0 → стоп → заслонка в позицию 2 (через 1.5с) // Speed > 0 → заслонка в 4 (через 1.5с) → скорость + вкл // Ручное изменение Gate → стоп → переход заслонки (через 1.5с) // CloseGate → speed=0 → off (через 1.5с) // // ============================================================= // ОБНОВЛЕНИЕ ДАННЫХ // ============================================================= // Контрол "ForceRefresh" (pushbutton) — принудительно запросить // состояние устройства (auth + эхо текущих capabilities). // Автоматически выполняется каждые deep_poll_interval секунд. // // ============================================================= // ОТЛАДОЧНЫЙ ЛОГ // ============================================================= // Контрол "Debug_Log" (switch) — включить/выключить детальный лог // Смотреть: journalctl -u wb-rules -f // ============================================================= createVakioOpenAir({ id: 1, topic: "VAKIO", endpoint: "openair", polling_interval: 10, // интервал heartbeat (сек) deep_poll_interval: 300 // интервал глубокого опроса (сек), по умолчанию 5 минут }); function createVakioOpenAir(params) { var id = params.id; var topic = params.topic; var endpoint = params.endpoint !== undefined ? params.endpoint : "openair"; var polling_interval = params.polling_interval !== undefined ? params.polling_interval : 10; var deep_poll_interval= params.deep_poll_interval!== undefined ? params.deep_poll_interval: 300; var vd = "Vakio_" + id + "_" + endpoint; // ============================================================ // Имена контролов // ============================================================ var ctrlState = "State"; var ctrlWorkmode = "Workmode"; var ctrlSpeed = "Speed"; var ctrlGate = "Gate"; var ctrlClose = "CloseGate"; var ctrlForceRefresh = "ForceRefresh"; var ctrlConnect = "Connect"; var ctrlTemp = "Temperature"; var ctrlHumidity = "Humidity"; var ctrlErrShutdown = "Err_Shutdown"; // Настройки смарт — то что ОТПРАВЛЯЕМ на устройство var ctrlSmartGate = "Smart_Gate"; // gate в smart (позиция заслонки) var ctrlSmartSpeed = "Smart_Speed"; // smart_speed var ctrlEmergShunt = "Emerg_Shunt"; // emerg_shunt (темп. отключения клапана) var ctrlSmartTempThreshold = "Smart_Temp"; // temperature_speed[0] — порог температуры var ctrlShutdownLimit = "Shutdown_Limit"; // Readonly — что ПОЛУЧАЕМ из settings устройства (feedback) var ctrlAutoTemp = "Auto_Temp"; // temperature_speed[0] var ctrlAutoSpeed = "Auto_Speed"; // temperature_speed[1] var ctrlRelGate = "Rel_Gate"; // settings.gate (feedback) var ctrlRelSmartSpd = "Rel_SmartSpeed"; // settings.smart_speed (feedback) var ctrlRelEmerg = "Rel_EmergShunt"; // settings.emerg_shunt (feedback) // Информация об устройстве (readonly) var ctrlFwVer = "FW_Version"; var ctrlMac = "Device_MAC"; var ctrlSeries = "HW_Series"; var ctrlSubtype = "HW_Subtype"; var ctrlExchange = "Exchange"; var ctrlDebugLog = "Debug_Log"; // ============================================================ // MQTT пути // ============================================================ var deviceBase = "device/" + topic + "/" + endpoint; var serverBase = "server/" + topic + "/" + endpoint; var legacyBase = topic; // ============================================================ // Внутреннее состояние // ============================================================ var _selfChange = false; var _initialized = false; var _lastAlive = 0; var _isOnline = false; var _deepPollTick = 0; // ============================================================ // Виртуальное устройство // ============================================================ defineVirtualDevice(vd, { title: "Vakio OpenAir [" + topic + "/" + endpoint + "]", cells: { // --- Основное управление --- State: { title: "Вкл / Выкл", type: "switch", value: false, order: 1 }, Workmode: { title: "Режим (manual / super_auto)", type: "text", value: "manual", readonly: false, order: 2 }, Speed: { title: "Скорость (0=стоп, 1-5)", type: "range", value: 0, min: 0, max: 5, order: 3 }, Gate: { title: "Заслонка (1=мин, 4=макс)", type: "range", value: 2, min: 1, max: 4, order: 4 }, CloseGate: { title: "Стоп + закрыть", type: "pushbutton", value: false, order: 5 }, ForceRefresh: { title: "Обновить данные с устройства", type: "pushbutton", value: false, order: 6 }, // --- Статус --- Connect: { title: "Связь", type: "switch", value: false, readonly: true, order: 10 }, Temperature: { title: "Температура (°C)", type: "temperature", value: 0, readonly: true, order: 11 }, Humidity: { title: "Влажность (%)", type: "rel_humidity", value: 0, readonly: true, order: 12 }, Err_Shutdown: { title: "Ошибка: переохлаждение", type: "switch", value: false, readonly: true, order: 13 }, // --- Настройки смарт-режима (ЗАПИСЫВАЕМЫЕ) --- Smart_Gate: { title: "Смарт: заслонка (1-4)", type: "range", value: 4, min: 1, max: 4, order: 20 }, Smart_Speed: { title: "Смарт: скорость (1-5)", type: "range", value: 3, min: 1, max: 5, order: 21 }, Smart_Temp: { title: "Смарт: порог температуры (°C)", type: "range", value: 20, min: -20, max: 40, order: 22 }, Emerg_Shunt: { title: "Смарт: темп. откл. клапана (°C)", type: "range", value: 10, min: -20, max: 25, order: 23 }, Shutdown_Limit: { title: "Порог переохлаждения (°C)", type: "range", value: 0, min: -30, max: 25, order: 24 }, // --- Авто-режим (readonly, feedback из settings) --- Auto_Temp: { title: "Авто: порог темп. (°C) [feedback]", type: "value", value: 0, readonly: true, order: 30 }, Auto_Speed: { title: "Авто: скорость [feedback]", type: "value", value: 0, readonly: true, order: 31 }, // --- Информация об устройстве (readonly) --- FW_Version: { title: "Прошивка", type: "text", value: "—", readonly: true, order: 40 }, Device_MAC: { title: "MAC адрес", type: "text", value: "—", readonly: true, order: 41 }, HW_Series: { title: "Серия железа", type: "text", value: "—", readonly: true, order: 42 }, HW_Subtype: { title: "Подтип железа", type: "text", value: "—", readonly: true, order: 43 }, Exchange: { title: "Протокол обмена", type: "text", value: "—", readonly: true, order: 44 }, // --- Rel (readonly, feedback) --- Rel_Gate: { title: "Rel: заслонка [feedback]", type: "value", value: 0, readonly: true, order: 50 }, Rel_SmartSpeed: { title: "Rel: скорость смарт [feedback]", type: "value", value: 0, readonly: true, order: 51 }, Rel_EmergShunt: { title: "Rel: emerg_shunt [feedback]", type: "value", value: 0, readonly: true, order: 52 }, // --- Отладка --- Debug_Log: { title: "Детальный лог (Debug)", type: "switch", value: false, order: 60 } } }); // ============================================================ // Вспомогательные функции // ============================================================ function dbg(tag, message) { if (dev[vd][ctrlDebugLog]) { log.info("[DBG][" + vd + "][" + tag + "] " + message); } } function sendCapabilities(caps) { var payload = JSON.stringify({ capabilities: caps }); publish(serverBase + "/mode", payload, 2, false); dbg("TX/mode", "capabilities: " + payload); } function sendSettings(s) { var payload = JSON.stringify({ settings: s }); publish(serverBase + "/mode", payload, 2, false); dbg("TX/mode", "settings: " + payload); } function sendSystem(obj) { var payload = JSON.stringify(obj); publish(serverBase + "/system", payload, 2, false); dbg("TX/system", payload); } function setCtrlError(ctrl, isError) { dev[vd + "/" + ctrl + "#error"] = isError ? "r" : ""; } var watchedCtrls = [ctrlState, ctrlSpeed, ctrlGate]; function setOnline() { _lastAlive = Date.now(); if (!_isOnline) { _isOnline = true; dev[vd][ctrlConnect] = true; for (var i = 0; i < watchedCtrls.length; i++) { setCtrlError(watchedCtrls[i], false); } log.info("[" + vd + "] онлайн"); } } function setOffline() { if (_isOnline) { _isOnline = false; dev[vd][ctrlConnect] = false; for (var i = 0; i < watchedCtrls.length; i++) { setCtrlError(watchedCtrls[i], true); } log.warning("[" + vd + "] офлайн"); } } function safeJson(str) { try { return JSON.parse(str); } catch (e) { log.warning("[" + vd + "] ошибка JSON: " + str); return null; } } // ============================================================ // Глубокий опрос — только запрос auth + 0687 // // ВАЖНО: НЕ эхоим capabilities обратно на устройство! // Если эхоить — устройство применяет значения и присылает feedback, // перезатирая реальное состояние после интерлока (speed=0 при смене gate). // // Устройство само присылает актуальное состояние в ответ на auth: // device/.../system → auth + device_subtype (mac, fw, серия) // device/.../mode → capabilities + settings (скорость, заслонка, режим) // ============================================================ function deepPoll() { sendSystem({ type: "auth" }); publish(legacyBase + "/system", "0687", 2, false); dbg("DEEP_POLL", "auth + 0687 отправлены, ждём ответа от устройства"); log.info("[" + vd + "] глубокий опрос выполнен"); } // ============================================================ // Команды пользователя → Устройство // ============================================================ trackMqtt( "/devices/" + vd + "/controls/" + ctrlState + "/on", function(msg) { var isOn = (msg.value === true || msg.value === "true" || msg.value === "1"); sendCapabilities({ on_off: isOn ? "on" : "off" }); } ); trackMqtt( "/devices/" + vd + "/controls/" + ctrlWorkmode + "/on", function(msg) { var mode = String(msg.value).trim(); log.info("[" + vd + "] режим → " + mode); sendCapabilities({ mode: mode }); } ); trackMqtt( "/devices/" + vd + "/controls/" + ctrlSpeed + "/on", function(msg) { var newSpeed = parseInt(msg.value, 10); _selfChange = true; if (newSpeed === 0) { sendCapabilities({ speed: 0 }); setTimeout(function() { sendCapabilities({ gate: 2 }); _selfChange = false; }, 1500); } else { sendCapabilities({ gate: 4 }); setTimeout(function() { sendCapabilities({ speed: newSpeed, on_off: "on" }); _selfChange = false; }, 1500); } } ); trackMqtt( "/devices/" + vd + "/controls/" + ctrlGate + "/on", function(msg) { if (_selfChange) { return; } var newGate = parseInt(msg.value, 10); _selfChange = true; sendCapabilities({ speed: 0 }); setTimeout(function() { sendCapabilities({ gate: newGate }); _selfChange = false; }, 1500); } ); defineRule("CloseGate_" + vd, { whenChanged: vd + "/" + ctrlClose, then: function(newValue) { if (!newValue) { return; } sendCapabilities({ speed: 0 }); setTimeout(function() { sendCapabilities({ on_off: "off" }); }, 1500); } }); defineRule("ForceRefresh_" + vd, { whenChanged: vd + "/" + ctrlForceRefresh, then: function(newValue) { if (!newValue) { return; } log.info("[" + vd + "] принудительное обновление данных..."); deepPoll(); } }); defineRule("SmartSettings_" + vd, { whenChanged: [ vd + "/" + ctrlSmartGate, vd + "/" + ctrlSmartSpeed, vd + "/" + ctrlEmergShunt ], then: function() { var s = { gate: dev[vd][ctrlSmartGate], smart_speed: dev[vd][ctrlSmartSpeed], emerg_shunt: dev[vd][ctrlEmergShunt] }; sendSettings(s); log.info("[" + vd + "] настройки смарт → gate=" + s.gate + " speed=" + s.smart_speed + " emerg=" + s.emerg_shunt); } }); defineRule("SmartTempSpeed_" + vd, { whenChanged: vd + "/" + ctrlSmartTempThreshold, then: function() { var threshold = parseFloat(dev[vd][ctrlSmartTempThreshold]); var speed = parseInt(dev[vd][ctrlSmartSpeed], 10); var s = { temperature_speed: [threshold, speed] }; sendSettings(s); log.info("[" + vd + "] temperature_speed → " + threshold + "°C / speed=" + speed); } }); defineRule("ShutdownLimit_" + vd, { whenChanged: vd + "/" + ctrlShutdownLimit, then: function() { sendSystem({ shutdown: { limit: dev[vd][ctrlShutdownLimit] } }); } }); // ============================================================ // Обратная связь Устройство → Wiren Board // ============================================================ trackMqtt(deviceBase + "/system", function(msg) { dbg("RX/system", "RAW: " + msg.value); var obj = safeJson(msg.value); if (!obj) { return; } if (obj.auth) { if (obj.auth.device_mac !== undefined && obj.auth.device_mac !== null) { dev[vd][ctrlMac] = String(obj.auth.device_mac); } if (obj.auth.version !== undefined && obj.auth.version !== null) { dev[vd][ctrlFwVer] = String(obj.auth.version); } dbg("RX/system", "auth: mac=" + dev[vd][ctrlMac] + " ver=" + dev[vd][ctrlFwVer]); } if (obj.device_subtype) { var ds = obj.device_subtype; if (ds.exchange_type !== undefined && ds.exchange_type !== null) { dev[vd][ctrlExchange] = String(ds.exchange_type); } if (ds.series !== undefined && ds.series !== null) { dev[vd][ctrlSeries] = String(ds.series); } if (ds.subtype !== undefined && ds.subtype !== null) { var sub = String(ds.subtype); if (ds.xtal_freq !== undefined && ds.xtal_freq !== null) { sub = sub + " (xtal:" + String(ds.xtal_freq) + ")"; } dev[vd][ctrlSubtype] = sub; } dbg("RX/system", "device_subtype: exchange=" + ds.exchange_type + " series=" + ds.series + " subtype=" + ds.subtype); } if (obj.errors !== undefined && obj.errors.shutdown !== undefined) { var isShutdown = (parseInt(obj.errors.shutdown, 10) === 1); dev[vd][ctrlErrShutdown] = isShutdown; setCtrlError(ctrlErrShutdown, isShutdown); if (isShutdown) { log.warning("[" + vd + "] ПЕРЕОХЛАЖДЕНИЕ! shutdown=1"); } } setOnline(); if (!_initialized) { _initialized = true; log.info("[" + vd + "] первый пакет — FW=" + dev[vd][ctrlFwVer] + " MAC=" + dev[vd][ctrlMac] + " series=" + dev[vd][ctrlSeries]); } }); trackMqtt(deviceBase + "/mode", function(msg) { dbg("RX/mode", "RAW: " + msg.value); var obj = safeJson(msg.value); if (!obj) { return; } if (obj.capabilities !== undefined) { var cap = obj.capabilities; if (cap.speed !== undefined && cap.speed !== null) { dev[vd][ctrlSpeed] = parseInt(cap.speed, 10); } if (cap.gate !== undefined && cap.gate !== null) { var g = parseInt(cap.gate, 10); if (g >= 1 && g <= 4) { dev[vd][ctrlGate] = g; } } if (cap.on_off !== undefined && cap.on_off !== null) { dev[vd][ctrlState] = (cap.on_off === "on"); } if (cap.mode !== undefined && cap.mode !== null) { dev[vd][ctrlWorkmode] = String(cap.mode); } dbg("RX/mode", "capabilities: mode=" + cap.mode + " on_off=" + cap.on_off + " speed=" + cap.speed + " gate=" + cap.gate); } if (obj.settings !== undefined) { var s = obj.settings; dbg("RX/mode", "settings: " + JSON.stringify(s)); if (s.temperature_speed !== undefined && Array.isArray(s.temperature_speed)) { if (s.temperature_speed.length >= 1) { var thr = parseFloat(s.temperature_speed[0]); dev[vd][ctrlAutoTemp] = thr; dev[vd][ctrlSmartTempThreshold] = thr; } if (s.temperature_speed.length >= 2) { dev[vd][ctrlAutoSpeed] = parseInt(s.temperature_speed[1], 10); } } if (s.emerg_shunt !== undefined && s.emerg_shunt !== null) { dev[vd][ctrlRelEmerg] = parseFloat(s.emerg_shunt); dev[vd][ctrlEmergShunt] = parseFloat(s.emerg_shunt); } if (s.gate !== undefined && s.gate !== null) { dev[vd][ctrlRelGate] = parseInt(s.gate, 10); dev[vd][ctrlSmartGate] = parseInt(s.gate, 10); } if (s.smart_speed !== undefined && s.smart_speed !== null) { dev[vd][ctrlRelSmartSpd] = parseInt(s.smart_speed, 10); dev[vd][ctrlSmartSpeed] = parseInt(s.smart_speed, 10); } } setOnline(); }); // Легаси-топики trackMqtt(legacyBase + "/temp", function(msg) { var v = parseFloat(msg.value); if (!isNaN(v)) { dev[vd][ctrlTemp] = v; } setOnline(); dbg("RX/temp", msg.value); }); trackMqtt(legacyBase + "/hud", function(msg) { var v = parseFloat(msg.value); if (!isNaN(v)) { dev[vd][ctrlHumidity] = v; } setOnline(); dbg("RX/hud", msg.value); }); trackMqtt(legacyBase + "/state", function(msg) { dev[vd][ctrlState] = (msg.value === "on"); setOnline(); dbg("RX/state", msg.value); }); trackMqtt(legacyBase + "/speed", function(msg) { var v = parseInt(msg.value, 10); if (!isNaN(v) && v >= 0 && v <= 5) { dev[vd][ctrlSpeed] = v; } setOnline(); dbg("RX/speed", msg.value); }); trackMqtt(legacyBase + "/gate", function(msg) { var v = parseInt(msg.value, 10); if (!isNaN(v) && v >= 1 && v <= 4) { dev[vd][ctrlGate] = v; } setOnline(); dbg("RX/gate", msg.value); }); trackMqtt(legacyBase + "/workmode", function(msg) { dev[vd][ctrlWorkmode] = String(msg.value); setOnline(); dbg("RX/workmode", msg.value); }); trackMqtt(legacyBase + "/system", function(msg) { setOnline(); dbg("RX/legacy-system", msg.value); }); // ============================================================ // Polling / watchdog // ============================================================ function pollDevice() { sendSystem({ type: "auth" }); publish(legacyBase + "/system", "0687", 2, false); dbg("POLL", "heartbeat отправлен"); } var _monitorReady = false; var _pollCount = 0; var _deepInterval = Math.max(1, Math.round(deep_poll_interval / polling_interval)); pollDevice(); setTimeout(deepPoll, 2000); setInterval(function() { _pollCount++; pollDevice(); if (_pollCount % _deepInterval === 0) { deepPoll(); log.info("[" + vd + "] плановый глубокий опрос #" + Math.floor(_pollCount / _deepInterval)); } if (!_monitorReady) { _monitorReady = true; return; } var silenceMs = polling_interval * 2 * 1000; if (_lastAlive === 0 || (Date.now() - _lastAlive) > silenceMs) { setOffline(); } }, polling_interval * 1000); log.info("[" + vd + "] инициализирован."); log.info("[" + vd + "] JSON RX : " + deviceBase + "/{system,mode}"); log.info("[" + vd + "] JSON TX : " + serverBase + "/{system,mode}"); log.info("[" + vd + "] Легаси : " + legacyBase + "/{system,temp,hud,state,speed,gate,workmode}"); log.info("[" + vd + "] Heartbeat : каждые " + polling_interval + "с"); log.info("[" + vd + "] Глубокий опрос: каждые " + deep_poll_interval + "с"); }
Результат

В итоге WirenBoard видит клапан как полноценное виртуальное устройство со всеми параметрами: температура, влажность, состояние заслонки, скорость, режим работы, ошибки.
Дальше можно строить любую автоматику: расписания, реакцию на CO₂ или влажность, интеграцию с другими устройствами в доме.
Выводы
Vakio OpenAir оказался приятным устройством: открытый API, нормальная документация, поддержка локального MQTT без обязательного облака. Единственное — пришлось разобраться с нюансами протокола самому, потому что готовых интеграций под WirenBoard на мой вкус не нашлось. Надеюсь, этот драйвер пригодится тем, кто захочет повторить что-то похожее.
UPD. Набор фотографий устройства




Комментарии (6)

ErshoffPeter
11.03.2026 05:19Интересно бы узнать что хотелось достичь и удалось ли этого?
...я сам себе сделал приточку и теперь думаю о критериях когда её запускать, когда выключать.

rmuhamedgaliev Автор
11.03.2026 05:19Ну меня все просто, есть поквартирное отопление , хоть труба в котле и двуслойная, но затсройщик все равно добавил клапаны для притока воздуха (возможно надо по пожарно безопастности), но обычно от эти оконных клапанов много грязи и пыли. А для праавильно работы вентиляции должен быть приоок воздуха, благодаря проветривателю он будет. В будущем когда поживу, то буду делать более точную автоматизацию по показателям CO2 и качества воздуха, что бы автоматизировать процесс. Так как в общей связке у меня в комнатах установленны WB-MSW v.4 .
Так что цель получить проветриватель с дистационным управление как будто достигнута.
А у вас какое решение и какое устройство?

ErshoffPeter
11.03.2026 05:19У меня получилось полностью своё решение (статья тут - https://habr.com/ru/articles/845778/), с использованием двухканальных реле Yeelight (экосистема Xiaomi Mi home), которое достаточно хорошо используется (в автоматическом режиме), что бы в жаркие летние дни проветривать детские и спальню вечерами (примерно с 20-00 на час-другой), когда температура воздуха на улице становится уже достаточно низкой, относительно той, что в помещениях. Фильтр особо не нужен, так как живём загородом - но при необходимости его поставить можно.
То есть эдакая замена кондиционерам там, где жаркое лето бывает обычно раз в два года.
Но встал вопрос - а в другие дни как использовать? Весной и осенью ещё норм при положительных температурах на улице, а вот зимой, когда на улице минус, например - 15°?
Отчасти проблема ещё в том, что некое подобие ШИМ плохо выходит, так как реле тупо виснет, если его включать-выключать чаще чем раз в десять секунд. Струя идёт холодного воздуха с потолка, с использованием анемостата смешивается с комнатным воздухом, но при постоянном обдуве это всё равно нехорошо ни детям ни взрослым, которые могут под неё попасть, так как они аак правило в этих комнатах мало перемещаются с места на место (сидят за столом, лежат в кровати и так далее).
Вот и встаёт вопрос - когда включать/выключать приточку, в какое время и при каких условиях? Известны и температура и влажность и на улице и в каждой из комнат (датчики того же Mi home).
Пока ничего кроме временных интервалов, когда примерно никого нет или нет население комнат мало двигается) придумать не удалось.
Junecat
Отличная работа!
Но как не хватает в статье фоточки самого устройства…
rmuhamedgaliev Автор
Спасибо, дополню тогда, чуть позже. Не хотел пост превращать в обзор устройства, а больше поделится рецептом для связки с Wirenboard.
rmuhamedgaliev Автор
Добавил фотографий. Надеюсь они будут полезны