60 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-17k |
⚙️ 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) · test_roundtrip.py (проверка целостности) |
| Конфиги | zont_config/ — свежие · zont_config/archive/ — историчные |
| Типы объектов | family/tech/zont-config-object-types |
| ZONT в общем контуре | family/how-to/home-automation §6 |
| Снять живой конфиг с прибора | 🔴 curl -s http://192.168.0.50/config.txt — без авторизации, формат ровно как у парсера. См. family/tech/zont-api §6bis |
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 → ошибка валидации).
2.1 Формат сценариев (type 11 / 45 / 46 / 49) — разобран 2026-09-17
🔴 Ключевое открытие: поле 2 сценария — это не список шагов типа 46, а последовательность ссылок, куда попадают и шаги (46), и задержки (45). Проверено на 65 сценариях боевого конфига.
#Z<step_46_id>=46,<priority>,[<action_ids…>],[]
#Z<act_id>=9,'<имя>',<relay_id>,'0|1' ← действие (тип 9)
#Z<delay_id>=45,<ms> ← задержка (тип 45)
#Z<cond_id>=49,<relay_id>,<operator>,<value> ← условие (тип 49)
#Z<scen_id>=11,'<имя>',[<step_46_id>…],0,0,<enabled>,0,0
| Поле | Значение |
|---|---|
11 поле 2 |
список ссылок: шаги 46 и задержки 45 |
46 поле 2 |
[action_ids] — действия 9 и задержки 45 внутри шага |
45 |
пауза в мс; 45,0 = пауза 0 (заглушка) |
11 поле 6 |
1 = сценарий включён, 0 = выключен |
11 число полей |
свежая прошивка — 8, старая (-2) — 7 |
Пример — #Z11109 «Передернуть Автомат Котельной» (единственный многошаговый из 65):
#Z11109=11,'Передернуть Автомат Котельной',[11827,11828],0,0,0,0,0 ← 11828 = задержка-«хвост»
#Z11827=46,0,11823,[11191,11030,11824,11029,11825,11192,11826],[] ← 7 действий, 2 из них задержки
#Z11823=49,11190,1,0 ← virt.Запретить == 0
#Z11824=45,20000 #Z11825=45,60000 #Z11826=45,0 #Z11828=45,0
Читается как: вкл virt.Запретить → выкл Автомат → пауза 20 с → вкл Автомат → пауза 60 с →
выкл virt.Запретить → пауза 0. Поле 6 = 0 → сценарий выключен, защиты от повторного
передёргивания нет.
Как это выглядит в YAML
✅ НОВАЯ форма (2026-09-17, §5c). Все сценарии — один плоский список
steps[]в исходном порядке поля 2, каждый элемент с телом на месте; плюс блокtrigger:. Плоскиеwhen/thenи дележ наblocks/extra_linksубраны (тот вариант был отвергнут Alex — см. §5c). Форма11109ниже — устаревшая, оставлена для истории; актуальная — §5c.
Устаревшая форма (коммит 199f2b1) — плоский when/then для 1-шаговых + steps + extra_links:
scenarios:
- id: 11109
name: Передернуть Автомат Котельной
enabled: false
steps:
- id: 11827
when: {id: 11823, relay_id: 11190, operator: equals, value: 0}
then: {actions: [11191, 11030, 11824, 11029, 11825, 11192, 11826]}
extra_links: [11828]
delays:
- {id: 11824, ms: 20000}
- {id: 11825, ms: 60000}
- {id: 11826, ms: 0}
- {id: 11828, ms: 0}
Как дописывать логику: задержки правятся в секции delays (поле ms), порядок срабатывания
задаётся порядком id в actions шага. Чтобы включить сценарий — enabled: true.
2.2 Round-trip: все 4 конфига чистые
Проверено test_roundtrip.py (новый скрипт в репо) — TXT → YAML → TXT, сравнение по множеству
строк с нормализацией кодировки:
| Конфиг | Объектов | Результат |
|---|---|---|
zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt |
598 | ✅ чисто |
zont_config/archive/H2000_PRO_config_actual-4.txt |
558 | ✅ чисто |
zont_config/archive/H2000_PRO_config_actual-3.txt |
558 | ✅ чисто |
zont_config/archive/H2000_PRO_config_actual-2.txt |
594 | ✅ чисто |
cd /Users/admin/Automation/HA-ZONT-Modbus
python3 test_roundtrip.py # свежий конфиг из zont_config/
python3 test_roundtrip.py zont_config/archive/H2000_PRO_config_actual-4.txt
Что исправлено 2026-09-17 (правки в обоих конвертерах):
| # | Проблема | Причина | Правка |
|---|---|---|---|
| 1 | Падение «поддерживается только 1 шаг» | require(len(steps) == 1) — не знал про задержки в поле 2 |
цикл по ссылкам; не-46 → extra_links |
| 2 | Падение «поддерживается только 1 действие» | require(len(actions) == 1) |
список действий целиком |
| 3 | Тип 45 отсутствовал | не было парсера/энкодера | секция delays, KNOWN_TYPES += 45, TYPE_ORDER += delays |
| 4 | Тип 5, поле 3 (1→0) терялось |
энкодер хардкодил 1 |
_raw_field3 |
| 5 | 1.0 → 1 (порча float) |
parse_atom превращал whole-float в int |
whole-float остаётся float |
| 6 | Сценарии старой прошивки (7 полей) → 8 | энкодер всегда писал 8 | _raw_field_count |
| 7 | divider=1.0 → 1 |
дефолт подавлялся | _raw_divider |
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
# ✅ Напрямую с контроллера, без авторизации (см. §5g):
curl -s http://192.168.0.50/config.txt -o zont_config/config_$(date +%Y-%m-%d_%H-%M-%S).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)
✅ Есть готовый скрипт — test_roundtrip.py (в репо с 2026-09-17, коммит 199f2b1).
Делает TXT → YAML → TXT, сравнивает множеством строк с нормализацией кодировки
(источник UTF-8 или windows-1251, выход всегда windows-1251). Exit: 0 чисто / 1 расхождения / 2 ошибка запуска.
cd /Users/admin/Automation/HA-ZONT-Modbus
python3 test_roundtrip.py # свежий конфиг из zont_config/
python3 test_roundtrip.py zont_config/archive/H2000_PRO_config_actual-4.txt
Вывод при успехе: ✅ ROUND-TRIP ЧИСТЫЙ — расхождений нет + счётчики #Z до/после.
Ручная проверка (если скрипт недоступен)
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 # проверить exit!
python3 /Users/admin/Automation/HA-ZONT-Modbus/yml-to-config.py a.yml > b.txt
iconv -f cp1251 -t utf-8 "$SRC" | tr -d '\r' | sort > A.txt
iconv -f cp1251 -t utf-8 b.txt | tr -d '\r' | sort > B.txt
diff A.txt B.txt # пусто = round-trip чистый
🔴 Сравнивать множеством строк (
sort+diff), НЕ построчно.yml-to-config.pyпишет объекты в порядкеTYPE_ORDER, а в исходном файле порядок другой → наивныйdiffдаёт ~56 ложных расхождений при полностью корректной конвертации.
📌 Нюанс кодировки: источник бывает в UTF-8, а
yml-to-config.pyвсегда пишет windows-1251 → наивныйdiffпокажет различия на кириллице. Нормализовать кодировку с обеих сторон (iconv), как в командах выше.
⚠️
>в шелле затирает целевой файл ещё до старта питона — поэтому рядом с непустым.txtпоявляется пустой.yml. Это признак падения конвертера на конкретном объекте: смотреть stderr.
🔴 Не проверять результат через пайп (
iconv … | grep -c). Пустой промежуточный файл в пайпе даёт ложный «успех». Смотретьwc -cцелевого файла напрямую. (Реальный случай 2026-09-17 — см. питфолл 16.)
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. ⚠️ Снятие конфига — уже не ручное: http://192.168.0.50/config.txt отдаёт весь конфиг без авторизации (§5g). Заливка — утилита по USB / облако / WS только для #S (§5g-2) |
| 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, а не добавляет дубль |
| 16 | 🔴 iconv … | grep в пайпе маскирует пустой файл |
Промежуточный b.txt был 0 байт, а grep -c в пайпе отработал «успешно» → ложный вывод «598 → 598, чисто». Проверять wc -c целевого файла напрямую, а не через пайп |
| 17 | 🔴 Правка parse_atom ради 1.0 ломает _raw_*-путь |
Первая попытка нормализовала whole-float → int; _raw_divider стал мёртвым кодом (isinstance(v[6], int) не срабатывал). Итог: whole-float остаётся float — иначе энкодер печатает 1 вместо 1.0 |
| 18 | 🔴 Порядок объектов в файле ≠ порядок типов в TYPE_ORDER |
Сравнение «как есть» даёт ~56 ложных расхождений. Сравнивать множеством строк (sort + diff), порядок не значим |
| 19 | ⚠️ Негативный тест-детектор проверять реальной порчей | Подмена id: 11109 → 111099 ничего не ломает (id косметический). Ловить нужно удаление объекта: снести delays[11824] → детектор обязан сработать |
| 20 | 🔴 read_file возвращает контент с номерами строк — не patch-ить им vault |
Правки Obsidian-заметок делать через obsidian-MCP (mcp_obsidian_patch_note), не файловыми скриптами. Alex 2026-09-17: «какого хуя ты скриптами лезешь в обсидиан» |
| 21 | 🔴 Не отдавать артефакт, не прочитав его самому | Round-trip «байты сходятся» ≠ «читаемо». Форма blocks/extra_links проходила round-trip, но сценарий 8456 превращался в список цифр — выявил Alex, а не я. Смотреть глазами главный/сложный кейс, а не только простые |
| 22 | 🔴 Артефакты — в проект, не в /tmp |
Alex 2026-09-17: «качай доки в папку в проекте а не в темп». YAML конфига — zont_config/*.yml, не /tmp |
Ограничения конвертера (найдено 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 целиком — ПРОЙДЕН на всех 4 конфигах (см. §2.2). Сверка по множеству строк с нормализацией кодировки.
Коммит
✅ Закоммичено 2026-09-17 — 199f2b1 «Support multi-step scenarios, delays (type 45) and multi-action steps».
3 файла, +327/−75. В коммит вошли: config-to-yml.py, yml-to-config.py, test_roundtrip.py.
Не запушено (origin/main..HEAD = 3 коммита впереди) — пуш ждёт команды Alex.
5c. 🔄 Переработка структуры YAML сценариев — ✅ СДЕЛАНО 2026-09-17
✅ СТАТУС (2026-09-17, ночь): парсер и энкодер дописаны, форма — плоский
steps[], round-trip чистый на 6 конфигах. Семантика типов 47/48/50/59 раскрыта (ответы Alex — family/tech/zont-scenario-logic-11109 §8.7), операторы и логика подтверждены фактами конфига. Код реализован. Не закоммичено — ждёт команды Alex.📄 Артефакт:
zont_config/config_local_2026-09-17_14-16-35.yml(6110 строк) — свежий локальный конфиг в читаемой форме, лежит в проекте (не в/tmp).
Задача Alex (исходная): «переписать блок парсинга/сборки сценариев чтобы он составлял синтаксис как у Home Assistant automations вместо текущей разбросанной структуры. с опциональными айдишниками у операторов».
🔴 Правка постановки Alex'ом (ключевое — не повторять ошибку)
Первая версия плана натягивала HA-синтаксис (triggers / platform: state / to:) на ZONT-логику.
Alex отклонил:
«нет структура должна быть как в zont. триггеров нет. у нас сценарий Передернуть Автомат Котельной буквально содержит вложенные инструкции: если ... то... итд. не надо натягивать сову структуры на глобус HA syntax.»
Правило: в ZONT нет триггеров — роль триггера играет само изменение реле, а сценарий читается как вложенные инструкции «если … то …». Структура YAML должна повторять ZONT, а не HA.
✅ Реализованная форма — плоский steps[] (ФИНАЛЬНАЯ, 2026-09-17)
🔴 ИСПРАВЛЕНО в конце сессии. Первый вариант делил поле 2 на
blocks(шаги 46) иextra_links(голые id остальных объектов) —blocksвыводился первым, и весь сценарий превращался в список цифр: тела «установка температуры»,puts,set var1, пауза 5 суток лежали в других секциях файла, порядок терялся. Alex: «почему там блядь if идет первым блоком?! где все что до него происходит?!». Правильно: ОДНО полеsteps— плоский список в исходном порядке, каждое тело инлайн.
scenarios:
- id: 8456
name: Простой тестовый сценарий
trigger: {type: schedule} # manual | trigger | schedule | interval
_raw_trigger_kind: 8 # исходное поле 5
steps: # = поле 2 сценария, в исходном порядке, всё с телами
- {id: 8457, raw: [9, 'Установить целевую температуру 23.8 …', 8669, '2968']}
- {id: 8458, script: 'objcmd 8700 "1 %0"', args: [10.5, 0, 1]}
- {id: 8463, script: 'objcmd 9102 "6,%0";#a', args: [8462, 0, 0]}
- {id: 11109, run_scenario: Передернуть Автомат Котельной}
- {id: 8466, script: 'puts "Отладка"', args: [0, 0, 0]}
- {id: 8473, script: 'set var1', args: [8471, 0, 0]}
- {id: 8489, wait: 432000000} # пауза 5 суток
- id: 8506 # шаг с условием — тоже элемент этого списка
if:
id: 8496
group: and # and | or | not
children:
- {id: 8493, op: '<', left: 8492, value: 3}
- {id: 8495, op: '>=', left: 8472, right: 8494}
then:
- id: 8505
kind: 1
if: {id: 8501, group: or, children: [...]}
then:
- {id: 8502, script: 'puts "then-text"', args: [0, 0, 0]}
else:
- {id: 8504, script: 'storeev A "alert"', args: [0, 0, 0]}
- {id: 8507, unresolved: true} # ссылка на отсутствующий объект
Правило: steps[] = поле 2 как есть, порядок значим, каждое тело на месте.
Никаких blocks/extra_links/_raw_links. Секции delays/scenario_steps/
scenario_conditions/scenario_scripts/scenario_raw_objects остаются как служебные
(для round-trip), но сценарий читается целиком в steps.
Маппинг типов на YAML:
| Тип | В YAML | Пример |
|---|---|---|
11 |
trigger: {type, …} + steps[] |
trigger: {type: schedule, days: [mon,…], time: '13:26'} |
46 |
steps[].{if, then, else} |
{id, kind?, if, then: […], else?: […]} |
47 |
{op, left, value} или {op, left, right} |
{id: 8493, op: '<', left: 8492, value: 3} |
48 |
{group, children} |
{id: 8496, group: and, children: […]} |
49 |
{condition: {object, operator, value}} |
старая форма, поддержана |
59 |
{script: <код дословно>, args: […]} |
{id: 8502, script: puts-text} |
9 |
{command, target, target_name, target_type, value} |
{id: 9826, command: …, target: 9263, target_name: 'Реле 1: Конвектор кухня', target_type: 14, value: '1'} |
5 |
{action, output, output_id, value, params?} |
{id: 9500, action: …, output: 145728, output_id: 9108, value: 1, params: [0, 0, …, 512]} |
45 |
{wait: ms} |
{id: 8489, wait: 432000000} |
3 (SMS) |
raw: *id00N — тело в sms_notifications |
якорь YAML |
50, неизвестные |
raw: […] |
{id: 8548, raw: [50, 1, 0, 0, 109]} |
Type 9 — команда (реле / контур / режим). Формат в конфиге: [9, '<подпись>', <id цели>, '<значение>'].
target разворачивается в target_name + target_type; value — строка ('1'/'0' или id-ссылка).
Проверено: 9826→тип 14 (реле), 8641→тип 16 (контур Спальня), 8640→тип 20 (режим Режим отопления),
8470→тип 16 (Контур ГВС, значение '8574').
⚠️ Поле 4 ('2968', '8574', '1') — не значение уставки, а ссылка/код (напр. '2968' при
тексте «температуру 23.8»). Семантику поля 4 Alex не подтверждал — не выводить её из подписи.
Type 5 — действие над выходом. Формат: [5, '<имя>', <output_ref>, <on/off>, …].
output_id = output_ref >> 4. Остальные непустые поля → params.
Операторы type 47 (порядок UI <, >, =, <=, >=): 0=<, 1=>, 2==, 3=<=, 4=>=.
Логика type 48: 0=and, 1=or, 2=not.
Триггер (поле 5 типа 11):
| Поле 5 | trigger.type |
Доп. поля |
|---|---|---|
0/1 |
manual |
enabled: bool |
8 |
schedule |
days[], days_mask, time, time_raw (поле 4 = маска дней, бит 0=ПН; поле 6 = (час<<8)|мин) |
9 |
trigger |
— |
10 |
interval |
interval_ms |
Реализация (код)
config-to-yml.py — рекурсивные хелперы dump_step() / dump_condition() / dump_leaf() /
dump_action(); dump_step() диспетчеризует по типу (46→if/then/else, 59→script, 47/48/49→
дерево, 45→wait, 50→trigger_object raw, 11→run_scenario, 5/9/3→raw-тело) — так каждый элемент поля 2
рендерится целиком на месте. Сборщики scenario_scripts / scenario_conditions /
scenario_raw_objects; fixed-point sweep операндов (left/right/args) — без него теряются
скрипты второго порядка (напр. 8459/8460 через 8462); неизвестные типы → raw_objects вместо
падения (KNOWN_TYPES += 47,48,50,59). blocks/extra_links/_raw_links — УБРАНЫ.
yml-to-config.py — emit_step() принимает любой элемент списка (raw по типу → в свою секцию;
script/wait/trigger_object/условие/if-then-else); emit_condition() / emit_action();
поддержка старых форм (blocks+extra_links, steps+when/then) через _scenario_from_legacy();
новые секции scenario_scripts/scenario_conditions/scenario_raw_objects/raw_objects
в TYPE_ORDER.
🔴 Четыре питфолла реализации (round-trip ловил каждый)
| # | Проблема | Причина | Решение |
|---|---|---|---|
| 1 | Объекты не попадали в вывод | result_lines в энкодере — фильтрованное подмножество: объект должен быть и в lines, и в TYPE_ORDER |
Добавить секции в TYPE_ORDER + в lines |
| 2 | Операнды терялись | left/right/args — не рёбра дерева, обход их не видит |
Fixed-point sweep: собирать референсы из raw-тел, докидывать, повторять |
| 3 | exit 4 «Duplicate ID» | Секции эмитились дважды (явный цикл + TYPE_ORDER) |
Убрать явный цикл; типы 5/9/3/0/36/1/14/16/20/27/53 не регистрировать в raw_objects (у них свои секции) |
| 4 | 🔴 Порядок сценария терялся | blocks первым + extra_links голыми id → сценарий = список цифр |
Плоский steps[] вместо blocks/extra_links |
| 5 | 🔴 Отдал артефакт, не прочитав сам | Round-trip «байты сходятся» ≠ «читаемо». Alex открыл YAML и увидел в 8456 первым блоком if, а до него — список цифр |
Читать глазами главный/сложный кейс, а не только простые 1-блочные сценарии. Питфолл 21 |
| 6 | Много raw в шагах |
type 9/5 рендерились как raw: [...] хотя поля прозрачны |
Раскрыты (§5c, «Type 9 / Type 5»). raw остался только у SMS (3) и непонятных (50) |
Обязано сохраниться байт-в-байт (сохранено):
- порядок объектов в файле (45 идёт после 11, но до 46)
- порядок ссылок поля 2 сценария → теперь сам
steps[]в исходном порядке - число полей (7 vs 8) и
_raw_*-поля (_raw_field_count,_raw_trigger_kind,_raw_trigger_params) — не удалять
Якоря YAML: тела объектов, которые принадлежат другим секциям (sms_notifications,
actions), при повторе рендерятся через *id00N-якоря (напр. 8195, 9500 в 8456). Тело
записано один раз в своей секции; в сценарии — ссылка. Читать менее удобно, но байт-точность
сохранена. (Решение о разворачивании в инлайн — за Alex.)
✅ Round-trip — 6/6 конфигов чисто
cd /Users/admin/Automation/HA-ZONT-Modbus
python3 test_roundtrip.py zont_config/config_local_2026-09-17_14-16-35.txt # ✅ 685/685
python3 test_roundtrip.py zont_config/config_0FA7C33CC89F_…_12-12-28.txt # ✅
for f in zont_config/archive/*.txt; do python3 test_roundtrip.py "$f"; done # ✅ -2, -3, -4
python3 test_roundtrip.py zont_local_ui_recon/config_live_192.168.0.50.txt # ✅
Бэкапы кода: /tmp/config-to-yml.py.bak, /tmp/yml-to-config.py.bak (сняты перед правкой).
Откат: git (199f2b1 — последний коммит).
5d. Что делает сценарий 11109 — и что логика УЖЕ есть
Разбор по факту (не гипотеза) — подробно в family/tech/zont-scenario-logic-11109:
Порядок действий шага 11827:
вкл virt.Запретить(11191) → выкл Автомат(11030) → пауза 20 с(11824)
→ вкл Автомат(11029) → пауза 60 с(11825) → выкл virt.Запретить(11192) → пауза 0(11826)
Условие 11823: virt.Запретить(11190) == 0. Смысл — защита от повторного передёргивания:
реле ставится в 1 первым действием, условие требует 0 → пока реле в 1, сценарий не перезапустится.
Минимальный интервал между передёргиваниями = 80 с (20+60).
🔴 Защита в конфиге УЖЕ есть, но сценарий НЕ активен. Поле v[5] сценария =
0→ ручной запуск в положении «выкл». ⚠️ Исправлено 2026-09-17: поле 5 — это тип запуска (0/1ручной,8расписание,9триггер,10интервал), а не булевenabled. См. §5i и family/tech/zont-scenario-logic-11109 §8.1.
5f. Ответы на вопросы постановки (этап 2 снят)
Вопрос Alex'у «что именно не хватает на сценариях» (варианты а/б/в) закрыт его же реакцией: задача —
«дописать парсер и энкодер», а не править логику. Правка YAML сценария 11109 (этап 2 прежнего плана)
с повестки снята — работа ограничена конвертерами.
🔴 Урок коммуникации 2026-09-17. Alex дважды резко реагировал на развёрнутые планы-опросники («нихуя не понял тебе че надо блядь? что блядь сломано?», «ТЫ ХУЛИ ВСТАЛ БЛЯДЬ!?»). Что сработало: сжатый факт «что сломано» + сразу делать. Не задавать уточняющих вопросов там, где симптом уже назван; не строить гипотезы вместо чтения конфига; не планировать сверх постановки.
⚙️ Бэкап кода — только git, не
/tmp. Alex 2026-09-17: «какой нахуй бэкап скриптов — там в гите все». Изменения скриптов откатываются через git, отдельные копии в/tmp/не делать.
5g. 🔴 Локальный эндпоинт контроллера
Контроллер отдаёт весь конфиг по HTTP без авторизации:
curl -s http://192.168.0.50/config.txt -o zont_config/config_$(date +%Y-%m-%d_%H-%M-%S).txt
Формат — тот же #Z… / #S…, что парсит config-to-yml.py. Снятие конфига больше не ручная
операция через облако.
Полный разбор локального интерфейса — WebSocket-протокол, чтение/запись #S-настроек,
кнопка сохранения #S15=1, 25 настроек против 9 в UI, утечка секретов, инструменты разведки:
➡️ family/tech/zont-api §6bis
5g-2. 🔼 ЗАЛИВКА конфига обратно — чем и как (добыто 2026-09-17)
Снятие конфига автоматизировано (§5g), но заливка остаётся ручной — идёт не через
config.txt (этот путь только на чтение). Доступные каналы:
| Канал | Что умеет | Ограничение |
|---|---|---|
| Настроечная утилита по USB | заливка конфига + прошивки | Windows-only, нужен USB-кабель и драйвер |
Облако ZONT (my.zont.online) |
правка сценариев/реле через веб-UI | руками, по одному объекту |
Локальный WebSocket ws://192.168.0.50/ws |
запись #S-настроек ({"scmd":"#S<n>=<val>"} + #S15=1) |
#Z-объекты не пишутся — только #S |
🔴 Локальный WS даёт запись только системных
#S-настроек (Wi-Fi, MQTT, номер). Сценарии, реле, шаги (#Z11/14/46/49) через него не заливаются — для них по-прежнему утилита или облако.
Утилита H1000 Programmator 2.8.5 — добыта
# официальный источник, скачивается без авторизации
curl -sL -o h1000_utility_beta.bin https://lk.zont-online.ru/download/simple/h1000_utility_beta
| Параметр | Значение |
|---|---|
| Версия | 2.8.5 (prgm.2.8.5.exe, 2.9 MB, Delphi/Borland) |
| Интерфейс | USB serial-over-USB (usbser.sys + Hxxxx.inf) |
| Архив на Mac | ~/rasputin-tmp/zont-util/h1000_utility_beta.bin (4.5 MB) |
| Распаковано | ~/rasputin-tmp/zont-util/util_beta/H1000 Programmator/ |
| Хелпы внутри | выходы.rtf, Отопление.rtf, пользователи.rtf, смс управление.rtf, DTMF управление.rtf |
🔴 Питфолл распаковки. macOS
unzipпадает на кириллических именах в архиве (write error (disk full?)— на самом деле не disk full). Имена в CP866. Решение — Pythonzipfileс перекодировкойcp437 → cp866:~/rasputin-tmp/zont-util/extract.py.
⚠️ Пятно на репутации формата: файлы
Configs/*.setвнутри архива — НЕ конфиги устройства. Это UTF-8 JSON-словари подписей интерфейса ({"Value":0,"Edit":"Пользователь 1"}). Не пытаться парсить их нашим конвертером.
Прошивка — формат .enc (зашифрован)
curl -sL -o H2000_fw.zip https://lk.zont-online.ru/download/firmwares/H2000_515__330_290.zip
| Файл | Размер | Энтропия | Вывод |
|---|---|---|---|
STM_MEGA_400_.enc |
145 177 B | 7.9986 / 8.0 | зашифрован, не сжат |
main_c.evc |
49 610 B | 5.6487 | частично структурный (Mega-CX) |
Прошивка тоже отдаётся без авторизации. Утилита грузит именно *.enc
(строки firmware_.enc, .enc|*.enc найдены в exe). Расшифровка — отдельное исследование.
🎯 Схема URL прошивки — НАЙДЕНА 2026-09-17
https://lk.zont-online.ru/download/firmwares/H2000_PRO_<HW>__<FW>_<PROFILE>.zip
H2000_PRO_723__678_1.zip ← наш контроллер, 1.2 MB
Правило: префикс серии (H2000_PRO_) + двойное подчёркивание перед версией ПО.
Одиночные варианты (H2000_723__678_1.zip, H2000_PRO_723__678.zip) → 404.
Проверка соответствия (конфиг #S7 ↔ API морды ↔ имя файла):
| Источник | Значение |
|---|---|
#S7 прибора |
H2000_PRO 723 678 |
get_firmware_releases (DevTools) |
"version":"678:1", "firmware_version":678 |
| Имя архива | H2000_PRO_723__678_1.zip → h2000_pro_v2_.enc |
→ 723 = плата (HW), 678 = прошивка, 1 = profile_version. 678 — последняя
стабильная; 602/585/564 — бета.
📁 Скачанные прошивки, утилита, скрипты разведки и живой конфиг лежат в проекте:
/Users/admin/Automation/HA-ZONT-Modbus/zont_local_ui_recon/(см. еёREADME.md).
➡️ Полный разбор утилиты, прошивок и API морды — family/tech/zont-api §10
5h. 🌐 Облачный API ZONT — конфига не даёт
Исследовано 2026-09-17. Доки скачаны в проект: zont_api_docs/.
Главный вывод: облачный API (my.zont.online/api/*, 11 методов) — это состояния и история,
не конфигурация. Слово scenario в доке — 0 раз. Методов для типов 11/14/46/49 нет.
z3k_config упоминается только как источник ID.
Следствие: конвертер .txt ⇄ .yml — единственный путь правки сценариев и реле.
Что API всё же умеет (для H-2000 PRO): чтение состояний (devices?load_io=true),
история (load_data), режимы отопления (update_device), сирена/охрана (set_io_port).
➡️ Полный разбор API — family/tech/zont-api
5i. 🔴 Конструктор логики ZONT (типы 47/48/50/59) — ✅ ПОДДЕРЖАН 2026-09-17
Найдено 2026-09-17. Alex создал в UI тестовые сценарии («все доступные триггеры и варианты логики»). Разбор локального конфига
config_local_2026-09-17_14-16-35.txt(660#Z) вскрыл визуальный конструктор логики со скриптовым движком.✅ Статус: поддержан. Парсер и энкодер дописаны (§5c), round-trip чистый. Ранее падал с
Ошибка: Условие 8496: не type 49(exit 2) — больше не падает.
Две ключевые находки:
- 🔴 Поле 5 типа 11 — НЕ
enabled, а ТИП ЗАПУСКА:0/1ручной,8расписание,9триггер,10интервал. Поле 6 — параметр (для интервала — мс; для расписания —(час<<8)|мин, поле 4 — маска дней недели, бит 0=ПН). - Новые типы:
47(лист условия:op, left, value|right),48(группаand/or/not),50(объект-триггер, хранится какraw),59(мини-скрипт:objcmd/objstate/expr/set/puts/storeev; код и args сохраняются дословно).
Операторы type 47 (порядок UI <, >, =, <=, >=): 0=<, 1=>, 2==, 3=<=, 4=>=.
📌 Стратегия (реализована): всё непонятое (скрипты
59дословно, суффиксы;#a/;#h/;#p, отсутствующие id,type 50) сохраняется байт-в-байт черезraw/unresolved— без «умного» перевода. Round-trip остаётся чистым, а непонятное не портится.
➡️ Полный разбор — family/tech/zont-scenario-logic-11109 §8
6. Состояние проекта (проверено 2026-09-17, ночь)
✅ Конвертеры переписаны под сценарный конструктор (§5c). Рабочее дерево — не закоммичено,
ждёт команды Alex. Последний коммит — 199f2b1.
✅ Round-trip чистый на 6/6 конфигах (включая новый локальный с 660 #Z и legacy 7-поле -2).
| Файл | Статус |
|---|---|
config-to-yml.py |
🆕 изменён — парсер типов 47/48/50/59, плоский steps[], триггер, type 9/5 раскрыты, raw-фоллбэк (§5c). не закоммичено |
yml-to-config.py |
🆕 изменён — обратные emit_*, emit_step по типу, новые секции в TYPE_ORDER (§5c). не закоммичено |
test_roundtrip.py |
✅ в коммите 199f2b1, без изменений |
zont_config/config_local_2026-09-17_16-02-18.txt |
🆕 34 962 байт, 686 строк — ПОСЛЕДНИЙ конфиг с тестовыми сценариями (Alex пересоздал: контур Спальня, режим Режим отопления). Снят с http://192.168.0.50/config.txt. не в git |
zont_config/config_local_2026-09-17_16-02-18.yml |
🆕 разобранный YAML (финальная форма) — round-trip ✅. не в git |
zont_config/config_local_2026-09-17_14-16-35.yml |
🆕 разобранный YAML (предыдущий конфиг, 6110 строк). не в git |
zont_config/config_local_2026-09-17_14-16-35.txt |
34 907 байт, 660 #Z — предыдущий с тестовыми сценариями. не в git |
zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt |
32 689 байт — свежий боевой конфиг, не в git |
zont_config/archive/ |
-2/-3/-4 — ✅ закоммичены (7ae0e32) |
zont_local_ui_recon/config_live_192.168.0.50.txt |
живой конфиг для разведки WS-интерфейса, не в git |
zont_api_docs/ |
✅ локальная копия доки облачного API (zont_api_docs.html, .txt, convert.py). не в git |
read_scenarios.py |
🆕 читаемый дамп сценария — дерево если/то/иначе с именами. Токенайзер split_top(). не в git |
dump_new_types.py, probe_types.py, trace_scenarios.py |
🆕 скрипты разбора сценарных типов. Разбор — family/tech/zont-scenario-logic-11109 §8.8. не в git |
probe_sched.py, verify_answers.py, check_ops.py, audit_8456.py, audit2.py, chk_extra.py, why_raw.py |
🆕 проверочные скрипты. не в git |
~/rasputin-tmp/zont-util/ |
✅ настроечная утилита H1000 Programmator 2.8.5, прошивка .enc, extract.py. Разбор — §5g-2 |
~/rasputin-tmp/zont-{auth-probe,recon,recon2,ws-probe}.js |
✅ скрипты разведки локального WS. не в git |
Бэкапы кода перед правкой §5c: /tmp/config-to-yml.py.bak, /tmp/yml-to-config.py.bak.
⛔ Не /tmp для артефактов — Alex: «качай доки в папку в проекте а не в темп». YAML конфигов — только zont_config/*.yml.
Не запушено: origin/main..HEAD = 3 коммита (199f2b1, 7ae0e32, 12ba22b). Плюс новые правки
§5c — незакоммичены.
✅ Пустой
.yml(0 байт) больше не актуален — причина была в падении на сценарии11109, исправлено (§5b).
Что НЕ в git и почему: .txt свежего конфига и его .yml — рабочие артефакты конвертации,
Alex их не добавлял. Не коммитить без команды.
Содержимое проекта, не относящееся к конвертерам:
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/tech/zont-api — облачный API ZONT: конфиг не поддерживает; альтернатива конвертеру отсутствует
- family/how-to/home-automation — контур автоматизации, ZONT, Modbus slave ID и регистры (§6)
- family/how-to/gitea-config — Gitea: креды, создание репо, питфоллы
- family/how-to/ha-automations — автоматизации HA