[2026-09-17] eagle: personal/projects/zont-config-compiler.md

This commit is contained in:
Alexey Martemyanov
2026-09-17 21:19:04 +06:00
parent 146d3eae8c
commit 3c85f91872
+86 -23
View File
@@ -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<id>=<тип>'
| 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_<HW>__<FW>_<PROFILE>.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, круги 1221), `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_<HW>__<FW>_<PROFILE>.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`) |