[2026-09-17] eagle: family/how-to/zont-config-compiler.md family/tech/zont-scenario-logic-11109.md work/projects/cpm-m3-telemetry-privacy-triage.md

This commit is contained in:
Alexey Martemyanov
2026-09-17 15:33:05 +06:00
parent d0dfc989e6
commit 4eae38bab8
3 changed files with 214 additions and 172 deletions
+121 -95
View File
@@ -21,7 +21,7 @@ tags:
- homeautomation
title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
type: how-to
updated: 2026-09-17i
updated: 2026-09-17j
---
# ⚙️ ZONT Config Compiler — конвертеры `.txt ⇄ .yml`
@@ -146,12 +146,11 @@ updated: 2026-09-17i
### Как это выглядит в YAML
> ⚠️ **ТЕКУЩАЯ форма (коммит `199f2b1`).** Переделывается по **§5c** — там одна вложенная форма
> `blocks[].if/then` для всех сценариев вместо плоского дубля и отдельных секций. Ниже — как есть
> сейчас, чтобы читать работающий код; целевая форма — в §5c.
> **НОВАЯ форма (2026-09-17, §5c).** Все сценарии — единая вложенная структура `blocks[].if/then/else`
> плюс блок `trigger:`. Плоские `when`/`then` и секции `steps`/`delays`/`scenario_conditions` убраны.
> **Форма `11109` ниже — устаревшая**, оставлена для истории; актуальная — §5c.
Одношаговые сценарии (64 из 65) сохраняют плоский вид `when`/`then` — старые YAML не ломаются.
Многошаговые получают список `steps`, а хвостовые задержки — `extra_links`:
**Устаревшая форма (коммит `199f2b1`)** плоский `when`/`then` для 1-шаговых + `steps` + `extra_links`:
```yaml
scenarios:
@@ -384,20 +383,12 @@ diff A.txt B.txt # пусто = round-trip чистый
---
## 5c. 🔄 Переработка структуры YAML сценариев — 🟢 СОГЛАСОВАНО, парсер дописывается (2026-09-17)
## 5c. 🔄 Переработка структуры YAML сценариев — СДЕЛАНО 2026-09-17
> ✅ **СТАТУС (обновлено 2026-09-17, вечер): Alex дал «дописывай парсер».** Семантика типов
> 47/48/50/59 **раскрыта** (ответы Alex — [[family/tech/zont-scenario-logic-11109]] §8.7),
> таблица операторов и логики подтверждена фактами конфига. Работа по §5c разрешена.
>
> ⚠️ **Но остались 8 непрозрачных элементов** конкретно в сценарии `8456`
> (`#8458`, `#8463`, `#8465`, `#8467`, `#8486`, `#8488`, `#9500`, `#8457`/`#8470`).
> Alex спросил «всё ли кристально прозрачно?» — ответ: **нет**. Вопросы заданы, ответа ещё нет
> (полный список — [[family/tech/zont-scenario-logic-11109]] §8.10).
>
> 📌 **Решение по стратегии парсера:** сохранять байт-в-байт всё, что не понято (скрипты `59`
> как сырые строки, суффиксы `;#a`/`;#h`/`;#p`, отсутствующие id — как есть), без «умного» перевода.
> Тогда round-trip чистый, а непонятное не портится.
> ✅ **СТАТУС (2026-09-17, ночь): парсер и энкодер дописаны, round-trip чистый на 6 конфигах.**
> Семантика типов 47/48/50/59 раскрыта (ответы Alex — [[family/tech/zont-scenario-logic-11109]] §8.7),
> операторы и логика подтверждены фактами конфига. Код реализован.
> **Не закоммичено** — ждёт команды Alex.
**Задача Alex (исходная):** «переписать блок парсинга/сборки сценариев чтобы он составлял синтаксис как у Home Assistant automations вместо текущей разбросанной структуры. с опциональными айдишниками у операторов».
@@ -411,58 +402,96 @@ Alex отклонил:
**Правило:** в ZONT **нет триггеров** — роль триггера играет само изменение реле, а сценарий читается
как вложенные инструкции «если … то …». Структура YAML должна повторять ZONT, а не HA.
### Утверждённая форма (предложена, ждёт финального ОК по двум вопросам)
### ✅ Реализованная форма
```yaml
scenarios:
- id: 11109
name: 'Передернуть Автомат Котельной'
enabled: false
- id: 8456
name: Простой тестовый сценарий
trigger: {type: schedule} # manual | trigger | schedule | interval
_raw_trigger_kind: 8 # исходное поле 5
blocks:
- id: 11827 # шаг (46)
- id: 8506 # шаг (46)
if:
id: 11823 # условие (49), id опционален
relay: 11190
operator: equals # equals | not_equals
value: 0
id: 8496
group: and # and | or | not
children:
- {id: 8493, op: '<', left: 8492, value: 3}
- {id: 8495, op: '>=', left: 8472, right: 8494}
then:
- {id: 11191, action: relay_on, relay: 11190}
- {id: 11030, action: relay_off, relay: 11028}
- {id: 11824, action: wait, ms: 20000}
- {id: 11029, action: relay_on, relay: 11028}
- {id: 11825, action: wait, ms: 60000}
- {id: 11192, action: relay_off, relay: 11190}
- {id: 11826, action: wait, ms: 0}
tail: # хвостовые задержки из поля 2
- {id: 11828, action: wait, ms: 0}
- id: 8505
kind: 1
if: {id: 8501, group: or, children: [...]}
then:
- {id: 8502, script: 'puts "then-text"', args: [0, 0, 0]}
else:
- {id: 8504, script: 'storeev A "alert"', args: [0, 0, 0]}
extra_links: [8457, 8458, ..., 8507] # прочие ссылки поля 2, в исходном порядке
_raw_links: [8457, ..., 8506, 8507] # точный порядок поля 2 (для байт-tочности)
```
**Что меняется по сравнению с текущим YAML:**
**Маппинг типов на YAML:**
| Сейчас (разбросано) | Станет |
|---|---|
| `scenarios[].when` / `.then` — плоский дубль для 1-шаговых | убрано: **одна форма** `blocks[].if/.then` для всех 65 |
| `scenarios[].steps[]` + отдельные `delays` / `scenario_conditions` / `scenario_steps` | одна вложенная структура `blocks` |
| `wait` лежит отдельной секцией верхнего уровня | `wait`**внутри** `then`, по месту срабатывания |
| `extra_links: [11828]` — голые id | `tail:` с блоками `wait` |
| Тип | В YAML | Пример |
|---|---|---|
| `11` | `trigger: {type, …}` + `blocks[]` | `trigger: {type: schedule, days: [mon,…], time: '13:26'}` |
| `46` | `blocks[].{if, then, else}` | `{id, kind?, if, then: […], else?: […]}` |
| `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 "then-text"'}` |
| `50`, неизвестные | `raw: […]` | `{id: 8548, raw: [50, 1, 0, 0, 109]}` |
**Имена ключей и нулевые паузы — решено, не переспрашивать:**
- Ключи: **`blocks` / `if` / `then`** — ближе к формулировке Alex «вложенные инструкции если … то …».
- Нулевые паузы (`ms: 0`) — **остаются как `wait`**. Честно отражают конфиг, нужны для round-trip.
- `id` у операторов — **опциональные**: пишутся, когда есть в конфиге; отсутствуют, когда нет.
**Операторы type 47 (порядок UI `<, >, =, <=, >=`):** `0`=`<`, `1`=`>`, `2`=`=`, `3`=`<=`, `4`=`>=`.
**Логика type 48:** `0`=`and`, `1`=`or`, `2`=`not`.
**План работ:**
1. `config-to-yml.py` — блок type 11: генерировать `blocks[].if/then`; `wait` (45) встраивать в `then`; `tail` для хвостовых
2. `yml-to-config.py` — обратный разбор `blocks`; **поддержать старые формы** (`when`/`then`, `steps`) для совместимости
3. `test_roundtrip.py` — все 4 конфига должны остаться чистыми
4. Доки — обновить §2.1 этой заметки
**Триггер (поле 5 типа 11):**
**Обязано сохраниться байт-в-байт (риск round-trip):**
| Поле 5 | `trigger.type` | Доп. поля |
|---|---|---|
| `0`/`1` | `manual` | `enabled: bool` |
| `8` | `schedule` | `days[]`, `days_mask`, `time`, `time_raw` (поле 4 = маска дней, бит 0=ПН; поле 6 = `(час<<8)\|мин`) |
| `9` | `trigger` | — |
| `10` | `interval` | `interval_ms` |
### Реализация (код)
**`config-to-yml.py`** — рекурсивные хелперы `dump_step()` / `dump_condition()` / `dump_leaf()` /
`dump_action()`; сборщики `scenario_scripts` / `scenario_conditions` / `scenario_raw_objects`;
`_raw_links` для точного порядка поля 2; **fixed-point sweep** операндов (`left`/`right`/`args`) —
без него теряются скрипты второго порядка (напр. `8459`/`8460` через `8462`); неизвестные типы →
`raw_objects` вместо падения (там же `KNOWN_TYPES += 47,48,50,59`).
**`yml-to-config.py`** — обратные `emit_step()` / `emit_condition()` / `emit_action()`; поддержка
старых форм (`steps`/`when`/`then`) через `_scenario_from_legacy()`; восстановление порядка ссылок
из `_raw_links`; новые секции `scenario_scripts`/`scenario_conditions`/`scenario_raw_objects`/`raw_objects`
в `TYPE_ORDER`.
### 🔴 Три питфолла реализации (round-trip ловил каждый)
| # | Проблема | Причина | Решение |
|---|---|---|---|
| 1 | Объекты не попадали в вывод | `result_lines` в энкодере — **фильтрованное подмножество**: объект должен быть и в `lines`, и в `TYPE_ORDER` | Добавить секции в `TYPE_ORDER` |
| 2 | Операнды терялись | `left`/`right`/`args`**не рёбра дерева**, обход их не видит | Fixed-point sweep: собирать референсы из `raw`-тел, докидывать, повторять |
| 3 | exit 4 «Duplicate ID» | Секции эмитились дважды (явный цикл + `TYPE_ORDER`) | Убрать явный цикл; типы 5/9/3/0/36 не регистрировать в `raw_objects` (у них свои секции) |
**Обязано сохраниться байт-в-байт (сохранено):**
- порядок объектов в файле (45 идёт **после** 11, но **до** 46)
- `extra_links` — хвостовые задержки в поле 2 сценария
- число полей (7 vs 8) и `_raw_*`-поля (`_raw_field_count`, `_raw_field3`, `_raw_divider`) — **не удалять**
- порядок ссылок поля 2 сценария`_raw_links`
- число полей (7 vs 8) и `_raw_*`-поля (`_raw_field_count`, `_raw_trigger_kind`, `_raw_trigger_params`) — **не удалять**
**Бэкап:** коммит `199f2b1` уже есть — откат возможен в любой момент, отдельных копий не делать.
### ✅ Round-trip — 6/6 конфигов чисто
```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 # ✅
```
**Бэкапы кода:** `/tmp/config-to-yml.py.bak`, `/tmp/yml-to-config.py.bak` (сняты перед правкой).
**Откат:** git (`199f2b1` — последний коммит).
---
@@ -617,67 +646,64 @@ https://lk.zont-online.ru/download/firmwares/H2000_PRO_<HW>__<FW>_<PROFILE>.zip
---
## 5i. 🔴 Конструктор логики ZONT (типы 47/48/50/59) — НЕ ПОДДЕРЖАН
## 5i. 🔴 Конструктор логики ZONT (типы 47/48/50/59) — ПОДДЕРЖАН 2026-09-17
> **Найдено 2026-09-17.** Alex создал в UI тестовые сценарии («все доступные триггеры и варианты
> логики»). Разбор локального конфига `config_local_2026-09-17_14-16-35.txt` (660 `#Z`) вскрыл
> **визуальный конструктор логики** со скриптовым движком.
**Конвертер на этом конфиге падает:**
```bash
$ python3 config-to-yml.py zont_config/config_local_2026-09-17_14-16-35.txt > /tmp/new.yml
Ошибка: Условие 8496: не type 49
EXIT=2
```
>
> ✅ **Статус: поддержан.** Парсер и энкодер дописаны (§5c), round-trip чистый. Ранее падал с
> `Ошибка: Условие 8496: не type 49` (exit 2) — **больше не падает**.
**Две ключевые находки:**
1. 🔴 **Поле 5 типа 11 — НЕ `enabled`, а ТИП ЗАПУСКА:** `0`/`1` ручной, `8` расписание,
`9` триггер, `10` интервал. Поле 6 — параметр (для интервала — мс). **Текущий конвертер
теряет эту информацию** (сводит к булеву `enabled`).
2. **Новые типы:** `47` (лист условия), `48` (группа И/ИЛИ), `50` (запуск по id),
`59` (мини-скрипт: `objcmd`/`objstate`/`expr`/`set`/`puts`/`storeev`).
`9` триггер, `10` интервал. Поле 6 — параметр (для интервала — мс; для расписания — `(час<<8)|мин`,
поле 4 — маска дней недели, бит 0=ПН).
2. **Новые типы:** `47` (лист условия: `op, left, value|right`), `48` (группа `and`/`or`/`not`),
`50` (объект-триггер, хранится как `raw`), `59` (мини-скрипт: `objcmd`/`objstate`/`expr`/`set`/
`puts`/`storeev`; код и args сохраняются **дословно**).
**Что нужно сделать** (семантика получена — [[family/tech/zont-scenario-logic-11109]] §8.7;
работа разрешена Alex'ом 2026-09-17 вечером, «дописывай парсер»):
парсер 47/48/50/59 с рекурсивным деревом условий; поле 5 хранить как тип запуска (плюс маска дней
и время для расписания); принимать непустое поле 4 шага; допускать не-46 типы в поле 2.
**Операторы type 47 (порядок UI `<, >, =, <=, >=`):** `0`=`<`, `1`=`>`, `2`=`=`, `3`=`<=`, `4`=`>=`.
> 📌 **Стратегия:** всё непонятое (скрипты `59` дословно, суффиксы `;#a`/`;#h`/`;#p`, отсутствующие id)
> сохранять байт-в-байт, без «умного» перевода — round-trip останется чистым. 8 открытых вопросов
> по сценарию `8456` — [[family/tech/zont-scenario-logic-11109]] §8.10.
> 📌 **Стратегия (реализована):** всё непонятое (скрипты `59` дословно, суффиксы `;#a`/`;#h`/`;#p`,
> отсутствующие id, `type 50`) сохраняется байт-в-байт через `raw` / `unresolved` — без «умного»
> перевода. Round-trip остаётся чистым, а непонятное не портится.
➡️ **Полный разбор — [[family/tech/zont-scenario-logic-11109]] §8**
---
## 6. Состояние проекта (проверено 2026-09-17, вечер)
## 6. Состояние проекта (проверено 2026-09-17, ночь)
**Конвертеры закоммичены — `199f2b1`.** Рабочее дерево чистое (после этого коммита).
**Конвертеры переписаны под сценарный конструктор (§5c).** Рабочее дерево — **не закоммичено**,
ждёт команды Alex. Последний коммит — `199f2b1`.
⚠️ **Но появился конфиг, который конвертер НЕ разбирает** — с конструктором логики (§5i).
**Round-trip чистый на 6/6 конфигах** (включая новый локальный с 660 `#Z` и legacy 7-поле `-2`).
| Файл | Статус |
|---|---|
| `config-to-yml.py`, `yml-to-config.py` | ✅ **изменены и закоммичены** (`199f2b1`, §5b) |
| `test_roundtrip.py` | **новый файл**, в коммите `199f2b1` |
| `zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt` | 32 689 байт — свежий конфиг с контроллера, **не в git** (`untracked`) |
| одноимённый `.yml` | **пересобран, 80 472 байта / 4320 строк / 24 секции**, YAML валиден. **не в git** |
| `zont_config/archive/` | **не в git** (`untracked`) — лежит в `.gitignore`-нейтральном состоянии |
| `zont_config/H2000_PRO_config_actual-{2,3,4}.txt` / `.yml` |**перемещены в `archive/` и закоммичены** (`7ae0e32`) |
| `zont_api_docs/` | ✅ **новая папка** — локальная копия доки облачного API ZONT (`zont_api_docs.html` 250 KB, `zont_api_docs.txt` 99 KB, `convert.py`). **не в git**. Исследование — [[family/tech/zont-api]] |
| `zont_config/config_local_2026-09-17_14-16-35.txt` | 🆕 **34 907 байт, 660 `#Z`, 25 `#S`** — конфиг с **тестовыми сценариями** Alex (все виды триггеров/логики). Снят с `http://192.168.0.50/config.txt`. **не в git** |
| `dump_new_types.py`, `probe_types.py`, `trace_scenarios.py` | 🆕 **новые скрипты разбора** сценарных типов 47/48/50/59. Разбор — [[family/tech/zont-scenario-logic-11109]] §8.8. **не в git** |
| `read_scenarios.py` | 🆕 **читаемый дамп сценария** — дерево если/то/иначе с именами объектов. Токенайзер `split_top()` учитывает кавычки/`\`/`[]`. **не в git** |
| `probe_sched.py`, `verify_answers.py`, `check_ops.py`, `audit_8456.py`, `audit2.py` | 🆕 **проверочные скрипты** — расписание, ответы Alex против конфига, операторы, аудит 8456. **не в git** |
| `~/rasputin-tmp/zont-util/` | ✅ **новая папка на Mac** (вне репо) — настроечная утилита `H1000 Programmator` 2.8.5, драйвер USB, прошивка `.enc`, скрипт распаковки `extract.py`. Разбор — §5g-2, [[family/tech/zont-api]] §10 |
| `~/rasputin-tmp/zont-{auth-probe,recon,recon2,ws-probe}.js` | ✅ скрипты разведки локального WS-интерфейса. **не в git** |
| `config-to-yml.py` | 🆕 **изменён** — парсер типов 47/48/50/59, `blocks/if/then/else`, триггер, raw-фоллбэк (§5c). **не закоммичено** |
| `yml-to-config.py` | 🆕 **изменён** — обратные `emit_*`, `_raw_links`, новые секции в `TYPE_ORDER` (§5c). **не закоммичено** |
| `test_roundtrip.py` | ✅ в коммите `199f2b1`, без изменений |
| `zont_config/config_local_2026-09-17_14-16-35.txt` | 🆕 **34 907 байт, 660 `#Z`, 25 `#S`** — конфиг с **тестовыми сценариями** Alex. Снят с `http://192.168.0.50/config.txt`. **не в git** |
| `zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt` | 32 689 байт — свежий боевой конфиг, **не в 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** |
| `read_scenarios.py` | 🆕 **читаемый дамп сценария** — дерево если/то/иначе с именами. Токенайзер `split_top()`. **не в git** |
| `dump_new_types.py`, `probe_types.py`, `trace_scenarios.py` | 🆕 скрипты разбора сценарных типов. Разбор — [[family/tech/zont-scenario-logic-11109]] §8.8. **не в git** |
| `probe_sched.py`, `verify_answers.py`, `check_ops.py`, `audit_8456.py`, `audit2.py`, `chk_extra.py` | 🆕 проверочные скрипты. **не в git** |
| `~/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** |
Не запушено: `origin/main..HEAD` = 3 коммита (`199f2b1`, `7ae0e32`, `12ba22b`).
**Бэкапы кода перед правкой §5c:** `/tmp/config-to-yml.py.bak`, `/tmp/yml-to-config.py.bak`.
Не запушено: `origin/main..HEAD` = 3 коммита (`199f2b1`, `7ae0e32`, `12ba22b`). Плюс **новые правки
§5c — незакоммичены**.
> ✅ **Пустой `.yml` (0 байт) больше не актуален** — причина была в падении на сценарии `11109`,
> которое исправлено (§5b). Файл пересобран, 598 объектов.
> исправлено (§5b).
**Что НЕ в git и почему:** `.txt` свежего конфига и его `.yml` — рабочие артефакты конвертации,
Alex их не добавлял. Не коммитить без команды.