15 KiB
План: автоматизация греющего кабеля ввода воды (t610)
Статус: конструкция согласована 2026-09-16 — выдержка 12 ч, порог −8 °C, 0 helper'ов (прогноз через
action:+response_variable). ⏳ Ждёт отмашки: как реализовать выдержку 12 ч (вариант A или B, см. ниже). Предыстория: family/how-to/home-automation · family/tech/zigbee-t610-z2m-i-zha · family/tech/heating-cable-water-inlet
Задача
Управлять греющим кабелем ввода воды по уличной температуре, чтобы бетонная подушка фундамента не промёрзла и труба не встала.
Исходные данные (факты, проверены)
| Факт | Значение | Источник |
|---|---|---|
| Регион | Новосибирск, ДНП «Лаки Парк» (~55.26°N) | Nominatim, бэкап плана |
| Грунт | суглинок | Alex |
| Труба в грунте | ~4 м | Alex |
| Бетонная подушка | 30 см | Alex |
| Тип кабеля | саморегулирующийся | Alex |
| Управление | switch.heating_cable_plug (NEO NAS-WR01B, котельная, a4c138eb6fbe9d19) |
HA states |
| Уличный датчик | sensor.h2000_pro_temperatura_ulitsa (ZONT) |
HA states |
| Внешний прогноз | weather.forecast_laki_dom (met.no) |
HA states |
Расчёт (СП 131.13330.2020)
Сумма |отрицательных среднемесячных| по Новосибирску: янв −17,6 · фев −15,4 · мар −7,7 · ноя −7,9 · дек −15,3 → Mt = 63,9
d_fn = d0 · √Mt = 0,23 · √63,9 = 1,84 м (суглинок, открытая поверхность)
Под снегом 30–40 см реально 1,3–1,6 м.
Глубина затухания годовой волны в суглинке (a = λ/C = 5,6·10⁻⁷ м²/с):
z_damp = √(a·T/π) = 2,37 м
На глубине 4 м амплитуда годового хода: 19 · e^(−4/2,37) = ±3,5 °C от среднегодовой.
→ Труба на 4 м не замерзает физически: +2,5…+3,5 °C круглый год.
Критичная точка — бетонная подушка 30 см
Бетон промерзает быстрее грунта: λ = 1,7 против 1,4, снеговой шубы нет.
Термические сопротивления: R_возд = 1/15 = 0,067, R_бетон = 0,30/1,7 = 0,176.
→ 72 % перепада падает на бетон.
| Улица | Труба в бетоне |
|---|---|
| −6 °C | +0,5 °C |
| −8 °C | −0,1 °C ← порог |
| −10 °C | −0,6 °C |
| −15 °C | −1,9 °C |
| −20 °C | −3,1 °C |
| −25 °C | −4,4 °C |
Пороги автоматизации
| Событие | Условие | Выдержка |
|---|---|---|
| ВКЛ | min(ZONT, прогноз24ч) ≤ −8 °C |
12 ч |
| ВЫКЛ | min(ZONT, прогноз24ч) ≥ −3 °C |
12 ч |
| Аварийный ВКЛ | ≤ −20 °C |
без выдержки |
| Fail-safe | оба источника unavailable > 1 ч |
ВКЛ + push |
| Обрыв кабеля | после ВКЛ + 10 мин power < 5 W |
push |
Почему 12 ч
Тепловая инерция бетонной подушки ~сутки. 12 ч отсекает одиночные ночные заморозки сентября-октября (бетон за ночь не промёрзнет), но ловит резкое похолодание в тот же день. Alex выбрал 12 ч как компромисс между 6 и 24.
Почему саморег меняет логику
Саморегулирующийся кабель сбрасывает мощность при нагреве сам — перегрев невозможен. Значит задержка нужна не «чтобы не спалить», а «чтобы не дёргать реле на каждую холодную ночь». Экономия электричества вторична.
Почему прогноз обязателен
ZONT — одна точка, может врать или отвалиться (прецедент: 2026-09-16 показывал °F вместо °C). Внешний прогноз — независимый канал. Берём худшее из двух (fail-safe: замёрзшая труба дороже лишних киловатт).
Шаги выполнения
- Бэкап
automations.yaml— ✅automations.yaml.bak-cable-20260915-234837(локально:~/tmp-t610/bak-cable-20260915-234837/automations.yaml, 650 строк) - Конструкция определена — ✅
action: weather.get_forecasts+response_variable, 1 автоматизация, 0 helper'ов. YAML-заготовка готова:~/tmp-t610/new_cable_autos.yaml - ⏳ Решить выдержку 12 ч (вариант A или B) — ждёт Alex
- Создать автоматизацию,
reload automations, проверитьon+ отсутствие ошибок - Проверить, что сущности существуют в runtime
- Обновить
family/how-to/home-automation.md— раздел про ввод воды - Записать питфоллы (WS
weather/subscribe_forecast) в доки
🔴 Конструкция: прогноз через ДЕЙСТВИЕ автоматизации (не через шаблон)
Открытие 2026-09-16 (две итерации). Первый вывод — «прогноз недоступен из
шаблона» — верен, но неполон. Второй вывод, «нужен input_number +
обновлятор» — НЕВЕРЕН, отменён. Итоговая конструкция проще: 0 helper'ов.
Что проверено фактом
| Способ | Результат |
|---|---|
атрибут forecast у weather.forecast_laki_dom |
None — прогноза в атрибутах нет |
weather.get_forecasts(...) в Jinja-шаблоне |
'weather' is undefined |
weather.get_forecasts как действие (action: + response_variable) |
✅ работает — 48 точек |
сервис weather.get_forecasts (REST/WS, мн. число) |
✅ работает, 48 ч + 6 дней |
WS weather/subscribe_forecast |
✅ работает (для стрима/CLI) |
✅ ГЛАВНОЕ:
weather.get_forecastsвызывается как ДЕЙСТВИЕ автоматизации сresponse_variable. Внутри action-блокаforecastдоступен последующимvariables:иchoose:. Это штатный, документированный путь.🔴 Ошибка, которую я допустил: проверил только Jinja-форму
{{ weather.get_forecasts(...) }}→'weather' is undefined— и сделал вывод «нельзя, нужен helper». На самом деле вaction:он работает. Alex поймал это вопросом «Ты серьезно?!». Урок: прежде чем строить обходной слой, проверь штатный путь целиком — не только удобную форму вызова.
Порядок в автоматизации (критично)
actions:
- action: weather.get_forecasts # 1. ПЕРВЫМ действием
target:
entity_id: weather.forecast_laki_dom
data:
type: hourly
response_variable: wx
- variables: # 2. ПОСЛЕ него — forecast уже есть
zont: "{{ states('sensor.h2000_pro_temperatura_ulitsa') | float(99) }}"
fc_min: >-
{{ wx['weather.forecast_laki_dom']['forecast'][:24]
| map(attribute='temperature') | min | default(99) | float(99) }}
eff: "{{ [zont, fc_min] | min }}"
- choose: [...] # 3. решения по eff
🔴
variables:вычисляются ДО действий. Если объявитьfc_minвvariables:на верхнем уровне автоматизации,forecastтам ещё не существует. Обязательно:variables:— внутриactions:, ПОСЛЕ вызова сервиса.
Путь до данных: response_variable = result.response целиком, поэтому
wx['weather.forecast_laki_dom']['forecast'] (проверено: ключи
['weather.forecast_laki_dom'] → ['forecast'] → 48 точек с полем temperature).
Что создаётся — 3 автоматизации, 0 новых сущностей
| № | Объект | Назначение |
|---|---|---|
| 1 | automation.heating_cable_ctl |
одна автоматизация: time_pattern /15 + numeric_state + state (отвал) → choose с 5 ветками |
Ветки choose:
| # | Условие | Действие |
|---|---|---|
| 1 | eff <= -20 |
ВКЛ + persistent + push (без выдержки) |
| 2 | zont == 99 (датчик недоступен) |
ВКЛ + push (fail-safe) |
| 3 | eff <= -8 and not cable_on |
ВКЛ + persistent + push |
| 4 | eff >= -3 and cable_on |
ВЫКЛ + persistent |
| 5 | cable_on and cable_power < 5 |
push «нет потребления» |
Плюс финальное действие logbook.log с zont / fc24 / eff / on / P —
для диагностики срабатываний в UI.
Файл с заготовкой: ~/tmp-t610/new_cable_autos.yaml (YAML валиден).
⏳ Не решено: как реализовать выдержку 12 ч
for: '12:00:00' нельзя повесить на вычисляемый шаблон eff — только на реальную
сущность. Два варианта:
| Как | Плюс | Минус | |
|---|---|---|---|
| A | numeric_state + for: 12:00:00 на sensor.h2000_pro_temperatura_ulitsa, отдельный триггер на прогноз |
честные 12 ч по температуре | выдержка только по ZONT, прогноз учитывается иначе |
| B ⭐ | time_pattern + for: '12:00:00' на switch.heating_cable_plug в условии включения |
одна автоматизация, всё работает | выдержка отсчитывается от состояния розетки, а не от температуры |
Рекомендация: B — включение срабатывает, только если порог держится 12 ч, а розетка всё это время выключена. Ждёт отмашки Alex.
Питфоллы
- 🔴
weather.get_forecastsработает как ДЕЙСТВИЕ (response_variable), но НЕ как Jinja-функция. В шаблоне даёт'weather' is undefined. Проверено фактом. - 🔴
variables:вычисляются доactions:— объявлять вычисления отresponse_variableтолько ВНУТРИactions:, после вызова сервиса. - Имя сервиса — множественное число
weather.get_forecasts. Единственноеweather.get_forecastне существует →not_found. - WS-команда —
weather/subscribe_forecast, полеforecast_type(неtype). Ниweather/forecast, ниweather.get_forecastчерез WS не работают →unknown_command/not_found. - Результат WS-подписки приходит двумя сообщениями: сначала
result, потомevent. WS-хелпер, ждущий толькоresult, вернёт пустой прогноз. - 🔴
render_templateпо WebSocket возвращаетnullдаже для{{ 2 + 2 }}. Шаблоны рендерить ТОЛЬКО через RESTPOST /api/template. Проверено фактом — на этом я дважды получил ложныйNoneи едва не сделал неверный вывод. for:нельзя вешать на template/вычисляемое значение — только на сущность.weather.forecast_laki_dom— met.no, привязана к посёлку (координаты Лаки Парк). 48 ч почасового + 6 дней дневного.switch.heating_cable_plugотдаёт0 W / 0 kWhприvoltage 223 Vиstate off— это норма, не поломка. Нагрузка не потребляет, пока выключено.- ⚠️ При записи скриптов
write_fileпортит строку с литераломAuthorization: Bearer *** (режет как секрет). Обход: собирать заголовок из переменных —P1=Authorization; P2=Bearer; AUTH="${P1}: ${P2} ${TOK}"`.
Инструменты
~/tmp-t610/ha_ws.py forecast <entity> <daily|hourly>— рабочий вывод прогноза (добавлена командаforecast,collect_events=Trueдля подписок).~/tmp-t610/test_action_forecast.py— доказательство, чтоweather.get_forecastsработает как действие и отдаёт 48 точек.~/tmp-t610/rest_tpl.sh,test_min_tpl.sh,probe_forecast.sh— рендер шаблонов через REST (проверка доступности функций Jinja).~/tmp-t610/get_states.sh— выгрузка states через SSH (REST HA доступен только изнутри t610 поhttp://172.30.32.1/api/, снаружи — черезwss://mallexxx.duckdns.org/api/websocket).
Проверка результата
- Автоматизации
on,unavailable= 0 - Принудительный прогон с подставной температурой → кабель включается
sensor.heating_cable_plug_power > 0при включении (кабель реально греет)- В Logbook видны строки
zont=… fc24=… eff=… on=… P=…W— значит действиеlogbook.logиresponse_variableработают