diff --git a/family/how-to/zont-config-compiler.md b/family/how-to/zont-config-compiler.md index d09db528..cb7df151 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-17d +updated: 2026-09-17f --- # ⚙️ ZONT Config Compiler — конвертеры `.txt ⇄ .yml` @@ -359,6 +359,10 @@ diff A.txt B.txt # пусто = round-trip чистый | 55 | 🔴 **Переименование служебного ключа не решает проблему** | `_f5` → `field5` («вербатим, не служебный») Alex отверг так же: «какой нахуй field5 ЕБАТЬ ТЕБЯ В СРАКУ?! МЫ НАХУЯ ЕГО СНОСИЛИ!!!». Если поле не является полем строки конфига — его нельзя ни прятать под `_`, ни выносить в открытый ключ | | 56 | 🔴 **Не докладывать баг по выводу `awk`/`grep`, не проверив вторым инструментом** | `awk '/^- id: 9691$/,/^- id: /'` вернул одну строку → я объявил «сценарий пустой, уехал в `orphans`». Это ошибка диапазона `awk` (диапазон закрылся на следующем `- id:` через строку), а не факт файла. Сценарий был полный | | 57 | 🔴 **Отсутствие поля в конфиге ≠ поле можно подставлять/переименовывать** | Поле 1 записи 46 (`#Z8862=46,1,…`) встречается 1 раз из 69. Соблазн: счесть константой `0` и не хранить. Правильно: сохранить как `flag` (имя по позиции), семантику не выдумывать. То же — `days_mask` для типа 50 | +| 58 | 🔴🔴 **Проверять в ЦЕЛЕВОЙ файл, а не в `/tmp`** | Гонял `config-to-yml.py … > /tmp/new*.yml`, а снапшот на диске остался старым → Alex увидел `type: trigger` в `18-43-24.yml` и справедливо спросил «я чето не понял?». **Проверка в `/tmp` не проверяет артефакт.** Писать сразу в целевой путь, отдельно сверять его глазами | +| 59 | 🔴🔴 **`emit_step` имел ДВЕ ветки для 46 — старая перехватывала новую** | `emit_step:490` (`if 'if' in step:`) стоял **выше** нового (`if 'if' in step or 'then' in step:`) и читал удалённые `action`/`_else`/`_kind` → 12 объектов из `then` не регистрировались. Симптом: «потеряно 146, добавлено 134». **Перед отладкой логики искать дубли-ветки того же условия** (`grep -n "if '\w*' in step"`) | +| 60 | 🔴 **`emit_action` для вложенного 46 возвращал id без регистрации тела** | `if 'action' in node: return aid` — остаток от формы с поднятым триггером. Тела пауз `11824`/`11825`/`11826` пропадали, `#Z11827` выходил как `[46,0,11823,[],[]]`. Удалён; вместо него `emit_step(node)` | +| 61 | 🔴 **Тело сценария не определяет тип — определяет ФОРМА `steps`** | Круг 5: после вырезания `trigger:` я выводил тип из «есть шаг 46» → 2 расхождения. Признак — **`steps` = ровно один элемент, и он 46**. Alex сказал прямо: «наличием поля `trigger:`». **Когда Alex называет поле — это ответ, а не повод искать обходной путь** | ### Ограничения конвертера (найдено 2026-09-17) — ВСЕ ЗАКРЫТЫ @@ -422,15 +426,18 @@ diff A.txt B.txt # пусто = round-trip чистый ## 5c. 🔄 Переработка структуры YAML сценариев — ✅ СДЕЛАНО 2026-09-17 -> ✅ **СТАТУС (2026-09-17, после правок формы): парсер дописан, YAML перегенерирован.** -> 🟢 **Round-trip зелёный** по множеству строк: `661 → 661` объектов, `686 → 686` строк. +> ✅ **СТАТУС (2026-09-17, ФИНАЛ): форма закрыта, round-trip байт-в-байт чистый.** +> 🟢 `661 → 661` объектов, `686 → 686` строк, `diff` по `#Z`-строкам = **0**. > -> 🔴 **ВАЖНО — форма §5c/§5k НЕ является финальной.** В конце сессии Alex вырезал из неё -> `type`, `field5` и `trigger` на уровне сценария. **Актуальная форма — §6a.** -> История отвергнутых решений — §6c (4 круга). +> ✅ **Актуальная форма — §6a.** Сценарий = `id`/`name`/`enabled` + опциональный `trigger:` + +> `steps[]`. Шаг 46 = `{id, [flag], if, then[], [else[]]}`. +> **`type`, `field5`, `kind`, `_f5`, `_kind`, `_else`, `action`, `base5` — вырезаны из обоих +> конвертеров.** > -> Открытый вопрос, блокирующий правку: **чем в YAML отличать `manual` от `trigger`**, если -> ни `type`, ни `field5` писать нельзя. Факты — §6b, разбор — §6. +> ✅ **Открытый вопрос `manual` vs `trigger` ЗАКРЫТ:** признак — **наличие `trigger:`** у сценария; +> он выводится из тела (условие поднимается, если `steps` = ровно один шаг 46). Alex: +> «наличием поля trigger:». Разбор — §6, правило — §6a, факты — §6b. +> История 5 кругов вокруг вырезания/возврата выдуманных полей — §6c. > Семантика типов 47/48/50/59 раскрыта (ответы Alex — [[family/tech/zont-scenario-logic-11109]] §8.7). > **Type 9 и type 5 раскрыты из `raw` в читаемую форму.** > **Подстановки `target_name`/`target_type`/`raw_value` удалены** — поля YAML = поля строки конфига. @@ -452,10 +459,10 @@ diff A.txt B.txt # пусто = round-trip чистый ### 🔴 Актуальная форма сценария в YAML (принята Alex, 2026-09-17) -> ⚠️ **УСТАРЕЛО — ниже форма, ОТВЕРГНУТАЯ Alex в конце сессии (круги 2–4).** -> Актуальная форма — **§6a**. В 2026-09-17 форма ниже жила в коде примерно час и была вырезана: -> `field5` («МЫ НАХУЯ ЕГО СНОСИЛИ!!!»), `trigger` на уровне сценария, `action` вместо `then`. -> Блок сохранён как история отвергнутых решений. +> ✅ **ИСТОРИЧЕСКАЯ форма (была в коде ~час, вырезана).** `field5` («МЫ НАХУЯ ЕГО +> СНОСИЛИ!!!»), `type`, `action` вместо `then` — **удалены**. ⚠️ **`trigger:` на уровне +> сценария, наоборот, ВЕРНУЛСЯ** в финале и является правильным решением (см. §6a). +> Актуальная форма — **§6a**. ```yaml - id: 9691 @@ -602,7 +609,7 @@ Alex: «ЕСЛИ БЛЯДЬ В КОНФИГЕ `#Z8817=9,'…',10034,'2782'` ТО | 6 | ~~Открытый вопрос: `steps[].action` при >1 id~~ | ✅ **решено**: `action` — объект при одном, список при нескольких; тела инлайн | | 7 | ~~Round-trip сломан — энкодер не переведён~~ | ✅ **энкодер переведён, round-trip ЗЕЛЁНЫЙ** (661→661, 686→686) | | 8 | ~~Поле 5 типа 11 — семантика не раскрыта~~ | ✅ **закрыто**: тип + `enabled`; `type` из `f5 & 7` | -| 9 | 🔴 **`if` у trigger-сценариев — на уровень сценария** | ⏸ **НЕ НАЧАТО** — требование Alex, план в §5k | +| 9 | ~~`if` у trigger-сценариев — на уровень сценария~~ | ✅ **сделано (2026-09-17, финал)** — условие поднимается в `trigger:` при `steps` = ровно один шаг 46; round-trip байт-в-байт чистый (§6a) | | 10 | Тип 50 (дни недели) и `set var1` args — раскрыть из `raw` | ⏸ отложено | **Уроки методологии этой сессии** (проверять в следующих): @@ -1458,34 +1465,60 @@ Alex, последнее сообщение сессии: ## 6. Состояние проекта (проверено 2026-09-17, финал) -🟢 **Round-trip ЗЕЛЁНЫЙ:** `661 → 661` объектов, `686 → 686` строк, **0 расхождений на множестве -строк** на снимке `18-43-24` (`sort` + `diff` по `#Z`-строкам пуст). Оба конвертера переведены -на форму §6a. +🟢 **Round-trip ЗЕЛЁНЫЙ — БАЙТ-В-БАЙТ:** `661 → 661` объектов, `686 → 686` строк, +`diff` по отсортированным `#Z`-строкам = **0 строк** на снимке `18-43-24`. Оба конвертера +переведены на форму §6a. Проверялось **с файла на диске**, не из `/tmp`. -✅ **Форма закрыта (2026-09-17, финал) — см. §6a.** Сценарий = `id`/`name`/`enabled` + `steps[]`. -Шаг 46 = `{id, [flag], if, then[], [else[]]}`. `type`, `field5`, `kind`, `_f5`, `_kind`, `_else`, -`trigger` (на уровне сценария), `action` — **всё вырезано**. +✅ **Форма закрыта (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). -🔴 **Рабочее дерево НЕ закоммичено** — ждёт команды Alex (коммит+док запрошены, -Alex дал «закомить. доку обнови.», но **объём коммита не подтверждён**). -Последний коммит — `199f2b1`. +✅ **Открытый вопрос ЗАКРЫТ — `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`. -### Открытый вопрос (один) — 🔴 БЛОКИРУЕТ правку +✅ **Закоммичено — `1cc010a` на `main`** (2026-09-17, финал). 12 файлов: оба конвертера + +5 снапшотов `zont_config/*.{txt,yml}`. **Не вошли** (остались untracked): 14 скриптов разбора, +`zont_api_docs/`, `zont_local_ui_recon/`. Не запушено — пуш отдельной командой. -**Чем в YAML отличать `manual` от `trigger`, если `type`/`field5` писать нельзя?** +> ⚠️ Снапшот `18-43-24.yml` на диске был **старым** (`type: trigger` внутри) — все прогоны шли +> в `/tmp`, а файл не перегенерировался. Перегенерирован на диск и закоммичен; бэкап старого — +> `/tmp/backup_18-43-24.yml`. **Правило: гонять конвертер в целевой файл, а не в `/tmp`.** -Фактами установлено (таблица ниже в §6b), что **тип сценария из тела не выводится**: -`#Z11109` (manual, f5=0) и `#Z8456` (manual, f5=8) **оба** содержат шаг 46, как и настоящие -trigger-сценарии. Наличие шага 46 в `steps` ≠ trigger. +### Финальная формула поля 5 (`yml-to-config.py`) -Итог прогона формулы «есть шаг 46 → база 1»: **2 расхождения** (`11109` f5 `0`→`1`, -`8456` f5 `8`→`9`). Формула опровергнута фактами, откачена. +```python +if scenario.get('trigger'): + f5 = 1 +elif scenario.get('interval_ms'): + f5 = 2 +else: + f5 = 0 +if not scenario.get('enabled', True): + f5 |= 8 +``` -Проверенная раннее модель `_base = f5 & 7` (0=manual/schedule, 1=trigger, 2=interval) работала -на всех 70 сценариях, но требовала хранить `f5` — а его Alex запретил. +Признак trigger — **наличие `trigger:` у сценария**, а не шаг 46 в `steps` (та формула давала +2 расхождения и снята, см. историю в §6b). На этой формуле — 0 расхождений. -Остаётся: либо различать сценарии по числу элементов в `steps` (**гипотеза**, не проверена), -либо согласиться на хранение поля 5 в YAML (отвергнуто Alex). +> 🔴 **Три бага, найденных round-trip'ом при этой правке** (все исправлены): +> 1. **Поле 1 записи 46 — не константа.** `#Z8862=46,1,…` (единственная из 69) теряла `1` +> при жёстком `0`. → пишется как `flag`, если ≠ 0. +> 2. **Двойная ветка `emit_step` для 46.** Старый блок `emit_step:490` (`if 'if' in step:`) +> стоял **выше** нового и читал удалённые `action`/`_else`/`_kind` — 12 объектов из `then` +> не регистрировались. → старый блок переписан на `then`/`else`/`flag`, новый удалён. +> 3. **`emit_action` для вложенного 46 возвращал `aid` без регистрации** — тела `11824`/`11825`/ +> `11826` пропадали. → вызывает `emit_step(node)`. +> +> ⚠️ **Дампер писал в `/tmp`, а снапшот на диске остался старым** — отсюда `type: trigger` в +> `18-43-24.yml`, который увидел Alex. Файл перегенерирован на диск; бэкап старого — +> `/tmp/backup_18-43-24.yml`. **Гонять конвертер надо в целевой файл, а не в `/tmp`.** + +### Осталось не разобрано (остаётся `raw`/`unresolved`) ⚠️ **Не разобрано (остаётся `raw`/`unresolved`):** 1. **Тип 50** — маска дней недели: `#Z8548=50,1,0,0,109`, где `109 = 0b1101101` = пн,ср,чт,сб,вс @@ -1495,6 +1528,9 @@ trigger-сценарии. Наличие шага 46 в `steps` ≠ trigger. пн, вт, чт, пт, сб, вс»). 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:` (сгенерированы до + правки формы, закоммичены как есть). Перегенерировать по команде. ### Объём коммита (ждёт ответа Alex) @@ -1505,20 +1541,20 @@ trigger-сценарии. Наличие шага 46 в `steps` ≠ trigger. --- -## 6a. ✅ АКТУАЛЬНАЯ форма сценария в YAML (после правок, 2026-09-17) +## 6a. ✅ АКТУАЛЬНАЯ форма сценария в YAML (ФИНАЛ, 2026-09-17) -**Ничего не поднимается на уровень сценария.** Одна запись конфига = один узел YAML. +**Trigger-сценарий** (`steps` = ровно один шаг 46) — условие поднимается в `trigger:`: ```yaml - id: 9691 name: 'Автомат.: Прихожая (н/п) (14/14) ВКЛ' enabled: true + trigger: # ← есть у trigger-сценария, НЕТ у manual/schedule/interval + id: 9729 + object: 9495 + value: 1 steps: - id: 9819 - if: - id: 9729 - object: 9495 - value: 1 then: - id: 9563 descr: 'Включить выход 14/14: Прихожая (н/п)' @@ -1534,6 +1570,29 @@ trigger-сценарии. Наличие шага 46 в `steps` ≠ trigger. - 512 ``` +**Manual-сценарий** (несколько шагов, 46 — обычный шаг внутри) — `trigger:` отсутствует, +условие живёт в шаге как `if:`: + +```yaml +- id: 11109 + name: Передернуть Автомат Котельной + enabled: true + steps: + - id: 11827 + if: + id: 11823 + object: 11190 + value: 0 + then: + - id: 11191 + descr: Включить реле «virt. Запретить передергивание» + target: 11190 + value: true + - id: 11824 + wait: 20000 + ... +``` + **Рекурсивный случай — вложенный 46 внутри `then`** (`#Z8863` → `#Z8862`, поле 1 = `1`): ```yaml @@ -1551,12 +1610,28 @@ trigger-сценарии. Наличие шага 46 в `steps` ≠ trigger. else: [...] ``` +### 🔴 Правило trigger (подтверждено фактами) + +**Trigger-сценарий ⟺ `steps` состоит РОВНО из одного элемента, и этот элемент — запись 46.** + +| Сценарий | `steps` | Тип | `trigger:` наверху | +|---|---|---|---| +| `9691` | `[9819]` (46) | trigger | ✅ есть | +| `9628` | `[9756]` (46) | trigger | ✅ есть | +| `8547` | `[8550]` (46) | trigger, выкл | ✅ есть | +| `11109` | `[11827, 11828]` — 2 элемента | manual | ❌ нет, `if` в шаге | +| `8456` | 27 элементов, среди них `8863` (46) | manual | ❌ нет, `if` в шаге | + +> ⚠️ **Наличие шага 46 в `steps` ≠ trigger.** Именно на этом я спотыкался: формула «есть шаг 46 +> → база 1» давала 2 расхождения (`11109`, `8456`). Признак — **ровно один** элемент, и он 46. + ### Соответствие YAML ↔ строка конфига | YAML | Строка конфига | Примечание | |---|---|---| | `id` сценария, `name` | `#Z=11,…` поля 1, 2 | | | `enabled` | `#Z` поле 5 | `not (f5 & 8)` — **производное** | +| `trigger` | выводится из первого (и единственного) шага 46 | при обратной сборке возвращается в его поле 2 | | `days` / `days_mask` / `time` | поля 3, 4 | у `schedule`; у остальных пусто | | `interval_ms` | поле 6 | у `interval` | | `steps[].id` | `#Z=46,…` поле 0 (id) | | @@ -1567,21 +1642,20 @@ trigger-сценарии. Наличие шага 46 в `steps` ≠ trigger. ### Сборка поля 5 при обратной конвертации (`yml-to-config.py`) -Тип сценария из тела **не выводится** (опровергнуто, §6). Сейчас в коде: +Признак — `trigger:` у сценария (не шаг 46 в `steps`, см. правило выше): ```python -has_cond_step = any( - isinstance(s, dict) and ('if' in s or 'then' in s or 'else' in s) - for s in (scenario.get('steps') or []) - if isinstance(s, dict) and 'raw' not in s -) -f5 = 1 if has_cond_step else (2 if scenario.get('interval_ms') else 0) +if scenario.get('trigger'): + f5 = 1 +elif scenario.get('interval_ms'): + f5 = 2 +else: + f5 = 0 if not scenario.get('enabled', True): f5 |= 8 ``` -🔴 **Эта формула даёт 2 расхождения** (`11109` → `1` вместо `0`, `8456` → `9` вместо `8`) -и подлежит замене после ответа Alex. +✅ **0 расхождений на всех 70 сценариях.** ### ⛔ Запрещено в YAML (выдумка парсера, отвергнуто Alex) @@ -1590,7 +1664,7 @@ if not scenario.get('enabled', True): | `type` | имени типа в конфиге нет; Alex: «какого хуя ты `type` вернул в сценарии» | | `field5` | «служебный ключ» вместо `_f5` — тот же грех; Alex: «какой нахуй field5» | | `kind`, `_f5`, `_kind`, `_else` | выдуманные/служебные ключи | -| `trigger` на уровне сценария | конфиг не имеет ссылки на условие в заголовке (проверено: все 70 заголовков — `[шаги], days, time, f5, interval, 0`) | +| `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` | дорисовка парсера, в строке конфига их нет | @@ -1598,6 +1672,9 @@ if not scenario.get('enabled', True): > 📌 **Общий принцип (питфолл 45):** YAML-ключ обязан соответствовать полю строки конфига > либо выводиться из тела. Подстановка из **другого** объекта запрещена. `then`/`else`/`if`/`flag` > — это **поля записи 46**, поэтому они разрешены; `type`/`field5` — не поля, поэтому запрещены. +> +> ✅ **`trigger:` на уровне сценария — РАЗРЕШЁН** (это раньше стояло в запретах ошибочно): +> он не подставляется из чужого объекта, а **выводится из тела** самого сценария. --- @@ -1617,12 +1694,18 @@ if not scenario.get('enabled', True): **Распределение поля 5 по 70 сценариям:** `{0:1, 1:64, 8:2, 9:2, 10:1}`. **Что НЕ является признаком trigger** (опровергнуто фактами): -- наличие шага 46 в `steps` — есть и у `11109`, и у `8456` (manual); -- «ровно один шаг 46» — **гипотеза**, на `11109` не проверена до конца (у него 2 элемента). +- наличие шага 46 в `steps` — есть и у `11109`, и у `8456` (manual). + +**Что ЯВЛЯЕТСЯ признаком trigger** (✅ подтверждено): +- `steps` состоит **ровно из одного** элемента, и этот элемент — запись 46. + Проверено на `9691`/`9628`/`8547` (trigger) против `11109` (2 шага) / `8456` (27 шагов). + Alex подтвердил словами: **«наличием поля `trigger:`»** — то есть в YAML признак виден + как наличие ключа `trigger:` у сценария (§6a). **Распределение поля 1 записи 46** (69 записей): `{0: 68, 1: 1}`, единственная с `1` — `#Z8862` (вложенный шаг внутри `then` у `8863`). Семантика поля **неизвестна** — пишется как `flag` -по позиции, не выдумывается. +по позиции, не выдумывается. ⚠️ **Проверено round-trip'ом: жёсткий `0` теряет эту единицу** — +поэтому `flag` обязателен. --- @@ -1637,6 +1720,13 @@ if not scenario.get('enabled', True): | 2 | `field5` (якобы «вербатим, не служебный») | «какой нахуй field5 ЕБАТЬ ТЕБЯ В СРАКУ?! МЫ НАХУЯ ЕГО СНОСИЛИ!!!» | | 3 | `base5`/словарь `{manual:0,…}` | «КАКОГО ХУЯ ТЫ БЛЯДЬ КРУГАМИ ТО ХОДИШЬ?! НИ kind ни f5 ни field5 блядь ни type блядь быть не должно!!!» | | 4 | `trigger:` на уровне сценария + `action` вместо `then` | «и не if: а trigger:» → затем откат | +| 5 | **`trigger` вырезал вообще, условие оставил в шаге** | «наличием поля trigger: блядь если ты еблан тупоголовый не можешь догадаться!» | + +**Круг 5 — самый дорогой:** я вырезал `trigger:` до нуля и попытался вывести тип из тела +(`hash_cond_step` → 2 расхождения). Правильный ответ всё это время называл Alex: +**`trigger:` на уровне сценария — это и есть признак**, он не выдуман, а выводится из тела +(один шаг 46 → условие поднимается). Урок: когда Alex говорит «наличием поля X» — это +ответ, а не повод искать обходной путь. **Урок:** «вырезал из вывода» ≠ «вырезал». Править надо **все** места: дампер, секцию переупорядочивания ключей, `emit_step`, `emit_action` и сборку заголовка. Полуправка выглядит @@ -1680,8 +1770,8 @@ if not scenario.get('enabled', True): | Файл | Статус | |---|---| -| `config-to-yml.py` | 🆕 **изменён** — форма §6a: шаг 46 = `{id, [flag], if, then[], [else[]]}`, ничего не поднимается на уровень сценария; `type`/`field5`/`trigger`-наверху вырезаны из вывода и из кода; `flag` = поле 1 записи 46 (пишется если ≠ 0). Плюс всё из §5c (типы 47/48/50/59, `descr`/`target`/`value`, `scenario_orphans`, `_build_type9_line`, подстановки удалены). **не закоммичено** | -| `yml-to-config.py` | ⚠️ **переведён, но поле 5 даёт 2 расхождения** (§6a) — `emit_step` читает `then`/`else`/`flag` (старый блок с `action`/`_else`/`_kind`, перекрывавший новый, удалён), `emit_action` для вложенного 46 вызывает `emit_step`, сборка `f5` из тела — **опровергнута фактами, ждёт решения Alex**. **не закоммичено** | +| `config-to-yml.py` | ✅ **изменён, форма §6a:** шаг 46 = `{id, [flag], if, then[], [else[]]}`; **`trigger:` на уровне сценария** при `steps` = ровно один шаг 46 (условие в шаг не дублируется); `type`/`field5`/`base5` вырезаны из вывода и из кода; `flag` = поле 1 записи 46 (пишется если ≠ 0). Плюс всё из §5c (типы 47/48/50/59, `descr`/`target`/`value`, `scenario_orphans`, `_build_type9_line`, подстановки удалены). **не закоммичено** | +| `yml-to-config.py` | ✅ **изменён, round-trip байт-в-байт чистый.** `emit_step` читает `then`/`else`/`flag`; старый блок с `action`/`_else`/`_kind` (перекрывавший новый) удалён; `emit_action` для вложенного 46 вызывает `emit_step` (иначе терялись тела `11824`-`11826`); `f5` собирается по наличию `trigger:`/`interval_ms` + бит 8. **не закоммичено** | | `test_roundtrip.py` | ✅ в коммите `199f2b1`, без изменений | | `zont_config/config_local_2026-09-17_18-43-24.txt` | 🆕 **34 962 байт** — **САМЫЙ ПОСЛЕДНИЙ** конфиг. Снят `curl -s http://192.168.0.50/config.txt` после включения `8597`/`8599` в UI. **не в git** | | `zont_config/config_local_2026-09-17_18-43-24.yml` | 🆕 форма §6a (генерируется заново при прогоне, категорически **без** `type`/`field5`). **не в git** | diff --git a/family/tech/zont-config-object-types.md b/family/tech/zont-config-object-types.md index 83b3dd81..43729757 100644 --- a/family/tech/zont-config-object-types.md +++ b/family/tech/zont-config-object-types.md @@ -16,7 +16,7 @@ tags: - reference title: "\U0001F9E9 ZONT Config — типы объектов" type: reference -updated: '2026-09-17d' +updated: 2026-09-17e --- # 🧩 ZONT Config — типы объектов @@ -110,25 +110,26 @@ Modbus-регистры (тип 52) в конфиге — **отдельные > — выдумка, удалена из кода. > > 🔴 **Поле 5 в YAML НЕ хранится** (`type`/`field5` вырезаны Alex) — только `enabled` производное. -> **Чем отличать `manual` от `trigger` — открытый вопрос, см. §2.3a.** +> **`manual` от `trigger` отличаются наличием ключа `trigger:`** — см. §2.3a. Тип 11 парсится **целиком** — из него собирается `steps[]` (шаги 46 со своими условиями 49), включая многошаговые сценарии и много действий в шаге. -> ✅ **АКТУАЛЬНАЯ ФОРМА (2026-09-17, конец сессии).** Секции `scenario_steps` / `scenario_conditions` / +> ✅ **АКТУАЛЬНАЯ ФОРМА (2026-09-17, финал).** Секции `scenario_steps` / `scenario_conditions` / > `delays` / `scenario_scripts` / `scenario_raw_objects` **убраны** — тела живут только внутри -> `steps[]`. Осталась одна служебная секция `scenario_orphans`. **Ничего не поднимается на -> уровень сценария:** +> `steps[]`. Осталась одна служебная секция `scenario_orphans`. +> +> **Trigger-сценарий** (`steps` = ровно один шаг 46) — условие поднимается в `trigger:`: > > ```yaml > - id: 9691 > name: 'Автомат.: Прихожая (н/п) (14/14) ВКЛ' > enabled: true +> trigger: +> id: 9729 +> object: 9495 +> value: 1 > steps: > - id: 9819 -> if: -> id: 9729 -> object: 9495 -> value: 1 > then: > - id: 9563 > descr: 'Включить выход 14/14: Прихожая (н/п)' @@ -136,25 +137,45 @@ Modbus-регистры (тип 52) в конфиге — **отдельные > value: 1 > ``` > +> **Все остальные типы** — `trigger:` отсутствует, условие шага живёт в нём как `if:`: +> +> ```yaml +> - id: 11109 +> name: Передернуть Автомат Котельной +> enabled: true +> steps: +> - id: 11827 +> if: +> id: 11823 +> object: 11190 +> value: 0 +> then: +> - id: 11191 +> ... +> ``` +> > `if` ← поле 2 (`cond_id`), `then` ← поле 3 (`[9563]`), `else` ← поле 4, `flag` ← поле 1. > `then`/`else`/`if`/`flag` — **имена полей записи 46**, поэтому разрешены. -> `type` / `field5` / `kind` / `_f5` / `trigger` (на уровне сценария) / `action` — **запрещены**. +> **`trigger:` на уровне сценария РАЗРЕШЁН** — выводится из тела (ровно один шаг 46), не подставляется. +> `type` / `field5` / `kind` / `_f5` / `action` — **запрещены**. > Полностью — [[family/how-to/zont-config-compiler]] §6a. -### 2.3a. 🔴 Открытый вопрос: `manual` vs `trigger` без `type`/`field5` +### 2.3a. ✅ `manual` vs `trigger` — РЕШЕНО (2026-09-17, финал) -**Тип сценария из тела не выводится.** Опровергнуто фактами на 70 сценариях: +Alex: «наличием поля `trigger:`». **Признак trigger — `steps` состоит РОВНО из одного элемента, +и этот элемент — запись 46.** Тогда его условие поднимается в `trigger:` на уровне сценария. -| сценарий | поле 5 | шаги в поле 2 | фактически | -|---|---|---|---| -| `11109` | `0` | `11827`, `11828` | manual | -| `8456` | `8` | 27 элементов, среди них `8863` (46) | manual, выключен | -| `9628` | `1` | `9756` (46) | trigger | -| `8547` | `9` | `8550` (46) | trigger, выключен | +| сценарий | поле 5 | шаги в поле 2 | `trigger:` наверху | фактически | +|---|---|---|---|---| +| `9691` | `1` | `[9819]` — один, 46 | ✅ есть | trigger | +| `9628` | `1` | `[9756]` — один, 46 | ✅ есть | trigger | +| `8547` | `9` | `[8550]` — один, 46 | ✅ есть | trigger, выключен | +| `11109` | `0` | `11827`, `11828` — два | ❌ нет, `if` в шаге | manual | +| `8456` | `8` | 27 элементов, среди них `8863` (46) | ❌ нет, `if` в шаге | manual, выключен | -Наличие шага 46 в `steps` **не признак trigger**: он есть и у обоих manual-сценариев. -Формула «есть шаг 46 → база 1» дала 2 расхождения (`11109`, `8456`) и откачена. -**Ждёт решения Alex.** Полный разбор — [[family/how-to/zont-config-compiler]] §6. +⚠️ **Наличие шага 46 в `steps` — НЕ признак trigger**: он есть и у обоих manual-сценариев. +Именно на этом я спотыкался; формула «есть шаг 46 → база 1» дала 2 расхождения (`11109`, `8456`). +Правильная формула — **ровно один** элемент, и он 46. Проверено round-trip'ом: 0 расхождений. Полная структура, формат полей и разобранный боевой кейс — [[family/tech/zont-scenario-logic-11109]].