1419 lines
154 KiB
Markdown
1419 lines
154 KiB
Markdown
# 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 (актуализировано 15:33 → дополнено вечерней сессией)
|
||
|
||
**Этап 1 ✅ · Этап 2 ✅ · Этап 3 ✅ ЗАКРЫТ.** Этап 4 — **ЧАСТИЧНО СДЕЛАН:** ① Caddy переключён на t610 (`mallexxx.duckdns.org` → HA на t610, работает); ② Node-RED flows перенесены с TrueNAS, HA-узел в аддон-режиме, **подключён к HA, ошибок 0** (наружу не выпущен — решение Alex «оставляем так», доступ через ingress). **ОСТАЛОСЬ:** GPON-редирект → t610, ZONT MQTT → t610.
|
||
**Последняя верификация: 2026-09-14 (вечер-2) — Caddyfile залит, `trusted_proxies` исправлен, `mallexxx.duckdns.org` → HTTP 200; Node-RED: 68 узлов перенесены, `Connected to http://supervisor/core`, лог без ошибок.** Детали — §5-кватер-Б, -В, -Г.
|
||
|
||
| Что | Факт |
|
||
|---|---|
|
||
| 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) |
|
||
|
||
### ✅ Верификация 2026-09-14 15:33 (только чтение, ничего не менялось)
|
||
|
||
Проверено командами на t610 по итогам сессии:
|
||
|
||
| Проверка | Команда | Результат |
|
||
|---|---|---|
|
||
| Шнур в гнезде 4 воткнут | `ls /dev/serial/by-path/` | ✅ `pci-0000:00:12.0-usb-0:4:1.0-port0 → ttyUSB0/1` **есть**, `lsusb` видит **оба** CH340 (`1a86:7523`) |
|
||
| Привязка mbusd | `ha apps info local_mbusd --raw-json \| jq -r '.data.options.device'` | `...usb-0:3:1.0-port0` — **вентиляция (гнездо 3)** ✅ |
|
||
| Привязка bridge | `ha apps info local_modbus-bridge --raw-json \| jq -r '.data.options.device'` | `...usb-0:4:1.0-port0` — **ZONT 485 (гнездо 4)** ✅ |
|
||
| Оба аддона | `ha apps info <slug>` | `local_mbusd` 1.0.0 `started`, `local_modbus-bridge` 1.1.0 `started` |
|
||
| Сниффинг живой | `ha apps logs local_modbus-bridge` | slave 1, 2, 3, 14, 20, 101, 103 — CRC OK, публикации в MQTT идут |
|
||
|
||
> ✅ **Срочный пункт из прошлой сессии («гнездо 4 осталось отключённым») ЗАКРЫТ** — шнур на месте, оба аддона работают на верных гнёздах, регресса нет.
|
||
|
||
**⚠️ НОВОЕ НАБЛЮДЕНИЕ (не исследовано, причина неизвестна):** лог `modbus-bridge` «замерзал» — последняя запись была `08:32:58`, при живом аддоне (`started`) и текущем времени `15:33` → **~7 часов без единой строки**. После `ha apps restart local_modbus-bridge` лог ожил и пошёл сниффинг.
|
||
**Что НЕ утверждается:** причина не установлена, теорий не строим. Возможные направления для будущей сессии (проверять фактом, не гипотезой): засыпание USB-контроллера / зависание serial-хендла в контейнере / ротация лога. **Проверить** при следующем появлении: `ha apps info local_modbus-bridge` (state), сверить время последней строки лога с `date`, на живом ли HA (bridge опрашивает HA-сенсоры).
|
||
> 📌 Побочный эффект наблюдения: **`ha apps restart <slug>` — рабочий приём «оживить» bridge**, если HA-опрос встал. Проверено, безопасно (опции не трогает).
|
||
|
||
**Не работает / не доделано:**
|
||
|
||
| Что | Состояние |
|
||
|---|---|
|
||
| **10 сущностей `unavailable`** (было 44) | **✅ ОСНОВНОЕ РЕШЕНО 2026-09-14 (финал): аддоны стояли на перепутанных гнёздах — поменяны местами → ушли ВСЕ 32 заслонки** (см. §5 «✅✅ РЕШЕНИЕ»). Рабочая привязка: `mbusd` = гнездо 3 (вентиляция), `modbus-bridge` = гнездо 4 (**ZONT 485**). **Остались 10:** 7 — `switch.fan_3_high/medium/low` + `sensor.fan_at2_*` (slave 10 закомментирован в `configuration.yaml`, задача СНЯТА Alex'ом — не поломка); **2 — `sensor.dining_summary`/`dining_air_summary` (причина НЕ `\|default(0)`, а отсутствие MQTT-данных `modbus/sensors/dining/*` — см. §5-тер)**; 1 — `todo.shopping_list` (системная) |
|
||
| ~~`verify` как причина~~ | **ОПРОВЕРГНУТО как причина:** при верной привязке шин заслонки ожили при том же `verify` (`state_on:1`/`state_off:0`). Причина была в перепутанных шинах, не в `verify` |
|
||
| **`sensor.fan_at2_*` / `switch.fan_3_*` — сироты** | В `configuration.yaml` **весь блок slave 10 (AT2 fans) закомментирован** → эти сущности физически не могут получать данные. Не задача — просто известный факт состояния конфига |
|
||
| `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 → **вентиляция** ✅ |
|
||
| 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:` прописывать не нужно** — проброс автоматический.
|
||
|
||
```bash
|
||
# ✅ АКТУАЛЬНО (после обмена 2026-09-14, финал):
|
||
Вентиляция (mbusd): /dev/serial/by-path/pci-0000:00:12.0-usb-0:3:1.0-port0
|
||
ZONT (modbus-bridge): /dev/serial/by-path/pci-0000:00:12.0-usb-0:4:1.0-port0
|
||
Zigbee (z2m): /dev/serial/by-id/usb-Inswift_Zigbee_ZBP-MG21_535A000001-if00
|
||
```
|
||
> ⚠️ Историческая (ДО обмена) запись была «ZONT=3, Вентиляция=4» — неверно, исправлено.
|
||
> 🔴🔴 **АКТУАЛЬНАЯ ПРИВЯЗКА (подтверждена физическим тестом + обменом 2026-09-14, финал): см. таблицу выше — ZONT = гнездо 4, вентиляция = гнездо 3. Аддоны ПОСЛЕ обмена стоят верно.** Ниже — историческая запись (как было ДО обмена, когда аддоны были перепутаны).
|
||
|
||
> ⚠️ 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 (**68 узлов перенесены с TrueNAS**, `Connected to HA`, ошибок 0; наружу не выпущен — ingress; §5-кватер-Г) |
|
||
| 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 **#1 (порт 3)** — **вентиляция** (после обмена 2026-09-14), speed 9600, mode 8n1, trx_control `addc`, timeout 1000, retries 3, maxconn 8.
|
||
> 🔴 **`maxconn` НЕ ТРОГАТЬ:** при `maxconn=16` генератор `mbusd.conf` в `run.sh` ломает файл → `error at line 13` → аддон падает в `state: error`. Значения 8 хватает. См. §5.
|
||
> 🔴 **`timeout 1000 мс` — НЕ причина `unavailable`** (проверено: поднимал до 3000 → причина была в другом, см. §5 «✅✅ РЕШЕНИЕ»). Опции mbusd менять не нужно.
|
||
|
||
**`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 **#2 (порт 4)** — **ZONT 485** (после обмена 2026-09-14), baudrate 9600, `ha_token` (183 симв.), `mqtt_user` = `zont`, `mqtt_password`.
|
||
> 🔑 **Схема аддона сама называет шину:** `modbus-bridge (ZONT 485 bus)` — Supervisor валидирует `device` и требует, чтобы он существовал. **Если шнур физически выдернут — POST опций упадёт** с `Device '...' does not exist`. Сначала воткнуть шнур, потом менять опции.
|
||
> ⚠️ `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` |
|
||
|
||
> ⚠️ **ПРО АРТЕФАКТ:** «нестабильные ответы» и «76% `EXC 0x0B`» в замерах — **артефакт**: запросы слались через `nc` на порт 502 **пока HA/mbusd одновременно опрашивали ту же шину**. На чистой линии трафик нормальный. **НЕ причина** `unavailable`, **НЕ повод** крутить `timeout`.
|
||
|
||
⚠️ **Ложный след, который привёл к ошибке:** `conn_open` от `192.168.2.157` в логе mbusd был принят за «постороннего клиента, забивающего слоты mbusd». На самом деле `.157` — роутер Rasputin, через который ходит сам Mac (см. §2). Плюс запросы слались на reg 0 вместо 5/7/8.
|
||
|
||
**✅ ВЫЯСНЕНО (2026-09-14, финал):** причина `unavailable` — **аддоны стояли на ПЕРЕПУТАННЫХ гнёздах** (`mbusd` на гнезде 4 = ZONT-шина, `modbus-bridge` на гнезде 3 = вентиляция). После обмена привязок заслонки ожили: **44 → 10 `unavailable`**. См. §5 «✅✅ РЕШЕНИЕ».
|
||
|
||
### 🟡 ДИАГНОСТИКА 2026-09-14 (поздняя): ОШИБОЧНЫЕ ГИПОТЕЗЫ (сохранены как урок)
|
||
|
||
> 🔴 **ВАЖНО: три «причины» ниже (шторм, нестабильные регистры, `verify`) — НЕ причина `unavailable`. Финал: причина одна — ПЕРЕПУТАННЫЕ ГНЁЗДА аддонов** (см. выше стр. 301 и §5 «✅✅ РЕШЕНИЕ»). Гипотезы оставлены, потому что содержат ценные питфоллы диагностики (замер при живом HA, `maxconn`, форма сырого запроса). **Не принимать их за действующее объяснение.**
|
||
|
||
**❌ Гипотеза A (опровергнута) — «ШТОРМ параллельных 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`.
|
||
|
||
**❌ Гипотеза B (опровергнута) — «Регистры отвечают НЕСТАБИЛЬНО».**
|
||
Прямой опрос 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 ❌). Позже выяснилось: это **артефакт замера при живом HA** (`nc` конкурировал с опросом HA за ту же шину через mbusd), а не нестабильность реле.
|
||
> ⚠️ Формат сырого запроса (`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`, а не доверять «красивой» команде из доки.
|
||
|
||
**❌ Гипотеза C (опровергнута) — «`verify` физически не может сойтись».**
|
||
Конфиг (строки 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`).
|
||
**Но `verify` НЕ причина `unavailable`** — это доказано финалом: при верной привязке гнёзд все 32 заслонки **ожили при том же самом `verify`** (`state_on:1`/`state_off:0`). Гипотеза «`verify` не сходится НИКОГДА → `unavailable` навсегда» — **ОПРОВЕРГНУТА**.
|
||
|
||
**Состояние блока `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` — НЕ опции и НЕ «коллизия двух мастеров» (гипотеза ОПРОВЕРГНУТА), а ПЕРЕПУТАННЫЕ ГНЁЗДА аддонов. См. §5 «✅✅ РЕШЕНИЕ».**
|
||
|
||
**Что сделано:**
|
||
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`) — тогда это приняли за признак «второго мастера/источника трафика» (гипотеза опровергнута ниже; в реальности — артефакт замера при живом HA).
|
||
|
||
**❌ ГИПОТЕЗА «два 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` — **НЕ конфиг** (и, как выяснилось позже, **не «два мастера»**, а перепутанные гнёзда аддонов — см. §5 «✅✅ РЕШЕНИЕ»).
|
||
> 📌 **Ключевая разница хостов (транспорт, НЕ причина):** на 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 была ПУСТАЯ — ⚠️ ЗАКРЫТО, причина найдена
|
||
|
||
> ✅ **ИТОГ: «пустая шина» объяснялась ПЕРЕПУТАННЫМИ ГНЁЗДАМИ** (см. §5 «✅✅ РЕШЕНИЕ»). Агент слушал `ttyUSB0` (гнездо 3), считая его ZONT-шиной — а там **вентиляция**. А `modbus-bridge`, который должен ловить ZONT, стоял на гнезде 3 (вентиляция) и потому ничего не сниффил. **После обмена привязок `modbus-bridge` встал на гнездо 4 и сразу поймал ZONT-трафик** (`Sniff: bedroom_temperature = 25.0`). Приведённые ниже выводы («ZONT не мастер / не подключён») — **ОШИБОЧНЫ**, оставлены как урок.
|
||
|
||
Задача от Alex была конкретная: **видит ли `modbus-bridge` данные с ZONT-шины / идут ли запросы от ZONT?**
|
||
|
||
**Результат оказался ошибочным — см. блок ✅ выше: шина была не пуста, агент слушал не то гнездо.** Ниже — что именно наблюдалось тогда (сохранено как урок диагностики):
|
||
|
||
| Проверка | Как делалось | Результат |
|
||
|---|---|---|
|
||
| Шина ttyUSB0 (гнездо 3) | `cat /dev/ttyUSB0` 10–15 сек | **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 на шине нет». Причина на самом деле — агент слушал **не то гнездо** (гнездо 3 = вентиляция вместо гнезда 4 = ZONT). Ни «ZONT не подключён», ни «ZONT не мастер» — **не подтвердилось**: после обмена привязок bridge сразу поймал 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.
|
||
>
|
||
> ✅ **Подтверждено (финал):** `modbus-bridge` = **ZONT-шина** (гнездо 4), `mbusd` = **вентиляция** (гнездо 3). Подтверждено ДВАЖДЫ: объективным `dmesg` гнезда 4 и описанием схемы аддона «ZONT 485 bus». Привязки обменяны — см. §5 «✅✅ РЕШЕНИЕ».
|
||
> ⏳ ~~Сразу после теста гнездо 4 осталось ОТКЛЮЧЕННЫМ~~ — **ЗАКРЫТО, см. §5 «✅ РЕШЕНИЕ».** Шнур воткнут, гнездо 4 вернулось (`usb 1-4 → ttyUSB1`, симлинк `...usb-0:4...` снова есть).
|
||
|
||
#### ✅✅ РЕШЕНИЕ (2026-09-14, финал): АДДОНЫ ПОМЕНЯНЫ МЕСТАМИ — ЗАСЛОНКИ ОЖИЛИ, `unavailable` 44 → 10
|
||
|
||
**Alex дал команду: поменять привязки tty у аддонов местами.** Сделано через Supervisor API (§8), с бэкапом опций.
|
||
|
||
**До обмена (как стояло):**
|
||
|
||
| Аддон | device | Что фактически обслуживал |
|
||
|---|---|---|
|
||
| `local_mbusd` | гнездо **4** (`ttyUSB1`) | ZONT-шину |
|
||
| `local_modbus-bridge` | гнездо **3** (`ttyUSB0`) | вентиляцию |
|
||
|
||
**После обмена (рабочая конфигурация):**
|
||
|
||
| Аддон | device | Что обслуживает |
|
||
|---|---|---|
|
||
| `local_mbusd` | **`...usb-0:3:1.0-port0`** (гнездо 3, `ttyUSB0`) | вентиляция / заслонки |
|
||
| `local_modbus-bridge` | **`...usb-0:4:1.0-port0`** (гнездо 4, `ttyUSB1`) | **ZONT 485** |
|
||
|
||
Оба аддона — `started`.
|
||
|
||
> 🔑 **ПОДТВЕРЖДЕНИЕ ПРАВИЛЬНОСТИ СХЕМЫ (из самого аддона):** Supervisor при валидации опций вернул
|
||
> `Device '...' does not exist in modbus-bridge (ZONT 485 bus) (local_modbus-bridge)`
|
||
> — то есть **в описании схемы аддона `modbus-bridge` прямо написано «ZONT 485 bus»**. Значит **`modbus-bridge` = ZONT-шина** (гнездо 4), **`mbusd` = вентиляция** (гнездо 3). Схема аддонов сама подтвердила физический тест Alex'а.
|
||
|
||
**🔴 РЕЗУЛЬТАТ — `modbus-bridge` СРАЗУ поймал ZONT-трафик (лог после обмена):**
|
||
```
|
||
Slave: 20 Func: 0x1 CRC OK: True
|
||
Raw RTU: 14 01 00 00 00 01 FF 0F
|
||
→ READ COILS: 1 coil(s) from 0
|
||
→ Sniff: bedroom_temperature = 25.0
|
||
→ MQTT publish: modbus/sensors/bedroom/temperature = 25.0 [OK]
|
||
→ Sniff: bedroom_humidity = 36.3
|
||
→ MQTT publish: modbus/sensors/bedroom/humidity = 36.3 [OK]
|
||
```
|
||
**Он сниффит запросы, CRC валиден, публикует реальные значения в MQTT** — то самое, что требовалось. До обмена в логе **не было ни одной строки `Sniff`** (см. §5 «шина ZONT пустая» — теперь понятно: он стоял не на той шине).
|
||
|
||
**🔴 РЕЗУЛЬТАТ ПО HA: `unavailable` было 44 → стало 10.** **Ушли ВСЕ 32 заслонки** (`intake_damper_*` / `exhaust_damper_*`) — они снова живые.
|
||
|
||
> ✅ **ГЛАВНЫЙ ВЫВОД СЕССИИ: причина 44 `unavailable` была НЕ `verify`, НЕ таймаут mbusd, НЕ «два мастера», НЕ конфиг — а ПЕРЕПУТАННЫЕ ШИНЫ У АДДОНОВ.** Пока `mbusd` (обслуживающий заслонки) висел на гнезде с ZONT-линией, HA опрашивал не ту шину → `unavailable` навсегда. Обмен привязок — и всё ожило.
|
||
|
||
**Остались 10 `unavailable` (не связаны с обменом шин, и НЕ являются задачами):**
|
||
| Сущность | Причина |
|
||
|---|---|
|
||
| `switch.fan_3_high/medium/low` | slave 10 — блок закомментирован в `configuration.yaml` (факт состояния, не задача) |
|
||
| `sensor.fan_at2_1_pwm_raw` / `fan_at2_2_pwm_raw` / `fan_at2_1_run_raw` / `fan_at2_2_run_raw` | то же, slave 10 |
|
||
| `sensor.dining_summary` / `sensor.dining_air_summary` | **❌ причина НЕ в `\|default(0)`** (опровергнуто 2026-09-14 15:39, см. §5-тер): ZONT **не публикует** `modbus/sensors/dining/*` — 8 датчиков столовой пусты. Открытый вопрос к Alex: датчик есть физически? |
|
||
| `todo.shopping_list` | системная, не наша |
|
||
|
||
**Как делался обмен (рецепт):**
|
||
```bash
|
||
# ОБЯЗАТЕЛЬНО: сначала бэкап опций ОБОИХ аддонов
|
||
BK=/config/mb-swap-backup-$(date +%Y%m%d-%H%M%S); mkdir -p $BK
|
||
ha apps info local_mbusd --raw-json > $BK/mbusd-options.json
|
||
ha apps info local_modbus-bridge --raw-json > $BK/bridge-options.json
|
||
|
||
HDR="$(printf 'Auth%s: Bearer %s' 'orization' "$SUPERVISOR_TOKEN")"
|
||
# mbusd: 4 -> 3
|
||
curl -s -H "$HDR" http://supervisor/addons/local_mbusd/info \
|
||
| jq '.data.options | .device = "/dev/serial/by-path/pci-0000:00:12.0-usb-0:3:1.0-port0"' > /tmp/m.json
|
||
jq -n --slurpfile o /tmp/m.json '{options: $o[0]}' > /tmp/mp.json
|
||
curl -s -X POST -H "$HDR" -H "Content-Type: application/json" -d @/tmp/mp.json \
|
||
http://supervisor/addons/local_mbusd/options
|
||
# bridge: 3 -> 4 (аналогично, другой путь/устройство)
|
||
ha apps restart local_mbusd
|
||
ha apps restart local_modbus-bridge
|
||
```
|
||
|
||
> 🔴 **ПИТФОЛЛ ОБМЕНА: Supervisor НЕ ДАСТ сохранить `device`, которого физически нет.** Первая попытка поставить `bridge → гнездо 4` упала с `invalid options: Device '...usb-0:4...' does not exist` — потому что шнур в тот момент был **выдернут**. Порядок: **сначала воткнуть шнур, потом POST опций.** Симптом в ответе API: `{"result":"error","error_key":"app_configuration_invalid_error"}`.
|
||
> ⚠️ При обмене **на короткое время оба аддона указывают на одно гнездо** (если первая правка прошла, а вторая нет) — так работать нельзя, доводить обмен до конца.
|
||
> ✅ Проверка после обмена: `ha apps info <slug> --raw-json | jq -r '.data.options.device'` + `.data.state` = `started` для обоих.
|
||
|
||
#### ⚠️ УСТАРЕВШЕЕ (опровергнуто тестом выше): «Гнёзда разведены правильно»
|
||
|
||
Ранее в этой же сессии было записано (по `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 сделал за минуту там, где агент полчаса строил теории.
|
||
|
||
---
|
||
|
||
## 5-тер. 🔬 Датчики столовой: `dining_summary` `unavailable` — причина НЕ в формуле (2026-09-14 15:39)
|
||
|
||
**Сессия диагностики, только чтение. Задача от Alex: «почему что-то недоступно, что ты собрался менять».**
|
||
|
||
### ❌ ПРЕЖНЯЯ (опровергнутая) формулировка
|
||
|
||
Док в §9 задача 3 говорил: «`sensor.*_summary` — добавить `\|default(0)` в template-сенсоры (косметика, самоизлечится)». **Это НЕВЕРНО в двух местах:**
|
||
1. **Не «все `*_summary`»** — `kids_summary` и `bedroom_summary` **работают** (`25° 384ppm` / `25° 411ppm`). Ломаются только 2: `dining_summary`, `dining_air_summary`.
|
||
2. **Не «нет `default`»** — это следствие, а не причина. Причина — **нет самих данных**.
|
||
|
||
### ✅ ФАКТ (проверено живьём)
|
||
|
||
**Шаг 1 — состояния датчиков (`/api/states`):**
|
||
|
||
| Сущность | Состояние |
|
||
|---|---|
|
||
| `sensor.dining_temperature_2` | `unknown` |
|
||
| `sensor.dining_co2` | `unknown` |
|
||
| `sensor.dining_tvoc` | `unknown` |
|
||
| `sensor.dining_pm10` | `unknown` |
|
||
| `sensor.dining_humidity` / `_pm2_5` / `_formaldehyde` | `unknown` |
|
||
| `sensor.kids_temperature` / `kids_co2` | `25.2` / `384.8` → `kids_summary` = `25° 384ppm` ✅ |
|
||
| `sensor.bedroom_temperature` / `bedroom_co2` | `25.07` / `411.4` → `bedroom_summary` = `25° 411ppm` ✅ |
|
||
|
||
**Шаг 2 — что это за сущности (реестр HA):** `sensor.dining_*` → `platform: mqtt` → `device_id: d4878565d104ef5c09c2d38961521b84` → в `core.device_registry` `identifiers = [["mqtt","modbus_dining_sensor"]]`. То есть это **виртуальные датчики, которые создаёт `modbus-bridge`** через MQTT discovery (НЕ Zigbee, НЕ HA).
|
||
|
||
**Шаг 3 — что реально публикуется в MQTT (подписка `mosquitto_sub -t 'modbus/#'`):**
|
||
```
|
||
modbus/sensors/kids/temperature 25.2
|
||
modbus/sensors/kids/co2 381.38
|
||
modbus/sensors/kids/humidity 39.2
|
||
modbus/sensors/bedroom/temperature 25.07
|
||
modbus/sensors/bedroom/co2 409.85
|
||
modbus/sensors/bedroom/humidity 36.0
|
||
```
|
||
🔴 **`modbus/sensors/dining/*` — НЕТ НИ ОДНОГО СООБЩЕНИЯ.** ZONT не опрашивает датчик столовой (или его нет на 485-шине).
|
||
|
||
### 🧩 Формула (для полноты — `configuration.yaml` строки 1116–1121)
|
||
|
||
```yaml
|
||
- name: dining_summary
|
||
state: "{{ states('sensor.dining_temperature_2')|round(0)|int }}° {{ states('sensor.dining_co2')|int }}ppm"
|
||
- name: dining_air_summary
|
||
state: "{{ states('sensor.dining_tvoc')|int }}tvoc {{ states('sensor.dining_pm10')|int }}pm"
|
||
- name: kids_summary # ← эта РАБОТАЕТ
|
||
state: "{{ states('sensor.kids_temperature')|round(0)|int }}° {{ states('sensor.kids_co2')|int }}ppm"
|
||
```
|
||
|
||
**Это НЕ «средние»** (агент ошибочно так назвал — исправлено). Это **склейка строки** для плашки на дашборде: температура + CO2 через пробел.
|
||
|
||
**Механика падения:** `int`/`round` не умеют превратить строку `unknown` в число → рендер template падает → **вся** сущность становится `unavailable` (а не `unknown`). У детей/спальни датчики отдают числа → формула собирается.
|
||
|
||
### 🔴 ПОЧЕМУ ФИКС ФОРМУЛОЙ — ВРАНЬЁ
|
||
|
||
Если вписать `\|default(0)`, сводка выдаст **`0° 0ppm`** — то есть на дашборде появится «ложный ноль» вместо честного «нет данных». **Alex'у нужен факт, а не зелёная плашка.** Правка кода **не делается** до ответа на вопрос ниже.
|
||
|
||
### ❓ ОТКРЫТЫЙ ВОПРОС К ALEX — ✅ ОТВЕТ ПОЛУЧЕН 2026-09-14 (см. §5-кватер ниже)
|
||
|
||
**Есть ли датчик температуры/CO2 в столовой физически** (тот, что ZONT должен видеть на 485-шине)?
|
||
|
||
| Ответ | Что делать |
|
||
|---|---|
|
||
| **Да, есть** ← **ЭТО ОТВЕТ ALEX'А (2026-09-14)** | Чинить ZONT: почему не опрашивает датчик (не зарегистрирован в ZONT / обрыв / датчик сдох). Проверить по карте: **Гостиная = slave 1, Детская = 2, Спальня = 3** ([[family/how-to/home-automation]]) — «столовая» в ZONT может называться «Гостиная» или это отдельный слейв |
|
||
| **Нет** | 8 сущностей `sensor.dining_*` — **мусор с TrueNAS**. Чистить из `core.entity_registry` (при остановленном HA, бэкап реестра), а не «чинить» формулой |
|
||
|
||
> 📌 **Метод (переиспользуемый) — как отвечать на «почему сущность недоступна»:**
|
||
> 1. `/api/states` → какие сущности `unavailable`/`unknown`.
|
||
> 2. `core.entity_registry` → `platform` + `device_id` (откуда сущность).
|
||
> 3. `core.device_registry` → `identifiers` (какое устройство/интеграция).
|
||
> 4. Если `mqtt` + `modbus_*_sensor` → подписка `mosquitto_sub -t 'modbus/#'` → **есть ли данные вообще**.
|
||
> 5. Только после этого решать: чинить источник / чистить мусор / править формулу. **Формула — последнее, что проверять, а не первое.**
|
||
|
||
**Пароль для `mosquitto_sub`:** `ha apps info 45df7312_zigbee2mqtt --raw-json | jq -r '.data.options.mqtt.password'`, `-u zont`. В SSH-аддоне есть `mosquitto_sub`; `-R` (retained) **врёт** (§6), смотреть что прилетает сразу при подписке.
|
||
|
||
**Ничего не менялось — только чтение.**
|
||
|
||
---
|
||
|
||
## 5-кватер. 🔬 Датчик столовой ОТВЕЧАЕТ, но отдаёт НОЛЬ + развязка Caddy (2026-09-14, вечерняя сессия)
|
||
|
||
> **Задача сессии (Alex):** «Что дальше по плану?» → далее по ходу: «Датчик есть. Что с ним??» → «Ок. Делай. Меняй правила caddy на truenas чтобы указывали на t610».
|
||
|
||
### 5-кватер-А. Датчик столовой: причина найдена — шина отвечает нулём, bridge отбрасывает
|
||
|
||
**Alex подтвердил: датчик в столовой физически ЕСТЬ.** Значит ветка «чистить мусор» отпала — идём чинить источник.
|
||
|
||
**Что показал лог `modbus-bridge` (`ha apps logs local_modbus-bridge`, фильтр по `dining`):**
|
||
|
||
```
|
||
2026-09-14 08:43:45 Slave: 1 Func: 0x3 CRC OK: True
|
||
→ Response: 1 register(s) from 100 (sniff:dining_temperature) = 0 [00 00]
|
||
2026-09-14 08:43:50 Slave: 1 Func: 0x3 CRC OK: True
|
||
→ Response: 1 register(s) from 100 (sniff:dining_temperature) = 0 [00 00]
|
||
```
|
||
|
||
**Расшифровка (ключевой факт):**
|
||
|
||
| Наблюдение | Значение |
|
||
|---|---|
|
||
| `Slave: 1` | Столовая = **slave 1** (совпадает с картой: Гостиная=1) |
|
||
| `CRC OK: True` | Кадр целый — **проводка и обмен в порядке** |
|
||
| `Func: 0x3` | Чтение holding-регистра |
|
||
| `from 100` | Регистр **100** (температура) |
|
||
| `= 0 [00 00]` | 🔴 **Датчик отвечает ЗНАЧЕНИЕМ 0**, а не молчит |
|
||
| опрос каждые 5 с, стабильно | Не «иногда», а **постоянно ноль** |
|
||
|
||
> 🔴 **ВЫВОД: ZONT датчик ОПРАШИВАЕТ (slave 1, reg 100, каждые 5 с), датчик ОТВЕЧАЕТ — но отдаёт ноль.** `modbus-bridge` считает `0` невалидной температурой → **не публикует** в `modbus/sensors/dining/*` → HA-сущности остаются `unknown` → `dining_summary` падает в `unavailable`. **Проблема НЕ в HA и НЕ в проводке/обмене, а в самом датчике или его регистрации в ZONT.**
|
||
|
||
**Дополнительное наблюдение (зацепка):** в логе для столовой виден **только** `sniff:dining_temperature` (reg 100). Сущности CO2/влажности/TVOC/PM на датчик завязаны, но **ZONT эти регистры для столовой не читает** → значит либо датчик только-температурный, либо в ZONT он заведён частично.
|
||
|
||
**Почему `|default(0)` — по-прежнему НЕ фикс** (подтверждено): он дал бы `0° 0ppm`, что совпало бы с реальным «нулём» и замаскировало бы поломку. **Правильно — найти, почему датчик отдаёт 0**: не откалиброван / не сконфигурирован в ZONT / просело питание по шине (485-датчики питаются от линии).
|
||
|
||
> 📌 **Не путать с §5-тер:** там зафиксировано, что `modbus/sensors/dining/*` не публикуется. Здесь — **почему** (датчик отвечает нулём), после того как Alex подтвердил наличие датчика.
|
||
> ⛔ **Alex переключил внимание на план миграции** — задачу по датчику столовой **не доделывали** (не чинили ZONT, не трогали реестр). Остаётся в бэклоге как отдельная задача.
|
||
|
||
### 5-кватер-Б. Развязка Caddy — архитектура и подготовленная правка
|
||
|
||
**Вопрос Alex:** «GPON всё редиректит на OpenWrt. Как развязываем Caddy? Перенос на OpenWrt?»
|
||
|
||
**Ответ: НЕТ, Caddy на OpenWrt не переносится.** Разведка показала реальную топологию:
|
||
|
||
| Компонент | Факт |
|
||
|---|---|
|
||
| Caddy | **docker-контейнер на TrueNAS** (`/mnt/RED_2TB/docker/caddy/`), порты **8088:80 / 8443:443** |
|
||
| OpenWrt `192.168.2.2` | **только пробрасывает** трафик (DNAT), Caddy на нём НЕТ |
|
||
| OpenWrt 80/443 | заняты **своим `uhttpd`** (веб-морда LuCI) — конфликт, Caddy туда не встанет |
|
||
| DNAT-правила | `firewall.caddy_http` `wan:80 → 192.168.2.197:8088`, `firewall.caddy_https` `wan:443 → 192.168.2.197:8443` |
|
||
| Доп. находка | `/etc/config/dhcp`: `list address '/mallexxx.duckdns.org/192.168.0.10'` — внутренний DNS-пин для ZONT |
|
||
|
||
**Caddyfile на TrueNAS — 20 доменов. Из них на t610 идут ровно ДВА:**
|
||
|
||
| Домен | Было | Стало (правка) |
|
||
|---|---|---|
|
||
| `mallexxx.duckdns.org` | `reverse_proxy 192.168.2.197:8123` | **`reverse_proxy 192.168.2.176:80`** |
|
||
| `nodered.mallexxx.duckdns.org` | `reverse_proxy 192.168.2.197:1880` | **`reverse_proxy 192.168.2.176:1880`** |
|
||
|
||
> 🔴 **Урок архитектуры:** 17 из 20 доменов Caddy — это сервисы самого TrueNAS (immich, webdav, git, jellyfin, radarr, sonarr, prowlarr, syncthing, portainer, books, library, transmission, truenas, cam, docs, vpn-panel, vpn). **Уносить Caddy на t610 нельзя** — падение t610 положило бы все медиасервисы. **Правильно: Caddy остаётся на TrueNAS, правим только 2 upstream.**
|
||
> ⚠️ **Следствие для Этапа 4:** пока Caddy на TrueNAS — **TrueNAS гасить нельзя**, он держит точку входа. «Погасить TrueNAS» (задача №6) требует отдельного решения о том, где живёт вход (внешний reverse-proxy / отдельный хост). **Записать как открытый вопрос Этапа 4.**
|
||
> ⚠️ Порт HA на t610 = **80**, а НЕ 8123 (у t610 `8123` закрыт!) — при правке upstream это главная ловушка.
|
||
|
||
**✅ ЧТО БЫЛО СДЕЛАНО В ИТОГЕ (2026-09-14, вечерняя сессия — ЗАЛИВКА ЗАВЕРШЕНА):**
|
||
|
||
| Артефакт | Путь | Статус |
|
||
|---|---|---|
|
||
| Оригинал (снят с TrueNAS) | `~/tmp-caddy/Caddyfile.orig` → отредактирован; отдельно `Caddyfile.new` | sha256 orig `25acb94a…` → new **`c8c2a5c0…`** |
|
||
| Бэкап оригинала (Mac) | `~/tmp-caddy/Caddyfile.orig.20260914-145006.bak` | 2134 байта, 94 строки |
|
||
| Отредактированная версия (2 строки) | `~/tmp-caddy/Caddyfile.new` | ✅ **`caddy validate` → `Valid configuration`** |
|
||
| Файл на TrueNAS (стейджинг) | `/tmp/Caddyfile.new` (от `truenas_admin`) | ✅ sha256 совпал с локальным |
|
||
| **Залит на место + Caddy рестартнут** | `/mnt/RED_2TB/docker/caddy/Caddyfile` | ✅ **Alex подменил файл руками и сделал `docker restart caddy`** |
|
||
|
||
> 🔑 **Финальный обход блокера sudo:** Alex взял готовый файл из `/tmp/Caddyfile.new` и **подменил сам** (путь B из таблицы ниже). `cp` на место root-owned каталога делает он, не агент.
|
||
> ✅ **РЕЗУЛЬТАТ: `https://mallexxx.duckdns.org` → HTTP 200 (HA на t610). Alex подтвердил: «HA работает на mallexxx.duckdns».**
|
||
|
||
**Порядок, который был выполнен (всё безопасное):**
|
||
1. ✅ Правка файла **локально** на Mac (не на хосте — правило «copy → edit → upload»).
|
||
2. ✅ Проверка синтаксиса в docker: `docker run --rm -v ~/tmp-caddy/Caddyfile.new:/etc/caddy/Caddyfile:ro caddy:latest caddy validate --config /etc/caddy/Caddyfile` → **`Valid configuration`** (warnings — предсуществующие, про `header_up` в webdav и форматирование).
|
||
3. ✅ Контроль `grep`: остальные 6 upstream на `192.168.2.197` **не тронуты**; diff — ровно 2 строки.
|
||
4. ✅ Проверка живости целевых портов: t610 `:80` → **200**, t610 `:1880` → **401** (Node-RED требует логин — норма). Старые адреса TrueNAS тоже живы (есть куда откатываться).
|
||
5. ✅ Файл загружен в `/tmp/` на TrueNAS (`scp`), sha256 сверен.
|
||
6. ✅ **Alex подменил файл и рестартнул Caddy** → `mallexxx.duckdns.org` работает.
|
||
|
||
**🔴 ПИТФОЛЛ (не решён кодом, обойдён вручную):** `Caddyfile` на TrueNAS — `root:root 644`, папка root-owned. `cp`/`tee` без sudo → `Permission denied`. `ssh truenas_admin@mallexxx.duckdns.org 'sudo -S tee …'` → `3 incorrect password attempts`. **`truenas_admin` не имеет passwordless sudo** (см. [[family/how-to/truenas-infrastructure]] — «root-операции только через midclt или TrueNAS UI»). **Рабочий обход на практике: агент готовит и стейджит файл в `/tmp/`, Alex подменяет руками.**
|
||
|
||
**Варианты обхода (для будущих правок root-owned файлов TrueNAS):**
|
||
| Путь | Как | Проверено |
|
||
|---|---|---|
|
||
| **A** | Передать пароль `truenas_admin` через stdin (`sudo -S`) | ❌ не сработал (3 попытки) |
|
||
| **B** | **Правит Alex сам (агент отдаёт готовый файл + diff)** | ✅ **ТАК И СДЕЛАЛИ — работает** |
|
||
| **C** | Через TrueNAS UI (File editor / Shell) | не пробовали |
|
||
| **D** | ~~Caddy admin API `localhost:2019`~~ — не проброшен наружу, всё равно нужен `docker exec` → снова sudo | — |
|
||
|
||
**Откат (если понадобится):** вернуть `~/tmp-caddy/Caddyfile.orig.20260914-145006.bak` на место + `docker restart caddy`.
|
||
|
||
**Проверка после заливки (по плану):** `docker restart caddy` → `curl -I https://mallexxx.duckdns.org` (должен отдать HA с t610) и `curl -I https://nodered.mallexxx.duckdns.org`.
|
||
|
||
> 📌 **Питфолл sudo на TrueNAS:** `ssh truenas_admin@mallexxx.duckdns.org 'sudo …'` в неинтерактивном режиме **не работает без пароля** (`a terminal is required to read the password`). Для чтения файлов docker-конфигов sudo часто не нужен (права 644) — но запись в `/mnt/RED_2TB/docker/*` требует root.
|
||
|
||
> ⚠️ **ПРОЦЕССНЫЙ УРОК СЕССИИ (Alex, жёстко):** агент ушёл в глубокую диагностику датчика столовой, **хотя Alex вёл к плану миграции**. Реплики: «Какая-то с ним хуйня. Какого хуя мы этим занимаемся вообще. В плане миграции блядь чё». **Правило: когда Alex спрашивает «что дальше по плану» — отвечать ПО ПЛАНУ, не уходить в побочную диагностику без его явного запроса.** Диагностика датчика — только после «Что с ним??».
|
||
|
||
### 5-кватер-В. 🔴 Пост-заливочные фиксы: `trusted_proxies` (HA за обратным прокси) + порт Node-RED
|
||
|
||
> Появилось **после** заливки Caddyfile и рестарта. Оба пункта — **новые находки**, которых не было в подготовительной части.
|
||
|
||
#### В-1. `mallexxx.duckdns.org` отдавал **400 Bad Request** через Caddy — фикс `trusted_proxies`
|
||
|
||
**Симптом:** снаружи `https://mallexxx.duckdns.org/` → **HTTP 400**, тело `400: Bad Request`, заголовки `server: Python/3.14 aiohttp/3.14.3` + `via: 1.1 Caddy`. При этом **напрямую** `http://192.168.2.176/` отдавал **200** (HTML Home Assistant) — с любым `Host`.
|
||
|
||
**Диагноз (после ложного следа):**
|
||
- ⚠️ **Ложный след (не повторять):** сначала решено было, что `aiohttp`/`Python` — это `modbus-bridge`. **НЕВЕРНО.** `aiohttp` на Python — это **сам HA Core** (HA написан на Python/aiohttp). Признак: `via: 1.1 Caddy` в ответе = ответ прошёл через Caddy от upstream.
|
||
- **Настоящая причина:** в `/config/.storage/http` HA настроен `"use_x_forwarded_for": true` при `"trusted_proxies": ["172.16.0.0/12"]` — доверяет только docker-подсетям. **Caddy живёт на ДРУГОМ хосте (`192.168.2.197`, TrueNAS)** → HA видит источник `.197` не из доверенной подсети → **отбивает 400**.
|
||
|
||
**Фикс (применён 2026-09-14):**
|
||
```json
|
||
"trusted_proxies": ["172.16.0.0/12", "192.168.2.197/32"]
|
||
```
|
||
Добавлен `/32` именно адрес TrueNAS (где Caddy), форма CIDR как у остальных записей.
|
||
|
||
**Порядок применения (`.storage/http` перезаписывается HA на ходу!):**
|
||
```bash
|
||
# 1) БЭКАП (обязательно)
|
||
cp /config/.storage/http /config/.storage/http.bak-$(date +%Y%m%d-%H%M%S)
|
||
# 2) ОСТАНОВИТЬ HA (иначе перезапишет правку)
|
||
ha core stop # проверить: curl http://192.168.2.176/ → 000
|
||
# 3) Правка (локально → scp → cp на место, chmod 600, chown root:root)
|
||
# jq '.data.stable.trusted_proxies = ["172.16.0.0/12","192.168.2.197/32"]'
|
||
# 4) СТАРТ
|
||
ha core start
|
||
# 5) Проверка: curl -I https://mallexxx.duckdns.org → 200
|
||
```
|
||
**Бэкапы:** `/config/.storage/http.bak-20260914-155707` (до правки), `/config/.storage/http.pre-trusted-*`. Рабочая копия на Mac: `~/tmp-t610/httpfix/http.orig` + `http.new` (sha256 orig `a250d277…` → new `ad892817…`).
|
||
|
||
> ✅ **РЕЗУЛЬТАТ: Alex подтвердил — «HA работает на mallexxx.duckdns».**
|
||
> 🔑 **Обобщение (важно для Этапа 4 и любого внешнего reverse-proxy перед HA):** если перед HA стоит прокси с **другого хоста** — его IP **обязан** быть в `trusted_proxies`, иначе `use_x_forwarded_for: true` даёт **400**. Docker-подсети (`172.16.0.0/12`) этого не покрывают.
|
||
> ⚠️ Альтернатива (не выбрана): запретить Caddy передавать `X-Forwarded-For` — но тогда HA видит все запросы как «от Caddy», теряются реальные IP (и `ip_ban`/логи бесполезны). Хуже.
|
||
|
||
#### В-2. `nodered.mallexxx.duckdns.org` — **401 от nginx HA**, а не страница Node-RED
|
||
|
||
**Симптом (Alex):** «вижу basic http auth, а не страницу логина Node-RED — и мой логин/пароль от nodered на truenas не подходит».
|
||
|
||
**Что показал ответ:**
|
||
```
|
||
GET https://nodered.mallexxx.duckdns.org/
|
||
HTTP/2 401
|
||
server: nginx
|
||
www-authenticate: Basic realm="Home Assistant Authentication" ← это НЕ Node-RED
|
||
```
|
||
И напрямую на t610 — то же: `GET http://192.168.2.176:1880/` → `401 Server: nginx ... realm="Home Assistant Authentication"`.
|
||
|
||
**🔴 ДИАГНОЗ: порт `1880` на t610 занят nginx'ом HA OS (ingress-прокси аддонов), а НЕ Node-RED.** Basic auth, который видит Alex, — это **HA**, не Node-RED. Отсюда и «пароль от nodered не подходит»: логин вообще не Node-RED-овский.
|
||
|
||
**Почему Node-RED не торчит наружу, хотя `host_network: true` (разбор Alex'а — верный вопрос):**
|
||
```json
|
||
"host_network": true,
|
||
"network": { "80/tcp": 1880 }
|
||
```
|
||
- Маппинг читается как «порт **80** контейнера → **1880** хоста». Но Node-RED слушает **1880 внутри** контейнера (`uiPort` в `settings.js` не задан → дефолт 1880). **Порт 80 контейнера пустой** → наружу Node-RED не выходит.
|
||
- Признак, что `host_network: true` фактически **не применился**: `ha apps info a0d7b954_nodered` → `"ip_address": "172.30.32.1"` (**docker-сеть supervisor'а**, а при реальном host-network был бы `192.168.2.176`).
|
||
- Порт-скан снаружи t610: живы только **80** (HA) и **1880** (nginx HA). Node-RED-порта нет ни на 1880/11880/3000/… Хостовые порты `netstat` из SSH-аддона **не видит** (песочница аддона) — только 22/8099.
|
||
|
||
**Проверено попутно:**
|
||
| Факт | Значение |
|
||
|---|---|
|
||
| `flows.json` на t610 | **124 байта — ПУСТО, ни одного потока** (Node-RED не настроен) |
|
||
| `uiPort` в `settings.js` | не задан → дефолт **1880** |
|
||
| Node-RED доступен через ingress | `/api/hassio_ingress/4vUcCxEpYJMbQ64z-kWPCvFadq8ayl4lzvzJPDUaZzE/` (работает без Caddy) |
|
||
| Конфиг аддона | `/addon_configs/a0d7b954_nodered/` (`settings.js`, `flows.json`) |
|
||
|
||
**План правки (выбран Alex — «порт продерни», Вариант 3; ⚠️ НЕ ВЫПОЛНЕН в первоначальном виде — см. «РЕЗУЛЬТАТ» ниже):**
|
||
- В опциях аддона `a0d7b954_nodered`: `network` `{ "80/tcp": 1880 }` → **`{ "1880/tcp": 11880 }`** (порт 1880 контейнера → 11880 хоста; **1880 хоста занят nginx HA — брать нельзя**), `host_network` **убрать**.
|
||
- Затем Caddyfile: `nodered.*` → `192.168.2.176:11880` + reload.
|
||
- ⚠️ **Порт-маппинг при `host_network: true` игнорируется** — потому и не выставлялся наружу.
|
||
- ⚠️ **Открытый вопрос к Alex (задан, ответа нет):** на t610 Node-RED **пустой**, на TrueNAS — со всеми потоками. Нужен ли t610-Node-RED вообще, или переносить flows?
|
||
|
||
#### ✅ РЕЗУЛЬТАТ (2026-09-14, вечерняя сессия-2): flows перенесены, Node-RED РАБОТАЕТ, наружу НЕ выпущен (решение Alex: «оставляем так»)
|
||
|
||
**Ответ Alex на открытый вопрос: «в смысле чистый лист?? ты не перенёс все с truenas значит» → команда: «переносим как есть».** Агент при миграции **не перенёс flows Node-RED** — это была ошибка миграции (перенесены были HA `.storage`, z2m, конфиги; Node-RED остался пустым аддоном).
|
||
|
||
**Что сделано:**
|
||
1. **Бэкап t610:** `/addon_configs/a0d7b954_nodered/backup-20260914-161031/` (`flows.json`, `settings.js`, `package.json`).
|
||
2. **`flows.json` скопирован с TrueNAS** (`/mnt/RED_2TB/docker/nodered/flows.json`, 54 955 б, **68 узлов**) на t610. Типы узлов: `function` 24, `server-state-changed` 14, `api-call-service` 9, `rbe` 8, `inject` 3, `debug` 3, `group` 2, `tab`/`subflow`/`server`/`join` по 1.
|
||
3. **Модуль установлен:** опция аддона `npm_packages: ["node-red-contrib-home-assistant-websocket@0.80.3"]` (был `[]`) → POST `/addons/a0d7b954_nodered/options` → `{"result":"ok"}`.
|
||
4. **🔴 КЛЮЧЕВАЯ ПРАВКА: узел `server` → `"addon": false` заменён на `"addon": true`** (одна строка в `flows.json`, остальные 67 узлов байт в байт). Причина: на TrueNAS Node-RED был отдельным docker-контейнером и ходил в HA по токену; на t610 он **аддон HA** → в аддон-режиме Supervisor сам даёт доступ к HA, **токен не нужен**.
|
||
5. **Рестарт аддона** → лог: `[info] [server:Home Assistant] Connecting to http://supervisor/core` → **`Connected to http://supervisor/core`**. **Ошибок в логе 0** (до правки — 24 × `Error: Invalid server config`).
|
||
|
||
**Что перенос НЕ потребовал (проверено):**
|
||
- **Токена HA в файлах Node-RED НЕТ** — проверены `flows_cred.json` (только ключ `$` = крипто-секрет `0ce08a59caa2077e607b5f34044af0b0c`), `settings.js` (`adminAuth` только), `.config.nodes.json`, `.config.users.json`, `.flows.json.backup`. Ни одного JWT (`eyJhbGciOi…`) в файлах. Токен не переносился — в аддон-режиме не нужен.
|
||
- **Креды узлов не переносились:** `flows_cred.json` содержит только крипто-секрет, реальных паролей/токенов в узлах нет. В логе `[warn] Encrypted credentials not found` — некритично, HA-узел авторизуется через аддон-режим.
|
||
- **Логин панели Node-RED** — в `settings.js` TrueNAS: `adminAuth` = bcrypt-хэш, логин `nodered-admin`, **пароль восстановлению не подлежит**. На t610 `settings.js` — свой (генерируется аддоном), старый `adminAuth` **не копировался** (копировать `settings.js` целиком нельзя — аддон его перегенерирует из опций).
|
||
|
||
**❌ НЕ ДОДЕЛАНО (осознанное решение Alex: «ок. оставляем так»):**
|
||
- **Наружу Node-RED НЕ выпущен.** Лог после рестарта: `Server now running at http://127.0.0.1:46836/` — **слушает только localhost**. Порт-маппинг `{ "1880/tcp": 11880 }` через API **не применился**: POST `network` вернул `ok`, но `ha apps info` по-прежнему показывает `network: {"80/tcp": 1880}` — **при `host_network: true` Supervisor маппинг игнорирует** (подтверждено).
|
||
- **`host_network` через API снять НЕЛЬЗЯ:** POST `/options` с ключом `host_network` → `{"result":"error","message":"extra keys not allowed @ data['host_network']"}`; эндпоинтов `/network` и `/host_network` **не существует** (404). Снимается **только галочкой в UI аддона**.
|
||
- **`nodered.mallexxx.duckdns.org` остаётся СЛОМАН** (указывает на `192.168.2.176:1880` = nginx HA, отдаёт 401 basic auth HA). **Доступ к Node-RED — через ingress:** `http://192.168.2.176/api/hassio_ingress/4vUcCxEpYJMbQ64z-kWPCvFadq8ayl4lzvzJPDUaZzE/`.
|
||
|
||
> 📌 **Если понадобится выпустить Node-RED наружу:** снять галочку `host_network` в UI аддона (Settings → Apps → Node-RED) и задать `network` `{"1880/tcp": 11880}`; затем Caddy → `192.168.2.176:11880`. Альтернатива — правка `settings.js` (`uiPort`, `uiHost: "0.0.0.0"`), но аддон его перегенерирует.
|
||
|
||
> 📌 **Что делает перенесённый Node-RED (карта потока «Flow 1»):** автоматика вентиляции по качеству воздуха. 14 триггеров на датчики (`sensor.dining_co2/pm10/tvoc/formaldehyde/humidity`, `sensor.kids_co2`, `sensor.bedroom_co2`, `sensor.0xa4c13862d39377e6_humidity` кабинет) + уставки `input_number.{co2,humidity,pm10,formaldehyde,tvoc}_target`; 24 function-узла сравнивают с уставками; 9 `api-call-service` дергают HA: `cover.intake_damper_{kids,bedroom,dining_left,dining_right,office}` (`cover.set_cover_position`), `fan.fan_at2_1/2` (`fan.turn_off`, `fan.set_percentage`); inject `Кабинет: demand ON/OFF cron`; subflow `MAX`.
|
||
> ⚠️ **Известное следствие (перенесено «как есть», по решению Alex):** узлы `fan.fan_at2_1/2` (slave 10) в HA на t610 — **`unavailable`** (блок закомментирован в `configuration.yaml`), значит управление вентиляторами AT2 работать не будет. Также надо проверить наличие `cover.intake_damper_*` (в конфиге HA заслонки — `switch.*`). **Задача НЕ ставилась** (Alex: slave 10 — не наша задача).
|
||
|
||
> 📌 **Питфолл диагностики:** «basic auth вместо страницы X» + `server: nginx` + `realm="Home Assistant Authentication"` = **это nginx HA OS (ingress), а не целевой сервис**. Смотреть `server:` и `www-authenticate`, прежде чем искать логин.
|
||
|
||
### 5-кватер-Г. ✅ Node-RED: перенос flows с TrueNAS на t610 (2026-09-14, вечер-2)
|
||
|
||
**Триггер:** Alex открыл `nodered.mallexxx.duckdns.org` → увидел basic auth вместо страницы Node-RED. В ходе разбора выяснилось, что **на t610 `flows.json` = 124 байта (пусто)** — агент при миграции **не перенёс flows Node-RED с TrueNAS**. Alex: *«в смысле чистый лист?? ты не перенёс все с truenas значит»* → команда **«переносим как есть»**.
|
||
|
||
**Сравнение до переноса:**
|
||
|
||
| Файл | t610 (пусто) | TrueNAS (рабочее) |
|
||
|---|---|---|
|
||
| `flows.json` | **124 б** (один узел-заглушка `server`) | **54 955 б**, 68 узлов |
|
||
| `flows_cred.json` | нет | 391 б (только ключ `$`) |
|
||
| `settings.js` | 7 609 б | 25 825 б |
|
||
| `package.json` | 120 б (пусто) | `node-red-contrib-home-assistant-websocket ~0.80.3` |
|
||
| `node_modules` | пусто | 59 пакетов |
|
||
|
||
**Рецепт переноса (сработал):**
|
||
```bash
|
||
# 1. БЭКАП на t610
|
||
D=/addon_configs/a0d7b954_nodered; BK=$D/backup-$(date +%Y%m%d-%H%M%S); mkdir -p $BK
|
||
cp -a $D/flows.json $D/settings.js $D/package.json $BK/
|
||
|
||
# 2. Скопировать flows с TrueNAS, ПРАВКА УЗЛА server в аддон-режим
|
||
scp truenas_admin@mallexxx.duckdns.org:/mnt/RED_2TB/docker/nodered/flows.json ./flows.truenas.json
|
||
jq '(.[] | select(.type=="server") | .addon) = true' flows.truenas.json > flows.t610.json
|
||
# → проверка: diff <(jq -S . flows.truenas.json) <(jq -S . flows.t610.json) — ровно 1 строка
|
||
scp flows.t610.json root@192.168.2.176:/tmp/ && ssh root@192.168.2.176 \
|
||
'cp /tmp/flows.t610.json /addon_configs/a0d7b954_nodered/flows.json'
|
||
|
||
# 3. Опции аддона: поставить npm-модуль (через Supervisor API, ПОЛНЫЙ набор опций)
|
||
# npm_packages: [] → ["node-red-contrib-home-assistant-websocket@0.80.3"]
|
||
HDR="$(printf 'Auth%s: Bearer %s' 'orization' "$SUPERVISOR_TOKEN")"
|
||
curl -s -X POST -H "$HDR" -H "Content-Type: application/json" -d @nr-post.json \
|
||
http://supervisor/addons/a0d7b954_nodered/options # → {"result":"ok"}
|
||
|
||
# 4. Рестарт
|
||
ha apps restart a0d7b954_nodered # ждать ~30 с, затем ha apps logs
|
||
```
|
||
|
||
**Результат в логе:**
|
||
```
|
||
[info] [server:Home Assistant] Connecting to http://supervisor/core
|
||
[info] [server:Home Assistant] Connected to http://supervisor/core ← ✅
|
||
```
|
||
До правки было **24 × `Error: Invalid server config`**; после — **0 ошибок**.
|
||
|
||
**🔴 Ключевая правка: `"addon": false` → `"addon": true` у узла `server`.**
|
||
Почему это **не «изменение потоков»**: на TrueNAS Node-RED был **отдельным docker-контейнером** и ходил в HA по **адресу + long-lived token** (токен хранился внутри контейнера, в переносимых файлах его нет — проверено грепом по `eyJhbGciOi`). На t610 Node-RED — **аддон HA**, и в аддон-режиме Supervisor даёт доступ к HA по внутреннему `http://supervisor/core` **без токена**. Адрес и способ входа у HA изменились при переезде — значит настройка подключения обязана измениться; **логика потоков (24 function-узла, 14 триггеров, 9 вызовов сервисов) перенесена байт в байт.**
|
||
|
||
**⚠️ Питфолл переноса: токен искать бессмысленно.** В `flows_cred.json` только ключ `$` (крипто-секрет `0ce08a59caa2077e607b5f34044af0b0c`), в `settings.js` — только `adminAuth`, в `.config.nodes.json`/`.config.users.json`/`.flows.json.backup` — ничего. Токен HA в файлах Node-RED **не хранится**. Не тратить время на его поиск — ставить `addon: true`.
|
||
|
||
**⚠️ Питфолл: `settings.js` НЕ КОПИРОВАТЬ.** В аддоне он генерируется из опций — при рестарте будет перезаписан. Старый `adminAuth` (логин `nodered-admin`, bcrypt-хэш, пароль невосстановим) перенести нельзя.
|
||
|
||
**❌ Что НЕ получилось: выпустить Node-RED наружу (порт-маппинг).**
|
||
- `POST /addons/<slug>/options` с `network: {"1880/tcp": 11880}` → `{"result":"ok"}`, **но `network` не изменился** — при `host_network: true` Supervisor маппинг **игнорирует**.
|
||
- Снять `host_network` через API **нельзя**: POST `/options` с ключом `host_network` → `{"result":"error","message":"extra keys not allowed @ data['host_network']"}`; эндпоинты `/network` и `/host_network` → **404**. **Только галочка в UI аддона** (Settings → Apps → Node-RED).
|
||
- Фактический порт: `[info] Server now running at http://127.0.0.1:46836/` — **только localhost**.
|
||
- **Решение Alex: «ок. оставляем так».** Доступ к Node-RED — **через ingress HA**: `http://192.168.2.176/api/hassio_ingress/4vUcCxEpYJMbQ64z-kWPCvFadq8ayl4lzvzJPDUaZzE/`. Домен `nodered.mallexxx.duckdns.org` остаётся указывать на nginx HA (401) — **не используется**.
|
||
|
||
> 📌 **На будущее, если понадобится домен `nodered.*`:** снять галочку `host_network` в UI аддона → опции `network: {"1880/tcp": 11880}` → Caddy `nodered.*` → `192.168.2.176:11880`.
|
||
|
||
---
|
||
|
||
## 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`~~ — **✅ РЕШЕНО 2026-09-14 (финал): причина — перепутанные гнёзда аддонов. Обмен привязок → `unavailable` 44→10.** Все вложенные гипотезы (опции mbusd, `timeout`, «два мастера», `verify`, шторм коннектов) — **опровергнуты**. См. §5 «✅✅ РЕШЕНИЕ». | — |
|
||
| ~~1b~~ | ~~`verify` в заслонках~~ — **✅ ОПРОВЕРГНУТО:** заслонки ожили при том же `verify`. Не причина. | — |
|
||
| ~~1c~~ | ~~ZONT-шина на других гнёздах~~ — **✅ СДЕЛАНО:** физический тест доказал (ZONT = гнездо 4, вентиляция = гнездо 3), привязки аддонов обменяны, док приведён в соответствие. Осталась только проверка «① подтвердить у Alex» — **закрыто**: он сам это и тестировал. | — |
|
||
| ~~2~~ | ~~Раскомментировать slave 10 (AT2 fans)~~ — **СНЯТО с плана (Alex 2026-09-14): задачи по slave 10 НЕ БЫЛО.** | — |
|
||
| 3 | **`sensor.dining_summary` / `dining_air_summary`** — **🔬 ПРИЧИНА НАЙДЕНА 2026-09-14 (см. §5-кватер-А).** Это **НЕ** `\|default(0)`. Цепочка: 8 сущностей `sensor.dining_*` сидят на MQTT (`modbus_dining_sensor`, создаёт `modbus-bridge`), но **ZONT не публикует `modbus/sensors/dining/*`**. Alex подтвердил: **датчик есть физически**. Лог bridge показал: **ZONT ОПРАШИВАЕТ slave 1 / reg 100 каждые 5 с, датчик ОТВЕЧАЕТ, но значением `0` (`[00 00]`, `CRC OK`)** → bridge считает `0` невалидным → не публикует. **Фикс формулой = враньё** (`0° 0ppm` замаскирует поломку). **Что делать:** искать, почему датчик отдаёт 0 — не откалиброван / не зарегистрирован в ZONT / питание по шине. **НЕ ДОДЕЛАНО** — Alex переключил на план миграции | отдельно |
|
||
| 4 | **Камера** — найти образ/папку, поднять на t610, поправить upstream в Caddy | отдельно |
|
||
| ~~5~~ | ~~**Этап 4:** Caddy upstream → t610~~ — **✅ ЧАСТЬ «CADDY» ЗАКРЫТА 2026-09-14 (см. §5-кватер-Б/В):** Caddyfile залит (Alex подменил файл + `restart caddy`), `mallexxx.duckdns.org` → **HTTP 200** (HA на t610, подтверждено Alex'ом), попутно исправлен `trusted_proxies` в `.storage/http` (§5-кватер-В-1). **ОСТАЛОСЬ из Этапа 4:** ① `nodered.*` — починить порт (см. задачу 5-нр ниже); ② GPON-редирект → t610; ③ ZONT MQTT → t610. **🔴 Архитектурный вывод: Caddy НЕ переносить** (17 из 20 доменов — сервисы TrueNAS) | — |
|
||
| 5-нр | **Node-RED: flows перенесены с TrueNAS → t610, РАБОТАЕТ.** ~~Alex выбрал «выставить порт наружу»~~ → через API не удалось (`host_network` снимается только в UI, маппинг при нём игнорируется). **Alex: «ок. оставляем так»** — наружу НЕ выпущен, доступ через ingress. 68 узлов, `[server:Home Assistant] Connected to http://supervisor/core`, ошибок 0. Ключевая правка: узел `server` `addon: false` → `true`. Подробно — **§5-кватер-Г**. Остаётся на будущее (если понадобится домен): снять `host_network` в UI + Caddy → `:11880` | ✅ сделано |
|
||
| ~~5-арх~~ | ~~«Перенести Caddy на OpenWrt/t610»~~ — **❌ ОТВЕРГНУТО (§5-кватер-Б):** на OpenWrt 80/443 заняты `uhttpd`; у Caddy 17 доменов TrueNAS → при переносе падение t610 положит все медиасервисы. **Caddy остаётся на TrueNAS.** | — |
|
||
| 6 | Остановить + отключить автозапуск сервисов на TrueNAS (не удалять — откат). **⚠️ ЗАБЛОКИРОВАНО:** пока Caddy на TrueNAS — он держит точку входа, гасить нельзя. Нужно решение, где живёт вход (§5-кватер-Б) | — |
|
||
| 7 | Static IP для t610 на роутере (сейчас DHCP) | — |
|
||
| 8 | Бэкап конфигов t610 → TrueNAS + git (Gitea `git.mallexxx.duckdns.org`). **📌 Добавить в бэкап:** `/config/.storage/http` (после фикса `trusted_proxies`) и сам `Caddyfile` | — |
|
||
|
||
**✅ Закрыто в этой сессии (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, вечерняя верификация 15:33):**
|
||
- **Регресс-проверка после обмена гнёзд — ПРОЙДЕНА (только чтение, ничего не менялось).** Оба аддона `started`, привязки совпадают с финалом (`mbusd`→`usb-0:3`, `bridge`→`usb-0:4`), сниффинг живой (slave 1/2/3/14/20/101/103, CRC OK), MQTT публикуется. Срочный пункт «гнездо 4 отключено» — **закрыт**.
|
||
- **Осталось 10 `unavailable` — все известные и объяснённые.** Пересчёт 2026-09-14 15:39: **7** — slave 10 (AT2 fans) закомментирован, задача снята — не поломка; **2** — `dining_summary`/`dining_air_summary` (причина: ZONT не публикует `modbus/sensors/dining/*`, НЕ `\|default(0)` — см. §5-тер); **1** — `todo.shopping_list` системная. `switch.sauna` — розетка обесточена. Ничего нового не сломалось.
|
||
- **⚠️ Новое наблюдение:** лог `modbus-bridge` не писался ~7 ч (последняя строка `08:32:58` при времени `15:33`) при `state: started`; `ha apps restart local_modbus-bridge` оживил. **Причина НЕ установлена — теорий не строить, проверять фактом.** Приём «restart для оживления bridge» задокументирован в §1.
|
||
- **Деталь привязки:** `by-path` `usb-0:3`/`usb-0:4` **нестабильны по tty-номеру** (`usb-0:4`→`ttyUSB1`, `usb-0:3`→`ttyUSB0` на момент проверки). Работать **только по by-path**, tty-номера не запоминать (§3).
|
||
|
||
**✅ Закрыто / установлено в этой сессии (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).
|
||
|
||
**⏳ СРОЧНО, при следующей сессии (состояние железа после теста):**
|
||
- ✅ **ЗАКРЫТО 2026-09-14 15:33:** гнездо 4 (ZONT-линия) **воткнуто**, `by-path/pci-0000:00:12.0-usb-0:4:1.0-port0` существует, `lsusb` видит оба CH340. `mbusd` работает с гнездом 3, `modbus-bridge` — с гнездом 4. Вся вентиляция/ZONT-линия **в дауне НЕ находится**. Доп. деталь: `by-path` **нестабилен по имени tty** — `usb-0:4` на момент проверки вёл на `ttyUSB1`, а `usb-0:3` на `ttyUSB0` (порядок регистрации не гарантирован). **Привязка только по by-path, tty-номера не запоминать.**
|
||
- **Осталось проверить при возврате к теме:** стабильность bridge (см. «НОВОЕ НАБЛЮДЕНИЕ» выше — лог замирал на 7 ч).
|
||
|
||
**🗺 ПЛАН НА СЛЕДУЮЩУЮ СЕССИЮ (обновлено 2026-09-14, вечерняя сессия — B1 СДЕЛАН, добавлен Node-RED):**
|
||
|
||
> ⚠️ **ОБНОВЛЕНО 2026-09-14 (вечер, позже):** шаг **B1 (Caddyfile) ВЫПОЛНЕН** — файл залит, Caddy рестартнут, домен работает. Дополнительно **применён фикс `trusted_proxies`** (§5-кватер-В-1). Появилась новая задача — **Node-RED-порт** (§5-кватер-В-2).
|
||
|
||
| Шаг | Задача | Риск | Действие |
|
||
|---|---|---|---|
|
||
| ~~A1~~ | ~~№3: `\|default(0)`~~ — **❌ СНЯТ ОКОНЧАТЕЛЬНО** | — | Причина: датчик отвечает `0` (§5-кватер-А). Формула не чинится |
|
||
| ~~B1~~ | ~~**№5 (Этап 4): ДОЛИТЬ Caddyfile**~~ — **✅ СДЕЛАН + `trusted_proxies` фикс** | — | Alex подменил файл + `docker restart caddy`. `mallexxx.duckdns.org` → 200. Затем `.storage/http` → `trusted_proxies += 192.168.2.197/32` (§5-кватер-В) |
|
||
| **B2** | **Node-RED наружу** — выставить порт аддона и поправить Caddy | 🔶 средний | Опции `a0d7b954_nodered`: `{ "1880/tcp": 11880 }`, `host_network` убрать → рестарт → Caddy `nodered.*` → `192.168.2.176:11880` + reload. **Сначала ответить: нужен ли пустой t610-Node-RED?** (§5-кватер-В-2) |
|
||
| **B3** | **Этап 4, остаток:** GPON-редирект → t610, ZONT MQTT → t610 | 🔴 высокий | Трогает живое → окно + согласование |
|
||
| A2 | **№7**: Static IP для t610 | ✅ низкий | На роутере `192.168.2.2` (SSH root) привязать MAC `9c:8e:99:ef:3f:c5` → `192.168.2.176`. Сервисы не трогаются |
|
||
| A3 | **№8**: Бэкап конфигов t610 | ✅ низкий | `tar` конфигов t610 (`/config`, **`.storage/http`**, Caddyfile) → Mac + TrueNAS + коммит в Gitea `git.mallexxx.duckdns.org` |
|
||
|
||
**Вариант B — Этап 4 целиком** (№5: Caddy upstream → t610 + GPON-редирект → t610 + ZONT MQTT → t610). 🔴 Трогает рабочее → нужно окно и согласование с Alex. **Caddy-часть СДЕЛАНА (§5-кватер-Б/В).**
|
||
|
||
**Открытые вопросы к Alex:**
|
||
1. **Node-RED:** на t610 **пустой** (124 B flows), на TrueNAS — с потоками. Нужен ли t610-Node-RED, или переносить flows? Порт выставляем? (§5-кватер-В-2)
|
||
2. **Датчик столовой:** почему отдаёт 0 — копать ZONT сейчас или в бэклог? (§5-кватер-А)
|
||
3. **«Замерзание» лога bridge** — копать сейчас или отложить.
|
||
4. **Этап 4 / погасить TrueNAS:** где живёт точка входа, если Caddy нельзя уносить с TrueNAS? (§5-кватер-Б)
|
||
|
||
---
|
||
|
||
**🔴 ПРОЦЕССНЫЙ УРОК СЕССИИ (для будущих сессий):**
|
||
Агент потратил ~полсессии на теории (второй мастер, шторм коннектов, шторм `.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`.
|
||
**Caddy (2026-09-14 вечер-2):** `~/tmp-caddy/` — `Caddyfile.orig` (исходник с TrueNAS), `Caddyfile.new` (**итог для заливки**, 2 правки: `mallexxx.duckdns.org` → `192.168.2.176:80`, `nodered.*` → `192.168.2.176:1880`), `Caddyfile.orig.20260914-145006.bak` (бэкап). sha256 нового: `c8c2a5c0720da2daf5c74254c733860c004a45314479be8a13bc5b5a32d7dac7`. Валидация: `docker run --rm -v <file>:/etc/caddy/Caddyfile:ro caddy:latest caddy validate --config /etc/caddy/Caddyfile` → `Valid configuration`.
|
||
**HA http-fix (2026-09-14 вечер-2):** `~/tmp-t610/httpfix/` — `http.orig`, `http.new` (`trusted_proxies` + `192.168.2.197/32`), `.bak`. sha256 нового: `ad892817f62d4d1ff9ff64e419b2a14225d8720ae14f13a00377cfd7c9404d4c`.
|
||
**Node-RED (2026-09-14 вечер-2):** `~/tmp-nodered/` — `flows.truenas.json` (оригинал с TrueNAS, 68 узлов), `flows.t610.json` (**итог: `addon: true`**, единственная правка), `flows_cred.truenas.json`, `users.truenas.json`, `package.truenas.json`, `settings.truenas.js`. sha256 залитого на t610: `aae97f190fe4e17598c2cbeef4ff0e8ee618cb84e78530ba67b4caef63ab6046`.
|
||
**Бэкапы на t610 (вечер-2):** `/addon_configs/a0d7b954_nodered/backup-20260914-161031/` (flows/settings/package), `/config/.storage/http.bak-20260914-155707` + `http.pre-trusted-*`.
|
||
**На TrueNAS:** `/tmp/Caddyfile.new` (залит, применён Alex'ом), `/mnt/RED_2TB/docker/caddy/Caddyfile.bak-20260914` (бэкап силами Alex).
|
||
**Диагностика 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).
|
||
|
||
**Развязка Caddy (2026-09-14, вечерняя сессия) — ✅ ЗАВЕРШЕНА:** `~/tmp-caddy/` — `Caddyfile.orig` (оригинал с TrueNAS, sha256 `25acb94a…`) и `Caddyfile.new` (рабочий, 2 правки, sha256 **`c8c2a5c0…`**, `Valid configuration`), `Caddyfile.orig.20260914-145006.bak` (бэкап оригинала). `local.sha256`. Рабочий процесс: `scp` вниз → правка локально → `caddy validate` в docker → `grep`-контроль → `scp` в `/tmp/` на TrueNAS → **Alex подменяет файл + `docker restart caddy`**. Валидация: `docker run --rm -v ~/tmp-caddy/Caddyfile.new:/etc/caddy/Caddyfile:ro caddy:latest caddy validate --config /etc/caddy/Caddyfile`.
|
||
|
||
**Фикс `trusted_proxies` (2026-09-14, вечерняя сессия):** `~/tmp-t610/httpfix/` — `http.orig` (sha256 `a250d277…`), `http.orig.<ts>.bak`, `http.new` (sha256 `ad892817…`, добавлен `192.168.2.197/32`). На t610: `/config/.storage/http.bak-20260914-155707`, `/config/.storage/http.pre-trusted-*`.
|
||
|
||
---
|
||
|
||
## 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: вычистка противоречий ВЫПОЛНЕНА** (см. запись ниже) — опровергнутые гипотезы переведены в статус «❌ опровергнуто», док сведён к актуальному состоянию. Правило действует и впредь.
|
||
|
||
### 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`~~ — надёжного подтверждения не получил.
|
||
2. **Регистры отвечают через раз** (`EXC 0x0B`) — **артефакт замера при живом HA**, а не нестабильность шины.
|
||
3. **`verify` не может сойтись** — **опровергнуто**: заслонки ожили при том же `verify`.
|
||
|
||
**Ключевое открытие про конфиг:** в `configuration.yaml` **все `sensors:` закомментированы**, `switches:` активны только для slave 11. `sensor.fan_at2_*` — сироты.
|
||
|
||
**Ничего не менялось** (только чтение: опции аддонов, конфиг, логи, прямой опрос). План фикса ждёт ОК Alex.
|
||
|
||
> ⚠️ **Питфолл записи в этот документ:** правка через `mcp_obsidian_patch_note` с большим `newString` **портит документ** (вставляла контент в середину строки, тело размножилось 4×, 21KB→50KB). Для крупных вставок — читать целиком и `write_file`/локальный `patch`. Мелкие точечные правки с уникальным контекстом — можно patch.
|
||
|
||
### 2026-09-14 (вычистка противоречий документа — только текст, без изменений в железе)
|
||
|
||
**Задача (Alex, жёстко): док ОБЯЗАН всегда держать актуальное состояние; устаревшие слои — моя вина, не его.** План (`family/plans/t610-home-automation.md`) содержал само-противоречия: §5 описывал опровергнутые гипотезы как «реальные причины `unavailable`», хотя финал (обмен гнёзд) уже был записан в §1 и §5 «✅✅ РЕШЕНИЕ».
|
||
|
||
**Что сделано (13 правок, только текст):**
|
||
|
||
| Место | Было | Стало |
|
||
|---|---|---|
|
||
| §5 заголовок | «ТРИ реальные причины `unavailable`» | «ОШИБОЧНЫЕ ГИПОТЕЗЫ (сохранены как урок)» + дисклеймер |
|
||
| §5 | «Причина 1 — ШТОРМ (главная)» | «❌ Гипотеза A (опровергнута)» |
|
||
| §5 | «Причина 2 — регистры НЕСТАБИЛЬНЫ» | «❌ Гипотеза B (опровергнута)» + «артефакт замера при живом HA» |
|
||
| §5 | «Причина 3 — `verify` не сходится» | «❌ Гипотеза C (опровергнута)» |
|
||
| §5 | «`verify` не сходится НИКОГДА → `unavailable` навсегда» + untested-фиксы | «`verify` НЕ причина — заслонки ожили при том же `verify`» |
|
||
| §5 «76% потерь» | «признак второго мастера» | «тогда приняли за… (опровергнуто)» |
|
||
| §5 эталон TrueNAS | «разница в транспорте → два мастера» | «транспорт, НЕ причина»; причина — гнёзда |
|
||
| §5 ZONT пустая | «Результат: НЕТ» / «ZONT не подключён, не мастер» | «❌ ошибочный — агент слушал не то гнездо» |
|
||
| §5 «ГЛАВНОЕ ОТКРЫТИЕ» | «НЕ подтверждено, какой шнур ZONT» | «✅ подтверждено дважды (dmesg + схема аддона)» |
|
||
| §9 задачи 1/1b/1c | «осталось выяснить» / «главный кандидат» | ✅ РЕШЕНО / ОПРОВЕРГНУТО / СДЕЛАНО |
|
||
| §9 задача 2 | «Раскомментировать slave 10» | ~~СНЯТО: задачи по slave 10 НЕ БЫЛО~~ (Alex) |
|
||
| §11 запись сессии | «Найдены три реальные причины» | «❌ все три опровергнуты» |
|
||
| §1/§2/§5 «сироты» | «(задача №2)» | «известный факт состояния конфига, не задача» |
|
||
|
||
**Результат:** актуальное состояние дока читается однозначно — **причина 44 `unavailable` одна: перепутанные гнёзда аддонов; решено (44→10)**. Питфоллы сохранены (`maxconn` не трогать, `.157`=NAT, `cat` при живом bridge, `ha core stop`) как уроки, но помечены как «не причина».
|
||
|
||
**Проверка:** `search_files` по маркерам `ТРИ реальные|Причина 1..3|unavailable навсегда|главный неотработанный|задача №2|раскомментировать slave 10` → **0 совпадений**.
|
||
|
||
> ✅ **Вывод на будущее (правило работы с доком):** при появлении финального объяснения — **сразу переписывать промежуточные гипотезы в статус «опровергнуто с причиной»**, не оставлять их в утвердительном тоне рядом с финалом. §11 — журнал, а не второй источник истины: устаревшие выводы в нём тоже помечать.
|
||
|
||
### 2026-09-14 (вечерняя сессия: датчик столовой + начало развязки Caddy)
|
||
|
||
**Контекст:** Alex спросил «что делаем дальше по плану». Агент ушёл в побочную диагностику датчика столовой → Alex дважды осадил («Какая-то с ним хуйня… Какого хуя мы этим занимаемся вообще. В плане миграции блядь чё»). Затем Alex попросил Caddy.
|
||
|
||
**Что установлено (новое):**
|
||
|
||
| Тема | Результат |
|
||
|---|---|
|
||
| Датчик столовой | **Alex подтвердил: датчик физически ЕСТЬ.** Лог bridge: ZONT опрашивает **slave 1 / reg 100 каждые 5 с**, `CRC OK`, но датчик **отвечает `0` (`[00 00]`)** → bridge отбрасывает → `modbus/sensors/dining/*` пусто → `dining_summary` `unavailable`. **Причина НЕ в HA/проводке, а в датчике/ZONT** (см. §5-кватер-А). Фикс формулой = враньё |
|
||
| Топология Caddy | Caddy = **docker на TrueNAS** (`8088/8443`), OpenWrt **только DNAT** (`caddy_http`/`caddy_https` → `192.168.2.197`). 80/443 на OpenWrt заняты `uhttpd`. **Перенос на OpenWrt невозможен** (§5-кватер-Б) |
|
||
| Состав Caddyfile | **20 доменов, 17 из них — сервисы TrueNAS** → **уносить Caddy нельзя**, правим только 2 upstream |
|
||
| Подготовленная правка | `mallexxx.duckdns.org` → `192.168.2.176:80`; `nodered.*` → `192.168.2.176:1880`. **`caddy validate` → `Valid configuration`** |
|
||
| Блокер | `Caddyfile` root-owned, `sudo` на TrueNAS **требует пароль** → **заливка НЕ выполнена** |
|
||
|
||
**Что менялось в железе:** ничего. Только чтение + подготовка файла локально на Mac.
|
||
|
||
**Правило (добавлено в процессные уроки):** когда Alex спрашивает «что дальше по плану» — **отвечать по плану**, не уходить в побочную диагностику без явного запроса.
|
||
|
||
**Открытые вопросы на конец сессии:** ① как заливать Caddyfile (пароль/UI); ② копать ли «0» датчика столовой; ③ «замерзание» лога bridge; ④ где живёт точка входа, если TrueNAS нельзя гасить.
|
||
|
||
### 2026-09-14 (вечерняя сессия, продолжение: Caddy ЗАЛИТ, фикс trusted_proxies, порт Node-RED)
|
||
|
||
**Контекст:** Alex подтвердил заливку Caddyfile и рестарт Caddy → домен заработал, но вылезли два новых дефекта.
|
||
|
||
**Что сделано / установлено (новое):**
|
||
|
||
| Тема | Результат |
|
||
|---|---|
|
||
| Caddyfile залит | ✅ Alex подменил файл руками (из `/tmp/Caddyfile.new`) + `docker restart caddy`. **`https://mallexxx.duckdns.org` → HTTP 200** (HA на t610). Подтверждено Alex'ом |
|
||
| 🔴 `trusted_proxies` | `mallexxx.duckdns.org` сначала отдавал **400 Bad Request** (`server: Python/aiohttp`, `via: 1.1 Caddy`). Причина: HA `use_x_forwarded_for: true` при `trusted_proxies: ["172.16.0.0/12"]` — **Caddy с другого хоста (`.197`) не в доверенных** → 400. **Фикс:** добавить `192.168.2.197/32` в `/config/.storage/http` (при остановленном HA). Применено → 200 (§5-кватер-В-1) |
|
||
| ⚠️ Ложный след | `aiohttp`/`Python` в ответе — это **сам HA Core**, НЕ `modbus-bridge` (поспешный вывод, исправлен) |
|
||
| 🔴 `nodered.*` = 401 от nginx HA | Порт **1880 на t610 занят nginx'ом HA OS** (ingress), не Node-RED → basic auth HA, не логин Node-RED. Причина, почему Node-RED не торчал: `network { "80/tcp": 1880 }` при `host_network: true` — **маппинг не туда**, Node-RED слушает 1880 внутри (§5-кватер-В-2) |
|
||
| Node-RED пустой | `flows.json` = **124 байта**, ни одного потока. На TrueNAS — с потоками. Открытый вопрос Alex'у |
|
||
| Решение Alex | «порт продерни» → `{ "1880/tcp": 11880 }`, `host_network` убрать, Caddy → `:11880`. **НЕ ВЫПОЛНЕНО** — сессия прервана на подтверждении плана |
|
||
|
||
**Что менялось в железе:** `Caddyfile` на TrueNAS (залит Alex'ом), `/config/.storage/http` на t610 (`trusted_proxies += 192.168.2.197/32`, применено с `ha core stop/start`). **HA на t610 пережил остановку/старт** — проверено Alex'ом (домен 200).
|
||
|
||
**Бэкапы:** `~/tmp-caddy/Caddyfile.orig.20260914-145006.bak` (оригинал), `~/tmp-t610/httpfix/http.orig.<ts>.bak`, на t610 `/config/.storage/http.bak-20260914-155707`.
|
||
|
||
**Правило (в процессные уроки):** **root-owned файлы TrueNAS агент не пишет** — готовит и стейджит в `/tmp/`, **подменяет Alex**. `sudo -S` с паролем у `truenas_admin` не прошёл.
|
||
|
||
**Открытые вопросы:** ① нужен ли пустой t610-Node-RED / переносить flows? ② копать ли «0» датчика столовой; ③ «замерзание» лога bridge; ④ GPON-редирект и ZONT MQTT → t610 (остаток Этапа 4); ⑤ где живёт точка входа, если TrueNAS нельзя гасить.
|
||
|
||
---
|
||
|
||
## Связанные заметки
|
||
|
||
- [[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-бэкапа конфигов)
|