Files
obsidian-vault/family/plans/t610-zigbee-ids-battery-freshsensors-modbus.md
T

560 lines
43 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: "План и результат: читаемые ID Zigbee, алерты батарей, виртуальные Modbus-датчики"
created: '2026-09-15'
updated: 2026-09-16 (поздний вечер: найден рабочий REST-путь `https://mallexxx.duckdns.org`, установлена папка проекта `~/Automation/HA-ZONT-Modbus`, файл проекта синхронизирован с HA и закоммичен `2eaa704`, патч объединения душевой применён к проекту — заливка в HA ожидается)
type: plan
namespace: family
status: 🟢 ВЫПОЛНЕНО 2026-09-16. Шаги 14 закрыты 2026-09-15; 5–6 (вычистка призраков + починка UI) — 2026-09-16 ночью; 7–8 (автоматизации кабинета — 2 дефекта; душевая — `fading_time` 10→2, задержка 10 с в ВЫКЛ, ложные включения ВКЛ) — 2026-09-16 днём. Всё проверено фактами. 🟡 Открыт один вопрос — feedback loop по порогам душевой (§4.6 в [[family/how-to/ha-automations]]), правка отклонена Alex.
tags:
- t610
- zigbee
- zha
- modbus
- battery
related:
- '[[family/tech/zigbee-t610-z2m-i-zha]]'
- '[[family/how-to/home-automation]]'
- '[[family/how-to/ha-automations]]'
---
# План и результат: читаемые ID, алерты батарей, виртуальные Modbus-датчики
> Задача от Alex (2026-09-15): 1) убедиться, что у **всех** Zigbee-устройств читаемые ID; 2) каждому **батарейному** — оповещение о низком заряде; 3) каждый **температурный** Zigbee-датчик прописать как виртуальное устройство Modbus в bridge; 4) обновить доку.
>
> ✅ **Всё выполнено в тот же вечер.** Ниже — план И фактический результат.
---
## 0. Исходная точка (снимок до работ)
Пришло **+7 новых TS0201** (датчики тёплых полов). ZHA: 24 записи (23 устройства + координатор), было 17.
| IEEE | Исходное `name_by_user` | Модель | Зона |
|---|---|---|---|
| `a4c13827ea585609` | `Темп теплый пол гостиная` | TS0201 | living_room |
| `a4c1382ac15720dd` | `Температура серая` | TS0201 | severnaia |
| `a4c1382dac5e10e2` | `Темп теплый пол кабинет` | TS0201 | kabinet |
| `a4c13837021f5298` | `Темп теплый пол кухня` | TS0201 | kitchen |
| `a4c13867875ee7d3` | `Темп теплый пол ванная` | TS0201 | vannaia |
| `a4c138cde6eed013` | `Темп теплый пол прихожая` | TS0201 | prikhozhaia |
| `a4c13865e226312d` | `Темп теплый пол душевая` | TS0201 | dushevaia |
### 0.1. 🔴 Дефекты ID, найденные при проверке
| # | Дефект | Пример | Итог |
|---|---|---|---|
| D1 | **Имя устройства по-русски** (HA транслитерирует) | `Темп теплый пол гостиная``sensor.temp_teplyi_pol_gostinaia_temperatura` | ✅ исправлено |
| D2 | **Перепутан префикс: `tualet_` при зоне `prikhozhaia`** | `sensor.tualet_temp_teplyi_pol_prikhozhaia_temperatura` | ✅ исправлено |
| D3 | **Дубли `_2` / `_3` у LQI/RSSI** (остатки миграции) | `sensor.tz3000_gjnozsaz_ts011f_lqi_2`, `_3` | ✅ исправлено |
| D4 | `sauna` — зона `tualet` | `switch.sauna` в зоне ТУАЛЕТ | ⛔ **НЕ дефект** — Alex: «Её зона туалет 1». Оставлено |
| D5 | `select`-сущности `sauna` названы `bed_dimmer_*` | `select.bed_dimmer_power_on_behavior` | ✅ исправлено |
### 0.2. Исходная занятость Modbus-адресов bridge
| Slave | Что | Регистр |
|---|---|---|
| 100 | `sensor.office_temperature_sensor_temperature` (Room temp) | 100 |
| 101 | снифф `dining_temperature` + **запись** `switch.recirculation_pump` | 100 / 1 |
| 102 | снифф `kids_temperature` | 100 |
| 103 | снифф `bedroom_temperature` | 100 |
| 104 | Zigbee-реле `switch.boiler_controller_power` | 1 |
Свободно: **105+**. Оповещений о низком заряде не было ни у одного батарейного.
---
## 1. ✅ РЕЗУЛЬТАТ — Шаг 1: единый вид ID (23 устройства)
**Формат: `<устройство>_<роль>`.** Для датчиков `_temperature` / `_humidity` / `_battery` / `_lqi` / `_rssi`, плюс `button.<dev>_identify` и `update.<dev>_firmware`. Для реле `_power` / `_voltage` / `_current` / `_energy`, `select.<dev>_indicator_mode` / `_power_outage_memory`.
| Было | Стало |
|---|---|
| `Темп теплый пол гостиная` | `living_room_floor_temperature` |
| `Температура серая` | `severnaia_floor_temperature` |
| `Темп теплый пол кабинет` | `kabinet_floor_temperature` |
| `Темп теплый пол кухня` | `kitchen_floor_temperature` |
| `Темп теплый пол ванная` | `vannaia_floor_temperature` |
| `Темп теплый пол прихожая` | `prikhozhaia_floor_temperature` |
| `Темп теплый пол душевая` | `dushevaia_floor_temperature` |
| `office_temperature_sensor` | `kabinet_temperature` |
| `light.night_light_shower_2` | `light.dushevaia_night_light` |
| `binary_sensor.tz3000_hy6ncvmw_ts0222` | `binary_sensor.light_sensor_stairs` |
**Масштаб:** **140 переименований** (8 устройств + 68 + 16 + 56 + 2 сущностей). Все 23 устройства в зонах, «без зоны» — нет.
### 1.1. 🔴 Механика — `name_by_user` НЕ переименовывает `entity_id`
HA перегенерирует `entity_id` только у **новых** сущностей. Нужны **два** шага:
```python
# Шаг 1 — имя устройства
{"type":"config/device_registry/update","device_id": D, "name_by_user": "living_room_floor_temperature"}
# Шаг 2 — КАЖДАЯ сущность отдельно
{"type":"config/entity_registry/update","entity_id":"sensor.old_name","new_entity_id":"sensor.new_name"}
```
### 1.2. Команды (воспроизведение)
```bash
# Полный аудит: устройства + сущности + зоны
cd ~/tmp-t610 && python3 zha_devs.py
# Переименование устройств (WS)
python3 rename_step1.py
# Переименование сущностей пакетом
python3 gen_rename_all.py # → /tmp/rename_all_plan.json (защита от конфликтов)
python3 apply_rename_all.py
# Аудит остатка нечитаемых
python3 audit_rest.py
```
**Скрипты:** `~/tmp-t610/{zha_devs,rename_step1,gen_rename_all,apply_rename_all,audit_rest,rename_ents,fix_last}.py`
### 1.3. 🔴 Побочный эффект: рвутся ссылки в автоматизациях
После переименования **молча** сломались 2 ссылки в `automations.yaml`:
- `light.night_light_shower_2` (×2) → `light.dushevaia_night_light`
- `sensor.tz3000_akqdg6g7_ts0201_batareia``sensor.kabinet_temperature_battery`
**Проверка (обязательна после любого переименования):**
```bash
curl -s -H @/tmp/h1 "$B/api/states" | jq -r '[.[].entity_id]' > /tmp/all_ents.json
ssh -i ~/.ssh/id_rsa root@192.168.2.176 'cat /config/automations.yaml' \
| grep -oE 'entity_id: [a-z_]+\.[a-zA-Z0-9_]+' | awk '{print $2}' | sort -u \
| while read e; do jq -e --arg e "$e" 'index($e)' /tmp/all_ents.json >/dev/null || echo "BROKEN: $e"; done
```
---
## 2. ✅ РЕЗУЛЬТАТ — Шаг 2: чиста наследия миграции
- `select.bed_dimmer_power_on_behavior` / `_switch_type` под `sauna``select.sauna_power_on_behavior` / `_switch_type`
- Все дубли `_2`/`_3` у LQI/RSSI/energy/identify приведены к `<device>_<role>`
- `sauna` в зоне `tualet`**оставлено** (не дефект, подтверждено Alex)
---
## 3. ✅ РЕЗУЛЬТАТ — Шаг 3: 12 автоматизаций контроля батарей
**Порог 20 %, выдержка 2 ч, двойной канал:** `persistent_notification` (видно в UI) + `notify.mobile_app_sm_s931b` (push). При возврате заряда выше порога уведомление гасится само.
```yaml
- id: '8800000000000' # …8800000000011 — 12 штук
alias: 'Батарея: <место>'
triggers:
- trigger: numeric_state
entity_id: sensor.<device>_battery
below: 20
for: '02:00:00'
id: low
- trigger: numeric_state
entity_id: sensor.<device>_battery
above: 20
id: ok
actions:
- choose:
- conditions: [{condition: trigger, id: low}]
sequence:
- action: persistent_notification.create
data: {title: '🔋 Батарея разряжена', notification_id: 'bat_<slug>'}
- action: notify.mobile_app_sm_s931b
- conditions: [{condition: trigger, id: ok}]
sequence:
- action: persistent_notification.dismiss
data: {notification_id: 'bat_<slug>'}
mode: single
```
**Покрытие (12 сущностей):** туалет 1 · лестница · душевая · котельная (протечка) · кабинет (воздух) · гостиная · серая · кабинет (пол) · кухня · ванная · прихожая · спальня (кнопка).
### 3.1. Заменённые старые
Две прежние автоматизации (`1773451323415`, `1773459663218`) **удалены** — были с порогом 10 и без push.
### 3.2. 🔴 Сироты: `unavailable` при чистом YAML
Тело удалено из `automations.yaml`, запись в реестре осталась → `unavailable`. Удаление:
```python
# Если config/entity_registry/remove падает с id_reuse:
# "Identifier values have to increase"
{"type":"config/entity_registry/update","entity_id": E, "disabled_by": "user"} # сначала это
{"type":"config/entity_registry/remove","entity_id": E} # потом это
```
**Скрипты:** `~/tmp-t610/{add_battery_autos,rm_orphans,rm_orphan2,rm_orphan3}.py`
### 3.3. Границы
`sensor.wireless_light_switch_bed_battery` = `unknown` — кнопка спальни спит. Автоматизация создана, но триггера не будет, пока датчик не проснётся (лечится нажатием кнопки).
---
## 4. ✅ РЕЗУЛЬТАТ — Шаг 4: виртуальные Modbus-датчики (slave 100 + 105112)
Добавлены в `/addons/modbus-bridge/data/config.template.tmpl`, `int16`, `divider: 10`:
| Slave | HA-сущность |
|---|---|
| **100** | `sensor.garderobnaia_temperature_temperature` (исторический «Room temp»; был `office_temperature_sensor``kabinet_temperature`) |
| 105 | `sensor.living_room_floor_temperature_temperature` |
| 106 | `sensor.severnaia_floor_temperature_temperature` |
| 107 | `sensor.kabinet_floor_temperature_temperature` |
| 108 | `sensor.kitchen_floor_temperature_temperature` |
| 109 | `sensor.vannaia_floor_temperature_temperature` |
| 110 | `sensor.prikhozhaia_floor_temperature_temperature` |
| 111 | `sensor.dushevaia_floor_temperature_temperature` |
| 112 | `sensor.toilet_1_floor_temperature_temperature` |
Итого в шаблоне **14 маппингов**. Свободно: `113+`.
### 4.1. 🔴 НАЙДЕНА И ИСПРАВЛЕНА РЕАЛЬНАЯ ПОЛОМКА: slave 100 отдавал `0`
Slave 100 ссылался на **старое** имя `sensor.office_temperature_sensor_temperature` — после переименования сущность стала `kabinet_temperature_temperature`. Bridge молча возвращал `0 [00 00]`.
```
ДО: → Response: 1 register(s) from 100 (ha:sensor.kabinet_temperature_temperature) = 0 [00 00]
ПОСЛЕ: → Response: 1 register(s) from 100 (ha:sensor.kabinet_temperature_temperature) = 23.97 [00 F0]
```
> 📌 **ПРАВИЛО: переименовал HA-сущность → проверь маппинг в `config.template.tmpl` + `rebuild`.**
> 📌 **Проверка slave'а без ZONT'а невозможна** — bridge это serial-slave, отвечает только на запрос. Признаки работы:
> 1. `ha apps logs local_modbus-bridge | grep "HA poll -> sensor.<entity>"` — поллер кэширует HA-значение.
> 2. `... | grep -A3 "Slave: 100"` → `Response: ... = <val> [<hex>]`.
### 4.2. Дубликат slave 113 — убран
При правке добавился «Kabinet air temp» (113) на ту же сущность, что slave 100. Удалён как дубль.
### 4.3. Команды
```bash
# правка шаблона → rebuild ОБЯЗАТЕЛЕН (Dockerfile: COPY data/config.template.tmpl)
scp config.template.tmpl root@192.168.2.176:/addons/modbus-bridge/data/config.template.tmpl
ssh root@192.168.2.176 'ha apps rebuild local_modbus-bridge && ha apps start local_modbus-bridge'
```
**Скрипты:** `~/tmp-t610/{add_modbus_temps,dedup_slave113,fix_slave100}.py`
---
## 4bis. ✅ ПОСЛЕ ПЛАНА — датчик перенесён в Гардеробную (2026-09-15, 22:5423:56)
**Задача Alex (вне исходного плана):** «Переименуй этот датчик в датчик гардеробная и перемести в нее и обнови конфиг моста» → «Кабинет который» (т.е. исторический `office_temperature_sensor`).
**Контекст:** в UI датчик не находился под старым именем (`office_temperature_sensor`) — потому что был переименован в `kabinet_temperature` шагом 1. Именно он и был перенесён.
| Что | Было | Стало |
|---|---|---|
| Имя устройства | `kabinet_temperature` | `garderobnaia_temperature` |
| Зона | `kabinet` | `garderobnaia` |
| Сущности (7) | `sensor.kabinet_temperature_*` | `sensor.garderobnaia_temperature_*` |
| Bridge slave 100 | `sensor.kabinet_temperature_temperature` | `sensor.garderobnaia_temperature_temperature` |
| Автоматизация батареи | «Кабинет (температура)», `bat_kabinet_temperature` | «Гардеробная (температура)`, `bat_garderobnaia_temperature` |
| entity_id автоматизации | `automation.batareia_kabinet_temperatura` | `automation.batareia_garderobnaia_temperatura` |
**Проверено фактом:** `Response: 1 register(s) from 100 (ha:sensor.garderobnaia_temperature_temperature) = 24.61 [00 F6]`.
### 🔴 Питфолл: `id_reuse: Identifier values have to increase`
При переименовании сущностей **6 из 7 упали** с этой ошибкой (хотя целевые `entity_id` были свободны). Это внутренний счётчик реестра, **не поломка**.
**Обход (проверен, 6/6 успешно):** переименовать в промежуточное имя, затем из него в целевое.
```python
# entity_id → dom.tmp_xxx → dom.новое_имя
{"type":"config/entity_registry/update","entity_id": old, "new_entity_id": tmp}
{"type":"config/entity_registry/update","entity_id": tmp, "new_entity_id": new}
# при провале second step — откат на old
```
> 📌 Тот же приём уже применялся для сироты-автоматизации (§3.2, `disabled_by` + `remove`). Общее правило: **любая операция реестра с `id_reuse` лечится промежуточным состоянием.**
### 🔴 Питфолл: HA сам перегенерировал `entity_id` автоматизаций
После `automation/reload` автоматизации получили новые `entity_id` **по alias**: `automation.datchik_protechki_kotelnaia_batareia` → `automation.batareia_kotelnaia_datchik_protechki`. Проверять автоматизации по **`attributes.id`**, а не по `entity_id`.
### Аудит автоматизаций (запрошен Alex: «убедись что автоматизация вся корректная»)
| Метрика | Значение |
|---|---|
| Всего | 24 |
| `on` | 23 |
| `off` | 1 — `Ventilation automation on` (намеренно) |
| `unavailable` | **0** |
| Битых ссылок | **0** (22 проверено) |
| Батарейных | 12, все `on` |
**Бэкапы этого этапа:** `/config/automations.yaml.bak-gard-20260915-225404`, `/addons/modbus-bridge/data/config.template.tmpl.bak-gard-20260915-225404`, реестры в `~/tmp-t610/backup-gard-20260915-225404.json`.
**Скрипты:** `~/tmp-t610/{gard_step1,gard_step2,gard_step2b,gard_fix,fix_aut_entid}.py`.
**Коммит vault:** `db20bb8`.
### ✅ ЗАКРЫТО (2026-09-16, ночь): «Office temperature sensor battery недоступно»
Alex: «Office temperature sensor battery — HA ругается что у устройства нет уникального id и оно недоступно», затем «light.smart_light_stairs_l1 — тоже недоступен».
**Причина найдена — записи лежали в архиве реестра**, а не в runtime: `core.entity_registry → data.deleted_entities`. Поэтому `/api/states`, WS-реестр и `repairs/list_issues` их не видели, а UI «Обслуживание» — видел.
**Решение:** контролируемый снос скриптом `~/tmp-t610/ghost_purge.sh` (исключает живое + вентиляцию, dry-run по умолчанию).
- Снято **185** записей: 6 точечно (`office_temperature_sensor_*` ×5 + `smart_light_stairs_l1`) + 179 пачкой.
- Архив реестра 360 → **175**. Живых 572 — не тронуто.
- На dry-run поймано 3 живых `switch_as_x`-записи (`light.night_light_shower_2`, `light.smart_light_office_left/right`) — исключены.
**Сопутствующее (этап 6):**
- **План этажей** (`home-plan`) ругался «недоступно» — ссылался на снесённого `light.smart_light_stairs_l1`. Исправлено на `light.light_stairs_left`. Все 29 ссылок валидны.
- **H2000_PRO** отдавал °F — ручной override `sensor.private.suggested_unit_of_measurement: "°F"`. Сброшено через WS с `options_domain: "sensor.private"` (⚠️ через `"sensor"` — `success: true`, но НЕ меняет). Стало 12.9 / 28.2 / 30.2 °C.
**Бэкапы:** `core.entity_registry.bak-ghostpurge-20260916-002503`, `.bak-purge-20260916-000530`, `.bak-ghosts-20260916-000329`, `lovelace.home_plan.bak-fixplan-20260916-003231`.
**Детали:** [[family/tech/zigbee-t610-z2m-i-zha]] §13, [[family/tech/ha-registry-operations]].
---
## 4a. ✅ ЭТАП 6 — починка UI после сноса призраков (2026-09-16)
| Проблема | Причина | Фикс |
|---|---|---|
| План этажей: «недоступно» на карте | `state-icon` → снесённый `light.smart_light_stairs_l1` | → `light.light_stairs_left` |
| H2000_PRO: °F вместо °C | override `sensor.private.suggested_unit_of_measurement` | сброс через `options_domain: "sensor.private"` |
**Правило:** **после сноса призраков — проверять ссылки дашбордов.** План этажей жалуется «недоступно» из-за мёртвой ссылки, а не из-за поломки устройства.
**Скрипты:** `fix_plan_stairs.sh` (ссылки плана), `fix_h2000_all.py` (единицы).
**Коммит vault:** `ee86207`.
---
## 4b. ✅ ЭТАП 7 — последствия переименования: починка автоматизаций кабинета + задержка в душевой (2026-09-16, день)
**Контекст:** этапы 1–4 переименовали сущности в единый вид `<device>_<role>`, но **две автоматизации кабинета остались на старых ZHA-именах** — хабы симптомов, найденных на следующий день.
### 7.1. Свитч у стола в кабинете не переключал свет
**Симптом (Alex):** «свитч у стола не тригерит переключение света». Сначала прозвучало «вчера ещё работало».
**Первопричина — урок №2 из §7, реализовавшийся буквально:** переименование рвёт ссылки в `automations.yaml` молча. Из 25 автоматизаций переехали 23, эти две — нет.
| id | alias | Было (мёртвое, 404) | Стало (живое) |
|---|---|---|---|
| `5735cb9f855e462dbdbdf680d0d6a66f` | `office_pass_switch_table` | `light.tz3000_5gey1ohx_ts0002_osveshchenie` | `light.office_table_light_switch_light` |
| `45b96f6f38f6488ba70266fa5da665f5` | `office_pass_switch_main` | `light.tz3000_5gey1ohx_ts0002_osveshchenie_2` | `light.office_table_light_switch_light_2` |
**Как доказано:** триггерные сущности → **404** в `/api/states`; реле при этом живы и щёлкали (нажатия доходили до Zigbee), но целевые `light.smart_light_office_left/right` висели `off` без изменений.
> ⚠️ **Ключевой признак, который легко пропустить:** автоматизация с мёртвым триггером остаётся **`state: on`** — `unavailable` НЕ появляется, ошибок в UI нет. Единственный симптом — `last_triggered` не растёт.
### 7.2. Свет мигал при перезагрузке HA — потерялась защита `not_from`
**Причина:** на TrueNAS триггеры несли `not_from: [unavailable, unknown]` (`~/tmp-t610/stage3/truenas/automations.yaml` — эталон). При пересборке автоматизаций под ZHA поле **не перенесли** → голая `platform: state` срабатывала на старте HA (`unavailable → on`) и дёргала `light.toggle`.
**Фикс:** восстановлен `not_from: [unavailable, unknown]` в обеих автоматизациях.
**Верификация — реальный рестарт HA Core, а не рассуждение:**
| Метрика | До | После | Итог |
|---|---|---|---|
| `office_pass_switch_table.last_triggered` | `05:43:07` | `05:43:07` | не изменился ✅ |
| `office_pass_switch_main.last_triggered` | `05:43:20` | `05:43:20` | не изменился ✅ |
| `light.smart_light_office_left/right` | `off` | `off` | не мигнул ✅ |
В logbook: `automation.office_pass_switch_table → unavailable` (`05:45:02.156`), через 3 мс `→ on` — и **ни одной** записи `triggered by state`. Событие отфильтровано.
> 🆕 **Урок №11:** защитные поля триггера (`not_from`/`to`) **теряются при пересборке автоматизаций**. При любой пересборке — сверять их наличие, а не только `entity_id`. `to:` как альтернатива **не нужен**: наблюдаемый порядок `unavailable → on` отсекается `not_from`.
### 7.3. Задержка 2 с на ветке освещённости в душевой
**Задача (Alex):** освещённость упала → sleep 2 → присутствие == true → включить. **Только на триггер по свету**, по присутствию — мгновенно.
**Зачем:** радар mmWave отдаёт `presence` с задержкой; при падении освещённости присутствие не успевало выставиться → условие не проходило → свет не включался.
**Решение:** триггеры размечены `trigger.id` (`lux` / `presence`), задержка повешена на ветку `lux` через `choose`:
```yaml
triggers:
- {type: occupied, entity_id: binary_sensor.shower_2_presence_sensor_presence, id: presence}
- {type: illuminance, entity_id: sensor.shower_2_presence_sensor_illuminance, below: 6, id: lux}
actions:
- choose:
- conditions: [{condition: trigger, id: lux}]
sequence:
- {delay: {seconds: 2}}
- {condition: device, type: is_occupied, entity_id: binary_sensor...presence}
default: []
- {action: light.turn_on, target: {entity_id: light.dushevaia_night_light}}
```
**Верификация:** замер по `last_changed` — вызов `06:13:34` → свет `06:13:37.24` (2 с выдержаны); ветка `lux` при `presence=off` свет **не** включает.
> 🔴 **ПИТФОЛЛ:** `condition: state` с числовым порогом **не принимает `below`** → `Message malformed: not a valid option at 'conditions[0].below'`. Порог освещённости задаётся только device-условием `type: is_illuminance`.
> 🔴 **ПИТФОЛЛ:** `automation.trigger` **не подставляет `trigger.id`** → прогон всегда уходит в `choose.default`, ветку так не протестировать. Проверять временным скриптом (`/api/config/script/config/<tmp>` → reload → вызов → DELETE).
> ⚠️ `delay` живёт только в `actions:` и не знает, какой триггер сработал — ветки различать через `trigger.id` + `choose`.
**ВЫКЛ (`1771997918348`) НЕ тронут** — по решению Alex задержка нужна только на триггере по свету.
### 7.4. `fading_time` радара 10 → 2 с + задержка 10 с в сценарии ВЫКЛ
**Задача (Alex):** снизить `fading_time` радара и перенести задержку в сценарий выключения — «если в течение 10 с датчик не поменялся снова на присутствие — выключать».
| Что | Было | Стало |
|---|---|---|
| `number.shower_2_presence_sensor_fading_time` | `10.0` | **`2.0`** — нижний предел железа |
| ВЫКЛ `1771997918348` | `turn_off` сразу | `wait_for_trigger` (presence→on) + `timeout: 10 s` → гасить, если не вернулось. `mode: restart` |
> 🔴 **ПИТФОЛЛ:** `fading_time` у `_TZE204_qasjif9e` (TS0601) **не опускается ниже 2 с**, хотя HA показывает `min: 1`. Проверено: `1.0` → откат к `2.0`; `1.5` → откат; `2.5` → записалось. HA отдаёт `state: 1.0` в ответе сервиса, но сущность возвращается к `2.0` — **всегда читать обратно**.
> 🔴 **ПИТФОЛЛ:** `wait_for_trigger` + `wait.completed` внутри `choose` требует **`mode: restart`** — при `mode: single` повторный триггер игнорируется и ожидание не перезапускается.
### 7.5. Тюнинг задержки ВКЛ: 2 → 3 → 5 с (закрытие ложных включений)
**Симптом:** вышел, выключил свет — подсветка зажигалась. Задержки 2 с и 3 с не хватало.
**Замер (цикл `13:58`):** при `fading_time = 2 с` радар отпускает присутствие **через 3.2 с после выключения света**. Задержка 3 с заканчивалась в `13:58:13.54`, presence сбрасывался в `13:58:13.73` — **на 0.19 с позже**, поэтому проверка `is_occupied` проходила и свет включался.
**Фикс (шаг 7):** `delay` в ветке `lux` поднят до **5 с** (запас 1.8 с).
> 🔴 **ПРАВИЛО ПОДБОРА: задержка должна быть больше ОКНА ОТПУСКАНИЯ радара (не `fading_time`).** Измеренное окно при `fading_time = 2 с` = **3.2 с**.
⛔ **НО 5 с ТОЖЕ НЕ ПОМОГЛИ.** Шаг 8 (см. ниже) показал, что причина была не в задержке вообще.
**Бэкапы:** `/config/automations.yaml.bak-office-20260916-124244`, `.bak-showerdelay-20260916-131126`, `.bak-shower-off-20260916-205245`, `.bak-shower-delay3-20260916-205557`, `.bak-shower-delay5-20260916-205953`, `.bak-shower-final-20260916-211051` на t610; `~/tmp-t610/automations/automations.yaml.{office-before,shower-before}` локально.
**Скрипты:** `fix_office_switch.sh`, `fix_office_guard.sh`, `fix_shower_lux_delay.sh`, `fix_shower_as_requested.sh`, `fix_shower_delay3.sh`, `fix_shower_delay5.sh`, **`fix_shower_final.sh`** (итог), `test_shower_lux_branch.sh`, `test_delay_timing.sh`, `check_shower_timing.sh`, **`trace_dump.py` / `trace_read.py`** (трассировки) — все в `~/tmp-t610/`.
**Детали:** [[family/how-to/ha-automations]] §2.2, §3, §4.1–§4.6; питфоллы №2741 в [[family/tech/zigbee-t610-z2m-i-zha]].
---
## 4.8. ✅ РЕЗУЛЬТАТ — Шаг 8: КОРЕНЬ ложных включений душевой (2026-09-16, день)
**Симптом:** свет включался после выхода из душевой, несмотря на проверку присутствия. Задержка 2→3→5 с и `fading_time` 10→2 **не помогали** — ни один вариант не дал результата.
**Первопричина — найдена трассировкой HA, а не перебором настроек:**
```
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`), выполнялся **всегда** — при любом результате проверки.
**Это была ошибка проектирования автоматизации, а не настройки.** Alex трижды просил «чинить то, что просили», а я подбирал числа вместо того, чтобы прочитать трассировку, которая всё время лежала в HA.
**Фикс:** оба `light.turn_on` перенесены **внутрь** веток `choose`:
```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: []
```
**Как искать причину (переиспользуемый рецепт):**
```bash
# 1. Трассировки — ТОЛЬКО WebSocket, REST → 404
# item_id = ВНУТРЕННИЙ ID автоматизации (1771997851260), НЕ entity_id
python3 ~/tmp-t610/trace_dump.py 14:02:33
# 2. Сопоставить три ряда в одном окне: history/period по presence + illuminance + light
# 3. logbook С ПОЛЕМ message → «triggered by numeric state of ...» = какая ветка choose
```
**Верификация:** конфиг прочитан обратно (оба `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`.
> ⏳ **Ждёт практического подтверждения Alex:** зайти в тёмную душевую (должно включиться сразу) и выйти, выключив свет (должно не зажечься).
**Детали:** [[family/how-to/ha-automations]] §4.4 (корневой разбор), §4.5 (что не сработало), §4.6 (предыстория).
---
### 7.6. 🟠 Объединение двух автоматизаций душевой — патч в проекте, заливка в HA ОЖИДАЕТСЯ (2026-09-16, поздний вечер)
**Задача Alex:** убрать гонку двух автоматизаций (`1771997851260` ВКЛ + `1771997918348` ВЫКЛ), реагирующих на одни события presence/lux. HA **не имеет** взаимной блокировки между автоматизациями — `mode` работает только внутри своей.
**Решение (согласовано Alex, дожато до финала):** одна автоматизация `1771997851260`, **4 триггера** (`P`/`Poff`/`Llow`/`Lhi`), **4 ветки `choose`**, `mode: restart`. Вторая (`1771997918348`) — отключается (пустые `triggers`/`actions`), не удаляется.
**Правки Alex поверх первой редакции — не откатывать:**
1. `wait_for_trigger` из ветки 3 **убран** («Триггер должен перезапустить сценарий») — `mode: restart` уже убивает текущий запуск при новом триггере.
2. Проверка `presence off` в ветке 3 **убрана** как избыточная.
3. Проверка `presence on` после `delay` в ветке 2 **убрана** — по той же логике `restart`.
**🔴 ГЛАВНОЕ ОТКРЫТИЕ СЕССИИ — папка проекта и транспорт:**
- **Папка проекта: `~/Automation/HA-ZONT-Modbus`** (git-репозиторий, файл `homeassistant/automations.yaml`). `~/tmp-t610/automations/` — свалка скриптов, **не проект**. В доке это записано не было — Alex поправил.
- **Рабочий REST-путь к HA: `https://mallexxx.duckdns.org`** (401 без токена, 200 с токеном). `192.168.2.176:8123` — ssh-аддон, порт закрыт.
- **Ручная склейка через `awk` провалена трижды**; заменена на `patch`-инструмент по якорям — сработал с первого раза. Обязательная проверка перед записью: `comm -23` по `^- id:` + `yaml.safe_load`.
**✅ Выполнено:**
1. Синхронизация файла проекта с HA: 283 строки / 14 автоматизаций → **806 / 25**, хэш совпал. **Коммит `2eaa704`** «Sync automations.yaml from t610 prod (14 → 25 automations)».
2. Патч финального конфига в **файле проекта** (не в HA): объединённый сценарий + заглушка второй. Проверено: 823 строки, 25 `^- id:`, `comm` пуст, YAML валиден.
**🟠 Ожидается (остановлено по «Стоп»):**
1. Заливка через `POST /api/config/automation/config/<ID>` (поля **во множественном числе**).
2. Чтение обратно + сверка.
3. `POST /api/services/automation/reload` → 25/24 `on`/1 `off`/`unavailable` = 0.
4. **Коммит патча** в git.
**Состояние:** боевой HA = исходные 2 автоматизации душевой. Патч лежит в рабочей копии git, не закоммичен.
**Инструменты:** `~/tmp-t610/ha_duck.sh` (рабочее чтение), `~/tmp-t610/post_merge_duck.py` (заливка, не запущен).
**Бэкапы:** `~/Automation/HA-ZONT-Modbus/homeassistant/automations.yaml.bak-20260916-205509`; на t610 — `.bak-shower-merge-20260916-214012` (эталон 25 автоматизаций), `.bak-before-restore-20260916-214949`.
**Детали:** [[family/how-to/ha-automations]] §4.7.
---
## 5. ✅ РЕЗУЛЬТАТ — Шаг 5: дока обновлена
- `family/tech/zigbee-t610-z2m-i-zha.md` — карта 16 → 23 устройств, §2.1 «как переименовывать», §4 батареи, §5 виртуальные Modbus, питфоллы 1721.
- `family/how-to/home-automation.md` — шапка, §1 итог, §6 карта slave'ов 100112.
- `family/how-to/ha-automations.md` — карта автоматизаций 14 → 26, батарейные.
- Коммит vault: **`ddba016`**.
---
## 6. Бэкапы, снятые перед работами (2026-09-15 22:34)
| Что | Где |
|---|---|
| Снапшот HA (full) | `23a119f4.tar` в `/backup/` на t610 |
| Реестры (entities/devices/areas) | `~/tmp-t610/backup-preids-20260915-223427/registries-before.json` |
| Реестры (на t610) | `/config/.storage.bak-preids-20260915-223427/` |
| `automations.yaml` | `/config/automations.yaml.bak-preids-20260915-223427` |
| `config.template.tmpl` | `/addons/modbus-bridge/data/config.template.tmpl.bak-preids-20260915-223427` |
---
## 7. Ключевые уроки
| # | Урок |
|---|---|
| 1 | `name_by_user` и `entity_id` — **разные вещи**, менять оба |
| 2 | Переименование рвёт ссылки в `automations.yaml` **молча** — проверять всегда |
| 3 | Переименование рвёт маппинг bridge → slave отдаёт `0` **молча** |
| 4 | Сирота-автоматизация (`unavailable` при чистом YAML) → `remove`, а при `id_reuse` — сначала `disabled_by: user` |
| 5 | Bridge нельзя проверить без ZONT'а иначе, чем через `HA poll ->` в логе |
| 6 | `rebuild` (не `restart`) после правки `data/config.template.tmpl` |
| 7 | Русские имена устройств → HA транслитерирует в мусорные `entity_id` |
| 8 | `id_reuse: Identifier values have to increase` — штатная ошибка реестра, обходится промежуточным именем (`old → tmp → new`) |
| 9 | HA перегенерирует `entity_id` автоматизаций по alias при `reload` — искать по `attributes.id` |
| 10 | Датчик, «не находимый в UI», может быть переименован — искать по IEEE в реестре, а не по старому имени |
| 11 | **Защитные поля триггера (`not_from`) теряются при пересборке автоматизаций** — при пересборке сверять не только `entity_id`, но и их наличие. Симптом-близнец: свет мигает при рестарте HA |
| 12 | **Автоматизация с мёртвым триггером остаётся `state: on`** — `unavailable` не появляется. Признак один: `last_triggered` не растёт. Проверять сверкой всех `entity_id:` из YAML со `/api/states` |
| 13 | `automation.trigger` не подставляет `trigger.id` → ветку `choose` по `trigger.id` так не протестировать; нужен временный скрипт |
| 14 | `condition: state` не принимает числовой `below` — порог только device-условием `type: is_illuminance` |
| 15 | 🔴 **`condition:` внутри `sequence:` НЕ отменяет остальные `actions`** — прерывает только свою `sequence`, родительский список продолжается. **Действие ставить ВНУТРИ ветки `choose`.** Иначе проверка вернёт `false`, а действие всё равно выполнится. Ошибка молчаливая |
| 16 | 🔴 **Трассировки автоматизаций — ТОЛЬКО WebSocket** (`trace/list` + `trace/get`; `item_id` = ВНУТРЕННИЙ ID, не `entity_id`; REST → 404). **Читать трассировку ДО перебора гипотез** — в этой сессии три итерации подбора задержек ушли впустую, потому что трассировка не читалась |
| 17 | 🔴 **Не строить `wait_for_trigger` с взаимоисключающими условиями** — `from: on, to: off` + требование `presence == on` невыполнимо, свет не включится никогда. Проверять выполнимость цепочки до заливки в прод |
| 18 | **Фиксированный `delay` ненадёжен для «дождаться, пока датчик отпустит»** — момент проверки плавает (замерено ~1.1 с накладных на диспетчеризацию). Окно отпускания радара тоже плавает (3.19–3.59 с) |
| 19 | ⚠️ **`condition: device / is_occupied` может не отражать свежий `state`** — для проверок по только что изменившемуся состоянию использовать `condition: state` |
| 20 | Проверенная тактика диалога: Alex не принимает объяснения вместо факта — на «не работает» нужно читать логи/трассировки, а не перебирать настройки |
## Связанные заметки
- [[family/tech/zigbee-t610-z2m-i-zha]] — справочник ZHA: устройства, рецепт переименования, батареи, Modbus slave'ы
- [[family/how-to/home-automation]] — топология, аддоны, Modbus §6
- [[family/how-to/ha-automations]] — логика автоматизаций