[2026-09-17] eagle: family/how-to/zont-config-compiler.md family/tech/zont-scenario-logic-11109.md

This commit is contained in:
Alexey Martemyanov
2026-09-17 12:55:15 +06:00
parent da8527f6a8
commit 78c04ea1fe
2 changed files with 209 additions and 41 deletions
+152 -36
View File
@@ -20,7 +20,7 @@ tags:
- homeautomation
title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
type: how-to
updated: '2026-09-17e'
updated: '2026-09-17g'
---
# ⚙️ ZONT Config Compiler — конвертеры `.txt ⇄ .yml`
@@ -32,7 +32,7 @@ updated: '2026-09-17e'
|---|---|
| **Проект (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) |
| **Скрипты** | `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 |
@@ -253,21 +253,41 @@ grep -c '^#Z' /tmp/zont_new.txt
## 4. Проверка целостности (round-trip)
`README_converters.md` заявляет round-trip **130/130 объектов**: TXT → YAML → TXT даёт идентичный файл.
**Есть готовый скрипт — `test_roundtrip.py`** (в репо с 2026-09-17, коммит `199f2b1`).
Делает `TXT → YAML → TXT`, сравнивает **множеством строк** с нормализацией кодировки
(источник UTF-8 или windows-1251, выход всегда windows-1251). Exit: `0` чисто / `1` расхождения / `2` ошибка запуска.
Команда для проверки на любом конфиге:
```bash
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` до/после.
### Ручная проверка (если скрипт недоступен)
```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/config-to-yml.py "$SRC" > a.yml # проверить exit!
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
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 чистый
grep -c '^#Z' A.txt B.txt # счётчик объектов
```
> 📌 **Нюанс кодировки:** источник бывает в UTF-8, а `yml-to-config.py` всегда пишет windows-1251 → наивный `diff` покажет различия на кириллице. Сравнивать структуру и числовые поля, либо нормализовать кодировку с обеих сторон.
> 🔴 **Сравнивать множеством строк (`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.)
---
@@ -290,6 +310,11 @@ grep -c '^#Z' A.txt B.txt # счётчик объектов
| 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: «какого хуя ты скриптами лезешь в обсидиан» |
### Ограничения конвертера (найдено 2026-09-17) — ВСЕ ЗАКРЫТЫ
@@ -340,50 +365,141 @@ grep -c '^#Z' A.txt B.txt # счётчик объектов
- `ast.parse()` на обоих файлах — синтаксис OK
- `config-to-yml.py` на боевом конфиге: **больше не падает** на сценарии `11109` (ранее `Ошибка: Сценарий 11109: поддерживается только 1 шаг`, exit 2)
-**Round-trip целиком — ПРОЙДЕН на всех 4 конфигах** (см. §2.2). Сверка по множеству строк с нормализацией кодировки.
### Что НЕ проверено (осталось)
### Коммит
- **Round-trip целиком**: `TXT → YAML → TXT`, сверка 598 объектов, `diff` чистый. Команда требует апрува, была запущена и истекла по таймауту.
**Закоммичено 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`.
### План дальше (этапы 2–3, ждут Alex)
Не запушено (`origin/main..HEAD` = 3 коммита впереди) — пуш ждёт команды Alex.
**Этап 2 — правка YAML** под новую логику (только после ответа Alex на вопрос: (а) просто `enabled: true` для «Передернуть Автомат Котельной» / (б) добавить триггер по событию / (в) другое).
---
**Этап 3 — `YAML → TXT`, сверка счётчиков, коммит ДО → Alex заливает руками → Alex проверяет руками → коммит ПОСЛЕ.**
## 5c. 🔄 Переработка структуры YAML сценариев — ПЛАН, ждёт апрува (2026-09-17)
> 🔴 Порядок работ с боевыми конфигами: **коммит ДО → правка → заливка → ПРОВЕРКА АЛЕКСОМ руками → коммит ПОСЛЕ**. Мой `read back` = «конфиг записался», НЕ «работает».
**Задача Alex:** «переписать блок парсинга/сборки сценариев чтобы он составлял синтаксис как у Home Assistant automations вместо текущей разбросанной структуры. с опциональными айдишниками у операторов».
### 🔴 Правка постановки Alex'ом (ключевое — не повторять ошибку)
Первая версия плана натягивала **HA-синтаксис** (`triggers` / `platform: state` / `to:`) на ZONT-логику.
Alex отклонил:
> «нет структура должна быть как в zont. **триггеров нет.** у нас сценарий Передернуть Автомат Котельной буквально содержит вложенные инструкции: **если ... то... итд.** не надо натягивать сову структуры на глобус HA syntax.»
**Правило:** в ZONT **нет триггеров** — роль триггера играет само изменение реле, а сценарий читается
как вложенные инструкции «если … то …». Структура YAML должна повторять ZONT, а не HA.
### Утверждённая форма (предложена, ждёт финального ОК по двум вопросам)
```yaml
scenarios:
- id: 11109
name: 'Передернуть Автомат Котельной'
enabled: false
blocks:
- id: 11827 # шаг (46)
if:
id: 11823 # условие (49), id опционален
relay: 11190
operator: equals # equals | not_equals
value: 0
then:
- {id: 11191, action: relay_on, relay: 11190}
- {id: 11030, action: relay_off, relay: 11028}
- {id: 11824, action: wait, ms: 20000}
- {id: 11029, action: relay_on, relay: 11028}
- {id: 11825, action: wait, ms: 60000}
- {id: 11192, action: relay_off, relay: 11190}
- {id: 11826, action: wait, ms: 0}
tail: # хвостовые задержки из поля 2
- {id: 11828, action: wait, ms: 0}
```
**Что меняется по сравнению с текущим YAML:**
| Сейчас (разбросано) | Станет |
|---|---|
| `scenarios[].when` / `.then` — плоский дубль для 1-шаговых | убрано: **одна форма** `blocks[].if/.then` для всех 65 |
| `scenarios[].steps[]` + отдельные `delays` / `scenario_conditions` / `scenario_steps` | одна вложенная структура `blocks` |
| `wait` лежит отдельной секцией верхнего уровня | `wait`**внутри** `then`, по месту срабатывания |
| `extra_links: [11828]` — голые id | `tail:` с блоками `wait` |
**Открытые вопросы (заданы Alex, ответа ещё нет):**
1. Имена ключей: `blocks`/`if`/`then` — или ближе к терминам ZONT `steps`/`condition`/`actions`?
2. Пауза в конце (`11826`, `ms: 0`) — оставлять как `wait` (честно для round-trip) или схлопывать?
**План работ (после апрува):**
1. `config-to-yml.py` — блок type 11: генерировать `blocks[].if/then`; `wait` (45) встраивать в `then`; `tail` для хвостовых
2. `yml-to-config.py` — обратный разбор `blocks`; **поддержать старые формы** (`when`/`then`, `steps`) для совместимости
3. `test_roundtrip.py` — все 4 конфига должны остаться чистыми
4. Доки — обновить §2.1 этой заметки
**Обязано сохраниться байт-в-байт (риск round-trip):**
- порядок объектов в файле (45 идёт **после** 11, но **до** 46)
- `extra_links` — хвостовые задержки в поле 2 сценария
- число полей (7 vs 8) и `_raw_*`-поля (`_raw_field_count`, `_raw_field3`, `_raw_divider`) — **не удалять**
**Бэкап:** коммит `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` → сценарий **выключен в контроллере**,
> поэтому защита и функция передёргивания не работают. Вопрос «дописать логику» на деле может быть
> «включить существующую» (`enabled: true`) — это НЕ подтверждено Alex'ом, не додумывать.
---
## 5f. Ответы на вопросы постановки (этап 2 снят)
Вопрос Alex'у «что именно не хватает на сценариях» (варианты а/б/в) **закрыт его же реакцией**: задача —
«дописать парсер и энкодер», а не править логику. Правка YAML сценария `11109` (этап 2 прежнего плана)
с повестки снята — работа ограничена конвертерами.
> 🔴 **Урок коммуникации 2026-09-17.** Alex дважды резко реагировал на развёрнутые планы-опросники
> («нихуя не понял тебе че надо блядь? что блядь сломано?», «ТЫ ХУЛИ ВСТАЛ БЛЯДЬ!?»). Что сработало:
> **сжатый факт «что сломано» + сразу делать**. Не задавать уточняющих вопросов там, где симптом уже
> назван; не строить гипотезы вместо чтения конфига; не планировать сверх постановки.
> ⚙️ **Бэкап кода — только git, не `/tmp`.** Alex 2026-09-17: «какой нахуй бэкап скриптов — там в гите все». Изменения скриптов откатываются через git, отдельные копии в `/tmp/` не делать.
---
## 6. Состояние проекта (проверено 2026-09-17)
## 6. Состояние проекта (проверено 2026-09-17, вечер)
Репозиторий `/Users/admin/Automation/HA-ZONT-Modbus`**рабочее дерево грязное**, разгребание ждёт отдельной команды Alex:
**Конвертеры закоммичены — `199f2b1`.** Рабочее дерево чистое (после этого коммита).
| Файл | Статус |
|---|---|
| `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) — не закоммичены |
| `config-to-yml.py`, `yml-to-config.py` | ✅ **изменены и закоммичены** (`199f2b1`, §5b) |
| `test_roundtrip.py` | ✅ **новый файл**, в коммите `199f2b1` |
| `zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt` | 32 689 байт — свежий конфиг с контроллера, **не в git** (`untracked`) |
| одноимённый `.yml` | ✅ **пересобран, 80 472 байта / 4320 строк / 24 секции**, YAML валиден. **не в git** |
| `zont_config/archive/` | **не в git** (`untracked`) — лежит в `.gitignore`-нейтральном состоянии |
| `zont_config/H2000_PRO_config_actual-{2,3,4}.txt` / `.yml` | ✅ **перемещены в `archive/` и закоммичены** (`7ae0e32`) |
> 🔴 **Причина пустого `.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-кода переносить в репо.
Не запушено: `origin/main..HEAD` = 3 коммита (`199f2b1`, `7ae0e32`, `12ba22b`).
> ✅ **Пустой `.yml` (0 байт) больше не актуален** — причина была в падении на сценарии `11109`,
> которое исправлено (§5b). Файл пересобран, 598 объектов.
**Что НЕ в git и почему:** `.txt` свежего конфига и его `.yml` — рабочие артефакты конвертации,
Alex их не добавлял. Не коммитить без команды.
**Содержимое проекта, не относящееся к конвертерам:**
+57 -5
View File
@@ -4,6 +4,7 @@ aliases:
- Передернуть Автомат Котельной
- ZONT тип 45 задержка
- ZONT сценарная логика
- ZONT поле 2 сценария
created: '2026-09-17'
namespace: family
related:
@@ -18,7 +19,7 @@ tags:
- homeautomation
title: 🔁 ZONT — сценарная логика (типы 11 / 46 / 49 / 45)
type: tech
updated: '2026-09-17'
updated: '2026-09-17b'
---
# 🔁 ZONT — сценарная логика (типы 11 / 46 / 49 / 45)
@@ -68,7 +69,8 @@ updated: '2026-09-17'
## 3. Разобранный кейс: `#Z11109` «Передернуть Автомат Котельной»
**Единственный сценарий в конфиге с нестандартной формой** 2 шага и 7 действий в одном шаге.
**Единственный сценарий в конфиге с нестандартной формой** — 7 действий в шаге и задержка (`45`)
прямо в поле 2 сценария (`11828`) вместо второго шага. См. §5 «Уточнение семантики поля 2».
```
#Z11109=11,'Передернуть Автомат Котельной',[11827,11828],0,0,0,0,0
@@ -100,7 +102,8 @@ updated: '2026-09-17'
| 6 | `11192` (type 9) | выключить `virt. Запретить передергивание` | — |
| 7 | `11826` (type 45) | пауза | 0 |
**Шаг 2 (`11828`)**`[45, 0]`, пустая пауза-заглушка. Оставлен планировщиком ZONT, функциональной нагрузки не несёт.
**Шаг 2 (`11828`)** на деле это не шаг, а **задержка** `[45, 0]` прямо в поле 2 сценария:
пустая пауза-заглушка. Оставлена планировщиком ZONT, функциональной нагрузки не несёт.
### Смысл логики
@@ -142,11 +145,60 @@ updated: '2026-09-17'
| Многошаговый сценарий (`[11827,11828]`) | ✅ разбирается с 2026-09-17 |
| 7 действий в шаге `11827` | ✅ разбирается с 2026-09-17 |
| Тип 45 (4 объекта) | ✅ парсер + эмиттер, секция YAML `delays` |
| Сценарий `enabled=0` | ℹ️ факт, не баг конвертера — решается на этапе 2 (правка YAML) |
| Round-trip всех 4 конфигов | ✅ чисто (`test_roundtrip.py`, коммит `199f2b1`) |
| Сценарий `enabled=0` | ℹ️ факт, не баг конвертера |
### 🔴 Уточнение семантики поля 2 (важно)
Поле 2 сценария — **не «список шагов»**, а **последовательность ссылок**, куда попадают объекты
**разных типов**. Полный список: шаг (`46`) **и** задержка (`45`).
В `#Z11109` поле 2 = `[11827, 11828]`, где:
- `11827` — настоящий шаг (`46`) с условием и 7 действиями;
- `11828`**задержка (`45,0`)**, т.е. «шаг-ожидание» без условия.
Поэтому формулировка «2 шага» неточна: **шаг один**, вторым элементом идёт задержка-заглушка.
Отсюда же — `extra_links` в YAML (см. [[family/how-to/zont-config-compiler]] §2.1): не-46 ссылки
сохраняются дословно, чтобы round-trip остался байт-точным.
> ⚠️ **Не путать с типом 45 внутри шага.** В `11827` задержки `11824`/`11825`/`11826` — это
> **действия внутри** шага. `11828` — **ссылка в поле 2 сценария**, т.е. элемент верхнего уровня.
---
## 6. Связанные заметки
## 6. План переработки YAML-структуры (2026-09-17, ждёт апрува)
Сценарии в YAML переписываются в нативную ZONT-форму **вложенных инструкций «если … то …»**
**без HA-синтаксиса** (`triggers`, `platform: state`). Alex: «нет структура должна быть как в zont.
**триггеров нет.**»
Предлагаемая форма для `11109`:
```yaml
scenarios:
- id: 11109
name: 'Передернуть Автомат Котельной'
enabled: false
blocks:
- id: 11827
if: {id: 11823, relay: 11190, operator: equals, value: 0}
then:
- {id: 11191, action: relay_on, relay: 11190}
- {id: 11030, action: relay_off, relay: 11028}
- {id: 11824, action: wait, ms: 20000}
- {id: 11029, action: relay_on, relay: 11028}
- {id: 11825, action: wait, ms: 60000}
- {id: 11192, action: relay_off, relay: 11190}
- {id: 11826, action: wait, ms: 0}
tail:
- {id: 11828, action: wait, ms: 0} # задержка из поля 2 сценария
```
Полный план и открытые вопросы — [[family/how-to/zont-config-compiler]] §5c.
---
## 7. Связанные заметки
- [[family/how-to/zont-config-compiler]] — конвертеры `.txt ⇄ .yml`; 2 шага и тип 45 **поддержаны с 2026-09-17** (§5b)
- [[family/tech/zont-config-object-types]] — полная таблица типов объектов и `#S`-настройки