[2026-09-17] eagle: family/how-to/zont-config-compiler.md family/tech/zont-scenario-logic-11109.md
This commit is contained in:
@@ -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, '<подпись>', <id цели>, '<значение>']`.
|
||||
`target` разворачивается в `target_name` + `target_type`; `value` — строка (`'1'`/`'0'` или **код уставки**).
|
||||
**Type 9 — команда (реле / контур / режим).** Формат в конфиге: `[9, '<descr>', <target id>, '<value>']`.
|
||||
`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_ref>, <on/off>, …]`.
|
||||
`output_id = output_ref >> 4` (для `9500`: `145728 >> 4 = 9108`). Остальные непустые поля → `params`.
|
||||
В YAML: `{action, output, output_id, value, params?}`.
|
||||
**Type 5 — действие над выходом.** Формат: `[5, '<descr>', <output_ref>, <value>, …]`.
|
||||
`output_id = output_ref >> 4` (для `9500`: `145728 >> 4 = 9108`) — **вычисляется на лету, в YAML не хранится**.
|
||||
Остальные непустые поля → `params`. В YAML: `{descr, target, value, params?}` (`target` = `output_ref` сырьём).
|
||||
|
||||
**`objcmd <target> "1 %0"` (type 59)** → в YAML `{script, args, target, target_name, set_value}`,
|
||||
где `set_value = args[0]` — значение, записываемое в объект `target`
|
||||
**`objcmd <target> "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_<HW>__<FW>_<PROFILE>.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_<HW>__<FW>_<PROFILE>.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 — незакоммичены**.
|
||||
|
||||
@@ -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 <id> "1 %0"` — запись значения в объект
|
||||
|
||||
|
||||
Reference in New Issue
Block a user