--- title: 'Node-RED: вентиляция по CO₂' tags: - family - home - automation - nodered - ventilation updated: '2026-09-14' related: - '[[family/plans/t610-home-automation]]' - '[[family/how-to/home-automation]]' --- > ## 🔄 ГДЕ ЖИВЁТ (обновлено 2026-09-14) > **Flows перенесены на HP t610** (HA OS) — **перенос выполнен 2026-09-14**, см. [[family/plans/t610-home-automation]] §5-кватер-Г. > - **Файл:** `/addon_configs/a0d7b954_nodered/flows.json` в аддоне `a0d7b954_nodered` (**68 узлов**, было 124 б пустых). > - **🔴 Ключевая правка при переезде:** узел `server` — `"addon": false` → **`"addon": true`**. На TrueNAS Node-RED был отдельным docker-контейнером и ходил в HA по адресу + long-lived token (**токена в переносимых файлах нет вообще** — не искать). На t610 он HA-аддон → в аддон-режиме Supervisor даёт доступ к HA без токена. Лог после правки: `[server:Home Assistant] Connected to http://supervisor/core`, ошибок 0. > - **Модуль:** опция аддона `npm_packages: ["node-red-contrib-home-assistant-websocket@0.80.3"]`. > - **Доступ:** наружу НЕ выпущен (`host_network: true` глушит порт-маппинг; снимается только галочкой в UI) → **через ingress HA**: `http://192.168.2.176/api/hassio_ingress/4vUcCxEpYJMbQ64z-kWPCvFadq8ayl4lzvzJPDUaZzE/`. Домен `nodered.mallexxx.duckdns.org` **не используется** (отдаёт 401 от nginx HA). > - **Старый Node-RED на TrueNAS** (`/mnt/RED_2TB/docker/nodered/`, порт 1880) пока жив как источник/откат. > - **⚠️ Известное следствие:** узел `fan.fan_at2_1/2` (slave 10) в HA на t610 — `unavailable` (блок закомментирован в `configuration.yaml`), поэтому управление вентиляторами AT2 не работает. Также проверить наличие `cover.intake_damper_*` (в конфиге HA заслонки описаны как `switch.*`). Alex: задача по slave 10 **не ставилась**. Node-RED flow для управления вентиляцией по данным CO₂ датчиков. ## Структура ``` [room demand msgs] ↓ (1) Demand Aggregator ↓ (2) Intake Allocation ↓ (3) Discretization per damper ↓ (4) Intake Damper Outputs ↓ (5) Exhaust Arbitration ↓ (6) Exhaust Dampers ↓ (7) Exhaust Fans ↓ (8) Kitchen Hood ``` ## 1. Demand Aggregator (stateful) ```js let demands = flow.get("room_demands") || {}; demands[msg.room] = msg.raw_demand; flow.set("room_demands", demands); msg.demands = demands; return msg; ``` ## 2. Intake Allocation Комнаты с intake дамперами: bedroom, kids, living. Приоритеты: living = 1.2, остальные = 1.0. ```js const ROOMS = ["bedroom", "kids", "living"]; const PRIORITY = { bedroom: 1.0, kids: 1.0, living: 1.2 }; let weighted = {}, sum = 0; for (let r of ROOMS) { let d = msg.demands[r] || 0; weighted[r] = d * (PRIORITY[r] || 1); sum += weighted[r]; } if (sum === 0) return null; let allocation = {}; for (let r of ROOMS) { allocation[r] = 100 * weighted[r] / sum; } msg.intake_pct = allocation; return msg; ``` ## 3. Discretization **Bedroom/Kids** (0 / 33 / 66 / 100): ```js function snap(x) { if (x < 10) return 0; if (x < 33) return 33; if (x < 66) return 66; return 100; } msg.payload = snap(msg.payload); return msg; ``` **Living (dual-damper)**: ```js let p = msg.payload; let A = 0, B = 0; if (p < 10) { A=0; B=0; } else if (p < 30) { A=66; B=0; } else if (p < 55) { A=66; B=66; } else if (p < 80) { A=100; B=66; } else { A=100; B=100; } msg.damperA = A; msg.damperB = B; return msg; ``` ## 5. Exhaust Arbitration ```js let d = msg.demands; msg.exhaust = { at2_1: Math.max(d.toilet || 0, d.shower || 0, d.kitchen || 0), at2_2: Math.max(d.bathroom || 0, d.office || 0) }; return msg; ``` ## 7. Fan Speed ```js function speed(d) { if (d < 5) return 30; if (d < 20) return 50; if (d < 40) return 70; return 100; } msg.payload = speed(msg.payload); return msg; ``` ## 8. Kitchen Hood (3-speed) ```js let d = msg.demands.kitchen || 0; if (d < 10) msg.payload = 0; else if (d < 30) msg.payload = 1; else if (d < 60) msg.payload = 2; else msg.payload = 3; return msg; ``` Gate: current_state (manual override → block automation). Все дамперы: rate-limit 1 msg / 30–60 s. ## Связанные заметки - [[family/how-to/home-automation]] — общая автоматизация дома - [[family/plans/t610-home-automation]] — миграция на t610, §5-кватер-Г (перенос flows) и §5-кватер-З (фикс датчика столовой) - [[Modbus/RTU_Framing_Source_Analysis]] — канон сборки RTU-кадров (почему `dining` молчал и как починили) ## Статус данных для автоматики (2026-09-14) ✅ **Все три комнаты (`bedroom`, `kids`, `dining`) публикуют данные в MQTT.** Датчик столовой (`slave 1`) был сломан багом сборки кадров в `modbus-bridge` — починен 2026-09-14 (см. [[family/plans/t610-home-automation]] §5-кватер-З). До фикса автоматика вентиляции по CO₂ работала без данных столовой. ⚠️ **`fan.fan_at2_1/2` (slave 10) — `unavailable`** (блок закомментирован в `configuration.yaml`) → исполнительный контур вентиляторов AT2 не работает. Задача по slave 10 Alex'ом не ставилась. ## 🔦 Ночной свет душевой 2 — пороги (правка 2026-09-15) **Сущности:** датчик освещённости `sensor.shower_2_presence_sensor_illuminance` (радар присутствия Tuya `ZY-M100-S_2` / `TS0601`, `ieee 0xa4c138c4a94a6a31`, устройство `shower_2_presence_sensor`); присутствие `binary_sensor.shower_2_presence_sensor_presence`; лампа `switch.night_light_shower_2` (`TS0001`, `ieee 0x84fd27fffed9e137`) + хелпер `light.night_light_shower_2` (switch_as_x, т.е. **две сущности на одно реле**). **Автоматизации** (в HA по `id`): «Вкл. ночной свет душевая» `1771997851260`, «Выкл. ночной свет душевая» `1771997918348`. **❌ Причина мигания:** порог стоял **8 lx**, а реальный рабочий диапазон датчика в душевой — **0…15 lx**. Замеры 2026-09-14/15 показали метание **7 ↔ 12** (и 0↔5, 8↔13 в другие дни) — ровно вокруг 8. Отсюда дёрганье. **✅ Поставлено (Alex: «ставь 6»):** `below: 6` (вкл) / `above: 6` (выкл). ⚠️ **Риск, о котором предупреждал:** при 2 lx (текущее состояние, покой) выключение ждёт `above: 6` и может не наступить → свет остаётся гореть. Гистерезис не сделан **осознанно** — по прямому указанию Alex. Если свет будет залипать — варианты: гистерезис `вкл below 4 / выкл above 10`, либо гасить ночной свет по факту включения основной лампы. **Питфолл (важно для будущих правок):** REST `GET/POST /api/config/automation/config/` отдаёт и принимает поля **во множественном числе — `triggers` / `conditions`** (в файле `automations.yaml` это `trigger`/`condition`). Правка по `trigger`/`condition` через API молча уходит в пустые пути, POST возвращает `200 {"result":"ok"}`, но значения НЕ меняются. **Всегда читать конфиг обратно и сверять значения.** **Рабочий скрипт:** `~/tmp-t610/fix_shower_light_threshold.sh` (читает конфиг → правит только `.below`/`.above` == 8 → POST → читает обратно). Бэкап файла: `~/tmp-t610/automations/automations.yaml.bak-20260915-105455`. ---