[2026-09-17] eagle: family/how-to/zont-config-compiler.md family/tech/zont-scenario-logic-11109.md
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|||||||
Reference in New Issue
Block a user