diff --git a/personal/projects/zont-config-compiler.md b/personal/projects/zont-config-compiler.md index 532b7f65..968ed38e 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-17h' +updated: '2026-09-17i' tags: - personal - zont @@ -625,6 +625,10 @@ iconv -f cp1251 -t utf-8 out.txt | tr -d '\r' | grep '^#Z=<тип>' | 28c | 🔴 **Две точки входа энкодера: правка в одной = потеря правки при зелёном round-trip** | `emit_action` (скрипты из списков/`scenario_orphans`) и инлайн-ветка `emit_step` (`descr`+`args`) — **форк**. Новый ключ вводить в **обе**; проверять тестом на **подмену значения** (§10.1) | | 28d | 🔴 **Регексп тела 59 был `^set var(\d+)$` → пропускал `set varname`** | Имя переменной — произвольный идентификатор, в UI задаётся руками. ✅ `^set (var\w*|\w+)\s*$`. Симптом: `descr: set varname` вместо `set_var` (§10.4) | | 28e | 🔴 **Секции YAML не несут номер типа объекта** | Энкодеру для разворота события нужна карта `id → тип`. `_body_type()`: словарь секций → тип + `executors.relays`/`analog_outputs` + `raw`-склад (тело = `[тип, …]`). Без карты — `exit 2`: `unknown event 'lost' for object 8450 (type None)` (§10.4) | +| 28f | 🔴 **Нельзя проверять «вернулся dict» для цели `set var`** | `_set_var_target()` даёт **raw-фоллбэк-словарь** для любого типа, поэтому `isinstance(tgt_body, dict)` пропускает цель-шаг (`objstate`) как объект значения и рождает `set_var: {type: 59, raw: […]}`. Нужен `_set_var_value_body()`: проверка `entry[0] in (49, 50)` **до** разворота. Симптом: мусорный `set_var` у шага `9886` (§10.6) | +| 28g | 🔴 **Мусорный ярлык в `type:` ломает читаемость сильнее, чем отсутствие ярлыка** | `type: condition` у объекта с `param: target_temp` — «condition» врёт: запись 49 несёт **значение**, а не условие. Alex: «тут почему condition?». Ярлык должен называть **содержимое** (`param`), а не «формат записи» | +| 28h | 🔴 **Переиспользование занятого имени ключа ломает сборку** | Алекс: «value», но `value` уже занят сырым числом в том же узле → коллизия. Решение: освободить `value` под `type`-слово, сырое число → `raw_value`. Перед вводом ключа — `grep` по **всем** использованиям имени | +| 28i | 🔴 **`grep -oE` c `[^\r]*` обрезает строку на мультибайте (cp1251)** | `grep -oE "#Z9964=[^\r]*"` вернул `59,'set va` — я чуть не доложил «строка битая». Читать файл целиком через `iconv -f cp1251 -t utf-8` + `grep -nE '^#Z…='`, либо `python3` с явной кодировкой. Кириллица в cp1251 ≠ байты UTF-8, регексп рвёт её посередине | ### 7.3. Проверка гипотез @@ -721,14 +725,14 @@ https://lk.zont-online.ru/download/firmwares/H2000_PRO____.zip | Что | Состояние | |---|---| -| Round-trip | 🟢 **ЗЕЛЁНЫЙ** — **`760 → 760`, ключей 760/760, различий 0** (снимок `21-07-03`); `698 → 698`, `723 → 723` (`20-40-48`); `661 → 661` (`19-53-21`). ⚠️ **Порядок строк `#S` и часть `#Z` не сохраняется** — но это **унаследованное** поведение (проверено на `b75c51f`: `661 → 661`, порядок тоже не идентичен), **не регрессия** | +| Round-trip | 🟢 **ЗЕЛЁНЫЙ** — **`774 → 774`, ключей 774/774, различий 0** (снимок `21-13-18`, круг 28); `760 → 760` (`21-07-03`); `698 → 698`, `723 → 723` (`20-40-48`); `661 → 661` (`19-53-21`). ⚠️ **Порядок строк `#S` и часть `#Z` не сохраняется** — но это **унаследованное** поведение (проверено на `b75c51f`: `661 → 661`, порядок тоже не идентичен), **не регрессия** | | Форма сценария | ✅ закрыта (§5), оба конвертера переведены | | Тела типа 59 | ✅ **`storeenv` (§10.2), `set_var` с вложенным телом цели (§10.1, круги 12–21), `log` плоский (§10.3) — готовы, не закоммичены** · `objcmd`/`expr`/`objstate` — `descr`+`args` | -| Условия 49 при поле 3 = `1` | ✅ **форма закрыта** — `type: condition` + `object` + `event`; поле 3 в YAML не пишется; 18 комбинаций из **Conditions Test** (§10.4) | -| Условия 49 при поле 3 = `0` | ✅ **ЗАКРЫТО (§10.6)** — `param: <имя>` по **типу владельца** (`PARAM_CODES`), 15 `param:` в YAML снимка `21-07-03`. Цель-шаг (`9885`) — не объект значения, остаётся `descr`+`args` | +| Условия 49 при поле 3 = `1` | ✅ **форма закрыта** — `event` + `object`; поле 3 в YAML не пишется; 18 комбинаций из **Conditions Test** (§10.4) | +| Условия 49 при поле 3 = `0` | ✅ **ЗАКРЫТО (§10.6)** — `param: <имя>` по **типу владельца** (`PARAM_CODES`), 15 `param:` в YAML снимка `21-13-18`. Ярлык `type: param` (круг 27, было `condition`). Цель-шаг (`9885`) — не объект значения, остаётся `descr`+`args` | | Объект 50 (`set_var`) | ✅ **ЗАКРЫТО (§10.5)** — **поле 2** = форма (`0` время / `1` дни), **поле 3** = знак сравнения, поле 4 = время, поле 5 = маска. Знак пишется как **`op`** — коды `0 <` `1 >` `2 =` `3 <=` `4 >=` (совпали с типом 47 на 4/4 условиях) | | Имя переменной | ✅ `set var1` **и** `set varname` — регексп `^set (var\w*\|\w+)$` (§10.4) | -| `field` / `mode` / `cmp` / `_f2` / `_f5` / `_head` | ⛔ **удалены** — 0 вхождений в коде и файлах (§10.1 круги 19–21, круг 26) | +| `field` / `mode` / `cmp` / `_f2` / `_f5` / `_head` / `condition` | ⛔ **удалены** — 0 вхождений в коде и файлах (§10.1 круги 19–21, §10.5 круги 26, §10.6 круг 27) | | Потери объектов | ✅ **0** — было 5 (`8472`, `8821`, `8849`, `8851`, `8855`) | | `unresolved: true` | ⚠️ висит у `#Z8860`, `#Z8864`, `#Z8601` — объекта в конфиге нет (§10, §10.4) | | Коммит | `b75c51f` — «Drop stale YAML snapshots in old scenario shape» ← **текущий HEAD** | @@ -736,6 +740,7 @@ https://lk.zont-online.ru/download/firmwares/H2000_PRO____.zip | Ранее | `823fabd` — форма сценария §5; `1cc010a` — **содержит сломанные версии**; закрыт §9.1 | | Документация | ✅ **три дока сведены в один** — `personal/projects/zont-config-compiler.md` (§9.2) | | Push | ❌ **не сделан** | +| Коммит — правило | 🔴 **Alex 2026-09-17: «отъебись блядь! я скажу комит когда надо будет!»** — сам не предлагать коммит повторно, не напоминать. Ждать явной команды | **Проверка на снимке `19-53-21`:** `trigger:` 66 · `action:` · `if:`/`then:` (тест `8456`) · анкоров **2** (`&id001` sensors, `&id002` SMS — оба законные, §5.10). @@ -810,21 +815,29 @@ git commit -m "Scenario YAML shape: bare action ids, no anchors" 4. **Тип 3** (SMS `8195`) — тело лежит якорем в `sms_notifications`, не раскрыто. 5. **Тип 11 внутри `steps` другого сценария** — ссылка на сценарий голым `id` (`#Z8456` держит `11109`). 6. **Старые снапшоты `14-16-35.yml` / `16-02-18.yml`** — по 2 вхождения старого `type:`, не перегенерированы. -7. ⚠️ **Семантика поля 3 объекта 49 — РАСКРЫТА, но ключ не заведён.** `0` = **значение** - (число или параметр), `1` = **событие**. Подтверждено двумя разведками: - §10.4 (**Conditions Test**, 18 событий → все поле 3 = `1`) и §10.6 (**Values test**, - 15 значений → все поле 3 = `0`). Различие восстанавливается по составу ключей - (есть `event` → `1`, есть `value` → `0`). Отдельного ключа (`field`, `mode`) нет и не будет — - Alex прошёл оба варианта и оба отверг. - 🔴 **Осталось:** при поле 3 = `0` поле 4 — **код параметра по типу владельца** (§10.6). - В YAML пока `value: <число>` без имени. **Словарь имён не заводить**, пока Alex не подтвердит - его для всех типов: один код даёт разные имена у разных типов (`3` = модуляция / целевая температура). +7. ✅ ~~**Семантика поля 3 объекта 49**~~ — **ЗАКРЫТО** (§10.6, круг 27). `0` = **значение + параметра** → ключ `param` (имя из `PARAM_CODES` по типу владельца), `1` = **событие** → ключ + `event`. Ярлык `type` = **`param`** (было `condition`, переименовано по требованию Alex). + Подтверждено двумя разведками: §10.4 (**Conditions Test**, 18 событий → все поле 3 = `1`) + и §10.6 (**Values test**, 15 значений → все поле 3 = `0`). + Отдельного ключа (`field`, `mode`) нет и не будет — Alex прошёл оба варианта и оба отверг. 8. **Семантика шага `8860`** (и `8864`, `8601`) — см. п. 3. 🔴 **Гипотеза «завершить сценарий» опровергнута данными** (§10.4): Alex добавил в сценарий **Conditions Test** все возможные conditions — и `8860` там **не появился**. В `then`-ветках нового сценария стоят обычные шаги 59. Значит `8860` — не типовое условие, а нечто иное (вероятно, служебный маркер ветки). UI-имени Alex так и не назвал. +> 🔴 **Правило круга 27 (стиль работы, подтверждено Alex):** спрашивать «что за объект в UI» по +> **каждому** неясному полю Alex'у дорого — он отвечает «тупые вопросы». Порядок действий: +> 1) **сначала сам** сводить данные (`iconv` + таблица полей по ВСЕМ объектам типа), +> 2) печатать **расхождения** списком, +> 3) и только если осталось **одно** неоднозначное поле — задать **один** вопрос. +> Ошибка сессии: я вместо перекачки конфига искал `Values test` в старом снимке и трижды спросил +> «где живёт сценарий» → «ты конфиг скачал?». **`curl` ПЕРЕД `grep`.** +> +> 🔴 **Правило про коммит:** Alex сам скажет. Предлагать повторно («скажи коммит») — раздражает. +> Фраза Alex: «отъебись блядь! я скажу комит когда надо будет!». + ### 10.4. 📊 Conditions Test — все 18 комбинаций условий (снимок `20-40-48`) **Как добыто:** Alex добавил в UI сценарий **«Conditions Test»** и попросил снять конфиг — @@ -1457,16 +1470,34 @@ cat /tmp/zfresh.txt | iconv -f cp1251 -t utf-8 | grep -nE "=11,'" | grep -viE " Alex: «словами» · «как везде». Значит — как у событий (`event: upper_threshold`), имя латиницей. +> 🔴 **Круг 27: `type` у записи 49 переименован `condition` → `param`.** Alex увидел +> `type: condition` у объекта с `param: target_temp` и спросил «тут почему condition?». +> Далее выбрал `value`, но ключ `value` уже занят сырым числом — тогда сказал «значит param». +> **Итог: `type: param`.** Слово `condition` врало: запись 49 с `param` несёт не условие, +> а **значение**. Все **39** записей 49 несут один ярлык `type: param`; различает их ключ +> внутри (`event` или `param`). Сырое число (объект-владелец неизвестен) пишется в +> **`raw_value`** — ключ `value` освобождён под `type`-слово, коллизии нет. + ```yaml - id: 9620 # #Z9620=59,'set var1',9619,0,0 set_var: id: 9619 # #Z9619=49,4099,0,4 name: var1 - type: condition + type: param object: 4099 - param: pressure # 🆕 поле 4 = 4 → 'давление' + param: pressure # поле 4 = 4 → 'давление' ``` +| Ярлык `type` у `set_var` | Кол-во (774) | Запись | Внутри | +|---|---|---|---| +| `param` | 39 | тип **49** | `event:` (событие) **или** `param:` (значение параметра), либо `raw_value:` (объект неизвестен) | +| `time_condition` | 4 | тип **50**, поле 2 = `0` | `op:` + `time:` | +| `days_mask` | 1 | тип **50**, поле 2 = `1` | `days: []` | + +> ⚠️ **Ярлык `param` покрывает И события тоже** — `9937` (`object: 8450, event: lost`) несёт +> `type: param`. Слово неточное для событий, различает их только ключ `event`. Alex это видел +> и оставил как есть («значит param»). Если понадобится `type: event` для событий — отдельный круг. + **Словарь `PARAM_CODES` (модульный, `config-to-yml.py` ‖ `_PARAM_CODES` в `yml-to-config.py`):** | Тип владельца | Объекты | код → имя | @@ -1503,8 +1534,39 @@ Alex: «словами» · «как везде». Значит — как у с `grep` по последнему снимку. Прибор отдаёт `config.txt` из памяти; сохранение в UI тоже нужно (`#S15=1`), но проверять надо **свежим запросом**, а не прошлым файлом. +#### 📌 Круг 28: свежий снимок `21-13-18` (774 строки) — круг ✅ зелёный + +Alex: «перескачай новый конфиг и заново прогони». Конфиг вырос **760 → 774** строк: дополнился +**«Простой тестовый сценарий»** (`#Z8456`) — новые шаги `9925…9990`. `Values test` (`#Z9324`) +не изменился. + +```bash +TS=$(date +%Y-%m-%d_%H-%M-%S) +curl -s --max-time 30 http://192.168.0.50/config.txt -o "config_local_${TS}.txt" +# → config_local_2026-09-17_21-13-18.txt, 774 строки, 72 сценария +``` + +| Проверка на `21-13-18` | Результат | +|---|---| +| парсер → YAML | `83782` байта | +| круг | `774 → 774`, ключей 774/774 | +| пропало / лишних | **0 / 0** | +| различий по значениям | **0** | +| тест на подмену (форма `set_var`) | ✅ `9620` → `param: pressure`; `9950` → `op: '>' time: '13:23'`; `9937` → `event: lost` | + +**Новые объекты 50 в снимке** (`#Z9949/9951/9953/9955/9957`) — те же четыре знака + дни, +что в `21-07-03`, коды `1`/`0`/`2`/`4`. Счётчики YAML: `param:` 15 · `op:` 9 · `event:` 24 · +`days_mask` 2. + > 📌 **Сравнение снимков:** `20-55-22` = 729 строк / 71 сценарий · `21-07-03` = 760 строк / -> **72 сценария** (+`Values test`). Разница 31 строка = 15 шагов + 15 целей 49 + 1 сценарий. +> **72 сценария** (+`Values test`) · `21-13-18` = **774 строки** / 72 сценария +> (`Values test` + расширенный «Простой тестовый сценарий»). + +> ⚠️ **`set varname` (шаги `9963/9964/9968/9971/9973`) НЕ разворачивается** — цель это +> `#Z9962=59,'2 ;#p'` / `9966` / `9969` / `9972` (`5 ;#p`, `exp…`), т.е. **другой скрипт**, +> а не запись 49/50. Остаётся `descr` + `args`. Это не дефект: разворачивать нечего. +> Отдельный случай — `#Z9964=59,'set varname',42,0,1`: цель `42` не объект, а **число**, +> аргумент 3 = `1` (а не `0`). Форма та же `descr` + `args`. --- @@ -1512,13 +1574,14 @@ Alex: «словами» · «как везде». Значит — как у с | Файл | Статус | |---|---| -| `config-to-yml.py` | ✅ форма §5: `trigger:` подъём через `pop('if')`, шаг = `{id, [flag], action}` или `{id, [flag], if, then, [else]}` · ✅ тип 59: `set_var` (§10.1), `storeenv` (§10.2), `log` (§10.3), `type: condition` + `event`/`value` (§10.4) · ✅ объект 50 двумя формами — **поле 2** = форма, **поле 3** = знак → `op` из `CMP_CODES` (§10.5) · ✅ **тип 49 поле 3 = `0` → `param` из `PARAM_CODES` по типу владельца** (§10.6) · ✅ `_set_var_value_body()` — разворот только для цели-записи `49`/`50` (§10.6) — **в рабочей копии, не закоммичено** | -| `yml-to-config.py` | ✅ `emit_step` читает `then`/`action`/`else`/`flag`; `f5` из `trigger:`/`interval_ms` + бит 8 · ✅ `set_var`/`storeenv` собраны в **`_script_body()`** — одна функция на обе точки входа (`emit_action` + инлайн `emit_step`) · ✅ `_body_type()` — карта `id → тип` по секциям + `raw`-склад (§10.4) · ✅ `time_condition` пишет `[50, 0, _OP_TO_CODE[op], (hh<<8)\|mm, 0]`, `days_mask` → `[50, 1, 0, 0, mask]` (§10.5) · ✅ **`param` → обратный `_PARAM_CODES`**, поле 3 = `0` (§10.6) — **не закоммичено** | +| `config-to-yml.py` | ✅ форма §5: `trigger:` подъём через `pop('if')`, шаг = `{id, [flag], action}` или `{id, [flag], if, then, [else]}` · ✅ тип 59: `set_var` (§10.1), `storeenv` (§10.2), `log` (§10.3) · ✅ объект 50 двумя формами — **поле 2** = форма, **поле 3** = знак → `op` из `CMP_CODES` (§10.5) · ✅ **тип 49 поле 3 = `0` → `param` из `PARAM_CODES` по типу владельца**, ярлык `type: param` (§10.6, круг 27) · ✅ `_set_var_value_body()` — разворот только для цели-записи `49`/`50` (§10.6) · ✅ сырое число при неизвестном владельце → `raw_value` — **в рабочей копии, НЕ закоммичено** | +| `yml-to-config.py` | ✅ `emit_step` читает `then`/`action`/`else`/`flag`; `f5` из `trigger:`/`interval_ms` + бит 8 · ✅ `set_var`/`storeenv` собраны в **`_script_body()`** — одна функция на обе точки входа (`emit_action` + инлайн `emit_step`) · ✅ `_body_type()` — карта `id → тип` по секциям + `raw`-склад (§10.4) · ✅ `time_condition` пишет `[50, 0, _OP_TO_CODE[op], (hh<<8)\|mm, 0]`, `days_mask` → `[50, 1, 0, 0, mask]` (§10.5) · ✅ **`param` → обратный `_PARAM_CODES`**, поле 3 = `0`; ветка `tgt_type == 'param'` (§10.6, круг 27) — **НЕ закоммичено** | | `test_roundtrip.py` | ✅ без изменений (в коммите `199f2b1`) | -| `zont_config/config_local_2026-09-17_21-07-03.{txt,yml}` | 🆕 **АКТУАЛЬНЫЙ** снимок (760 строк, 72 сценария) — содержит **`#Z9324=11,'Values test'`** (§10.6, 15 значений параметров) и 4 time-условия Alex (§10.5). **Не закоммичен** | -| `zont_config/config_local_2026-09-17_20-55-22.{txt,yml}` | предыдущий (729 строк, 71 сценарий) — 4 time-условия Alex (`#Z9248/9250/9252/9254`), знак локализован в поле 3. **Values test в нём НЕТ** | -| `zont_config/config_local_2026-09-17_20-40-48.{txt,yml}` | сценарий **`9144` Conditions Test** (18 шагов) + ⚠️ **`_f2: 1` — мусорный ключ** (§10.5): `set_var` объекта `8844` (`type: days_mask`) донёс `_f2` из поля 2. Файл **устарел**, ключ вычищен из кода | -| `zont_config/config_local_2026-09-17_19-53-21.{txt,yml}` | ещё раньше (686 строк, 661 `#Z`) — форма §10.2/§10.3; **форма объекта 50 в нём УСТАРЕЛА** (§10.5) | +| `zont_config/config_local_2026-09-17_21-13-18.{txt,yml}` | 🆕 **АКТУАЛЬНЫЙ** снимок (774 строки, 72 сценария) — «Простой тестовый сценарий» расширен шагами `9925…9990`, `Values test` не изменился. Круг ✅ `774 → 774`, различий 0 (§10.6, круг 28). **Не закоммичен** | +| `zont_config/config_local_2026-09-17_21-07-03.{txt,yml}` | предыдущий (760 строк, 72 сценария) — первый снимок с **`#Z9324=11,'Values test'`** (§10.6) и 4 time-условиями Alex (§10.5) | +| `zont_config/config_local_2026-09-17_20-55-22.{txt,yml}` | 729 строк, 71 сценарий — 4 time-условия Alex (`#Z9248/9250/9252/9254`), знак локализован в поле 3. **Values test в нём НЕТ** | +| `zont_config/config_local_2026-09-17_20-40-48.{txt,yml}` | сценарий **`9144` Conditions Test** (18 шагов) + ⚠️ **`_f2: 1` — мусорный ключ** (§10.5). Файл **устарел**, ключ вычищен из кода | +| `zont_config/config_local_2026-09-17_19-53-21.{txt,yml}` | 686 строк, 661 `#Z` — форма §10.2/§10.3; **форма объекта 50 в нём УСТАРЕЛА** (§10.5) | | `zont_config/config_local_2026-09-17_18-43-24.{txt,yml}` | ещё раньше, форма §5 | | `zont_config/config_local_2026-09-17_{17-45-00,16-13-28,16-02-18,14-16-35}.*` | исторические снапшоты | | `zont_config/config_0FA7C33CC89F_…_12-12-28.txt` | боевой конфиг (598 `#Z`, 25 `#S`) |