[2026-09-17] eagle: personal/projects/zont-config-compiler.md
This commit is contained in:
@@ -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-17f'
|
updated: '2026-09-17h'
|
||||||
tags:
|
tags:
|
||||||
- personal
|
- personal
|
||||||
- zont
|
- zont
|
||||||
@@ -186,8 +186,8 @@ python3 test_roundtrip.py zont_config/config_X.txt
|
|||||||
| **46** | **Шаги сценариев** | `if`/`then`/`else`/`action`, `flag` | инлайн в `scenarios[].steps[]` |
|
| **46** | **Шаги сценариев** | `if`/`then`/`else`/`action`, `flag` | инлайн в `scenarios[].steps[]` |
|
||||||
| **47** | Лист дерева условий | `op`, `left`, `right`, `value` | инлайн в `if` |
|
| **47** | Лист дерева условий | `op`, `left`, `right`, `value` | инлайн в `if` |
|
||||||
| **48** | Группа условий И/ИЛИ/НЕ | `group`, `children[]` | инлайн в `if` |
|
| **48** | Группа условий И/ИЛИ/НЕ | `group`, `children[]` | инлайн в `if` |
|
||||||
| 49 | Условия сценариев | `object`, `operator`, `value` | инлайн в `trigger` / `steps[].if` |
|
| 49 | Условия сценариев | `object`, `event` (поле 3 = `1`) **‖** `param` (поле 3 = `0`) | инлайн в `trigger` / `steps[].if` / `set_var` |
|
||||||
| **50** | Маска дней недели | `[50, 1, 0, 0, <mask>]` — `raw` | инлайн как объект-условие |
|
| **50** | Условие по времени **‖** маска дней | `op` + `time` (поле 2 = `0`) **‖** `days` (поле 2 = `1`) | инлайн как объект-условие |
|
||||||
| 51 | Modbus-устройства | `slave_id`, `name`, `poll_interval`, `timeout`, `registers` | `modbus_devices` |
|
| 51 | Modbus-устройства | `slave_id`, `name`, `poll_interval`, `timeout`, `registers` | `modbus_devices` |
|
||||||
| 52 | Modbus-регистры | `name`, `register`, `bit_width`, `repeat_period`, `num_vars` | вложены в устройство 51 |
|
| 52 | Modbus-регистры | `name`, `register`, `bit_width`, `repeat_period`, `num_vars` | вложены в устройство 51 |
|
||||||
| 53 | Аналоговые выходы | `name`, `min`, `max`, `value`, `offset`, `scale`, `flags`, `address` | `analog_outputs` |
|
| 53 | Аналоговые выходы | `name`, `min`, `max`, `value`, `offset`, `scale`, `flags`, `address` | `analog_outputs` |
|
||||||
@@ -202,12 +202,13 @@ python3 test_roundtrip.py zont_config/config_X.txt
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `11` | сценарий | `[11, name, [step_ids], days, time, f5, interval_ms, 0]` | `scenarios` |
|
| `11` | сценарий | `[11, name, [step_ids], days, time, f5, interval_ms, 0]` | `scenarios` |
|
||||||
| `46` | шаг | `[46, flag, cond_id, [then_ids], [else_ids]]` | инлайн в `steps[]` |
|
| `46` | шаг | `[46, flag, cond_id, [then_ids], [else_ids]]` | инлайн в `steps[]` |
|
||||||
| `49` | условие | `[49, object, operator, value]` | `trigger` / `steps[].if` |
|
| `49` | условие | `[49, object, f3, value]` — f3=`1` событие ‖ f3=`0` параметр | `trigger` / `steps[].if` / `set_var` |
|
||||||
| `45` | пауза | `[45, ms]` | `wait: ms` |
|
| `45` | пауза | `[45, ms]` | `wait: ms` |
|
||||||
| `47` | сравнение | `[47, op, left, value|right]` | `{op, left, value}` |
|
| `47` | сравнение | `[47, op, left, value|right]` | `{op, left, value}` |
|
||||||
| `48` | группа | `[48, logic, [ids]]` | `{group, children}` |
|
| `48` | группа | `[48, logic, [ids]]` | `{group, children}` |
|
||||||
| `59` | мини-скрипт | `[59, '<код>', arg1, arg2, flag]` | `{descr, args[, set_var]}` |
|
| `59` | мини-скрипт | `[59, '<код>', arg1, arg2, flag]` | `{descr, args[, set_var]}` |
|
||||||
| — | **тела 59** | `set var<N>` → `set_var` ‖ `puts` ‖ `storeev I/A` ‖ `objcmd` (+`target`/`value`) | §5.10 |
|
| — | **тела 59** | `set var<N>` → `set_var` ‖ `puts` ‖ `storeev I/A` ‖ `objcmd` (+`target`/`value`) | §5.10 |
|
||||||
|
| — | **тело цели `set_var`** | `49` → `type: condition` + `event`/`param`; `50` → `time_condition`/`days_mask` | §10.1 / §10.5 / §10.6 |
|
||||||
| `5` | действие над выходом | `[5, '<descr>', output_ref, value, …]` | `{descr, target, value, params?}` |
|
| `5` | действие над выходом | `[5, '<descr>', output_ref, value, …]` | `{descr, target, value, params?}` |
|
||||||
| `9` | команда (реле/контур/режим) | `[9, '<descr>', target, '<value>']` | `{descr, target, value}` |
|
| `9` | команда (реле/контур/режим) | `[9, '<descr>', target, '<value>']` | `{descr, target, value}` |
|
||||||
|
|
||||||
@@ -382,13 +383,15 @@ enabled = not (f5 & 8)
|
|||||||
| ⛔ **`days_mask: <число>` / `mask: <число>` / `_head` / `raw` в теле объекта 50** | «какой нахуй days», «какой mask», «откуда блядь опять raw выполз», «че за head блядь» — §10.5 |
|
| ⛔ **`days_mask: <число>` / `mask: <число>` / `_head` / `raw` в теле объекта 50** | «какой нахуй days», «какой mask», «откуда блядь опять raw выполз», «че за head блядь» — §10.5 |
|
||||||
| ⛔ **выдумывать знак сравнения, если поле не подтверждено** | `>` у `8842` получен случайным совпадением (`1` ∈ `LEAF_OPS`); контрпримера нет — знак в YAML не пишется, §10.5 |
|
| ⛔ **выдумывать знак сравнения, если поле не подтверждено** | `>` у `8842` получен случайным совпадением (`1` ∈ `LEAF_OPS`); контрпримера нет — знак в YAML не пишется, §10.5 |
|
||||||
| ⛔ **`_f2` / `_f5` — служебные ключи «на всякий случай»** | `_f2: 1` доехало до YAML у `set_var` объекта `8844` (`type: days_mask`); Alex: **«это че бля»**. Поля 2/5 восстанавливаются из `type`+`days`/`op` — дублировать нельзя, §10.5 круг 26 |
|
| ⛔ **`_f2` / `_f5` — служебные ключи «на всякий случай»** | `_f2: 1` доехало до YAML у `set_var` объекта `8844` (`type: days_mask`); Alex: **«это че бля»**. Поля 2/5 восстанавливаются из `type`+`days`/`op` — дублировать нельзя, §10.5 круг 26 |
|
||||||
| ⛔ **имя параметра объекта 49 при поле 3 = `0`** | поле 4 — код **параметра по типу владельца** (§10.6): `3` = модуляция у электрокотла и целевая температура у контура. Единого словаря нет, имена не выдумывать — ждать подтверждения |
|
| ⛔ **имя параметра объекта 49 при поле 3 = `0` как ЧИСЛО** | поле 4 — код **параметра по типу владельца** (§10.6): `3` = модуляция у электрокотла и целевая температура у контура. ✅ **ЗАКРЫТО:** ключ `param: <имя>` + словарь `PARAM_CODES` по типу владельца — Alex: «словами», «как везде» |
|
||||||
|
| ⛔ **разворачивать `set var` в `set_var`, если цель — не запись 49/50** | `#Z9886=59,'set var1',9885,0,0`, а `9885` = шаг `objstate` → остаётся `descr`+`args`. См. `_set_var_value_body()` (§10.6, питфолл 27) |
|
||||||
|
|
||||||
✅ **РАЗРЕШЕНО:** `trigger:` (выводится из тела — один шаг 46), `if`/`then`/`else`/`flag` — **реальные поля
|
✅ **РАЗРЕШЕНО:** `trigger:` (выводится из тела — один шаг 46), `if`/`then`/`else`/`flag` — **реальные поля
|
||||||
записи 46**, `action` — имя списка у шага с поднятым условием, `set_var: {id, name, object/operator/value
|
записи 46**, `action` — имя списка у шага с поднятым условием, `set_var: {id, name, object/operator/value
|
||||||
| days/days_mask}` — **разобранное значение** (§10.1), `log: <текст>` — плоский (§10.3),
|
| days/days_mask}` — **разобранное значение** (§10.1), `log: <текст>` — плоский (§10.3),
|
||||||
`storeenv: {level, text}` — **разобранный вызов** журнала событий (§10.2),
|
`storeenv: {level, text}` — **разобранный вызов** журнала событий (§10.2),
|
||||||
**`op: '…'` + `time: 'HH:MM'`** у `type: time_condition` — знак подтверждён на 4/4 условиях (§10.5).
|
**`op: '…'` + `time: 'HH:MM'`** у `type: time_condition` — знак подтверждён на 4/4 условиях (§10.5),
|
||||||
|
**`param: <имя>`** у `type: condition` при поле 3 = `0` — словарь по типу владельца (§10.6).
|
||||||
Служебные `_`-ключи — **только** на нестандартных случаях (`_op` при операторе ≠ 1, `_raw_level`, `_raw_op`).
|
Служебные `_`-ключи — **только** на нестандартных случаях (`_op` при операторе ≠ 1, `_raw_level`, `_raw_op`).
|
||||||
Безымянные `_head`/`_body` заменены на `raw` (§10.1 круги 15–16).
|
Безымянные `_head`/`_body` заменены на `raw` (§10.1 круги 15–16).
|
||||||
|
|
||||||
@@ -423,6 +426,11 @@ enabled = not (f5 & 8)
|
|||||||
| 22 | объект 50 разложен как расписание: `days`/`days_mask`/`mask`/`raw`/`_head` | «откуда блядь опять raw выполз» · «какой нахуй days» · «какой mask» | ⛔ §10.5 |
|
| 22 | объект 50 разложен как расписание: `days`/`days_mask`/`mask`/`raw`/`_head` | «откуда блядь опять raw выполз» · «какой нахуй days» · «какой mask» | ⛔ §10.5 |
|
||||||
| 23 | `cmp` вместо `op` (термин выдуман) | «че такое `cmp`… гдето у нас еще есть термин cmp?» | ⛔ → `op`, единообразно с типом 47 |
|
| 23 | `cmp` вместо `op` (термин выдуман) | «че такое `cmp`… гдето у нас еще есть термин cmp?» | ⛔ → `op`, единообразно с типом 47 |
|
||||||
| 24 | временную форму 50 применил ко ВСЕМ объектам 50 (`8844` → `op: <`/`time: 00:00`) | «ты сломал его нахуй» · «ты дебил?!» | ⛔ → две формы по полю 3, §10.5 |
|
| 24 | временную форму 50 применил ко ВСЕМ объектам 50 (`8844` → `op: <`/`time: 00:00`) | «ты сломал его нахуй» · «ты дебил?!» | ⛔ → две формы по полю 3, §10.5 |
|
||||||
|
| 25 | `op` не писал, потому что «совпадение с `LEAF_OPS` не подтверждено» | «в смысле блядь не буду? они не совпадают со знаками в операции сравнения??» | ✅ **Alex дал 4 знака — совпали 4/4** → `op` пишется (§10.5) |
|
||||||
|
| 26 | `_f2: 1` в `set_var` объекта 50 (`type: days_mask`) | «это че бля» | ⛔ служебный ключ «на всякий случай»; поля 2/5 не дублировать (§10.5) |
|
||||||
|
| 27 | доложил «`Values test` нет в конфиге», не перекачав | «ты дебил?» · «да как нахуй нет то блядь?! ты блядь скачал конфиг новый?!» | ⛔ → `curl` заново: 760 строк, `#Z9324` на месте (§10.6, питфоллы 21–22) |
|
||||||
|
| 28 | мусорный `set_var: {…, type: 59, raw: […]}` у шага, чья цель — другой ШАГ (`objstate`) | — | ⛔ → `_set_var_value_body()`: разворачивать только если цель — запись `49`/`50` (§10.6) |
|
||||||
|
| 29 | `param` заведён **словами** по типу владельца | «словами» · «как везде» | ✅ **принято** — `param: pressure`, как `event: upper_threshold` (§10.6) |
|
||||||
|
|
||||||
> 📌 Круги 10–11 разобраны в §10.2, 12–18 — в §10.1, 14 — в §10.3, 19–24 — в §10.4/§10.5.
|
> 📌 Круги 10–11 разобраны в §10.2, 12–18 — в §10.1, 14 — в §10.3, 19–24 — в §10.4/§10.5.
|
||||||
> Итог: `storeenv: {level: info|alert, text}` — два ключа, `id` команды снаружи, словами, без `event`;
|
> Итог: `storeenv: {level: info|alert, text}` — два ключа, `id` команды снаружи, словами, без `event`;
|
||||||
@@ -587,6 +595,10 @@ iconv -f cp1251 -t utf-8 out.txt | tr -d '\r' | grep '^#Z<id>=<тип>'
|
|||||||
| 22 | 🔴 **Не рапортовать «в данных этого нет», не перепроверив источник свежим запросом** | Тот же случай. Утверждение «нет в конфиге» проверяется только на **снимке, снятом ПОСЛЕ** изменения. Порядок: `curl` → `wc -l` (сравнить с прошлым) → `grep`. Если строк стало больше — данные новые. |
|
| 22 | 🔴 **Не рапортовать «в данных этого нет», не перепроверив источник свежим запросом** | Тот же случай. Утверждение «нет в конфиге» проверяется только на **снимке, снятом ПОСЛЕ** изменения. Порядок: `curl` → `wc -l` (сравнить с прошлым) → `grep`. Если строк стало больше — данные новые. |
|
||||||
| 23 | ⚠️ **Служебный `_`-ключ «на всякий случай» = мусор в артефакте** | `_f2: 1` в `set_var` объекта 50 (`type: days_mask`) — я сохранял поля 2/5 «пока семантика не ясна». Alex: «это че бля». Поля, которые восстанавливаются из разобранных ключей, **не дублировать**. Ключ удалён. |
|
| 23 | ⚠️ **Служебный `_`-ключ «на всякий случай» = мусор в артефакте** | `_f2: 1` в `set_var` объекта 50 (`type: days_mask`) — я сохранял поля 2/5 «пока семантика не ясна». Alex: «это че бля». Поля, которые восстанавливаются из разобранных ключей, **не дублировать**. Ключ удалён. |
|
||||||
| 24 | ⚠️ **`sed`/`execute_code` падают на середине проверки — это НЕ «расхождение»** | Round-trip-диф упал на `sed: RE error: illegal byte sequence` (cp1251) и на `No module named 'psutil'` — оба раза вывод выглядел как «есть расхождения». **Сравнивать в Python по байтам** (`open(...,'rb')` + нормализация `\r\n`), не `sed`/`iconv`-пайплайном. |
|
| 24 | ⚠️ **`sed`/`execute_code` падают на середине проверки — это НЕ «расхождение»** | Round-trip-диф упал на `sed: RE error: illegal byte sequence` (cp1251) и на `No module named 'psutil'` — оба раза вывод выглядел как «есть расхождения». **Сравнивать в Python по байтам** (`open(...,'rb')` + нормализация `\r\n`), не `sed`/`iconv`-пайплайном. |
|
||||||
|
| 25 | 🔴 **Сравнивать конфиг по КЛЮЧУ `#Z<id>`, а не построчно** | `yml-to-config.py` пишет в порядке `TYPE_ORDER` → построчный `diff` даёт десятки **ложных** расхождений. Собрать `dict{key: value}` с обеих сторон и сравнить: пропало / лишних / различий. Так на `21-07-03` получено честное **760/760, различий 0**. |
|
||||||
|
| 26 | ⚠️ **Перестановка порядка строк — проверить на БАЗОВОМ коммите, прежде чем объявлять регрессией** | Порядок `#S` и части `#Z` не сохраняется. Прогнал базовый `b75c51f` на том же входе → `661 → 661`, порядок тоже не идентичен. **Унаследованное поведение, не моя правка.** Не чинить в рамках другой задачи. |
|
||||||
|
| 27 | 🔴 **`_set_var_target()` возвращает raw-фоллбэк для ЛЮБОГО неизвестного типа** | Проверка `isinstance(result, dict)` пропускает шаг (`objstate`) как объект значения → в YAML появляется мусорное `set_var: {…, type: 59, raw: […]}`. Заведена отдельная `_set_var_value_body()`: сначала `entry[0] in (49, 50)`, потом вызов. Проверять **тип исходной записи**, а не тип возврата. |
|
||||||
|
| 28 | 🔴 **Правка не завершена → НЕ запускать конвертер в целевой файл** | Пока правка «в процессе» и Alex её не подтвердил, генерировать в `/tmp` и проверять `wc -c`. `>` затирает целевой `.yml` **до** старта питона — падение оставляет **0 байт**. |
|
||||||
|
|
||||||
### 7.2. Код — парсер/энкодер
|
### 7.2. Код — парсер/энкодер
|
||||||
|
|
||||||
@@ -709,14 +721,14 @@ https://lk.zont-online.ru/download/firmwares/H2000_PRO_<HW>__<FW>_<PROFILE>.zip
|
|||||||
|
|
||||||
| Что | Состояние |
|
| Что | Состояние |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Round-trip | 🟢 **ЗЕЛЁНЫЙ** — `698 → 698`, `723 → 723` (снимок `20-40-48`); `661 → 661` (`19-53-21`). На `20-55-22` и **`21-07-03` прогон НЕ сделан** — только регенерация `.yml` (80945 байт на `20-55-22`) |
|
| 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`, порядок тоже не идентичен), **не регрессия** |
|
||||||
| Форма сценария | ✅ закрыта (§5), оба конвертера переведены |
|
| Форма сценария | ✅ закрыта (§5), оба конвертера переведены |
|
||||||
| Тела типа 59 | ✅ **`storeenv` (§10.2), `set_var` с вложенным телом цели (§10.1, круги 12–21), `log` плоский (§10.3) — готовы, не закоммичены** · `objcmd`/`expr`/`objstate` — `descr`+`args` |
|
| Тела типа 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 = `1` | ✅ **форма закрыта** — `type: condition` + `object` + `event`; поле 3 в YAML не пишется; 18 комбинаций из **Conditions Test** (§10.4) |
|
||||||
| Условия 49 при поле 3 = `0` | ⚠️ **семантика раскрыта, ключ не заведён** — поле 4 = **код параметра по типу владельца** (§10.6, **Values test**, 15 значений). В YAML пока `value: <число>`. Имена параметров не выдумывать — ждать подтверждения словаря |
|
| Условия 49 при поле 3 = `0` | ✅ **ЗАКРЫТО (§10.6)** — `param: <имя>` по **типу владельца** (`PARAM_CODES`), 15 `param:` в YAML снимка `21-07-03`. Цель-шаг (`9885`) — не объект значения, остаётся `descr`+`args` |
|
||||||
| Объект 50 (`set_var`) | ✅ **ЗАКРЫТО (§10.5)** — **поле 2** = форма (`0` время / `1` дни), **поле 3** = знак сравнения, поле 4 = время, поле 5 = маска. Знак пишется как **`op`** — коды `0 <` `1 >` `2 =` `3 <=` `4 >=` (совпали с типом 47 на 4/4 условиях) |
|
| Объект 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) |
|
| Имя переменной | ✅ `set var1` **и** `set varname` — регексп `^set (var\w*\|\w+)$` (§10.4) |
|
||||||
| `field` / `mode` / `cmp` / `_f2` / `_f5` | ⛔ **удалены** — 0 вхождений в коде и файлах (§10.1 круги 19–21, круг 26) |
|
| `field` / `mode` / `cmp` / `_f2` / `_f5` / `_head` | ⛔ **удалены** — 0 вхождений в коде и файлах (§10.1 круги 19–21, круг 26) |
|
||||||
| Потери объектов | ✅ **0** — было 5 (`8472`, `8821`, `8849`, `8851`, `8855`) |
|
| Потери объектов | ✅ **0** — было 5 (`8472`, `8821`, `8849`, `8851`, `8855`) |
|
||||||
| `unresolved: true` | ⚠️ висит у `#Z8860`, `#Z8864`, `#Z8601` — объекта в конфиге нет (§10, §10.4) |
|
| `unresolved: true` | ⚠️ висит у `#Z8860`, `#Z8864`, `#Z8601` — объекта в конфиге нет (§10, §10.4) |
|
||||||
| Коммит | `b75c51f` — «Drop stale YAML snapshots in old scenario shape» ← **текущий HEAD** |
|
| Коммит | `b75c51f` — «Drop stale YAML snapshots in old scenario shape» ← **текущий HEAD** |
|
||||||
@@ -786,9 +798,9 @@ git commit -m "Scenario YAML shape: bare action ids, no anchors"
|
|||||||
|
|
||||||
⚠️ **Остаётся `raw` / `unresolved`:**
|
⚠️ **Остаётся `raw` / `unresolved`:**
|
||||||
|
|
||||||
1. **Тип 50** — ✅ **РАЗОБРАН** (§10.5, круг 25): поле 2 = признак формы (`0` время / `1` дни),
|
1. ~~**Тип 50**~~ — ✅ **ЗАКРЫТО** (§10.5, круги 25–26): поле 2 = признак формы (`0` время / `1` дни),
|
||||||
поле 3 = **знак сравнения**, поле 4 = время, поле 5 = маска дней. Осталось **одно**: подписи
|
поле 3 = **знак сравнения** (коды `0 <` `1 >` `2 =` `3 <=` `4 >=` — **подтверждены** четырьмя
|
||||||
4 условий Alex (`9248`/`9250`/`9252`/`9254`) из UI → тогда `op` пишется.
|
условиями Alex), поле 4 = время, поле 5 = маска дней. `op` и `time` пишутся в YAML.
|
||||||
2. ~~**Несущие объекты `set var1`** — тело цели в `scenario_orphans`~~ → ✅ **ЗАКРЫТО**, §10.1 круг 13:
|
2. ~~**Несущие объекты `set var1`** — тело цели в `scenario_orphans`~~ → ✅ **ЗАКРЫТО**, §10.1 круг 13:
|
||||||
тело цели разворачивается **внутрь** `set_var`. **Вариант B выбран Alex.**
|
тело цели разворачивается **внутрь** `set_var`. **Вариант B выбран Alex.**
|
||||||
3. **`unresolved: true`** у `#Z8860`, `#Z8864`, `#Z8601` — этих объектов нет в конфиге.
|
3. **`unresolved: true`** у `#Z8860`, `#Z8864`, `#Z8601` — этих объектов нет в конфиге.
|
||||||
@@ -798,11 +810,15 @@ git commit -m "Scenario YAML shape: bare action ids, no anchors"
|
|||||||
4. **Тип 3** (SMS `8195`) — тело лежит якорем в `sms_notifications`, не раскрыто.
|
4. **Тип 3** (SMS `8195`) — тело лежит якорем в `sms_notifications`, не раскрыто.
|
||||||
5. **Тип 11 внутри `steps` другого сценария** — ссылка на сценарий голым `id` (`#Z8456` держит `11109`).
|
5. **Тип 11 внутри `steps` другого сценария** — ссылка на сценарий голым `id` (`#Z8456` держит `11109`).
|
||||||
6. **Старые снапшоты `14-16-35.yml` / `16-02-18.yml`** — по 2 вхождения старого `type:`, не перегенерированы.
|
6. **Старые снапшоты `14-16-35.yml` / `16-02-18.yml`** — по 2 вхождения старого `type:`, не перегенерированы.
|
||||||
7. 🔴 **Семантика поля 3 объекта 49 — НЕ ПОДТВЕРЖДЕНА, но поле ПОИМЕНОВАНО.** По данным поле принимает
|
7. ⚠️ **Семантика поля 3 объекта 49 — РАСКРЫТА, но ключ не заведён.** `0` = **значение**
|
||||||
только `0` (5×) и `1` (71×); поле 4 — значения и ссылки на объекты. Знак из `LEAF_OPS` типа 47
|
(число или параметр), `1` = **событие**. Подтверждено двумя разведками:
|
||||||
был подставлен **по аналогии** и **убран** (§10.1 круг 18). Alex: **«field его обзови»** →
|
§10.4 (**Conditions Test**, 18 событий → все поле 3 = `1`) и §10.6 (**Values test**,
|
||||||
ключ `field`, значение — сырой код как есть. Слова из UI всё ещё нужны, чтобы расшифровать `0`/`1`.
|
15 значений → все поле 3 = `0`). Различие восстанавливается по составу ключей
|
||||||
📊 **Разведка сделана:** §10.4 (сценарий **Conditions Test**, снимок `20-40-48`) — все 18 комбинаций.
|
(есть `event` → `1`, есть `value` → `0`). Отдельного ключа (`field`, `mode`) нет и не будет —
|
||||||
|
Alex прошёл оба варианта и оба отверг.
|
||||||
|
🔴 **Осталось:** при поле 3 = `0` поле 4 — **код параметра по типу владельца** (§10.6).
|
||||||
|
В YAML пока `value: <число>` без имени. **Словарь имён не заводить**, пока Alex не подтвердит
|
||||||
|
его для всех типов: один код даёт разные имена у разных типов (`3` = модуляция / целевая температура).
|
||||||
8. **Семантика шага `8860`** (и `8864`, `8601`) — см. п. 3.
|
8. **Семантика шага `8860`** (и `8864`, `8601`) — см. п. 3.
|
||||||
🔴 **Гипотеза «завершить сценарий» опровергнута данными** (§10.4): Alex добавил в сценарий
|
🔴 **Гипотеза «завершить сценарий» опровергнута данными** (§10.4): Alex добавил в сценарий
|
||||||
**Conditions Test** все возможные conditions — и `8860` там **не появился**. В `then`-ветках
|
**Conditions Test** все возможные conditions — и `8860` там **не появился**. В `then`-ветках
|
||||||
@@ -1437,15 +1453,45 @@ cat /tmp/zfresh.txt | iconv -f cp1251 -t utf-8 | grep -nE "=11,'" | grep -viE "
|
|||||||
> ⚠️ **Старое толкование поля 4 как «кода события» (§10.4) верно ТОЛЬКО при поле 3 = `1`.**
|
> ⚠️ **Старое толкование поля 4 как «кода события» (§10.4) верно ТОЛЬКО при поле 3 = `1`.**
|
||||||
> При поле 3 = `0` поле 4 — код параметра. Это **два разных словаря в одном поле**.
|
> При поле 3 = `0` поле 4 — код параметра. Это **два разных словаря в одном поле**.
|
||||||
|
|
||||||
#### ❌ Что ещё НЕ закрыто в тип 49
|
#### ✅ ЗАКРЫТО — ключ `param`, словарь по типу владельца
|
||||||
|
|
||||||
- `#Z9229=49,4099,0,8` — по Values test `8` у электрокотла = «температура ГВС». Сходится.
|
Alex: «словами» · «как везде». Значит — как у событий (`event: upper_threshold`), имя латиницей.
|
||||||
- ⚠️ **Ключ `object` содержит id владельца, а `value` — код параметра. Пока в YAML пишется
|
|
||||||
`value: <число>` без имени параметра.** Имя параметра требует словаря по типу владельца —
|
```yaml
|
||||||
Alex его ещё не подтверждал для **всех** типов. **Не выдумывать имена** (питфолл: «YAML = строка
|
- id: 9620 # #Z9620=59,'set var1',9619,0,0
|
||||||
конфига + вывод из тела»); словарь заводить только после явного подтверждения.
|
set_var:
|
||||||
- Шаг 15 (`9885`) — не объект 49, а скрипт `objstate 9838 0 0` (`9838` = «13/9: Рад. ванная 2эт»,
|
id: 9619 # #Z9619=49,4099,0,4
|
||||||
тип 10). В UI показывает «Значение элемента управления» — то есть `objstate` тоже читает значение.
|
name: var1
|
||||||
|
type: condition
|
||||||
|
object: 4099
|
||||||
|
param: pressure # 🆕 поле 4 = 4 → 'давление'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Словарь `PARAM_CODES` (модульный, `config-to-yml.py` ‖ `_PARAM_CODES` в `yml-to-config.py`):**
|
||||||
|
|
||||||
|
| Тип владельца | Объекты | код → имя |
|
||||||
|
|---|---|---|
|
||||||
|
| **6** адаптер котла | `4098`, `4099` | `3` modulation · `4` pressure · `5` state · `6` error_code · `7` coolant_temp · `8` dhw_temp · `9` return_temp |
|
||||||
|
| **16** контур | `8560`, `8669`, `10152` | `2` error · `3` target_temp · `4` current_temp · `5` calculated_heat · `6` heat_request |
|
||||||
|
| **1** виртуальный датчик | `8911`, `8254` | `4` input_value |
|
||||||
|
| **0** дискретный датчик | `9864` | `2` error · `4` input_value |
|
||||||
|
| **27** датчик температуры | — | `4` input_value |
|
||||||
|
|
||||||
|
Развилка в парсере — по полю 3: `1` → `event` (по типу владельца), `0` → `param` (по типу владельца),
|
||||||
|
иначе → `value` (число, без догадок). Энкодер: `param` → обратный словарь `{v: k}`.
|
||||||
|
|
||||||
|
**Проверка:** 15 `param:` в YAML (= 15 присваиваний Values test) · круг `21-07-03` → **760/760, различий 0**.
|
||||||
|
|
||||||
|
> ⚠️ **Шаг 15 (`9885`) — цель НЕ объект значения.** `#Z9886=59,'set var1',9885,0,0`, но
|
||||||
|
> `#Z9885=59,'objstate 9838 0 0',0,0,0` — это **другой шаг**, а не объект 49/50. Разворачивать нечего:
|
||||||
|
> в YAML остаётся `descr: set var1` + `args: [9885, 0, 0]`. В UI показывает «Значение элемента
|
||||||
|
> управления» — `objstate` тоже читает значение, но тела-цели у него нет.
|
||||||
|
> 🔴 **Проверка типа цели — `_set_var_value_body()`, а не `isinstance(dict)`:** `_set_var_target()`
|
||||||
|
> возвращает raw-фоллбэк-словарь для любого неизвестного типа, поэтому проверка «вернулся dict»
|
||||||
|
> пропускает шаг как объект значения. Проверять `entry[0] in (49, 50)`.
|
||||||
|
|
||||||
|
> 📌 **Сравнение снимков:** `20-55-22` = 729 строк / 71 сценарий · `21-07-03` = 760 строк /
|
||||||
|
> **72 сценария** (+`Values test`). Разница 31 строка = 15 шагов + 15 целей 49 + 1 сценарий.
|
||||||
|
|
||||||
#### 🔴 Питфолл: «я добавил в UI» → ПЕРЕКАЧАТЬ, а не искать в старом снимке
|
#### 🔴 Питфолл: «я добавил в UI» → ПЕРЕКАЧАТЬ, а не искать в старом снимке
|
||||||
|
|
||||||
@@ -1466,8 +1512,8 @@ cat /tmp/zfresh.txt | iconv -f cp1251 -t utf-8 | grep -nE "=11,'" | grep -viE "
|
|||||||
|
|
||||||
| Файл | Статус |
|
| Файл | Статус |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `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) — **в рабочей копии, не закоммичено** |
|
| `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) — **не закоммичено** |
|
| `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) — **не закоммичено** |
|
||||||
| `test_roundtrip.py` | ✅ без изменений (в коммите `199f2b1`) |
|
| `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_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-55-22.{txt,yml}` | предыдущий (729 строк, 71 сценарий) — 4 time-условия Alex (`#Z9248/9250/9252/9254`), знак локализован в поле 3. **Values test в нём НЕТ** |
|
||||||
|
|||||||
Reference in New Issue
Block a user