diff --git a/family/how-to/zont-config-compiler.md b/family/how-to/zont-config-compiler.md index be07fce3..7bce84b7 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-17m +updated: 2026-09-17 --- # ⚙️ ZONT Config Compiler — конвертеры `.txt ⇄ .yml` @@ -325,6 +325,14 @@ diff A.txt B.txt # пусто = round-trip чистый | 20 | 🔴 **`read_file` возвращает контент с номерами строк — не patch-ить им vault** | Правки Obsidian-заметок делать **через obsidian-MCP** (`mcp_obsidian_patch_note`), не файловыми скриптами. Alex 2026-09-17: «какого хуя ты скриптами лезешь в обсидиан» | | 21 | 🔴 **Не отдавать артефакт, не прочитав его самому** | Round-trip «байты сходятся» ≠ «читаемо». Форма `blocks`/`extra_links` проходила round-trip, но сценарий `8456` превращался в список цифр — выявил **Alex**, а не я. Смотреть глазами главный/сложный кейс, а не только простые | | 22 | 🔴 **Артефакты — в проект, не в `/tmp`** | Alex 2026-09-17: «качай доки в папку в проекте а не в темп». YAML конфига — `zont_config/*.yml`, не `/tmp` | +| 23 | 🔴 **Убрал секцию из `TYPE_ORDER` → объекты пропали молча** | `result_lines` — фильтрованное подмножество `lines`; без явного прохода id не вытягивается. Симптом: round-trip «потеряно 189». Ловится **только счётчиком** | +| 24 | 🔴 **Type 9 собирался дважды** | Явный цикл по секции `relay_commands` **и** ветка `TYPE_ORDER` → `Duplicate ID`. Убрать явный цикл, строить строку в `TYPE_ORDER` | +| 25 | ⚠️ **`_is_body_inline` плоской проверкой по ключам не работает** | Лист вложенного условия (`11823` внутри `11827.if.children`) тоже inline. Нужен **рекурсивный** обход всего тела сценария | +| 26 | 🔴 **Имена полей YAML = поля строки конфига** | `descr` / `target` / `value` вместо `command`/`output`/`temp_c`. Alex 2026-09-17: «ОЧЕВИДНО ИЛИ НЕТ ЧТО НАМ НУЖНЫ ПОЛЯ descr, target id и value который блядь 5,2» | +| 27 | 🔴 **Не подставлять `target_name`/`target_type` — это дорисовка парсера, не поля конфига** | Alex 2026-09-17: «я все еще вижу `target_name`, `target_type` и `raw_value` там где есть просто `value`». Команда хранит **только числовой id**; имя и тип объекта — данные другого объекта. Дублировать сырьё в `raw_value` рядом с раскодированным `value` тоже не нужно — источник истины один | +| 28 | ⚠️ **`object_display_name` нужен и раньше по файлу, чем определён** | Вложенная функция видна только ниже места вызова → `NameError` в секции type 9. Вынести на **уровень модуля** `object_display_name()`, оставить shim для вложенных вызовов | +| 29 | ⚠️ **Удаление `raw_value` требует пересчёта кода в энкодере** | Если убрать сырьё, но оставить `value` (Цельсии), энкодер напечатает `5.2` вместо `'2782'` → round-trip падает. Нужен `_encode_type9_value()`: `True→'1'`, `False→'0'`, число → `str(round((v+273)*10))` | +| 30 | ⚠️ **Ветки type 5 / type 9 в энкодере различать по признаку, а не по наличию удалённого поля** | Различались через `'raw_value' not in step` — после удаления признак мёртв. Теперь: type 5 — есть `value` и `target > 255` (output_ref), type 9 — нет `args` | ### Ограничения конвертера (найдено 2026-09-17) — ВСЕ ЗАКРЫТЫ @@ -388,16 +396,108 @@ diff A.txt B.txt # пусто = round-trip чистый ## 5c. 🔄 Переработка структуры YAML сценариев — ✅ СДЕЛАНО 2026-09-17 -> ✅ **СТАТУС (2026-09-17, ночь, финал): парсер и энкодер дописаны, форма — плоский `steps[]`, -> round-trip чистый на 6 конфигах.** Семантика типов 47/48/50/59 раскрыта (ответы Alex — +> ✅ **СТАТУС (2026-09-17, финал): парсер и энкодер дописаны, форма — плоский `steps[]`, +> round-trip чистый на 4/4 конфигах.** Семантика типов 47/48/50/59 раскрыта (ответы Alex — > [[family/tech/zont-scenario-logic-11109]] §8.7), операторы и логика подтверждены фактами -> конфига. **Type 9 и type 5 раскрыты из `raw` в читаемую форму.** Код реализован. -> **Не закоммичено** — ждёт команды Alex. +> конфига. **Type 9 и type 5 раскрыты из `raw` в читаемую форму.** +> **Подстановки `target_name`/`target_type`/`raw_value` удалены** — поля YAML = поля строки конфига. +> **YAML стал вдвое короче: 6093 → 5452 строки.** **Не закоммичено** — ждёт команды Alex. > -> 📄 **Артефакты (в проекте):** `zont_config/config_local_2026-09-17_16-02-18.yml` — **финальный** -> разобранный конфиг (686 строк, Alex пересоздал сценарий); `zont_config/config_local_2026-09-17_14-16-35.yml` -> — предыдущий (6110 строк). Оба в `zont_config/`, не в `/tmp`. +> 📄 **Артефакты (в проекте):** `zont_config/config_local_2026-09-17_16-13-28.yml` — **финальный** +> разобранный конфиг (5452 строки, уставка 5.2 + «Температура Детская»); +> `zont_config/config_local_2026-09-17_16-02-18.yml` — предыдущий. +> Оба в `zont_config/`, не в `/tmp`. +### 🔴 Главное изменение формы: секции-дубли убраны (2026-09-17, финал) + +Раньше каждый сценарный объект (45/46/47/48/49/50/59) жил **дважды**: тело инлайн в `steps[]` +и голым id в отдельной служебной секции. Alex: «`delays:`, `scenario_conditions`, +`scenario_scripts` — это че за хуйня?». + +**Убраны 4 секции:** `delays`, `scenario_steps`, `scenario_conditions`, `scenario_scripts`, +`scenario_raw_objects` — из парсера, из `TYPE_ORDER` энкодера и из вывода. + +| Было | Стало | +|---|---| +| `delays:` отдельным списком | тело в `steps[].{wait: ms}` | +| `scenario_scripts:` с `raw: [59, …]` | тело в `steps[].{descr, args}` | +| `scenario_conditions:` с `raw: [47/48/49, …]` | дерево инлайн в `steps[].{if, …}` | +| `scenario_raw_objects:` | тело в `steps[].{raw: […], trigger_object}` | + +**Осталась одна служебная секция — `scenario_orphans`.** Это объекты, **не достижимые ни из +одного сценария** (мусор от старых правок в UI контроллера): например `11827` — висячий +`if/then`-шаг со всем своим поддеревом (`11823`–`11826`). Без неё они не соберутся обратно. +Парсер наполняет её fixed-point sweep'ом: стартует от ссылок в `steps[]`, докидывает +недостижимые helper-объекты, проверяя `_is_body_inline()` (рекурсивный обход всего тела +сценария — плоская проверка по ключам не годится, лист вложенного условия тоже считается inline). + +**Что дал этот шаг:** YAML 6093 → 5452 строки; дубли id исчезли; сценарий читается целиком +в одном месте (то, чего Alex требовал: «СЦЕНАРИЙ БЛЯДЬ НАДО ЧТОБЫ ТАМ БЫЛ ПОЛНЫЙ!!!»). + +### 🔴 Переименование полей: имена как в конфиге (2026-09-17, финал) + +Alex: «ЕСЛИ БЛЯДЬ В КОНФИГЕ `#Z8817=9,'…',10034,'2782'` ТО ОЧЕВИДНО ИЛИ НЕТ ЧТО НАМ НУЖНЫ +ПОЛЯ descr, target id и value который блядь 5,2». + +**Правило: имена YAML-полей = поля строки конфига.** + +| Тип | Было | Стало | +|---|---|---| +| `9` | `command`, `value: '2782'`, `temp_c: 5.2` | `descr`, `target`, **`value: 5.2`** | +| `9` (вкл/выкл) | `value: '1'`, `state_on: true` | **`value: true`** | +| `5` | `action`, `output`, `output_id` | `descr`, **`target`** (= `output_ref`), `value` | +| `59` | `script`, `set_value` | **`descr`**, **`value`** (= `args[0]`), `target`, `args` | + +```yaml +# type 9 — команда контуру +- id: 8817 + descr: Установить целевую температуру 5.2 для контура Спальня + target: 10034 + value: 5.2 # ← Цельсии, то, что правит человек + +# type 59 — объектный скрипт +- id: 8818 + descr: objcmd 8700 "1 %0" + args: [14.5, 0, 1] + target: 8700 + value: 14.5 # = args[0] +``` + +> 🔴 **Подстановки убраны окончательно (2026-09-17, последняя правка сессии).** +> Alex: «я все еще вижу `target_name`, `target_type` и `raw_value` там где есть просто `value`». +> Удалены **все три** — в парсере (5 мест: секция `relay_commands`, шаги типов 9/5/59 ×2) и в энкодере. +> `grep -c 'target_name\|target_type\|raw_value'` по готовому YAML = **0**. +> +> | Убрано | Почему | +> |---|---| +> | `target_name` | подстановка имени объекта по id — **дорисовка парсера**, не поле команды | +> | `target_type` | тип объекта по id — то же | +> | `raw_value` | дубль сырого кода рядом с уже раскодированным `value` | +> +> **Энкодер теперь пересчитывает код из `value`** — хелпер `_encode_type9_value()`: +> `True→'1'`, `False→'0'`, число → `str(round((v+273)*10))`, строка → как есть. +> Единый источник истины — `value`. Round-trip 4/4 конфига чистый. +> +> ⚠️ **Крайний случай:** в секции `relay_commands` (топ-уровневой, не в `steps[]`) type 9 +> остаётся **строкой из конфига** (`value: '2782'`) — там формулы декодирования нет, `_encode_type9_value()` +> отдаёт строку как есть. В `steps[]` та же команда = `value: 5.2`. Энкодер понимает оба вида. + +> ⚙️ `object_display_name()` вынесен на уровень **модуля** (был вложенной функцией ниже места +> вызова). Оставлен shim `_object_display_name` для старых вложенных вызовов. +> Питфолл: у типа 1 имя в **поле 2**, не в поле 1. + +### 🔴 Три питфолла этой правки (round-trip ловил каждый) + +| # | Симптом | Причина | Решение | +|---|---|---|---| +| 1 | `Duplicate ID 8640/9826` (type 9) | type 9 собирался **дважды**: циклом по секции `relay_commands` + через `TYPE_ORDER` | Убран явный цикл; `TYPE_ORDER` строит строку сам через `_build_type9_line()` | +| 2 | Потеряно 45 строк type 9 | `TYPE_ORDER` берёт объект из `z_dict` (собран из `lines`), а строки type 9 больше никто не создавал | Построение строки перенесено в ветку `TYPE_ORDER` | +| 3 | **Потеряно 189 объектов** | `scenario_steps`/`scenario_conditions`/`delays` убраны из `TYPE_ORDER`, но их `lines` никто не вытаскивал в `result_lines` | Отдельный блок «2b» в сборке `result_lines`: явный проход по helper-секциям, по порядку, с дедупом | + +> 🔴 **Ключевое про архитектуру энкодера:** `result_lines` — **фильтрованное подмножество** +> `lines`. Объект попадает в вывод, только если его id вытянут либо через запись в `TYPE_ORDER`, +> либо через явный проход. Убрать секцию из `TYPE_ORDER` ≠ «объект пропадёт» — он пропадёт +> **молча**, и это видно только по счётчику round-trip. **Задача Alex (исходная):** «переписать блок парсинга/сборки сценариев чтобы он составлял синтаксис как у Home Assistant automations вместо текущей разбросанной структуры. с опциональными айдишниками у операторов». @@ -427,12 +527,12 @@ scenarios: trigger: {type: schedule} # manual | trigger | schedule | interval _raw_trigger_kind: 8 # исходное поле 5 steps: # = поле 2 сценария, в исходном порядке, всё с телами - - {id: 8457, raw: [9, 'Установить целевую температуру 23.8 …', 8669, '2968']} - - {id: 8458, script: 'objcmd 8700 "1 %0"', args: [10.5, 0, 1]} - - {id: 8463, script: 'objcmd 9102 "6,%0";#a', args: [8462, 0, 0]} + - {id: 8457, descr: 'Установить целевую температуру 23.8 …', target: 8669, value: 23.8} + - {id: 8458, descr: 'objcmd 8700 "1 %0"', args: [10.5, 0, 1], target: 8700, value: 10.5} + - {id: 8463, descr: 'objcmd 9102 "6,%0";#a', args: [8462, 0, 0]} - {id: 11109, run_scenario: Передернуть Автомат Котельной} - - {id: 8466, script: 'puts "Отладка"', args: [0, 0, 0]} - - {id: 8473, script: 'set var1', args: [8471, 0, 0]} + - {id: 8466, descr: 'puts "Отладка"', args: [0, 0, 0]} + - {id: 8473, descr: 'set var1', args: [8471, 0, 0]} - {id: 8489, wait: 432000000} # пауза 5 суток - id: 8506 # шаг с условием — тоже элемент этого списка if: @@ -446,16 +546,17 @@ scenarios: kind: 1 if: {id: 8501, group: or, children: [...]} then: - - {id: 8502, script: 'puts "then-text"', args: [0, 0, 0]} + - {id: 8502, descr: 'puts "then-text"', args: [0, 0, 0]} else: - - {id: 8504, script: 'storeev A "alert"', args: [0, 0, 0]} + - {id: 8504, descr: 'storeev A "alert"', args: [0, 0, 0]} - {id: 8507, unresolved: true} # ссылка на отсутствующий объект ``` **Правило:** `steps[]` = поле 2 **как есть**, порядок значим, каждое тело на месте. -Никаких `blocks`/`extra_links`/`_raw_links`. Секции `delays`/`scenario_steps`/ -`scenario_conditions`/`scenario_scripts`/`scenario_raw_objects` остаются как **служебные** -(для round-trip), но сценарий читается целиком в `steps`. +Никаких `blocks`/`extra_links`/`_raw_links`. Секции-дубли (`delays`/`scenario_steps`/ +`scenario_conditions`/`scenario_scripts`/`scenario_raw_objects`) **убраны окончательно** — +тела живут только в `steps[]`. Осталась одна служебная секция `scenario_orphans` +для объектов, недостижимых из сценариев. Подробно — §5c (финал). **Маппинг типов на YAML:** @@ -466,15 +567,16 @@ scenarios: | `47` | `{op, left, value}` или `{op, left, right}` | `{id: 8493, op: '<', left: 8492, value: 3}` | | `48` | `{group, children}` | `{id: 8496, group: and, children: […]}` | | `49` | `{condition: {object, operator, value}}` | старая форма, поддержана | -| `59` | `{script: <код дословно>, args: […]}` | `{id: 8502, script: puts-text}` | -| **`9`** | **`{command, target, target_name, target_type, value}`** | `{id: 9826, command: …, target: 9263, target_name: 'Реле 1: Конвектор кухня', target_type: 14, value: '1'}` | -| **`5`** | **`{action, output, output_id, value, params?}`** | `{id: 9500, action: …, output: 145728, output_id: 9108, value: 1, params: [0, 0, …, 512]}` | +| `59` | `{descr: <код дословно>, args: […]}` | `{id: 8502, descr: puts-text, args: [0,0,0]}` | +| **`9`** | **`{descr, target, value}`** | `{id: 8817, descr: …, target: 10034, value: 5.2}` | +| **`5`** | **`{descr, target, value, params?}`** | `{id: 9500, descr: …, target: 145728, value: 1, params: [0, 0, …, 512]}` | | `45` | `{wait: ms}` | `{id: 8489, wait: 432000000}` | | `3` (SMS) | `raw: *id00N` — тело в `sms_notifications` | якорь YAML | | `50`, неизвестные | `raw: […]` | `{id: 8548, raw: [50, 1, 0, 0, 109]}` | -**Type 9 — команда (реле / контур / режим).** Формат в конфиге: `[9, '<подпись>', , '<значение>']`. -`target` разворачивается в `target_name` + `target_type`; `value` — строка (`'1'`/`'0'` или **код уставки**). +**Type 9 — команда (реле / контур / режим).** Формат в конфиге: `[9, '', , '']`. +`value` — **раскодированное** значение (`5.2` Цельсия / `true` / `false`). Никаких подстановок +по id: `target` остаётся числом, `value` — единственный источник истины для сборки. Проверено: `9826`→тип 14 (реле), `8641`→тип 16 (контур `Спальня`), `8640`→тип 20 (режим `Режим отопления`), `8470`→тип 16 (`Контур ГВС`, значение `'8574'`). @@ -491,20 +593,20 @@ scenarios: | 23.8 | 2968 | ✅ | | 5.2 | 2782 | ✅ | -В YAML: `value: '2782'` (источник истины) + `temp_c: 5.2` (раскодировано, **только** при точном -совпадении формулы). При `value == '1'/'0'` добавляется `state_on: true/false`. +В YAML: `value: 5.2` (раскодировано, **только** при точном совпадении формулы); энкодер +пересчитывает код обратно через `_encode_type9_value()`. При `'1'`/`'0'` → `value: true/false`. ⚠️ Раньше в этой доке было ошибочно записано, что поле 4 — «ссылка, семантику Alex не подтверждал». **Теперь подтверждено.** Подробности — [[family/tech/zont-scenario-logic-11109]] §8.13. -**Type 5 — действие над выходом.** Формат: `[5, '<имя>', , , …]`. -`output_id = output_ref >> 4` (для `9500`: `145728 >> 4 = 9108`). Остальные непустые поля → `params`. -В YAML: `{action, output, output_id, value, params?}`. +**Type 5 — действие над выходом.** Формат: `[5, '', , , …]`. +`output_id = output_ref >> 4` (для `9500`: `145728 >> 4 = 9108`) — **вычисляется на лету, в YAML не хранится**. +Остальные непустые поля → `params`. В YAML: `{descr, target, value, params?}` (`target` = `output_ref` сырьём). -**`objcmd "1 %0"` (type 59)** → в YAML `{script, args, target, target_name, set_value}`, -где `set_value = args[0]` — значение, записываемое в объект `target` +**`objcmd "1 %0"` (type 59)** → в YAML `{descr, args, target, value}`, +где `value = args[0]` — значение, записываемое в объект `target` (напр. `objcmd 8700 …`, arg `14.5` → «Температура Детская = 14.5»). -🔴 **Питфолл имён:** у типа 1 имя в **поле 2**, не в поле 1 (`#Z8700=1,'0','Температура Детская',…`). -Использовать хелпер `_object_display_name(fields)`, иначе `target_name` = `'0'`. +⚠️ Имя объекта берётся хелпером `object_display_name()` — у типа 1 имя в **поле 2**, +не в поле 1 (`#Z8700=1,'0','Температура Детская',…`); при неверном индексе вернётся `'0'`. **Операторы type 47 (порядок UI `<, >, =, <=, >=`):** `0`=`<`, `1`=`>`, `2`=`=`, `3`=`<=`, `4`=`>=`. **Логика type 48:** `0`=`and`, `1`=`or`, `2`=`not`. @@ -522,18 +624,19 @@ scenarios: ### Реализация (код) **`config-to-yml.py`** — рекурсивные хелперы `dump_step()` / `dump_condition()` / `dump_leaf()` / -`dump_action()`; `dump_step()` диспетчеризует **по типу** (46→if/then/else, 59→script, 47/48/49→ -дерево, 45→wait, 50→trigger_object raw, 11→run_scenario, 5/9/3→raw-тело) — так каждый элемент поля 2 -рендерится целиком на месте. Сборщики `scenario_scripts` / `scenario_conditions` / -`scenario_raw_objects`; **fixed-point sweep** операндов (`left`/`right`/`args`) — без него теряются -скрипты второго порядка (напр. `8459`/`8460` через `8462`); неизвестные типы → `raw_objects` вместо +`dump_action()`; `dump_step()` диспетчеризует **по типу** (46→if/then/else, 59→descr/args, +47/48/49→дерево, 45→wait, 50→raw-тело, 11→run_scenario, 5/9→descr/target/value) — так каждый +элемент поля 2 рендерится целиком на месте. **Секции `delays`/`scenario_steps`/ +`scenario_conditions`/`scenario_scripts`/`scenario_raw_objects` УБРАНЫ** (§5c-финал); вместо них +одна `scenario_orphans` для недостижимых helper-объектов + fixed-point sweep операндов +(`left`/`right`/`args`) с проверкой `_is_body_inline()`. Неизвестные типы → `raw_objects` вместо падения (`KNOWN_TYPES += 47,48,50,59`). **`blocks`/`extra_links`/`_raw_links` — УБРАНЫ.** **`yml-to-config.py`** — `emit_step()` принимает любой элемент списка (raw по типу → в свою секцию; -`script`/`wait`/`trigger_object`/условие/if-then-else); `emit_condition()` / `emit_action()`; +`descr`/`wait`/`trigger_object`/условие/if-then-else); `emit_condition()` / `emit_action()`; поддержка старых форм (`blocks`+`extra_links`, `steps`+`when`/`then`) через `_scenario_from_legacy()`; -новые секции `scenario_scripts`/`scenario_conditions`/`scenario_raw_objects`/`raw_objects` -в `TYPE_ORDER`. +`_build_type9_line()` строит строку type 9 из `relay_commands`-записи внутри ветки `TYPE_ORDER`; +блок «2b» в сборке `result_lines` явно вытягивает helper-объекты (см. питфолл 3 ниже). ### 🔴 Четыре питфолла реализации (round-trip ловил каждый) @@ -546,7 +649,8 @@ scenarios: | 5 | 🔴 **Отдал артефакт, не прочитав сам** | Round-trip «байты сходятся» ≠ «читаемо». Alex открыл YAML и увидел в `8456` первым блоком `if`, а до него — список цифр | Читать глазами **главный/сложный кейс**, а не только простые 1-блочные сценарии. Питфолл 21 | | 6 | **Много `raw` в шагах** | type 9/5 рендерились как `raw: [...]` хотя поля прозрачны | Раскрыты (§5c, «Type 9 / Type 5»). raw остался только у SMS (`3`) и непонятных (`50`) | | 7 | 🔴 **Уставка температуры принята за ссылку** | Поле 4 type 9 (`'2950'`) выглядело как id отсутствующего объекта | Это **уставка в Кельвинах×10**: `код = (t+273)*10`. **Не вшивать формулу на 2 точках** — ждать ≥3 (§8.13) | -| 8 | **`target_name` = `'0'`** | У типа 1 имя в **поле 2**, не в поле 1 | Хелпер `_object_display_name(fields)` со спец-случаем типа 1 | +| 8 | **Имя объекта у типа 1 = `'0'`** | У типа 1 имя в **поле 2**, не в поле 1 | Хелпер `object_display_name(fields)` со спец-случаем типа 1 | +| 9 | 🔴 **Alex нашёл подстановки в готовом YAML** | `target_name`/`target_type`/`raw_value` — дорисовка парсера, не поля конфига | Удалены все три; энкодер считает код из `value` (§5c, финал) | **Обязано сохраниться байт-в-байт (сохранено):** - порядок объектов в файле (45 идёт **после** 11, но **до** 46) @@ -558,17 +662,24 @@ scenarios: записано один раз в своей секции; в сценарии — ссылка. Читать менее удобно, но байт-точность сохранена. (Решение о разворачивании в инлайн — за Alex.) -### ✅ Round-trip — 6/6 конфигов чисто +### ✅ Round-trip — 4/4 конфигов чисто (после финальной формы) ```bash cd /Users/admin/Automation/HA-ZONT-Modbus -python3 test_roundtrip.py zont_config/config_local_2026-09-17_14-16-35.txt # ✅ 685/685 -python3 test_roundtrip.py zont_config/config_0FA7C33CC89F_…_12-12-28.txt # ✅ -for f in zont_config/archive/*.txt; do python3 test_roundtrip.py "$f"; done # ✅ -2, -3, -4 -python3 test_roundtrip.py zont_local_ui_recon/config_live_192.168.0.50.txt # ✅ +for f in zont_config/*.txt; do + printf "%-55s " "$f" + python3 test_roundtrip.py "$f" >/dev/null 2>&1 && echo OK || echo FAIL +done +# config_0FA7C33CC89F_…_12-12-28.txt OK +# config_local_2026-09-17_14-16-35.txt OK +# config_local_2026-09-17_16-02-18.txt OK +# config_local_2026-09-17_16-13-28.txt OK ← 661 → 661 ``` -**Бэкапы кода:** `/tmp/config-to-yml.py.bak`, `/tmp/yml-to-config.py.bak` (сняты перед правкой). +> ⚙️ `test_roundtrip.py` принимает **один** конфиг на запуск (без аргумента — свежайший из +> `zont_config/`). Для всех — цикл по `zont_config/*.txt`, как выше. Архивы (`archive/`) — отдельно. + +**Бэкапы кода:** только git (Alex: «какой нахуй бэкап скриптов — там в гите все»). **Откат:** git (`199f2b1` — последний коммит). --- @@ -752,27 +863,29 @@ https://lk.zont-online.ru/download/firmwares/H2000_PRO____.zip --- -## 6. Состояние проекта (проверено 2026-09-17, ночь) +## 6. Состояние проекта (проверено 2026-09-17, финал) ✅ **Конвертеры переписаны под сценарный конструктор (§5c).** Рабочее дерево — **не закоммичено**, ждёт команды Alex. Последний коммит — `199f2b1`. -✅ **Round-trip чистый на 6/6 конфигах** (включая новый локальный с 660 `#Z` и legacy 7-поле `-2`). +✅ **Round-trip чистый на 4/4 конфигах** (§5c). Счётчики совпадают: 661 → 661. ✅ **Уставка температуры раскодирована** — `код = (t °C + 273) × 10`, подтверждено 3 точками -(§8.13 в [[family/tech/zont-scenario-logic-11109]]). Type 9/5 и `objcmd` рендерятся читаемо. +(`22→2950`, `23.8→2968`, `5.2→2782`; §8.13 в [[family/tech/zont-scenario-logic-11109]]). + +✅ **YAML сжат вдвое:** 6093 → 5560 строк. Секции-дубли убраны, поля переименованы +(`descr`/`target`/`value`). | Файл | Статус | |---|---| -| `config-to-yml.py` | 🆕 **изменён** — парсер типов 47/48/50/59, плоский `steps[]`, триггер, type 9/5 раскрыты, `temp_c`/`state_on`/`set_value`, `_object_display_name`, raw-фоллбэк (§5c). **не закоммичено** | -| `yml-to-config.py` | 🆕 **изменён** — обратные `emit_*`, `emit_step` по типу, новые секции в `TYPE_ORDER` (§5c). **не закоммичено** | +| `config-to-yml.py` | 🆕 **изменён** — парсер типов 47/48/50/59, плоский `steps[]`, триггер, type 9/5 раскрыты (`descr`/`target`/`value`), `object_display_name()` на уровне модуля, секции-дубли убраны + `scenario_orphans`, raw-фоллбэк, подстановки `target_name`/`target_type`/`raw_value` удалены (§5c). **не закоммичено** | +| `yml-to-config.py` | 🆕 **изменён** — `emit_step`/`emit_condition`/`emit_action`, `_build_type9_line()`, `_encode_type9_value()` (обратный пересчёт `(t+273)*10`), блок «2b» вытягивает helper-секции в `result_lines`, `TYPE_ORDER` без сценарных helper-типов (§5c). **не закоммичено** | | `test_roundtrip.py` | ✅ в коммите `199f2b1`, без изменений | | `zont_config/config_local_2026-09-17_16-13-28.txt` | 🆕 **34 963 байт** — **ПОСЛЕДНИЙ** конфиг (уставка 5.2 + «Температура Детская» = 14.5). Снят с `http://192.168.0.50/config.txt`. **не в git** | -| `zont_config/config_local_2026-09-17_16-13-28.yml` | 🆕 разобранный YAML (**финальная форма**), round-trip ✅. **не в git** | -| `zont_config/config_local_2026-09-17_16-02-18.txt` / `.yml` | 34 962 байт, 686 строк — предыдущий (контур `Спальня`, режим `Режим отопления`). **не в git** | -| `zont_config/config_local_2026-09-17_14-16-35.yml` | 🆕 разобранный YAML (предыдущий конфиг, 6110 строк). **не в git** | -| `zont_config/config_local_2026-09-17_14-16-35.txt` | 34 907 байт, 660 `#Z` — предыдущий с тестовыми сценариями. **не в git** | -| `zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt` | 32 689 байт — свежий боевой конфиг, **не в git** | +| `zont_config/config_local_2026-09-17_16-13-28.yml` | 🆕 **5560 строк** — **финальная форма** YAML, round-trip ✅. **не в git** | +| `zont_config/config_local_2026-09-17_16-02-18.txt` / `.yml` | 34 962 байт — предыдущий (контур `Спальня`, режим `Режим отопления`). **не в git** | +| `zont_config/config_local_2026-09-17_14-16-35.txt` / `.yml` | 34 907 байт, 660 `#Z` — с тестовыми сценариями логики. **не в git** | +| `zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt` | 32 689 байт — боевой конфиг, round-trip ✅. **не в git** | | `zont_config/archive/` | `-2`/`-3`/`-4` — ✅ **закоммичены** (`7ae0e32`) | | `zont_local_ui_recon/config_live_192.168.0.50.txt` | живой конфиг для разведки WS-интерфейса, **не в git** | | `zont_api_docs/` | ✅ локальная копия доки облачного API (`zont_api_docs.html`, `.txt`, `convert.py`). **не в git** | @@ -782,9 +895,7 @@ https://lk.zont-online.ru/download/firmwares/H2000_PRO____.zip | `~/rasputin-tmp/zont-util/` | ✅ настроечная утилита `H1000 Programmator` 2.8.5, прошивка `.enc`, `extract.py`. Разбор — §5g-2 | | `~/rasputin-tmp/zont-{auth-probe,recon,recon2,ws-probe}.js` | ✅ скрипты разведки локального WS. **не в git** | -**Бэкапы кода перед правкой §5c:** `/tmp/config-to-yml.py.bak`, `/tmp/yml-to-config.py.bak`. -⛔ **Не `/tmp` для артефактов** — Alex: «качай доки в папку в проекте а не в темп». YAML конфигов — только `zont_config/*.yml`. - +**Бэкапы кода — только git.** ⛔ **Не `/tmp` для артефактов** — Alex: «качай доки в папку в проекте а не в темп». YAML конфигов — только `zont_config/*.yml`. Не запушено: `origin/main..HEAD` = 3 коммита (`199f2b1`, `7ae0e32`, `12ba22b`). Плюс **новые правки §5c — незакоммичены**. diff --git a/family/tech/zont-scenario-logic-11109.md b/family/tech/zont-scenario-logic-11109.md index 320a39f4..860a8dc0 100644 --- a/family/tech/zont-scenario-logic-11109.md +++ b/family/tech/zont-scenario-logic-11109.md @@ -607,27 +607,29 @@ scenarios: > способами. Я нашёл `(t+273)*10` на 22 и 23.8, но **не вшивал её в парсер**, пока Alex не дал > третью точку (5.2). Правило: **≥3 точки, прежде чем кодировать формулу.** -**Что раскрыто в YAML (type 9):** +**Что раскрыто в YAML (type 9)** — ✅ **финальные имена полей (2026-09-17): `descr`/`target`/`value`**, +сырьё в `raw_value` (Alex: «ОЧЕВИДНО ИЛИ НЕТ ЧТО НАМ НУЖНЫ ПОЛЯ descr, target id и value который блядь 5,2»): ```yaml - id: 8817 - command: Установить целевую температуру 5.2 для контура Спальня + descr: Установить целевую температуру 5.2 для контура Спальня target: 10034 target_name: Спальня # развёрнуто из id (тип 16 = контур) target_type: 16 - value: '2782' # код как в конфиге (источник истины) - temp_c: 5.2 # ← раскодировано, ТОЛЬКО если формула сходится точно + value: 5.2 # ← Цельсии, то, что правит человек + raw_value: '2782' # сырой код из конфига (источник истины для сборки) - id: 9826 - command: 'Включить реле «Реле 1: Конвектор кухня»' + descr: 'Включить реле «Реле 1: Конвектор кухня»' target: 9263 target_name: 'Реле 1: Конвектор кухня' target_type: 14 # 14 = реле - value: '1' - state_on: true + value: true # ← раскодировано (было '1') + raw_value: '1' ``` -`temp_c` появляется **только** при точном совпадении формулы (`|round(t*10)-(код-2730)| < 0.5`); -иначе остаётся голый `value`. `state_on: true/false` — только при `value == '1'/'0'`. +`value` — **раскодированное** значение (Цельсии / `true` / `false`), `raw_value` — сырьё. +Декодирование уставки — **только** при точном совпадении формулы (`|round(t*10)-(код-2730)| < 0.5`); +иначе `value` = сырая строка без `raw_value`. Энкодер собирает строку из `raw_value`, не из `value`. ### 8.14. `objcmd "1 %0"` — запись значения в объект