[2026-09-17] eagle: personal/projects/zont-config-compiler.md

This commit is contained in:
Alexey Martemyanov
2026-09-17 22:30:21 +06:00
parent 88ff373606
commit 9de141c903
+146 -8
View File
@@ -3,7 +3,7 @@ title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
namespace: personal namespace: personal
type: how-to type: how-to
created: '2026-09-17' created: '2026-09-17'
updated: '2026-09-17k30' updated: '2026-09-17k31'
tags: tags:
- personal - personal
- zont - zont
@@ -20,6 +20,8 @@ aliases:
- ZONT типы объектов - ZONT типы объектов
- ZONT object types - ZONT object types
- zont-scenario-logic-11109 - zont-scenario-logic-11109
- ZONT pickle
- ZONT pickle_value
related: related:
- '[[family/tech/zont-api]]' - '[[family/tech/zont-api]]'
- '[[family/how-to/home-automation]]' - '[[family/how-to/home-automation]]'
@@ -1657,7 +1659,7 @@ name, tgt, …]`, и в `emit_action`/`emit_step` условие `'var' in ('obj
|---|---|---|---|---| |---|---|---|---|---|
| 1 | запись **49/50** | `_set_var_value_body(t)` ≠ None | `set_var: {id, name, type: param\|time_condition\|days_mask, …тело}` | `#Z9961=59,'set var1',9960,0,0` | | 1 | запись **49/50** | `_set_var_value_body(t)` ≠ None | `set_var: {id, name, type: param\|time_condition\|days_mask, …тело}` | `#Z9961=59,'set var1',9960,0,0` |
| 2 | **число** | target **отсутствует** в `Z_dict` | `set_var: {name, value: 42, args: [0,1]}` | `#Z9964=59,'set varname',42,0,1` | | 2 | **число** | target **отсутствует** в `Z_dict` | `set_var: {name, value: 42, args: [0,1]}` | `#Z9964=59,'set varname',42,0,1` |
| 3 | другой скрипт **59** | target есть, `entry[0] == 59` | `set_var: {name, target: <id>, type: const\|expr\|objstate, …}` | `#Z9963=59,'set varname',9962,0,0` | | 3 | другой скрипт **59** | target есть, `entry[0] == 59` | `set_var: {name, target: <id>, type: pickle_value\|expr\|objstate, …}` | `#Z9963=59,'set varname',9962,0,0` |
| 4 | объект иного типа | target есть, не 49/50/59 | `descr` + `args` (**без** `set_var`) | `#Z9886=59,'set var1',9885,0,0` | | 4 | объект иного типа | target есть, не 49/50/59 | `descr` + `args` (**без** `set_var`) | `#Z9886=59,'set var1',9885,0,0` |
Живые примеры снимка `21-13-18`: Живые примеры снимка `21-13-18`:
@@ -2086,6 +2088,14 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег
🔴 **Правки гонять ТОЛЬКО в ЦЕЛЕВОЙ файл** (`> zont_config/<имя>.yml`), проверку круга — по файлу 🔴 **Правки гонять ТОЛЬКО в ЦЕЛЕВОЙ файл** (`> zont_config/<имя>.yml`), проверку круга — по файлу
**с диска**, не по свежему прогону. Повтор питфолла 5 — он же записан в §7.1. **с диска**, не по свежему прогону. Повтор питфолла 5 — он же записан в §7.1.
**ПИТФОЛЛ 61 🔴 — `fmt % op` на формате с `%0`/`%1`.** `'%0 %s %1' % op` падает:
`ValueError: unsupported format character '%' (0x25)`. Формат expr содержит свои `%`,
поэтому собирать конкатенацией: `'%0 ' + op + ' %1'`.
**ПИТФОЛЛ 62 🔴 — `f[2:]` вместо `f[2:4]` для операндов `expr`.** В `operands` уезжало
поле 4 третьим элементом (`- 0` в списке). Операндов **ровно два** — поля 2 и 3; поле 4
несёт признак «второй операнд — литерал» (`0` = объект, `2` = число) и в операнды не идёт.
**ПИТФОЛЛ 60 — `.get('scenario')` как «страховка».** В `_is_body_inline` стояло **ПИТФОЛЛ 60 — `.get('scenario')` как «страховка».** В `_is_body_inline` стояло
`sc.get('scenario') or sc`, ключа `scenario` в словаре нет → выражение всегда вырождалось в `sc`. `sc.get('scenario') or sc`, ключа `scenario` в словаре нет → выражение всегда вырождалось в `sc`.
Работало случайно. 🔴 Accessor к несуществующему ключу в горячем пути = мёртвый код, который Работало случайно. 🔴 Accessor к несуществующему ключу в горячем пути = мёртвый код, который
@@ -2141,19 +2151,23 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег
### 10.11.1. Питфолл «`descr + args`» — законная форма, НЕ баг ### 10.11.1. Питфолл «`descr + args`» — законная форма, НЕ баг
`descr` + `args: [поле2, поле3, поле4]` — это **raw-фоллбэк** для тел 59, которых нет в разборе `descr` + `args: [поле2, поле3, поле4]` — это **raw-фоллбэк** для тел 59, которых нет в разборе
(`puts`, `storeev`, `objcmd`, литералы). `args: [0,0,0]` в нём — **буквально нули из строки конфига**, (`objcmd`, прочие незнакомые тела). `args: [0,0,0]` в нём — **буквально нули из строки конфига**,
а не потеря. Полный фоллбэк на `raw` **хуже**: круг перестаёт сходиться (`#Z9964` `42,0,1``42,0,0`, питфолл 54). а не потеря. Полный фоллбэк на `raw` **хуже**: круг перестаёт сходиться (`#Z9964` `42,0,1``42,0,0`, питфолл 54).
> ✅ **Круг 31 (§14) сократил список:** `puts` → `log`, `storeev` → `storeenv`, литералы → `pickle`.
> Остался только **`objcmd`** (вопрос 2, §13) — формы вызова Alex не давал.
--- ---
## 11. Файлы проекта ## 11. Файлы проекта
| Файл | Статус | | Файл | Статус |
|---|---| |---|---|
| `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'` · ⚠️ **не закоммичено** | | `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`, `pickle: <число>`, `_is_body_inline` учитывает `'target'` — закоммичено (`375d01a`) · 🆕 **круг 31 (§14): `const`→`pickle_value`, операнды `expr` раскрываются телами + `op:`, хелпер `_operand_body` + `_seen_operands`** · ⚠️ **НЕ закоммичено** |
| `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 · ⚠️ **не закоммичено** | | `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`/`pickle` в **обеих** ветках (`emit_action` + `emit_step` — питфолл 57) — закоммичено (`375d01a`) · 🆕 **круг 31 (§14): `const`→`pickle_value` в 6 местах** · ⚠️ **НЕ закоммичено**; **сборка `op`+`operands` → строка 59 ещё НЕ написана** |
| `test_roundtrip.py` | ✅ без изменений (в коммите `199f2b1`) · ✅ **оба круга зелёные после восстановления** | | `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_21-13-18.{txt,yml}` | 🆕 **актуальный рабочий снимок** (774 строки, 749 `#Z`) — «Простой тестовый сценарий» `#Z8456` содержит шаги `9925…9990`, `Values test` `#Z9324` не изменился. Круг ✅ `774 → 774`, различий **0** |
| `zont_config/config_local_2026-09-17_22-26-26.{txt,yml}` | 🆕 **свежий снимок с прибора** (2026-09-17 22:26, 757 `#Z`, 38048 байт → YAML 84759 байт). Alex добавил тестовые объекты: пять операторов expr (`+`, `-`, `*`, `/`, `mod``10077…10085`), `pickle_value` (`10067`/`10068`). ⚠️ круг красный — энкодер не собирает `op`+`operands` (§14.5) |
| `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 — все строки) | | `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 — все строки) |
| `zont_config/config_local_2026-09-17_19-53-21.{txt,yml}` | ✅ **ВОССТАНОВЛЕНЫ и ЗАКОММИЧЕНЫ** (`07c076f`) — были в индексе как `AD` (удалены из индекса, файлов на диске нет); вернул `git checkout-index -f -- …`, круг `686 → 686`, потеряно 0 | | `zont_config/config_local_2026-09-17_19-53-21.{txt,yml}` | ✅ **ВОССТАНОВЛЕНЫ и ЗАКОММИЧЕНЫ** (`07c076f`) — были в индексе как `AD` (удалены из индекса, файлов на диске нет); вернул `git checkout-index -f -- …`, круг `686 → 686`, потеряно 0 |
| `zont_config/config_0FA7C33CC89F_…_12-12-28.txt` + 4 исторических `.txt` | ↩️ **ВОССТАНОВЛЕНЫ** из `b75c51f` (см. §10.8) | | `zont_config/config_0FA7C33CC89F_…_12-12-28.txt` + 4 исторических `.txt` | ↩️ **ВОССТАНОВЛЕНЫ** из `b75c51f` (см. §10.8) |
@@ -2161,9 +2175,13 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег
> 📌 **Состояние на 2026-09-17, конец сессии.** HEAD = **`375d01a`** («Expand mini-script bodies: > 📌 **Состояние на 2026-09-17, конец сессии.** HEAD = **`375d01a`** («Expand mini-script bodies:
> storeenv / log / pickle, trigger 50»), не запушен. > storeenv / log / pickle, trigger 50»), не запушен.
> 🔴 **Изменённые файлы: `config-to-yml.py`, `yml-to-config.py` — незакоммиченного НЕТ.** > 🔴 **Круг 31 (§14) НЕ закоммичен:** `config-to-yml.py` + `yml-to-config.py` изменены
> Развёртывание тел мини-скриптов и условий (`set_var`, `param`, `event`, `op`/`time`/`days_mask`, > (`const` → `pickle_value`, раскрытие операндов `expr` через `op:` + `operands`).
> `expr`/`objstate`/`const`) — **в коде, работает**, все три круга чистые, дефект `#Z9964` закрыт. > ⚠️ **Энкодер ещё не собирает `op` + `operands` обратно** — круг на свежем `22-26-26` красный,
> на 8 старых конфигах не перепроверялся. Закоммитить **после** этой правки и зелёного круга.
> ✅ До круга 31 состояние было: развёртывание тел мини-скриптов и условий (`set_var`, `param`,
> `event`, `op`/`time`/`days_mask`, `expr`/`objstate`/`pickle_value`) — в коде, работает,
> все три круга чистые, дефект `#Z9964` закрыт.
> Круги: `21-13-18` → **774 → 774** · `18-43-24` → **686 → 686** · `19-53-21` → **686 → 686** > Круги: `21-13-18` → **774 → 774** · `18-43-24` → **686 → 686** · `19-53-21` → **686 → 686**
> (потеряно 0, лишнее 0; сравнение по **содержимому**, `sort` + `comm`). > (потеряно 0, лишнее 0; сравнение по **содержимому**, `sort` + `comm`).
> ✅ **После `375d01a` проверены ВСЕ 8 конфигов в `zont_config/`:** `623→623`, `685→685`, `686→686` ×5, > ✅ **После `375d01a` проверены ВСЕ 8 конфигов в `zont_config/`:** `623→623`, `685→685`, `686→686` ×5,
@@ -2214,3 +2232,123 @@ comm -13 <(sort orig) <(sort back) # → пусто (ничего лишнег
| 4 | «завершить сценарий» (тип 11 без объекта) — оставить `unresolved: true` или дать имя (`end`)? | `9986`, `9990`, `8601` | в конфиге объекта нет | | 4 | «завершить сценарий» (тип 11 без объекта) — оставить `unresolved: true` или дать имя (`end`)? | `9986`, `9990`, `8601` | в конфиге объекта нет |
| 5 | удалять ли секцию `scenario_orphans` (21 запись — разделяемые операнды)? | низ YAML | требует правки энкодера, риск `#Z9964` | | 5 | удалять ли секцию `scenario_orphans` (21 запись — разделяемые операнды)? | низ YAML | требует правки энкодера, риск `#Z9964` |
| 6 | ✅ **ЗАКРЫТ** — круг 30 закоммичен (`375d01a`). Push — отдельной командой | оба конвертера | — | | 6 | ✅ **ЗАКРЫТ** — круг 30 закоммичен (`375d01a`). Push — отдельной командой | оба конвертера | — |
| 7 | ✅ **ЗАКРЫТ**`const`**`pickle_value`** (круг 31, §10.12) | `10068` | — |
| 8 | ✅ **ЗАКРЫТ** — операнды `expr` раскрываются телами + `op:` отдельным ключом (круг 31, §10.12) | `10077…10085` | — |
| 9 | ⛔ **ОТВЕРГНУТО** — имя `flag` для поля 4 записи-числа. Alex: «я кажется просил нахуй снести ебаный флаг!» | `10069` | форма не дана; см. §10.12 |
| 10 | что значит **поле 4 = `2`** у expr-записи (`0` = второй операнд объект, `2` = литерал)? своего имени нет — в YAML не выводится | `10077…10085` | энкодер из-за этого их пока не собирает |
---
## 14. Круг 31 — `pickle_value` + операнды `expr` (2026-09-17, поздний вечер)
**Снимок.** Alex добавил в UI новые тестовые объекты → снят **свежий конфиг прямо с прибора**:
```bash
curl -s --max-time 8 http://192.168.0.50/config.txt -o /tmp/live.txt
# → 757 объектов, 38048 байт → сохранён как zont_config/config_local_2026-09-17_22-26-26.txt
```
> 🔴 Питфолл «я добавил в UI» — **первое действие `curl`**, не `grep` по старому снимку (§10.5).
### 14.1. ✅ `const` → `pickle_value` (Alex: «это не const а pickle_value»)
| Строка конфига | YAML было | YAML стало |
|---|---|---|
| `#Z10067=59,'2 ;#p',0,0,0` | `type: const` + `value: 2` | `type: pickle_value` + `value: 2` |
Правка — **13 замен** в обоих конвертерах: декодер (`_script_value`), энкодер
(`_script_value_row`, `_parse_script_raw`, `emit_action`, `emit_step`, ветка орфанов) + докстроки.
`type: const` в YAML — **0 вхождений**.
### 14.2. ✅ Операнды `expr` раскрываются телами, `op:` — отдельным ключом
Alex: «`left: 9146` — орфаны блядь развернуты должны быть!» и «у нас стандартный шаблон `op: ">"`».
**Оператор лежит в самом теле** `expr "<fmt>"` как `%0 <op> %1`; отдельного кода нет —
извлекается из формата. Проверено на **всех 8** expr-объектах живого конфига, операторы:
`+`, `-`, `*`, `/`, `mod` (Alex добавил их по порядку на проверку).
**Виды операндов (все три в конфиге):**
| Вид | Пример | Что |
|---|---|---|
| объект 59 (`var`) | `...,8472,10074,0` | `#Z8472=59,'set var1',0,0,0` |
| объект 49 (`param`) | `...,10030,10031,0` | `#Z10030=49,9864,0,4` |
| объект 59 (`objstate`/`pickle_value`) | `...,10070,10071,0` | `objstate 9838 0 0` + `5 ;#p` |
| **литерал** (объекта нет) | `...,9146,-1,2` · `...,9146,1,2` | число прямо в поле 3 |
**Принятая форма:**
```yaml
- id: 10080 # #Z10080=59,'set varname',10079,0,0
set_var:
name: varname
target: 10079
type: expr
op: '-'
operands:
- type: var # операнд РАЗВЁРНУТ телом, не голый id
name: varname
value: 0
tail: [0, 0]
- -1 # литерал остался числом
```
```yaml
- id: 10032 # #Z10032=59,'expr "%0 + %1"',10030,10031,0
type: expr
op: +
operands:
- {type: param, object: 9864, param: input_value, id: 10030}
- {type: param, object: 8254, param: input_value, id: 10031}
```
**Реализация:** хелпер `_operand_body(oid)` в декодере — разворачивает операнд-объект
(59 → `_script_value`, 49/50 → `_set_var_target_body`), защита от циклов через
`_seen_operands`. Операнды — **ровно поля 2 и 3**; поле 4 в операнды не идёт.
**ПИТФОЛЛ 61 — `fmt` нельзя подставлять в `%`-формат.** `'%0 %s %1' % op` падает с
`ValueError: unsupported format character '%'`. Собирать конкатенацией: `'%0 ' + op + ' %1'`.
**ПИТФОЛЛ 62 — третье поле едет в операнды.** Первая версия брала `f[2:]` → в `operands`
попадал хвостовой `0` (поле 4) третьим элементом. Операнды = `f[2:4]`.
### 14.3. ⛔ `flag` — ОТВЕРГНУТ, форма для поля 4 записи-числа не дана
`#Z10069=59,'set varname',42,0,1` — поле 4 = `1`. История имён для этого хвоста:
| Вариант | Итог |
|---|---|
| `args: [0, 1]` (коммит `07c076f`) | ⛔ Alex: «тут args убирай» |
| `kind: 0` + `flag: 1` (коммит `375d01a`) | ⛔ Alex: «блядь я кажется просил нахуй снести ебаный флаг!» |
| `tail: [0, 1]` / `raw_tail` / `_raw_field4` | ❓ предложено, ответа нет |
> 🔴 **Физика:** без хвоста строка соберётся `42,0,0` — **поле 4 теряется**, круг красный
> (это ровно дефект, ловившийся весь вечер, питфолл 54). Поэтому вариант «просто не писать»
> без `_raw_field4` невозможен.
### 14.4. ✅ `descr + args` = законная форма (подтверждено)
`descr: '3'` + `args: [0,0,0]` у `9933` — это **raw-фоллбэк** для тела, которого нет в разборе;
`args` = буквально поля 2/3/4 строки конфига, не потеря. `9933` закрыт через `pickle: 3`
(§10.11), а `objcmd` (`9925`/`9929`/`9931`/`9948`) остаётся `descr`+`args` — формы вызова нет (вопрос 2).
### 14.5. Состояние после круга 31
| Проверка | Результат |
|---|---|
| `type: const` в YAML | **0** (стало `type: pickle_value`) |
| `op:` у expr-объектов | `+`, `-`, `*`, `/`, `mod` — все пять |
| операнды `expr` | раскрыты телами (`type: var` / `type: param` / `type: pickle_value`) |
| `descr: puts` | **0 вхождений** |
**⚠️ Энкодер ещё не собирает `op` + `operands` обратно в строку 59** — правка запланирована
(`_script_value_row`: `op` + `operands``[59, 'expr "%0 <op> %1"', id1, id2, f4]`).
**До этой правки круг на `22-26-26` красный.** Круг на 8 старых конфигах после
`const → pickle_value` и раскрытия операндов — прогнать вместе с энкодером.
> 📌 **Свежие файлы на диске:** `zont_config/config_local_2026-09-17_22-26-26.{txt,yml}`
> (757 объектов, YAML — 84759 байт). Старый рабочий снимок `21-13-18` — сохранить как эталон
> круга, пока `22-26-26` не закрыт.
---