Files
obsidian-vault/family/tech/kitchen-hood-fan-template.md
T

351 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "🍳 Вытяжка кухни — смена домена light → fan (`fan.kitchen_hood`, 3 скорости)"
aliases:
- kitchen hood
- kitchen_hood
- вытяжка домен
- вытяжка категория свет
- TS0003 fan
- hood light to fan
- fan.kitchen_hood
- kitchen-hood-domain-conversion
- kitchen-hood-fan-template
created: '2026-09-16'
updated: '2026-09-16 — ✅ ЗАКРЫТО полностью: `fan.kitchen_hood` создан, зона назначена, свет и диагностика скрыты, ссылка в карте этажей `home_plan` переведена с мёртвого `fan.fan_3` на `fan.kitchen_hood``'
type: tech
namespace: family
status: 🟢 РАБОТАЕТ. `fan.kitchen_hood` (3 скорости, `speed_count: 3`) поверх `light.kitchen_hood_light[,_2,_3]`. Три света + 13 сущностей диагностики скрыты. План этажей починен. Проверено фактом 2026-09-16. ⚠️ Порядок скоростей ждёт физической проверки — §10.
tags:
- family
- tech
- smarthome
- home-assistant
- t610
- haos
- zigbee
- zha
- template
related:
- '[[family/how-to/home-automation]]'
- '[[family/how-to/ha-automations]]'
- '[[family/tech/zigbee-t610-z2m-i-zha]]'
- '[[family/tech/ha-registry-operations]]'
- '[[family/documents/home-automation-wishlist]]'
---
# 🍳 Вытяжка кухни — смена домена (light → fan)
> **Задача Alex (2026-09-16):** «можно всё-таки кухонную вытяжку перевести из категории свет в другую?» → ответ: прямого способа нет; сделан `fan.kitchen_hood` Template-хелпером поверх трёх реле, свет спрятан.
>
> **Единственная дока по вытяжке.** Раньше было две (`kitchen-hood-domain-conversion` — исследование, `kitchen-hood-fan-template` — реализация) — сведены сюда 2026-09-16. Оба имени оставлены в `aliases`.
---
## 1. Ответ на исходный вопрос: «никак нельзя»
**Прямого способа сменить домен `light.` → `switch.`/`fan.` у существующей сущности в HA НЕТ.** Проверено по всем четырём путям:
| Путь | Результат |
|---|---|
| **UI «Показать как»** (`device_class`) | ❌ Домена не меняет. Для `light` вообще не применимо |
| **Реестр сущностей** (`config/entity_registry/update` + `new_entity_id`) | ❌ Домен менять нельзя, только имя внутри домена. ⚠️ Питфолл 4 в [[family/tech/zigbee-t610-z2m-i-zha]]: «Переименование `light.``switch.` запрещено HA» |
| **`switch_as_x`** (офиц. хелпер «Change device type of a switch») | ❌ **Источник — только `switch`.** Конвертирует в Light/Cover/Fan/Lock/Siren/Valve, но `light` на вход НЕ принимает. Для ZHA-вытяжки бесполезен |
| **Кастомный quirk в `zhaquirks`** | ⚠️ Технически возможно (подменить Light-cluster на Switch), но ломается при каждом обновлении HA + переименование сущностей рвёт все ссылки. Отвергнуто |
| **`homeassistant: customize:`** | ❌ Меняет только атрибуты (device_class/icon), **не домен**. `light.` останется `light.` |
> 📄 `switch_as_x` — https://www.home-assistant.io/integrations/switch_as_x/
> Ключевая цитата доки: «lets you convert any Home Assistant **switch** into a Home Assistant Light, Cover, Fan, Lock, Siren, or Valve».
**Работает только обход:** Template-хелпер создаёт **новую** сущность рядом, оригинальные прячутся.
> ⚠️ **Почему Template работает там, где `switch_as_x` пасует:** у Template-сущности блок `action:` может звать **любой** сервис (`light.turn_on`), тогда как `switch_as_x` жёстко ограничен источником-`switch`. Это и есть обход ограничения домена.
### Варианты, которые рассматривались
| # | Что | Итог |
|---|---|---|
| **A** | Template `fan` поверх **одного** `light_2` | промежуточный; отвергнут — не даёт 3 скоростей |
| **B** | Template `switch` (без `percentage`) | если скорость не нужна, нужен просто «не свет» |
| **C** | Три `fan`/`switch` + автоматизация взаимоблокировки / `input_select` | ⭐ **не понадобился**`set_percentage` с `choose` дал то же самое в одной сущности |
---
## 2. Железо и что отдаёт ZHA
**Устройство:** `kitchen_hood` · IEEE `a4:c1:38:07:b6:4c:7f:d4` · `_TZ3000_odzoiovu` · **TS0003** (3-gang реле) · Router · зона `kitchen` · device_id `200ea4fb25daaa907279045e47a330b5`
**ZHA отдаёт реле как `light`, не `switch`:**
| Сущность | Роль | Состояние до работ |
|---|---|---|
| `light.kitchen_hood_light` | скорость 1 (33 %) | `on` |
| `light.kitchen_hood_light_2` | скорость 2 (66 %) | `off` |
| `light.kitchen_hood_light_3` | скорость 3 (100 %) | `off` |
Все три: `supported_color_modes: ["onoff"]`, `supported_features: 8`, платформа `zha`.
> 🔑 **`supported_features: 8` + `supported_color_modes: ["onoff"]` = чистое реле без яркости.** Именно поэтому HA-логика «это лампа» здесь ложная, а переклассификация в `fan` осмысленна.
> 🔑 **Ключевое для понимания:** TS0003 — это **три независимых реле**, а «3 скорости вытяжки» на железе = три провода, из которых активен ровно один. Поэтому HA изначально показала их как три света, а не как одну вентиляционную сущность. Один `fan` с процентами склеивает их логически.
**Прочие сущности того же устройства** (позже скрыты, см. §8): `select.kitchen_hood_indicator_mode` (`LightWhenOn`), `select.kitchen_hood_power_outage_memory` (`Off`), `sensor.kitchen_hood_power` / `_voltage` / `_current` / `_energy` (все `0.0`), `button.kitchen_hood_identify`, `update.kitchen_hood_firmware`, `sensor.kitchen_hood_lqi` / `_rssi`.
> 🔴 **`sensor.kitchen_hood_lqi` / `_rssi` → HTTP 404 через `/api/states`.** Это **норма**: `disabled_by: integration` — сущности в реестре есть, в runtime их нет. **Не считать поломкой.**
---
## 3. Что сделано — шаг 1: Template-фан
### 3.1. Бэкап
```
/config/configuration.yaml.bak-fanhood-20260916-081926 (30108 байт)
```
### 3.2. Новый блок `template:` в `/config/configuration.yaml`
Добавлен **16-м** блоком `template:` (после `fan_3`), перед `- cover:`. `speed_count: 3` → шаг 33 % в UI.
```yaml
- fan:
- name: Kitchen hood
unique_id: kitchen_hood_fan
default_entity_id: fan.kitchen_hood
speed_count: 3
state: >
{{ is_state('light.kitchen_hood_light','on')
or is_state('light.kitchen_hood_light_2','on')
or is_state('light.kitchen_hood_light_3','on') }}
percentage: >
{% if is_state('light.kitchen_hood_light_3','on') %}100
{% elif is_state('light.kitchen_hood_light_2','on') %}66
{% elif is_state('light.kitchen_hood_light','on') %}33
{% else %}0
{% endif %}
turn_on:
- action: light.turn_on
target:
entity_id: light.kitchen_hood_light
turn_off:
- action: light.turn_off
target:
entity_id:
- light.kitchen_hood_light
- light.kitchen_hood_light_2
- light.kitchen_hood_light_3
set_percentage:
- action: light.turn_off
target:
entity_id:
- light.kitchen_hood_light
- light.kitchen_hood_light_2
- light.kitchen_hood_light_3
- choose:
- conditions:
- condition: template
value_template: "{{ percentage | int(0) > 0 and percentage | int(0) <= 33 }}"
sequence:
- action: light.turn_on
target:
entity_id: light.kitchen_hood_light
- conditions:
- condition: template
value_template: "{{ percentage | int(0) > 33 and percentage | int(0) <= 66 }}"
sequence:
- action: light.turn_on
target:
entity_id: light.kitchen_hood_light_2
- conditions:
- condition: template
value_template: "{{ percentage | int(0) > 66 and percentage | int(0) <= 100 }}"
sequence:
- action: light.turn_on
target:
entity_id: light.kitchen_hood_light_3
```
**Логика:** `set_percentage` сначала гасит **все три** реле, затем зажигает одно нужное → **взаимоисключение гарантировано**, состояние «две скорости одновременно» невозможно by design.
> 🔴 **ИСПРАВЛЕНО 2026-09-16 (было ошибочно в первой версии доки):** ранняя версия утверждала, что «Template-fan с `percentage` не даст настоящую скорость — он включит одно реле». **Факт опроверг:** один Template-fan с `set_percentage` + `choose` **даёт полноценные 3 скорости** — 33/66/100 % выбирают нужное реле, `percentage` читается обратно из состояния. Взаимоблокировку обеспечивает `set_percentage`, отдельная автоматизация и `input_select` **не нужны**. Проверено живым прогоном — §4.
**Порядок применения:** правка YAML → локальный парс → `scp` на t610 → `check_config`**`homeassistant.restart`** (не reload!) → `RUNNING`.
### 3.3. Скрытие света
Три сущности скрыты через WS `config/entity_registry/update` с `hidden_by: "user"`.
Скрипт: `~/tmp-t610/hood_hide.py` (возврат — тот же скрипт с `--unhide`).
---
## 4. ✅ Проверка на живом железе
| Шаг | `fan.kitchen_hood` | light_1 | light_2 | light_3 |
|---|---|---|---|---|
| baseline | `off / 0` | off | off | off |
| `set_percentage 33` | `on / 33` | **on** | off | off |
| `set_percentage 66` | `on / 66` | off | **on** | off |
| `set_percentage 100` | `on / 100` | off | off | **on** |
| `turn_off` | `off / 0` | off | off | off |
`fan.kitchen_hood`: `supported_features: 49` = TURN_ON | TURN_OFF | SET_SPEED ✅
> ️ `speed_count` **не отдаётся** как атрибут состояния — HA его не публикует. Шаги ползунка считаются из `percentage_step`. Это норма, не дефект.
---
## 5. 🔴 Шаг 2: доводка UI — «вижу показатели kitchen hood и нет нового вентилятора»
**Симптом Alex:** «Я в кухне до сих пор вижу показатели kitchen hood и нет нового вентилятора».
**Две причины, обе найдены фактом:**
| # | Причина | Факт |
|---|---|---|
| 1 | **`fan.kitchen_hood` создан без зоны** — Template-сущность из YAML не привязана к устройству → `area_id: null`, на кухне не видна | WS-реестр: `area=null`. У `fan.fan_3` зона `kitchen` **была** → он и торчал на кухне вместо нового |
| 2 | **Диагностика устройства не скрыта** — «показатели kitchen hood» = 4 сенсора + 2 select + update + button, все `hidden_by=None` | Найдено сканом `config/entity_registry/list` |
**Фикс — скрипт `~/tmp-t610/hood_cleanup.py`** (обратимо флагом `--unhide`):
1. `fan.kitchen_hood``area_id: kitchen`
2. Скрыто `hidden_by: user`**13 сущностей**:
- `fan.fan_3` (мёртвый template-фан «Вытяжка Кухня»)
- `switch.fan_3_low` / `_medium` / `_high` + `script.set_fan_3_speed` — подтверждены `unavailable` (slave 10 / AT2 офлайн)
- `sensor.kitchen_hood_energy` / `_power` / `_voltage` / `_current`
- `select.kitchen_hood_indicator_mode` / `_power_outage_memory`
- `update.kitchen_hood_firmware`
- `button.kitchen_hood_identify`
**Проверено чтением обратно:** все 13 → `hidden_by=user`; `fan.kitchen_hood``area=kitchen`, `hidden_by=None`.
---
## 6. 🔴 Шаг 3: настоящая причина «он не открыт» — карта этажей
**Симптом Alex:** «Нихуя он не открыт!» — на кухне в UI по-прежнему старая вытяжка, новый `fan.kitchen_hood` отсутствует.
**Причина — дашборд `home_plan` ссылался руками на мёртвую сущность.**
Дашборд `home_plan` (storage-mode, `/config/.storage/lovelace.home_plan`) содержит `picture-elements` с иконкой:
```json
{ "type": "state-icon", "entity": "fan.fan_3", "tap_action": {"action": "more-info"},
"style": {"top": "18.0%", "left": "86.3%"} }
```
`fan.fan_3` = мёртвый template-фан (источник `switch.fan_3_*` закомментирован, slave 10 офлайн). Иконка на кухне вела на него.
**Диагностика — как найти мёртвую ссылку:**
```bash
# все entity, на которые ссылается план
jq -r '.. | objects | select(.entity? != null) | .entity' /config/.storage/lovelace.home_plan | sort -u
# что из них реально живо — сверить со /api/states
```
> 🔴 **`hidden_by: user` НЕ влияет на дашборды со storage-mode.** Ссылки в `.storage/lovelace.*` вписаны руками — скрытие сущности их не трогает. Тот же класс проблемы, что питфолл «план этажей ссылался на снесённого призрака `light.smart_light_stairs_l1`» ([[family/tech/zigbee-t610-z2m-i-zha]] §13.3).
> 🔴 **Второе:** `hidden_by` не влияет и на `area_entities()` в шаблонах. `area_entities("kitchen")` продолжает отдавать скрытые сущности — это два независимых механизма. Если сущность нужна «невидимой» реально, её надо `disabled_by`, либо убрать ссылку из дашборда.
**Фикс — скрипт `~/tmp-t610/fix_plan_hood.py`** (dry-run по умолчанию, `--apply` для записи):
1. Рекурсивный обход JSON плана → замена `fan.fan_3``fan.kitchen_hood`
2. `assert` что `fan.fan_3` не осталось в выводе
3. Бэкап remote → `scp` на t610
**Результат:** заменено **1** вхождение (`.data.config.views[0].sections[0].cards[0].elements[6]`). Бэкап `lovelace.home_plan.bak-hoodfan-20260916-074008`.
**Проверено фактом на t610:** `fan.fan_3` в плане = **0**, `fan.kitchen_hood` = есть, `jq -e .` → JSON валиден.
> ⚠️ Дашборд читает `.storage` при загрузке страницы → нужен **hard-refresh (Cmd+Shift+R)**, обычный F5 может отдать кэш.
### 6.1. Итоговая цепочка причин (три штуки, каждая выглядела как «не работает»)
| # | Что было | Как выглядело | Фикс |
|---|---|---|---|
| 1 | `fan.kitchen_hood` без зоны (`area_id: null`) | «на кухне нет нового вентилятора» | WS `area_id: kitchen` |
| 2 | диагностика реле не скрыта | «вижу показатели kitchen hood» | 8 сущностей → `hidden_by: user` |
| 3 | **план этажей ссылался на `fan.fan_3`** | «старая вытяжка вместо новой» | `fix_plan_hood.py --apply` |
> 🔑 **ГЛАВНЫЙ УРОК:** «создал сущность» ≠ «пользователь её видит». Проверять **четыре** вещи:
> 1. `/api/states/<entity>` отвечает
> 2. `area_id` в реестре не `null` (Template-сущности из YAML зону **не получают** — нет `device_id` → назначать вручную WS `config/entity_registry/update` с `area_id`)
> 3. диагностика устройства скрыта (`hidden_by: user`)
> 4. **дашборды/план этажей не ссылаются на мёртвые сущности** (`jq` по `.storage/lovelace.*`)
---
## 7. Питфоллы
| # | Питфолл | Обход |
|---|---|---|
| 1 | **`switch_as_x` не принимает `light`** | Источник только `switch`. Для ZHA-light бесполезен — нужен Template |
| 2 | **Домен сущности сменить нельзя** | Только Template-хелпер рядом + скрыть оригинал |
| 3 | **`!include` ломает `yaml.safe_load`** при локальной проверке | Заглушка: `L.add_multi_constructor('!', ...)` — иначе проверка конфига локально невозможна |
| 4 | 🔴 **`check_config` через сервис отдаёт `[]`** | Это **не** ошибка — сервис не возвращает тело. Проверять `GET /api/config``.state` или `/api/template` |
| 5 | **`_1`/`_2`/`_3` ≠ порядок скоростей автоматически** | ZHA нумерует по endpoint'ам, не по физической скорости. ⚠️ Требует физической проверки — §10 |
| 6 | **Template `turn_on` без аргумента** | `fan.turn_on` без `percentage` → скорость 1 (33 %). Задано явно |
| 7 | 🔴 **Правка `configuration.yaml` требует `restart` HA Core** | `automation reload` здесь **не** поможет — новый template-блок не подхватится |
| 8 | 🔴 **REST `POST` без `-H "Content-Type: application/json"` → пустой ответ** | Всегда ставить заголовок |
| 9 | 🔴 **Заголовок авторизации рвётся маскировщиком Hermes** в скриптах | Собирать по частям либо Python + `urllib` (пример — `hood_scan.py`, `hood_hide.py`, `hood_cleanup.py`) |
| 10 | **`sensor.kitchen_hood_lqi` / `_rssi` → 404 в `/api/states`** | `disabled_by: integration` — норма, не поломка |
| 11 | 🔴 **Template-сущность из YAML не получает `area_id`** → «создал, а в UI нет» | Назначать зону вручную WS `config/entity_registry/update` |
| 12 | 🔴 **`hidden_by: user` не влияет на дашборды и `area_entities()`** | Правки кэширует UI → hard-refresh; ссылки в `.storage/lovelace.*` править руками |
| 13 | 🔴 **Правка `.storage/lovelace*` вступает в силу только при загрузке страницы** | Hard-refresh (Cmd+Shift+R), F5 может отдать кэш |
---
## 8. Итоговое состояние кухни (вытяжка)
| Видно пользователю | Скрыто (`hidden_by: user`) |
|---|---|
| `fan.kitchen_hood` (зона `kitchen`) | `fan.fan_3`, `switch.fan_3_low/medium/high`, `script.set_fan_3_speed` |
| `light.kitchen_hood_light` / `_2` / `_3` | `sensor.kitchen_hood_energy/power/voltage/current` |
| | `select.kitchen_hood_indicator_mode` / `_power_outage_memory` |
| | `update.kitchen_hood_firmware`, `button.kitchen_hood_identify` |
> `sensor.kitchen_hood_lqi` / `_rssi` — `disabled_by: integration`, не отображаются нигде (норма).
**Карта этажей `home_plan`:** иконка на кухне (`elements[6]`, 18 %/86.3 %) переведена на `fan.kitchen_hood`.
**`fan.fan_3` не удалён** — только скрыт. Возврат: `hood_cleanup.py --unhide`.
---
## 9. Роадмап вентиляции
[[family/documents/home-automation-wishlist]] §2 п.1 ожидает **«Kitchen Hood»** как конечный узел алгоритма вентиляции по CO₂ (Node-RED: `Demand Aggregator → Intake Allocation → Discretization → Damper Outputs → Exhaust Arbitration → Fans → Kitchen Hood`). Ранее узел был мёртв вместе с контуром AT2 — теперь у вытяжки есть **рабочая сущность `fan.kitchen_hood`**, на которую можно вешать автоматику. Ждёт решения по slave 10 (AT2 снят Alex'ом).
---
## 10. ⚠️ ОТКРЫТО — требует проверки Alex
1. 🔴 **Порядок скоростей не подтверждён физически.** Принято как `_1`=33 / `_2`=66 / `_3`=100, но какое **реле** = какая **скорость** — не проверялось. Нужно встать у вытяжки: поставить 33 % и 100 %, убедиться что низкая/высокая. **Если перепутано — правка трёх строк** в `percentage` и `set_percentage`.
2. ⚠️ **`light.kitchen_hood_light` мог быть лампой подсветки, а не скоростью.** До работ он был `on` (горел), после рестарта HA стал `off`. Если это **лампа подсветки вытяжки** — вынести отдельно, фан её не должен гасить.
> 📌 **Проверка для будущей сессии (одной командой):** поставить `fan.set_percentage` на 33 и на 100 и сравнить, какое реле зажглось и **что физически слышно**. Маппинг в коде — три строки в `percentage` и три ветки `choose` в `set_percentage`.
---
## 11. Артефакты
| Файл | Назначение |
|---|---|
| `~/tmp-t610/configuration.yaml.current` | рабочая копия конфига (с новым блоком) |
| `~/tmp-t610/hood_scan.py` | read-only скан: devices + entities + device_class + area + states |
| `~/tmp-t610/hood_hide.py` | скрыть/показать три `light.*` (`--unhide`) |
| `~/tmp-t610/hood_cleanup.py` | зона `kitchen` + скрытие 13 сущностей (`--unhide`) |
| `~/tmp-t610/fix_plan_hood.py` | правка ссылки в `home_plan` (dry-run / `--apply`) |
| `~/tmp-t610/home_plan.json` | локальная копия карты этажей |
**Бэкапы на t610:**
`configuration.yaml.bak-fanhood-20260916-081926` · `lovelace.home_plan.bak-hoodfan-20260916-074008`
---
## Связанные
- [[family/how-to/home-automation]] — топология, t610, Zigbee, команды, питфоллы
- [[family/tech/zigbee-t610-z2m-i-zha]] — ZHA, реестры, переименование, питфолл «домен менять нельзя», §13.3 план этажей
- [[family/tech/ha-registry-operations]] — реестры HA: переименование, опции, призраки
- [[family/how-to/ha-automations]] — автоматизации, справочник
- [[family/documents/home-automation-wishlist]] — роадмап автоматизаций (Kitchen Hood как узел CO₂)