Files
obsidian-vault/family/plans/t610-heating-cable-automation.md
T

15 KiB
Raw Blame History

План: автоматизация греющего кабеля ввода воды (t610)

Статус: ВЫПОЛНЕНО 2026-09-16 — автоматизация automation.greiushchii_kabel_upravlenie загружена, on, ошибок нет. Реализован вариант B (выдержка через for: 12:00:00 на switch.heating_cable_plug). Источник: family/how-to/ha-automations §7 Предыстория: family/how-to/home-automation · family/tech/zigbee-t610-z2m-i-zha

Задача

Управлять греющим кабелем ввода воды по уличной температуре, чтобы бетонная подушка фундамента не промёрзла и труба не встала.

Исходные данные (факты, проверены)

Факт Значение Источник
Регион Новосибирск, ДНП «Лаки Парк» (~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,31,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: замёрзшая труба дороже лишних киловатт).

Шаги выполнения

  1. Бэкап automations.yaml automations.yaml.bak-cable-20260915-234837 (локально: ~/tmp-t610/bak-cable-20260915-234837/automations.yaml, 650 строк)
  2. Конструкция определена action: weather.get_forecasts + response_variable, 1 автоматизация, 0 helper'ов. YAML-заготовка готова: ~/tmp-t610/new_cable_autos.yaml
  3. Решить выдержку 12 ч (вариант A или B) — ждёт Alex
  4. Создать автоматизацию, reload automations, проверить on + отсутствие ошибок
  5. Проверить, что сущности существуют в runtime
  6. Обновить family/how-to/home-automation.md — раздел про ввод воды
  7. Записать питфоллы (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 }}. Шаблоны рендерить ТОЛЬКО через REST POST /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 работают