[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 15:53:25 +06:00
parent 4eae38bab8
commit 0656011f6d
2 changed files with 99 additions and 26 deletions
+56 -23
View File
@@ -21,7 +21,7 @@ tags:
- homeautomation - homeautomation
title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
type: how-to type: how-to
updated: 2026-09-17j updated: 2026-09-17k
--- ---
# ⚙️ ZONT Config Compiler — конвертеры `.txt ⇄ .yml` # ⚙️ ZONT Config Compiler — конвертеры `.txt ⇄ .yml`
@@ -146,8 +146,9 @@ updated: 2026-09-17j
### Как это выглядит в YAML ### Как это выглядит в YAML
> ✅ **НОВАЯ форма (2026-09-17, §5c).** Все сценарии — единая вложенная структура `blocks[].if/then/else` > ✅ **НОВАЯ форма (2026-09-17, §5c).** Все сценарии — один плоский список `steps[]` в исходном
> плюс блок `trigger:`. Плоские `when`/`then` и секции `steps`/`delays`/`scenario_conditions` убраны. > порядке поля 2, каждый элемент с телом на месте; плюс блок `trigger:`. Плоские `when`/`then` и
> дележ на `blocks`/`extra_links` убраны (тот вариант был отвергнут Alex — см. §5c).
> **Форма `11109` ниже — устаревшая**, оставлена для истории; актуальная — §5c. > **Форма `11109` ниже — устаревшая**, оставлена для истории; актуальная — §5c.
**Устаревшая форма (коммит `199f2b1`)** — плоский `when`/`then` для 1-шаговых + `steps` + `extra_links`: **Устаревшая форма (коммит `199f2b1`)** — плоский `when`/`then` для 1-шаговых + `steps` + `extra_links`:
@@ -322,6 +323,8 @@ diff A.txt B.txt # пусто = round-trip чистый
| 18 | 🔴 **Порядок объектов в файле ≠ порядок типов в `TYPE_ORDER`** | Сравнение «как есть» даёт ~56 ложных расхождений. Сравнивать **множеством строк** (`sort` + `diff`), порядок не значим | | 18 | 🔴 **Порядок объектов в файле ≠ порядок типов в `TYPE_ORDER`** | Сравнение «как есть» даёт ~56 ложных расхождений. Сравнивать **множеством строк** (`sort` + `diff`), порядок не значим |
| 19 | ⚠️ **Негативный тест-детектор проверять реальной порчей** | Подмена `id: 11109``111099` ничего не ломает (id косметический). Ловить нужно удаление объекта: снести `delays[11824]` → детектор обязан сработать | | 19 | ⚠️ **Негативный тест-детектор проверять реальной порчей** | Подмена `id: 11109``111099` ничего не ломает (id косметический). Ловить нужно удаление объекта: снести `delays[11824]` → детектор обязан сработать |
| 20 | 🔴 **`read_file` возвращает контент с номерами строк — не patch-ить им vault** | Правки Obsidian-заметок делать **через obsidian-MCP** (`mcp_obsidian_patch_note`), не файловыми скриптами. Alex 2026-09-17: «какого хуя ты скриптами лезешь в обсидиан» | | 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) — ВСЕ ЗАКРЫТЫ ### Ограничения конвертера (найдено 2026-09-17) — ВСЕ ЗАКРЫТЫ
@@ -385,10 +388,13 @@ diff A.txt B.txt # пусто = round-trip чистый
## 5c. 🔄 Переработка структуры YAML сценариев — ✅ СДЕЛАНО 2026-09-17 ## 5c. 🔄 Переработка структуры YAML сценариев — ✅ СДЕЛАНО 2026-09-17
> ✅ **СТАТУС (2026-09-17, ночь): парсер и энкодер дописаны, round-trip чистый на 6 конфигах.** > ✅ **СТАТУС (2026-09-17, ночь): парсер и энкодер дописаны, форма — плоский `steps[]`,
> Семантика типов 47/48/50/59 раскрыта (ответы Alex — [[family/tech/zont-scenario-logic-11109]] §8.7), > round-trip чистый на 6 конфигах.** Семантика типов 47/48/50/59 раскрыта (ответы Alex —
> операторы и логика подтверждены фактами конфига. Код реализован. > [[family/tech/zont-scenario-logic-11109]] §8.7), операторы и логика подтверждены фактами
> **Не закоммичено** — ждёт команды Alex. > конфига. Код реализован. **Не закоммичено** — ждёт команды Alex.
>
> 📄 **Артефакт:** `zont_config/config_local_2026-09-17_14-16-35.yml` (6110 строк) — свежий
> локальный конфиг в читаемой форме, лежит в проекте (не в `/tmp`).
**Задача Alex (исходная):** «переписать блок парсинга/сборки сценариев чтобы он составлял синтаксис как у Home Assistant automations вместо текущей разбросанной структуры. с опциональными айдишниками у операторов». **Задача Alex (исходная):** «переписать блок парсинга/сборки сценариев чтобы он составлял синтаксис как у Home Assistant automations вместо текущей разбросанной структуры. с опциональными айдишниками у операторов».
@@ -402,7 +408,14 @@ Alex отклонил:
**Правило:** в ZONT **нет триггеров** — роль триггера играет само изменение реле, а сценарий читается **Правило:** в ZONT **нет триггеров** — роль триггера играет само изменение реле, а сценарий читается
как вложенные инструкции «если … то …». Структура YAML должна повторять ZONT, а не HA. как вложенные инструкции «если … то …». Структура YAML должна повторять ZONT, а не HA.
### ✅ Реализованная форма ### ✅ Реализованная форма — плоский `steps[]` (ФИНАЛЬНАЯ, 2026-09-17)
> 🔴 **ИСПРАВЛЕНО в конце сессии.** Первый вариант делил поле 2 на `blocks` (шаги 46) и
> `extra_links` (голые id остальных объектов) — **`blocks` выводился первым, и весь сценарий
> превращался в список цифр**: тела «установка температуры», `puts`, `set var1`, пауза 5 суток
> лежали в других секциях файла, порядок терялся. Alex: «почему там блядь if идет первым блоком?!
> где все что до него происходит?!». **Правильно: ОДНО поле `steps` — плоский список в исходном
> порядке, каждое тело инлайн.**
```yaml ```yaml
scenarios: scenarios:
@@ -410,8 +423,15 @@ scenarios:
name: Простой тестовый сценарий name: Простой тестовый сценарий
trigger: {type: schedule} # manual | trigger | schedule | interval trigger: {type: schedule} # manual | trigger | schedule | interval
_raw_trigger_kind: 8 # исходное поле 5 _raw_trigger_kind: 8 # исходное поле 5
blocks: steps: # = поле 2 сценария, в исходном порядке, всё с телами
- id: 8506 # шаг (46) - {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: if:
id: 8496 id: 8496
group: and # and | or | not group: and # and | or | not
@@ -426,10 +446,14 @@ scenarios:
- {id: 8502, script: 'puts "then-text"', args: [0, 0, 0]} - {id: 8502, script: 'puts "then-text"', args: [0, 0, 0]}
else: else:
- {id: 8504, script: 'storeev A "alert"', args: [0, 0, 0]} - {id: 8504, script: 'storeev A "alert"', args: [0, 0, 0]}
extra_links: [8457, 8458, ..., 8507] # прочие ссылки поля 2, в исходном порядке - {id: 8507, unresolved: true} # ссылка на отсутствующий объект
_raw_links: [8457, ..., 8506, 8507] # точный порядок поля 2 (для байт-tочности)
``` ```
**Правило:** `steps[]` = поле 2 **как есть**, порядок значим, каждое тело на месте.
Никаких `blocks`/`extra_links`/`_raw_links`. Секции `delays`/`scenario_steps`/
`scenario_conditions`/`scenario_scripts`/`scenario_raw_objects` остаются как **служебные**
(для round-trip), но сценарий читается целиком в `steps`.
**Маппинг типов на YAML:** **Маппинг типов на YAML:**
| Тип | В YAML | Пример | | Тип | В YAML | Пример |
@@ -457,29 +481,38 @@ scenarios:
### Реализация (код) ### Реализация (код)
**`config-to-yml.py`** — рекурсивные хелперы `dump_step()` / `dump_condition()` / `dump_leaf()` / **`config-to-yml.py`** — рекурсивные хелперы `dump_step()` / `dump_condition()` / `dump_leaf()` /
`dump_action()`; сборщики `scenario_scripts` / `scenario_conditions` / `scenario_raw_objects`; `dump_action()`; `dump_step()` диспетчеризует **по типу** (46→if/then/else, 59→script, 47/48/49→
`_raw_links` для точного порядка поля 2; **fixed-point sweep** операндов (`left`/`right`/`args`) — дерево, 45→wait, 50→trigger_object raw, 11→run_scenario, 5/9/3→raw-тело) — так каждый элемент поля 2
без него теряются скрипты второго порядка (напр. `8459`/`8460` через `8462`); неизвестные типы → рендерится целиком на месте. Сборщики `scenario_scripts` / `scenario_conditions` /
`raw_objects` вместо падения (там же `KNOWN_TYPES += 47,48,50,59`). `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()` / `emit_condition()` / `emit_action()`; поддержка **`yml-to-config.py`** — `emit_step()` принимает любой элемент списка (raw по типу → в свою секцию;
старых форм (`steps`/`when`/`then`) через `_scenario_from_legacy()`; восстановление порядка ссылок `script`/`wait`/`trigger_object`/условие/if-then-else); `emit_condition()` / `emit_action()`;
из `_raw_links`; новые секции `scenario_scripts`/`scenario_conditions`/`scenario_raw_objects`/`raw_objects` поддержка старых форм (`blocks`+`extra_links`, `steps`+`when`/`then`) через `_scenario_from_legacy()`;
новые секции `scenario_scripts`/`scenario_conditions`/`scenario_raw_objects`/`raw_objects`
в `TYPE_ORDER`. в `TYPE_ORDER`.
### 🔴 Три питфолла реализации (round-trip ловил каждый) ### 🔴 Четыре питфолла реализации (round-trip ловил каждый)
| # | Проблема | Причина | Решение | | # | Проблема | Причина | Решение |
|---|---|---|---| |---|---|---|---|
| 1 | Объекты не попадали в вывод | `result_lines` в энкодере — **фильтрованное подмножество**: объект должен быть и в `lines`, и в `TYPE_ORDER` | Добавить секции в `TYPE_ORDER` | | 1 | Объекты не попадали в вывод | `result_lines` в энкодере — **фильтрованное подмножество**: объект должен быть и в `lines`, и в `TYPE_ORDER` | Добавить секции в `TYPE_ORDER` + в `lines` |
| 2 | Операнды терялись | `left`/`right`/`args`**не рёбра дерева**, обход их не видит | Fixed-point sweep: собирать референсы из `raw`-тел, докидывать, повторять | | 2 | Операнды терялись | `left`/`right`/`args`**не рёбра дерева**, обход их не видит | Fixed-point sweep: собирать референсы из `raw`-тел, докидывать, повторять |
| 3 | exit 4 «Duplicate ID» | Секции эмитились дважды (явный цикл + `TYPE_ORDER`) | Убрать явный цикл; типы 5/9/3/0/36 не регистрировать в `raw_objects` (у них свои секции) | | 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`** |
**Обязано сохраниться байт-в-байт (сохранено):** **Обязано сохраниться байт-в-байт (сохранено):**
- порядок объектов в файле (45 идёт **после** 11, но **до** 46) - порядок объектов в файле (45 идёт **после** 11, но **до** 46)
- порядок ссылок поля 2 сценария → `_raw_links` - порядок ссылок поля 2 сценария → теперь сам `steps[]` в исходном порядке
- число полей (7 vs 8) и `_raw_*`-поля (`_raw_field_count`, `_raw_trigger_kind`, `_raw_trigger_params`) — **не удалять** - число полей (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 конфигов чисто ### ✅ Round-trip — 6/6 конфигов чисто
```bash ```bash
+43 -3
View File
@@ -19,7 +19,7 @@ tags:
- homeautomation - homeautomation
title: 🔁 ZONT — сценарная логика и конструктор (11/46/47/48/49/50/59/45) title: 🔁 ZONT — сценарная логика и конструктор (11/46/47/48/49/50/59/45)
type: tech type: tech
updated: '2026-09-17f' updated: '2026-09-17g'
--- ---
# 🔁 ZONT — сценарная логика и конструктор логики # 🔁 ZONT — сценарная логика и конструктор логики
@@ -489,8 +489,8 @@ Alex спросил: «а по всем действиям в "Простой т
|---|---|---| |---|---|---|
| 1 | Парсер типов **47/48/50/59** + рекурсивное дерево условий | ✅ `dump_condition`/`dump_leaf`/`dump_action` | | 1 | Парсер типов **47/48/50/59** + рекурсивное дерево условий | ✅ `dump_condition`/`dump_leaf`/`dump_action` |
| 2 | Поле 5 типа 11 — **тип запуска** (0/1/8/9/10), не `enabled` | ✅ `trigger: {type, days, time, interval_ms}` | | 2 | Поле 5 типа 11 — **тип запуска** (0/1/8/9/10), не `enabled` | ✅ `trigger: {type, days, time, interval_ms}` |
| 3 | Поле 4 шага — принимать непустые списки | ✅ `blocks[].else` | | 3 | Поле 4 шага — принимать непустые списки | ✅ `else``steps[]`) |
| 4 | Ссылки в поле 2 — допускать не-46 типы (45, 11, 3, 5, 9) | ✅ `extra_links` + `_raw_links` | | 4 | Ссылки в поле 2 — допускать не-46 типы (45, 11, 3, 5, 9) | ✅ **плоский `steps[]`** — каждый элемент по порядку, тело инлайн |
| 5 | Round-trip всех 5 конфигов | ✅ **6/6** (боевой + 3 архива + новый локальный + live) | | 5 | Round-trip всех 5 конфигов | ✅ **6/6** (боевой + 3 архива + новый локальный + live) |
**Счётчики нового конфига (для сверки):** `660 #Z`, `25 #S`, байт — 34 907. **Счётчики нового конфига (для сверки):** `660 #Z`, `25 #S`, байт — 34 907.
@@ -502,3 +502,43 @@ Alex спросил: «а по всем действиям в "Простой т
| 45 | 5 | | 48 | 3 | | 45 | 5 | | 48 | 3 |
| 9 | 44 | | 50 | 3 | | 9 | 44 | | 50 | 3 |
| 59 | 28 | | 0/36 | 13/13 | | 59 | 28 | | 0/36 | 13/13 |
### 8.11. ✅ ФИНАЛЬНАЯ YAML-форма сценария — плоский `steps[]` (2026-09-17)
> 🔴 **Урок, который стоит запомнить.** Первая реализация делила поле 2 на `blocks` (шаги 46) +
> `extra_links` (голые id). Round-trip был чистый (байты сходились), но **сценарий читался как
> список цифр**: `blocks` выводился первым, а всё «до него» — 25 элементов «установка температуры,
> `puts`, `set var1`, пауза 5 суток» — лежало в `extra_links` без тел. Alex: «почему там блядь if
> идет первым блоком?! где все что до него происходит?!»
> **Вывод: round-trip ≠ читаемость. Артефакт надо читать глазами до отдачи.**
**Правильная форма** — одно поле `steps`, плоский список = поле 2 сценария **как есть**:
```yaml
scenarios:
- id: 8456
name: Простой тестовый сценарий
trigger: {type: schedule}
_raw_trigger_kind: 8
steps:
- {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]} # + ещё 6 × set var1
- {id: 8489, wait: 432000000} # пауза 5 суток
- id: 8506 # шаг с условием — тоже элемент
if: {id: 8496, group: and, children: [
{id: 8493, op: '<', left: 8492, value: 3},
{id: 8495, op: '>=', left: 8472, right: 8494}]}
then: [{id: 8505, kind: 1, if: {…}, then: [{id: 8502, script: 'puts "then-text"'}], else: [{id: 8504, script: 'storeev A "alert"'}]}]
- {id: 8507, unresolved: true}
```
**Диспетчер `dump_step()`** — по типу элемента:
`46``if/then/else`, `59``script`+`args`, `47/48/49`→дерево условий, `45``wait`, `50``trigger_object` (raw),
`11``run_scenario`, `5/9/3``raw`-тело. **Мелкая деталь:** тела объектов чужих секций (`sms_notifications`,
`actions`) рендерятся как YAML-якоря `*id00N` (`8195`, `9500` в `8456`) — читается хуже, но байт-точно.
**Артефакт:** `zont_config/config_local_2026-09-17_14-16-35.yml` (6110 строк) — в проекте, не в `/tmp`.