[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

This commit is contained in:
Alexey Martemyanov
2026-08-25 17:19:55 +06:00
parent 58b9e966ff
commit b16407df72
3 changed files with 143 additions and 22 deletions
+4
View File
@@ -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 не менялись.)
## Карта регистров контроллера вентиляторов
```
+55 -22
View File
@@ -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/<app>`.
> - **Контейнеры запускались через `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/<app>/` → `docker compose up -d`; **4 контейнера без compose (ha, webdav, modbus-bridge, zigbee2mqtt) восстанавливать вручную** — параметры и полный инвентарь в разделе «Инвентарь compose-файлов по папкам» ниже. Конфиги целы в `/mnt/RED_2TB/docker/<app>`, образы перекачаются из 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/<app>`.
> **Контейнеры запускались через `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/<room>/...`), а также пишет в 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 <c> --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
@@ -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` монтирует устройство ДО запуска процесса** — если `<src>` не существует на _момент создания/старта контейнера_, контейнер вообще не стартует, и `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