[2026-09-16] eagle: family/how-to/ha-automations.md family/tech/zigbee-t610-z2m-i-zha.md

This commit is contained in:
Alexey Martemyanov
2026-09-16 20:12:37 +06:00
parent 64dfc5762c
commit 75d053ceb4
2 changed files with 74 additions and 24 deletions
+72 -24
View File
@@ -267,6 +267,56 @@ trigger:
## 4. Ночной свет душевой 2
> 🔴 **Главный разбор — §4.4** (`condition` внутри `sequence` не отменяет остальные `actions`). Читать до правок этой автоматизации.
### 4.4. 🔴 КОРЕНЬ ВСЕХ БАГОВ: `condition` внутри `sequence` не отменяет остальные `actions` (2026-09-16)
**Симптом:** свет зажигался после выхода из душевой, несмотря на проверку присутствия. Задержка 2→3→5 с не помогала, `fading_time` 10→2 не помогал.
**Первопричина — доказана трассировкой HA (`14:02:38`):**
```
14:02:38.426597 action/0/choose/0/sequence/1/entity_id/0
{"result": false, "state": "off", "wanted_state": "on"} ← проверка ОТБИЛА
14:02:38.427615 action/1 → light.turn_on ← ВКЛЮЧИЛ
```
> 🔴 **`condition:` внутри `sequence:` прерывает ТОЛЬКО свою `sequence`.** Управление возвращается в родительский список `actions` — и выполняются следующие шаги. Если `light.turn_on` стоит **после** `choose` (на верхнем уровне `actions`), он выполнится **всегда**, независимо от результата проверки.
**Сломанная структура (моя ошибка, 3 итерации):**
```yaml
actions:
- choose:
- conditions: [trigger lux]
sequence:
- {delay: 5}
- {condition: state, presence == on} # false → вышли из sequence
default: []
- action: light.turn_on # ❌ выполняется ВСЕГДА
```
**Правильная структура — действие ВНУТРИ ветки:**
```yaml
actions:
- choose:
- conditions: [{condition: trigger, id: presence}]
sequence: [{action: light.turn_on, ...}] # заход → мгновенно
- conditions: [{condition: trigger, id: lux}]
sequence:
- {delay: {seconds: 5}}
- {condition: state, entity_id: presence, state: "on"}
- {action: light.turn_on, ...} # ✅ внутри, после проверки
default: []
```
**Верификация:** конфиг прочитан обратно — оба `light.turn_on` внутри `choose`; автоматизации 25/24 `on`/1 `off`, `unavailable` = 0.
**Бэкап:** `/config/automations.yaml.bak-shower-final-20260916-211051`.
**Скрипт:** `~/tmp-t610/fix_shower_final.sh`.
> 🔴 **ПИТФОЛЛ ДИАГНОСТИКИ: трассировки HA — единственный способ увидеть реальные значения условий.** Доступны **только по WebSocket** (`trace/list` + `trace/get`), REST отдаёт 404. `item_id` = **внутренний ID автоматизации** (`1771997851260`), **не** `entity_id`. Файл `.storage/trace.saved_traces` содержит только вручную сохранённые через UI, живые трассировки — в памяти.
> 🧰 Инструмент: `~/tmp-t610/trace_dump.py` (Mac, python3 + `websocket-client`). На t610 python3 НЕТ.
---
**Сущности** (device_id `4095e7c3b47b9dc9640cfb8c3aeff022` = радар, `4d6e55505ff7dbad13d2674cdcb18d5a` = лампа):
- `sensor.shower_2_presence_sensor_illuminance` — освещённость, lx
@@ -413,7 +463,9 @@ mode: single
---
## 4.4. 📋 Разбор: ложные включения при выходе (feedback loop, 2026-09-16)
## 4.6. 📋 Разбор: ложные включения при выходе (2026-09-16) — предыстория
> **✅ ЗАКРЫТО.** Корень найден трассировкой — см. **§4.5**, итоговая конструкция — **§4.4** (действие внутри ветки `choose`). Ниже сохранён исходный разбор как контекст.
**Симптом (Alex):** «выхожу, выключаю свет, через 2 секунды включается подсветка хоть и не должна».
@@ -453,46 +505,42 @@ mode: single
---
## 4.5. ✅ ИТОГ: фиксированная задержка заменена на `wait_for_trigger` (2026-09-16)
## 4.5. ⛔ ЧТО НЕ СРАБОТАЛО: полная хронология перебора (2026-09-16)
**Хронология перебора — что НЕ сработало и почему:**
> **Итоговое решение — §4.4** (действие **внутри** ветки `choose`). Ниже — что перебиралось и почему каждый вариант провалился.
| Попытка | Конструкция ветки `lux` | Результат |
| # | Конструкция ветки `lux` | Результат |
|---|---|---|
| 1 | `delay: 2 s` + `condition: device / is_occupied` | свет зажигался через ~3 с ❌ |
| 2 | `delay: 3 s` + `is_occupied` | зажигался через 5 с ❌ |
| 3 | `delay: 5 s` + `is_occupied` | зажигался через 5 с ❌ |
| 4 | `wait_for_trigger from: on to: off` + `{{ wait.trigger is not none }}` + `condition: state == on` | **противоречивая конструкция** — требует presence и `off`, и `on` одновременно → свет не включался бы вообще ❌ (поймано до прода) |
| 5 | `delay: 5 s` + `condition: state == on` | зажигался ❌ |
| 6 | `wait_for_trigger to: off`, `timeout: 6 s` + `{{ wait.trigger is none }}` | ✅ **применено** |
| 6 | `wait_for_trigger to: off`, `timeout: 6 s` + `{{ wait.trigger is none }}` | зажигался ❌ (это поймала трассировка) |
| 7 | **`light.turn_on` внутри той же ветки `choose`** | ✅ **итог — §4.4** |
**Почему таймер не работал, хотя арифметика сходилась.** Замеренные окна в циклах Alex: `13:58` — lux-триггер `13:58:10.54`, presence `off` в `13:58:13.73` (окно **3.19 с**); `14:00` — окно **3.59 с**; `14:02` — окно **3.19 с**. Задержка 5 с формально перекрывала все три, проверка `condition: state` при `presence = off` **работает** (проверено изолированным тестом: при `presence = off` свет не включается). Тем не менее включение происходило.
**Накладные расходы:** замер `call → свет on` при `delay: 5` дал **6.1 с** (≈1.1 с на диспетчеризацию). Т.е. фактический момент проверки плавает и таймером его надёжно не поймать.
**Почему варианты 1–6 провалились, хотя арифметика сходилась.** Замеренные окна в циклах Alex: `13:58` — lux-триггер `13:58:10.54`, presence `off` в `13:58:13.73` (окно **3.19 с**); `14:00` — **3.59 с**; `14:02` — **3.19 с**. Задержка 5 с формально перекрывала все три, а проверка `condition: state` при `presence = off` **действительно работает** (проверено изолированным тестом: при `presence = off` свет не включается).
> 🔴 **ВЫВОД (главный урок): для «дождаться, пока датчик отпустит» фиксированный таймер принципиально ненадёжен — момент проверки плавает из-за накладных, а окно отпускания зависит от того, сколько Alex двигался. Нужен триггер по СОБЫТИЮ, а не по времени.**
**Настоящая причина — не в условиях, а в структуре `actions`.** Трассировка `14:02` показала:
**Применённая конструкция (ветка `lux`):**
```yaml
- wait_for_trigger:
- {platform: state, entity_id: binary_sensor.shower_2_presence_sensor_presence, to: "off"}
timeout: {seconds: 6}
continue_on_timeout: true
- condition: template
value_template: "{{ wait.trigger is none }}" # сработал → кто-то ушёл → ОТБОЙ
mode: restart
```
14:02:38.426597 action/0/choose/0/sequence/1/entity_id/0
{"result": false, "state": "off", "wanted_state": "on"} ← проверка ОТБИЛА
14:02:38.427615 action/1 → light.turn_on ← ВКЛЮЧИЛ
```
Семантика: если за 6 с присутствие **пропало хоть на миг** — включать не будем. Если держалось всё время — включаем.
`condition` внутри `sequence` прерывает **только свою `sequence`**; управление возвращается в родительский список `actions`, где стоял `light.turn_on` **вне** `choose` — и выполнялся всегда. Полный разбор — **§4.4**.
> 🔴 **ПИТФОЛЛ: не строить `wait_for_trigger` с взаимоисключающими условиями.** Вариант `from: on, to: off` + следующее условие `presence == on` — логически невыполним (ждём уход и одновременно требуем присутствие). Свет по такой ветке не включится **никогда**. Проверять выполнимость цепочки до заливки в прод.
> ⚠️ **`wait.completed` vs `wait.trigger`:** `wait.completed` = `true` при срабатывании, `false` при таймауте → для «дождаться ухода» нужно `{{ not wait.completed }}` или `{{ wait.trigger is none }}`. В сценарии ВЫКЛ (§4.2) семантика обратная — там нужен `{{ wait.completed }}` (ждём возврат).
> 🔴 **Главный урок (главнее любых задержек):** порядок `choose` → «условие» → **действие снаружи** ломает всю логику молча. Проверка может вернуть `false`, и действие всё равно выполнится. **Действие обязано быть внутри той же ветки, после проверки.**
> 🔴 **Второй урок, про диагностику:** три итерации ушли на перебор задержек, потому что я не читал **трассировку автоматизации**. Она лежала в HA всё время и сразу показала `result: false` перед включением. Трассировки — **только WebSocket** (`trace/list` + `trace/get`), `item_id` = внутренний ID. Инструмент: `~/tmp-t610/trace_dump.py`.
**Метод диагностики, который дал ответ (воспроизводимо):** сопоставление трёх рядов в одном окне — `history/period` по `presence` + `illuminance` + `light` и `logbook` по обеим автоматизациям с полем `message` (`triggered by numeric state of ...` показывает, **какая ветка** сработала). Без `message` версию с `presence` и `lux` не различить.
> 🔴 **ПИТФОЛЛ: не строить `wait_for_trigger` с взаимоисключающими условиями.** Вариант 4 (`from: on, to: off` + требование `presence == on`) логически невыполним. Проверять выполнимость цепочки до заливки в прод.
> ⚠️ **`wait.completed` vs `wait.trigger`:** `wait.completed` = `true` при срабатывании, `false` при таймауте. В сценарии ВЫКЛ (§4.2) нужен `{{ wait.completed }}` (ждём возврат); для «дождаться ухода» — `{{ wait.trigger is none }}`.
**Бэкапы:** `.bak-shower-delay3-20260916-205557`, `.bak-shower-delay5-20260916-205953`, `.bak-shower-wait-20260916-*`, `.bak-shower-waitoff-20260916-*`.
**Скрипты:** `~/tmp-t610/fix_shower_delay3.sh`, `fix_shower_delay5.sh`, `fix_shower_waitrelease.sh`, `fix_shower_state_cond.sh`, `fix_shower_waitoff.sh`; тесты — `test_state_condition.sh` (проверка, что `condition: state` отбивает), `test_trigger_lag.sh` (замер накладных), `check_shower_timing.sh` (три ряда истории).
**Метод диагностики, который дал ответ:** сопоставление трёх рядов в одном окне — `history/period` по `presence` + `illuminance` + `light` и `logbook` по обеим автоматизациям с полем `message` (`triggered by numeric state of ...` показывает, **какая ветка** сработала) — **плюс `trace/get` по WebSocket**, который вскрыл корень.
> ⏳ **Ждёт подтверждения Alex.** Если зажжётся и при этой конструкции — значит модель последовательности действий (кто/когда двигается) понята неверно, и нужен замер с секундомером от самого Alex.
**Бэкапы:** `.bak-shower-delay3-*`, `.bak-shower-delay5-*`, `.bak-shower-wait-*`, `.bak-shower-waitoff-*`, `.bak-shower-final-20260916-211051`.
**Скрипты:** `~/tmp-t610/fix_shower_final.sh` (итог), `fix_shower_delay3.sh`, `fix_shower_delay5.sh`, `fix_shower_waitoff.sh`; тесты — `test_state_condition.sh`, `test_trigger_lag.sh`, `check_shower_timing.sh`; трассировки — `trace_dump.py`, `trace_read.py`.
---