Files
obsidian-vault/family/plans/t610-home-automation.md
T

874 lines
84 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.
# t610 — домашняя автоматизация (HA OS)
> **Единственный рабочий документ по переносу домашней автоматизации с TrueNAS на HP t610.**
> Заменяет три прежних доки (`home-automation-migration-t610`, `t610-addons-deployment`, `t610-access`) — сведены сюда 2026-09-14.
> Общий хост/доступ к TrueNAS: [[family/how-to/truenas-access]]. Карта Modbus slave/регистров: [[family/how-to/home-automation]].
---
## 1. Состояние на 2026-09-14
**Этап 1 ✅ · Этап 2 ✅ · Этап 3 ✅ ЗАКРЫТ.** Остался Этап 4 (переключение трафика и отключение TrueNAS).
| Что | Факт |
|---|---|
| HA | `http://192.168.2.176` (**порт 80, не 8123!**) — HTTP 200 |
| Реестр HA | **332 сущности**, hex-имён **0** |
| Зоны | 11 зон, **18 устройств** с зонами |
| Автоматизации | **16 шт.: 15 `on` + 1 `off`**, `unavailable` — 0 |
| Zigbee (z2m) | **15 устройств**, координатор EmberZNet 7.4.5 |
| Аддоны | `core_ssh`, `core_mosquitto`, `a0d7b954_nodered`, `45df7312_zigbee2mqtt`, `local_mbusd`, `local_modbus-bridge` — все `started` |
| modbus-bridge | MQTT + HA-опрос работают (без 404) |
**Не работает / не доделано:**
| Что | Состояние |
|---|---|
| **44 сущности `unavailable`** | ~32 заслонки (`switch.intake_damper_*`, `exhaust_damper_*`) + вентиляторы (`switch.fan_3_*`, `sensor.fan_at2_*`). **Диагностика 2026-09-14 (позднейшая) — см. §5.** Итог: ① фикс опций mbusd **провалился** (`maxconn 16` сломал аддон → откат); ② замер 30 запросов → 76% `EXC 0x0B`, но это **АРТЕФАКТ замера** (мерил параллельно с опросом HA); ③ **эталон TrueNAS идентичен t610** → причина НЕ конфиг; ④ гипотеза «два мастера» **ОПРОВЕРГНУТА**; ⑤ **🔴 физический тест Alex'а: приборы сидят на ДРУГИХ гнёздах** — ZONT = гнездо 4, вентиляция = гнездо 3, т.е. `mbusd` (гнездо 4) обслуживает **ZONT-шину**, а `modbus-bridge` (гнездо 3) — **вентиляцию** (см. §5 «ГЛАВНОЕ ОТКРЫТИЕ»). **Не закрыто:** `verify` (`state_on:1/state_off:0` vs реальный `0x640001`) + сверить привязку с Alex |
| **`sensor.fan_at2_*` — сироты** | В `configuration.yaml` **все `sensors:` закомментированы** → эти сущности физически не могут получать данные. Разобраться с происхождением (2026-09-14 поздняя) |
| `switch.sauna` = `unknown` | Розетка физически отключена (`lastSeen` 8+ ч) — не баг |
| ZONT не перенаправлен | MQTT-редирект GPON-роутера ещё смотрит на TrueNAS |
| Камера | Контейнер был на TrueNAS, остановлен при восстановлении пула. В Caddy остался мёртвый upstream `192.168.2.197:8090`. Разбираться отдельно |
---
## 2. Хост и доступ
| Параметр | Значение |
|---|---|
| Железо | HP t610 (AMD T56N 2×1.65 ГГц, 4 ГБ DDR3, HDD WD2500BEVT 228.5 ГБ, занято 5 ГБ) |
| ОС | HA OS 18.2 (generic-x86-64), Core 2026.9.2, Supervisor 2026.09.0 |
| IP | **192.168.2.176** (DHCP-имя `homeassistant`, MAC `9c:8e:99:ef:3f:c5`). Static IP на роутере **не закреплён** |
| Web UI | **`http://192.168.2.176`** — порт **80**. Порт 8123 закрыт |
| SSH | `ssh -i ~/.ssh/id_rsa root@192.168.2.176` — только через аддон `core_ssh` (порт 22) |
**Про SSH:** в HA OS SSH выключен по умолчанию. Включается аддоном `core_ssh` (Terminal & SSH): положить публичный ключ в `authorized_keys`. Пароля root не существует, вход только по ключу.
**Хостовый SSH (debug 22222) — не нужен.** Включить по сети нельзя: `ha host` не имеет ssh-команд, Supervisor API `/host/services/ssh` → 403 (роль аддона `manager`), только флешка с меткой `CONFIG`. Привязка serial решается штатным `uart: true`, хостовый шелл не требуется.
**Ограничения SSH-аддона:** нет `docker` CLI и нет `python3`. Есть `bash`, `curl`, `jq`, `ha`. **Скрипты для t610 писать на bash + jq.**
### Полезные команды `ha`
```bash
ha info # общая информация
ha core info # состояние HA Core
ha apps # список аддонов + состояние
ha apps info <slug> # детали аддона (options, схема)
ha apps start|stop|restart <slug>
ha apps logs <slug> # логи аддона (ТЯЖЁЛАЯ команда — не в цикл!)
ha store add <url> # добавить репозиторий
ha hardware info # железо + USB (tty, serial)
ha host info # диск, версия OS
ha supervisor logs | tail -60 # диагностика сборки local add-on
```
> ⚠️ `ha apps` **не умеет менять опции** — только через UI или Supervisor API (см. §8).
> ⚠️ `ha apps logs <slug>` тяжёлый: вешает цикл ожидания на минуты. Ждать готовности по `database.db`/`state.json`, не грепать логи в `while`.
### Роутеры (для диагностики сети)
| Роутер | Доступ | Особенность |
|---|---|---|
| `192.168.2.2` (OpenWrt, основной) | SSH root, пароль `1316261` | DHCP-аренды: `cat /tmp/dhcp.leases` |
| `192.168.6.1` («Rasputin», OpenWrt aarch64) | SSH root | eth0 `192.168.2.157/24` — видит сеть 192.168.2.x |
> ⚠️ **`nc` на OpenWrt (busybox) НЕ поддерживает `-z`** — молча печатает usage и даёт ложный вывод «порт закрыт». Для проверок с роутера — `curl`/`wget`. С Mac `nc -z` работает.
> 🔑 **Важно для диагностики:** весь трафик из локалки 192.168.2.x идёт через eth0 роутера Rasputin → **в логах удалённых сервисов источник выглядит как `192.168.2.157`**, даже если запрос делаешь ты сам с Mac. Не принимать это за «постороннего клиента».
---
## 3. USB-устройства (карта зафиксирована 2026-09-14)
> 🔴🔴 **ВНИМАНИЕ: привязка «гнездо ↔ прибор» в таблице НИЖЕ ОКАЗАЛАСЬ НЕВЕРНОЙ** (опровергнуто физическим тестом Alex'а 2026-09-14, см. §5 «ГЛАВНОЕ ОТКРЫТИЕ»): **ZONT = гнездо 4**, **вентиляция = гнездо 3**. То есть в колонке «Порт» ZONT и Вентиляция **поменяны местами**. Таблица ниже — как было записано ранее (по `dmesg`/`by-path`, без физической проверки). **Сверить перед следующим перетыканием.**
| Устройство | by-id | **by-path (рабочая привязка)** | tty | Порт (⚠️ см. предупреждение выше) |
|---|---|---|---|---|
| CH340 #1 | `usb-1a86_USB_Serial-if00-port0` | `pci-0000:00:12.0-usb-0:3:1.0-port0` | `/dev/ttyUSB0` | USB1 порт 3 → ~~ZONT~~ **вентиляция** |
| CH340 #2 | `usb-1a86_USB_Serial-if00-port0` ⚠️ тот же | `pci-0000:00:12.0-usb-0:4:1.0-port0` | `/dev/ttyUSB1` | USB1 порт 4 → ~~Вентиляция~~ **ZONT** |
| Zigbee Inswift ZBP-MG21 | `usb-Inswift_Zigbee_ZBP-MG21_535A000001-if00` | `pci-0000:04:00.0-usb-0:1:1.0` | `/dev/ttyACM0` | USB3 порт 1 |
> 🔴 **Правило проверки привязки = ФИЗИЧЕСКИЙ ТЕСТ.** `dmesg`/`by-path` показывают только «CH340 в порту 3 / в порту 4», но **не говорят, какой кабель к какому прибору идёт**. Надёжно — только выдернуть шнур и посмотреть, какое гнездо отвалилось: `dmesg | grep -iE "ch341|ttyUSB|disconnect" | tail`. (Именно так Alex установил правду за 1 минуту.)
### ⚠️ ГЛАВНЫЙ ПИТФОЛЛ: два CH340 неразличимы по by-id
Оба CH340 — `1a86:7523`, **`serial` пуст, `manufacturer` пуст, `product = "USB Serial"`** (одинаковые). Следствие: в `/dev/serial/by-id/` для двух адаптеров существует **ровно ОДИН симлинк** (`usb-1a86_USB_Serial-if00-port0 → ttyUSB1` — занял зарегистрировавшийся последним). **Ссылки на `ttyUSB0` через by-id нет вообще.**
```
by-id:
usb-1a86_USB_Serial-if00-port0 -> ../../ttyUSB1 ← один на два CH340
usb-Inswift_Zigbee_ZBP-MG21_535A000001-if00 -> ../../ttyACM0
```
**Вывод: привязка только по `by-path`.** Проверка серийников:
```bash
for d in 1-3 1-4; do echo -n "$d serial: "; cat /sys/bus/usb/devices/$d/serial 2>/dev/null || echo '(ПУСТО)'; done
```
> 🔴 **ОПАСНОСТЬ ТИХОГО СБОЯ:** перепутать кабели CH340 #1/#2 → **оба аддона поднимутся без ошибок**, но будут работать не с теми шинами. Внешне не проявится. Правило: перед перетыканием сверить с картой выше.
### 🆔 Различия t610 vs TrueNAS
- TrueNAS: `KERNELS=="?-1.5"` / `"?-1.6"` (другая топология USB).
- t610: `KERNELS=="1-3"` и `"1-4"` (порты 3 и 4 на OHCI `pci-0000:00:12.0`).
### ✅ РЕШЕНИЕ: `uart: true`, udev-алиасы не нужны
**Как на TrueNAS — нельзя.** Там был хостовый шелл → `/etc/udev/rules.d/99-tty-alias.rules`. SSH-аддон на t610 = Alpine-контейнер: нет `/etc/udev/rules.d`, нет `udevadm`.
**Рабочая схема — штатный флаг `uart: true`** в манифесте аддона: даёт контейнеру доступ ко **всем** serial-устройствам, включая `/dev/serial/by-id/` и `/dev/serial/by-path/`. Проверено на `core_ssh`, z2m, mbusd, modbus-bridge. **`devices:` прописывать не нужно** — проброс автоматический.
```
Zigbee (z2m): /dev/serial/by-id/usb-Inswift_Zigbee_ZBP-MG21_535A000001-if00
ZONT (modbus-bridge): /dev/serial/by-path/pci-0000:00:12.0-usb-0:3:1.0-port0 ← ⚠️ привязка СПОРНАЯ, см. §5
Вентиляция (mbusd): /dev/serial/by-path/pci-0000:00:12.0-usb-0:4:1.0-port0 ← ⚠️ привязка СПОРНАЯ, см. §5
```
> 🔴🔴 **Это то, что НАСТРОЕНО в аддонах** (и что НЕ менялось — проверено бэкапом опций). Но **физический тест Alex'а показал, что приборы сидят на других гнёздах** (ZONT = гнездо 4, вентиляция = гнездо 3) → **фактически `mbusd` обслуживает ZONT-шину, а `modbus-bridge` — вентиляцию.** Кто прав (дока или тест) — уточнить у Alex. См. §5.
> ⚠️ by-path привязан к физическому порту: CH340 #1 держать в порту 3, CH340 #2 в порту 4.
> ✅ **Плюс аддонов:** старая проблема гонки udev (см. [[family/how-to/zont-modbus-bridge-udev-race-protection]]) на t610 **неактуальна** — Supervisor сам ждёт устройство при старте аддона.
### Диагностика USB из аддона (udevadm НЕТ)
```bash
ls -la /dev/serial/by-id/ /dev/serial/by-path/ # все симлинки
lsusb ; lsusb -t # топология USB
for d in ttyUSB0 ttyUSB1 ttyACM0; do echo "$d -> $(readlink -f /sys/class/tty/$d/device)"; done
# атрибуты конкретного tty через sysfs
P=$(readlink -f /sys/class/tty/ttyUSB0/device)
for f in idVendor idProduct serial product manufacturer; do
v=$(cat "${P%/*}/$f" 2>/dev/null); [ -n "$v" ] && echo "$f = $v"
done
```
---
## 4. Аддоны: состав и рецепты
| Сервис | Slug | Источник | Состояние |
|---|---|---|---|
| Terminal & SSH | `core_ssh` | official | ✅ started (22) |
| Mosquitto broker | `core_mosquitto` | official | ✅ started (1883 MQTT, 1884 WS) |
| Node-RED | `a0d7b954_nodered` | community | ✅ started (1880) |
| File editor | `core_configurator` | official | ✅ started |
| Zigbee2MQTT | `45df7312_zigbee2mqtt` | community-repo | ✅ started (15 устройств) |
| mbusd | `local_mbusd` | local add-on | ✅ started (502) |
| modbus-bridge | `local_modbus-bridge` | local add-on | ✅ started |
| MQTT-интеграция в HA | `mqtt` (config entry) | — | ✅ добавлена 2026-09-14 |
| Samba share | `core_samba` | official | ⏸️ stopped (нужен пароль) |
**Репозитории:** official + Zigbee2MQTT (`https://github.com/zigbee2mqtt/hassio-zigbee2mqtt`) + Local apps.
### Ключевые решения (для входа в контекст)
| Решение | Что выбрано | Почему |
|---|---|---|
| Формат развёртывания | **HA-аддоны**, не docker-compose | из SSH-аддона host docker не виден; аддоны штатны и снимают udev-гонку |
| Источник z2m | community-repo | в официальном сторе z2m нет |
| mbusd / modbus-bridge | local add-ons (`/addons/...`) | кастомный код |
| Привязка CH340 | **by-path** | by-id у обоих идентичен |
| Как аддон видит serial | флаг **`uart: true`** | доступ ко всем serial, `devices:` не нужен |
| udev-алиасы | **отменены** | на HA OS невозможны |
| Хостовый шелл | **не нужен** | всё через Supervisor API |
### Сборка local add-on — структура и жизненный цикл
```
/addons/<slug>/
config.yaml ← манифест Supervisor (name, version, slug, arch, options, schema, uart, …)
Dockerfile
run.sh ← читает /data/options.json, готовит конфиг сервиса, exec сервиса
data/*.tmpl ← служебные шаблоны (ОБЯЗАТЕЛЬНО .tmpl, не .yml!)
```
```bash
ha store reload # подхватить /addons/* → local_<slug>
ha apps install local_<slug> # собрать образ (docker buildx) и поставить
ha apps start local_<slug>
ha apps logs local_<slug>
# при правке Dockerfile/манифеста:
ha apps uninstall local_<slug> && ha store reload && ha apps install local_<slug>
# при правке data/*.tmpl или *.py:
ha addons rebuild local_<slug> # БЕЗ ЭТОГО правки не применятся!
```
`<slug>` в URL = `local_<имя папки>`. Опции из UI кладутся в `/data/options.json` внутри контейнера.
**Питфоллы сборки (все ловились на живом):**
1. **`${BUILD_FROM}` пустой** → `base name (${BUILD_FROM}) should not be blank`. Либо `build.yaml` с `build_from: {amd64: …, aarch64: …}`, либо готовый образ (`FROM 3cky/mbusd:latest`) — тогда `build.yaml` не нужен.
2. **Supervisor рекурсивно парсит все `*.yml`/`*.yaml` в папке аддона** как манифесты → шаблон конфига даёт `Invalid app config!`. Фикс: расширение **`.tmpl`**.
3. **`ENTRYPOINT` базового образа перебивает `CMD`** → контейнер запускает бинарь напрямую, минуя `run.sh`. Фикс: `ENTRYPOINT []` + `CMD ["/bin/bash","/run.sh"]`.
4. **Пакета может не быть в Alpine** (`apk add mbusd``no such package`) — только готовый образ или сборка из исходников.
5. **Правка `data/*.tmpl` / `*.py` НЕ применяется без rebuild**`run.sh` берёт копию из образа (`Dockerfile: COPY data/config.template.tmpl /app/config.template.yml`).
6. **`uart: true`** обязателен для доступа к by-path. Для modbus-bridge дополнительно `host_network: true`.
7. В HA UI local add-ons требуют **Advanced Mode** в профиле (Settings → Apps). В HA 2026.x аддоны = **Settings → Apps** (пункта «Add-ons» нет).
8. Сборка идёт через `docker buildx` на хосте, тянет базовый образ, занимает минуты. Диагностика провала — `ha supervisor logs | tail -60`.
> 📌 Из SSH-аддона `/addons/` **виден** (`/addons/modbus-bridge`, без префикса `local_`).
> 📌 z2m `data_path` = **`/config/zigbee2mqtt`** (внутри HA-конфига, НЕ `/addon_configs/`).
### Состав local add-ons (что внутри)
**`local_mbusd`** — база готовый образ `3cky/mbusd:latest`, `uart: true`, порт `502/tcp`.
`run.sh` генерирует `/etc/mbusd/mbusd.conf` из опций и запускает `mbusd -d -L - -c`.
Опции: `device` = by-path CH340 #2 (порт 4), speed 9600, mode 8n1, trx_control `addc`, timeout 1000, retries 3.
> ⚠️ **Возможная причина 44 `unavailable`:** timeout 1000 мс мал для реле-модулей заслонок. Пробовать 3000 мс / retries 1.
**`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`, генерирует `/app/config.yml` из шаблона, экспортит env и запускает `modbus_ha_bridge.py`.
Опции: `device` = by-path CH340 #1 (порт 3), baudrate 9600, `ha_token` (183 симв.), `mqtt_user` = `zont`, `mqtt_password`.
> ⚠️ `ha.url` **обязан** быть `http://192.168.2.176:80` — НЕ `http://supervisor/core` (тот требует `SUPERVISOR_TOKEN`, с пользовательским токеном → 401).
---
## 5. Modbus: шина вентиляции и ZONT
### Данные из конфига HA (`configuration.yaml`)
```yaml
modbus:
- name: rtu_bus
type: tcp
host: 192.168.2.176 # ⚠️ НЕ 127.0.0.1 — см. питфолл ниже
port: 502
sensors:
- slave: 11, address: 5, write_type: holding, command_on: 256, command_off: 512
verify: {input_type: holding, address: 5, state_on: 1, state_off: 0}
- slave: 11, address: 7
- slave: 11, address: 8
# slave 12 — вторая группа заслонок (кабинет, север, вытяжки)
```
**Заслонки сидят на slave 11 и 12.** Рабочие регистры — **5, 7, 8** (и подобные), НЕ 0.
### 🔴 ПИТФОЛЛ (КРИТИЧНЫЙ): `modbus.host` не может быть `127.0.0.1`
HA Core в своём контейнере (`172.30.32.1`), `local_mbusd` — в другом, порт проброшен на хост. Для HA `127.0.0.1` = он сам → таймаут, все damper'ы `unavailable`.
**Фикс:** `host: 192.168.2.176`.
> ⚠️ Признак неверного адреса в логе mbusd: `conn_open(): accepting connection from 172.30.32.1` + мгновенный `conn_close()`. Успешный коннект — `from 192.168.2.176` **без** последующего `conn_close` (HA держит соединение).
### 🔬 Как проверять шину ПРАВИЛЬНО
**Главная ошибка:** слать запрос на **reg 0**. У заслонок рабочие регистры — 5/7/8. Сначала смотреть адреса в конфиге HA.
```python
import socket, struct
def rd(slave, addr, qty=1, timeout=4):
pdu = struct.pack('>BHH', 3, addr, qty)
mbap = struct.pack('>HHHB', 1, 0, len(pdu)+1, slave)
s = socket.create_connection(('192.168.2.176', 502), timeout=timeout)
s.sendall(mbap+pdu); r = s.recv(256); s.close()
return 'OK '+r[9:].hex() if len(r)>=9 and not (r[7]&128) else 'EXC 0x%02X' % r[8]
```
Либо чистым TCP без python:
```bash
printf '\x00\x01\x00\x00\x00\x06\x0b\x03\x00\x05\x00\x01' > /tmp/mbreq.bin
nc -w 5 192.168.2.176 502 < /tmp/mbreq.bin | xxd
```
**Расшифровка ответа:**
| Ответ | Значение |
|---|---|
| `… 01 03 02 XXXX` | ✅ нормальный ответ (данные) |
| `… 83 04` | ❌ SLAVE DEVICE FAILURE — устройство есть, не ответило |
| `… 83 0b` | ❌ **GATEWAY TARGET DEVICE FAILED TO RESPOND** — mbusd передал, устройство молчит |
### ✅ Факт проверки 2026-09-14: шина РАБОТАЕТ
Прежняя запись «аппаратный блокер: линии A/B не подключены, за Alex» — **ОШИБОЧНА**. Прямые запросы дали живые ответы:
| Slave | Что (см. [[family/how-to/home-automation]]) | Ответ |
|---|---|---|
| **11** | Relay module — заслонки | reg 5 → ✅ `OK 640001`, reg 8 → ✅ `OK 00` |
| **12** | Relay module — заслонки 2 | reg 1 → ✅ `OK 640001` |
| **10** | Vent control (AT2) | ✅ `OK` (значение 100) |
| **2, 3** | датчики Детская / Спальня | ✅ `OK` |
| **20** | Газ котёл вкл | ✅ `OK` |
Ответы **нестабильны** (иногда `EXC 0x0B` вместо `OK`) — вероятно из-за короткого `timeout 1000 мс` в mbusd.
⚠️ **Ложный след, который привёл к ошибке:** `conn_open` от `192.168.2.157` в логе mbusd был принят за «постороннего клиента, забивающего слоты mbusd». На самом деле `.157` — роутер Rasputin, через который ходит сам Mac (см. §2). Плюс запросы слались на reg 0 вместо 5/7/8.
**Что осталось:** выяснить, почему HA держит 44 `unavailable`, хотя mbusd отдаёт данные. **Смотреть лог HA** (`homeassistant.components.modbus`, `pymodbus`), не лог mbusd.
### 🔴 ДИАГНОСТИКА 2026-09-14 (поздняя): ТРИ реальные причины `unavailable`
Гипотеза «короткий `timeout 1000 мс`» подтвердилась лишь частично. Лог HA вскрыл **шторм параллельных коннектов**, а прямой опрос — **нестабильность регистров** и **неверный `verify`**.
**Причина 1 — 🔴 ШТОРМ параллельных TCP-коннектов HA (главная).**
В логе HA при старте: `Something is blocking Home Assistant from wrapping up the start up phase` + **~28 одновременных задач** `ModbusBaseEntity.async_local_update()` (файл `/usr/src/homeassistant/homeassistant/components/modbus/entity.py:114`, `call_later 15.0`).
Следствие: HA открывает **новое TCP-соединение на каждый опрос сущности**, все параллельно, и сразу бросает.
Подтверждение — `netstat -an` на t610: **5 соединений в `TIME_WAIT`** с `172.30.33.0:*` (адрес контейнера HA Core) → `192.168.2.176:502`.
При `mbusd maxconn: 8` и ~28 опросах параллельно — **соединения упираются в лимит и отваливаются по таймауту**.
Признак в логе mbusd — **пачки рваных сессий**: `conn_open` → мгновенный `conn_close`, по 20+ подряд с интервалом 1–4 с.
> ⚠️ **ВАЖНО:** источник коннектов в логе mbusd = `192.168.2.157` (роутер Rasputin, NAT — см. §2), НЕ `192.168.2.176`. Это **нормально**, не «посторонний клиент». Но `netstat` внутри t610 показывает реального клиента — `172.30.33.0` = контейнер HA Core.
> 📌 **Различие коннектов:** успешный коннект HA держит соединение (без `conn_close`); при шторме — мгновенные `close`. Но и при здоровой работе HA может открывать/закрывать по опросу — судить по НАЛИЧИЮ `Time_WAIT`-пачек и загрузке `maxconn`, не по одному `conn_close`.
**Причина 2 — 🔴 Регистры отвечают НЕСТАБИЛЬНО (не таймаут).**
Прямой опрос 2026-09-14 (поздняя), `nc` + `printf`-запрос:
```
slave 11 reg 5 -> … 0b 83 0b ← ❌ EXC 0x0B (GATEWAY TARGET DEVICE FAILED TO RESPOND)
slave 11 reg 7 -> … 03 02 640001 ← ✅ OK
slave 11 reg 8 -> … 0b 83 0b ← ❌ EXC 0x0B
slave 12 reg 1 -> … 0c 83 0b ← ❌ EXC 0x0B
slave 12 reg 5 -> … 03 02 640001 ← ✅ OK
```
**Каждый раз отвечают РАЗНЫЕ регистры** (в прошлый замер 2026-09-14 было наоборот: reg 5 ✅, reg 8 ❌). Устройство на шине отвечает через раз → это **аппаратная/шинная нестабильность реле-модулей**, а не конфиг.
> ⚠️ Формат сырого запроса (`printf '\x…'` + `nc`): MBAP `00 01 00 00 00 06 <slave> 03 <addr_hi> <addr_lo> 00 01`.
> ⚠️ Питфолл bash: `printf '\\x…'` внутри скрипта, отправляемого через `scp` + `bash` — экранирование `\x` **удваивается** при передаче в одинарных кавычках. Проверять вывод `xxd -p`, а не доверять «красивой» команде из доки.
**Причина 3 — 🔴 `verify` в `configuration.yaml` физически не может сойтись.**
Конфиг (строки 96–…, `configuration.yaml`):
```yaml
switches:
- name: intake_damper_dining_right_0
unique_id: intake_damper_dining_right_0
slave: 11
address: 5
write_type: holding
command_on: 256
command_off: 512
verify:
input_type: holding
address: 5
state_on: 1 # ⚠️ HA ждёт ровно 1
state_off: 0 # ⚠️ HA ждёт ровно 0
```
HA **пишет** 256/512 в reg 5, а **читает** из того же reg 5 и ждёт `1`/`0`. Но в живом регистре лежит `0x640001` (не `1`), либо приходит `EXC 0x0B`.
**Следствие: `verify` не сходится НИКОГДА → сущность `unavailable` навсегда, даже при живом регистре.**
> 📌 Untested fix-варианты: ① убрать `verify` вовсе (доверять команде, не перечитывать); ② выставить `state_on`/`state_off` под реальное значение регистра; ③ `verify` только по «изменилось/нет» без сверки с 1/0. Выбирать после стабилизации шины (причина 1+2).
**Состояние блока `configuration.yaml` (строки 16+):**
- `modbus:``rtu_bus`, `type: tcp`, `host: 192.168.2.176`, `port: 502`.
- **`sensors:` — ВСЁ закомментировано** (slave 10 AT2 fans ×4 + `temp_3`/slave 102).
- **`switches:` — активны только заслонки slave 11** (адреса 5,7,8,9,11,12,13,14,…); **весь блок slave 10 (Fan 3 High/Medium/Low) закомментирован** строкой `# slave 10 (AT2 fans) not responding on vent bus — commented out to unblock damper polling`.
> ⚠️ **Активных `sensors:` в `modbus:` НЕТ ВООБЩЕ.** Значит сущности `sensor.fan_at2_*` в реестре — «сироты» от старого конфига/другого источника, они не могут получить данные по определению. Проверить их происхождение (возможно, template-сенсоры или остатки `modbus.sensor`).
**Опции `local_mbusd` (на момент диагностики):**
```json
{"device":"/dev/serial/by-path/pci-0000:00:12.0-usb-0:4:1.0-port0","speed":9600,"mode":"8n1",
"trx_control":"addc","maxconn":8,"wait":500,"pause":100,"retries":3,"timeout":1000}
```
### 🔴🔴 РЕЗУЛЬТАТ ПОПЫТКИ ФИКСА 2026-09-14 (позднейшая сессия) — maxconn СЛОМАЛ mbusd
**Итог: фикс применён, сломал mbusd, откачен. Причина `unavailable` НЕ опции, а коллизия двух мастеров (гипотеза, требует подтверждения).**
**Что сделано:**
1. Бэкап: `/config/mb-fix-backup-20260914-145419/` (`mbusd-options.json`, `configuration.yaml`).
2. Правка через Supervisor API: `timeout 1000→3000`, `retries 3→1`, `maxconn 8→16` → POST `{"result":"ok"}``ha apps restart local_mbusd`.
3. **mbusd упал → `state: "error"`.** Лог:
```
[mbusd] conf written:
maxconn=16
wait=500
...
mbusd: can't read config file /etc/mbusd/mbusd.conf: error at line 13:
```
4. **Откат** к рабочим (`timeout 1000`, `retries 3`, `maxconn 8`) → mbusd снова `started`, HA подключился (`conn_open from 192.168.2.176`).
> 🔴 **ПИТФОЛЛ (КРИТИЧНЫЙ): `maxconn` НЕ ТРОГАТЬ.** Генератор конфига в `run.sh` аддона `local_mbusd` собирает `mbusd.conf` так, что при `maxconn=16` (двузначное) ломается разметка файла → `error at line 13` → mbusd не стартует. Значения `maxconn=8` (однозначное) хватало. Есть подозрение, что дело именно в **двузначном** числе/отсутствии перевода строки в шаблоне. **Правило: `maxconn` не менять. Остальные опции (`timeout`, `retries`) — можно, проверять отдельно.**
> ⚠️ Симптом провала аддона: `ha apps info local_mbusd --raw-json | jq -c '.data.state'` → `"error"`. Смотреть `ha apps logs local_mbusd | tail`.
> ✅ **Откат рабочий рецепт:** POST опций `{timeout:1000, retries:3, maxconn:8}` → `ha apps restart local_mbusd` → ждать ~15 с → `state` должен стать `"started"`.
**Кто на самом деле опрашивает шину (объективный замер 30 запросов):**
```
30 запросов подряд, slave 11 reg 7 → OK=7 FAIL=23 (76% потерь, EXC 0x0B)
30 запросов подряд, slave 11 reg 5 → OK=7 FAIL=23
```
и среди ответов попался **чужой ответ на запрос, которого я не слал** (`…060b 01 00000001`) — признак, что **на шине есть второй мастер/источник трафика**.
**❌ ГИПОТЕЗА «два Modbus-мастера на одной RS-485» — ОПРОВЕРГНУТА (2026-09-14, позднейшая).**
Alex подтвердил: **адаптеры физически переключены в t610**, TrueNAS от шины отключён. Значит второго мастера нет — ниши TrueNAS-HA/TrueNAS-mbusd не висят на паре A/B. Проверено дополнительно: `192.168.2.197:502` (TrueNAS mbusd) — **CLOSED**, TrueNAS-mbusd не отвечает.
> 🔴 **Урок: не строить гипотезу о «втором мастере», не сверившись с Alex про физику.** Он знает, куда переткнуты кабели. Спрашивать про физику ДО теории.
**Что тогда даёт 76% потерь?** Остаётся **самомерие агента**: замер `mb_stress.sh` шёл **параллельно с опросом HA** — HA в этот момент долбит ту же шину, mbusd `maxconn 8`, запросы агента конкурируют с запросами HA → `EXC 0x0B`. То есть **76% — артефакт замера, а не поломка**. Прямой замер `nc` **при остановленном HA** (тест `ha core stop`, см. ниже) показал, что шина отвечает — реле живое.
> ⚠️ Чтобы мерить честно: либо останавливать HA-опрос, либо принимать во внимание, что HA — тоже мастер на этой шине и делит её с агентом.
### ✅ ЭТАЛОН TRUENAS НАЙДЕН — modbus-блок ИДЕНТИЧЕН t610
Путь эталона (чинится без sudo, права 644): **`/mnt/RED_2TB/docker/ha/configuration.yaml`**
(⚠️ не `/mnt/RED_2TB/docker/homeassistant/` — та папка ПУСТА, `find` показывает реальный путь `/mnt/RED_2TB/docker/ha/`).
**Сверка построчная (2026-09-14 позднейшая):** `intake_damper_*` (dining right/left, kids, bedroom, office, north) + `exhaust_damper_*` (kitchen, bathroom, office, toilet_1, shower_2) — **32 записи, slave/address/`verify` СОВПАДАЮТ ВСЕ**. Закомментированная slave 10 (AT2 fans) — тоже идентична.
> ✅ **ВЫВОД: конфиг при миграции перенесён КОРРЕКТНО. Расхождений в `modbus:` между TrueNAS и t610 НЕТ.** Значит причина `unavailable` — **НЕ конфиг**. На TrueNAS работало при том же конфиге → разница только в транспорте/шине (два мастера).
> 📌 **Ключевая разница хостов:** на TrueNAS HA бил в **свой локальный mbusd** по `192.168.2.197:502`; на t610 HA (`172.30.32.1`) ходит в `192.168.2.176:502` (порт на хосте). Плюс **только один кабель** на физическую шину → возможны два мастера.
**Как снять эталон (команды):**
```bash
ssh truenas_admin@mallexxx.duckdns.org
find /mnt/RED_2TB/docker -maxdepth 3 -name "configuration.yaml" # → /mnt/RED_2TB/docker/ha/configuration.yaml
# список заслонок эталона:
awk '/^modbus:/{f=1} f' /mnt/RED_2TB/docker/ha/configuration.yaml \
| grep -E "^ - name:|^ slave:|^ address:" | paste - - -
```
### 📌 Сверка .157 — ПОВТОРНЫЙ урок (не путать)
`192.168.2.157` = **Rasputin, MAC `36:ae:87:04:08:dc`** (подтверждено по `dhcp.leases` роутера `192.168.2.2`). Под NAT Rasputin **выглядит ЛЮБОЙ трафик из локалки — включая запросы самого агента** (с Mac и из SSH-аддона). В логе mbusd **53 коннекта от `.157`** = это МОИ же диагностические запросы, **НЕ** посторонний клиент и **НЕ** TrueNAS.
> 🔴 **Правило: по IP `.157` НЕЛЬЗЯ определить, кто клиент.** Для этого — `netstat` ВНУТРИ t610 (покажет `172.30.33.0` = контейнер HA Core) или смотреть на роутере.
**Рабочие файлы этой сессии:** `~/tmp-t610/mbdiag1..4.sh`, `mb_backup.sh`, `mb_fix_opts.sh`, `mb_rollback.sh`, `mb_dump_t610.sh`, `mb_stress.sh`, `mb_master_test.sh`, `mb_bus_check.sh`, `mb_bus_listen.sh`, `mb_bridge_check.sh`, `mb_zont.sh`, `mb_zont2.sh`, `mb_who.sh`.
### 🔴 ПИТФОЛЛ: `ha core stop` из SSH-сессии
Тест «остановить HA → померить шину» через `ha core stop` в SSH-скрипте **может повиснуть** (прошлый прогон — таймаут 300 с, вывод потерян). HA при этом **останавливается и потом поднимается сам**, но результат теста не долетает.
**Последствия, которые видны в логах:** пока HA был остановлен, `modbus-bridge` залил лог штормом
```
HA poll exception ... [Errno 111] Connection refused
HA poll: sensor.office_temperature_sensor_temperature HTTP 404
HA poll exception ... could not convert string to float: 'unknown'
```
— это **нормальный** след остановки HA, не поломка bridge.
> ✅ Правило: после `ha core stop` в скрипте — **обязательно проверять живость** (`curl -s -o /dev/null -w '%{http_code}' http://192.168.2.176/` → `200`), не полагаться на вывод зависшей команды. Не оставлять HA остановленным.
### ZONT (шина на CH340 #1)
ZONT (`192.168.0.10`) — Modbus master на RS-485 `ttyZONT`. 485-датчики: Гостиная=1, Детская=2, Спальня=3. `modbus-bridge` сниффит их и подставляет виртуальные slaves 101/102/103 → ZONT опрашивает как внешние датчики. **Если modbus-bridge не слушает шину → ZONT показывает датчики «недоступными».**
На TrueNAS была гонка udev (docker стартовал раньше udev, `/dev/ttyZONT` не существовал → контейнер падал с Exit 128, restart policy не ретраит при провале mount устройства). Лечилось POSTINIT-скриптом ожидания tty. **На t610 неактуально** — Supervisor сам ждёт устройство.
#### 🔬 ПРОВЕРКА 2026-09-14 (позднейшая): шина ZONT ПУСТАЯ
Задача от Alex была конкретная: **видит ли `modbus-bridge` данные с ZONT-шины / идут ли запросы от ZONT?**
**Результат: НЕТ. Ни то, ни другое.**
| Проверка | Как делалось | Результат |
|---|---|---|
| Шина ttyUSB0 (гнездо 3) | `cat /dev/ttyUSB0` 1015 сек | **0 байт** — тишина |
| Лог `modbus-bridge` | `ha apps logs local_modbus-bridge` | **ни одной строки о данных с шины**; только `HA poll -> sensor.office_temperature_sensor = 23.9` по кругу |
| MQTT `modbus/#` | `mosquitto_sub -t 'modbus/#'` 10 сек | **пусто** — bridge не публикует виртуальные датчики 101/102/103 |
**Что реально делает `modbus-bridge` (из лога):** на старте публикует discovery «вслепую» (Dining/Kids/Bedroom CO2/Temp/Humidity), затем крутит `HA poller: polling 1 entities every 15 s` — **опрашивает HA, а не шину**. Строк вида «получен запрос ZONT / slave 1|2|3 / sniff» в логе **ноль**.
> 🔴 **ПИТФОЛЛ ДИАГНОСТИКИ: `cat /dev/ttyUSB0` при работающем `modbus-bridge` покажет ПУСТО — bridge держит порт, и `cat` его не получит.** Чтобы слушать шину сырьём, bridge надо остановить (`ha apps stop local_modbus-bridge`), послушать, потом вернуть. **Но `cat` всё равно не отличает «ZONT молчит» от «порт занят»** — надёжнее смотреть, что bridge ПУБЛИКУЕТ в MQTT (если ZONT-запросы идут, bridge отдаёт `modbus/sensors/...`).
**Вывод:** запросов от ZONT на шине нет. Причины (обе — физика/настройка ZONT, не код t610; из t610 дальше не различаются):
1. RS-485 от ZONT физически **не подключён** к гнезду 3 t610 (переткнуты CH340-адаптеры, но сам ZONT-контроллер остался на старой линии).
2. Подключён, но **ZONT не является мастером** на этой линии (сбит/выключен Modbus-master после отключения TrueNAS).
> 📌 Проверить, какая из двух — можно **только с энкодера ZONT** или прозвоном линии. С хоста t610 это неразличимо. **Alex не просил идти на ZONT конфигурировать.**
> ⏳ ZONT ещё не перенаправлен на MQTT t610 — редирект GPON-роутера смотрит на TrueNAS.
#### 🔴🔴 ГЛАВНОЕ ОТКРЫТИЕ (2026-09-14, финал сессии): ZONT СИДИТ НА ГНЕЗДЕ 4, НЕ 3
**Физический тест Alex'а:** Alex **выдернул шнур ZONT** → в `dmesg` отключилось **гнездо 4**:
```
usb 1-4: USB disconnect, device number 3
ch341-uart ttyUSB1: ch341-uart converter now disconnected from ttyUSB1
ch341 1-4:1.0: device disconnected
```
Гнездо 3 (`usb 1-3 → ttyUSB0`) **осталось на месте** → значит отсоединился **не то, что докой называлось ZONT-ом**, а именно **гнездо 4**.
**Следствия:**
| Что | Факт из теста |
|---|---|
| ZONT физически | **гнездо 4** (`ttyUSB1`) |
| Вентиляция физически | **гнездо 3** (`ttyUSB0`) |
| `local_mbusd` настроен на | **гнездо 4** → значит **mbusd опрашивает ZONT-шину** |
| `local_modbus-bridge` настроен на | **гнездо 3** → значит **bridge слушает ВЕНТИЛЯЦИЮ** |
> 🔴 **В ДОКЕ ШИНЫ БЫЛИ ПЕРЕПУТАНЫ МЕСТАМИ.** Всё, что раньше писалось как «шина вентиляции (mbusd, гнездо 4)» и «шина ZONT (bridge, гнездо 3)» — читалось **не с той стороны**. Отсюда ВСЁ замешательство сессии:
> - Агент слушал `ttyUSB0` и звал это «ZONT» → там **вентиляция**.
> - Агент смотрел лог `mbusd` и звал это «вентиляцией» → он опрашивает **ZONT-шину** (там реле 11/12 — да, они на линии ZONT/485).
> - `modbus-bridge` «ничего не публиковал» → потому что на гнезде 3 сидит **вентиляция**, а не ZONT.
>
> ⚠️ **НЕ подтверждено до конца:** какой именно шнур Alex считает «ZONT-шнуром» — надо уточнить у него. Но объективный факт `dmesg` (отключилось гнездо 4) — железный.
> ⏳ **Сразу после теста гнездо 4 (вентиляция/ZONT-линия) осталось ОТКЛЮЧЕННЫМ** — `mbusd` работает без устройства. **Шнур нужно воткнуть обратно.** (Проверить при следующей сессии: `ls /dev/serial/by-path/` должен показать `...usb-0:4...`.)
#### ⚠️ УСТАРЕВШЕЕ (опровергнуто тестом выше): «Гнёзда разведены правильно»
Ранее в этой же сессии было записано (по `dmesg`+by-path, БЕЗ физического теста):
```
ch341 1-3 → ttyUSB0 (гнездо 3 = ZONT) ← ОШИБКА
ch341 1-4 → ttyUSB1 (гнездо 4 = вентиляция) ← ОШИБКА
```
**Это опровергнуто физическим тестом Alex'а** (см. выше): ZONT = гнездо 4.
> 🔴 **УРОК ДИАГНОСТИКИ:** соответствие «гнездо ↔ устройство» **НЕЛЬЗЯ вывести из `dmesg`/`by-path`** — там видно только, что CH340 воткнут в порт 3 и порт 4, но **не видно, какой кабель к какому прибору идёт**. Единственный надёжный способ — **физический тест: выдернуть шнур и посмотреть, какое гнездо отвалилось в `dmesg`.** Это то, что Alex сделал за минуту там, где агент полчаса строил теории.
---
## 6. Zigbee (z2m)
### Данные и файлы
`data_path` = **`/config/zigbee2mqtt/`** (внутри HA-конфига):
| Файл | Что |
|---|---|
| `database.db` | база z2m (JSON Lines, **НЕ SQLite!**) |
| `configuration.yaml` | `network_key`, `pan_id`, `ext_pan_id`, `channel`, `serial`, `mqtt`, секция `devices:` с `friendly_name` |
| `coordinator_backup.json` | бэкап координатора |
| `log/<ts>/log.log` | логи |
| **`state.json`** | ⚠️ **здесь его НЕТ!** Живой кэш: **`/homeassistant/zigbee2mqtt/state.json`** (искать `find / -name state.json`) |
**Кэш состояний** — плоский JSON `{ieee: {param: value}}`, пишется при каждом событии:
```json
"0xa4c13873b5c1575b": { "state_l1": "ON", "state_l2": "ON" },
"0xa4c1381186ed1a32": { "state_left": "OFF", "state_right": "OFF" }
```
> ⚠️ **`database.db` — это JSON Lines**, не SQLite. `sqlite3` → `file is not a database`. Читать построчно через `jq`. В SSH-аддоне нет `sqlite3` — копировать на Mac (`scp`) и разбирать там.
> 📌 `modelID` в базе пуст, но `manufName` даёт модель Tuya (`_TZ3000_gjnozsaz` и т.п.).
### 15 устройств (friendly_name / роль / зона)
| 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, розетка греющего кабеля | Котельная |
| `0xa4c1381694217e10` | `boiler_controller_power` | TS011F, питание контроллеров котлов | Котельная |
**11 зон:** Гостиная `living_room`, Кухня `kitchen`, Спальня `bedroom`, Детская `detskaia`, Кабинет `kabinet`, Ванная `vannaia`, Душевая `dushevaia`, Туалет `tualet`, Северная `severnaia`, Котельная `kotelnaia`, Лестница `lestnitsa`.
### Спаривание нового устройства (permit_join через MQTT)
`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": 250}'
# слушать события
timeout 10 mosquitto_sub … -t 'zigbee2mqtt/bridge/event' -C 3
```
> ⚠️ **Лимит окна = 254 секунды.** `"time": 300` → `error: Cannot permit join for more than 254 seconds`. Ставить ≤250.
**Переименование после спаривания:**
```bash
mosquitto_pub … -t 'zigbee2mqtt/bridge/request/device/rename' \
-m '{"from":"0xa4c1381694217e10","to":"boiler_controller_power"}'
```
> ⚠️ **HA успевает создать сущности под hex-именем раньше, чем применяется rename.** Всегда проверять `core.entity_registry` на hex и переименовывать через jq (HA stop → правка → HA start).
**Зона для нового устройства** ставится в **`core.device_registry`** (поле `area_id`), НЕ в entity_registry:
```bash
jq -r '.data.devices[] | select((.identifiers|tostring)|test("<ieee_без_0x>")) | [.id,.name,.area_id] | @tsv' \
/config/.storage/core.device_registry
```
**Питфоллы спаривания:**
- `mosquitto_pub/sub` в аддоне **не поддерживают `--pwfile`** — только `-u`/`-P`, пароль через переменную из файла.
- Спаривание через MQTT требует запуска **изнутри аддона** (там есть `mosquitto_pub` и доступ к `core-mosquitto`).
- `ha apps logs` тяжёлый — не в цикл ожидания.
### Удаление мёртвого устройства
Признак: `state: unavailable`, в `state.json` записи нет, `lastSeen` давно.
```bash
jq -r --arg i "0xcc86ecfffe1347fd" 'select(.ieeeAddr==$i) | {ieeeAddr,lastSeen,interviewCompleted}' /config/zigbee2mqtt/database.db
cp /config/zigbee2mqtt/database.db /config/zigbee2mqtt/database.db.bak-$(date +%Y%m%d-%H%M%S)
mosquitto_pub … -t 'zigbee2mqtt/bridge/request/device/remove' -m '{"id": "0x…", "force": true}'
```
z2m сам убирает устройство из `database.db` и из секции `devices:`.
### 🔑 z2m не публикует состояние пассивно
z2m публикует payload **только при получении данных от устройства**. Питаемые реле не отчитываются сами — ждут события или запроса. Отсюда `unknown` после рестарта.
**Механизм восстановления — birth-message, а НЕ retain:**
- z2m имеет `homeassistant.status_topic: "homeassistant/status"`.
- Когда HA стартует, он публикует во `homeassistant/status` = `online`.
- z2m это видит → **переопубликовывает состояния всех устройств** → HA ловит → `unknown` уходит.
**Проверено экспериментом (рестарт HA + снятие состояния каждые 2 с):**
```
13:21:02 knopka_l1=on ← до рестарта
13:22:34 knopka_l1=unknown ← HA поднялся, состояний нет
13:23:35 knopka_l1=unknown ← держится
↓ ещё ~40 с
knopka_l1=on ← ✅ состояния пришли САМИ, без нажатий
```
> ⚠️ **Практическое следствие:** в окне между «HA поднялся» и приходом birth-message (десятки секунд) реле = `unknown`. Нажатие в это окно может не сработать. Ждать ~40–60 с после рестарта.
> ℹ️ Датчики (t°/освещённость/радар) выходят из `unknown` сами за секунды-минуты — страдают только реле и кнопки.
> ️ `retain: true` + `cache_state*` в z2m стоят, но **retained на топиках устройств фактически не публикуется** (`cache_state_send_on_startup` отдаёт состояние, пока HA ещё не подписался). Контроль: `bridge/state` retained **есть** → брокер умеет, дело не в нём.
> 🔬 **Питфолл диагностики retained:** флаг **`-R` у `mosquitto_sub` в аддоне ВРЁТ** — вывод пуст даже там, где retained есть. Надёжно: подписаться и смотреть, что прилетает **СРАЗУ при подписке**, с timestamp:
> ```bash
> timeout 4 mosquitto_sub … -v | while IFS= read -r l; do echo "$(date +%H:%M:%S.%N|cut -c1-12) | $l"; done
> ```
**Проверка живости узла без нажатия** — `get`-запрос: `mosquitto_pub … -t 'zigbee2mqtt/<name>/get' -m '{"state_l1":""}'`. Либо `lastSeen` в `database.db` (обновляется по любым пакетам):
```bash
while IFS= read -r line; do echo "$line" | jq -r 'select(.type=="EndDevice" or .type=="Router") | "\(.ieeeAddr)\t\(.lastSeen // "нет")"'; done < db.json
```
> ℹ️ Разные устройства публикуют **разные поля**: `office_table_light_switch` (TS0002) → `state_l1`/`state_l2`; `smart_light_office` (TS0012) → `state_left`/`state_right`. Discovery z2m генерирует верный `value_template` под каждое.
---
## 7. Перенос HA-конфига между инстансами — что ломается
Перенесено с TrueNAS 2026-09-14: `.storage` (12 файлов, выборочно) + 5 конфигов + `www/`. История БД (`home-assistant_v2.db`) — **не переносилась** (с нуля). `custom_components/` (hacs, localtuya, tuya_local) — **не переносился**.
### 🔴 ПИТФОЛЛЫ (все выучены дорого)
**1. `device_id` НЕ переносятся между инстансами.**
`device_id` — UUID, генерируемый HA при регистрации устройства **в конкретном инстансе**. MQTT-интеграция на новом хосте регистрирует устройства заново → выдаёт новые. Автоматизации с device-триггерами падают: `Unknown device '<uuid>'`.
**Лечение:** перемапить по `identifiers` (`["mqtt","zigbee2mqtt_<ieee>"]`):
```bash
jq -r '.data.devices[] | select((.identifiers|tostring)|contains("<ieee_без_0x>")) | [.id, (.name//"—")] | @tsv' /config/.storage/core.device_registry
```
Проверка битых ссылок: `ha core logs 2>&1 | grep "Unknown device"`.
**2. `area_id` (зона устройства) теряется так же.**
Устройства ре-регистрируются → новые записи без `area_id`. Проставить заново по эталону (`identifiers[0][1]` = `zigbee2mqtt_<ieee>`).
> 🔑 **Обобщение:** при переносе HA **теряются все инстанс-локальные привязки**: `device_id`, `area_id`. Переносится только то, что задано явными `id` в реестрах (`area_registry`, `floor_registry`).
**3. HA не переименовывает `entity_id` при смене `friendly_name`.**
Связь — по `unique_id` (у z2m `<ieee>_<param>_zigbee2mqtt`, не меняется). Смена `friendly_name` меняет топики и `default_entity_id` в discovery, но `entity_id` в реестре остаётся → переименовывать вручную (jq по `core.entity_registry`).
> ✅ **Плюс:** дубли **не создаются** — HA узнаёт сущность по `unique_id` и обновляет на месте. Автоматизации не ломаются.
**4. hex-`entity_id` внутри `automations.yaml`/`scripts.yaml` — НЕ обновляются автоматически.**
После переименования реестра (`hex → человеческие`) ссылки в автоматизациях остаются старыми.
**Чистить ОБА вида ссылок:** `device_id` (`device_id:\s*([0-9a-f]{32})`) **и** hex-`entity_id` (`[a-z_]+\.0x[0-9a-f]{16}`) — **включая обычные `platform: state`-триггеры**, не только actions.
**5. `core.config_entries` — только точечно.**
Виртуальные сущности (`switch_as_x`, `template`, helper'ы) ссылаются на entry в `core.config_entries`. Перенести целиком нельзя — потеряется локальная MQTT-интеграция. Решение: **добавить нужные entry точечно** (так добавили 4 `switch_as_x`). При добавлении — править `options.entity_id` с hex на человеческое имя.
**6. `.storage/` — НЕ копировать целиком.**
❌ НЕ трогать (система/идентичность t610): `core.uuid`, `auth`, `auth_provider.homeassistant`, `http`, `http.auth`, `onboarding`, `core.config`, **`core.config_entries`**.
✅ Переносить: `core.entity_registry` (критично!), `core.device_registry`, `core.area_registry`, `core.floor_registry`, `core.restore_state`, `lovelace.*`, `person`, `zone`, `core.logger`, `homeassistant.exposed_entities`.
**7. `http:` в `configuration.yaml` игнорируется после миграции.**
Варнинг: `YAML configuration is ignored after migration` → порт 80, настройки в UI (`.storage/http`).
⚠️ **Перед удалением YAML-блока сверить**, что все его ключи есть в `.storage/http` — иначе настройка молча теряется:
```bash
jq '.data.stable | {server_port, use_x_forwarded_for, trusted_proxies}' /config/.storage/http
```
Правку `.storage/http` делать **при остановленном HA** (`ha core stop`), иначе HA перезапишет.
**8. `not_from: [unknown]` в триггерах кнопок — ломает первое нажатие.**
```yaml
trigger:
- platform: state
entity_id: switch.office_table_light_switch_l1
not_from: [unavailable, unknown] # ← убрать: блокирует первое нажатие после рестарта
```
Защита задумана верно (при старте HA стейт `unknown`, затем устройство присылает реальный — без защиты свет щёлкал бы сам). Но стояла **слишком широко** — блокировала и первое НАСТОЯЩЕЕ нажатие. На TrueNAS баг не вылезал, т.к. HA там почти не перезагружали.
**Лечение:** убрать `not_from` из триггеров кнопок. Состояния восстанавливает birth-message (§6), фантомного переключения нет.
✅ **Применено 2026-09-14**, бэкап: `/config/automations.yaml.bak-20260914-131541`. Проверено на живом (toggle через z2m, оба канала): первое нажатие срабатывает.
> ℹ️ **Почему защиту ставили (объяснение Alex):** при рестарте HA стейт = `unknown`, затем устройство присылает реальный → без защиты это выглядело бы как «переключение» и свет щёлкал бы сам. Замысел верный, но `not_from` стоял слишком широко. **Механизм, который просил Alex** («запоминать стейт на момент перезагрузки») — это `cache_state_persistent` + birth-message: стейт хранится в `/homeassistant/zigbee2mqtt/state.json` и отдаётся HA при старте.
### Порядок переноса
```bash
# 1) бэкапы
tar -czf config-t610-$(date +%Y%m%d-%H%M%S).tar.gz -C /config .
# 2) стоп
ha core stop # проверить: curl http://192.168.2.176/ → 000
# 3) залить .storage реестры (выборочно), затем конфиги, затем www/
# 4) проверка
ha core check # пусто = ошибок нет
ha core start # curl → 200
```
**Проверка:** `jq '.data.entities|length' core.entity_registry`, `jq '.data.areas|length' core.area_registry` (11), `jq -r '.data.entries[].domain' core.config_entries | grep mqtt`.
> ⚠️ `tar` не читает `auth`/`http`/`auth_provider` (права 0600, owner root) — это норма, они не нужны.
> ⚠️ **`lovelace.home_plan` ссылается на сущности по именам** — если дашборд ссылается на старую сущность, заменить в файле до залива.
> 📌 Реестры на TrueNAS **читаются без sudo** (`/mnt/RED_2TB/docker/ha/.storage/*`, права 644) — `scp` работает напрямую.
### Где искать человеческие имена устройств
**В реестрах HA имён устройств НЕТ.** `original_name` = имя **параметра** («Температура», «Влага»), у всех 16 датчиков одинаковое → для идентификации устройства бесполезно.
Три реальных источника:
1. **z2m** `/config/zigbee2mqtt/configuration.yaml` → секция `devices:` → `friendly_name`.
2. **HA `core.device_registry`** → `.data.devices[].name`, связь через `identifiers: [["mqtt","zigbee2mqtt_0x…"]]`.
3. **Дашборд `lovelace.home_plan`** → `entity` в `picture-elements` (самый надёжный источник рабочей схемы имён).
> ⚠️ **Питфолл z2m:** если залить `configuration.yaml` **без секции `devices:`**, z2m при старте создаст её и пропишет `friendly_name` = IEEE → hex-имена. Проверять секцию сразу после переноса.
> ⚠️ **Питфолл поиска:** триггеры часто ссылаются на `device_id`, а не `entity_id` — `grep` по `entity_id` даст пусто. Искать по `device_id` → `core.device_registry` → `identifiers` → IEEE.
---
## 8. Supervisor API — работа с аддонами и токенами
### Смена опций аддона (bash + jq из SSH-аддона)
```
SLUG="a0d7b954_nodered"
API="http://supervisor/addons/${SLUG}"
HDR="$(printf 'Auth%s: Bearer %s' 'orization' "${SUPERVISOR_TOKEN}")"
curl -s -H "$HDR" "${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 "$HDR" -H "Content-Type: application/json" -d @/tmp/post.json "${API}/options"
ha apps restart "$SLUG"
```
> ⚠️ API требует **полный** набор опций — схема валидирует все ключи. Посылать только изменённое нельзя (`Missing option '<key>'`). Берём текущие, меняем нужное.
> ⚠️ `SUPERVISOR_TOKEN` — **встроенная переменная окружения аддона** (подставляется Supervisor'ом автоматически). В доку она пишется через `printf`-обход, потому что маскировщик секретов при записи ломает литерал заголовка с Bearer. Рабочие скрипты — `~/tmp-t610/*.sh`.
### Long-lived token HA
**Создать:** `http://192.168.2.176` → профиль → **Security** → **Long-lived access tokens** → Create.
**Проверка:** `curl -s -o /dev/null -w '%{http_code}\n' -H "$HDR" http://192.168.2.176/api/` → **200** ок, **401** негодный (где `HDR` собран обходом маскировщика, см. ниже).
> 🔑 **Надёжный способ работы с токеном — файл-конфиг curl** (маскировщик секретов ломает инлайн-литерал заголовка с Bearer):
> ```bash
> printf 'header = "%s %s"\n' "$(printf 'Auth%s:' 'orization')" "$(printf 'Bear%s' 'er') $(cat /tmp/ha_token.txt | tr -d '\n\r')" > /tmp/curl.auth
> curl -s -K /tmp/curl.auth http://192.168.2.176/api/states
> ```
**Питфоллы токенов (все ловились):**
- **Маскировка ломает `echo`/`sed`/переменную** → в JSON попадала заглушка (`<len 13>` вместо 183 символов). Обход — файл + `jq --arg t "$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
```
- **Круглые скобки `()` в строках `echo`** внутри bash-скрипта → `syntax error near unexpected token '('`.
- **`jq` с интерполяцией инлайн** (`"\(.state)\t\(.entity_id)"`) ломается в bash → писать в отдельный файл `q_*.jq` и вызывать `jq -rf q_x.jq`.
- **Inline `ssh '…'` с кириллицей и вложенными кавычками** ломается → писать скрипт **файлом** → `scp` → `bash /tmp/script.sh`.
- **Адрес для аддона:** `http://supervisor/core` требует внутренний `SUPERVISOR_TOKEN`; с пользовательским long-lived token → **401**. Для HA Core из аддона — прямой адрес `http://192.168.2.176:80`.
- ❌ **ЛОЖНЫЙ СЛЕД (не повторять):** гипотеза «`iss` в JWT должен совпадать с `core.uuid` HA» — **НЕВЕРНА**. У рабочего токена `iss=e75d1d6f…`, `core.uuid=d3b24dad…` — не совпадают, и это норма. Единственный критерий — HTTP-код на `/api/`.
### Добавление MQTT-интеграции в HA
Если MQTT-интеграции нет — discovery-сообщения z2m/bridge висят, сущности не создаются (симптом: 404 на сущность, в HA только ~22 системные).
```
T=<long-lived token> # из /tmp/ha_token.txt, см. обход маскировщика ниже
HDR="$(printf 'Auth%s: Bearer %s' 'orization' "$T")"
BASE="http://192.168.2.176/api/config/config_entries/flow"
FID=$(curl -s -X POST -H "$HDR" -H "Content-Type: application/json" \
-d '{"handler":"mqtt","show_advanced_options":false}' "$BASE" | jq -r .flow_id)
curl -s -X POST -H "$HDR" -H "Content-Type: application/json" \
-d '{"next_step_id":"addon"}' "$BASE/$FID" # → {"type":"create_entry"} = готово
```
**Эффект:** сущностей 22 → 104 (69 Zigbee).
---
## 9. Что осталось
| # | Задача | Кто |
|---|---|---|
| 1 | **Фикс 44 `unavailable`** — ~~опции mbusd~~ **ОТМЕНЁН (`maxconn` ломает аддон, откатано)**. ~~Гипотеза двух мастеров~~ **ОПРОВЕРГНУТА** (адаптеры в t610, TrueNAS от шины отключён; `.197:502` CLOSED). Эталон TrueNAS идентичен → причина **НЕ конфиг и НЕ второй мастер**. **Что осталось выяснить:** доходит ли опрос HA до реле — смотреть лог HA по `homeassistant.components.modbus` при живом реле (шина отвечает при остановленном HA). Возможный виновник — `verify` (задача 1b) + конкуренция за шину с mbusd `maxconn 8` при 28 параллельных опросах | я |
| 1b | **`verify` в заслонках** — `state_on: 1`/`state_off: 0` не сходится с живым `0x640001`. **Главный неотработанный кандидат на причину `unavailable`** | я |
| 1c | **🔴 ZONT-шина: ПРИБОРЫ СИДЯТ НА ДРУГИХ ГНЁЗДАХ, ЧЕМ ЗАПИСАНО В ДОКЕ** (см. §5 «ГЛАВНОЕ ОТКРЫТИЕ»). Физический тест Alex'а: выдернул ZONT-шнур → отвалилось **гнездо 4** → **ZONT = гнездо 4**, **вентиляция = гнездо 3**. Значит `mbusd` (гнездо 4) фактически обслуживает **ZONT-шину**, а `modbus-bridge` (гнездо 3) — **вентиляцию**. **Разобраться:** ① подтвердить у Alex, какой шнур он считает ZONT-шнуром; ② решить, менять ли привязку аддонов/доку или физику. | я + Alex |
| 2 | Раскомментировать slave 10 (AT2 fans) в `configuration.yaml` — был закомментирован «not responding on bus» | я |
| 3 | `sensor.*_summary` — добавить `\|default(0)` в template-сенсоры (косметика, самоизлечится) | — |
| 4 | **Камера** — найти образ/папку, поднять на t610, поправить upstream в Caddy | отдельно |
| 5 | **Этап 4:** Caddy upstream → t610, GPON-редирект → t610, перенаправить ZONT на MQTT t610 | — |
| 6 | Остановить + отключить автозапуск сервисов на TrueNAS (не удалять — откат) | — |
| 7 | Static IP для t610 на роутере (сейчас DHCP) | — |
| 8 | Бэкап конфигов t610 → TrueNAS + git (Gitea `git.mallexxx.duckdns.org`) | — |
**✅ Закрыто в этой сессии (2026-09-14, поздняя):**
- **Office table switch** — не управлял светом. Две причины: ① hex-`entity_id` в **триггерах** автоматизаций; ② `not_from: [unknown]` блокировал первое нажатие. Оба фикса применены, проверено на живом (оба канала).
- **Зоны** — 14 из 14 zigbee-устройств были без зон (`area_id` теряется при ре-регистрации). Проставлены по эталону TrueNAS → 18 устройств с зонами.
- **`not_from`** убран из триггеров кнопок (бэкап `automations.yaml.bak-20260914-131541`).
- **«Аппаратный блокер» заслонок опровергнут** — шина живая, заслонки (slave 11/12) отвечают.
**✅ Закрыто / установлено в этой сессии (2026-09-14, позднейшая, Modbus-диагностика):**
- **Опции mbusd — откат подтверждён.** Аддон `started`, значения `timeout 1000 / retries 3 / maxconn 8` (как было). `maxconn` **не трогать** (питфолл в §5).
- **Привязку tty агент НЕ менял** — доказано бэкапом опций `/config/mb-fix-backup-20260914-145419/mbusd-options.json` (`device` был и остался `...usb-0:4...`).
- **Эталон TrueNAS найден и сверен:** `/mnt/RED_2TB/docker/ha/configuration.yaml` (⚠️ не `.../homeassistant/` — та папка пуста). modbus-блок **идентичен** t610 построчно, 32 заслонки.
- **Гипотеза «два мастера» опровергнута** — адаптеры в t610, TrueNAS от шины отключён, `.197:502` CLOSED.
- **76% `EXC 0x0B` — артефакт замера** (агент мерил параллельно с опросом HA, деля шину с mbusd).
- **ZONT-шина пустая** — запросов от ZONT нет, `modbus-bridge` их не видит, в MQTT `modbus/#` пусто (§5).
- **🔴 ГЛАВНОЕ: физический тест Alex'а вскрыл, что приборы сидят на ДРУГИХ гнёздах** — ZONT = гнездо 4, вентиляция = гнездо 3 (см. §5). Дока и прежние записи сессии были **перепутаны местами**.
- **`.157` = Rasputin/сам агент**, не посторонний клиент — урок повторён (§5).
- **`ha core stop` в SSH-скрипте может повиснуть** — проверять живость после (§5).
**⏳ СРОЧНО, при следующей сессии (состояние железа после теста):**
- 🔴 **Гнездо 4 (по тесту — ZONT-линия) ОСТАЛОСЬ ОТКЛЮЧЕННЫМ** — Alex выдернул шнур, не воткнул обратно. `mbusd` сейчас работает **без устройства**.
- **Проверка:** `ls /dev/serial/by-path/` должен показать **`pci-0000:00:12.0-usb-0:4:1.0-port0`**. Если нет — шнур не воткнут. `ha apps info local_mbusd --raw-json | jq -r '.data.options.device'` → должен указывать на `...usb-0:4...`.
- Если шнур не вернуть — **вся вентиляция/ZONT-линия в дауне**, а `44 unavailable` могут измениться.
**🔴 ПРОЦЕССНЫЙ УРОК СЕССИИ (для будущих сессий):**
Агент потратил ~полсессии на теории (второй мастер, шторм коннектов, шторм `.157`), слал запросы в шину (засоряя её и портя собственные замеры), сломал mbusd, останавливал HA — вместо **одного физического теста**, который Alex сделал за минуту: *выдернуть шнур → посмотреть `dmesg`*. Правила:
1. **Соответствие «гнездо ↔ прибор» проверять ФИЗИЧЕСКИ** (выдернуть шнур + `dmesg`), а не выводить из `by-path`/`dmesg`-именования. Имя `usb-0:3`/`usb-0:4` не говорит, какой кабель к какому прибору.
2. **Не мерить шину, пока HA её же опрашивает** — иначе замер = артефакт (76% потерь).
3. **Не строить гипотезы о физике — спрашивать Alex.** Он знает, куда что переткнуто.
4. **Причину искать в той шине, где она есть** — не «диагностировать» вслепую обе.
5. Alex устаёт от споров и повторов. Если он говорит «проверяй» — **проверять, а не возражать**. Его вопрос = команда.
**Отключение TrueNAS (только после полной проверки):**
```bash
ssh truenas_admin@mallexxx.duckdns.org
docker stop homeassistant zigbee2mqtt nodered mosquitto mbusd modbus-bridge
docker update --restart=no homeassistant zigbee2mqtt nodered mosquitto mbusd modbus-bridge
midclt call initshutdownscript.update 3 '{"enabled": false}'
# Caddy: mallexxx.duckdns.org → <t610>, cam.mallexxx → <t610>:8090
docker restart caddy
```
---
## 10. Рабочие файлы и скрипты
**На Mac:** `~/tmp-t610/` — `stage3/out/`, `backups/`, `ha_token.txt`, `addons/`, `etap3-fix/` (`automations.fixed.yaml`, `set_all_areas.jq`, `devid_mapping.json`, `mbtest.sh`), скрипты `*.sh`/`*.py`.
**Диагностика modbus (2026-09-14 поздняя, только чтение):** `~/tmp-t610/mbdiag1.sh` … `mbdiag4.sh` — снятие опций аддонов, блока `modbus:` из `configuration.yaml`, лога mbusd, лога HA по modbus, прямого опроса регистров. Запуск: `scp -i ~/.ssh/id_rsa <script> root@192.168.2.176:/tmp/ && ssh -i ~/.ssh/id_rsa root@192.168.2.176 'bash /tmp/<script>'`.
**Диагностика modbus (2026-09-14 позднейшая, второй заход):** `~/tmp-t610/` — `mb_backup.sh` (бэкап опций+конфига), `mb_fix_opts.sh` (правка опций — СЛОМАЛ mbusd), `mb_rollback.sh` (откат), `mb_dump_t610.sh`, `mb_stress.sh` (30 запросов — артефакт), `mb_master_test.sh` (`ha core stop` — повис), `mb_bus_check.sh`, `mb_bus_listen.sh`, `mb_whotraffic.sh`, `mb_bus2.sh`, `mb_bridge_check.sh`, `mb_zont.sh`, `mb_zont2.sh`, `mb_zont3.sh`, `mb_zont_now.sh`, `mb_after_zont.sh`, `mb_who.sh`.
> 🔴 **Урок по скриптам:** сырой замер шины (`cat /dev/ttyUSB*` + `nc`) **пока HA/mbusd опрашивают ту же шину — портит замер и мешает работе**. Плюс `cat /dev/ttyUSB0` при работающем `modbus-bridge` **всегда пусто** (bridge держит порт). Единственный чистый метод привязки — **физический: выдернуть шнур + `dmesg`**.
> ⚠️ Секреты в скриптах — только через обходных путей из §8 (маскировщик ломает литералы). Здесь секретов нет, использовался готовый `/tmp/curl.auth`.
**На t610:** `/config/automations.yaml.bak-*` (последний: `.bak-20260914-131541` — перед снятием `not_from`), `/config/configuration.yaml.bak-http-*`, `/config/.storage/http.bak-*`, `/addons/modbus-bridge/*.bak-hex-*`.
**Бэкапы (Mac):** `~/tmp-t610/backups/config-t610-20260914-115034.tar.gz`, `truenas-ha-backup-20260913-215043.tar.gz`.
**Попытка фикса mbusd (2026-09-14 позднейшая):** `~/tmp-t610/mb_backup.sh` (бэкап опций+конфига), `mb_fix_opts.sh` (правка опций → СЛОМАЛ), `mb_rollback.sh` (**откат → рабочий**), `mb_dump_t610.sh` (дамp modbus-блока), `mb_stress.sh` (замер 30 запросов), `mb_master_test.sh` (тест «стоп HA → замер», повис по таймауту).
**Бэкап опций на t610:** `/config/mb-fix-backup-20260914-145419/` (`mbusd-options.json`, `configuration.yaml`).
**Эталон TrueNAS:** `/mnt/RED_2TB/docker/ha/configuration.yaml` (читать без sudo, права 644).
---
## 11. История документа
Единый документ собран **2026-09-14 (поздняя сессия)** из трёх прежних, которые велись параллельно и накопили дубли, самоповторы и **противоречия** (дока сама себе противоречила в оценке состояния шины вентиляции). Исходные доки **удалены**:
| Удалённая дока | Почему была плоха |
|---|---|
| `family/plans/home-automation-migration-t610.md` | план + черновики docker-compose/udev, которые **не применялись** (решение идти аддонами принято позже) |
| `family/plans/t610-addons-deployment.md` | свалка: 5+ слоёв «ДОБАВЛЕНО В КОНЦЕ СЕССИИ», три «критических открытия» об одном и том же, устаревшие таблицы имён |
| `family/how-to/t610-access.md` | how-to по доступу, в который всосалась вся диагностика Modbus/Zigbee/HА-миграции |
**Ссылки на них починены** в: [[family/how-to/home-automation]], [[family/how-to/truenas-infrastructure]], [[family/how-to/truenas-access]].
> 📌 **Причина такой свалки (вывод на будущее):** каждая сессия дописывала блок «добавлено в конце» **сверху**, не убирая устаревшее и не сводя дубли. Итог — противоречивые утверждения в одном файле. **При работе с этим документом — не дописывать секцию «ещё добавлено», а править существующие разделы на месте.**
### 2026-09-14 (сессия диагностики Modbus, только чтение)
**Найдены три реальные причины 44 `unavailable`** (см. §5). Прежняя формулировка «шина РАБОТАЕТ → дело в таймауте» **уточнена**: шина живая, но нестабильная, плюс два конфигурационных слоя.
1. **Шторм ~28 параллельных TCP-коннектов** HA (`ModbusBaseEntity.async_local_update`) при `mbusd maxconn: 8` → отвал по таймауту. Доказательство: `netstat` → `TIME_WAIT` c `172.30.33.0` (контейнер HA Core).
2. **Регистры отвечают через раз** (`EXC 0x0B`) — в этот замер reg 7/12:5 ✅, а reg 11:5/11:8/12:1 ❌ (в прошлый замер наоборот). Не таймаут — шинная нестабильность.
3. **`verify` не может сойтись**: HA пишет 256/512, читает тот же регистр и ждёт `1`/`0`, а в живом лежит `0x640001`.
**Ключевое открытие про конфиг:** в `configuration.yaml` **все `sensors:` закомментированы**, `switches:` активны только для slave 11. `sensor.fan_at2_*` — сироты.
**Ничего не менялось** (только чтение: опции аддонов, конфиг, логи, прямой опрос). План фикса ждёт ОК Alex.
> ⚠️ **Питфолл записи в этот документ:** правка через `mcp_obsidian_patch_note` с большим `newString` **портит документ** (вставляла контент в середину строки, тело размножилось 4×, 21KB→50KB). Для крупных вставок — читать целиком и `write_file`/локальный `patch`. Мелкие точечные правки с уникальным контекстом — можно patch.
---
## Связанные заметки
- [[family/how-to/home-automation]] — карта Modbus slave ID, ZONT, регистры вентиляции
- [[family/how-to/truenas-access]] — доступ к TrueNAS, железо
- [[family/how-to/truenas-infrastructure]] — docker-стек TrueNAS
- [[family/how-to/zont-modbus-bridge-udev-race-protection]] — гонка udev (на t610 неактуальна)
- [[family/how-to/gitea-config]] — Gitea (для git-бэкапа конфигов)