24 KiB
aliases, created, namespace, related, tags, title, type, updated
| aliases | created | namespace | related | tags | title | type | updated | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
2026-09-17 | family |
|
|
⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml | how-to | 2026-09-17e |
⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
Двусторонние конвертеры между конфигом контроллера ZONT (.txt) и читаемым YAML.
Позволяют править конфиг руками — имена реле, адреса Modbus, интервалы опроса, датчики — не заходя в UI контроллера.
| Проект (Mac) | /Users/admin/Automation/HA-ZONT-Modbus |
| Repo (private) | https://git.mallexxx.duckdns.org/git_admin/HA-ZONT-Modbus |
| Скрипты | config-to-yml.py (TXT → YAML) · yml-to-config.py (YAML → TXT) |
| Конфиги | zont_config/ — свежие · zont_config/archive/ — историчные |
| Типы объектов | family/tech/zont-config-object-types |
| ZONT в общем контуре | family/how-to/home-automation §6 |
1. Формат конфига ZONT
Текстовый файл, одна запись = одна строка, разделитель строк CRLF, кодировка windows-1251:
#Z<id>=<тип>,<поле>,<поле>,…
#S<id>=<значение>
| Префикс | Что это | Пример |
|---|---|---|
#Z<id> |
объект конфига: реле, датчик, сценарий, Modbus-устройство | #Z12=14,'Спальня левый',… — id 12, тип 14 (реле) |
#S<id> |
системная настройка | #S7=H2000_PRO 723 678 — модель и версии ПО |
- Тип объекта — первое поле после
=. - Строки — в
'одинарных кавычках', списки —[...], числа — как есть. #Z<id>=*— служебный маркер (пустой/унаследованный объект).- Порядок строк значим и сохраняется при конвертации.
2. Как работает
config-to-yml.py — TXT → YAML
- Читает файл, определяет кодировку: UTF-8, при неудаче — windows-1251.
- Парсит строки регекспом
^#([ZS])(\d+)=(.*)$(хвостовые пробелы в payload сохраняются). split_payload()— режет payload по запятым с учётом кавычек и вложенных[…].parse_atom()— пусто/''→None,'строка'→ строка,[…]→ список, иначе int → float → строка.- Раскладывает объекты по секциям YAML. Вложенное прячет под родителя: Modbus-регистры (тип 52) → внутрь своего устройства (тип 51).
system_settings(#S…) выводит в начало файла, чтобы было видно при правке.
yml-to-config.py — YAML → TXT
- Загружает YAML (UTF-8) и собирает строки
#Z<id>=…обратно, в порядке типов. - Форматирование: числа без кавычек, строки в
'…', bool →0/1, пустая строка →'', списки →[…]. - Разворачивает вложенное: регистры типа 52 снова становятся отдельными строками после своего устройства.
validate_config()проверяет структуру до вывода. При ошибках —❌ VALIDATION ERRORS, exit 4, файл не отдаётся.- Вывод: windows-1251, переводы строк CRLF, финальный перевод строки как в оригинале.
Коды возврата
| Скрипт | Код | Значение |
|---|---|---|
config-to-yml.py |
1 | неверное число аргументов |
| 2 | ParseError — неизвестный формат строки или неразбираемое значение |
|
yml-to-config.py |
1 | файл не найден |
| 2 | ConversionError — нет raw или неизвестный тип объекта |
|
| 3 | прочее исключение | |
| 4 | ❌ ошибки валидации (файл не выдан) |
Неизвестный тип объекта — падение с ошибкой, а не тихий пропуск. Сделано намеренно: молча потерять объект хуже, чем не отдать файл.
Проверено против исходников (2026-09-17)
| Факт | Где в коде |
|---|---|
config-to-yml.py exit-коды {1, 2} |
sys.exit(1) — argc; sys.exit(2) — ParseError |
yml-to-config.py exit-коды {1, 2, 3, 4} |
argc / FileNotFoundError / ConversionError / общее / validation |
validate_config() вызывается до вывода |
стр. 913 validation_errors = validate_config(data, lines) |
| Вход YAML — UTF-8, выход — windows-1251 | стр. 907 open(…, encoding='utf-8'); стр. 921 stdout.reconfigure(encoding='windows-1251', errors='replace') |
| Дефолт типа 0 (16 полей) | add_z_line(obj['id'], 0, register_ref, name, 0, 0, 1000, 2000, 7424, [], 20, [], [], 1, config_id, 0, 0) |
| Дефолт типа 36 | add_z_line(config_id, 36, [], [], [], 10, 0) |
| README перечисляет 23 типа; код обрабатывает 25 | README пропускает 0 и 36 |
📌
validate_config()дополнительно проверяет кодируемость каждой строки в windows-1251 (символ вне CP1251 → ошибка валидации).
3. Как пользоваться
Требования: python3 + PyYAML. Проверка: python3 -c 'import yaml; print(yaml.__version__)'
cd /Users/admin/Automation/HA-ZONT-Modbus
# 1. Снять текущий конфиг с контроллера → в zont_config/
# имя файла: config_<SN>_<SN>_<YYYY-MM-DD_HH-MM-SS>.txt
# 2. TXT → YAML
python3 config-to-yml.py zont_config/config_XXXX.txt > /tmp/zont.yml
# 3. Править YAML: имена, адреса, интервалы опроса, пороги
# ⚠️ id объектов и их порядок значимы — не переставлять без нужды
# 4. YAML → TXT
python3 yml-to-config.py /tmp/zont.yml > /tmp/zont_new.txt
# 5. Сверить число объектов до/после
grep -c '^#Z' /tmp/zont_new.txt
# 6. Загрузить /tmp/zont_new.txt в контроллер (UI / облако ZONT)
🔴 Главное правило: raw не трогать
В YAML часть полей декодирована (адрес, интервал опроса, регистры), часть лежит как raw / raw_params / raw_field_N — это страховка от потери данных.
Если у объекта пустой raw, скрипт подставит жёстко зашитые дефолты (тип 0 — набор из 16 полей, тип 36 — [],[],[],10,0), и исходные настройки будут потеряны молча.
✅ Правь декодированные поля.
raw-поля не удаляй и не «чисти». Если поле не декодировано — правка возможна только с пониманием исходного формата.
Поддерживаемые типы
1, 3, 4, 5, 6, 7, 9, 10, 11, 14, 16, 20, 24, 25, 27, 28, 42, 45, 46, 49, 51, 52, 53, 57
➕ дополнительно обрабатываются 0 (дискретные датчики — индикаторы состояния реле) и 36 (их вложенные конфиги).
Полная таблица с полями — family/tech/zont-config-object-types.
Сценарные типы — полностью поддержаны с 2026-09-17 (см. §5b):
| Тип | Роль | Формат | YAML-секция |
|---|---|---|---|
11 |
сценарий | [11, name, [step_ids], 0, 0, enabled, 0, 0] |
scenarios |
45 |
задержка, мс | [45, ms] |
delays |
46 |
шаг | [46, 0, cond_id, [action_ids], []] |
scenario_steps |
49 |
условие | [49, relay_id, operator, value] |
scenario_conditions |
4. Проверка целостности (round-trip)
README_converters.md заявляет round-trip 130/130 объектов: TXT → YAML → TXT даёт идентичный файл.
Команда для проверки на любом конфиге:
cd /tmp && rm -rf zont-rt && mkdir zont-rt && cd zont-rt
SRC=/Users/admin/Automation/HA-ZONT-Modbus/zont_config/<файл>.txt
python3 /Users/admin/Automation/HA-ZONT-Modbus/config-to-yml.py "$SRC" > a.yml
python3 /Users/admin/Automation/HA-ZONT-Modbus/yml-to-config.py a.yml > b.txt
tr -d '\r' < "$SRC" > A.txt; tr -d '\r' < b.txt > B.txt
diff A.txt B.txt # пусто = round-trip чистый
grep -c '^#Z' A.txt B.txt # счётчик объектов
📌 Нюанс кодировки: источник бывает в UTF-8, а
yml-to-config.pyвсегда пишет windows-1251 → наивныйdiffпокажет различия на кириллице. Сравнивать структуру и числовые поля, либо нормализовать кодировку с обеих сторон.
5. Питфоллы
| # | Питфолл | Как обойти |
|---|---|---|
| 1 | 🔴 Вывод yml-to-config.py — windows-1251 + CRLF, не UTF-8 |
Редирект в файл и передавать байтами. Не копипастить из терминала, не пересохранять в редакторе |
| 2 | 🔴 Пустой raw у типов 0/36 → молчаливая подстановка дефолтов |
Не трогать raw*-поля |
| 3 | 🔴 Правка #S… в YAML бессмысленна — они хранятся как raw_payload |
Системные настройки менять только через UI контроллера |
| 4 | YAML на входе yml-to-config.py — строго UTF-8; выход — windows-1251. Плюс автоопределение кодировки при чтении .txt (UTF-8 → windows-1251) |
Править YAML в UTF-8. Исходный .txt не пересохранять в редакторе |
| 5 | Неизвестный тип объекта → exit 2, файл не создаётся | Смотреть stderr — там причина |
| 6 | validate_config() → exit 4 |
Печатает ❌ VALIDATION ERRORS + список причин; файл намеренно не выдан (вывод пустой) |
| 7 | Регистр без своего устройства / аналоговый выход с битой ссылкой | WARNING в stderr, конвертация продолжается — проверить ссылки вручную |
| 8 | Загрузка конфига в контроллер — руками, скрипты только конвертируют | Конвертер не имеет доступа к ZONT |
| 9 | INFRASTRUCTURE.md, docker-compose.yml, docker run.txt в проекте — исторический TrueNAS-стек |
Актуальный контур — family/how-to/home-automation. Не искать modbus-bridge/mbusd на NAS |
| 10 | Дополнительных зависимостей нет | Только pyyaml — jsonschema/ruamel не нужны |
| 11 | ✅ ИСПРАВЛЕНО 2026-09-17 — Сценарий с >1 шагом падал (exit 2) | config-to-yml.py теперь цикл по всем шагам; yml-to-config.py пишет все step_ids |
| 12 | ✅ ИСПРАВЛЕНО 2026-09-17 — Шаг с >1 действием падал (exit 2) | Оба скрипта работают со всем списком действий |
| 13 | ✅ ИСПРАВЛЕНО 2026-09-17 — Тип 45 (задержка, мс) не был поддержан | Парсер + эмиттер, секция YAML delays. См. §5b |
| 14 | ⚠️ > в шелле затирает .yml до старта питона |
Проверять exit-код до переноса файла в репо |
| 15 | ⚠️ Один шаг может принадлежать нескольким сценариям | yml-to-config.py использует хелпер _register() — обновляет запись по id, а не добавляет дубль |
Ограничения конвертера (найдено 2026-09-17) — ВСЕ ЗАКРЫТЫ
Прогон боевого конфига config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt (598 #Z, 25 #S) вскрыл 4 дырки в сценарной логике — все четыре независимы, падал на первой же. Все четыре исправлены, см. §5b.
| # | Чего не было | Что терялось | Масштаб в конфиге | Статус |
|---|---|---|---|---|
| 1 | 2-й и последующие шаги сценария | шаг 11828 сценария 11109 |
1 сценарий из 65 | ✅ закрыто |
| 2 | Тип 45 — задержка в мс, [45, ms] |
4 объекта: 11824=20000, 11825=60000, 11826=0, 11828=0 |
4 объекта | ✅ закрыто |
| 3 | Несколько действий в одном шаге | шаг 11827 содержит 7 действий |
1 шаг из 65 | ✅ закрыто |
| 4 | — (информационно) сценарий выключен | #Z11109 — enabled=0 |
1 сценарий | ℹ️ факт, не баг |
✅ Остальные 64 сценария — каноническая одношаговая форма
11 → [46] → 49, конвертер их разбирал и разбирает корректно. Все 65 условий (49) имеют ровно 4 поля[49, relay, op, value],op=1= equals у всех. Все 64 «простых» шага — ровно 5 полей[46, prio, cond, [1 действие], []].
Тип 45 vs delay_ms у типа 5 — это разные вещи:
delay_msживёт внутри объекта-действия типа 5 (поле 4) — уже поддержан обоими скриптами.- Тип 45 — самостоятельный объект-задержка в списке действий шага. Восстановленный формат:
[45, <миллисекунды>], 2 поля. Проверено на 4 объектах, больше в конфиге не встречается.
5b. Доработка сценариев — СДЕЛАНО 2026-09-17
Задача Alex: «перегнать в yml, дописав парсер и энкодер». Правка кода конвертеров, боевой конфиг не тронут.
Изменения в config-to-yml.py (парсер)
| Что | Детали |
|---|---|
| Многошаговые сценарии | Убрано require(len(steps) == 1). Цикл for step_id in steps → parsed_steps[], каждый со своим when/then |
| Много действий в шаге | Убрано require(len(actions) == 1). Все id проверяются на существование в Z_dict |
| Новый тип 45 | Отдельная секция # --- scenario delays (type 45). Валидация: ровно 2 поля, ms — неотрицательный int → out['delays'] = [{'id':…, 'ms':…}] |
KNOWN_TYPES |
Добавлен 45 (был {0,1,…,42,46,49,…}) |
| Выходной dict | Добавлен ключ delays: [] |
| Обратная совместимость | Если у сценария 1 шаг — дополнительно пишутся плоские when/then (как раньше). Старые YAML не ломаются |
Изменения в yml-to-config.py (энкодер)
| Что | Детали |
|---|---|
| Все шаги | Собирает step_ids из scenario['steps'] (fallback — плоский then.id) и пишет в строку сценария |
| Все действия | [46, 0, cond_id, actions, []] — весь список, без [action_id] |
| Новый тип 45 | Секция эмиттера: raw passthrough или ms → [45, ms]. Валидация: ms — неотрицательный int. Вставлена ДО шагов (46) — порядок строк значим |
TYPE_ORDER |
Добавлено ('delays', 45) между gui_tabs и scenario_steps |
Хелпер _register() |
Локальная функция рядом с add_z_line. Регистрирует шаг/условие по id, обновляя существующую запись вместо добавления дубля — один шаг может принадлежать нескольким сценариям |
Без when в шаге |
Если у шага нет when, но есть запись в scenario_steps — переиспользует известный cond_id из неё. Если нет — ConversionError |
Что проверено
ast.parse()на обоих файлах — синтаксис OKconfig-to-yml.pyна боевом конфиге: больше не падает на сценарии11109(ранееОшибка: Сценарий 11109: поддерживается только 1 шаг, exit 2)
Что НЕ проверено (осталось)
- ⏳ Round-trip целиком:
TXT → YAML → TXT, сверка 598 объектов,diffчистый. Команда требует апрува, была запущена и истекла по таймауту.
План дальше (этапы 2–3, ждут Alex)
Этап 2 — правка YAML под новую логику (только после ответа Alex на вопрос: (а) просто enabled: true для «Передернуть Автомат Котельной» / (б) добавить триггер по событию / (в) другое).
Этап 3 — YAML → TXT, сверка счётчиков, коммит ДО → Alex заливает руками → Alex проверяет руками → коммит ПОСЛЕ.
🔴 Порядок работ с боевыми конфигами: коммит ДО → правка → заливка → ПРОВЕРКА АЛЕКСОМ руками → коммит ПОСЛЕ. Мой
read back= «конфиг записался», НЕ «работает».
⚙️ Бэкап кода — только git, не
/tmp. Alex 2026-09-17: «какой нахуй бэкап скриптов — там в гите все». Изменения скриптов откатываются через git, отдельные копии в/tmp/не делать.
6. Состояние проекта (проверено 2026-09-17)
Репозиторий /Users/admin/Automation/HA-ZONT-Modbus — рабочее дерево грязное, разгребание ждёт отдельной команды Alex:
| Файл | Статус |
|---|---|
zont_config/H2000_PRO_config_actual-2.txt / -2.yml |
удалены из индекса (целы в zont_config/archive/) |
zont_config/H2000_PRO_config_actual-3.txt / -3.yml |
то же |
zont_config/H2000_PRO_config_actual-4.txt / -4.yml |
то же |
zont_config/archive/ |
не добавлена в git |
zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt |
32 689 байт — свежеснятый конфиг с контроллера, не закоммичен |
одноимённый .yml |
0 байт — см. причину ниже |
config-to-yml.py, yml-to-config.py |
✅ изменены 2026-09-17 (§5b) — не закоммичены |
🔴 Причина пустого
.yml(0 байт) — найдена 2026-09-17.config-to-yml.pyпадал с exit 2 на первом невыразимом объекте и не писал ничего:$ python3 config-to-yml.py zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt > /tmp/zont.yml Ошибка: Сценарий 11109: поддерживается только 1 шаг EXIT=2 → /tmp/zont.yml = 0 байт✅ Исправлено 2026-09-17 — сценарий
11109теперь разбирается (он двухшаговый). Причина исчезла.Проверка:
.yml0 байт всегда означает, что конвертер не «не доехал», а упал на конкретном объекте. Смотреть stderr, а не перезапускать вслепую.⚠️
>в шелле затирает целевой файл ещё до старта питона — поэтому рядом с непустым.txtпоявляется пустой.yml. Писать через>/tmp/out.ymlи только после успешного exit-кода переносить в репо.
Содержимое проекта, не относящееся к конвертерам:
INFRASTRUCTURE.md(342 стр.),docker-compose.yml,docker run.txt— исторический TrueNAS-стек (docker-контейнерыhomeassistant,mbusd,modbus-bridge,mosquitto,zigbee2mqtt,nodered,caddy,immich,transmission,webdav,inpxer,cups-splix,portainer,watchtower,rclone). Стек декомиссирован, автоматизация живёт на t610 — актуальное: family/how-to/home-automation.modbus_ha_bridge.py,modbus_mqtt_bridge.py— исходники мостов (исторические, для TrueNAS).nodered-flows-backup.json,nodered-flows-updated.json— дампы потоков Node-RED (Node-RED остановлен).homeassistant/,floorplan/— снапшоты конфига HA и планировки (исторические, там же workflow «fetch from NAS → edit → deploy»).README_converters.md— исходное описание конвертеров (типы: 23, без0и36)..gitignore— исключает*.cur, логи,__pycache__,.venv,.DS_Store,project_home.pdf(крупный бинарь).
7. Связанные заметки
- family/tech/zont-config-object-types — таблица типов объектов (0, 1…57, 36) и
#S-настройки - family/tech/zont-scenario-logic-11109 — разобранная логика сценария «Передёрнуть Автомат Котельной»
- family/how-to/home-automation — контур автоматизации, ZONT, Modbus slave ID и регистры (§6)
- family/how-to/gitea-config — Gitea: креды, создание репо, питфоллы
- family/how-to/ha-automations — автоматизации HA