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

This commit is contained in:
Alexey Martemyanov
2026-09-17 20:53:38 +06:00
parent f11cbb4d1e
commit 041446b32c
+85 -11
View File
@@ -3,7 +3,7 @@ title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
namespace: personal
type: how-to
created: '2026-09-17'
updated: '2026-09-17e'
updated: '2026-09-17f'
tags:
- personal
- zont
@@ -365,8 +365,8 @@ enabled = not (f5 & 8)
| `level: I` / `level: A` (буква) | уровень пишется **словом** `info`/`alert` (§10.2 круг 1) |
| `id` внутри `storeenv` | id команды уже снаружи, в ключе `id` объекта (§10.2 круг 3б) |
| `set_var_name` / имя целевого объекта вместо id | **подстановка имени из другого объекта**у ссылки только `id` (§5.10, §10.1) |
| `field` / `value` в теле объекта 49 | сырые имена без семантики → `mode` + `event`, §10.4 |
| `value: <код>` при `mode: event` | код события, а не имя; список Alex дал готовые имена, §10.4 |
| `field` / `mode` — отдельный ключ для поля 3 | поле 3 восстанавливается по составу ключей (`event``1`, `value``0`); Alex отверг оба имени, §10.1 круги 19–21 |
| `value: <код>` при событии | код события, а не имя; список Alex дал готовые имена, §10.4 |
| `set_var: {n: 1, target: 8829}` | «номер» переменной я выдумал; имя и id — отдельные ключи `name`/`id`, §10.1 круг 12 |
| `set_var: {var1: 8829}` (голый id) | значение обязано быть **развёрнуто** телом цели, а не отослано в orphans, §10.1 круг 13 |
| `set_var: {var1: {id, …}}` — имя как ключ-контейнер | «var1 это имя переменной блядь» → `id`+`name` внутри `set_var`, §10.1 круг 15 |
@@ -410,7 +410,10 @@ enabled = not (f5 & 8)
| 15 | `set_var: {var1: {id, …}}` — имя переменной как ключ-контейнер | «var1 это имя переменной блядь» → `id`+`name` внутри, тело плоско (§10.1) |
| 16 | `mask: 123` + `_head: [1,0,0]` у маски дней | «нормально блядь дни разверни и че за head блядь… как в schedule сделано, паттерн уже есть» → `days: [mon,…]` + `raw` (§10.1) |
| 17 | `object_name: 'Контур газ котла'` рядом с `object: 8560` | **«откуда там блядь имя объекта?!»** → подстановка имени из чужого объекта запрещена, `object` = только id (§5.7) |
| 18 | `type: condition` / `type: days_mask` в теле цели | «че блядь за `type: condition`?» → тип не пишем, ветвимся по составу ключей (§10.1) |
| 18 | `type: condition` / `type: days_mask` в теле цели | «че блядь за `type: condition`?» → тип убран, ветвление по составу ключей | ✅ вернулся в круге 21 |
| 19 | `field: 1` (сырое имя поля 3) | «че блядь за `field/value`?» | ⛔ |
| 20 | `mode: compare\|event` — переименовал заодно с ответом про события | «какой нахуй `field`!» · «еб твою мать» | ⛔ |
| 21 | `type: condition` + `event`/`value`, поле 3 НЕ пишется | **«`type: condition` блядь!»** | ✅ **принято** |
> 📌 Круги 10–11 разобраны в §10.2, 1218 — в §10.1, 14 — в §10.3. Итог: `storeenv: {level: info|alert, text}`
> — два ключа, `id` команды снаружи, словами, без `event`; `set_var: {id, name, object/operator/value
@@ -566,6 +569,10 @@ iconv -f cp1251 -t utf-8 out.txt | tr -d '\r' | grep '^#Z<id>=<тип>'
| 14 | ⚠️ Проверочные скрипты на кириллице падают на `cut`/`awk` | Читать в Python (`open(...,'rb').read().decode('cp1251')`), не резать шеллом |
| 15 | 🔴 **Счёт ключей одним `grep '^ key:'` врёт** | Отступ разный (вложенный шаг +2 пробела), а часть объектов вообще в других секциях. Печатать **какие id** не попали, прежде чем выводить «N из M» |
| 16 | 🔴 **`patch` по относительному пути попадает в проект, не в vault** | `patch(path='personal/...')` резолвится от CWD (`~/Automation/...`) → «file not found». Для `obsidian/`**абсолютный** путь `/Users/admin/obsidian/personal/...` |
| 17 | 🔴 **Ключ «за компанию»: после «да» на ОДИН вопрос не переименовывать второй** | Alex сказал «да» про имена событий — я заодно переименовал `field``mode` → «**еб твою мать**». Потом три круга отката (`field` → без ключа → `type: condition`), Alex: «я тебе нахуя бл*ть чето пишу!». **Один ответ = одна правка.** |
| 18 | 🔴 **Alex называет ГОТОВОЕ имя, а не направление** | «`field` его обзови» / «`type: condition` блядь!» — это финальный ответ, а не подсказка для дальнейшего изобретения. Принимать буквально, не искать «улучшение». Он отвергает промежуточные формы (круги 19–21: `field`/`value``field``type: condition`). |
| 19 | 🔴 **Не «узнавать» смысл поля по аналогии с другим типом** | Объект 50 дважды выведен неверно: как расписание сценария (`days`) и как маска. Alex: «какой нахуй days! я тебе блядь недоступно написал?!» → истина «current time > 13:23». **Сначала СЛОВА из UI, потом модель** (питфолл 32). Проверять множество значений поля по ВСЕМУ конфигу (`Counter`), не строить аналогию. |
| 20 | 🔴 **Выдуманный термин = выдуманная модель** | Alex: «че такое `cmp`… гдето у нас еще есть термин "cmp"?» — термина в проекте не было. Имена ключей брать из **уже принятого словаря** (`op` у типа 47), не изобретать синонимы. |
### 7.2. Код — парсер/энкодер
@@ -590,6 +597,8 @@ iconv -f cp1251 -t utf-8 out.txt | tr -d '\r' | grep '^#Z<id>=<тип>'
| 28a | 🔴 **10 разных `set var1` выглядят в YAML одинаково** | `descr` у всех один, различие — в `set_var` (+ `args[0]`). Не «оптимизировать» их обратно в одну запись. §5.10, §10.1 |
| 28b | ⚠️ **Несущие объекты 49/50 живут в `scenario_orphans`, не в теле шага** | `#Z8830``#Z8829=49,8450,1,0`. Sweep (§7.4) докидывает их только как raw-склад. Раскрытие = отдельное решение (вариант B §10.1), не «попутная починка» |
| 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) |
### 7.3. Проверка гипотез
@@ -686,11 +695,14 @@ https://lk.zont-online.ru/download/firmwares/H2000_PRO_<HW>__<FW>_<PROFILE>.zip
| Что | Состояние |
|---|---|
| Round-trip | 🟢 **ЗЕЛЁНЫЙ, байт-в-байт**`661 → 661`, `686 → 686`, `diff` = 0 (проверен на `19-53-21`) |
| Round-trip | 🟢 **ЗЕЛЁНЫЙ, байт-в-байт**`698 → 698`, `723 → 723` на снимке `20-40-48` (было `661 → 661` на `19-53-21`) |
| Форма сценария | ✅ закрыта (§5), оба конвертера переведены |
| Тела типа 59 | ✅ **`storeenv` (§10.2), `set_var` с вложенным телом цели (§10.1, круги 12–18), `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 (`set_var`) | ✅ **форма закрыта**`type: condition` + `object` + `event`\|`value`; поле 3 в YAML не пишется; 18 комбинаций из **Conditions Test** (§10.4) |
| Имя переменной | ✅ `set var1` **и** `set varname` — регексп `^set (var\w*|\w+)$` (§10.4) |
| `field` / `mode` | ⛔ **удалены** — 0 вхождений в файле (§10.1 круги 1921) |
| Потери объектов | ✅ **0** — было 5 (`8472`, `8821`, `8849`, `8851`, `8855`) |
| `unresolved: true` | ⚠️ висит у `#Z8860`, `#Z8864`, `#Z8601` — объекта в конфиге нет (§10) |
| `unresolved: true` | ⚠️ висит у `#Z8860`, `#Z8864`, `#Z8601` — объекта в конфиге нет (§10, §10.4) |
| Коммит | `b75c51f` — «Drop stale YAML snapshots in old scenario shape» ← **текущий HEAD** |
| Откачено | `87e315c` («set-var target into `set_var`») — снят `git reset --soft` без разрешения Alex (§10.1) |
| Ранее | `823fabd` — форма сценария §5; `1cc010a`**содержит сломанные версии**; закрыт §9.1 |
@@ -869,6 +881,22 @@ curl -s --max-time 20 http://192.168.0.50/config.txt -o "zont_config/config_loca
> ❌ **`8860` в Conditions Test НЕ появился** — опровергает версию «8860 = один из типовых conditions».
#### 🔴 Имя переменной — не только цифры (`set varname`)
Регексп тела `set_var` был `^set var(\d+)$` и **пропускал `set varname`** — такие шаги уходили
в `descr`+`args`. Alex: «че за хуйня то блядь опять?! … `descr: set varname`».
```python
# ✅ принимает и 'set var1', и 'set varname'
m_sv = re.match(r'^set (var\w*|\w+)\s*$', code)
```
Имя переменной в конфиге — **произвольный идентификатор**, в UI задаётся руками (`varname`, `var1`).
Не привязываться к «var + цифры».
> ⚠️ **Исключение:** `#Z8472=59,'set var1',0,0,0` — цель `0`. Такие записи остаются `descr`+`args`
> (разворачивать нечего, `set_var` без цели не собирается). Это **не** потеря: round-trip зелёный.
#### Энкодер: тип целевого объекта ищется по секциям (`_body_type`)
Секции YAML не несут номер типа объекта, поэтому энкодеру нужна карта `id → тип`:
@@ -974,6 +1002,9 @@ Alex видел `field: 1` **после** переименования в `mode`
raw: [1, 0, 0] # ← поля 2..4 строки 50
```
> ⛔ **ОТМЕНЕНО (круг 22): объект 50 — это НЕ «маска дней».** Разбор — §10.5.
> Alex: «там: current time ">" 13:23» и «какой нахуй days». Форма выше **устарела**.
🔴 **`type` в `set_var` НЕТ** (круг 17). Тело различается по составу ключей: `days`/`days_mask`
→ объект 50; `object` + `mode` → объект 49. Энкодер ветвится так же
(`tgt_type == 'days_mask'` / `('object' in sv)`). Неизвестное тело — `raw`.
@@ -1209,17 +1240,60 @@ iconv -f cp1251 -t utf-8 out.txt | tr -d '\r' | grep '^#Z8830=59'
> (вложенный шаг), а два — в `scenario_orphans`. Реальный счёт был 3 из 5, не 2. Считать без
> привязки к отступу и печатать **какие именно id** не попали, прежде чем делать вывод.
### 10.5. ✅ Объект 50 — условие по ВРЕМЕНИ, не «маска дней» (круг 22, 2026-09-17)
**Отправная точка:** `#Z8842=50,0,1,3351,0` выводился как `type: days_mask` + `days: []` +
`days_mask: 0` + `raw: [0, 1, 3351]`. Alex: **«откуда блядь опять raw выполз блядь»**, затем
**«там: current time ">" 13:23»**, затем **«какой нахуй days! я тебе блядь недоступно написал?!»**.
**Итог:** объект 50 — это условие `current time <op> HH:MM`. `3351 = 13:23` — это **время**,
тот же формат, что у расписания сценария (`time = (час << 8) | мин`, §5.6; ср. `#Z8597` = `3354` = 13:22).
```yaml
- id: 8843 # #Z8843=59,'set var1',8842,0,0
set_var:
id: 8842 # #Z8842=50,0,1,3351,0
name: var1
type: time_condition
op: '>' # поле 3 = 1 → '>' (LEAF_OPS)
time: '13:23' # поле 4 = 3351
```
| Поле 50 | Что | В YAML |
|---|---|---|
| 2 | неизвестно (`0` у `8842`, `1` у `8844`/`8548`) | `_f2`**только если ≠ 0** |
| 3 | **оператор** сравнения (`LEAF_OPS`) | `op: '>'` |
| 4 | **время**, `(час<<8)\|мин` | `time: 'HH:MM'` |
| 5 | неизвестно (`0` / `123` / `109`) | `_f5`**только если ≠ 0** |
> ⛔ **Запрещено (круг 22):** `days`, `days_mask`, `mask`, `raw` в теле объекта 50 — Alex отверг
> все четыре подряд («какой нахуй days», «какой mask», «откуда raw выполз»). Поля 2/5 —
> служебные `_f2`/`_f5` и **не пишутся, когда нули**.
> 🔴 **Ключ знака — `op`, не `cmp`.** Alex: «че такое cmp… гдето у нас еще есть термин "cmp"?» —
> термина в проекте не было, я его выдумал. У типа 47 (§5.2) тот же смысл называется `op`,
> берём единообразно. `LEAF_OPS` = `{0:'<',1:'>',2:'=',3:'<=',4:'>='}`.
**Проверка §10.5:** round-trip `20-40-48` → ✅ `698 → 698`, `723 → 723`; `cmp` в файле — **0 вхождений**.
Тест на подмену: `op: >``<=`, `time: 13:23``07:45` даёт `#Z8842=50,0,3,1837,0`
(`3` = `<=`, `1837 = 7*256+45`).
> ⚠️ **Паттерн ошибки (круги 15 → 22):** я дважды «узнавал» смысл объекта 50 по аналогии —
> сначала как расписание сценария (`days`), потом как маску. Alex оба раза бил: **сначала СЛОВА
> из UI, потом модель** (питфолл 32). Для объекта 50 слова даны: «current time > 13:23».
---
## 11. Файлы проекта
| Файл | Статус |
|---|---|
| `config-to-yml.py` | ✅ форма §5: `trigger:` подъём через `pop('if')`, шаг = `{id, [flag], action}` или `{id, [flag], if, then, [else]}` · ✅ тип 59: `set_var` (§10.1) и `storeenv` (§10.2) — **в рабочей копии, не закоммичено** |
| `yml-to-config.py` | ✅ `emit_step` читает `then`/`action`/`else`/`flag`; `f5` из `trigger:`/`interval_ms` + бит 8 · ✅ `set_var`/`storeenv` собраны в **`_script_body()`** — одна функция на обе точки входа (`emit_action` + инлайн `emit_step`) — **не закоммичено** |
| `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), `type: time_condition` (§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)**не закоммичено** |
| `test_roundtrip.py` | ✅ без изменений (в коммите `199f2b1`) |
| `zont_config/config_local_2026-09-17_19-53-21.{txt,yml}` | 🆕 **актуальный** снимок с прибора (686 строк, 661 `#Z`) — в индексе, **не закоммичен**; YAML перегенерирован (форма §10.2, 4× `storeenv`) |
| `zont_config/config_local_2026-09-17_18-43-24.{txt,yml}` | предыдущий снимок, форма §5 |
| `zont_config/config_local_2026-09-17_20-40-48.{txt,yml}` | 🆕 **АКТУАЛЬНЫЙ** снимок (723 строки, 698 `#Z`) — содержит сценарий **`9144` Conditions Test** (18 шагов, все комбинации условий). Круг ✅ `698 → 698`. **Не закоммичен** |
| `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`) |
| `zont_config/archive/` | `-2`/`-3`/`-4` — закоммичены (`7ae0e32`) |