From d5f6e1476a30432eae00d47824c55332635e50be Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Thu, 17 Sep 2026 23:21:13 +0600 Subject: [PATCH] [2026-09-17] eagle: personal/projects/zont-config-compiler.md --- personal/projects/zont-config-compiler.md | 45 +++++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/personal/projects/zont-config-compiler.md b/personal/projects/zont-config-compiler.md index c4a6ea40..83289196 100644 --- a/personal/projects/zont-config-compiler.md +++ b/personal/projects/zont-config-compiler.md @@ -3,7 +3,7 @@ title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml namespace: personal type: how-to created: '2026-09-17' -updated: '2026-09-17k40' +updated: '2026-09-17k42' tags: - personal - zont @@ -41,6 +41,12 @@ aliases: - ZONT args выдуманный ключ - ZONT круг 39 - ZONT круг 40 + - ZONT круг 41 + - ZONT круг 42 + - ZONT шаг 5 хвост полей + - ZONT params выдуманный ключ + - ZONT value поле 10 действия + - ZONT запись типа 5 related: - '[[family/tech/zont-api]]' - '[[family/how-to/home-automation]]' @@ -171,6 +177,29 @@ python3 test_roundtrip.py zont_config/config_X.txt # 6. Залить /tmp/zont_new.txt в контроллер (см. §9) ``` +### 🔴 Круг по ВСЕМ конфигам — без аргумента гоняется только свежий + +`test_roundtrip.py` без аргумента берёт **только самый свежий** `.txt` из `zont_config/`. +Зелёный результат «по умолчанию» ≠ зелёный по всем снапшотам. Полный прогон: + +```bash +cd /Users/admin/Automation/HA-ZONT-Modbus +pass=0; fail=0 +for f in zont_config/config_local_2026-09-17_*.txt; do + out=$(python3 test_roundtrip.py "$f" 2>&1) + if echo "$out" | grep -q "ЧИСТЫЙ"; then + pass=$((pass+1)); echo "OK $(basename $f)" + else + fail=$((fail+1)); echo "FAIL $(basename $f)" + echo "$out" | grep -E "❌|расхожд|потеря|лишн" | head -5 + fi +done +echo "=== pass=$pass fail=$fail" +``` + +Правило: **любая правка конвертера — прогон по всем снапшотам, а не по свежему.** Проверка +одного файла маскирует регрессию на остальных (см. питфолл 72, §18.2). + ### 🔴 Главное правило: `raw` не трогать Часть полей декодирована (адрес, интервал, регистры, сценарии), часть лежит как `raw` / `raw_params` / @@ -451,6 +480,8 @@ enabled = not (f5 & 8) | ⛔ **`action: ` и вообще ключ `action`** | **ОТМЕНЁН** (круг 39, `8f2edd4`). При поднятом условии шага в `steps` нет вообще: id шага едет в `trigger.step_id`, `steps` = тела действий. См. §5.3, §21.3 | | ⛔ **`args: [0, 1]` у `set_var` с числовым полем 2** | поле 5 записи 59 — **производное** («поле 2 = литерал, а не id»), энкодер ставит сам. Alex: «да блядь! опять?!» (§21.5) | | ⛔ **раскрывать поля 4/5 записи 59 в YAML** | у `expr` поле 4 = `2` и у `set` поле 5 = `1` — **одно и то же производное поле** («операнд/значение — литерал»). В YAML не пишется, восстановимо по составу (§21.5) | +| ⛔ **`params:` у шага-действия (запись типа 5)** | хвост `0,0,[],0,0,0` (поля 4..9) — дефолты, а поле 10 = `512`/`256` — **код действия**, он и есть `value`. Alex: «тут просто выполнить действие по id». Шаг = `descr` + `target` + `value`, всё (§22, `9f3c1ab`) | +| ⛔ **`value: 1` как «значение действия»** | `1` — поле 3 (флаг формы), у всех 100+ действий одинаков. Настоящий код — в поле 10: `256` = выкл, `512` = вкл (§22) | ✅ **РАЗРЕШЕНО:** `trigger:` (выводится из тела — один шаг 46) с ключом **`step_id`** (id шага 46) и **телами действий прямо в `steps`**, `if`/`then`/`else`/`flag` — **реальные поля записи 46**, @@ -722,6 +753,9 @@ iconv -f cp1251 -t utf-8 out.txt | tr -d '\r' | grep '^#Z=<тип>' | 28r | 🔴 **Операнды условия 47 не раскрывались → выглядели орфанами** | `dump_leaf` писал голый `left: 10088`, хотя `#Z10088=59,'objstate 9838 0 0'` в конфиге **есть**. Alex: «почему left: 10088 — в орфане?!». Правило: **голый id в YAML = красный флаг**, любой операнд раскрывается телом (`_operand_body` / `_operand_id`) | | 28s | 🔴 **`unresolved: true` — шум, а не информация** | Для объекта, которого нет в конфиге (битая ссылка прибора: `10099`, `8601`, `10103`), достаточно **голого `id`** — энкодер соберёт `[10098,10099]` байт-в-байт и без отметки. Проверено на копии до правки. Alex: «але блядь!!!» трижды. Снято в 4 местах: `dump_step`, `dump_condition`, `dump_leaf`, список шагов | | 28t | 🔴 **`value` у `var` — не значение, а поле 2** | `#Z8472=59,'set var1',0,0,0` → поле 2 = `0`. Раскрытие его как `value: 0` путалось с «значением переменной». Попытка переименовать в `from` — тоже отвергнута («я сказал снести, а не менять названия»). **Итог: поле 2 у `var` в YAML не раскрывается**, энкодер подставляет `0` | +| 28u | 🔴 **Хвост полей записи типа 5 сваливался в `params`, реальный код действия уезжал в конец списка** | `#Z9500=5,'Вкл. Рад. ванная 2эт',145728,1,0,0,[],0,0,0,512`: парсер внутри шага брал `value = step[3]` (поле 3 = **флаг**, всегда `1`) и сваливал поля 4..10 в `params`. Настоящий код действия — **поле 10** (`512`). Симптом в артефакте: `value: 1` + `params: [0,0,[],0,0,0,512]`. Alex: «это че за хуйня у нас зарегрессилась? тут просто выполнить действие по id». Фикс: `value = step[10]`, поля 3..9 не выводятся (дефолтные, восстанавливаются энкодером) | +| 28v | 🔴 **Энкодер типа 5 собирал 4 поля вместо 11** | Симметрично 28u: `fields = [5, descr, target, value]` + `params` — и при непустом `params` строка удлинялась сверх 11 полей. Правильная сборка: `[5, descr, target, 1, 0, 0, [], 0, 0, 0, value]`. Хвост `params` допустим **только** как продолжение (поля 11+), иначе `require(len(v) == 11)` в декодере упадёт | +| 28w | 🔴 **`params` как ключ для «ничего не значащего хвоста»** | Тот же класс, что `args`/`flag`/`kind`/`tail` (§28o): имя родилось из «осталось что-то после разобранных полей». Признак ошибки — **большинство дефолты**, значение только в одном-двух местах. Прежде чем давать ключ остатку — проверить, не является ли он **производным** от уже разобранных полей (ср. 28x). Легитимный `params` — общий список именованных параметров Modbus-устройств (`raw_params`, §1), не хвост записи | ### 7.3. Проверка гипотез @@ -3093,13 +3127,18 @@ Alex прочитал `git show --stat ba6ef44` (2 файла, 10 строк) и | | | |---|---| | **HEAD** | **`4b99d8e`** — «set_var: args убран у числового поля 2, поле 5 ставится само» · ✅ **ЗАПУШЕН** | -| **Рабочее дерево** | чистое по конвертерам | +| **Рабочее дерево** | ⚠️ **изменено, НЕ закоммичено** (`config-to-yml.py`, `yml-to-config.py` — круг 42, §22) | | **Файл на диске** | `config_local_2026-09-17_22-26-26.yml` — 757 объектов; `trigger.step_id`, `end: true` у терминаторов, `args` только у `objcmd` | -| **Круг** | ✅ **9/9** — потеряно 0, лишнее 0 | +| **Круг** | ✅ **8/8** (в `zont_config/` 8 снапшотов `.txt`) — потеряно 0, лишнее 0 | | **Коммиты сессии** | `4b99d8e` · `8f2edd4` · `272f4c6` · `6d0b6a5` · `4c6a6f5` · `806ed2e` · `b770bed` · `ba6ef44` · `0cbbeb8` · `2007771` · `0c4b9a6` · `821659b` · `16ec710` · `375d01a` · `07c076f` — ✅ **ЗАПУШЕНЫ** (`d94b809..4b99d8e`) | +| **Док в vault** | `personal/projects/zont-config-compiler.md` — коммит vault `ab32dd4` | | **Незакоммичено в `zont_config/`** | 2 удалённых `.yml`-снапшота (`18-43-24`, `19-53-21`), новый `.txt` | | **Открыто** | только поле 4 = `2` у `expr` (`10077`–`10085`) — имя не дано; всё остальное ✅ закрыто | +⚠️ **Первая строка «HEAD» описывает коммиты, а не рабочее дерево.** После круга 42 в дереве лежат +**несохранённые** правки `config-to-yml.py` / `yml-to-config.py` — при следующей сессии начинать с +`git status` / `git diff`, а не с предположения «всё закоммичено». + ### 21.2.1 ✅ Круг 40 (ФИНАЛ) — `set_var`: `args` выдуман, поле 5 производное **«да блядь! опять?!»** на `args: [0, 1]` у `#Z10069` — Alex отверг его в третий раз (круги 32,