diff --git a/family/how-to/zont-config-compiler.md b/family/how-to/zont-config-compiler.md index a927de0c..4c09c6bc 100644 --- a/family/how-to/zont-config-compiler.md +++ b/family/how-to/zont-config-compiler.md @@ -21,7 +21,7 @@ tags: - homeautomation title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml type: how-to -updated: 2026-09-17j +updated: 2026-09-17k --- # ⚙️ ZONT Config Compiler — конвертеры `.txt ⇄ .yml` @@ -146,8 +146,9 @@ updated: 2026-09-17j ### Как это выглядит в YAML -> ✅ **НОВАЯ форма (2026-09-17, §5c).** Все сценарии — единая вложенная структура `blocks[].if/then/else` -> плюс блок `trigger:`. Плоские `when`/`then` и секции `steps`/`delays`/`scenario_conditions` убраны. +> ✅ **НОВАЯ форма (2026-09-17, §5c).** Все сценарии — один плоский список `steps[]` в исходном +> порядке поля 2, каждый элемент с телом на месте; плюс блок `trigger:`. Плоские `when`/`then` и +> дележ на `blocks`/`extra_links` убраны (тот вариант был отвергнут Alex — см. §5c). > **Форма `11109` ниже — устаревшая**, оставлена для истории; актуальная — §5c. **Устаревшая форма (коммит `199f2b1`)** — плоский `when`/`then` для 1-шаговых + `steps` + `extra_links`: @@ -322,6 +323,8 @@ diff A.txt B.txt # пусто = round-trip чистый | 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) — ВСЕ ЗАКРЫТЫ @@ -385,10 +388,13 @@ diff A.txt B.txt # пусто = round-trip чистый ## 5c. 🔄 Переработка структуры YAML сценариев — ✅ СДЕЛАНО 2026-09-17 -> ✅ **СТАТУС (2026-09-17, ночь): парсер и энкодер дописаны, round-trip чистый на 6 конфигах.** -> Семантика типов 47/48/50/59 раскрыта (ответы Alex — [[family/tech/zont-scenario-logic-11109]] §8.7), -> операторы и логика подтверждены фактами конфига. Код реализован. -> **Не закоммичено** — ждёт команды Alex. +> ✅ **СТАТУС (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 вместо текущей разбросанной структуры. с опциональными айдишниками у операторов». @@ -402,7 +408,14 @@ Alex отклонил: **Правило:** в ZONT **нет триггеров** — роль триггера играет само изменение реле, а сценарий читается как вложенные инструкции «если … то …». Структура YAML должна повторять ZONT, а не HA. -### ✅ Реализованная форма +### ✅ Реализованная форма — плоский `steps[]` (ФИНАЛЬНАЯ, 2026-09-17) + +> 🔴 **ИСПРАВЛЕНО в конце сессии.** Первый вариант делил поле 2 на `blocks` (шаги 46) и +> `extra_links` (голые id остальных объектов) — **`blocks` выводился первым, и весь сценарий +> превращался в список цифр**: тела «установка температуры», `puts`, `set var1`, пауза 5 суток +> лежали в других секциях файла, порядок терялся. Alex: «почему там блядь if идет первым блоком?! +> где все что до него происходит?!». **Правильно: ОДНО поле `steps` — плоский список в исходном +> порядке, каждое тело инлайн.** ```yaml scenarios: @@ -410,8 +423,15 @@ scenarios: name: Простой тестовый сценарий trigger: {type: schedule} # manual | trigger | schedule | interval _raw_trigger_kind: 8 # исходное поле 5 - blocks: - - id: 8506 # шаг (46) + 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 @@ -426,10 +446,14 @@ scenarios: - {id: 8502, script: 'puts "then-text"', args: [0, 0, 0]} else: - {id: 8504, script: 'storeev A "alert"', args: [0, 0, 0]} - extra_links: [8457, 8458, ..., 8507] # прочие ссылки поля 2, в исходном порядке - _raw_links: [8457, ..., 8506, 8507] # точный порядок поля 2 (для байт-tочности) + - {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 | Пример | @@ -457,29 +481,38 @@ scenarios: ### Реализация (код) **`config-to-yml.py`** — рекурсивные хелперы `dump_step()` / `dump_condition()` / `dump_leaf()` / -`dump_action()`; сборщики `scenario_scripts` / `scenario_conditions` / `scenario_raw_objects`; -`_raw_links` для точного порядка поля 2; **fixed-point sweep** операндов (`left`/`right`/`args`) — -без него теряются скрипты второго порядка (напр. `8459`/`8460` через `8462`); неизвестные типы → -`raw_objects` вместо падения (там же `KNOWN_TYPES += 47,48,50,59`). +`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()` / `emit_condition()` / `emit_action()`; поддержка -старых форм (`steps`/`when`/`then`) через `_scenario_from_legacy()`; восстановление порядка ссылок -из `_raw_links`; новые секции `scenario_scripts`/`scenario_conditions`/`scenario_raw_objects`/`raw_objects` +**`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 ловил каждый) +### 🔴 Четыре питфолла реализации (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`-тел, докидывать, повторять | -| 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) -- порядок ссылок поля 2 сценария → `_raw_links` +- порядок ссылок поля 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 конфигов чисто ```bash diff --git a/family/tech/zont-scenario-logic-11109.md b/family/tech/zont-scenario-logic-11109.md index a8a5fdf4..4a04d6a4 100644 --- a/family/tech/zont-scenario-logic-11109.md +++ b/family/tech/zont-scenario-logic-11109.md @@ -19,7 +19,7 @@ tags: - homeautomation title: 🔁 ZONT — сценарная логика и конструктор (11/46/47/48/49/50/59/45) type: tech -updated: '2026-09-17f' +updated: '2026-09-17g' --- # 🔁 ZONT — сценарная логика и конструктор логики @@ -489,8 +489,8 @@ Alex спросил: «а по всем действиям в "Простой т |---|---|---| | 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}` | -| 3 | Поле 4 шага — принимать непустые списки | ✅ `blocks[].else` | -| 4 | Ссылки в поле 2 — допускать не-46 типы (45, 11, 3, 5, 9) | ✅ `extra_links` + `_raw_links` | +| 3 | Поле 4 шага — принимать непустые списки | ✅ `else` (в `steps[]`) | +| 4 | Ссылки в поле 2 — допускать не-46 типы (45, 11, 3, 5, 9) | ✅ **плоский `steps[]`** — каждый элемент по порядку, тело инлайн | | 5 | Round-trip всех 5 конфигов | ✅ **6/6** (боевой + 3 архива + новый локальный + live) | **Счётчики нового конфига (для сверки):** `660 #Z`, `25 #S`, байт — 34 907. @@ -502,3 +502,43 @@ Alex спросил: «а по всем действиям в "Простой т | 45 | 5 | | 48 | 3 | | 9 | 44 | | 50 | 3 | | 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`.