[2026-09-17] eagle: personal/projects/zont-config-compiler.md
This commit is contained in:
@@ -3,7 +3,7 @@ title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
|
||||
namespace: personal
|
||||
type: how-to
|
||||
created: '2026-09-17'
|
||||
updated: '2026-09-17m'
|
||||
updated: '2026-09-17k30'
|
||||
tags:
|
||||
- personal
|
||||
- zont
|
||||
@@ -223,6 +223,20 @@ python3 test_roundtrip.py zont_config/config_X.txt
|
||||
|
||||
> ✅ **СТАТУС: закрыта, round-trip байт-в-байт чистый.** `661 → 661` объектов, `686 → 686` строк, `diff` = 0.
|
||||
|
||||
### 5.0. 🔴 Pickle — мини-язык ZONT
|
||||
|
||||
Тела объектов типа 59 — это **Pickle**, стековый мини-язык контроллера. Alex: «это Команда Pickle "3"».
|
||||
|
||||
| Тело в конфиге | YAML | Примечание |
|
||||
|---|---|---|
|
||||
| `'set varname'` | `set_var: {…}` | см. §10.9, четыре формы цели |
|
||||
| `'storeev I "<текст>"'` | `storeenv: {level: info, text: …}` | `I`→info, `A`→alert, `W`→warning, `E`→error |
|
||||
| `'puts "<текст>"'` | `log: <текст>` | плоский ключ, без `args` |
|
||||
| `'3'` (голый литерал) | `pickle: 3` | **числом**, не строкой (подтверждено Alex) |
|
||||
| `'objcmd <id> "<fmt>"'` | ⏳ `descr` + `args` | форма вызова **не согласована** — см. §13 |
|
||||
| `'expr "%0 + %1"'` | `expr: '%0 + %1'` + `args: [операнды]` | операнды — id объектов |
|
||||
| `'objstate <id> 0 0'` | `type: objstate` + `object` | |
|
||||
|
||||
### 5.1. Trigger-сценарий
|
||||
|
||||
`steps` состоит **ровно из одного шага типа 46** — условие **переносится** в `trigger:` наверх,
|
||||
@@ -575,7 +589,7 @@ iconv -f cp1251 -t utf-8 out.txt | tr -d '\r' | grep '^#Z<id>=<тип>'
|
||||
| 2 | 🔴 Пустой `raw` у типов 0/36 → **молчаливые дефолты** | Не трогать `raw*`-поля |
|
||||
| 3 | 🔴 Правка `#S…` в YAML бессмысленна — они `raw_payload` | Системные настройки — только через UI |
|
||||
| 4 | YAML на входе — строго **UTF-8** | Править YAML в UTF-8, `.txt` не пересохранять |
|
||||
| 5 | 🔴 **Гонять конвертер в ЦЕЛЕВОЙ файл, а не в `/tmp`** | Проверка в `/tmp` не проверяет артефакт. Alex видел `type: trigger` в файле, который я не перегенерировал |
|
||||
| 5 | 🔴 **Гонять конвертер в ЦЕЛЕВОЙ файл, а не в `/tmp`** | Проверка в `/tmp` не проверяет артефакт. Alex видел `type: trigger` в файле, который я не перегенерировал. 🔴 **Повторено 2026-09-17 (круг 30):** правки `storeenv`/`log`/`flag` ушли в круг через `/tmp`, а `zont_config/config_local_2026-09-17_21-13-18.yml` на диске остался старым → Alex дважды видел `descr: puts "Отладка"` и был прав, что «нихуя не поменялось». **Правило: после каждой правки — `python3 config-to-yml.py <src>.txt > <src>.yml` в тот же каталог и `grep` по нему, а не по выводу в `/tmp`** |
|
||||
| 6 | 🔴 **Не отдавать артефакт, не прочитав его самому** | Round-trip «байты сходятся» ≠ «читаемо». Форма `blocks` проходила round-trip, но `8456` превращался в список цифр — выявил Alex |
|
||||
| 7 | 🔴 **Артефакты — в проект, не в `/tmp`** | «качай доки в папку в проекте а не в темп» |
|
||||
| 8 | 🔴 **Бэкапы кода — только git. Не создавать копии «на всякий»** | «какой нахуй бэкап скриптов — там в гите все» (2026-09-17: «какой нах бэкап. у нас гит»). Коммит-чекпойнт ДО правки = бэкап; откат = `git checkout <SHA>`. Копии файлов в проект **не плодить**. Исключение — **доки Obsidian перед УДАЛЕНИЕМ** (MCP-удаление необратимо) |
|
||||
@@ -812,6 +826,12 @@ git commit -m "Scenario YAML shape: bare action ids, no anchors"
|
||||
> Пункты 1, 2, 7 закрыты **аналитически** (семантика установлена и подтверждена на данных) **и**
|
||||
> реализованы: `set_var` (4 формы), `param`/`event` (49), `op`/`time`/`days_mask` (50), тела 59.
|
||||
> Все три круга чистые (§10.9). Код, потерянный при откате (§10.8), написан заново.
|
||||
>
|
||||
> 🆕 **Поздний вечер 2026-09-17 — круг 30 (§10.11):** п. 3 **версия подтверждена в новом снапшоте**
|
||||
> (`21-13-18`): `#Z9986`/`#Z9990`/`#Z8601` — те же ссылки без объекта (было `8860`/`8864`/`8601` в `18-43-24`,
|
||||
> номера переехали при перенумерации). Alex: «**завершить сценарий**». Ключ **не заведён** — имени в UI нет.
|
||||
> Добавлен разбор `storeev`→`storeenv` и `puts`→`log` (был потерян вместе с надстройкой §10.8).
|
||||
> Новый незакрытый остаток — **тела `Pickle`** (§10.11, `#Z9933=59,'3',0,0,0`, «Команда Pickle "3"»).
|
||||
|
||||
⚠️ **Остаётся `raw` / `unresolved`:**
|
||||
|
||||
@@ -2012,8 +2032,117 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег
|
||||
> ```
|
||||
> `type: var` (круг 29, вытеснявшая `set_var`) — **0 вхождений**. `set_var:` — 53.
|
||||
|
||||
> ✅ **`zont_config/config_local_2026-09-17_21-13-18.yml` ПЕРЕГЕНЕРИРОВАН** в целевой файл
|
||||
> (не в `/tmp` — питфолл 34/5). Круг `18-43-24` → `661 → 661` ✅ (не затронут).
|
||||
> 🆕 **`zont_config/config_local_2026-09-17_21-13-18.yml` ПЕРЕГЕНЕРИРОВАН на диск 2026-09-17 22:18**
|
||||
> (83514 байт) — до этого правки гонялись только в `/tmp`, и Alex видел старый файл: питфолл, повторённый дважды.
|
||||
> Круг с файла на диске: **774 → 774, потеряно 0, лишнее 0** ✅.
|
||||
|
||||
---
|
||||
|
||||
## 10.11. 🔵 Круг 30 — `storeenv` / `log` / `flag` (2026-09-17, вечер)
|
||||
|
||||
**Контекст.** Alex видел в YAML `descr: puts "Отладка"` + `args: [0,0,0]` и `descr: storeev I "…"` — тела
|
||||
59, которые в прошлой сессии **уже были развёрнуты**, но потерялись вместе с незакоммиченной работой
|
||||
(`git checkout b75c51f --` → питфолл 55). В коммите `07c076f` восстановили только `set_var`/`param`/`expr`.
|
||||
Принятую форму подняли **из истории сессий** (Zulip/SQLite), а не изобрели заново.
|
||||
|
||||
**ПРИНЯТАЯ форма (из истории — не выдумывать заново):**
|
||||
|
||||
| Строка конфига | YAML |
|
||||
|---|---|
|
||||
| `#Z8598=59,'storeev I "инфо событие в пн, ср…"',0,0,0` | `storeenv: {level: info, text: 'инфо событие в пн, ср…'}` |
|
||||
| `#Z9934=59,'storeev I "z"',0,0,0` | `storeenv: {level: info, text: z}` |
|
||||
| `#Z9935=59,'storeev A "asdf"',0,0,0` | `storeenv: {level: alert, text: asdf}` |
|
||||
| `#Z9933=59,'3',0,0,0` | `pickle: 3` — **команда Pickle**, литерал числом (Alex: «это Команда Pickle "3"», затем «pickle: 3 блядь») |
|
||||
| `#Z9932=59,'puts "Отладка"',0,0,0` | `log: Отладка` |
|
||||
|
||||
- 🔴 **в конфиге `storeev`, ключ YAML — `storeenv`** (подтверждено историей: «`storeev` или `storeenv`? → в конфиге `storeev`; ключ YAML — `storeenv`»).
|
||||
- `level` — словами: `I`→`info`, `A`→`alert`, `W`→`warning`, `E`→`error` (обратный маппинг в энкодере).
|
||||
- `log` — **плоский** ключ, без `args`.
|
||||
- Отдельного типа «alarm» нет: сигналка идёт телом `storeev`.
|
||||
|
||||
**Правки — ✅ ЗАКОММИЧЕНО в `375d01a` («Expand mini-script bodies: storeenv / log / pickle, trigger 50»):**
|
||||
|
||||
| Файл | Что |
|
||||
|---|---|
|
||||
| `config-to-yml.py` | в ветке `t == 59` разбор тел `storeev <lvl> "<text>"` → `storeenv`, `puts "<text>"` → `log`, голый литерал (`'3'`) → `pickle: <число>` |
|
||||
| `config-to-yml.py` | хвост `set_var`-числа: `[0,1]` → `flag: 1` вместо `args: [0,1]` (значимые пишутся, нули нет; остаток — `args`) |
|
||||
| `config-to-yml.py` | тип 50 в `dump_step` — раскрытие через `_set_var_target_body` (`days_mask`/`time_condition`) вместо голого `raw` |
|
||||
| `config-to-yml.py` | `_is_body_inline` — `'target'` считается ссылкой, как `'id'`; убран мёртвый `sc.get('scenario') or sc` |
|
||||
| `yml-to-config.py` | приём `storeenv`/`log`/`pickle` в **обеих** ветках (`emit_action` + `emit_step`) + в выводе орфанов; `_script_value_row` отдаёт `None` для `param`/`time_condition`/`days_mask`; `kind`/`flag` собираются в поля 3/4 |
|
||||
|
||||
**ПИТФОЛЛ 57 🔴 — две ветки-близнеца в энкодере.** `emit_action` и `emit_step` — **разные** функции с
|
||||
одинаковыми проверками. Правка, добавленная только в `emit_action`, дала
|
||||
`unrecognised node {'id': 9932, 'log': 'Отладка'}` на **всех 8 конфигах** (exit ≠ 0). Формы тел надо
|
||||
добавлять в **обе** ветки; проверять прогоном **всех** конфигов, не одного.
|
||||
|
||||
**ПИТФОЛЛ 58 🔴 — «маленькая правка» `_is_body_inline` даёт потери.** Замена обхода на
|
||||
`obj_id in v` (матч голых чисел в любом списке) выбила из вывода **6 объектов** на `21-13-18`
|
||||
(`9146`, `9928`, `9930`, `9965`, `9985`, `9987` — все операнды/цели). Симптом: `774 → 768`.
|
||||
Откат к строгому варианту (`'id'` + `'target'`, без матча чисел) вернул `774 → 774`.
|
||||
🔴 Правка «по смыслу похоже» ломает сборку молча — **мерить счётчиком до и после**.
|
||||
|
||||
**ПИТФОЛЛ 59 🔴 — конвертер писал в `/tmp`, Alex смотрел файл на диске.** Дважды за сессию:
|
||||
«я до сих пор вижу `descr: puts "Отладка"`» — при том что прогон уже давал `log: Отладка`.
|
||||
🔴 **Правки гонять ТОЛЬКО в ЦЕЛЕВОЙ файл** (`> zont_config/<имя>.yml`), проверку круга — по файлу
|
||||
**с диска**, не по свежему прогону. Повтор питфолла 5 — он же записан в §7.1.
|
||||
|
||||
**ПИТФОЛЛ 60 — `.get('scenario')` как «страховка».** В `_is_body_inline` стояло
|
||||
`sc.get('scenario') or sc`, ключа `scenario` в словаре нет → выражение всегда вырождалось в `sc`.
|
||||
Работало случайно. 🔴 Accessor к несуществующему ключу в горячем пути = мёртвый код, который
|
||||
маскирует себя; писать прямо `sc`.
|
||||
|
||||
**Итог круга 30 (все 8 конфигов, сравнение `sort` + `set`):**
|
||||
|
||||
| Снимок | Строк | Потеряно | Лишнее |
|
||||
|---|---|---|---|
|
||||
| `12-12-28` | 623 → 623 | 0 | 0 |
|
||||
| `14-16-35` | 685 → 685 | 0 | 0 |
|
||||
| `16-02-18` · `16-13-28` · `17-45-00` · `18-43-24` · `19-53-21` | 686 → 686 | 0 | 0 |
|
||||
| `21-13-18` | **774 → 774** | **0** | **0** |
|
||||
|
||||
`descr: puts` в `21-13-18.yml` — **0 вхождений**; `descr: storeev` — 0.
|
||||
|
||||
**Контроль по файлу на диске (после `375d01a`):**
|
||||
|
||||
```yaml
|
||||
- id: 9932
|
||||
log: Отладка
|
||||
- id: 9933
|
||||
pickle: 3
|
||||
- id: 8598
|
||||
storeenv:
|
||||
level: info
|
||||
text: инфо событие в пн, ср, чт, пт, сб
|
||||
- id: 9964
|
||||
set_var: {name: varname, value: 42, flag: 1}
|
||||
```
|
||||
|
||||
Круг с **файла на диске**: `774 → 774`, потеряно 0, лишнее 0.
|
||||
|
||||
**Осталось неизменным (не баги — нет формы от Alex):**
|
||||
|
||||
1. **`Pickle`-тела — частично.** Литерал решён: `#Z9933=59,'3',0,0,0` → `pickle: 3` ✅.
|
||||
Но `objcmd` **не раскрыт**: `#Z9925=59,'objcmd 8700 "1 %0"',14.5,0,1`, `#Z9929`, `#Z9931`, `#Z9948`
|
||||
сейчас `descr` + `args`. 🔴 **`objcmd` из этой правки исключён намеренно** — я добавил только
|
||||
ветку литерала, чтобы не выдумывать форму вызова. Alex дал форму **только для литерала** («pickle: 3»).
|
||||
Нужна форма для вызова с форматом (`objcmd <id> "<fmt>"`) — тогда добавить.
|
||||
2. **Тип 50 в `dump_step`** — `#Z8548=50,1,0,0,109` остаётся `raw: [50,1,0,0,109]`. Раскрытие
|
||||
(`days: [mon,wed,fri,sat]`) написано в `_set_var_target_body` и в код внесено, но на `8548`
|
||||
**не срабатывает** — путь `trigger:` идёт через другой вызов (§5.1), до `dump_step` объект 50
|
||||
не доходит. **Не откачено — не срабатывает.** Требует отдельной правки пути `trigger:`.
|
||||
3. **`unresolved: true`** у `9986`, `9990`, `8601` — объектов нет в конфиге 21-13-18 (в `18-43-24` на их месте
|
||||
были `8859`/`8861`; тела `puts "then-text"` / `storeev A "alert"` есть, а самих объектов нет).
|
||||
4. **`scenario_orphans`: 27 → 21** — часть «орфанов» на самом деле разделяемые операнды, они раскрыты в
|
||||
`args` шага, но строку в конце файла всё ещё получают. Полное удаление орфан-секции требует правки энкодера.
|
||||
|
||||
> 🔴 **`unresolved: true` ≠ баг парсера.** Для ссылок на шаги без объекта (напр. «завершить сценарий»,
|
||||
> тип 11 — `9986`) своей строки в конфиге нет, поэтому тело писать нечего. Признак: id есть, `#Z<id>=` — нет.
|
||||
|
||||
### 10.11.1. Питфолл «`descr + args`» — законная форма, НЕ баг
|
||||
|
||||
`descr` + `args: [поле2, поле3, поле4]` — это **raw-фоллбэк** для тел 59, которых нет в разборе
|
||||
(`puts`, `storeev`, `objcmd`, литералы). `args: [0,0,0]` в нём — **буквально нули из строки конфига**,
|
||||
а не потеря. Полный фоллбэк на `raw` **хуже**: круг перестаёт сходиться (`#Z9964` `42,0,1` → `42,0,0`, питфолл 54).
|
||||
|
||||
---
|
||||
|
||||
@@ -2021,8 +2150,8 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег
|
||||
|
||||
| Файл | Статус |
|
||||
|---|---|
|
||||
| `config-to-yml.py` | ✅ форма §5 (`trigger:` подъём через `pop('if')`, шаг = `{id, [flag], action}` или `{id, [flag], if, then, [else]}`) **плюс** развёртывание тел (§10.9): `_script_value`, `_set_var_target_body`, `_target_set_name`, `PARAM_CODES`/`COND_EVENTS`/`_OP_NAMES`, разворот орфанов-59 · ✅ **закоммичено** (`07c076f`) |
|
||||
| `yml-to-config.py` | ✅ `emit_step` читает `then`/`action`/`else`/`flag`; `f5` из `trigger:`/`interval_ms` + бит 8 **плюс** сборка тел (§10.9): `_parse_script_raw`, `_script_value_row`, `_set_var_target_row`, `_set_var_body`, `_body_type`/`_collect_ids`, `_PARAM_TO_CODE`/`_EVENT_TO_CODE`/`_OP_TO_CODE`/`_SEC_TYPE` · ✅ **расхождение `#Z9964` ЗАКРЫТО** (полный `raw` 59 выводится как есть, питфолл 54) · ✅ **закоммичено** (`07c076f`) |
|
||||
| `config-to-yml.py` | ✅ форма §5 (`trigger:` подъём через `pop('if')`, шаг = `{id, [flag], action}` или `{id, [flag], if, then, [else]}`) **плюс** развёртывание тел (§10.9): `_script_value`, `_set_var_target_body`, `_target_set_name`, `PARAM_CODES`/`COND_EVENTS`/`_OP_NAMES`, разворот орфанов-59 · ✅ закоммичено (`07c076f`) · 🆕 **круг 30 (§10.11):** разбор `storeev`→`storeenv`, `puts`→`log`, хвост `[0,1]`→`flag: 1`, `_is_body_inline` учитывает `'target'` · ⚠️ **не закоммичено** |
|
||||
| `yml-to-config.py` | ✅ `emit_step` читает `then`/`action`/`else`/`flag`; `f5` из `trigger:`/`interval_ms` + бит 8 **плюс** сборка тел (§10.9): `_parse_script_raw`, `_script_value_row`, `_set_var_target_row`, `_set_var_body`, `_body_type`/`_collect_ids`, `_PARAM_TO_CODE`/`_EVENT_TO_CODE`/`_OP_TO_CODE`/`_SEC_TYPE` · ✅ расхождение `#Z9964` ЗАКРЫТО (питфолл 54) · ✅ закоммичено (`07c076f`) · 🆕 **круг 30 (§10.11):** приём `storeenv`/`log` в **обеих** ветках (`emit_action` + `emit_step` — питфолл 57), `_script_value_row` отдаёт `None` для 49/50 · ⚠️ **не закоммичено** |
|
||||
| `test_roundtrip.py` | ✅ без изменений (в коммите `199f2b1`) · ✅ **оба круга зелёные после восстановления** |
|
||||
| `zont_config/config_local_2026-09-17_21-13-18.{txt,yml}` | 🆕 **актуальный рабочий снимок** (774 строки, 749 `#Z`) — «Простой тестовый сценарий» `#Z8456` содержит шаги `9925…9990`, `Values test` `#Z9324` не изменился. Круг ✅ `774 → 774`, различий **0** |
|
||||
| `zont_config/config_local_2026-09-17_18-43-24.{txt,yml}` | ↩️ **ВОССТАНОВЛЕН 2026-09-17** из `b75c51f` после ошибочной чистки. Форма `trigger:` + `action:`. Круг ✅ строк `686 → 686`, **потеряно 0, лишнее 0** (661 — это счёт `#Z`, 686 — все строки) |
|
||||
@@ -2030,12 +2159,15 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег
|
||||
| `zont_config/config_0FA7C33CC89F_…_12-12-28.txt` + 4 исторических `.txt` | ↩️ **ВОССТАНОВЛЕНЫ** из `b75c51f` (см. §10.8) |
|
||||
| `zont_config/archive/` | `-2`/`-3`/`-4` — закоммичены (`7ae0e32`) |
|
||||
|
||||
> 📌 **Состояние на 2026-09-17, конец сессии.** HEAD = `07c076f`, не запушен.
|
||||
> Рабочая копия **закоммичена** — развёртывание тел (§10.9) в истории.
|
||||
> 📌 **Состояние на 2026-09-17, конец сессии.** HEAD = **`375d01a`** («Expand mini-script bodies:
|
||||
> storeenv / log / pickle, trigger 50»), не запушен.
|
||||
> 🔴 **Изменённые файлы: `config-to-yml.py`, `yml-to-config.py` — незакоммиченного НЕТ.**
|
||||
> Развёртывание тел мини-скриптов и условий (`set_var`, `param`, `event`, `op`/`time`/`days_mask`,
|
||||
> `expr`/`objstate`/`const`) — **в коде, работает**, все три круга чистые, дефект `#Z9964` закрыт.
|
||||
> Круги: `21-13-18` → **774 → 774** · `18-43-24` → **686 → 686** · `19-53-21` → **686 → 686**
|
||||
> (потеряно 0, лишнее 0; сравнение по **содержимому**, `sort` + `comm`).
|
||||
> ✅ **После `375d01a` проверены ВСЕ 8 конфигов в `zont_config/`:** `623→623`, `685→685`, `686→686` ×5,
|
||||
> `774→774` — потеряно 0, лишнее 0. `descr: puts` в `21-13-18.yml` — 0 вхождений, `pickle: 3` на месте.
|
||||
> Расходится только **порядок строк** — унаследованное, не регрессия (§10.10).
|
||||
|
||||
> ✅ **`19-53-21.{txt,yml}` закоммичены** (`07c076f`, круг `686 → 686`). В корне 13
|
||||
@@ -2066,3 +2198,19 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег
|
||||
- [[family/how-to/home-automation]] §6 — ZONT в общем контуре, Modbus slave ID и регистры
|
||||
- [[family/how-to/ha-automations]] — автоматизации HA
|
||||
- [[family/how-to/gitea-config]] — Gitea: креды, создание репо, питфоллы
|
||||
|
||||
---
|
||||
|
||||
## 13. Открытые вопросы к Alex (нужна форма, не догадки)
|
||||
|
||||
> 🔴 Имя YAML-ключа и имена сущностей **не угадываются** (§5.7). Пока ответа нет — тела остаются
|
||||
> в законном raw-фоллбэке `descr` + `args` и это **не баг**.
|
||||
|
||||
| # | Вопрос | Где | Почему ждёт |
|
||||
|---|---|---|---|
|
||||
| 1 | ✅ **ЗАКРЫТ** — имя ключа для Pickle-литерала: `pickle: 3` (числом). Внесено в код | `9933` | — |
|
||||
| 2 | форма для **Pickle-вызова**: `objcmd <id> "<fmt>"` — ключ `pickle: 'objcmd 8700 "1 %0"'` (строкой) или своё? | `9925`, `9929`, `9931`, `9948` | форма дана только для литерала |
|
||||
| 3 | раскрывать ли **тип 50** прямо в шаге (`#Z8548` → `days: [mon,wed,fri,sat]`)? | `8548` | правка в коде есть, но путь `trigger:` её минует |
|
||||
| 4 | «завершить сценарий» (тип 11 без объекта) — оставить `unresolved: true` или дать имя (`end`)? | `9986`, `9990`, `8601` | в конфиге объекта нет |
|
||||
| 5 | удалять ли секцию `scenario_orphans` (21 запись — разделяемые операнды)? | низ YAML | требует правки энкодера, риск `#Z9964` |
|
||||
| 6 | ✅ **ЗАКРЫТ** — круг 30 закоммичен (`375d01a`). Push — отдельной командой | оба конвертера | — |
|
||||
|
||||
Reference in New Issue
Block a user