313 lines
24 KiB
Markdown
313 lines
24 KiB
Markdown
---
|
||
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
|