[2026-09-15] eagle: family/how-to/home-automation.md family/plans/t610-heating-cable-automation.md family/tech/ha-registry-operations.md family/tech/heating-cable-water-inlet.md

This commit is contained in:
Alexey Martemyanov
2026-09-15 23:57:20 +06:00
parent 174d7104ed
commit 82e5cb2640
4 changed files with 156 additions and 76 deletions
+97 -51
View File
@@ -1,7 +1,9 @@
# План: автоматизация греющего кабеля ввода воды (t610)
> Статус: **согласован 2026-09-16** — выдержка 12 ч, порог −8 °C.
> Предыстория: [[family/how-to/home-automation]] · [[family/tech/zigbee-t610-z2m-i-zha]]
> Статус: **конструкция согласована 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]]
## Задача
@@ -85,69 +87,108 @@ ZONT — одна точка, может врать или отвалиться
1. Бэкап `automations.yaml` — ✅ `automations.yaml.bak-cable-20260915-234837`
(локально: `~/tmp-t610/bak-cable-20260915-234837/automations.yaml`, 650 строк)
2. **Создать 6 объектов** (см. раздел «Конструкция» ниже) — ⏳ не начато
3. `reload automations` и проверить `on` + отсутствие ошибок
4. Проверить, что сущности существуют в runtime
5. Обновить `family/how-to/home-automation.md` — раздел про ввод воды
6. Записать питфоллы (WS `weather/subscribe_forecast`) в доки
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.** Прогноз в этой версии HA недоступен шаблону — проверено
четырьмя способами:
**Открытие 2026-09-16 (две итерации).** Первый вывод — «прогноз недоступен из
шаблона» — **верен, но неполон**. Второй вывод, «нужен `input_number` +
обновлятор» — **НЕВЕРЕН, отменён**. Итоговая конструкция проще: **0 helper'ов**.
### Что проверено фактом
| Способ | Результат |
|---|---|
| атрибут `forecast` у `weather.forecast_laki_dom` | **`None`** |
| `weather.get_forecasts(...)` в Jinja | **`'weather' is undefined`** |
| атрибут `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` | ✅ работает |
| WS `weather/subscribe_forecast` | ✅ работает (для стрима/CLI) |
**Следствие:** пороги нельзя написать шаблоном «на лету» — прогноз надо
**материализовать в сущность**. Иначе в триггер автоматизации его не подставить.
> ✅ **ГЛАВНОЕ: `weather.get_forecasts` вызывается как ДЕЙСТВИЕ автоматизации
> с `response_variable`.** Внутри action-блока `forecast` доступен последующим
> `variables:` и `choose:`. Это штатный, документированный путь.
>
> 🔴 **Ошибка, которую я допустил:** проверил только Jinja-форму
> `{{ weather.get_forecasts(...) }}` → `'weather' is undefined` — и сделал вывод
> «нельзя, нужен helper». На самом деле в `action:` он работает. Alex поймал это
> вопросом «Ты серьезно?!». **Урок: прежде чем строить обходной слой, проверь
> штатный путь целиком — не только удобную форму вызова.**
**Решение — слой обновления:**
### Порядок в автоматизации (критично)
```
automation.heating_cable_forecast_update (каждый час)
weather.get_forecasts('weather.forecast_laki_dom','hourly')
→ min(map(attribute='temperature')[:24])
→ input_number.heating_cable_forecast_min
sensor.heating_cable_outside_min_24h (template)
→ min( sensor.h2000_pro_temperatura_ulitsa ,
input_number.heating_cable_forecast_min )
```yaml
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
```
Триггеры ВКЛ/ВЫКЛ/авария вешаются **на template-сенсор** — он числовой и всегда
доступен. Исходный вариант (триггер прямо на прогноз) был нерабочим.
> 🔴 **`variables:` вычисляются ДО действий.** Если объявить `fc_min` в
> `variables:` на верхнем уровне автоматизации, `forecast` там ещё не существует.
> **Обязательно:** `variables:` — внутри `actions:`, ПОСЛЕ вызова сервиса.
### Что создать (6 объектов)
**Путь до данных:** `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` — только на реальную
сущность. Два варианта:
| | Как | Плюс | Минус |
|---|---|---|---|
| 1 | `input_number.heating_cable_forecast_min` | helper | мин. прогноз на 24 ч |
| 2 | `sensor.heating_cable_outside_min_24h` | template | `min(ZONT, прогноз)` |
| 3 | `automation.heating_cable_forecast_update` | automation | обновляет №1 раз в час |
| 4 | `automation.heating_cable_on` | automation | ВКЛ при ≤ 8 °C / 12 ч |
| 5 | `automation.heating_cable_off` | automation | ВЫКЛ при ≥ 3 °C / 12 ч |
| 6 | `automation.heating_cable_emergency` | automation | ≤ 20 °C, fail-safe, обрыв |
| **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` в условии включения | одна автоматизация, всё работает | выдержка отсчитывается от состояния розетки, а не от температуры |
### Открытый вопрос (не решён)
**Где создавать helper №1** — Storage (UI, правится мышкой, но значение не
попадает в док) или YAML (`configuration.yaml`, версионируется, но правка только
файлом)? Alex не ответил.
Смежный вопрос: пороги −8/−3/−20 сейчас зашиты в YAML автоматизаций. Если Alex
захочет пороги из UI — нужно 4 helper'а вместо одного.
Файл с заготовкой: `~/tmp-t610/new_cable_autos.yaml` (3 автоматизации, YAML валиден).
**Рекомендация:** **B** — включение срабатывает, только если порог держится 12 ч,
а розетка всё это время выключена. **Ждёт отмашки Alex.**
## Питфоллы
- 🔴 **`weather.get_forecasts` — ТОЛЬКО как сервис, не в Jinja.** В шаблоне даёт
`'weather' is undefined` (вызов сервисов из шаблонов убран). Проверено фактом.
- 🔴 **`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`).
@@ -156,20 +197,24 @@ sensor.heating_cable_outside_min_24h (template)
- Результат WS-подписки приходит **двумя сообщениями**: сначала `result`, потом
`event`. WS-хелпер, ждущий только `result`, вернёт пустой прогноз.
- 🔴 **`render_template` по WebSocket возвращает `null`** даже для `{{ 2 + 2 }}`.
Шаблоны рендерить ТОЛЬКО через REST `POST /api/template`. Проверено фактом.
Шаблоны рендерить ТОЛЬКО через 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 $TOK` (режет как секрет). Обход: собирать заголовок из
`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/rest_tpl.sh`, `test_jinja_forecast.sh`, `probe_forecast.sh`
- `~/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/`, снаружи — через
@@ -180,4 +225,5 @@ sensor.heating_cable_outside_min_24h (template)
- Автоматизации `on`, `unavailable` = 0
- Принудительный прогон с подставной температурой → кабель включается
- `sensor.heating_cable_plug_power > 0` при включении (кабель реально греет)
- `input_number.heating_cable_forecast_min` обновляется ежечасно
- В Logbook видны строки `zont=… fc24=… eff=… on=… P=…W` — значит действие
`logbook.log` и `response_variable` работают