Files
obsidian-vault/family/how-to/zont-config-compiler.md
T

313 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
aliases:
- ZONT config compiler
- config-to-yml
- yml-to-config
- HA-ZONT-Modbus
- ZONT конвертеры конфига
created: '2026-09-17'
namespace: family
related:
- '[[family/tech/zont-config-object-types]]'
- '[[family/tech/zont-scenario-logic-11109]]'
- '[[family/how-to/home-automation]]'
- '[[family/how-to/gitea-config]]'
tags:
- family
- how-to
- zont
- modbus
- homeautomation
title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
type: how-to
updated: '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
1. Читает файл, определяет кодировку: UTF-8, при неудаче — windows-1251.
2. Парсит строки регекспом `^#([ZS])(\d+)=(.*)$` (хвостовые пробелы в payload сохраняются).
3. `split_payload()` — режет payload по запятым **с учётом кавычек и вложенных `[…]`**.
4. `parse_atom()` — пусто/`''``None`, `'строка'` → строка, `[…]` → список, иначе int → float → строка.
5. Раскладывает объекты по секциям YAML. Вложенное прячет под родителя: Modbus-регистры (тип 52) → внутрь своего устройства (тип 51).
6. `system_settings` (`#S…`) выводит **в начало файла**, чтобы было видно при правке.
### `yml-to-config.py` — YAML → TXT
1. Загружает **YAML (UTF-8)** и собирает строки `#Z<id>=…` обратно, в порядке типов.
2. Форматирование: числа без кавычек, строки в `'…'`, bool → `0/1`, пустая строка → `''`, списки → `[…]`.
3. Разворачивает вложенное: регистры типа 52 снова становятся отдельными строками после своего устройства.
4. `validate_config()` проверяет структуру **до вывода**. При ошибках — `❌ VALIDATION ERRORS`, **exit 4**, файл не отдаётся.
5. Вывод: **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__)'`
```bash
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 даёт идентичный файл.
Команда для проверки на любом конфиге:
```bash
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()` на обоих файлах — синтаксис OK
- `config-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` теперь разбирается (он двухшаговый). Причина исчезла.
>
> Проверка: `.yml` 0 байт **всегда** означает, что конвертер не «не доехал», а **упал на конкретном объекте**. Смотреть 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