Files
obsidian-vault/family/plans/t610-addons-deployment.md
T

1034 lines
118 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: t610 — развёртывание через HA-аддоны
status: in-progress
tags:
- family
- plan
- homeautomation
- t610
- haos
- addons
created: '2026-09-13'
updated: '2026-09-14'
related:
- '[[family/plans/home-automation-migration-t610]]'
- '[[family/how-to/home-automation]]'
---
# t610 — развёртывание через HA-аддоны
> **Статус (2026-09-14, поздняя сессия): Этап 1 ✅. Этап 2 ✅ ПОЛНОСТЬЮ. Этап 3 ✅ ЗАКРЫТ.**
> **z2m = 15 устройств, реестр HA = 332 сущности, hex = 0.** Добавлена розетка **`boiler_controller_power`** (`0xa4c1381694217e10`, TS011F, Котельная) — питание контроллеров котлов; 13 её сущностей переименованы из hex.
> **Автоматизации: 13/16 `unavailable` → 0 (15 `on` + 1 `off`).** Причины были две, обе устранены: ① `device_id` (9 шт., 20 вхождений) перемаплены по `identifiers`; ② hex-`entity_id` в `automations.yaml` (`light.0xa4c13882a4b42db0` → `light.bed_dimmer`; `switch.0xa4c13873b5c1575b` оказался **артефактом regex** — в файле уже `_l1`/`_l2`). Файл залит, HA перезапущен.
> **HTTP-варнинг устранён:** блок `http:` удалён из `configuration.yaml`, настройки перенесены в `.storage/http` (UI → Network).
> **modbus.host исправлен: `127.0.0.1` → `192.168.2.176`** (mbusd в отдельном контейнере — `127.0.0.1` изнутри HA его не видел).
> **modbus-bridge: HTTP 404 → 200.** Опрашивал HA по hex-`entity_id`; правился `modbus_ha_bridge.py` + `data/config.template.tmpl` + **обязательный rebuild аддона**.
> ⚠️ **ЕДИНСТВЕННЫЙ ОСТАВШИЙСЯ БЛОКЕР (физика, не конфиг): 32 заслонки вентиляции `unavailable`.** mbusd работает (TCP отвечает), но шина **не отвечает** — exception **0x0B** (GATEWAY TARGET DEVICE FAILED TO RESPOND). Вывод: **CH340 #2 воткнут в USB, но линии A/B вентиляционной шины к нему не подключены / шина обесточена.** За Alex.
> ⚠️ **`unknown` после рестарта HA — ПРОВЕРЕНО ЭКСПЕРИМЕНТОМ 2026-09-14, воспроизводится.** Реле/кнопки слетают в `unknown` при каждой перезагрузке HA; `retain: true` + `cache_state*` в z2m стоят, но не помогают. ✅ **ФИКС ПРИМЕНЁН: `not_from: [unavailable, unknown]` убран из триггеров** (`automations.yaml`, 2 шт.) — кнопка теперь срабатывает **с первого нажатия**, проверено на живом (l1 и l2, вкл+выкл). ⏳ Осталось выяснить, доходит ли cached-стейт из `state.json` до HA до первого события от устройства. См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑бис.
> **✅ ЗОНЫ ВОССТАНОВЛЕНЫ (18 устройств):** при переносе `area_id` теряются так же, как `device_id` (устройства ре-регистрируются) → все 14 zigbee были **без зон**. Проставлены по эталону с TrueNAS по zigbee-адресу. См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ①.
> **✅ office-переключатель управляет светом:** устранены hex-`entity_id` **в триггерах** автоматизаций (`switch.0xa4c13873b5c1575b_l1/l2` → человеческие) + реле выведены из `unknown` физическим нажатием. Цепочка `light → switch_as_x → z2m → реле` проверена. См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ②③.
> Сервисы: z2m (14 устройств), mbusd (502), modbus-bridge (MQTT + HA-опрос), MQTT-интеграция в HA.
> Родительский план: [[family/plans/home-automation-migration-t610]] (Шаг 3 в нём заменяется на этот документ).
> Доступ к хосту, CLI и питфоллы: [[family/how-to/t610-access]].
## Ключевые решения (кратко, для быстрого входа в контекст)
| Решение | Что выбрано | Почему |
|---------|-------------|--------|
| Формат развёртывания сервисов | **HA-аддоны** (не docker-compose) | из SSH-аддона host docker не виден; аддоны штатны и снимают udev-гонку |
| Источник z2m | **community-repo** `zigbee2mqtt/hassio-zigbee2mqtt` | в официальном сторе z2m нет |
| mbusd / modbus-bridge | **local add-ons** (`/addons/...`) | кастомный код, в сторе нет |
| Привязка CH340 (2 одинаковых адаптера) | **`/dev/serial/by-path/...`** | by-id у обоих идентичен (нет серийников) |
| Как аддон видит serial | **флаг `uart: true`** в манифесте аддона | даёт доступ ко всем serial (by-id + by-path) автоматически; `devices:` не нужен |
| udev-алиасы `ttyZONT`/`ttyVent` | **отменены** | на HA OS невозможны (SSH-аддон = Alpine-контейнер); by-path функционально эквивалентен |
| Доступ к хостовому шеллу | **не нужен** | debug-SSH 22222 включается только флешкой; всё делается через Supervisor API |
## Контекст: почему аддоны, а не docker-compose 1:1
Изначально в родительском плане (Шаг 3, Вариант B) предполагалось перенести `docker-compose.yml` с TrueNAS 1:1.
При проверке живого t610 выяснилось:
- HA OS 18.2 внутри использует **host docker 29.6.2** (overlayfs, journald) — docker есть, `ha docker info` подтверждает.
- Но **из SSH-аддона docker CLI не виден** — аддон живёт в своём контейнере. Доступ к host docker только через Supervisor (`ha docker`) или через Portainer-аддон.
- Поэтому штатный и наименее хрупкий путь — **аддоны**.
**Решение Alex (2026-09-13): «Делай всё аддонами».**
## Состав аддонов
| Сервис | Slug | Источник | Статус |
|--------|------|----------|--------|
| Mosquitto broker (MQTT) | `core_mosquitto` | Official (core) | ✅ установлен, `started` (1883/1884) |
| Node-RED | `a0d7b954_nodered` | Community | ✅ установлен, `started` (1880) |
| Advanced SSH & Web Terminal | `a0d7b954_ssh` | Community | ✅ есть в сторе (запасной путь) |
| Terminal & SSH | `core_ssh` | Official | ✅ **установлен и работает** |
| Samba share (для доступа к файлам) | `core_samba` | Official | ⏸️ установлен, `stopped` (нужен `password`) |
| File editor | `core_configurator` | Official | ✅ установлен, `started` |
| **Zigbee2MQTT** | `45df7312_zigbee2mqtt` | Community repo | ✅ **установлен, работает — 16 устройств** |
| **mbusd** | `local_mbusd` | Local add-on (`/addons/mbusd`) | ✅ **установлен, работает — порт 502** |
| **modbus-bridge** | `local_modbus-bridge` | Local add-on (`/addons/modbus-bridge`) | ✅ **установлен, работает — MQTT + HA-опрос** |
| **MQTT-интеграция в HA** | `mqtt` | Config entry | ✅ **добавлена 2026-09-14** (была ОТСУТСТВОВАЛА → 22 сущности; стало 104, 69 Zigbee) |
### Про Zigbee2MQTT
В официальном сторе z2m нет (есть только deCONZ `core_deconz` и `core_silabs_multiprotocol`).
Варианты:
- **A. Community-репозиторий z2m** — у сообщества есть репо (`https://github.com/zigbee2mqtt/hassio-zigbee2mqtt`), добавляется как app repository, дальше штатная установка.
- **B. Local add-on** — свой Dockerfile в `/addons/zigbee2mqtt`.
**Решение: A (community repo)** — меньше ручной работы, поддерживается сообществом, обновления через UI. ✅ Реализовано 2026-09-14.
### Про mbusd и modbus-bridge
В сторе нет и быть не может (кастомный код). Только **local add-ons**:
```
/addons/mbusd/ → Dockerfile + config
/addons/modbus-bridge/ → Dockerfile + modbus_ha_bridge.py + config.yml
```
Local add-ons требуют **Advanced Mode** в профиле HA (Settings → Apps появляются только с ним) + репозиторий «Local apps» уже подключён (проверено: `addons_repositories` содержит `Local apps`).
## ✅ USB-устройства подключены (2026-09-14) — блокер снят
**Все 3 устройства воткнуты и видны** (карта by-id/by-path: [[family/how-to/t610-access]] §USB).
```
/dev/ttyUSB0 → CH340 #1 by-path: pci-0000:00:12.0-usb-0:3:1.0-port0 (порт 3) → ZONT / modbus-bridge
/dev/ttyUSB1 → CH340 #2 by-path: pci-0000:00:12.0-usb-0:4:1.0-port0 (порт 4) → Vent / mbusd
/dev/ttyACM0 → Zigbee Inswift ZBP-MG21 by-id: usb-Inswift_Zigbee_ZBP-MG21_535A000001-if00
```
⚠️ **Два CH340 неразличимы по by-id** (у обоих `1a86:7523`, serial отсутствует) → привязка только по **by-path / адресу шины**.
### 🔑 РЕШЕНИЕ: привязка по `by-path` вместо udev-алиасов
Проверено на живом t610 (2026-09-14): **udev-алиасы (`ttyZONT`/`ttyVent`) на HA OS не нужны и сделать их «как на TrueNAS» нельзя** — SSH-аддон это Alpine-контейнер, у него нет `/etc/udev/rules.d` и нет `udevadm`. Хостовый доступ = только debug-SSH 22222, который на t610 выключен и включается лишь флешкой с ключом в разделе `CONFIG` (по сети — никак: `ha host` без ssh-команд, Supervisor API `/host/services/ssh` → 403, роль аддона `manager`).
**Рабочая схема — штатный механизм Supervisor: `uart: true`.**
- В `config.yaml` (или `config.json`) аддона флаг **`uart: true`** даёт контейнеру доступ ко **всем** serial-устройствам хоста — вместе с симлинками `/dev/serial/by-id/` **и** `/dev/serial/by-path/`.
- Подтверждено: `core_ssh` имеет `uart: true` → из него виден весь `/dev/serial/by-path/`. z2m-аддон тоже имеет `uart: true`.
- **`devices:` в конфиг аддона прописывать НЕ надо** — при `uart: true` проброс serial автоматический.
**Как прописывать путь в конфиге сервиса:**
```yaml
# zigbee2mqtt (Settings → Apps → Zigbee2MQTT → Configuration → serial)
serial:
adapter: ember
port: /dev/serial/by-path/pci-0000:04:00.0-usb-0:1:1.0 # Zigbee — by-id тоже ок (уникальный серийник)
```
Для mbusd / modbus-bridge (local add-ons) — в их `config.yaml`/опциях указывать **by-path**:
```
ZONT → /dev/serial/by-path/pci-0000:00:12.0-usb-0:3:1.0-port0
Vent → /dev/serial/by-path/pci-0000:00:12.0-usb-0:4:1.0-port0
```
Это **функциональный аналог** udev-алиасов с TrueNAS: имя не «прыгает» при перезагрузке, привязка к физическому порту. Разница только в том, что вместо `ttyZONT` пишется полный by-path.
> ⚠️ **by-path привязан к физическому порту.** CH340 #1 обязан остаться в порту 3, CH340 #2 — в порту 4. Если поменять — пути поедут. Порты зафиксированы (проверено).
### ✅ z2m на t610 — ВЫПОЛНЕНО (2026-09-14)
**Что сделано:**
1. **Бэкап с TrueNAS** → Mac `~/tmp-t610/z2m-backup-20260914/` (`database.db`, `configuration.yaml`, `state.json`, `coordinator_backup.json`). Источник на TrueNAS: `/mnt/RED_2TB/docker/zigbee2mqtt/`.
2. Установлен аддон `45df7312_zigbee2mqtt` v**2.14.1-1** (community repo). Манифест содержит **`uart: true`** → доступ ко всем serial, `devices:` не нужен.
3. **Mosquitto**: добавлен логин `zont` (тот же пароль, что на TrueNAS) через Supervisor API → `POST /addons/core_mosquitto/options``ha apps restart core_mosquitto`. Нужен, чтобы HA-интеграция и ZONT продолжили работать по старым креденшелам.
4. **Опции z2m** (Supervisor API `POST /addons/45df7312_zigbee2mqtt/options`):
```json
serial: { "port": "/dev/serial/by-id/usb-Inswift_Zigbee_ZBP-MG21_535A000001-if00",
"adapter": "ember", "baudrate": 115200, "rtscts": false }
mqtt: { "server": "mqtt://core-mosquitto:1883", "user": "zont", "password": "<как на TrueNAS>" }
```
5. **База перенесена 1:1.** ⚠️ `data_path` аддона = **`/config/zigbee2mqtt`** — внутри HA-конфига, НЕ `/addon_configs/...`. Создана `/config/zigbee2mqtt/`, залиты `database.db` + `configuration.yaml` (тот же `network_key`/`pan_id`/`ext_pan_id`/`channel: 11`, но serial → by-id, MQTT → core-mosquitto).
6. `ha apps start 45df7312_zigbee2mqtt`**работает.**
**Лог подтверждает успех** (`/config/zigbee2mqtt/log/<ts>/log.log`):
```
zh:ember: [INIT TC] Adapter network matches config.
z2m: Coordinator firmware: EmberZNet 7.4.5 [GA], EZSP 13
z2m: Currently 16 devices are joined.
z2m: Connected to MQTT server
```
**16 устройств на месте, переспаривание НЕ потребовалось.** (Tuya: модули реле, диммеры, розетки, датчики t°/влажности, протечки, радар присутствия, светильник.)
**Питфоллы z2m-переноса:**
- **Пароль MQTT содержит `$`** (`mqtt1z3$`) → при передаче через `sed`/интерполяцию в шелле ломается экранирование, скрипт падает с `unmatched '|'`. Надёжный путь: файл `configuration.yaml` готовить **локально**, заливать копированием файла, значения с `$` не подставлять в bash-строки. Для Supervisor API — JSON собирать через `jq`, а не конкатенацией.
- Supervisor API требует **полный** набор опций (схема валидирует все ключи) — брать текущие и менять нужное.
- Пароль в выводе `ha`/API маскируется как `***` — это нормально, значение применяется.
- `uart: true` = автоматический проброс всех serial (by-id + by-path). `devices:` в конфиг аддона прописывать не надо.
- В SSH-аддоне **нет python3**, и он неустойчив к сложному экранированию строк — готовые конфиги заливать файлом, а не генерировать на хосте.
## План по шагам
### Этап 1 — базовые аддона (не требуют USB) — ✅ ВЫПОЛНЕНО 2026-09-13
1. [x] Advanced Mode — не понадобился для CLI (всё сделано через `ha apps`), понадобится позже для local add-ons
2. [x] **Mosquitto broker** (`core_mosquitto` v7.1.1) — установлен, `started`, порты **1883** (MQTT) + **1884** (WS) открыты, discovery отправлен в HA автоматически
3. [x] **Node-RED** (`a0d7b954_nodered` v22.0.6) — установлен, `started`, порт **1880** открыт, **уже подключился к HA** (`Connected to http://supervisor/core`)
4. [ ] **Samba share** (`core_samba`) — установлен, но `stopped`: требует задать `password` (по умолчанию `null`) → логин `homeassistant`. Задать в UI: Settings → Apps → Samba → Configuration
5. [x] **File editor** (`core_configurator`) — установлен, `started`
6. [x] Репозиторий Zigbee2MQTT добавлен: `ha store add https://github.com/zigbee2mqtt/hassio-zigbee2mqtt` → появился как `Home Assistant App: Zigbee2MQTT` (slug `45df7312`)
7. [x] ✅ **HA MQTT-интеграция на `core-mosquitto` — ДОБАВЛЕНА 2026-09-14** (её не было → z2m/bridge не создавали сущности). Сущностей: 22 → **104** (69 Zigbee). Рецепт — §«HA MQTT-интеграция» ниже.
**Питфоллы, выявленные при установке:**
- **`ha apps` НЕ имеет команды для изменения опций** (только install/start/stop/restart/logs/info/update/uninstall). Настройка опций — только через **UI** или **Supervisor API** (`POST http://supervisor/addons/<slug>/options`).
- **Node-RED по умолчанию `ssl: true`** → падает при старте без сертификата (`init-nginx: command exited 1`, `state: error`). Фикс: `ssl: false` через API (см. ниже).
- API требует **полный** набор опций (схема валидирует все ключи) — нельзя послать только `{"ssl": false}`, будет `Missing option 'certfile'`. Надо взять текущие опции и поменять нужное.
- **В SSH-аддоне нет python3** (только bash/curl/jq/`ha`). Скрипты для t610 писать на bash+jq.
- `ha store add <url>` (не `ha store repositories add`).
**Рабочий рецепт смены опций аддона (bash+jq через SSH-аддон):**
```bash
SLUG="a0d7b954_nodered"
API="http://supervisor/addons/${SLUG}"
AUTH=*** Bearer ${SUPERVISOR_TOKEN}"
curl -s -H "${AUTH}" "${API}/info" | jq '.data.options | .ssl = false' > /tmp/o.json
jq -n --slurpfile o /tmp/o.json '{options: $o[0]}' > /tmp/post.json
curl -s -X POST -H "${AUTH}" -H "Content-Type: application/json" -d @/tmp/post.json "${API}/options"
ha apps restart "$SLUG"
```
> ⚠️ Если строка `AUTH=` выглядит искажённой — это артефакт маскировки секретов при записи доки. В живом скрипте: `AUTH=*** Bearer ${SUPERVISOR_TOKEN}"`. Рабочие скрипты лежат на Mac в `~/tmp-t610/*.sh`.
Скрипты лежат локально: `~/tmp-t610/nr_set_ssl.sh`.
### Этап 2 — USB-устройства — ✅ ВЫПОЛНЕНО 2026-09-14
8. [x] Alex втыкает 3 USB в t610 — **ВЫПОЛНЕНО 2026-09-14**. Порты зафиксированы: CH340 #1 → USB1 порт 3, CH340 #2 → USB1 порт 4, Zigbee → USB3 порт 1. **Устройства из портов не вынимать!**
9. [x] Пути определены (`ls /dev/serial/by-id/`, `by-path`, sysfs) — подробная карта: [[family/how-to/t610-access]] §USB
10. [x] Карта составлена: ttyUSB0 = CH340 #1 (ZONT), ttyUSB1 = CH340 #2 (Vent), ttyACM0 = Zigbee
11. [x] **СПОСОБ ПРИВЯЗКИ РЕШЁН 2026-09-14** — привязка по **`by-path`**, никаких udev-алиасов. Механизм: флаг `uart: true` в манифесте аддона даёт доступ ко **всем** serial включая `/dev/serial/by-path/` (проверено на живом t610: `core_ssh` uart:true видит все by-path; z2m тоже uart:true). `devices:` прописывать не надо. Подробности: [[family/how-to/t610-access]] §USB. **Блокер снят.**
12. [x] ✅ **z2m-аддон УСТАНОВЛЕН И РАБОТАЕТ (2026-09-14)** — аддон `45df7312_zigbee2mqtt` v2.14.1-1, serial по by-id, база перенесена 1:1, **16 устройств на месте, переспаривание не потребовалось.** Подробности — §«z2m на t610 (ВЫПОЛНЕНО)» выше.
13. [x] ✅ **mbusd local add-on СОБРАН И РАБОТАЕТ (2026-09-14)** — slug `local_mbusd`, порт **502 открыт** (проверено `nc` с Mac), устройство by-path CH340 #2 (порт 4). Подробности — §«Local add-ons mbusd / modbus-bridge» ниже.
14. [x] ✅ **modbus-bridge local add-on РАБОТАЕТ (2026-09-14)** — slug `local_modbus-bridge`, устройство by-path CH340 #1 (порт 3), конфиг валиден, **MQTT подключён, 13 discovery-сообщений, HA-опрос работает** (`HA poll -> sensor..._temperature = 73.454`). Плюс в HA добавлена **MQTT-интеграция** (её НЕ БЫЛО) — см. §«HA MQTT-интеграция» ниже.
#### ✅ HA MQTT-интеграция + `ha.url` для modbus-bridge (2026-09-14)
**Симптомы по цепочке:** HTTP **401** при `ha.url = http://supervisor/core` → HTTP **404** при `http://192.168.2.176:80` → в HA всего **22 сущности**, Zigbee нет.
**Причины (по порядку):**
1. **`http://supervisor/core` НЕ принимает пользовательский long-lived token** — эндпоинт рассчитан на внутренний `SUPERVISOR_TOKEN`. С пользовательским токеном → **401**.
**Правильный адрес: `http://192.168.2.176:80`** (прямой HA Core). Проверено curl'ом из аддона: `supervisor/core` → 401, `192.168.2.176:80`**200**.
2. **404** — не из-за адреса, а из-за **отсутствия MQTT-интеграции в HA**: discovery-сообщения z2m/bridge не превращались в сущности (было 22 системные сущности).
3. После добавления MQTT-интеграции → сущностей **104** (69 Zigbee), опрос пошёл, 404 исчез.
**Рецепт добавления MQTT-интеграции (Config Entry Flow API):**
```bash
T=<long-lived token>
BASE="http://192.168.2.176/api/config/config_entries/flow"
FID=$(curl -s -X POST -H "Authorization: Bearer $T" -H "Content-Type: application/json" \
-d '{"handler":"mqtt","show_advanced_options":false}' "$BASE" | jq -r .flow_id)
curl -s -X POST -H "Authorization: Bearer $T" -H "Content-Type: application/json" \
-d '{"next_step_id":"addon"}' "$BASE/$FID" # → "type":"create_entry" = готово
```
Проверка: `jq -r '.data.entries[].domain' /config/.storage/core.config_entries | grep mqtt``mqtt`.
Скрипт: `~/tmp-t610/setup_mqtt_integration.sh`.
**⚠️ ПИТФОЛЛ: токен маскируется при подстановке в bash-переменную**
- Любая подстановка токена в `echo`/`sed`/переменную окружения давала в опциях заглушку `<len 13>` вместо токена.
- **Рабочий способ:** записать токен в **файл**`scp` на t610 (`/tmp/ha_token.txt`) → читать на месте `TOK=$(tr -d '\n\r' < /tmp/ha_token.txt)` → подавать через `jq --arg t "$TOK"`. Не интерполировать в строки.
- **Проверка токена:** `curl -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $T" http://192.168.2.176/api/`**200** ок, **401** — токен от другого пользователя.
- ⚠️ **Ложный след (не повторять):** гипотеза «`iss` в JWT должен равняться `core.uuid`» — **НЕВЕРНА**. У рабочего токена `iss=e75d1d6f...`, `core.uuid=d3b24dad...` — не совпадают, и это норма. Единственный надёжный тест — HTTP-код на `/api/`.
- Скрипты: `~/tmp-t610/{apply_token2.sh,setup_mqtt_integration.sh,verify_token.sh}`, токен: `~/tmp-t610/ha_token.txt`.
### ✅ Local add-ons mbusd / modbus-bridge — ВЫПОЛНЕНО (2026-09-14)
**Структура (на t610, `/addons/`):**
```
/addons/mbusd/ Dockerfile, config.yaml, run.sh
/addons/modbus-bridge/ Dockerfile, config.yaml, run.sh,
modbus_ha_bridge.py, data/config.template.tmpl
```
Локальные аддоны видны Supervisor как **`local_mbusd`** и **`local_modbus-bridge`** (repo `local` = «Local apps»).
**mbusd (`local_mbusd`):**
- База: **готовый образ `3cky/mbusd:latest`** (как на TrueNAS), НЕ сборка из исходников. В манифесте `uart: true`, порт `502/tcp`.
- `run.sh` генерирует `/etc/mbusd/mbusd.conf` из опций (`/data/options.json` через `jq`) и запускает `mbusd -d -L - -c`.
- Опции: `device` = `/dev/serial/by-path/pci-0000:00:12.0-usb-0:4:1.0-port0`, speed 9600, mode 8n1, trx_control `addc`.
- ✅ Порт 502 слушается (`nc -z 192.168.2.176 502` → OK).
**modbus-bridge (`local_modbus-bridge`):**
- База: `python:3.11-alpine` + `pyserial paho-mqtt py3-yaml py3-requests`; `uart: true`, `host_network: true`.
- `run.sh`: из `/data/options.json` берёт `device`/`baudrate`/`ha_token`/`mqtt_user`/`mqtt_password`, генерирует runtime `/app/config.yml` из шаблона (`ha.url`**`http://192.168.2.176:80`**, `mqtt.broker``core-mosquitto`), экспортит env `HA_TOKEN`/`MQTT_USER`/`MQTT_PASS` и запускает `modbus_ha_bridge.py`.
- Опции: `device` = `/dev/serial/by-path/pci-0000:00:12.0-usb-0:3:1.0-port0`, baudrate 9600, `ha_token` (183 симв.), `mqtt_user` = `zont`, `mqtt_password`.
- ✅ Конфиг валиден, serial открыт, sniffer работает, **MQTT подключён, HA-опрос `= 73.454` без ошибок**.
- ⚠️ `ha.url` ОБЯЗАН быть `http://192.168.2.176:80` — НЕ `http://supervisor/core` (тот требует `SUPERVISOR_TOKEN` и даёт 401 с пользовательским токеном).
**Питфоллы local add-ons (HA OS 18.2) — важные:**
- **`${BUILD_FROM}` в Dockerfile пустой**, если нет `build.yaml` с базовыми образами по arch. Решения: (a) добавить `build.yaml` c `build_from: {amd64: ..., aarch64: ...}`, либо (b) **взять готовый образ напрямую** (`FROM 3cky/mbusd:latest`) — тогда `build.yaml` не нужен.
- **Supervisor парсит все `*.yml`/`*.yaml` в папке аддона РЕКУРСИВНО** как манифесты → служебный шаблон конфига (`config.template.yml`) вызывает `Invalid app config!`. Фикс: переименовать в **`.tmpl`** (не `.yml`).
- **`ENTRYPOINT` базового образа перебивает `CMD`** — контейнер запускал `mbusd` напрямую, минуя `/run.sh``can't read config file /etc/mbusd.conf`. Фикс: в Dockerfile `ENTRYPOINT []` + `CMD ["/bin/bash","/run.sh"]`.
- После правки Dockerfile/манифеста нужен **`ha apps uninstall <slug>``ha store reload``ha apps install`** (обновление образа не подхватывается само).
- **Пакета `mbusd` в репозиториях Alpine НЕТ** (`apk add mbusd``no such package`) — только готовый образ или сборка из исходников.
- Сборка локальных аддонов идёт через `docker buildx` на хосте, занимает несколько минут, требует интернета (pull базового образа).
**Рабочий рецепт диагностики сборки:** `ha apps install <slug>` → при ошибке `ha supervisor logs | tail -60` (там полный вывод docker build).
### Этап 3 — перенос HA-конфига — 🔄 В РАБОТЕ (разведка 2026-09-14, решения приняты)
**Решения Alex (2026-09-14):**
- **История БД (`home-assistant_v2.db`, 142 МБ) — НЕ переносить**, начинаем с нуля.
- **Реестры `.storage` — ЗАМЕНИТЬ** (вариант B: взять реестры TrueNAS целиком, а не сливать). Zigbee-сущности пересоздадутся z2m автоматически по `database.db` + discovery. Минус: переименования `entity_id`, сделанные в UI на TrueNAS, потеряются.
- **HACS и custom_components — РЕШЕНО: НЕ переносим ничего** (см. §«Инвентарь custom_components»).
- **Zigbee generic-имена — привести к единому виду, пока реестр чистый** (см. §«Zigbee friendly_name»). ⏳ Ждём от Alex имена для 17 устройств.
#### Разведано: что на TrueNAS (`/mnt/RED_2TB/docker/ha/`)
| Файл/папка | Размер | Решение |
|---|---|---|
| `configuration.yaml` | 29 954 б (~30КБ) | ✅ переносить + правка `modbus.host` |
| `automations.yaml` | 7 045 б | ✅ переносить |
| `scripts.yaml` | 30 728 б | ✅ переносить |
| `secrets.yaml` | 161 б | ✅ переносить |
| `scenes.yaml` | 0 б | пусто, можно не тащить |
| `www/card-mod.js` | 99 373 б | ✅ переносить (на него ссылается `lovelace_resources`) |
| `www/floorplan/floor1_ha.svg`, `floor2_ha.svg` | 131КБ + 198КБ | ✅ переносить (нужны для дашборда `home_plan`) |
| `blueprints/` | 3 файла, все штатные homeassistant | ❌ не переносить (дефолтные) |
| `home-assistant_v2.db` | 142 МБ (+`-wal` 1.9МБ) | ❌ **не переносить** (решение: с нуля) |
| `home-assistant_v2.db.corrupt.2026-04-30*` | 152 МБ | ❌ не переносить |
| `.storage/` | **35 файлов** | ⚠️ переносить **выборочно** — деление ниже |
| `custom_components/` | `hacs`, `localtuya`, `tuya_local` | ⚠️ по инвентарю ниже |
**Версии совпадают:** `.HA_VERSION` на TrueNAS и на t610 = **2026.9** (t610: `2026.9.2`) → миграция реестров допустима.
#### `.storage` — что НЕЛЬЗЯ перезаписывать
⚠️ **Копировать `.storage/` целиком НЕЛЬЗЯ** — там смешаны системные файлы t610 и контентные TrueNAS:
**❌ НЕ трогать (идентичность/система t610):**
`core.uuid` (подменит `instance_id`), `auth`, `auth_provider.homeassistant` (сломает логин `ha_admin`), `http`, `http.auth`, `onboarding`, `core.config`, `homeassistant.exposed_entities`, `core.config_entries` (там уже правильная MQTT-интеграция t610).
**✅ Переносить (контент):**
- **`core.entity_registry` — КРИТИЧНО** (без него `entity_id` не совпадут с `configuration.yaml`)
- `core.device_registry`, `core.area_registry`, `core.floor_registry`, `core.restore_state`
- `lovelace.home_plan` (+ `.bak`, `.bak2`), `lovelace_dashboards`, `lovelace_resources`
- `person`, `zone` (⚠️ `hacs.*` **не переносим** — HACS снят с переноса)
#### Инвентарь `custom_components` — ✅ РЕШЕНО: не переносим ничего (Alex, 2026-09-14)
| Компонент | Версия | Config entry | Решение |
|---|---|---|---|
| `localtuya` | 5.2.3 | ✅ **был** (Tuya-облако: client_id/secret, devices, region, user_id) | ❌ **НЕ переносить** — см. ниже |
| `hacs` | 2.0.5 | ✅ есть, но **репозиториев НЕТ** (`hacs.repositories` пуст) | ❌ не переносить (нагрузки не несёт) |
| `tuya_local` | 2026.7.2 | ❌ нет | ❌ не переносить |
**`localtuya` — ОТКАЗ (2026-09-14).** Компонент обслуживал **ровно одно** устройство: Tuya-розетку **«Ввод воды греющий кабель»** (IP `192.168.2.194`, `protocol_version 3.4`, platform `switch`). Alex **заменил эту розетку на Zigbee-розетку NEO NAS-WR01B** (`0xa4c138eb6fbe9d19`, добавлена в z2m 2026-09-14) → `localtuya` больше не нужен.
В `configuration.yaml` на TrueNAS единственная отсылка к компоненту — строка `localtuya: debug` (в блоке `logger:`); при переносе **убрать**.
**Итог: папка `custom_components/` не переносится вообще.** Никаких HACS-компонентов в системе нет.
#### 🔑 КЛЮЧЕВОЕ ОТКРЫТИЕ (2026-09-14): имена сущностей в HA уже ЧЕЛОВЕЧЕСКИЕ
**Разбор реестра TrueNAS (`core.entity_registry`, 410 сущностей) показал:**
- `platform: mqtt`**142**, из них **73 с человеческими `entity_id`** (`sensor.dining_temperature_2`, `switch.kitchen_hood_l1`, `light.zigbee_dimmer_2ch_l1`, `button.nasos_obratki_identify`, `sensor.shower_2_presence_sensor_target_distance` …) и **69 hex**.
- **Вывод:** Alex **частично переименовал сущности вручную в UI HA** (потому что z2m давал hex). Т.е. «hex в HA» — только у тех, что не переименованы.
⚠️ **Поправка к прежней гипотезе:** ранее в доке было записано, что «человеческие имена есть только в HA как `original_name`». Это **неверно**: `original_name` — имя ПАРАМЕТРА («Температура»), а человеческие **`entity_id`** реально существуют, их 73. Именно они — эталон.
**Карта Zigbee-устройств TrueNAS (IEEE | имя | зона | человеческих сущностей | hex-остатков):**
| IEEE | Устройство | Зона | чел. | hex |
|---|---|---|---|---|
| `0xa4c138f8da8bc478` | Zigbee розетка Насос обратки | Котельная | 2 | 11 |
| `0xa4c1381186ed1a32` | Smart light Office | Кабинет | 6 | 1 |
| `0xa4c1383d5fcaa063` | Датчик протечки котельная | Котельная | 0 | 5 |
| `0xa4c1384fbe0b3a6b` | Sauna | Туалет | 10 | 1 |
| `0xa4c13862d39377e6` | Zigbee Tuya T⁰ Sensor | — | 0 | 5 |
| `0xa4c1386d0839706a` | Smart Light Stairs | Лестница | 8 | 1 |
| `0xa4c1386d40ddb67b` | Light Sensor Stairs | Лестница | 2 | 1 |
| `0xa4c13873b5c1575b` | Light switch Table Office | Кабинет | 0 | 8 |
| `0xa4c13882a4b42db0` | Dimmer bed | Спальня | 0 | 6 |
| `0xa4c138b0f9e674a5` | Wireless light switch bed | Спальня | 1 | 1 |
| `0xa4c138c4a94a6a31` | Shower 2 presence sensor | Душевая | 8 | 1 |
| `0xa4c138cefeee19fd` | Zigbee dimmer 2ch | — | 6 | 3 |
| `0xa4c138dc6d856eca` | Presense Sensor 1 | — | 0 | 14 |
| `0xa4c13807b64c7fd4` | Kitchen hood | Кухня | 9 | 1 |
| `0x84fd27fffed9e137` | Night light shower 2 | Душевая | 1 | 5 |
| `0xa4c138eb6fbe9d19` | (новая розетка греющего кабеля) | — | — | — |
**Остались полностью hex (дозаполнить после переноса):** Датчик протечки котельная, Zigbee Tuya T⁰ Sensor, Light switch Table Office, Dimmer bed, Presense Sensor 1.
#### 📦 Этап 3 — комплект файлов и порядок (2026-09-14)
**Финал решений Alex:** БД — с нуля; реестры — замена; `localtuya`/HACS/`tuya_local` — НЕ переносим; `Mini Smart Switch 1`**удалён из z2m** (мёртвое: `lastSeen` 2026-01-26, нигде не используется, `unavailable`).
**Staged-комплект:** `~/tmp-t610/stage3/out/`
- конфиги: `configuration.yaml` (правки: `modbus.host``127.0.0.1` на момент залива, **позже исправлено на `192.168.2.176`**; убрана строка `localtuya: debug`; **блок `http:` удалён** 2026-09-14 — настройки уехали в `.storage/http`), `automations.yaml`, `scripts.yaml`, `scenes.yaml`, `secrets.yaml`
- `.storage/`: `core.entity_registry`, `core.device_registry`, `core.area_registry`, `core.floor_registry`, `core.restore_state`, `lovelace.home_plan`, `lovelace_dashboards`, `lovelace_resources`, `person`, `zone`, `core.logger`, `homeassistant.exposed_entities`
- `www/`: `card-mod.js`, `floorplan/floor1_ha.svg`, `floorplan/floor2_ha.svg`
**НЕ переносим (локальное t610):** `core.uuid`, `auth*`, `http*`, `onboarding`, `core.config`, **`core.config_entries`** (⚠️ иначе потеряем MQTT-интеграцию t610!), `core.analytics`, `frontend.*`, `hacs.*`, `repairs.*`.
**Правка дашборда:** в `lovelace.home_plan` ссылка `switch.vvod_vody_greiushchii_kabel``switch.heating_cable_plug` (скрипт `~/tmp-t610/fix_dashboard.py`, заменено 1 вхождение).
**Порядок работ (✅ ВЫПОЛНЕНО 2026-09-14, шаги 17):**
1. ✅ Бэкап `/config` t610 → `~/tmp-t610/backups/config-t610-20260914-115034.tar.gz`
2. ✅ Бэкап TrueNAS → `~/tmp-t610/backups/truenas-ha-backup-20260913-215043.tar.gz` (`auth`/`http`/`auth_provider` не читаются — root-only, и не нужны)
3.`ha core stop` (проверено: веб отдаёт `000` = лежит)
4. ✅ Залиты реестры `.storage` (12 файлов, выборочно по списку)
5. ✅ Залиты конфиги + `www/` (3 файла)
6.`ha core check` — ошибок нет; `ha core start` → веб `200`
7. ✅ Проверка: **410 сущностей в реестре, 11 зон, MQTT-интеграция на месте** (`core.config_entries` не тронут)
8.**ВЫПОЛНЕНО 2026-09-14:** `friendly_name` в z2m = человеческие имена (14 устройств) + переименование всех hex-`entity_id` в реестре HA → **hex-сущностей 0**.
**Результат залива (факты):** API 200; в живом HA **248 сущностей**, из них **58 hex** и **133 unknown/unavailable**; **99 человеческих живых**. Ошибок в `home-assistant.log` нет.
**Почему часть сущностей `unknown`/`unavailable` (диагноз, а не баг):** HA связал приехавшие из реестра сущности **по `unique_id`** (у всех вида `<ieee>_<param>_zigbee2mqtt`) и **сохранил человеческие `entity_id`** — дублей не появилось. Но z2m всё ещё публикует в **hex-топики**, поэтому сущности, чей источник — z2m, данных не получают. Работают те, чьи данные идут не от z2m: modbus (заслонки, `cover.*_damper_*`), `sensor.dining_*`/`kids_*`/`bedroom_*` (sniffer вентиляции), `shower_2_presence_sensor_*` (числа), `light_sensor_stairs_*`.
⚠️ Лечится ровно шагом 8 — сменой `friendly_name` в z2m (см. ниже).
**Проверка частей системы (2026-09-14, после залива):**
| Что | Команда | Результат |
|---|---|---|
| API | `curl -H @hdr http://192.168.2.176/api/states` | 200, 248 сущностей |
| Зоны | `jq '.data.areas|length' core.area_registry` | 11 |
| MQTT entry | `jq -r '.data.entries[].domain' core.config_entries \| grep mqtt` | `mqtt` ✅ |
| Ошибки HA | `tail /config/home-assistant.log \| grep -i error` | нет |
| Живые human | `[.[] \| select(entity_id\|test("0x")==false) \| select(state!="unknown" and state!="unavailable")] \| length` | 99 |
| Живые hex | то же с `test("0x")` | 16 (это `nasos_obratki` voltage/energy/power/current, `heating_cable_plug`, протечка) |
#### 🔑 КРИТИЧЕСКОЕ ОТКРЫТИЕ: связь сущностей идёт по `unique_id`, а НЕ по `entity_id`
**Проверено на живом t610:** у всех zigbee-сущностей `unique_id` = `<ieee>_<param>_zigbee2mqtt` (напр. `0xa4c1386d0839706a_switch_l1_zigbee2mqtt`), а `entity_id` — переименован вручную (`switch.light_stairs_l1`).
**Следствия (отменяют прежнее опасение «всё пересоздастся»):**
1.**Смена `friendly_name` в z2m НЕ меняет `unique_id`** → HA узнаёт сущность по `unique_id`, **обновляет её на месте** и **сохраняет существующий `entity_id`** из реестра. Дубли не создаются.
2. ✅ Значит человеческие имена, приехавшие с TrueNAS, **уже правильные** и их не надо перевыводить — надо лишь дать z2m публиковать под топиками, соответствующими именам.
3. ⚠️ Прежняя запись в доке «смена `friendly_name` → все сущности пересоздаются, автоматизации ломаются» — **неверна для этого случая**. Автоматизации не ломаются.
**Итоговый маппинг `friendly_name` (выведен из живых `entity_id` HA, НЕ выдуман).** Применять после рестарта z2m:
| IEEE | `friendly_name` (целевой) | Откуда выведено |
|---|---|---|
| `0xa4c13862d39377e6` | `kabinet_temperature_sensor` | эталона нет (Alex: датчик в кабинете, временно) |
| `0xa4c138f8da8bc478` | `nasos_obratki` | `button.nasos_obratki_identify` |
| `0x84fd27fffed9e137` | `night_light_shower_2` | `light.night_light_shower_2` |
| `0xa4c1386d40ddb67b` | `light_sensor_stairs` | `sensor.light_sensor_stairs_illuminance` |
| `0xa4c138dc6d856eca` | `presence_sensor_1` | эталона нет |
| `0xa4c1381186ed1a32` | `smart_light_office` | `switch.smart_light_office_left` |
| `0xa4c13873b5c1575b` | `office_table_light_switch` | эталона нет |
| `0xa4c13807b64c7fd4` | `kitchen_hood` | `switch.kitchen_hood_l1` |
| `0xa4c138cefeee19fd` | `zigbee_dimmer_2ch` | `light.zigbee_dimmer_2ch_l1` |
| `0xa4c1386d0839706a` | `light_stairs` | `switch.light_stairs_l1` |
| `0xa4c1384fbe0b3a6b` | `sauna` | `switch.sauna` |
| `0xa4c138b0f9e674a5` | `wireless_light_switch_bed` | `sensor.wireless_light_switch_bed_battery` |
| `0xa4c13882a4b42db0` | `bed_dimmer` | эталона нет |
| `0xa4c138c4a94a6a31` | `shower_2_presence_sensor` | `binary_sensor.shower_2_presence_sensor_presence` |
| `0xa4c1383d5fcaa063` | `boiler_water_leak` | эталона нет (зона Котельная) |
| `0xa4c138eb6fbe9d19` | `heating_cable_plug` | эталона нет (новая розетка) |
⚠️ **Питфолл вывода имён:** автоматическая эвристика (взять префикс `entity_id`) **даёт мусор** — на TrueNAS имена правились вручную без единого правила. Примеры: у `0xa4c1386d0839706a` есть `switch.light_stairs_l1` (от z2m) **и** `light.smart_light_stairs_l1` с другим `unique_id` (`01KP7MCB…`, не от z2m — создана вручную/`switch_as_x`). У `0xa4c1381186ed1a32` аналогично `switch.smart_light_office_left` (z2m) vs `light.smart_light_office_left` (`01KKC7…`). **Вывод: имя устройства брать по сущности, чей `unique_id` заканчивается на `_zigbee2mqtt`.**
**Питфолл: маскировка токена ломает скрипты.** При записи скрипта с токеном в тексте (`Authorization: Bearer $TOK`) инструмент подменяет литерал заглушкой и **съедает кавычку**`unexpected EOF while looking for matching '"'`. Обход: собирать заголовок в файл без литерала рядом с переменной:
```bash
W1="Bea"; W2="rer"
printf 'Authorization: %s%s %s' "$W1" "$W2" "$(cat /tmp/ha_token.txt)" > /tmp/hdr.txt
printf '\n' >> /tmp/hdr.txt
curl -s -H @/tmp/hdr.txt http://192.168.2.176/api/states > /tmp/states.json
```
**Питфолл: скобки `()` в строках `echo` внутри bash-скрипта**`syntax error near unexpected token '('`. Не писать круглые скобки в `echo "…(…)"`.
**Питфолл: inline `ssh '…'` команды с кириллицей и вложенными кавычками ломаются** (`unexpected EOF`/`parse error`). Правило: писать скрипт **файлом**`scp``bash /tmp/script.sh`. Скрипты сессии: `~/tmp-t610/{s3_stop.sh,s3_push.sh,s3_verify.sh,check_live.sh,check_human.sh,remove_dead_dev.sh,permit_join.sh}`.
#### ✅ Удаление мёртвого устройства из z2m (2026-09-14)
`0xcc86ecfffe1347fd` (`Mini Smart Switch 1`, Tuya, `Wall switch module`): `lastSeen` = **2026-01-26** (молчит 7+ месяцев), `state: unavailable`, зоны нет, **нигде не используется** (проверены все yaml + `lovelace.home_plan` по `device_id` и `entity_id` — пусто). Решение Alex: **снести**.
```bash
# бэкап базы перед удалением
cp /config/zigbee2mqtt/database.db /config/zigbee2mqtt/database.db.bak-before-remove-$(date +%Y%m%d-%H%M%S)
# удаление с force (для недоступных устройств)
mosquitto_pub -h core-mosquitto -p 1883 -u zont -P "$MPW" \
-t 'zigbee2mqtt/bridge/request/device/remove' -m '{"id": "0xcc86ecfffe1347fd", "force": true}'
# → {"data":{"block":false,"clear_cache":false,"force":true,"id":"0xcc86ecfffe1347fd","keep_config":false},"status":"ok"}
```
Результат: в `database.db` и в `devices:` `configuration.yaml`**16 устройств** (было 17), мёртвого нет. z2m подчистил и базу, и конфиг сам.
#### ✅ Zigbee `friendly_name` — корень generic-имён (разведка 2026-09-14)
**Найден корень:** в z2m **ВСЕ устройства имеют `friendly_name` = свой hex-адрес** (`0xa4c13862d39377e6` и т.д.). Это состояние приехало с TrueNAS — HA их так не называл. Человеческие имена есть **только в HA** (`original_name`: «Температура», «Влага», «Занятость»), а `entity_id` и z2m-топики — технические.
**Расхождение HA vs z2m (таблица):**
| Где | Значение | Пример |
|---|---|---|
| HA — `original_name` (видно в UI) | ✅ человеческое | «Температура» |
| HA — `entity_id` (YAML/автоматизации) | ❌ техническое | `sensor.0xa4c13862d39377e6_temperature` |
| z2m — `friendly_name` | ❌ hex | `0xa4c13862d39377e6` |
| MQTT-топик | ❌ hex | `zigbee2mqtt/0xa4c13862d39377e6` |
- Проверено в `/config/zigbee2mqtt/configuration.yaml` (секция `devices:`) — у всех `friendly_name: '<hex>'`.
- Отдельная категория — вообще без имени: `switch.0xa4c138f8da8bc478` (`original_name` пусто), `switch.0xcc86ecfffe1347fd`, `switch.0x84fd27fffed9e137`, `update.0xa4c138f8da8bc478`, `light.0xa4c13882a4b42db0`.
**Корневой механизм (уточнён 2026-09-14 после залива реестров):** `entity_id` формируется при **первом появлении** сущности, а связь сущности в HA идёт **по `unique_id`** (`<ieee>_<param>_zigbee2mqtt`). Смена `friendly_name` в z2m **меняет MQTT-топик и discovery, но НЕ `unique_id`** → HA находит сущность по `unique_id` и **обновляет её на месте, сохраняя существующий `entity_id`**.
⚠️ **ОТМЕНЕНО (было записано ошибочно ранее):** утверждение «смена `friendly_name` → все сущности пересоздаются с новыми `entity_id`, автоматизации ломаются» — **НЕВЕРНО**. Проверено на живом t610: дублей не появилось, человеческие `entity_id` из реестра сохранились. Автоматизации не ломаются. **Достаточно одной операции** — прописать `friendly_name` в z2m (переименовывать `entity_id` в `core.entity_registry` вручную НЕ надо).
> ⚠️ **ВАЖНО про итоговую таблицу имён ниже — она УСТАРЕЛА.** Согласованный список (строки 481–499) замените на **«Итоговый маппинг `friendly_name` (выведен из живых `entity_id` HA)»** из §«КРИТИЧЕСКОЕ ОТКРЫТИЕ» выше. Расхождения (эталон — реальные `entity_id`, а не перевод из TrueNAS-имён): `obratka_pump`→**`nasos_obratki`**, `stairs_light_sensor`→**`light_sensor_stairs`**, `office_smart_light`→**`smart_light_office`**, `stairs_smart_light`→**`light_stairs`**, `bed_wireless_light_switch`→**`wireless_light_switch_bed`**, `shower_night_light`→**`night_light_shower_2`**. Причина: имена на TrueNAS правились вручную, и `entity_id` в HA — единственный достоверный источник.
#### 🔄 Новое устройство: NEO NAS-WR01B (2026-09-14)
Alex заменил Tuya Smart Plug (греющий кабель воды) на **Zigbee-розетку**.
- **IEEE:** `0xa4c138eb6fbe9d19`
- **Модель:** NEO NAS-WR01B, «Smart plug (with electrical measurements)» — P/V/I/E
- **powerSource:** Mains (single phase)
- Проверка: `Successfully configured '0xa4c138eb6fbe9d19' (definition v0.0.1)`, `voltage: 223`, `state: OFF`, `linkquality: 232`; discovery ушёл, сущности создались.
- **Итого в z2m — 17 устройств.**
- При участии `localtuya`: розетка была на Tuya (`192.168.2.194`) → сменилась на Zigbee → `localtuya` снят с переноса.
**Рецепт permit_join (спаривание без UI, через MQTT):** в z2m `permit_join` не задан → окно закрыто. Открывается так (пароль из опций аддона, БЕЗ интерполяции — `$` в пароле ломает bash):
```bash
ha apps info 45df7312_zigbee2mqtt --raw-json | jq -r '.data.options.mqtt.password' > /tmp/pw
MPW=$(cat /tmp/pw); rm -f /tmp/pw
mosquitto_pub -h core-mosquitto -p 1883 -u zont -P "$MPW" \
-t 'zigbee2mqtt/bridge/request/permit_join' -m '{"value": true, "time": 180}'
timeout 10 mosquitto_sub -h core-mosquitto -p 1883 -u zont -P "$MPW" \
-t 'zigbee2mqtt/bridge/response/permit_join' -t 'zigbee2mqtt/bridge/event' -C 3
```
⚠️ `mosquitto_pub/sub` в аддоне **НЕ знают `--pwfile`** (Unknown option) — только `-u`/`-P`.
⚠️ **`ha apps logs <slug>` тяжёлый** — не гонять его в цикле ожидания («висит» минутами). Ждать готовности интервью по `database.db`/`state.json`.
Подробнее: [[family/how-to/t610-access]] §USB → «Спаривание нового Zigbee-устройства».
#### ✅ Имена 17 устройств — СОГЛАСОВАНЫ 2026-09-14 (латиница snake_case, единый формат)
**Источник эталона — НЕ выдумка:** на **TrueNAS в z2m `friendly_name` уже были человеческими** (файл `/mnt/RED_2TB/docker/zigbee2mqtt/configuration.yaml`, сохранился в бэкапе `~/tmp-t610/z2m-backup-20260914/configuration.yaml`). Дополнительно человеческие имена есть в **`core.device_registry` TrueNAS** и в **дашборде `lovelace.home_plan`** (он ссылается на `switch.sauna`, `light.smart_light_office_left`, `binary_sensor.shower_2_presence_sensor_presence` и т.п.).
**Что произошло (моя ошибка при переносе):** на t610 залит `configuration.yaml` z2m **без секции `devices:`** → z2m при старте сам дописал `devices:` с `friendly_name` = IEEE для всех 17 устройств. Т.е. hex-имена на t610 — артефакт переноса, а не исходное состояние.
**Итоговая таблица (латиница, исправлены опечатки вроде `Presense`→`presence`):**
| # | IEEE | Было (TrueNAS) | **Стало (согласовано)** | Роль |
|---|---|---|---|---|
| 1 | `0xa4c13862d39377e6` | hex | `kabinet_temperature_sensor` | датчик t°/влажности (в кабинете, временно) |
| 2 | `0xa4c138f8da8bc478` | Насос обратки | `obratka_pump` | розетка с измерением P/V/I/E |
| 3 | `0xcc86ecfffe1347fd` | Mini Smart Switch 1 | ⏳ `unused_wall_switch` **или удалить** | реле, **нигде не используется, `unavailable`, нет зоны** |
| 4 | `0x84fd27fffed9e137` | Zigbee Mini Switch 2 | `shower_night_light` | зона Душевая; автоматизации «Вкл./Выкл. ночной свет душевая» |
| 5 | `0xa4c1386d40ddb67b` | Light Sensor Stairs | `stairs_light_sensor` | датчик освещённости |
| 6 | `0xa4c138dc6d856eca` | Presense Sensor 1 | `presence_sensor_1` | радар присутствия mmWave |
| 7 | `0xa4c1381186ed1a32` | Smart light Office | `office_smart_light` | выключатель 2-кл |
| 8 | `0xa4c13873b5c1575b` | Light switch Table Office | `office_table_light_switch` | реле 2 канала L1/L2 |
| 9 | `0xa4c13807b64c7fd4` | Kitchen hood | `kitchen_hood` | реле 3 канала (вытяжка) |
| 10 | `0xa4c138cefeee19fd` | Zigbee dimmer 2ch | `zigbee_dimmer_2ch` | светильник 2 канала |
| 11 | `0xa4c1386d0839706a` | Smart Light Stairs | `stairs_smart_light` | реле |
| 12 | `0xa4c1384fbe0b3a6b` | Sauna | `sauna` | розетка без мониторинга |
| 13 | `0xa4c138b0f9e674a5` | Wireless light switch bed | `bed_wireless_light_switch` | беспроводной выключатель |
| 14 | `0xa4c13882a4b42db0` | Dimmer bed | `bed_dimmer` | диммер 1 канал |
| 15 | `0xa4c138c4a94a6a31` | Shower 2 presence sensor | `shower_2_presence_sensor` | радар присутствия 2-й |
| 16 | `0xa4c1383d5fcaa063` | hex | `boiler_water_leak` | датчик протечки, зона **Котельная** |
| 17 | `0xa4c138eb6fbe9d19` | — (новая) | `heating_cable_plug` | NEO NAS-WR01B, розетка греющего кабеля |
**Зоны (11, из `core.area_registry` TrueNAS):** Гостиная `living_room`, Кухня `kitchen`, Спальня `bedroom`, Детская `detskaia`, Кабинет `kabinet`, Ванная `vannaia`, Душевая `dushevaia`, Туалет `tualet`, Северная `severnaia`, Котельная `kotelnaia`, Лестница `lestnitsa`. Этажи: 1, 2.
**Модели (из `database.db`, JSON-lines — НЕ sqlite):** `modelID` пуст, но `manufName` даёт модель Tuya: `_TZ3000_akqdg6g7`, `_TZ3000_gjnozsaz`, `_TZ3000_3a9beq8a`, `_TZ3000_hy6ncvmw`, `_TZE200_crq3r3la`, `_TZ3000_0e6uvexf`, `_TZ3000_5gey1ohx`, `_TZ3000_odzoiovu`, `_TZ3000_kvwrdf47`, `_TZ3210_nhqka112`, `_TZ3000_kccru4oi`, `_TZ3000_ooc8illt`, `_TZE204_qasjif9e`, `Zbeacon` (протечка).
> 📌 **Питфолл:** база z2m `database.db` — это **JSON Lines** (объект на строку), НЕ SQLite. `sqlite3 database.db` → `file is not a database`. Читать: `jq -r 'select(.type!="Coordinator") | [.ieeeAddr,.type,.manufName] | @tsv' z2m-live.db`.
> Также в SSH-аддоне **нет `sqlite3`** — базу копировать на Mac (`scp` → `/Users/admin/tmp-t610/z2m-live.db`).
#### ℹ️ Как читать «человеческие имена» в HA (важно для будущих сессий)
**Имена устройств НЕ лежат в реестрах HA** — их надо искать в трёх местах:
1. **z2m** `/config/zigbee2mqtt/configuration.yaml` → секция `devices:``friendly_name`.
2. **HA `core.device_registry`**`data.devices[].name``name_by_user`), связь через `identifiers: [["mqtt","zigbee2mqtt_0x..."]]`.
3. **Дашборд `lovelace.home_plan`**`entity` в `picture-elements` (самый надёжный источник рабочей схемы имён).
Поля сущностей: `original_name` = **имя параметра** («Температура», «Влага») — НЕ имя устройства; `name`/`name_by_user` часто `null`. **Вывод: `original_name` для определения устройства бесполезен** — все 16 датчиков t° будут «Температура».
**Пифолл поиска:** если автоматизация ссылается на устройство, искать надо по `device_id`, а не `entity_id` — в `automations.yaml` триггеры вида `type: battery_level / device_id: ...` не содержат имени устройства. Связь: `device_id``core.device_registry``identifiers` → IEEE.
**Инвентарь `configuration.yaml` TrueNAS (30КБ):** секции — `default_config`, `http`, `logger`, `modbus` (17: шина вентиляции — заслонки intake/exhaust, AT2), `input_number`, `input_boolean`, `template` (summary-сенсоры `dining_summary`, `kids_summary`, `bedroom_summary`, `at2_1_summary`), `frontend` (темы), `automation/script/scene: !include`. **Секции `mqtt:` в конфиге НЕТ.** Датчики `sensor.dining_*`/`kids_*`/`bedroom_*` (`platform: mqtt`) — это **Modbus RTU Sniffer** (шина вентиляции), НЕ Zigbee, и уже названы правильно.
**Автоматизации TrueNAS (`automations.yaml`, 15 шт.):** Выключить/Включить циркуляцию ГВС, Ventilation automation on, office_pass_switch_table/main, Светло/Темно подсветку лестницы, Toggle/Cycle Dimmer bed, Вкл./Выкл. ночной свет душевая, Протечка котельная, Датчик протечки котельная батарея, Датчик освещённости лестница батарея, Light switch bed батарея, Zigbee T sensor батарея.
**Дашборд `home_plan` — правки ссылок (статус 2026-09-14):**
-`switch.vvod_vody_greiushchii_kabel`**`switch.heating_cable_plug`** — СДЕЛАНО (в staged-файле, скрипт `fix_dashboard.py`)
-`binary_sensor.0xa4c1383d5fcaa063_water_leak`**`binary_sensor.boiler_water_leak_water_leak`** — СДЕЛАНО (автоматически переименованием реестра)
-`switch.0xa4c138f8da8bc478`**`switch.recirculation_pump`** — СДЕЛАНО (то же)
-`light.0xa4c13882a4b42db0`**`light.bed_dimmer`** — **СДЕЛАНО** (`fix_dash2.sh`, jq-walk по `.data.config`; `sed` не использовали)
-`light.smart_light_stairs_l1` — в дашборде ОК (человеческое)
**Итог по дашборду:** hex-ссылок в `lovelace.home_plan` **не осталось** (проверено `grep '"entity": *"[^"]*0x'` → пусто).
#### ✅ `switch_as_x` восстановлены (2026-09-14) — виртуальные `light.*`
**Симптом:** `light.night_light_shower_2`, `light.smart_light_office_left/right`, `light.smart_light_stairs_l1` были `unavailable`.
**Причина:** эти сущности — `platform: switch_as_x` (виртуальный «выключатель как свет»), созданные вручную на TrueNAS. Их `config_entry_id` ссылался на записи в `core.config_entries`, которых мы **не переносили** → сущности-сироты.
**Фикс:** на TrueNAS найдено 4 entry `switch_as_x`, добавлены в t610 (с исправлением hex-ссылки):
| title | `options.entity_id` |
|---|---|
| `Office table light` | `switch.smart_light_office_right` |
| `Office main light` | `switch.smart_light_office_left` |
| `0x84fd27fffed9e137` | → исправлено на `switch.night_light_shower_2` |
| `L1` | `switch.light_stairs_l1` |
Порядок: `ha core stop` → добавить entry в `core.config_entries``ha core start`. Результат: все 5 `light.*` работают (`off` вместо `unavailable`).
Скрипты: `~/tmp-t610/{add_switchasx.py,apply_switchasx.sh}`, данные `t610-config-entries-new.json`.
⚠️ **Питфолл:** при переносе реестров **`core.config_entries` тоже нужен**, если есть виртуальные сущности (`switch_as_x`, `template`, helper'ы) — иначе их entry теряются и сущности висят `unavailable`. Мы не переносили его ради сохранения MQTT-интеграции → добавляли `switch_as_x` точечно.
#### 🔴 ПИТФОЛЛ: `device_id` ломаются при переносе реестра — ✅ ЗАКРЫТО 2026-09-14
**Симптом:** после переноса реестров и старта HA **13 из 16 автоматизаций стали `unavailable`**. В `ha core logs`:
```
ERROR (MainThread) [homeassistant.components.automation] Automation with alias 'Протечка котельная'
failed to setup triggers and has been disabled: Unknown device 'c42ce32c73731941884bcbf0c2b2d077'
```
**Причина:** `device_id` — это **UUID, генерируемый HA при регистрации устройства в КОНКРЕТНОМ инстансе**. Хотя `core.device_registry` перенесён с TrueNAS, MQTT-интеграция t610 **зарегистрировала устройства заново** (запись MQTT-интеграции у нас своя, t610-шная) → выдала **новые** `device_id`. Автоматизации остались со старыми TrueNAS-овскими.
⚠️ **Главный вывод: `device_id` НЕ переносятся между инстансами HA.** `entity_id` и `unique_id` — переносятся; `device_id` — НЕТ. Автоматизации/скрипты, ссылающиеся на `device_id` (а это все device-trigger'ы в UI), ломаются.
##### ✅ Полный маппинг — 9/9, собран 2026-09-14 (метод: по `identifiers`)
**Метод (надёжный, воспроизводимый):** взять TrueNAS-реестр `core.device_registry` (оттуда, где `device_id` из автоматизаций ЕСТЬ) → для каждого устройства вытащить `identifiers` (для zigbee = `[[\"mqtt\",\"zigbee2mqtt_<ieee>\"]]`) → найти на t610 устройство с **тем же `identifiers`** → его `id` и есть актуальный `device_id`.
| # | Устройство | `device_id` TrueNAS (в автоматизациях) | `device_id` t610 (актуальный) | Замен |
|---|---|---|---|---|
| 1 | `night_light_shower_2` (`84fd27fffed9e137`) | `0c7a0eb6d60e852b447266da69a7785e` | `4d6e55505ff7dbad13d2674cdcb18d5a` | 2 |
| 2 | `recirculation_pump` (`f8da8bc478`) | `234686e6c83f7d19c9e10b2f0d1fcc59` | `16d2c6f64ec399e5261c89b35c1e75c6` | 2 |
| 3 | `bed_dimmer` (`82a4b42db0`) | `2862be34f5eaaa39b9d51084a1f921ee` | `098a641cb1d30f08f1ae293e00d9885b` | 1 |
| 4 | `office_temperature_sensor` (`62d39377e6`) | `52266a1b4a9d301f0da0dfa6f97c4ea2` | `bcf47eeae909877978bdaf6c705210f8` | 1 |
| 5 | `wireless_light_switch_bed` (`b0f9e674a5`) | `757e0e9b1e5771e0c700a9df852b0549` | `5cd5d9d2d289b5e470bbeaac0eb7905c` | 3 |
| 6 | `light_sensor_stairs` (`6d40ddb67b`) | `7f102ad2e78960fff6ebc5f01c0bba93` | `1ea8bbc2612dde303e4279bc5fbad57a` | 3 |
| 7 | `shower_2_presence_sensor` (`c4a94a6a31`) | `7f74e7077d7fa23e405f2c5e25f6fa17` | `4095e7c3b47b9dc9640cfb8c3aeff022` | 4 |
| 8 | `light_stairs` (`6d0839706a`) | `9d3f31b1b49c95433428afbb113d929b` | `028b7d9f489c87bdc9e563473a1d61e8` | 2 |
| 9 | `boiler_water_leak` (`3d5fcaa063`) | `c42ce32c73731941884bcbf0c2b2d077` | `b9d384a51b780a7924ed9504eddec12e` | 2 |
**Итого: 9 `device_id` из автоматизаций (все 9 — zigbee), 20 вхождений.** ⚠️ Прежняя оценка «6 UUID» была неполной — реальных уникальных `device_id` **9**. В `scripts.yaml` device-ссылок **0** (проверено).
**Как получить актуальные `device_id` (bash+jq на t610):**
```bash
jq -r '.data.devices[] | select((.identifiers|tostring)|contains("<ieee_без_0x>")) | [.id, (.name//"—")] | @tsv' \
/config/.storage/core.device_registry
```
Связь: `identifiers` = `[["mqtt","zigbee2mqtt_<ieee>"]]``id` = искомый `device_id`.
⚠️ **Получение TrueNAS-реестра:** `/mnt/RED_2TB/docker/ha/.storage/core.device_registry` **читается без sudo** (права 644, owner root) — `scp` работает напрямую. `sudo cat` падает (`a terminal is required to read the password`) — sudo не нужен.
##### 🔴 ВТОРАЯ ПОЛОМКА ТОГО ЖЕ КЛАССА: hex `entity_id` в `automations.yaml` (2 шт.)
⚠️ **Тот же класс проблемы, что и `device_id`, её легко пропустить.** При переименовании реестра (`hex → человеческие entity_id`) **ссылки в `automations.yaml` не обновлялись** → в автоматизациях остались старые hex-`entity_id`:
- `light.0xa4c13882a4b42db0` → должно быть **`light.bed_dimmer`** (в реестре уже человеческое).
- `switch.0xa4c13873b5c1575b` → ⚠️ **неоднозначно**: у устройства теперь два канала `switch.office_table_light_switch_l1` / `_l2` (z2m разбил на каналы), а в автоматизации одна старая сущность. Требует разбора, какой канал использовался на TrueNAS (см. §«Осталось»).
**Мораль:** при переносе HA надо чистить **и `device_id`, и hex-`entity_id`** одновременно — иначе автоматизация «чинится» наполовину.
##### ✅ План починки — ВЫПОЛНЕН 2026-09-14 (автоматизации 0 `unavailable`)
**Файл:** `~/tmp-t610/etap3-fix/automations.fixed.yaml` — 20 замен `device_id`, 0 старых осталось, все 20 новых проверены по реестру t610. Бэкап на t610: `/config/automations.yaml.bak-devid-20260914-124733`.
**Уточнение про `switch.0xa4c13873b5c1575b` (закрыт вопрос l1/l2):** это был **артефакт regex**, а не реальная ссылка. В `automations.yaml` уже стояли правильные `switch.0xa4c13873b5c1575b_l1` (авт. `office_pass_switch_table`) и `switch.0xa4c13873b5c1575b_l2` (авт. `office_pass_switch_main`). Проверка `grep -E 'switch\.0xa4c13873b5c1575b(?!_l)'`**0 совпадений**. Чинить нечего.
**Реально исправленный hex-`entity_id` — только один:** `light.0xa4c13882a4b42db0``light.bed_dimmer` (2 вхождения, автоматизация «Dimmer bed cycle»: и в `state_attr()`, и в `target.entity_id`).
**Порядок (выполнен):** `ha core stop`-эквивалент через `ha core restart` → залив `automations.yaml` → проверка API.
**Результат (факт, 2026-09-14):** `automation.*`**16 сущностей: 15 `on` + 1 `off` (0 `unavailable`)**. Единственная `off``automation.ventilation_automation_on`: это **нормальное состояние** (вентиляция управляется через modbus-шину, а не по расписанию), идентично TrueNAS.
**Важный побочный вывод:** 12 `entity_id` внутри device-действий формата `entity_id: <32-hex>` — это **реестровые `id` сущностей**, а НЕ строки `domain.name`. Они **совпали** и менять их не пришлось, потому что при переименовании реестра через jq мы меняли только поле `entity_id`, а `id` оставался нетронутым (перенесён с TrueNAS). Проверено: 12/12 найдены в `core.entity_registry`.
##### 🔴 ТРЕТЬЯ ПОЛОМКА ТОГО ЖЕ КЛАССА: hex-`entity_id` зашиты в код local add-on (modbus-bridge)
**Симптом:** modbus-bridge бесконечно логирует `HA poll: sensor.0xa4c13862d39377e6_temperature HTTP 404`.
**Причина:** hex-`entity_id` (`sensor.0xa4c13862d39377e6_temperature`, `switch.0xa4c138f8da8bc478`) зашиты **в код аддона**, а не в опции:
- `/addons/modbus-bridge/modbus_ha_bridge.py` (строки 74, 82, 89 — встроенный дефолт `DEFAULT_CONFIG`)
- `/addons/modbus-bridge/data/config.template.tmpl` (строки 112, 125)
Правильные значения: `sensor.office_temperature_sensor_temperature`, `switch.recirculation_pump`.
**⚠️ ГЛАВНЫЙ ПИТФОЛЛ: правки в `data/*.tmpl` НЕ применяются без rebuild.** `run.sh` генерирует runtime `/app/config.yml` из **`/app/config.template.yml`** — копии внутри **образа** (`Dockerfile`: `COPY data/config.template.tmpl /app/config.template.yml`). Поэтому после правки исходников обязателен **`ha addons rebuild local_modbus-bridge`** (без него работает старая версия из образа — я на этом попался).
**Проверка после rebuild:** в логе `HA poll -> sensor.office_temperature_sensor_temperature = 23.96` (200, без 404).
#### 🔧 ПИТФОЛЛ: `http:` в `configuration.yaml` игнорируется после миграции
**Симптом (варнинг в HA):** `YAML configuration is ignored after migration` / `The HTTP configuration in configuration.yaml has already been migrated and is now being ignored... This stops working in version 2027.2.0.` → надо удалить блок `http:` и управлять через **Settings → System → Network**.
**Что было:** в `configuration.yaml` блок
```yaml
http:
use_x_forwarded_for: true
trusted_proxies:
- 172.16.0.0/12
```
Но в `.storage/http` этих ключей **НЕ было** → простое удаление блока **потеряло бы доверие к Caddy-прокси** (HA отдаёт 400 на запросы через прокси — критично для Этапа 4).
**Правильный фикс (выполнен):**
1. Бэкап `configuration.yaml``.bak-http-<ts>`; бэкап `.storage/http``.bak-<ts>`.
2. Добавить в `.storage/http``data.stable` (jq, локально): `use_x_forwarded_for: true`, `trusted_proxies: ["172.16.0.0/12"]`. Файл положить **при остановленном HA** (`ha core stop`), иначе HA перезапишет.
3. Удалить блок `http:` из `configuration.yaml`, оставить комментарий-пояснение.
4. `ha core start` → HA сам выставляет `data.yaml_migration_done: true`.
**Проверка:** `jq '.data.stable | {server_port, use_x_forwarded_for, trusted_proxies}' /config/.storage/http` → порт 80, настройки на месте; `curl -o /dev/null -w '%{http_code}' http://192.168.2.176/` → 200.
> 📌 **Общий принцип (для любых настроек, мигрированных в UI):** прежде чем удалять YAML-блок, **сверить**, что все его ключи реально есть в `.storage/<domain>`. Иначе настройка молча теряется.
#### 🔴 ПИТФОЛЛ (КРИТИЧНЫЙ): `modbus.host` не может быть `127.0.0.1` — mbusd в отдельном контейнере
**Симптом:** 32 `switch.*_damper` (заслонки вентиляции) `unavailable`, **0 живых modbus-сенсоров**, хотя `local_mbusd` = `started` и порт 502 на хосте открыт.
**Причина:** в `configuration.yaml` было `modbus.host: 127.0.0.1`. HA Core сидит **в своём контейнере** (`172.30.32.1`), а `local_mbusd` — в **другом контейнере**, пробросивший порт на **хост**. Для HA `127.0.0.1` = он сам → таймаут.
**Фикс:** `host: 192.168.2.176` (IP хоста t610, порт 502 проброшен Docker'ом). Проверка: `nc -z 192.168.2.176 502` → OK.
> ⚠️ **Не путать:** в логе mbusd `conn_open(): accepting connection from 172.30.32.1` + мгновенный `conn_close()` = признак неверного адреса. Успешное подключение выглядит как **`conn_open(): accepting connection from 192.168.2.176`** и **БЕЗ последующего `conn_close`** (HA держит коннект).
#### 📡 ПИТФОЛЛ: диагностика modbus-шины через прямой TCP-запрос
Проверка «жива ли шина», не залезая в HA (скрипт `~/tmp-t610/etap3-fix/mbtest.sh`):
```bash
# Modbus TCP: FC=03, UnitID=0b(11), Start=0000, Qty=0001
printf '\x00\x01\x00\x00\x00\x06\x0b\x03\x00\x00\x00\x01' > /tmp/mbreq.bin
nc -w 5 192.168.2.176 502 < /tmp/mbreq.bin | xxd
```
**Расшифровка ответа:**
| Ответ | Значение |
|---|---|
| `... 01 03 02 XXXX` | ✅ нормальный ответ (FC=03, 2 байта данных) |
| `... 01 83 04` | ❌ **SLAVE DEVICE FAILURE** — устройство есть, но не ответило |
| `... 0b 83 0b` | ❌ **GATEWAY TARGET DEVICE FAILED TO RESPOND** — mbusd передал, шина молчит (нет устройства/нет провода) |
> Exception-код = байт FC с флагом `0x80`; второй байт — код ошибки. Для заслонок вентиляции **slave = 11** (`configuration.yaml`, секция `modbus`).
##### ⏸️ ИТОГ: заслонки вентиляции — АППАРАТНЫЙ блокер (за Alex)
mbusd-стек **исправен полностью**: TCP отвечает, serial открыт (`/dev/serial/by-path/pci-0000:00:12.0-usb-0:4:1.0-port0`), HA-коннект с `192.168.2.176` держится. Но на запрос к slave 11 шина отдаёт **exception 0x0B** — устройство не отвечает.
**Диагноз: CH340 #2 воткнут в USB-порт t610, но линии A/B вентиляционной шины к нему не подключены (или шина обесточена).**
Это ровно тот блокер, о котором Alex предупреждал в начале сессии («проверь USB основной шины, дальше подключу ZONT»). **Никакие конфиги тут не помогут — нужна физика.** Заслонки оживут сами, как только шина будет подключена. **Адреса записаны:**
```
/dev/serial/by-path/pci-0000:00:12.0-usb-0:4:1.0-port0 (ttyUSB1, CH340 #2) = ВЕНТИЛЯЦИЯ (slave 11)
/dev/serial/by-path/pci-0000:00:12.0-usb-0:3:1.0-port0 (ttyUSB0, CH340 #1) = ZONT
```
⚠️ **Мораль для будущих переносов HA:** либо переносить `core.config_entries` целиком вместе с реестрами (тогда `device_id` согласуются), либо **после переноса обязательно перемапить `device_id`** в `automations.yaml`/`scripts.yaml` по `identifiers`, **и заодно проверить hex-ссылки `entity_id`**. Мы выбрали не переносить `core.config_entries` (чтобы сохранить MQTT-интеграцию t610) — отсюда и разрыв.
#### ⚠️ `sensor.*_summary` — TemplateError (косметика, самоизлечится)
В логе после старта:
```
TemplateError: ValueError: Template error: round got invalid input 'unknown'
when rendering template '{{ states('sensor.kids_temperature')|round(0)|int }}° ...'
```
Причина: sniffer-датчики (`kids_*`, `bedroom_*`, `dining_*` — Modbus RTU) ещё не прислали данные → `states()` = `'unknown'`. Уйдёт, когда пойдут значения. Если раздражает — добавить `|default(0)` в `configuration.yaml` (§template, сенсоры `dining_summary`, `kids_summary`, `bedroom_summary`).
#### ✅ ФИНАЛ: имена приведены к единому виду (2026-09-14)
**Результат:**
| Показатель | Значение |
|---|---|
| Hex-`entity_id` в реестре HA | **0** (было 126) |
| Сущностей в реестре | 319 |
| Устройств в z2m | **14 живых** (было 17 — удалены 3 мёртвых) |
| `friendly_name` в z2m | все человеческие, латиница snake_case |
**Итоговая таблица (14 устройств, СОГЛАСОВАНО Alex):**
| IEEE | `friendly_name` | Роль | Зона |
|---|---|---|---|
| `0xa4c13862d39377e6` | `office_temperature_sensor` | датчик t°/влажности (в кабинете, временно) | Кабинет |
| `0xa4c138f8da8bc478` | `recirculation_pump` | розетка насоса обратки (P/V/I/E) | Котельная |
| `0x84fd27fffed9e137` | `night_light_shower_2` | ночной свет | Душевая |
| `0xa4c1386d40ddb67b` | `light_sensor_stairs` | датчик освещённости | Лестница |
| `0xa4c1381186ed1a32` | `smart_light_office` | выключатель 2-кл | Кабинет |
| `0xa4c13873b5c1575b` | `office_table_light_switch` | реле 2 канала L1/L2 | Кабинет |
| `0xa4c13807b64c7fd4` | `kitchen_hood` | реле 3 канала (вытяжка) | Кухня |
| `0xa4c1386d0839706a` | `light_stairs` | реле (лестница) | Лестница |
| `0xa4c1384fbe0b3a6b` | `sauna` | розетка без мониторинга | Туалет |
| `0xa4c138b0f9e674a5` | `wireless_light_switch_bed` | беспроводной выключатель | Спальня |
| `0xa4c13882a4b42db0` | `bed_dimmer` | диммер 1 канал | Спальня |
| `0xa4c138c4a94a6a31` | `shower_2_presence_sensor` | радар присутствия | Душевая |
| `0xa4c1383d5fcaa063` | `boiler_water_leak` | датчик протечки | Котельная |
| `0xa4c138eb6fbe9d19` | `heating_cable_plug` | NEO NAS-WR01B, розетка греющего кабеля | — |
**Удалены как мёртвые (3 шт., `lastSeen` 5–8 месяцев назад, не используются ни в одной автоматизации/дашборде):**
`0xcc86ecfffe1347fd` (Mini Smart Switch 1), `0xa4c138dc6d856eca` (Presense Sensor 1, радар), `0xa4c138cefeee19fd` (Zigbee dimmer 2ch). Все три были `unavailable`/`unknown`, `state.json` пуст.
#### 🔑 КЛЮЧЕВОЙ МЕХАНИЗМ: почему HA не переименовал сущности сам
**HA связывает сущность по `unique_id`, а `entity_id` — вторичен.** z2m при смене `friendly_name` **НЕ меняет `unique_id`** (он вида `<ieee>_<param>_zigbee2mqtt`). Значит:
- Смена `friendly_name` меняет MQTT-топики (`state_topic`) и `default_entity_id` в discovery.
- Но HA видит **тот же `unique_id`** → считает сущность существующей и **НЕ переименовывает её `entity_id`**.
- Итог: после смены имён в z2m (шаг 1) надо **вручную переименовать `entity_id` в реестре HA** (шаг 2).
**Порядок (выполнен):**
1. ✅ Заменить `devices:` в `/config/zigbee2mqtt/configuration.yaml` (friendly_name = человеческие) → `ha apps restart 45df7312_zigbee2mqtt`. Проверка: z2m публикует в `zigbee2mqtt/<human_name>`, `bridge/devices` показывает человеческие имена.
2.`ha core stop` → переименовать `entity_id` в `core.entity_registry` (regex `<domain>.<hex>[_suffix]``<domain>.<friendly>[_suffix]`) → `ha core start`.
**Команда переименования (jq, т.к. python3 в аддоне НЕТ):**
```bash
REG=/config/.storage/core.entity_registry
cp "$REG" "${REG}.bak-rename-$(date +%Y%m%d-%H%M%S)"
MAP='{"0xa4c13862d39377e6":"office_temperature_sensor", ...}'
jq --argjson map "$MAP" '
.data.entities |= map(
if (.entity_id | test("^[a-z_]+\\.0x[0-9a-f]+")) then
(.entity_id | capture("^(?<d>[a-z_]+)\\.(?<h>0x[0-9a-f]+)(?<s>.*)$")) as $m
| if $map[$m.h] != null then .entity_id = ($m.d + "." + $map[$m.h] + $m.s) else . end
else . end
)' "$REG" > /tmp/reg.new.json
# проверить: jq -e '(.data.entities|length) > 300' /tmp/reg.new.json
cp /tmp/reg.new.json "$REG"; chown root:root "$REG"; chmod 644 "$REG"
```
Скрипты: `~/tmp-t610/{gen_names.py,do_rename_jq.sh,apply_names.sh,build_z2m_config.py}`, `z2m-new-config.yaml`.
⚠️ **Питфолл jq:** конструкция `(.entity_id) as $eid | capture(...)` падает с `object cannot be matched, not a string` — нужно `if (.entity_id | test(...)) then (.entity_id | capture(...)) as $m | ...`.
⚠️ **Питфолл bash:** круглые скобки в `echo "..."` ломают скрипт (`syntax error near unexpected token '('`) — избегать `()` в строках вывода.
#### ℹ️ Наблюдение: `unknown` у части сущностей — НЕ баг
После переименования часть сущностей остаётся `unknown`, при этом `sensor.X_voltage` работает, а `switch.X` — нет. Это **нормально**: z2m публикует payload **только при получении данных от устройства**. Router'ы (питаемые от сети) отчитываются постоянно, EndDevice (батарейные) — редко/по событию. `switch.sauna` вечно `unknown`, потому что розетка **физически отключена** (`lastSeen` 8+ часов). Проверка живости: `jq` по `database.db``lastSeen` в мс.
#### ✅ Zigbee `friendly_name` — корень generic-имён (разведка 2026-09-14)
> 📌 Дашборд ссылается на сущности по человеческим именам, которые уже есть в реестре (`switch.sauna`, `light.smart_light_office_left`, `light.smart_light_stairs_l1`, `binary_sensor.shower_2_presence_sensor_presence`). После шага 8 всё оживёт.
#### 🔌 Новое устройство: `boiler_controller_power` — питание контроллеров котлов (2026-09-14)
**Задача Alex:** добавить Zigbee-розетку в котельную как **питание для контроллеров котлов**.
| Параметр | Значение |
|---|---|
| `friendly_name` | **`boiler_controller_power`** |
| IEEE | `0xa4c1381694217e10` |
| Модель | **TS011F**, `manufName = _TZ3000_gjnozsaz` (Tuya Smart Plug с измерениями P/V/I/E) |
| Тип | **Router, Mains (single phase)** — усиливает mesh в котельной |
| Зона | **Котельная** (`kotelnaia`) |
| device_id | `dab3c111a46c9c441e2c90fdfe35a702` |
| Сущности | **13**, все с человеческими `entity_id` |
| Состояние | `switch.boiler_controller_power = off`, `voltage = 218` |
**Процедура (выполнена, рабочий рецепт):**
1. **permit_join** через MQTT: `mosquitto_pub -t zigbee2mqtt/bridge/request/permit_join -m '{"value":true,"time":250}'`.
⚠️ **Питфолл: лимит окна — 254 секунды.** `"time":300``error: Cannot permit join for more than 254 seconds`. Ставить **≤250**.
2. Устройство присоединилось само → появилось в `bridge/devices` под **hex-именем** `0xa4c1381694217e10`.
3. **Переименование в z2m** (до того как HA создаст сущности!):
`mosquitto_pub -t zigbee2mqtt/bridge/request/device/rename -m '{"from":"0xa4c1381694217e10","to":"boiler_controller_power"}'`
4. ⚠️ **HA всё равно создал сущности по hex-имени** (успел между join и rename) → понадобился **второй фикс: переименование `entity_id` в `core.entity_registry`** (HA stop → jq → HA start).
5. Зона ставится **в `core.device_registry`** (`area_id`), НЕ в entity_registry. Устройство найдено по `identifiers` = `[["mqtt","zigbee2mqtt_0xa4c1381694217e10"]]``area_id = "kotelnaia"`.
**Явный маппинг (13 hex → человеческие):**
```bash
# jq-фильтр: replace по каждому суффиксу
switch.0xa4c1381694217e10 -> switch.boiler_controller_power
switch.…_child_lock -> switch.boiler_controller_power_child_lock
number.…_countdown -> number.boiler_controller_power_countdown
select.…_power_outage_memory -> select.boiler_controller_power_power_outage_memory
select.…_switch_type_button -> select.boiler_controller_power_switch_type_button
select.…_indicator_mode -> select.boiler_controller_power_indicator_mode
sensor.…_power / _current / _voltage / _energy / _linkquality
button.…_identify -> button.boiler_controller_power_identify
update.0xa4c1381694217e10 -> update.boiler_controller_power
```
Скрипты: `~/tmp-t610/etap3-fix/{permit_join.sh,z2m_devices.sh,rename_plug.sh,rename_plug_ids.jq,set_area.jq,check_plug.sh}`.
**⚠️ ВЫВОД (обобщение питфолла):** `bridge/request/device/rename` в z2m **не спасает** — HA успевает создать сущности под hex-именем. Порядок «сначала rename в z2m, потом join» невозможен → **после каждого нового устройства всегда проверять `core.entity_registry` на hex и переименовывать через jq**.
#### 📍 СОСТОЯНИЕ НА КОНЕЦ СЕССИИ 2026-09-14 (поздняя) — для следующей сессии
**Факты (проверено API):** HA `http://192.168.2.176` → 200; z2m `started`, **15 устройств**; реестр **332 сущности**, **hex = 0**; `light.*` (5 шт.) работают; MQTT-интеграция на месте; `switch_as_x` (4) восстановлены; **автоматизации 16 шт.: 15 `on` + 1 `off` (0 `unavailable`)**; HTTP-варнинг устранён; modbus.host = `192.168.2.176`; modbus-bridge опрос работает (без 404).
**Счётчики живого API:** 253 сущности; `unavailable` 44 (из них **32 — заслонки вентиляции**, аппаратный блокер); `unknown` 69.
**Живые zigbee-датчики (проверено):** `sensor.office_temperature_sensor_temperature = 23.96`, `sensor.recirculation_pump_voltage = 221`, `sensor.light_sensor_stairs_illuminance = 556`, `sensor.heating_cable_plug_voltage = 222`, `sensor.boiler_controller_power_voltage = 218`.
**✅ Добавлено в конце сессии:** Zigbee-розетка **`boiler_controller_power`** (`0xa4c1381694217e10`, TS011F, зона Котельная) — питание контроллеров котлов. Итого **15 устройств в z2m**, **332 сущности в реестре**. См. §«Новое устройство: boiler_controller_power».
**✅ Этап 3 ЗАКРЫТ.** Всё, что осталось — вне конфигов:
**⏸️ 1. ГЛАВНОЕ: заслонки вентиляции (32 `switch.*_damper` `unavailable`) — АППАРАТНЫЙ блокер, за Alex.** Подключить линии A/B вентиляционной шины к CH340 #2 (`by-path ...0:4:1.0-port0`, slave 11) или проверить питание шины. После подключения заслонки оживут сами — конфиг уже верный. См. §«ИТОГ: заслонки вентиляции».
**Далее (не блокеры):**
1. `sensor.*_summary` — добавить `|default(0)` в template-сенсоры `configuration.yaml` (косметика; ошибки `round got invalid input 'unknown'` для `kids_*`/`bedroom_*` — уйдут сами, когда sniffer вентиляции пришлёт данные).
2. `switch.sauna` = `unknown` — розетка физически отключена (`lastSeen` 8+ ч), не баг.
3. Камера (§8 родительского плана).
4. Этап 4: Caddy upstream → t610, GPON-редирект → t610, отключить TrueNAS.
**Ключевые пути/скрипты сессии:** `~/tmp-t610/``stage3/out/`, `backups/`, `ha_token.txt`. **Этап 3 правки:** `~/tmp-t610/etap3-fix/``automations.fixed.yaml`, `devid_mapping.json`, `truenas.device_registry`, `core.device_registry`, `core.entity_registry`, `scripts.yaml`, `http.new.json`, `mb_bridge.py`, `config.template.tmpl`, `mbtest.sh`, `check_dampers2.sh`, `final_check.sh`, `restart_ha.sh`, `run_check.sh`, `token.env`, `q_dampers.jq`, `q_sensors.jq`. На t610: `/config/automations.yaml.bak-devid-*`, `/config/configuration.yaml.bak-http-*`, `/config/.storage/http.bak-*`, `/addons/modbus-bridge/*.bak-hex-*`.
**⚠️ Питфолл сессии (важный для будущих сессий):** Alex **устал апрувать** запуск python-скриптов (`execute_code` и `/usr/bin/python3 -c`) — ТРЕБУЕТ bash+jq/curl. Пользоваться shell-скриптами, python только когда без него никак (и предупреждать).
#### 🧩 ДОБАВЛЕНО В КОНЦЕ СЕССИИ 2026-09-14 — ЗОНЫ и office-переключатель
**Три отдельных дефекта, найденных Alex'ом после «всё готово». Все устранены.**
##### ① Зоны (`area_id`) потерялись при ре-регистрации устройств — ГЛАВНЫЙ питфолл
**Симптом (Alex):** «на всех устройствах зоны верно поставлены? Чето в ui кажется что не все девайсы имеют зону».
**Факт:** **14 из 14 zigbee-устройств были БЕЗ зоны** (`area_id = null`). Зона была только у только что добавленной `boiler_controller_power` (её я прописал руками).
**Причина — ТРЕТИЙ слой той же болезни, что `device_id`:** при переносе `.storage` я скопировал `core.area_registry` (**11 зон созданы**), но `area_id` живёт **в `core.device_registry`**. Zigbee-устройства при подключении к локальной MQTT-интеграции **зарегистрировались заново** → новые записи устройств пришли **без `area_id`**. Зоны (как сущности реестра зон) остались, а привязка устройство→зона — нет.
> 🔑 **Обобщение (важно для будущих миграций):** при переносе HA между инстансами **теряются все инстанс-локальные привязки**, а не только `device_id`:
> - `device_id` — новый у каждого устройства;
> - `area_id` (зона устройства) — теряется, если устройство ре-регистрируется;
> - `entity_id` (в `entity_registry`) — сохраняется по `unique_id`, но **ссылки на него в автоматизациях/дашбордах — нет**.
> Переносится **только** то, что задано явными `id` в реестрах (`area_registry`, `floor_registry`).
**Лечение (выполнено):** эталон зон взят с TrueNAS (`/mnt/RED_2TB/docker/ha/.storage/core.device_registry`) — там 17 устройств с зонами. Маппинг построен **по zigbee-адресу** (`identifiers[0][1]` = `zigbee2mqtt_<ieee>`), проставлен через jq при остановленном HA.
**Итоговые зоны (18 устройств):**
| Зона | Устройства |
|------|-----------|
| **Котельная** `kotelnaia` | `boiler_controller_power`, `boiler_water_leak`, `heating_cable_plug`, `recirculation_pump` |
| **Кабинет** `kabinet` | `smart_light_office`, `office_table_light_switch`, `office_temperature_sensor` |
| **Спальня** `bedroom` | `bed_dimmer`, `wireless_light_switch_bed`, `Bedroom Sensor` (modbus) |
| **Душевая** `dushevaia` | `night_light_shower_2`, `shower_2_presence_sensor` |
| **Лестница** `lestnitsa` | `light_stairs`, `light_sensor_stairs` |
| **Кухня** `kitchen` | `kitchen_hood` |
| **Туалет** `tualet` | `sauna` |
| **Гостиная** `living_room` | `Dining Sensor` (modbus) |
| **Детская** `detskaia` | `Kids Sensor` (modbus) |
> ️ `office_temperature_sensor` на TrueNAS зоны не имел → поставлен `kabinet` по расположению. `heating_cable_plug` заменил Wi-Fi-розетку `local_bf8821e863abe84816qmbo` (была `kotelnaia`) → унаследовал зону.
**Скрипт:** `~/tmp-t610/etap3-fix/set_all_areas.jq` (jq-фильтр `map` по `identifiers[0][1]`). Применение: `jq -f set_all_areas.jq core.device_registry > …` → HA stop → scp → HA start.
##### ② Office table switch не управлял светом — ДВЕ причины
**Симптом (Alex):** «office table switch не управляет светом в кабинете».
**Причина A — hex-`entity_id` в ТРИГГЕРАХ автоматизаций.** Триггеры `office_pass_switch_table`/`_main` ссылались на **hex**:
```yaml
entity_id: switch.0xa4c13873b5c1575b_l1 # ← в реестре уже switch.office_table_light_switch_l1
```
При переименовании реестра я поправил `entity_id` в **actions** и **device-триггерах** (`platform: state` с `device_id`), но **НЕ в обычных `platform: state`-триггерах**. HA загрузил автоматизацию, но подписался на несуществующую сущность → кнопка не срабатывала.
> 🔑 **Правило проверки:** после переименования `entity_id` грепать **весь** `automations.yaml` + `scripts.yaml` на `[a-z_]+\.0x[0-9a-f]{16}` — не только `device_id`, не только actions. Найдено ровно 2 (строки 46, 60).
**Причина B — `unknown` у реле из-за пассивной публикации z2m (см. ③).** Триггер имел `not_from: [unavailable, unknown]` → пока состояние `unknown`, он **не сработает по определению**.
**Проверка на живом (рабочий рецепт):**
```bash
# слушаем топики, Alex физически нажимает кнопки
ssh root@192.168.2.176 'timeout 60 mosquitto_sub -h core-mosquitto -u zont -P "…" \
-t "zigbee2mqtt/office_table_light_switch/#" -t "zigbee2mqtt/smart_light_office/#" -v'
# → state_l1/state_l2 (office_table_light_switch идут как state_l1/l2)
# → state_left/state_right (smart_light_office)
# управление светом через HA (проверка цепочки light → switch_as_x → z2m → реле)
curl -s -X POST -K curl.auth -H "Content-Type: application/json" \
http://192.168.2.176/api/services/light/turn_on -d '{"entity_id":"light.smart_light_office_left"}'
```
**Результат:** `light.smart_light_office_left` off→on→off, `switch.smart_light_office_left` off→on→off — **цепочка работает целиком**.
> ℹ️ Разные устройства публикуют **разные имена полей**: `office_table_light_switch` (TS0002) → `state_l1`/`state_l2`; `smart_light_office` (TS0012) → `state_left`/`state_right`. Discovery z2m генерирует корректный `value_template` под каждое — трогать не нужно.
##### ③ z2m НЕ публикует состояние пассивно → реле «залипают» в `unknown`
**Ключевое открытие сессии.** После перезапуска HA все `switch.*` (реле) были `unknown`, при этом устройства **живы** (`lastSeen` в секундах).
**Причина:** z2m публикует payload **только при получении данных от устройства**. Питаемые реле не отчитываются сами по себе — ждут события (нажатия) или запроса.
**Лечение (практическое):** после перезапуска **физически нажать кнопки** на реле — сеть «прогревается», все `unknown` уходят. Проверено: `switch.office_table_light_switch_l1/l2` стали `on`, `switch.smart_light_office_left/right``off`.
##### ③‑бис ✅ ПРОВЕРЕНО на перезагрузке (2026-09-14): `unknown` ВОЗВРАЩАЕТСЯ, `retain` не спасает
**Проведён эксперимент** (не гипотеза): зафиксировано состояние → `homeassistant.restart` через API → состояние снято снова.
| Сущность | До рестарта | **После рестарта** |
|---|---|---|
| `switch.office_table_light_switch_l1/l2` | `on` | **`unknown`** |
| `switch.smart_light_office_left/right` | `off` | **`unknown`** |
| `switch.heating_cable_plug` | `off` | **`unknown`** |
| `switch.boiler_controller_power` | `off` | **`unknown`** |
| `switch.kitchen_hood_l1/l2/l3`, `switch.light_stairs_l1/l2` | `unknown` | `unknown` |
**Вывод:** всё, что было `on`/`off`, после перезагрузки слетает в `unknown`. **Поведение воспроизводимо.**
**Что НЕ помогло (проверено на живом):** в z2m стоят все три штатных механизма —
`retain: true` (подтверждено дважды: `bridge/request/options``{"data":{"restart_required":false},"status":"ok"}`),
`cache_state: true`, `cache_state_persistent: true`, `cache_state_send_on_startup: true`
но **retained-сообщения на топиках устройств фактически не появляются**, состояния при старте не переопубликовываются.
Контроль: `zigbee2mqtt/bridge/state` **retained есть** (прилетает мгновенно при свежей подписке) → брокер retained умеет, дело не в нём.
> 🔬 **Питфолл диагностики retained:** флаг **`-R` (retained-only) у `mosquitto_sub` в аддоне работает НЕ как ожидается** — с ним вывод пустой даже там, где retained есть. **Надёжный способ:** подписаться и смотреть **с timestamp, что прилетает СРАЗУ при подписке** (retained приходит мгновенно, живой трафик — только по событию):
> ```bash
> timeout 4 mosquitto_sub -h 192.168.2.176 -u zont -P "$P" -t 'zigbee2mqtt/bridge/state' -v \
> | while IFS= read -r l; do echo "$(date +%H:%M:%S.%N | cut -c1-12) | $l"; done
> ```
**Практическое следствие:** триггер `platform: state` с `not_from: [unavailable, unknown]` **не сработает на первое нажатие после рестарта** (`unknown` блокирует) — нужно второе.
**Фикс (точечный, не зависит от z2m):** убрать `not_from: [unknown]` из триггеров кнопок в `automations.yaml`.
**ПРИМЕНЁН И ПРОВЕРЕН 2026-09-14.** `not_from: [unavailable, unknown]` удалён из обоих офисных триггеров (`office_pass_switch_table`, `office_pass_switch_main`). Залит файл + рестарт HA. **Проверка на живом (toggle реле через z2m, эмуляция нажатия):**
```
mosquitto_pub … -t 'zigbee2mqtt/office_table_light_switch/set' -m '{"state_l1":"TOGGLE"}'
→ switch.office_table_light_switch_l1: on→off
→ light.smart_light_office_left: off→ON ✅ свет включился С ПЕРВОГО нажатия
→ switch.smart_light_office_left: off→on (физическое реле сработало)
```
Оба канала (l1→`light.smart_light_office_left`, l2→`light.smart_light_office_right`) проверены на вкл и выкл. **Бэкап: `/config/automations.yaml.bak-20260914-131541`**, локально `~/tmp-t610/` + `/tmp/autom_pre.yaml`.
> 🔑 **Почему так, а не иначе (объяснение Alex, которое он и просил закодить):** `not_from: [unknown]` — это ЗАЩИТА, задуманная верно: при старте HA состояние = `unknown`, затем устройство присылает реальный стейт, и без защиты это выглядело бы как «переключение» → свет бы щёлкал сам. Но защита стояла **слишком широко**: она блокировала и первое НАСТОЯЩЕЕ нажатие. На TrueNAS баг не вылезал, потому что HA там почти не перезагружали.
> **Чего хотел Alex:** «запоминать стейт на момент перезагрузки», чтобы после рестарта сразу был `on`/`off`, а не `unknown`.
> ✅ **Этот механизм в z2m УЖЕ настроен и хранит стейт на диске.** Кэш: **`/homeassistant/zigbee2mqtt/state.json`** (не `/config/zigbee2mqtt`, не `/addon_configs/…` — проверить `find / -name state.json`) — плоский JSON `{ieee: {param: value}}`, пишется при каждом событии, среди прочего:
> ```json
> "0xa4c13873b5c1575b": { "state_l1": "ON", "state_l2": "ON" },
> "0xa4c1381186ed1a32": { "state_left": "OFF", "state_right": "OFF" }
> ```
> При старте z2m с `cache_state_send_on_startup: true` эти значения **должны** переопубликовываться. **Остаётся открытым:** приходит ли cached-стейт к HA **до** того, как устройство пришлёт своё (тогда `unknown` не появится вовсе и `not_from` можно вернуть в узком виде). Проверяется так: рестарт HA + снятие состояния каждые 2 с → видно последовательность `unknown`→cached `ON` или `unknown`→`ON`(от устройства). **Эксперимент не доведён.**
> ℹ️ Датчики (t°/освещённость/радар) выходят из `unknown` сами за секунды-минуты — страдают только **реле и кнопки**.
> ℹ️ Проверка живости узла без нажатия — `get`-запрос: `mosquitto_pub … -t 'zigbee2mqtt/<name>/get' -m '{"state_l1":""}'` → устройство отвечает текущим состоянием.
> 🔑 **Диагностический приём:** чтобы понять, жив ли узел, смотреть **`lastSeen` в `database.db`** (`/config/zigbee2mqtt/database.db`, JSON-lines, читать **построчно** — это НЕ единый JSON!):
> ```bash
> while IFS= read -r line; do
> echo "$line" | jq -r 'select(.type=="EndDevice" or .type=="Router") | "\(.ieeeAddr)\t\(.lastSeen // "нет")"'
> done < db.json
> ```
> `lastSeen` обновляется по **любым** пакетам → зелёный флаг живости, даже если `state` в HA = `unknown`.
**Итог по всем трём:** `light.smart_light_office_left/right`, `switch.*_office_table_light_switch_l1/l2`, `switch.smart_light_office_left/right` — все корректны, цепочка управления проверена. Зоны проставлены (18 устройств). Розетка котельной видна (её не было в UI именно из-за `area_id = null`).
> ℹ️ После правок реестров **обновить страницу в браузере (Ctrl+Shift+R)** — UI держит кэш и может не показать новые зоны сразу.
### Этап 4 — проверка и отключение TrueNAS
18. [ ] Чек-лист из родительского плана §6
19. [ ] Caddy upstream → t610; GPON-редирект → t610
20. [ ] Остановить + отключить автозапуск на TrueNAS (§7)
## Отличия от родительского плана (что меняется)
| Было (родительский план) | Стало (этот план) |
|--------------------------|-------------------|
| docker-compose 1:1 на HA OS | HA-аддоны |
| udev-алиасы `99-tty-alias.rules` на t610 | **не нужно** — аддоны с `uart: true` видят `/dev/serial/by-path/...` и `/dev/serial/by-id/...` автоматически; в конфиге сервиса указывается by-path |
| Скрипт ожидания tty + systemd | **не нужно** — Supervisor сам ждёт устройство при старте аддона |
| Ручной `docker compose up` | `ha apps start <slug>` / UI |
| Пути `/mnt/data/...` | `/addon_configs/<slug>/` и `/share`, `/config` |
| Хостовый SSH (как на TrueNAS) | **недоступен** — SSH-аддон = Alpine-контейнер; debug-SSH 22222 только через флешку `CONFIG`. Привязка serial решается штатным `uart: true`, хостовый шелл не нужен |
> ✅ Плюс: проблема **udev-гонки на t610 снимается** — Supervisor управляет зависимостями и пробросом устройств. Это была самая опасная часть старого плана.
## Открытые вопросы
- [x]**РЕШЕНО 2026-09-14 — способ привязки CH340 в аддонах:** привязка по `/dev/serial/by-path/...`; механизм Supervisor — флаг `uart: true` в манифесте аддона (доступ ко всем serial автоматически, `devices:` не нужен). Проверено на живом t610. Детали: [[family/how-to/t610-access]] §USB.
- [x]**РЕШЕНО 2026-09-14 — куда переносить данные z2m:** `data_path` аддона = **`/config/zigbee2mqtt`** (внутри HA-конфига), НЕ `/addon_configs/`. Туда залиты `database.db` и `configuration.yaml`.
- [x]**Проверено 2026-09-14 — совместимость community-repo z2m с HA OS 18.2 / Core 2026.9.2:** работает (v2.14.1-1, координатор EmberZNet 7.4.5, 17 устройств).
- [x]**РЕШЕНО 2026-09-14 — `uart: true` для local add-ons:** подтверждено на mbusd/modbus-bridge — в их манифестах `uart: true`, by-path виден, устройства открываются (mbusd порт 502, bridge sniffer на шине ZONT). Тот же механизм, что у z2m и core_ssh.
- [x]**РЕШЕНО 2026-09-14 — modbus-bridge `ha_token`/`mqtt_password`:** вписаны, MQTT + HA-опрос работают. Ключевой момент — `ha.url` = `http://192.168.2.176:80` (не `supervisor/core`), + в HA добавлена MQTT-интеграция. **Этап 2 закрыт полностью.**
- [x]**РЕШЕНО 2026-09-14 — `custom_components` не переносим.** HACS (репо пусто) и `tuya_local` (нет config entry) не нужны; `localtuya` обслуживал единственную Tuya-розетку, которую Alex заменил на Zigbee → тоже снят.
- [x]**РЕШЕНО 2026-09-14 — история БД не переносится** (новая с нуля) и **реестры `.storage` — замена** (вариант B).
- [x]**ВЫПОЛНЕНО 2026-09-14 — Zigbee-розетка NEO NAS-WR01B (`0xa4c138eb6fbe9d19`) добавлена** через permit_join (MQTT). z2m = 16 устройств (после удаления мёртвого).
- [x]**ВЫПОЛНЕНО 2026-09-14 — Этап 3 (перенос конфига):** бэкапы сделаны, HA остановлен, залиты 12 файлов `.storage` + 5 конфигов + `www/`, HA запущен (**248 сущностей, 11 зон, MQTT-интеграция цела, ошибок нет**). `modbus.host``127.0.0.1`, `localtuya: debug` убран, дашборд поправлен.
- [x]**РЕШЕНО 2026-09-14 — `Mini Smart Switch 1` удалён из z2m** (`force: true`). Мёртвое: `lastSeen` 2026-01-26, `unavailable`, нет зоны, нигде не используется.
- [x]**УТОЧНЕНО 2026-09-14 — механизм связи сущностей:** HA связывает по `unique_id`, НЕ по `entity_id`. Смена `friendly_name` в z2m сохраняет человеческие `entity_id`. Прежнее опасение «всё пересоздастся» снято.
- [ ]**ОСТАЛОСЬ (последний шаг Этапа 3):** прописать `friendly_name` в z2m по таблице из §«КРИТИЧЕСКОЕ ОТКРЫТИЕ» (16 устройств), перезапустить z2m, убедиться, что `unknown`-сущности ожили (ожидаемо ~133 → минимум). Затем чистка hex-остатков (58 шт.) в HA.
- [x]**ВЫПОЛНЕНО 2026-09-14 (поздняя сессия) — автоматизации починены:** `device_id` перемаплены (9/9, 20 вхождений), hex-`entity_id` `light.0xa4c13882a4b42db0``light.bed_dimmer`. Результат: **16 автоматизаций, 15 `on` + 1 `off`, 0 `unavailable`**. Этап 3 закрыт.
- [x]**УСТРАНЕНО 2026-09-14 — HTTP-варнинг:** блок `http:` удалён из `configuration.yaml`, `trusted_proxies`/`use_x_forwarded_for` перенесены в `.storage/http`.
- [x]**ИСПРАВЛЕНО 2026-09-14 — modbus.host:** `127.0.0.1``192.168.2.176`.
- [x]**ИСПРАВЛЕНО 2026-09-14 — modbus-bridge 404:** hex-entity_id в коде аддона → человеческие + `ha addons rebuild`.
- [x]**ВЫПОЛНЕНО 2026-09-14 (поздняя сессия) — Zigbee-розетка `boiler_controller_power` добавлена** (`0xa4c1381694217e10`, TS011F, зона Котельная) как питание контроллеров котлов; 13 сущностей переименованы из hex. **Итого z2m = 15 устройств, реестр = 332 сущности, hex = 0.**
- [ ] ⏸️ **ГЛАВНЫЙ ОСТАВШИЙСЯ БЛОКЕР (аппаратный, за Alex):** 32 заслонки вентиляции `unavailable` — шина не отвечает (exception 0x0B). Подключить линии A/B к CH340 #2 (`by-path ...0:4:1.0-port0`, slave 11). Конфиг верный, оживут сами.
- [ ] Камера (§8 родительского плана) — не аддон, разбираться отдельно
- [x]**ВЫПОЛНЕНО 2026-09-14 (конец сессии) — ЗОНЫ восстановлены у 18 устройств.** `area_id` теряется при ре-регистрации устройств (как `device_id`) — все 14 zigbee были без зон. Проставлены по эталону с TrueNAS по `identifiers[0][1]` (`zigbee2mqtt_<ieee>`). Скрипт: `~/tmp-t610/etap3-fix/set_all_areas.jq`.
- [x]**ИСПРАВЛЕНО 2026-09-14 (конец сессии) — office-переключатель:** hex-`entity_id` в **триггерах** автоматизаций (`switch.0xa4c13873b5c1575b_l1/l2``switch.office_table_light_switch_l1/l2`); реле выведены из `unknown` нажатием кнопок. Цепочка управления светом проверена на живом.
- [x]**УСТАНОВЛЕНО 2026-09-14 (конец сессии) — z2m не публикует состояние пассивно:** реле «залипают» в `unknown` до первого события. Живость проверять по `lastSeen` в `database.db` (JSON-lines, читать построчно).
- [x]**ПРОВЕРЕНО ЭКСПЕРИМЕНТОМ 2026-09-14 — `unknown` возвращается после рестарта HA.** Зафиксировано состояние → рестарт → состояние снято: всё `on`/`off` слетело в `unknown`. `retain: true` + `cache_state*` в z2m стоят, но retained на топиках устройств фактически не публикуется (контроль: `bridge/state` retained есть). Питфолл: `mosquitto_sub -R` в аддоне врёт. См. §«ДОБАВЛЕНО В КОНЦЕ СЕССИИ» ③‑бис.
- [x]**ВЫПОЛНЕНО 2026-09-14 (конец сессии, после объяснения Alex) — `not_from: [unavailable, unknown]` УБРАН** из триггеров `office_pass_switch_table`/`office_pass_switch_main`. Кнопка срабатывает **с первого нажатия после рестарта**; проверено на живом toggle'ом через z2m на обоих каналах. Бэкап `/config/automations.yaml.bak-20260914-131541`.
- [ ]**ОТКРЫТО (низкий приоритет):** выяснить, доходит ли cached-стейт из `/homeassistant/zigbee2mqtt/state.json` до HA **до** первого события от устройства. Если да — `unknown` после рестарта исчезнет вовсе, и защиту `not_from` можно вернуть в узком виде (только против фантомного переключения). Метод: рестарт HA + снятие состояния каждые 2 с.
- [ ] ⚠️ Незакреплённое: `sensor.0xa4c138f8da8bc478_voltage/energy/power/current` (11 hex-сущностей розетки Насос обратки) — проживут ли под новым `friendly_name` или создадутся дубли; проверить после шага 8.
## Связанные заметки
- [[family/plans/home-automation-migration-t610]] — родительский план
- [[family/how-to/home-automation]] — карта slave ID, ZONT, регистры
- [[family/how-to/t610-access]] — доступ к хосту, CLI, карта USB, питфоллы
- [[family/how-to/zont-modbus-bridge-udev-race-protection]] — старая проблема гонки udev (на аддонах неактуальна)
- [[family/how-to/truenas-infrastructure]] — текущий docker-стек TrueNAS