Files
obsidian-vault/family/how-to/zont-modbus-bridge-udev-race-protection.md
T

6.0 KiB
Raw Blame History

status, tags, title, updated
status tags title updated
obsolete
family
homeautomation
zont
modbus
Защита modbus-bridge от гонки с udev (ZONT датчики) 2026-09-14

Защита modbus-bridge/mbusd от гонки с udev (ZONT датчики)

🔴 ДОКУМЕНТ УСТАРЕЛ (2026-09-14). Речь идёт о контейнерах на TrueNASmodbus-bridge и mbusd. Они погашены (docker stop + --restart=no) вместе со всем стеком автоматизации; USB-адаптеры физически переехали на t610, где узлов /dev/ttyZONT//dev/ttyVent на TrueNAS больше не существует. Проблема неактуальна. На t610 гонка udev решается иначе: Supervisor (HA OS) сам ждёт устройство перед стартом аддона — отдельный init-скрипт не нужен. История/контекст декомиссии: family/plans/t610-home-automation §5-кватер-Л, канон-блок — family/how-to/truenas-infrastructure. ⚠️ Документ оставлен как историческая справка (пригодится, если когда-нибудь откатывать TrueNAS-стек).

Статус: внедрено (2026-08-25, шаги 13). Датчики в ZONT снова отвечают, скрипт и init script на месте.

Проблема

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): гонка загрузки TrueNAS — docker стартовал раньше udev, /dev/ttyZONT и /dev/ttyVent ещё не существовали → контейнеры modbus-bridge (Exit 128) и mbusd (Exit 255) упали на старте: error gathering device information while adding custom device "/dev/ttyZONT": no such file or directory (RestartCount=0). Фикс был: ручной docker start modbus-bridge mbusd.

Почему контейнеры сами НЕ поднялись (RestartCount=0)

restart: unless-stopped/always ретраит в ДВУХ случаях:

  1. Процесс контейнера стартовал и завершился с ненулевым кодом → демон ретраит с backoff.
  2. Перезапуск docker daemon → демон пробует поднять unless-stopped/always контейнеры.

Наш случай — ТРЕТИЙ, особый: контейнер вообще не стартовал — ошибка на этапе монтирования устройства.

  • Нет процесса, который «завершился бы» → restart policy не на что применятьRestartCount=0, демон не ретраит.
  • После падения (13:24) демон не перезапускался → второго триггера не было. Вывод: отказ на этапе mount устройства НЕ перезапускается restart policy. Авто-подъём гарантирован только скриптом, ждущим устройства.

Ключевое ограничение (задержка в entrypoint НЕ помогает)

У обоих контейнеров устройство проброшено через devices: /dev/ttyZONT:/dev/ttyUSB0 / devices: /dev/ttyVent:/dev/ttyUSB0. Docker при start монтирует устройство ДО запуска процесса — если <src> отсутствует на момент старта, контейнер вообще не стартует, и sleep внутри entrypoint/CMD не выполняется. Поэтому решение — на уровне init скрипта.

Внедрённое решение

1. Скрипт /mnt/RED_2TB/system/start-modbus.sh (root, -rw-r--r--)

Ожидает появления tty-алиасов от udev (до ~50с), затем запускает контейнеры:

#!/bin/bash
# Ждём tty-алиасы от udev (макс ~25с), затем стартуем modbus контейнеры.
for i in $(seq 1 50); do
  [ -e /dev/ttyZONT ] && [ -e /dev/ttyVent ] && break
  sleep 1
done
sleep 2
docker start modbus-bridge mbusd 2>/dev/null

2. Init script (TrueNAS, id=3)

  • when: POSTINIT, type: COMMAND, timeout: 30, enabled: true
  • command: bash /mnt/RED_2TB/system/start-modbus.sh
  • comment: Start modbus-bridge/mbusd after udev tty aliases

Порядок POSTINIT скриптов

id command comment enabled
1 cp .../99-tty-alias.rules /etc/udev/rules.d/ && udevadm control --reload-rules && udevadm trigger Map ttyUSB
2 bash /mnt/RED_2TB/system/tunnel.sh &
3 bash /mnt/RED_2TB/system/start-modbus.sh Start modbus-bridge/mbusd after udev tty aliases

Проверка

  • /dev/ttyZONTttyUSB1 (CH340), /dev/ttyVentttyUSB0 — симлинки появляются после POSTINIT id=1.
  • После reboot: docker ps — modbus-bridge и mbusd Up; датчики в ZONT отвечают.
  • Просмотр init scripts: midclt call initshutdownscript.query
  • Откат: midclt call initshutdownscript.delete <ID> + удалить скрипт с диска.

Связанные заметки