From b16407df72ece5d8ea2cfaf3e97173f99f56a3aa Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Tue, 25 Aug 2026 17:19:55 +0600 Subject: [PATCH] [2026-08-25] eagle: family/how-to/home-automation.md family/how-to/truenas-infrastructure.md family/plans/zont-modbus-bridge-udev-race-protection.md --- family/how-to/home-automation.md | 4 + family/how-to/truenas-infrastructure.md | 77 ++++++++++++----- ...zont-modbus-bridge-udev-race-protection.md | 84 +++++++++++++++++++ 3 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 family/plans/zont-modbus-bridge-udev-race-protection.md diff --git a/family/how-to/home-automation.md b/family/how-to/home-automation.md index 18f1a19a..f064665d 100644 --- a/family/how-to/home-automation.md +++ b/family/how-to/home-automation.md @@ -194,6 +194,10 @@ ZONT relays [8: Конвектор котельная - н.п.] ``` +> **ℹ️ Про «виртуальные sensor 101/102/103» и «недоступные датчики в ZONT»:** +> 101/102/103 — это **виртуальные Modbus-slaves**, которые контейнер `modbus-bridge` на TrueNAS подставляет на шине `ttyZONT`: реальные 485-датчики **Гостиная=1, Детская=2, Спальня=3** сниффятся и их температуры выдаются ZONT'у как датчики 101/102/103 (регистр 100). ZONT (Modbus master) опрашивает их как внешние датчики. +> Если `modbus-bridge` не запущен/не слушает `ttyZONT` → ZONT показывает эти датчики **«недоступные»**. Известная первопричина — гонка docker/udev после рестарта TrueNAS. Подробности и план защиты: `[[family/how-to/truenas-infrastructure.md#Проблема-modbus-bridge/mbusd-после-рестарта-TrueNAS-гонка-с-udev]]` и `[[family/plans/zont-modbus-bridge-udev-race-protection]]`. (Заметка обновлена 2026-08-25: добавлено пояснение про 101/102/103, ZONT relays не менялись.) + ## Карта регистров контроллера вентиляторов ``` diff --git a/family/how-to/truenas-infrastructure.md b/family/how-to/truenas-infrastructure.md index 91cbd94d..2146372f 100644 --- a/family/how-to/truenas-infrastructure.md +++ b/family/how-to/truenas-infrastructure.md @@ -1,15 +1,23 @@ # TrueNAS — инфраструктура -> Обновлено: 2026-08-24 (пересоздание закончено, пул ONLINE, остался подъём docker) +> Обновлено: 2026-08-25 (docker поднят; modbus-bridge/mbusd починены после гонки с udev) -> ## ✅ СТАТУС на 2026-08-24: пул ПЕРЕСОЗДАН и работает; docker НЕ поднят -> **Пул RED_2TB пересоздан и УЖЕ зареган в TrueNAS** — перезагрузка/импорт НЕ нужны. `zpool status` ONLINE (`mirror-0{sdc,sdf}`+`mirror-1{sde,sdd}`), `midclt call pool.query` id=1 RED_2TB ONLINE, топология обновлена. rsync возврата данных ЗАВЕРШЁН (storage 3.29T, immich 353G, backup 287G, Edu 214G, Photos 146G, old-bu 65G, docker, docker.bak, system — все смонтированы, `/mnt/RED_2TB/`). -> **⚠️ НЕ ГОТОВО — слой приложений (docker) НЕ восстановлен:** -> - `docker.service` — `inactive (dead)`, `disabled`. Образы/тома/контейнеры НЕ запущены. -> - **`.ix-apps` датасет (docker storage, был 24G) НЕ перенесён при выгрузке на IronWolf** и НЕ сохранён — rsync шёл из `/mnt/RED_2TB/*` и не включал монтируемый в `/mnt/.ix-apps` системный датасет. Проверено: `/mnt/.ix-apps` пуст, на IronWolf его нет, в `zfs list` нет. Образы = кеш → перекачаются из registry сами; конфиги целы в `/mnt/RED_2TB/docker/`. -> - **Контейнеры запускались через `docker-compose`, НЕ через TrueNAS Apps (TrueCharts/Custom App).** Управление в доке — `docker restart homeassistant`, `docker exec ...`; daemon.json `data-root:/mnt/.ix-apps/docker`. `.ix-apps` = просто data-root docker, не TrueNAS Apps. -> **TODO подъёма docker:** (1) создать датасет/папку под `data-root` (куда смотрит `/mnt/.ix-apps/docker`), (2) Docker engine: `systemctl enable --now docker` + проверить `docker info`, (3) поднять контейнеры: большинство уже имеют `docker-compose.yml` в `/mnt/RED_2TB/docker//` → `docker compose up -d`; **4 контейнера без compose (ha, webdav, modbus-bridge, zigbee2mqtt) восстанавливать вручную** — параметры и полный инвентарь в разделе «Инвентарь compose-файлов по папкам» ниже. Конфиги целы в `/mnt/RED_2TB/docker/`, образы перекачаются из registry (storage `.ix-apps` потерян = только кеш). -> **⚠️ НЕ ЗАКРЫТО отдельно:** TimeMachine (~277G, бэкапы macOS) НЕ возвращён — на IronWolf `/mnt/IRONWOLF/TimeMachine/` root-only NFSv4 ACL → rsync не докопировал. Алекс решение по нему не подтвердил («какой нахуй возврат time machine» — вероятно не нужен, старые снепшоты не важны). +> ## ✅ СТАТУС на 2026-08-25: пул ONLINE, docker поднят, почти все контейнеры Up +> **Пул RED_2TB пересоздан (2026-08-21) и работает:** `zpool status` ONLINE, rsync возврата данных завершён, `/mnt/RED_2TB/` смонтирован. +> **Docker: ПОДНЯТ на 2026-08-25.** Почти все контейнеры Up (zigbee2mqtt, webdav, vless-proxy, homeassistant, caddy, transmission, syncthing, mosquitto, nodered, immich*, gitea, filebrowser, portainer*, hermes-taiga и др.). Ещё не подняты/падают отдельные (library был Restarting; inpxer/inpx-web/arr/cups/ser2net не в списке — проверить при надобности). +> **⚠️ Известная проблема modbus-bridge/mbusd — гонка с udev при рестарте TrueNAS:** +> - После перезагрузки docker стартует раньше udev, поэтому `/dev/ttyZONT` и `/dev/ttyVent` на момент старта контейнеров ещё не существуют. +> - Контейнеры с `devices: /dev/ttyZONT:...` падают на `start` с `error gathering device information while adding custom device "/dev/ttyZONT": no such file or directory` (ExitCode 128/255, RestartCount=0). +> - `restart: unless-stopped` НЕ перезапускает такой контейнер (отказ до старта процесса). +> - Следствие: modbus-bridge не отвечает на шине → 485-датчики в ZONT отображаются **«недоступные»**. +> - Ручной фикс: `docker start modbus-bridge mbusd` (после готовности tty-симлинков). +> - Защита от рецидива (план, НЕ внедрён — ждёт OK Alex): `family/plans/zont-modbus-bridge-udev-race-protection.md` — POSTINIT init script с ожиданием симлинков + `docker start`. +> **⚠️ ДРУГОЕ (открытое):** пул `DEST` (IronWolf) на 2026-08-24 `DEGRADED` — разобраться, почему (отвалился mirror-член). TimeMachine (~277G) не возвращён (бэкапы macOS, вероятно не нужен — решение Alex не подтверждено). SSH с Mac по 192.168.2.197 нестабилен — использовать `mallexxx.duckdns.org`. + +> ## 🔒 ИСТОРИЯ: статус до восстановления (2026-08-24) +> **Пул RED_2TB пересоздан и УЖЕ зареган в TrueNAS** — `zpool status` ONLINE (`mirror-0{sdc,sdf}`+`mirror-1{sde,sdd}`), `midclt call pool.query` id=1 RED_2TB ONLINE. rsync возврата данных ЗАВЕРШЁН (storage 3.29T, immich 353G, backup 287G, Edu 214G, Photos 146G, old-bu 65G, docker, docker.bak, system — все смонтированы). +> **`.ix-apps` датасет (docker storage, был 24G) НЕ перенесён** — rsync шёл из `/mnt/RED_2TB/*` и не включал монтируемый в `/mnt/.ix-apps` системный датасет. Образы = кеш → перекачаются из registry сами; конфиги целы в `/mnt/RED_2TB/docker/`. +> **Контейнеры запускались через `docker-compose`, НЕ через TrueNAS Apps (TrueCharts/Custom App).** daemon.json `data-root:/mnt/.ix-apps/docker`. `.ix-apps` = просто data-root docker, не TrueNAS Apps. > **⚠️ ДРУГОЕ (проверено 2026-08-24):** пул `DEST` (IronWolf, источник спасения) на момент проверки `DEGRADED` в `zpool list` — стоит разобраться, почему (вероятно отвалился mirror-член). Также SSH с Mac к TrueNAS по 192.168.2.197 нестабилен (`Connection reset/reset by peer`, `kex_exchange_identification read`), возможен конфликт сессий при активном входе на консоль — использовать `mallexxx.duckdns.org` и ретраи. > ## ⚠️ СТАТУС на 2026-08-21: пул RED_2TB — данные СПАСЕНЫ, пул OFFLINE, пересоздаём @@ -182,24 +190,49 @@ ssh truenas_admin@mallexxx.duckdns.org "docker restart homeassistant" ssh truenas_admin@mallexxx.duckdns.org "docker exec homeassistant python -m homeassistant --script check_config --config /config" ``` -### mbusd — Modbus TCP gateway +### mbusd — Modbus RTU → TCP gateway -Мост Modbus RTU → TCP: пробрасывает серийный порт инвертора AT2 вентиляции в TCP 502. +Мост Modbus RTU → TCP: пробрасывает серийный порт в TCP 502. Используется и для вентиляции (AT2), и для ZONT-шины. -- **Image:** `3cky/mbusd` -- **Порт:** `502:502` -- **Config:** `/mnt/RED_2TB/docker/mbusd/mbusd.conf` → `/etc/mbusd.conf` -- **Device:** `/dev/ttyVent` → `/dev/ttyUSB0` (внутри контейнера) +- **Image:** `3cky/mbusd`, compose в `/mnt/RED_2TB/docker/mbusd/` +- **Порт:** `502:502` (TCP) +- **Config:** `/mnt/RED_2TB/docker/mbusd/mbusd.conf` → `/etc/mbusd.conf` (`device=/dev/ttyUSB0 speed=9600 mode=8n1`, `port=502`) +- **Device:** `devices: - /dev/ttyVent:/dev/ttyUSB0` +- **Entrypoint:** `["/usr/bin/mbusd","-d","-L","-","-c","/etc/mbusd.conf"]` +- **Pitfall udev-race:** см. ниже «Проблема modbus-bridge/mbusd после рестарта». -### modbus-bridge — AT2 Modbus → MQTT +### modbus-bridge — 485-датчики + виртуальные slaves для ZONT -Кастомный Python-мост: читает регистры инвертора AT2 через mbusd и публикует в MQTT → Home Assistant. +Кастомный Python-мост (`modbus_ha_bridge.py`, репо `HA-ZONT-Modbus`), контейнер `/mnt/RED_2TB/docker/modbus-bridge/`. -- **Image:** `modbus-bridge` (локальная сборка) -- **Volumes:** - - `/mnt/RED_2TB/docker/modbus-bridge/config.yml` → `/app/config.yml` (ro) - - `/mnt/RED_2TB/docker/modbus-bridge/modbus_ha_bridge.py` → `/app/modbus_ha_bridge.py` (ro) -- **Source:** `modbus_ha_bridge.py` в репозитории `HA-ZONT-Modbus` +- **Image:** `modbus-bridge` (локальная сборка, `--no-cache`). **НЕ имеет compose-файла** — создавался через `docker-compose`-подобный run; для восстановления использовать параметры ниже. +- **Device:** `devices: - /dev/ttyZONT:/dev/ttyUSB0` (внутри контейнера слушает `/dev/ttyUSB0`) +- **Volumes:** `config.yml` → `/app/config.yml` (ro), `modbus_ha_bridge.py` → `/app/modbus_ha_bridge.py` (ro) +- **Env (из compose):** `HA_TOKEN`, `MQTT_USER=zont`, `MQTT_PASS=...` +- **`network_mode: host`**; MQTT broker `localhost:1883` (mosquitto), HA `http://localhost:8123`. + +**Роль (важно — два режима работы):** +1. **Sniff (чтение)** — сниффит реальные 485-датчики на шине ZONT: **Dining=slave 1, Kids=slave 2, Bedroom=slave 3** (CO₂/температура/влажность, регистры 2 и 100) и публикует в MQTT (`modbus/sensors//...`), а также пишет в HA. +2. **Mappings (виртуальные slaves для ZONT)** — подставляет на **slave 101/102/103** (регистр 100) значения temperature от 485-датчиков: Dining→101, Kids→102, Bedroom→103. **ZONT (Modbus master) опрашивает 101/102/103 как свои внешние датчики температуры.** Есть также wifi-термостат как slave 100 (`sensor.0xa4c13862d39377e6_temperature`) и Socket 1 = slave 101 через switch (обратим внимание: 101 занят и Dining temp — переиспользуется на одном slave с разными функциями; сверить в config.yml). +3. Также через mbusd может работать с AT2 вентиляции. + +**Если modbus-bridge не слушает `ttyZONT`** (упал/не запущен) → ZONT не получает ответ на запросы slave 101/102/103 → в интерфейсе ZONT 485-датчики отображаются **«недоступные»**. + +### Проблема modbus-bridge/mbusd после рестарта TrueNAS (гонка с udev) + +**Симптом:** после перезагрузки TrueNAS `/dev/ttyZONT` и `/dev/ttyVent` присутствуют (`ttyZONT`→ttyUSB1, `ttyVent`→ttyUSB0, создаются init script id=1), НО контейнеры `modbus-bridge`/`mbusd` упали на старте и не перезапустились; 485-датчики в ZONT «недоступные». + +**Причина:** docker стартует раньше udev → при `start` контейнера устройство ещё не существует → падение ДО запуска процесса: +``` +docker inspect --format '{{.State.Error}}' +error gathering device information while adding custom device "/dev/ttyZONT": no such file or directory +# ExitCode 128 / 255, RestartCount=0 +``` +`restart: unless-stopped` такой отказ **не подхватывает** (RestartCount=0), и задержка внутри entrypoint не поможет (device mount происходит до старта процесса). + +**Ручной фикс:** `docker start modbus-bridge mbusd` (после того как симлинки созданы). + +**Защита от рецидива (план, НЕ внедрён, ждёт OK Alex):** `family/plans/zont-modbus-bridge-udev-race-protection.md` — POSTINIT init script `/mnt/RED_2TB/system/start-modbus.sh`, который ждёт появления `ttyZONT`/`ttyVent` (≤25с) и делает `docker start modbus-bridge mbusd`. ### USB device aliases diff --git a/family/plans/zont-modbus-bridge-udev-race-protection.md b/family/plans/zont-modbus-bridge-udev-race-protection.md new file mode 100644 index 00000000..c95e55d6 --- /dev/null +++ b/family/plans/zont-modbus-bridge-udev-race-protection.md @@ -0,0 +1,84 @@ +--- +title: 'План: защита modbus-bridge от гонки с udev (ZONT датчики)' +tags: + - plan + - family + - homeautomation + - zont + - modbus +updated: '2026-08-25' +status: proposed +--- +# План: защита modbus-bridge/mbusd от гонки с udev (после рестарта TrueNAS) + +> Статус: **план на согласование** (2026-08-25), ничего не применено. +> Задача: исключить повторение «485-датчики недоступные в ZONT» после перезагрузки TrueNAS. + +## Контекст / причины +- ZONT (192.168.0.10) — Modbus master на RS-485 шине `ttyZONT`. 485-датчики: Dining=1, Kids=2, Bedroom=3. +- Контейнер `modbus-bridge` сниффит их и подставляет виртуальные slaves 101/102/103 → ZONT опрашивает как внешние датчики. +- Если modbus-bridge не слушает шину → ZONT показывает датчики «недоступные». +- Первопричина 2026-08-25: **гонка загрузки** — docker стартовал раньше udev, `/dev/ttyZONT` и `/dev/ttyVent` ещё не существовали → контейнеры упали на старте: + `error gathering device information while adding custom device "/dev/ttyZONT": no such file or directory` (ExitCode 128/255, RestartCount=0). +- `restart: unless-stopped` **не перезапускает** контейнер, упавший на этапе `docker start` (binding device fail) — отказ до старта процесса. + +## Ключевое ограничение (почему задержка в entrypoint НЕ поможет в чистом виде) +У modbus-bridge и mbusd устройство проброшено через `devices: /dev/ttyZONT:/dev/ttyUSB0` / `devices: /dev/ttyVent:/dev/ttyUSB0`. +**Docker при `start` монтирует устройство ДО запуска процесса** — если `` не существует на _момент создания/старта контейнера_, контейнер вообще не стартует, и `sleep` внутри entrypoint/CMD никогда не выполнится. +→ Простой «sleep N в entrypoint» проблему НЕ решает. + +## Почему контейнеры сами НЕ поднялись (RestartCount=0) — разбор +`restart: unless-stopped`/`always` ретраит в ДВУХ случаях: +1. **Процесс контейнера стартовал и завершился** с ненулевым кодом → демон ретраит с backoff (RestartCount растёт). +2. **Перезапуск самого docker daemon** → демон пробует поднять `unless-stopped`/`always` контейнеры. + +Наш случай — ТРЕТИЙ, особый: контейнер **вообще не стартовал** — ошибка на этапе монтирования устройства: +`error gathering device information while adding custom device "/dev/ttyZONT": no such file or directory`. +- Нет процесса, который «завершился бы» — docker не смог примонтировать устройство, процесс не запущен вовсе. +- Раз нет завершённого процесса, **restart policy не на что применять** → `RestartCount=0`, демон не ретраит. +- После падения (13:24) демон НЕ перезапускался (docker.pid с 07:14) → второго триггера не было. + +Вывод: **отказ на этапе mount устройства НЕ перезапускается restart policy**. Авто-подъём при перезагрузке гарантирован только если устройства существуют на момент старта демона. Иначе — контейнеры молча остаются в exited до ручного `docker start`. + +## Решение (рекомендуемое): POSTINIT init script с ожиданием + docker start +Уже есть init script id=1 «Map ttyUSB» (POSTINIT), который копирует udev-правила и делает `udevadm trigger`. Он НЕ ждёт создания симлинков и не запускает докер-контейнеры. + +Добавить новый POSTINIT init script (COMMAND, when=POSTINIT, enabled=true), который: +1. Ждёт появления `/dev/ttyZONT` и `/dev/ttyVent` (poll до ~20 сек). +2. Как только оба есть — `docker start modbus-bridge mbusd`. +3. Если docker ещё не готов — также пробует переждать. + +Пример скрипта (положить в `/mnt/RED_2TB/system/start-modbus.sh`): +```bash +#!/bin/bash +# Ожидание tty-алиасов от udev (макс ~25 сек) +for i in $(seq 1 25); do + [ -e /dev/ttyZONT ] && [ -e /dev/ttyVent ] && break + sleep 1 +done +sleep 2 # небольшая пауза на устойчивость udev +docker start modbus-bridge mbusd 2>/dev/null +``` +Команда в TrueNAS init: `bash /mnt/RED_2TB/system/start-modbus.sh` +Timeout скрипта в TrueNAS: `30`. + +> Альтернатива (не рекомендуется): поменять `devices:` на прямой `/dev/ttyUSB1`/`/dev/ttyUSB0` — но номер ttyUSB может прыгать, алиасы для того и нужны. + +> Примечание: менять Dockerfile/entrypoint и/или `restart: always` здесь **недостаточно** — отказ на этапе mount устройства restart policy не обрабатывает. + +## Шаги внедрения (после согласования) +1. Создать `/mnt/RED_2TB/system/start-modbus.sh` на TrueNAS (chmod +x). +2. Добавить init script через midclt: + ```bash + midclt call initshutdownscript.create '{"type":"COMMAND","command":"bash /mnt/RED_2TB/system/start-modbus.sh","when":"POSTINIT","enabled":true,"timeout":30,"comment":"Start modbus-bridge/mbusd after udev tty aliases"}' + ``` +3. Проверить: `midclt call initshutdownscript.query` +4. Проверить после reboot: контейнеры Up, `ttyZONT`/`ttyVent` созданы, датчики в ZONT отвечают. + +## Вердикт по исходному вопросу «добавить задержку в entrypoint» +- Задержка внутри entrypoint/CMD для этих контейнеров **не выполняется** из-за device mount до старта процесса → этот путь отпадает. +- Правильный эквивалент «задержки до готовности» — скрипт выше на уровне TrueNAS init (ожидание устройств, затем `docker start`). + +## Связанные заметки +- [[family/how-to/home-automation.md]] — карта slave ID, ZONT, датчики +- [[family/how-to/truenas-infrastructure.md]] — docker, modbus-bridge, mbusd, udev rules, init scripts