From 084253fb29454cc4429b833cdace2690273d0d3e Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Thu, 17 Sep 2026 19:37:19 +0600 Subject: [PATCH] [2026-09-17] eagle: family/how-to/zont-config-compiler.md family/tech/zont-config-object-types.md --- family/how-to/zont-config-compiler.md | 204 +++++++++++++++++++++--- family/tech/zont-config-object-types.md | 13 +- 2 files changed, 188 insertions(+), 29 deletions(-) diff --git a/family/how-to/zont-config-compiler.md b/family/how-to/zont-config-compiler.md index dd7c8e10..b913c7d0 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-17f +updated: 2026-09-17g --- # ⚙️ ZONT Config Compiler — конвертеры `.txt ⇄ .yml` @@ -1470,16 +1470,53 @@ Alex, последнее сообщение сессии: переведены на форму §6a. Проверялось **с файла на диске**, не из `/tmp`. ✅ **Форма закрыта (2026-09-17, финал) — см. §6a.** Сценарий = `id`/`name`/`enabled` + -опциональный `trigger:` + `steps[]`. Шаг 46 = `{id, [flag], if, then[], [else[]]}`. -`type`, `field5`, `kind`, `_f5`, `_kind`, `_else`, `action` — **всё вырезано** (round-trip -подтверждает: `grep -c 'type:\|field5\|kind'` = 0). +опциональный `trigger:` + `steps[]`. Шаг 46 = `{id, [flag], if, then[], [else[]]}` либо +`{id, [flag], action[], [else[]]}`. -✅ **Открытый вопрос ЗАКРЫТ — `trigger:` на уровне сценария.** Alex: «наличием поля trigger: -блядь если ты еблан тупоголовый не можешь догадаться!». Правило: **если `steps` состоит ровно -из одного шага-46 — его условие поднимается в `trigger:`**, сам шаг остаётся в `steps` с `id` -и `then` (запись `#Z9819` существует отдельно). Подтверждено на `9691/9819`, `9628/9756`, -`8547/8550`. Проверено и обратное: `11109` (2 шага) и `8456` (27 шагов) с 46 внутри — **не** -trigger, поле 5 = `0`/`8`. +🔴 **ГЛАВНОЕ ПРАВИЛО ФОРМЫ ШАГА (2026-09-17, самый последний фикс):** + +| У шага есть свой `if`? | Список действий называется | Почему | +|---|---|---| +| **да** — условие внутри шага | **`then`** (+ `else` парой) | условие и его ветки — единая конструкция if/then/else | +| **нет** — условие поднято в `trigger:` | **`action`** | от шага остался только список действий | + +```yaml +# trigger: условие наверху → шаг = action +- id: 9688 + name: 'Автомат.: (н/п) (14/12) ВЫКЛ' + enabled: true + trigger: + id: 9726 + object: 9494 + value: 0 + steps: + - id: 9816 + action: + - id: 9560 + descr: 'Выключить выход 14/12: (н/п)' + target: 146032 + value: 1 + +# if внутри шага → then/else парой +- id: 8863 + if: {id: 8853, group: and, children: [...]} + then: + - id: 8862 + flag: 1 + if: {id: 8858, group: or, children: [...]} + then: [- id: 8859, ...] + else: [- id: 8861, ...] +``` + +Проверка на снимке `18-43-24`: `trigger:` 66 · `action:` 66 · `if:` 2 · `then:` 2 · анкоров 3 +(законные, не сценарии). `type`, `field5`, `kind`, `_f5`, `_kind`, `_else` — **вырезаны** +(`grep -c` = 0). + +✅ **Открытый вопрос ЗАКРЫТ — `trigger:` на уровне сценария.** Alex: «наличием поля trigger:». +Правило: **если `steps` состоит ровно из одного шага-46 — его условие поднимается в +`trigger:`**, а шаг остаётся в `steps` с `id` и `action` (запись `#Z9819` существует +отдельно). Подтверждено на `9691/9819`, `9628/9756`, `8547/8550`. Проверено и обратное: +`11109` (2 шага) и `8456` (27 шагов) с 46 внутри — **не** trigger, поле 5 = `0`/`8`. ✅ **Закоммичено — `1cc010a` на `main`** (2026-09-17, финал). 12 файлов: оба конвертера + 5 снапшотов `zont_config/*.{txt,yml}`. **Не вошли** (остались untracked): 14 скриптов разбора, @@ -1522,7 +1559,118 @@ if not scenario.get('enabled', True): > `18-43-24.yml`, который увидел Alex. Файл перегенерирован на диск; бэкап старого — > `/tmp/backup_18-43-24.yml`. **Гонять конвертер надо в целевой файл, а не в `/tmp`.** -### Осталось не разобрано (остаётся `raw`/`unresolved`) +### Объём коммита — РЕШЁН (2026-09-17) + +Alex: «комит.» → закоммичено `1cc010a` на `main`, 12 файлов: оба конвертера + +5 снапшотов `zont_config/*.{txt,yml}`. **Не вошли** (untracked): 14 скриптов разбора, +`zont_api_docs/`, `zont_local_ui_recon/`. Push не сделан. + +> 🔴 Коммит `1cc010a` сделан **до** финальных правок формы шага (`then`/`action`) — в нём +> сломанные версии конвертеров и снапшот с `type:`. Требует `--amend` либо нового коммита, +> см. §6f. + +--- + +## 6d. 🔴 Питфоллы последнего круга (2026-09-17, после коммита `1cc010a`) + +### 1. Дампер писал в `/tmp` — снапшот на диске остался старым 🔴 + +Все проверки шли как `python3 config-to-yml.py in.txt > /tmp/new.yml`. Файл +`zont_config/config_local_2026-09-17_18-43-24.yml` не перегенерировался — Alex видел там +`type: trigger` (70 шт), хотя из кода `type` уже был вырезан. **Правило: гонять конвертер +в целевой файл, а не в `/tmp`; проверять `exit=` и размер файла.** + +### 2. PyYAML-анкоры `&id061` / `*id061` 🔴 + +Подъём `trigger:` был сделан **той же dict-ссылкой**, что осталась в шаге: + +```python +scenario['trigger'] = flat_steps[0]['if'] # одна и та же ссылка в двух местах! +``` + +PyYAML на один объект в двух местах пишет анкор и алиас — в файле появлялось 69 пар +`&idNNN`/`*idNNN`. Alex: «че это блядь за хуяня?! нормально же блядь все было!». + +**Две последовательные ошибки, обе исправлены:** +1. `deepcopy` — убирает анкор, но оставляет **дубль** условия в файле. +2. `pop('if')` — условие **переносится** в `trigger:` и из шага уходит совсем. ✅ финал. + +Остаются 3 законных анкора — не сценарии: пустой список `[]`, одинаковый `raw`, `sensors`. + +### 3. `if` нельзя вынимать из `dump_step` — ломается подъём `trigger` 🔴 + +Удаление `node['if'] = ...` в `dump_step` сломало **всё**: подъём ищет `'if' in flat_steps[0]`, +не находил — условие терялось и у trigger (`9691`), и у manual (`11827`), и у `8555`. +Снаружи выглядело как «шаг без условия, голый action». + +**Правильно:** `if` **пишется** в `dump_step`, а у trigger-сценария **вынимается** через `pop` +уже после (`scenario['trigger'] = first.pop('if')`). + +### 4. `then` vs `action` — по наличию своего `if` у шага 🔴 + +- шаг **со своим** `if` → список действий = **`then`** (+ `else` парой); +- шаг **без** своего `if` (условие поднято в `trigger:`) → список = **`action`**. + +Alex: «какого хуя там then блядь?!» → затем «ДА БЛЯДЬ! КАК ТЫ БЛЯДЬ ДУМАЕШЬ?! +Then конечно!!!» — это **не противоречие**: он говорил про разные шаги. Реализация в +`dump_step` — ветка по `cond_id`; при подъёме `trigger:` ключ `then` переименовывается +в `action` с сохранением порядка (`id`, `flag`, `action`, `else`). + +### 5. Энкодер: обе ветки `emit_step` должны принимать И `then`, И `action` 🔴 + +После переименования в дампере энкодер терял списки (`#Z8550=46,0,8548,[],[]`). Исправлено +во **всех четырёх** местах: `'then' in X or 'action' in X`, список берётся как +`X.get('then') or X.get('action') or []`. + +### 6. Две ветки `emit_step` для типа 46 — мёртвый код 🪤 + +В `yml-to-config.py` жили **две** ветки для 46: старая (стр. ~490, `if 'if' in step:`) и новая +(стр. ~528). Старая стояла **выше**, перехватывала вызов и читала удалённые +`action`/`_else`/`_kind`. Правка нижней ветки **не давала эффекта**, а round-trip был зелёным, +пока не всплывали 12 потерянных объектов из `then`. +**Правило: после правки проверить, что нужная ветка достижима (порядок `if`-ов сверху вниз), +а не только что она написана.** + +> 📌 **Итог проверки на снимке `18-43-24` (файл с диска):** `661 → 661` объектов, +> `686 → 686` строк, `diff` по отсортированным `#Z` = **0**. `trigger:` 66 · `action:` 66 · +> `if:` 2 · `then:` 2 · анкоров 3. + +--- + +## 6e. Круглые итерации — счёт вырос до 7 + +К §6c добавляются круги этой сессии. Диагноз: **вырезал поле → затащил его обратно под новым +именем**, повторено 7 раз; в двух случаях источником были мои же комментарии, которые я правил +вместо кода. + +| Круг | Что затащил | Реплика Alex | +|---|---|---| +| 6 | `action` вместо `then` **у шага с `if`** | «ты блядь теперь if-then констркукции запорол» | +| 7 | `then` вместо `action` **у шага без `if`** | «какого хуя там then блядь?!» | + +**Урок круга 6–7 (главный):** у одного и того же поля (поле 3 записи 46) **имя зависит от +контекста шага**. Я искал одно «правильное» имя, а их два, и выбор диктуется наличием `if`. +Когда Alex сказал «then конечно!» — это не отменяло предыдущее «какого хуя там then»: +он говорил **про разные шаги**. Слушать условие, а не только имя. + +--- + +## 6f. Состояние репозитория (2026-09-17, самый финал) + +| Что | Состояние | +|---|---| +| Коммит | `1cc010a` на `main` — **содержит сломанные версии** конвертеров + снапшот с `type`/анкорами | +| Рабочее дерево | ✅ исправлено: правило `then`/`action`, `pop('if')` при подъёме, энкодер принимает оба ключа (4 ветки) | +| Снапшот на диске | ✅ перегенерирован, round-trip `diff` = 0 | +| Push | ❌ не сделан | +| Не в коммите | 14 скриптов разбора, `zont_api_docs/`, `zont_local_ui_recon/` | + +🔴 **Коммит требует дописывания:** `git commit --amend` либо отдельный коммит с правкой формы +шага (`then`/`action`) + перегенерированным снапшотом. + +--- + +## 6g. Осталось не разобрано (после этой сессии) ⚠️ **Не разобрано (остаётся `raw`/`unresolved`):** 1. **Тип 50** — маска дней недели: `#Z8548=50,1,0,0,109`, где `109 = 0b1101101` = пн,ср,чт,сб,вс @@ -1532,22 +1680,24 @@ if not scenario.get('enabled', True): пн, вт, чт, пт, сб, вс»). 3. **`unresolved: true`** у `#Z8860`, `#Z8864`, `#Z8601` — этих объектов нет в конфиге. 4. **Старый конфиг `14-16-35`** — судьба не решена (с тестовыми сценариями логики). -5. ~~Старые снапшоты содержат `type:`~~ — `18-43-24.yml` перегенерирован (§6). Старые - `14-16-35.yml` / `16-02-18.yml` всё ещё содержат по 2 вхождения `type:` (сгенерированы до - правки формы, закоммичены как есть). Перегенерировать по команде. +5. **Старые снапшоты `14-16-35.yml` / `16-02-18.yml`** — по 2 вхождения `type:` + (сгенерированы до правки формы, закоммичены как есть). Перегенерировать по команде. -### Объём коммита (ждёт ответа Alex) +--- -| Включить | Не включать (если не скажет иначе) | -|---|---| -| `config-to-yml.py`, `yml-to-config.py` | 14 скриптов разбора (`probe_types.py`, `audit2.py`, `read_scenarios.py`, `dump_new_types.py`, `trace_scenarios.py`, `probe_sched.py`, `verify_answers.py`, `check_ops.py`, `audit_8456.py`, `chk_extra.py`, `why_raw.py`, `fit_temp.py`, `fit_temp2.py` и т.п.) | -| снапшот `zont_config/config_local_2026-09-17_18-43-24.{txt,yml}` | `zont_api_docs/`, старые снапшоты, `zont_local_ui_recon/` | +## 7. Связанные заметки + +- [[family/tech/zont-config-object-types]] — таблица типов объектов +- [[family/tech/zont-scenario-logic-11109]] — разбор сценария 11109 +- [[family/tech/zont-api]] — API ZONT: конфиг через него недоступен +- [[family/how-to/home-automation]] §6 — ZONT в общем контуре --- ## 6a. ✅ АКТУАЛЬНАЯ форма сценария в YAML (ФИНАЛ, 2026-09-17) -**Trigger-сценарий** (`steps` = ровно один шаг 46) — условие поднимается в `trigger:`: +**Trigger-сценарий** (`steps` = ровно один шаг 46) — условие поднимается в `trigger:`, +в шаге остаётся `action` (условия в шаге больше нет): ```yaml - id: 9691 @@ -1559,7 +1709,7 @@ if not scenario.get('enabled', True): value: 1 steps: - id: 9819 - then: + action: # ← 'action', а не 'then': своего 'if' у шага нет - id: 9563 descr: 'Включить выход 14/14: Прихожая (н/п)' target: 146064 @@ -1575,7 +1725,7 @@ if not scenario.get('enabled', True): ``` **Manual-сценарий** (несколько шагов, 46 — обычный шаг внутри) — `trigger:` отсутствует, -условие живёт в шаге как `if:`: +условие живёт в шаге как `if:`, а список действий — как `then:` (парой с `else:`): ```yaml - id: 11109 @@ -1641,7 +1791,8 @@ if not scenario.get('enabled', True): | `steps[].id` | `#Z=46,…` поле 0 (id) | | | `steps[].flag` | `#Z` **поле 1** | пишется только если ≠ 0 (иначе `0`) | | `steps[].if` | `#Z` поле 2 (id условия) → `#Z=49,…` | поле 3 = оператор → `_op` если ≠ 1 | -| `steps[].then[]` | `#Z` поле 3 (список id) | тела инлайн | +| `steps[].then[]` | `#Z` поле 3 (список id) | тела инлайн; **только когда у шага есть свой `if`** | +| `steps[].action[]` | `#Z` поле 3 (список id) | то же поле, но у шага **нет** своего `if` (условие ушло в `trigger:`) | | `steps[].else[]` | `#Z` поле 4 (список id) | пишется только если непусто | ### Сборка поля 5 при обратной конвертации (`yml-to-config.py`) @@ -1669,16 +1820,19 @@ if not scenario.get('enabled', True): | `field5` | «служебный ключ» вместо `_f5` — тот же грех; Alex: «какой нахуй field5» | | `kind`, `_f5`, `_kind`, `_else` | выдуманные/служебные ключи | | `base5` / словарь `{manual:0, schedule:0, trigger:1, interval:2}` | тот же выдуманный маппинг имён типов | -| `action` вместо `then` | `then`/`else` — имена полей 3/4 самой записи 46, это факт, а не выдумка | | `run_scenario: <имя>` | подстановка имени из чужого объекта — у ссылки только `id` | | `target_name`, `target_type`, `raw_value` | дорисовка парсера, в строке конфига их нет | > 📌 **Общий принцип (питфолл 45):** YAML-ключ обязан соответствовать полю строки конфига -> либо выводиться из тела. Подстановка из **другого** объекта запрещена. `then`/`else`/`if`/`flag` +> либо выводиться из тела. Подстановка из **другого** объекта запрещена. `if`/`then`/`else`/`flag` > — это **поля записи 46**, поэтому они разрешены; `type`/`field5` — не поля, поэтому запрещены. > > ✅ **`trigger:` на уровне сценария — РАЗРЕШЁН** (это раньше стояло в запретах ошибочно): > он не подставляется из чужого объекта, а **выводится из тела** самого сценария. +> +> ✅ **`action:` — РАЗРЕШЁН и обязателен** для шага с поднятым `trigger:` (см. §6, правило формы +> шага). Ошибочная запись «`action` запрещён» относилась к другой итерации, где `action` был +> **единственным** id вместо списка и подменял `then` там, где шаг имел свой `if`. --- diff --git a/family/tech/zont-config-object-types.md b/family/tech/zont-config-object-types.md index e193dd61..bb999c1f 100644 --- a/family/tech/zont-config-object-types.md +++ b/family/tech/zont-config-object-types.md @@ -156,11 +156,16 @@ Modbus-регистры (тип 52) в конфиге — **отдельные > ... > ``` > -> `if` ← поле 2 (`cond_id`), `then` ← поле 3 (`[9563]`), `else` ← поле 4, `flag` ← поле 1. -> `then`/`else`/`if`/`flag` — **имена полей записи 46**, поэтому разрешены; `then`/`else` — **пара**. +> `if` ← поле 2 (`cond_id`), поле 3 — список действий, `else` ← поле 4, `flag` ← поле 1. +> +> 🔴 **Имя списка действий зависит от того, есть ли у шага свой `if`:** +> - **есть `if`** → `then:` (+ `else:` парой) — вся конструкция if/then/else в шаге; +> - **`if` поднят в `trigger:`** (у сценария ровно один шаг-46) → `action:`, шаг — чистый список. +> +> `if`/`then`/`else`/`action`/`flag` — **имена полей записи 46** (по контексту), поэтому разрешены. > **`trigger:` на уровне сценария РАЗРЕШЁН** — выводится из тела (ровно один шаг 46), не подставляется. -> `type` / `field5` / `kind` / `_f5` / `action` — **запрещены**. -> Полностью — [[family/how-to/zont-config-compiler]] §6a, правки после коммита — §6d. +> `type` / `field5` / `kind` / `_f5` — **запрещены**. +> Полностью — [[family/how-to/zont-config-compiler]] §6 и §6a. > > ⚠️ **Питфолл (§6d):** условие для `trigger:` вынимается из шага **переносом (`pop`)**, не копией. > `deepcopy` даёт PyYAML-анкоры `&idNNN`/`*idNNN`; убирание `if` из `dump_step` теряет условие у ВСЕХ