[2026-09-17] eagle: family/how-to/zont-config-compiler.md family/tech/zont-config-object-types.md

This commit is contained in:
Alexey Martemyanov
2026-09-17 19:37:19 +06:00
parent 4e63dc7bbe
commit 084253fb29
2 changed files with 188 additions and 29 deletions
+179 -25
View File
@@ -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<sid>=46,…` поле 0 (id) | |
| `steps[].flag` | `#Z<sid>` **поле 1** | пишется только если ≠ 0 (иначе `0`) |
| `steps[].if` | `#Z<sid>` поле 2 (id условия) → `#Z<cond>=49,…` | поле 3 = оператор → `_op` если ≠ 1 |
| `steps[].then[]` | `#Z<sid>` поле 3 (список id) | тела инлайн |
| `steps[].then[]` | `#Z<sid>` поле 3 (список id) | тела инлайн; **только когда у шага есть свой `if`** |
| `steps[].action[]` | `#Z<sid>` поле 3 (список id) | то же поле, но у шага **нет** своего `if` (условие ушло в `trigger:`) |
| `steps[].else[]` | `#Z<sid>` поле 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`.
---
+9 -4
View File
@@ -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` теряет условие у ВСЕХ