Files
obsidian-vault/family/tech/zont-scenario-logic-11109.md
T

663 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
aliases:
- ZONT сценарий 11109
- Передернуть Автомат Котельной
- ZONT тип 45 задержка
- ZONT сценарная логика
- ZONT поле 2 сценария
created: '2026-09-17'
namespace: family
related:
- '[[family/how-to/zont-config-compiler]]'
- '[[family/tech/zont-config-object-types]]'
- '[[family/how-to/home-automation]]'
tags:
- family
- tech
- zont
- modbus
- homeautomation
title: 🔁 ZONT — сценарная логика и конструктор (11/46/47/48/49/50/59/45/9/5)
type: tech
updated: 2026-09-17f
---
# 🔁 ZONT — сценарная логика и конструктор логики
> 🔴 **ВНИМАНИЕ: раздел §1 ниже содержит УСТАРЕВШУЮ модель.** Поле 5 типа 11 — **НЕ «enabled»**,
> это **тип запуска сценария** (0/1 ручной, 8 расписание, 9 триггер, 10 интервал). Исправление и
> полная модель — **§8**. Раздел §1 оставлен как история разбора; актуальная семантика в §8.
Как устроены сценарии в конфиге ZONT и что именно разобрано в боевом конфиге
`config_0FA7C33CC89F_0FA7C33CC89F_2026-09-17_12-12-28.txt` (598 `#Z`, 25 `#S`, 65 сценариев).
Контекст конвертеров — [[family/how-to/zont-config-compiler]].
---
## 1. Структура сценария
Сценарий — это **4 связанных типа объектов**:
```
#Z<sid>=11,'Имя',[<шаг>,<шаг>,…],0,0,<enabled>,0,0 ← сценарий
#Z<step>=46,<prio>,<cond>,[<действие>,…],[] ← шаг
#Z<cond>=49,<relay_id>,<operator>,<value> ← условие
#Z<act>=… ← действие (тип 5, 9 или 45)
```
| Тип | Роль | Формат | Поля |
|---|---|---|---|
| **11** | Сценарий | `[11, name, [step_ids], 0, 0, enabled, 0, 0]` | **8** полей. `enabled` — поле v[5] (0/1) |
| **46** | Шаг | `[46, prio, cond_id, [action_ids], []]` | **5** полей. `prio` и последнее поле — всегда 0/`[]` |
| **49** | Условие | `[49, relay_id, operator, value]` | **4** поля. `operator`: `1`=equals, иначе not_equals |
| **45** | Задержка | `[45, <миллисекунды>]` | **2** поля |
**Каноническая форма** (99% конфига): 1 сценарий → 1 шаг → 1 условие → 1 действие.
---
## 2. Действия в шаге — три разных типа
Список действий шага (`46` поле 3) может содержать объекты **трёх** типов:
| Тип | Что это | Формат | Декодирование |
|---|---|---|---|
| **5** | Действие над выходом контроллера | `[5, name, output_ref, 1, delay, impulse_dur, [], sched_bmp, sched_time, impulse_period, value]` | 11 полей. `output_ref >> 4` = id выхода; `delay` — поле 4 (мс) |
| **9** | Команда реле | `[9, name, target_relay, '1'/'0']` | 4 поля. `value`**строка** `'1'`/`'0'`, не число |
| **45** | Пауза между действиями | `[45, ms]` | 2 поля |
> ⚠️ **Тип 45 ≠ `delay_ms` типа 5.** `delay_ms` — задержка **внутри** действия перед его выполнением.
> Тип 45 — **отдельный объект-пауза** в последовательности. Их часто путают при чтении конфига.
---
## 3. Разобранный кейс: `#Z11109` «Передернуть Автомат Котельной»
**Единственный сценарий в конфиге с нестандартной формой** — 7 действий в шаге и задержка (`45`)
прямо в поле 2 сценария (`11828`) вместо второго шага. См. §5 «Уточнение семантики поля 2».
```
#Z11109=11,'Передернуть Автомат Котельной',[11827,11828],0,0,0,0,0
#Z11823=49,11190,1,0 ← условие
#Z11827=46,0,11823,[11191,11030,11824,11029,11825,11192,11826],[]
#Z11828=45,0 ← шаг 2
#Z11824=45,20000 #Z11825=45,60000 #Z11826=45,0
#Z11029=9,'Включить реле «20/1 Автомат Котельная»',11028,'1'
#Z11030=9,'Выключить реле «20/1 Автомат Котельная»',11028,'0'
#Z11191=9,'Включить реле «virt. Запретить передергивание»',11190,'1'
#Z11192=9,'Выключить реле «virt. Запретить передергивание»',11190,'0'
#Z11028=14,'20/1 Автомат Котельная',175728,0
#Z11190=14,'virt. Запретить передергивание',179904,0
```
### Что происходит по шагам
**Условие:** `relay 11190 ('virt. Запретить передергивание') == 0` — т.е. передёргивание **не** запрещено.
**Шаг 1 (`11827`) — 7 действий подряд:**
| # | Объект | Действие | Пауза после |
|---|---|---|---|
| 1 | `11191` (type 9) | включить `virt. Запретить передергивание` | — |
| 2 | `11030` (type 9) | **выключить** `20/1 Автомат Котельная` | — |
| 3 | `11824` (type 45) | пауза | **20 000 мс** |
| 4 | `11029` (type 9) | **включить** `20/1 Автомат Котельная` | — |
| 5 | `11825` (type 45) | пауза | **60 000 мс** |
| 6 | `11192` (type 9) | выключить `virt. Запретить передергивание` | — |
| 7 | `11826` (type 45) | пауза | 0 |
**Шаг 2 (`11828`)** — на деле это не шаг, а **задержка** `[45, 0]` прямо в поле 2 сценария:
пустая пауза-заглушка. Оставлена планировщиком ZONT, функциональной нагрузки не несёт.
### Смысл логики
Это **защита от повторного передёргивания**:
1. Виртуальное реле `virt. Запретить передергивание` (id `11190`) ставится в 1 **первым** действием.
2. Условие сценария (`11823`) требует `== 0` → пока реле в 1, сценарий повторно **не запустится**.
3. Снимается реле только **после** 20 с + 60 с пауз (действие 6), т.е. через 80 секунд после старта → минимальный интервал между передёргиваниями автомата = 80 с.
### 🔴 Проблема
`#Z11109` поле v[5] = **`0`** → **сценарий выключен в контроллере**. Защита и сама функция передёргивания не работают.
---
## 4. Полные счётчики по конфигу (для сверки после правок)
| Тип | Кол-во | Комментарий |
|---|---|---|
| 11 / 46 | 65 / 65 | все одношаговые, кроме `11109` |
| 49 | 65 | все ровно 4 поля, `op=1` (equals) у всех |
| 45 | 4 | `11824`, `11825`, `11826`, `11828` — только в `11109` |
| 9 | 42 | 21 пара ВКЛ/ВЫКЛ, `value` — строка `'1'`/`'0'` |
| 5 | 66 | действия над выходами |
| 14 | 40 | реле |
| 10 | 22 | GUI-переключатели |
| 0 / 36 | 13 / 13 | дискретные датчики + вложенные конфиги |
| 52 / 51 / 53 | 93 / 11 / 64 | Modbus-регистры / устройства / ещё один слой |
| 16 / 20 | 10 / 1 | контуры и режимы отопления |
| **Всего `#Z`** | **598** | |
> Проверка после любой правки: `grep -c '^#Z' <файл>` должен дать **598**.
---
## 5. Статус разбора конвертером
| Что | Статус |
|---|---|
| Многошаговый сценарий (`[11827,11828]`) | ✅ разбирается с 2026-09-17 |
| 7 действий в шаге `11827` | ✅ разбирается с 2026-09-17 |
| Тип 45 (4 объекта) | ✅ парсер + эмиттер, секция YAML `delays` |
| Round-trip всех 4 конфигов | ✅ чисто (`test_roundtrip.py`, коммит `199f2b1`) |
| Сценарий `enabled=0` | ℹ️ факт, не баг конвертера |
### 🔴 Уточнение семантики поля 2 (важно)
Поле 2 сценария — **не «список шагов»**, а **последовательность ссылок**, куда попадают объекты
**разных типов**. Полный список: шаг (`46`) **и** задержка (`45`).
В `#Z11109` поле 2 = `[11827, 11828]`, где:
- `11827` — настоящий шаг (`46`) с условием и 7 действиями;
- `11828`**задержка (`45,0`)**, т.е. «шаг-ожидание» без условия.
Поэтому формулировка «2 шага» неточна: **шаг один**, вторым элементом идёт задержка-заглушка.
Отсюда же — `extra_links` в YAML (см. [[family/how-to/zont-config-compiler]] §2.1): не-46 ссылки
сохраняются дословно, чтобы round-trip остался байт-точным.
> ⚠️ **Не путать с типом 45 внутри шага.** В `11827` задержки `11824`/`11825`/`11826` — это
> **действия внутри** шага. `11828` — **ссылка в поле 2 сценария**, т.е. элемент верхнего уровня.
---
## 6. План переработки YAML-структуры (2026-09-17, ждёт апрува)
Сценарии в YAML переписываются в нативную ZONT-форму **вложенных инструкций «если … то …»**
**без HA-синтаксиса** (`triggers`, `platform: state`). Alex: «нет структура должна быть как в zont.
**триггеров нет.**»
Предлагаемая форма для `11109`:
```yaml
scenarios:
- id: 11109
name: 'Передернуть Автомат Котельной'
enabled: false
blocks:
- id: 11827
if: {id: 11823, relay: 11190, operator: equals, value: 0}
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:
- {id: 11828, action: wait, ms: 0} # задержка из поля 2 сценария
```
Полный план — [[family/how-to/zont-config-compiler]] §5c.
> ⏸ **РАБОТА ПО §5c ПРИОСТАНОВЛЕНА (2026-09-17).** Alex дал «стоп» и запросил исследование облачного
> API ZONT — есть ли альтернатива конвертеру. **Ответ: API конфиг не поддерживает** (сценарии/реле/
> 11/14/46/49 в API отсутствуют). Переписывать с 0 не на что, конвертер остаётся единственным путём.
> Разбор — [[family/tech/zont-api]]. Форма `blocks/if/then` выше — **предложение, в коде не реализовано**.
---
## 7. Связанные заметки
- [[family/how-to/zont-config-compiler]] — конвертеры `.txt ⇄ .yml`; 2 шага и тип 45 **поддержаны с 2026-09-17** (§5b)
- [[family/tech/zont-api]] — облачный API ZONT: конфиг не поддерживает, альтернативы конвертеру нет
- [[family/tech/zont-config-object-types]] — полная таблица типов объектов и `#S`-настройки
- [[family/how-to/home-automation]] — контур автоматизации, ZONT, Modbus slave ID и регистры
---
## 8. 🔴 КОНСТРУКТОР ЛОГИКИ ZONT — типы 47 / 48 / 50 / 59 (разобрано 2026-09-17)
> **Источник:** локальный конфиг с контроллера, снят с `http://192.168.0.50/config.txt`
> (без авторизации, см. [[family/tech/zont-api]] §6bis):
> `zont_config/config_local_2026-09-17_14-16-35.txt` — **34 907 байт, 660 `#Z`, 25 `#S`**.
>
> Alex создал тестовые сценарии в UI, «постарался использовать все доступные триггеры и варианты
> логики». Это позволило вскрыть **полноценный визуальный конструктор логики** со скриптовым движком.
### 8.1. 🔴 ИСПРАВЛЕНИЕ: поле 5 типа 11 — ТИП ЗАПУСКА, а не `enabled`
Прежняя модель (§1, строка `[11, name, [step_ids], 0, 0, enabled, 0, 0]`) **неверна**.
Поле 5 (индекс 5) — **тип запуска сценария**:
| Значение | Что это | Доказательство из тестовых сценариев |
|---|---|---|
| `0` / `1` | ручной запуск (кнопка), вкл/выкл | 64 старых сценария = `1`; `11109` = `0` |
| `8` | по **расписанию** | `#Z8597` — поле 4 = `61`, поле 6 = `3354` ✅ расшифровано |
| `9` | по **триггеру** (условию) | `#Z8547`, `#Z8551` — оба триггерные (подтверждено Alex) |
| `10` | по **интервалу** | `#Z8599` — поле 6 = `43200000` мс = **12 ч** |
**Поле 6** — параметр запуска (для `10` — интервал в мс; для `8` — время запуска).
### 🔴 Поле 4 + поле 6 при запуске по расписанию — РАСШИФРОВАНО (2026-09-17)
`#Z8597` «Тестовый сценарий по расписанию», Alex: **ПН, СР, ЧТ, ПТ, СБ, в 13:26**.
| Поле | Значение | Расшифровка |
|---|---|---|
| поле 4 | `61` = `0b00111101` | **битовая маска дней недели**: бит 0=ПН, 1=ВТ, …, 6=ВС. Установлены биты **0,2,3,4,5** = ПН, СР, ЧТ, ПТ, СБ ✅ |
| поле 6 | `3354` = `0x0D1A` | **время**: старший байт `0x0D` = **13** (час), младший `0x1A` = **26** (минута) → **13:26** ✅ |
```text
#Z8597 = 11, 'Тестовый сценарий по расписанию', [8598], 61, 3354, 8, 0, 0
^годнедели ^время ^тип
```
> Формула времени: `поле6 = (час << 8) | минута`. Начало недели — **ПН** (бит 0), не ВС.
> Проверка: `61 >> 0 & 1 = 1` (ПН есть), `61 >> 1 & 1 = 0` (ВТ нет — у Alex ВТ не отмечен) ✅.
```text
#Z8456=11,'Простой тестовый сценарий',[<26 ссылок>],0,0, 8,0,0
#Z8547=11,'тестовый сценарий по времени', [8550],0,0, 9,0,0
#Z8551=11,'Тестовый сценарий по триггеру',[8555],0,0, 9,0,0
#Z8597=11,'Тестовый сценарий по расписанию',[8598],61,3354, 8,0,0
#Z8599=11,'Тестовый сценарий по интервалу',[8600,8601],0,0, 10,43200000,0
```
> ⚠️ **Следствие для конвертера:** `enabled` в YAML сейчас выводится из поля 5 как булево —
> это **потеря информации** для значений 8/9/10. Конвертер на новом конфиге **падает**
> (`Ошибка: Условие 8496: не type 49`) — новые типы не поддержаны.
### 8.2. Новые типы объектов (УТОЧНЕНО ответами Alex 2026-09-17)
| Тип | Что | Формат | Пример |
|---|---|---|---|
| **47** | **лист дерева условий** (сравнение) | `[47, <оператор>, <левый>, <правый_или_0>, <константа>]` | `[47, 3, 8552, 0, -5]` — «погода <= -5» |
| **48** | **группа условий** | `[48, <логика>, [<id условий>]]` | `[48, 0, [8493,8495]]` |
| **50** | **объект-триггер запуска** (внешний/системный) | `[50, 1, 0, 0, <ext_id>]` | `[50, 1, 0, 0, 109]` |
| **59** | **мини-скрипт ZONT** | `[59, '<код>', <арг1>, <арг2>, <флаг>]` | `[59, 'expr "%0 + %1"', 8459, 8460, 0]` |
**Type 47 — модель полей.** `[47, op, LEFT, RIGHT, K]`:
- `LEFT` — объект/скрипт-выражение слева;
- если `RIGHT` != `0`**второй объект**, сравнение двух объектов (`8495`: `var1 >= objstate 9838`, RIGHT = `8494`);
- если `RIGHT` = `0` — сравнение **с константой** из поля 4 (`8493`: `OBJECT < 3`).
**Type 47, операторы — ПОДТВЕРЖДЕНО Alex** (порядок пунктов UI: `<`, `>`, `=`, `<=`, `>=`):
| Поле 1 | Оператор | Доказательство (формулировка Alex) |
|---|---|---|
| `0` | `<` | `8493` «значение элемента управления < 3»; `8497` «var1 < 2» |
| `1` | `>` | `8499` «Pickle(2) > 3» |
| `2` | `=` | в тестовом блоке нет; следует из порядка UI |
| `3` | `<=` | `8553` «погода из интернета <= -5» |
| `4` | `>=` | `8495` «var1 >= значение 13/9 рад. ванная 2эт» |
> 🔴 **Ловушка:** порядок пунктов UI (`<`,`>`,`=`,`<=`,`>=`) **не** логически упорядочен.
> `=` стоит **третьим** (код `2`), между `>` и `<=`. Не «додумывать» порядок как
> «сначала все меньше, потом все больше» — это даёт неверные коды.
**Type 48, логика группы — ПОДТВЕРЖДЕНО Alex** (по описанию теста 8456):
| Поле 1 | Логика | Доказательство |
|---|---|---|
| `0` | **И** (все условия) | `8496 = [48,0,[8493,8495]]` — «условие И: ...» |
| `1` | **ИЛИ** (любое) | `8501 = [48,1,[8497,8500]]` — «ИЛИ: ...» |
| `2` | **НЕ** (отрицание) | `8500 = [48,2,[8499]]` — «НЕ (...)» |
**Type 50 — триггер запуска.** `[50, 1, 0, 0, <ext_id>]`, где `<ext_id>` — **внешний/системный
объект**, которого нет среди `#Z` (напр. `109` — «погода из интернета»). Сценарий «по времени»
(`8547`, шаг `8550`) ссылается на `109` — в UI это тоже триггер-условие запуска
(поле 5 типа 11 = `9`).
### 8.3. Type 59 — встроенный скриптовый язык
Найденные команды (в поле 1, в кавычках — аргументы):
| Команда | Пример | Смысл (предположительно) |
|---|---|---|
| `objcmd <id> "<args>"` | `objcmd 8700 "1 %0"` | команда объекту с подстановкой `%0`, `%1` |
| `objstate <id> <n> <n>` | `objstate 9838 0 0` | чтение состояния объекта |
| `expr "<формула>"` | `expr "%0 + %1"`, `expr "%0 - %1"` | арифметика |
| `set <var>` | `set var1` | присваивание переменной |
| `puts "текст"` | `puts "Отладка"`, `puts "test"` | вывод (отладка) |
| `storeev I "z"` / `storeev A "asdf"` | — | запись события (I = integer?, A = alert/array?) |
| просто число/строка | `3`, `2 ;#p` | константа; суффиксы `;#a`, `;#h`, `;#p` |
Есть **переменные** (`var1`), **подстановки** `%0`/`%1` (аргументы из полей 2/3), **суффиксы** `;#a`/`;#h`/`;#p`.
Это объясняет всё: у ZONT есть **полноценный визуальный конструктор логики** поверх скриптового движка.
### 8.4. Дерево условий — рабочий пример (`#Z8505`, `#Z8506`)
Вложенная логика читается так:
```
#Z8506 = [46, 0, 8496, [8505], []] ← шаг: условие 8496, действие 8505
#Z8496 = [48, 0, [8493, 8495]] ← группа: 8493 И 8495
#Z8493 = [47, 0, 8492, 0, 3] ← лист: объект 8492, значение 0, порог 3
#Z8492 = [59, 'objstate 9838 0 0', 0, 0, 0] ← объект = скрипт!
#Z8495 = [47, 4, 8472, 8494, 0]
#Z8494 = [59, 'objstate 9838 0 0', 0, 0, 0]
#Z8505 = [46, 1, 8501, [8502, 8503], [8504]] ← шаг: приоритет 1, доп. список [8504]
#Z8501 = [48, 1, [8497, 8500]] ← группа: 8497 ИЛИ 8500
#Z8497 = [47, 0, 8472, 0, 2]
#Z8500 = [48, 2, [8499]] ← группа: логика 2 над [8499]
#Z8499 = [47, 1, 8498, 0, 3]
#Z8498 = [59, '2 ;#p', 0, 0, 0]
```
> 📌 В `#Z8505` поле 1 (приоритет) = **1**, и поле 4 (последнее) = **непустой список `[8504]`** —
> в отличие от «канонических» шагов, где там всегда `[]`. Конвертер это отвергает
> (`require(step[4] == [])`). Семантика поля 4 — **не подтверждена**.
### 8.5. Контейнер-сценарий `#Z8456` (поле 2 = смесь типов)
`#Z8456 = [11, 'Простой тестовый сценарий', [<26 ссылок>], 0, 0, 8, 0, 0]` — поле 2 содержит
объекты **разных типов**, а не только шаги:
| Тип | Кол-во | Примеры |
|---|---|---|
| 9 (команда реле) | 2 | `8457` «Установить целевую температуру 23.8…», `8470` «Активировать режим…» |
| 59 (скрипт) | 17 | `8458``8504` |
| 11 (другой сценарий!) | 1 | `11109` «Передернуть Автомат Котельной» |
| 3 (SMS) | 1 | `8195` «CMC оповещение» |
| 5 (действие) | 1 | `9500` «Вкл. Рад. ванная 2эт» |
| 45 (задержка) | 1 | `8489` = 432 000 000 мс = **5 суток** |
| 46 (шаг) | 1 | `8506` |
> 🔴 **Сценарий может ссылаться на другой сценарий** (`11109` внутри `8456`). Это **не дерево**,
> а плоский список ссылок.
### 8.6. Сводка тестового блока (id 84508601)
Типы в блоке: `{9: 2, 11: 3, 14: 1, 16: 1, 27: 1, 45: 1, 46: 4, 47: 5, 48: 3, 49: 11, 50: 3, 59: 26}`.
| Сценарий | Имя | Поле 5 (запуск) |
|---|---|---|
| `8456` | Простой тестовый сценарий | `8` (расписание) |
| `8547` | тестовый сценарий по времени | `9` (триггер) |
| `8551` | Тестовый сценарий по триггеру | `9` (триггер) |
| `8597` | Тестовый сценарий по расписанию | `8` (расписание) |
| `8599` | Тестовый сценарий по интервалу | `10` (интервал, 12 ч) |
### 8.7. ✅ ВОПРОСЫ ЗАКРЫТЫ — ответы Alex (2026-09-17)
Alex создал тестовые сценарии и описал, что видно в UI. Все пять вопросов закрыты:
| # | Вопрос | Ответ | Проверка по конфигу |
|---|---|---|---|
| 1 | **Type 47, оператор** | Операнды в UI: **`<`, `>`, `=`, `<=`, `>=`** → коды `0..4` | ✅ все 5 листов сошлись |
| 2 | **Type 48, логика** | **`0`=И, `1`=ИЛИ, `2`=НЕ** | ✅ по описанию теста 8456 |
| 3 | **Type 50** | **Объект-триггер запуска** (внешний, напр. `109` «погода из интернета») | ✅ |
| 4 | **Type 11, поле 4** | **Маска дней недели** (`61` = ПН,СР,ЧТ,ПТ,СБ), поле 6 = `(час<<8)|мин` | ✅ 13:26 |
| 5 | **`;#a`/`;#h`/`;#p`** | (не переспрашивал; остаются гипотезой) | ⏳ |
**Разбор тестовых сценариев Alex (ответы дословно → факты конфига):**
| Сценарий | Что сказал Alex | Что в конфиге |
|---|---|---|
| `8597` расписание | «пн, ср, чт, пт, сб, в 13:26» | поле 4=`61`, поле 6=`3354`=13:26 ✅ |
| `8547` «по времени» | «по триггеру, триггер на день недели: пн,ср,чт,сб,вс» | поле 5=`9`, шаг `8550``[50,1,0,0,109]` |
| `8551` «по триггеру» | «триггер сравнение: погода из интернета <= -5, действие вывод в терминал» | `8553=[47,3,8552,0,-5]`, `8552=[49,8254,0,4]`, `8254`=«Погода из интернета», then `8554`=`puts "Температура упала ниже -5"` ✅ |
| `8547` шаг `8550` | «только одно действие: вывод "test" в терминал» | `8549=[59,'puts "test"',0,0,0]` ✅ |
| `8456` (26 ссылок) | «set var много, у каждого разный тип значения» | 7× `set var1` + 7 разных типов объектов ✅ |
**Type 46 — модель полей ПОДТВЕРЖДЕНА (5 полей = если/то/иначе):**
```text
#Z<step> = [46, <вид>, <id_условия_или_0>, [<ТОГДА>...], [<ИНАЧЕ>...]]
```
Alex по тесту `8456` описал: «если (условие И: …) То: … Иначе: Сохранить событие» —
и это точно соответствует `8506 = [46, 0, 8496, [8505], []]` + `8505 = [46, 1, 8501, [8502,8503], [8504]]`,
где `[8504]` = ветка **ИНАЧЕ**. Поле 4 = список **ИНАЧЕ** (ранее помечено «семантика не подтверждена» — теперь подтверждена).
> ⚠️ Поле 1 шага: `0` у внешнего, `1` у вложенного (`8505`) — вероятно «уровень вложенности»
> или признак вложенного блока. Точная семантика не подтверждена, но round-trip требует
> сохранять как есть.
### 8.8. Скрипты разбора (в проекте)
| Файл | Назначение |
|---|---|
| `read_scenarios.py` | 🆕 **читаемый дамп любого сценария** — дерево если/то/иначе с именами объектов. `python3 read_scenarios.py <config> <id>` |
| `dump_new_types.py` | дамп типов 11/45/46/47/48/49/50/59 целиком |
| `probe_types.py` | распределение поля 5 у type 11; тело контейнера; блок 8450–8560 |
| `trace_scenarios.py` | трассировка сценарных графов, поиск сценариев с новыми типами |
| `probe_sched.py` | 🆕 проверка расписания: маска дней + время |
| `verify_answers.py` | 🆕 кросс-проверка ответов Alex против фактов конфига |
| `check_ops.py` | 🆕 проверка таблицы операторов type 47 |
| `audit_8456.py`, `audit2.py` | 🆕 аудит прозрачности элементов сценария 8456 |
```bash
cd /Users/admin/Automation/HA-ZONT-Modbus
python3 read_scenarios.py zont_config/config_local_2026-09-17_14-16-35.txt 8456 # дерево сценария
python3 read_scenarios.py zont_config/config_local_2026-09-17_14-16-35.txt # все сценарии
python3 probe_types.py # семантика поля 5, тестовый блок
python3 dump_new_types.py # все сценарные объекты
```
> ✅ **Токенайзер починен.** Старый `read_scenarios.py` рвал строки со скриптами на запятых
> внутри кавычек (`objcmd 9102 "6,%0";#a` разваливалось). Новый `split_top()` учитывает
> кавычки, экранирование `\` и вложенные `[]` — теперь скрипты печатаются как есть.
### 8.10. ⏳ НЕПРОЗРАЧНЫЕ ЭЛЕМЕНТЫ СЦЕНАРИЯ 8456 (✅ разблокированы raw-стратегией)
Alex спросил: «а по всем действиям в "Простой тестовый сценарий" — тебе всё кристально прозрачно?»
Ответ был — **нет**. Ниже ровно то, что не выводится из конфига.
> ✅ **Разблокировано 2026-09-17.** Alex решил: «допиливаешь парсер с известными типами, а неизвестные
> — пусть в yml будут raw values, и тогда я доразберу». После этого конвертер дописан, round-trip
> чистый, **эти вопросы больше не блокируют работу** (§8.9). Семантику доразбирает Alex в YAML
> через `raw`-поля. **Гипотезы по-прежнему не строить** — открытые пункты остаются открытыми.
| # | Элемент | Что неясно |
|---|---|---|
| 1 | `#Z8456` поле 5 = `8` (расписание), но поле 4 = `0`, поле 6 = `0` | **Как запускается 8456?** Расписание не задано. Что значит `8` без маски/времени? Ручной запуск? |
| 2 | `#Z8458 = [59, 'objcmd 8700 "1 %0"', 10.5, 0, 1]` | Единственный скрипт с **нецелым** `%0` (`10.5`) и **флагом `1`** в поле 4 (у всех остальных `0`). Что за инструкция в UI? |
| 3 | `#Z8463 = [59, 'objcmd 9102 "6,%0";#a', 8462, 0, 0]`, где `8462` = скрипт `expr "%0 + %1"` | Аргумент = **ссылка на другой скрипт**. Что за суффикс `;#a`? |
| 4 | `#Z8465 = [59, 'objcmd 11907 ",,,%0";#h', 8464, 0, 0]`, где `8464` = `[49, 4099, 0, 8]` (условие) | Аргумент = **условие**. Что за `;#h`? |
| 5 | `#Z8467 = [59, '3', 0, 0, 0]` | Скрипт = просто цифра `3`. Константа? Зачем отдельным действием? |
| 6 | `#Z8486`/`#Z8488` = `set var1`, arg1 = `8485`/`8487`**type 50** | Type 50 стоит как **источник значения** для var1, а не как триггер. Это то же самое, что триггер? |
| 7 | `#Z9500 = [5,'Вкл. Рад. ванная 2эт',145728,1,0,0,[],0,0,0,512]` | Объект выхода `145728` **отсутствует** в конфиге. Поле 10 = `512` — что это? → ✅ **пункт 7 ЗАКРЫТ**: `145728` — id выхода (`output_id = 145728 >> 4 = 9108`), `512` — код. Alex: «145728 — это очевидно id». **Выводить семантику `512` из подписи не нужно** |
| 8 | `#Z8457 = [9,'Установить целевую температуру 23.8...',8669,'2968']`, `#Z8470 = [9,...,8669,'8574']` | → ✅ **пункт 8 ЗАКРЫТ**: type 9 = `[9, '<подпись>', <id цели>, '<значение>']`. Цель — реле (14) / контур (16) / режим (20). Поле 4 — **ссылка/код, не уставка**`'2968'`» при тексте «23.8»). Alex подтвердил: `8470` → цель `8669` = «Контур ГВС» |
### 8.12. 🔴 Type 9 и Type 5 — модель полей (подтверждено Alex 2026-09-17, вечер)
**Type 9 — команда (реле / контур / режим отопления):**
```text
#Z<id> = [9, '<подпись команды>', <id цели>, '<значение>']
```
| Часть | Смысл | Пример |
|---|---|---|
| поле 2 | подпись, собранная UI | `'Установить целевую температуру 22 для контура Спальня'` |
| поле 3 | **id цели** → тип 14 (реле) / 16 (контур) / 20 (режим) | `10034` = `Спальня` (16), `8574` = `Режим отопления` (20), `9263` = `Реле 1: Конвектор кухня` (14) |
| поле 4 | **значение как строка**`'1'`/`'0'` (вкл/выкл) **или ссылка/код** | `9826``'1'` (вкл реле), `8470``'8574'`, `8457``'2968'` |
🔴 **Ловушка:** поле 4 ≠ значение уставки. У `8457` подпись «температуру 23.8», а поле 4 = `'2968'`
(ссылка на отсутствующий объект). **Не выводить значение из текста подписи.**
**Type 5 — действие над выходом контроллера:**
```text
#Z<id> = [5, '<имя>', <output_ref>, <on/off>, <delay>, <impulse>, [], bmp, time, <code>]
```
`output_id = output_ref >> 4`. Пример `9500`: `output_ref=145728``output_id=9108`, `value=1`.
> ✅ **Ответы Alex (пересозданный сценарий):** «`8457` — поменял температуру на 22, контур на Спальня»
> (→ новый `#Z8641=9,'Установить целевую температуру 22 для контура Спальня',10034,'2950'`);
> «`8470`: `8669` это очевидно Контур ГВС. больше там полей в UI нет. добавил второе такое же действие
> но с "для Всех контуров"» (→ новый `#Z8640=9,'Включить режим «Режим отопления»',8574,'1'`).
**Что уже прозрачно (не переспрашивать):**
- 7× `set var1` и их источники: `8450`(Температура подачи, тип 27), `9864`(Статус 13/9, тип 0),
`8560`(Контур газ котла, тип 16), `9263`(Реле 1 конвектор, тип 14), `8254`(Погода из интернета, тип 1),
`4098`(Адаптер газ. котла, тип 6), `8382`(Температура Zigbee, тип 1) ✅
- `#8466` `puts "Отладка"`, `#8468` `storeev I "z"`, `#8469` `storeev A "asdf"` — вывод/события ✅
- `#8489` пауза 5 суток (`432000000` мс), дерево `#8506``#8505` (И/ИЛИ/НЕ + ИНАЧЕ) ✅
- `#8195` SMS `CMC оповещение``8192` (2 номера) ✅
- `#11109` как **вложенный сценарий** внутри 8456 ✅
- `#8507` — ссылка на **отсутствующий объект** (хвост поля 2) ✅
**Почему неясные элементы не блокируют парсер:** стратегия — сохранять их **дословно**
(type 59 как сырые строки с `arg1..arg3`, отсутствующие id — как есть). Round-trip останется
байт-точным независимо от того, понята семантика или нет.
### 8.9. ✅ Состояние: конвертер новый конфиг РАЗБИРАЕТ (2026-09-17)
> ✅ **ИСПРАВЛЕНО.** Все пять пунктов ниже реализованы в конвертерах (§5c в
> [[family/how-to/zont-config-compiler]]). Round-trip чистый на 6/6 конфигах.
> Ранее падал с `Ошибка: Условие 8496: не type 49` (exit 2) — **больше не падает**.
**Что было нужно — ВСЁ СДЕЛАНО:**
| # | Было нужно | Статус |
|---|---|---|
| 1 | Парсер типов **47/48/50/59** + рекурсивное дерево условий | ✅ `dump_condition`/`dump_leaf`/`dump_action` |
| 2 | Поле 5 типа 11 — **тип запуска** (0/1/8/9/10), не `enabled` | ✅ `trigger: {type, days, time, interval_ms}` |
| 3 | Поле 4 шага — принимать непустые списки | ✅ `else``steps[]`) |
| 4 | Ссылки в поле 2 — допускать не-46 типы (45, 11, 3, 5, 9) | ✅ **плоский `steps[]`** — каждый элемент по порядку, тело инлайн |
| 5 | Round-trip всех 5 конфигов | ✅ **6/6** (боевой + 3 архива + новый локальный + live) |
**Счётчики конфига `config_local_2026-09-17_14-16-35.txt` (для сверки):** `660 #Z`, `25 #S`, байт — 34 907.
| Тип | Кол-во | | Тип | Кол-во |
|---|---|---|---|---|
| 11 | 70 | | 49 | 76 |
| 46 | 69 | | 47 | 5 |
| 45 | 5 | | 48 | 3 |
| 9 | 44 | | 50 | 3 |
| 59 | 28 | | 0/36 | 13/13 |
**Финальный конфиг `config_local_2026-09-17_16-02-18.txt`** (Alex пересоздал сценарий, вечер):
`686 строк`, **34 962 байт**. Сценарий `8456` полностью перегенерирован (новые id 86408777):
добавлены `#Z8640=9,'Включить режим «Режим отопления»',8574,'1'` и
`#Z8641=9,'Установить целевую температуру 22 для контура Спальня',10034,'2950'`.
YAML — `zont_config/config_local_2026-09-17_16-02-18.yml`, round-trip ✅.
### 8.11. ✅ ФИНАЛЬНАЯ YAML-форма сценария — плоский `steps[]` (2026-09-17)
> 🔴 **Урок, который стоит запомнить.** Первая реализация делила поле 2 на `blocks` (шаги 46) +
> `extra_links` (голые id). Round-trip был чистый (байты сходились), но **сценарий читался как
> список цифр**: `blocks` выводился первым, а всё «до него» — 25 элементов «установка температуры,
> `puts`, `set var1`, пауза 5 суток» — лежало в `extra_links` без тел. Alex: «почему там блядь if
> идет первым блоком?! где все что до него происходит?!»
> **Вывод: round-trip ≠ читаемость. Артефакт надо читать глазами до отдачи.**
**Правильная форма** — одно поле `steps`, плоский список = поле 2 сценария **как есть**:
```yaml
scenarios:
- id: 8456
name: Простой тестовый сценарий
trigger: {type: schedule}
_raw_trigger_kind: 8
steps:
- {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: 11109, run_scenario: Передернуть Автомат Котельной}
- {id: 8466, script: 'puts "Отладка"', args: [0, 0, 0]}
- {id: 8473, script: 'set var1', args: [8471, 0, 0]} # + ещё 6 × set var1
- {id: 8489, wait: 432000000} # пауза 5 суток
- id: 8506 # шаг с условием — тоже элемент
if: {id: 8496, group: and, children: [
{id: 8493, op: '<', left: 8492, value: 3},
{id: 8495, op: '>=', left: 8472, right: 8494}]}
then: [{id: 8505, kind: 1, if: {…}, then: [{id: 8502, script: 'puts "then-text"'}], else: [{id: 8504, script: 'storeev A "alert"'}]}]
- {id: 8507, unresolved: true}
```
**Диспетчер `dump_step()`** — по типу элемента:
`46``if/then/else`, `59``script`+`args`, `47/48/49`→дерево условий, `45``wait`, `50``trigger_object` (raw),
`11``run_scenario`, `5/9/3``raw`-тело. **Мелкая деталь:** тела объектов чужих секций (`sms_notifications`,
`actions`) рендерятся как YAML-якоря `*id00N` (`8195`, `9500` в `8456`) — читается хуже, но байт-точно.
**Артефакт:** `zont_config/config_local_2026-09-17_14-16-35.yml` (6110 строк) — в проекте, не в `/tmp`.
### 8.13. 🔴 УСТАВКА ТЕМПЕРАТУРЫ — формула `код = (t °C + 273) × 10` (подтверждено 3 точками, 2026-09-17)
**Главное открытие разбора type 9.** Поле 4 команды «Установить целевую температуру» — **не ссылка,
а закодированная уставка в Кельвинах ×10** (fixed point):
```text
код = (t_celsius + 273) * 10 обратно: t_celsius = (код - 2730) / 10
```
**Три подтверждённые точки** (Alex менял температуру в UI, я сверял с конфигом):
| Уставка в UI | Код в конфиге | Проверка |
|---|---|---|
| `22` | `2950` | `(22 + 273) * 10 = 2950` ✅ |
| `23.8` | `2968` | `(23.8 + 273) * 10 = 2968` ✅ |
| `5.2` | `2782` | `(5.2 + 273) * 10 = 2782` ✅ (предсказано заранее, совпало) |
> **Как это выглядит в конфиге:** сам код уставки лежит в поле 4, а **человекочитаемая**
> температура — только в текстовой подписи поля 2, которую контроллер собирает сам:
> `#Z8817=9,'Установить целевую температуру 5.2 для контура Спальня',10034,'2782'`.
> 🔴 **Раньше я ошибочно считал `'2950'`/`'2968'` «ссылкой на отсутствующий объект»** —
> это была уставка, просто закодированная.
>
> ⚠️ **Питфолл метода:** на **двух** точках линейная формула подбирается бесконечно многими
> способами. Я нашёл `(t+273)*10` на 22 и 23.8, но **не вшивал её в парсер**, пока Alex не дал
> третью точку (5.2). Правило: **≥3 точки, прежде чем кодировать формулу.**
**Что раскрыто в YAML (type 9)** — ✅ **финальные имена полей (2026-09-17): `descr`/`target`/`value`**,
сырьё в `raw_value` (Alex: «ОЧЕВИДНО ИЛИ НЕТ ЧТО НАМ НУЖНЫ ПОЛЯ descr, target id и value который блядь 5,2»):
```yaml
- id: 8817
descr: Установить целевую температуру 5.2 для контура Спальня
target: 10034
target_name: Спальня # развёрнуто из id (тип 16 = контур)
target_type: 16
value: 5.2 # ← Цельсии, то, что правит человек
raw_value: '2782' # сырой код из конфига (источник истины для сборки)
- id: 9826
descr: 'Включить реле «Реле 1: Конвектор кухня»'
target: 9263
target_name: 'Реле 1: Конвектор кухня'
target_type: 14 # 14 = реле
value: true # ← раскодировано (было '1')
raw_value: '1'
```
`value`**раскодированное** значение (Цельсии / `true` / `false`), `raw_value` — сырьё.
Декодирование уставки — **только** при точном совпадении формулы (`|round(t*10)-(код-2730)| < 0.5`);
иначе `value` = сырая строка без `raw_value`. Энкодер собирает строку из `raw_value`, не из `value`.
### 8.14. `objcmd <id> "1 %0"` — запись значения в объект
Скрипт `#Z<id> = [59, 'objcmd <target> "1 %0"', <значение>, 0, <флаг>]` пишет `<значение>`
в объект `<target>`:
```yaml
- id: 8818
script: objcmd 8700 "1 %0"
args: [14.5, 0, 1]
target: 8700
target_name: Температура Детская # развёрнуто из id
set_value: 14.5 # ← «= 14.5»
```
🔴 **Питфолл имён:** у **типа 1** (виртуальный датчик) имя лежит в **поле 2**, а не в поле 1:
`#Z8700=1,'0','Температура Детская',…`. Наивный `fields[1]` даёт `'0'`.
Использовать хелпер **`_object_display_name(fields)`** — он знает про это исключение.
Проверено: `target_name` был `'0'`, после фикса — «Температура Детская».
### 8.15. Финальное состояние конвертеров (2026-09-17, вечер)
| Артефакт | Путь | Состояние |
|---|---|---|
| Сырой конфиг | `zont_config/config_local_2026-09-17_16-13-28.txt` | 34 963 Б, снят с `http://192.168.0.50/config.txt` |
| YAML | `zont_config/config_local_2026-09-17_16-13-28.yml` | в проекте, round-trip ✅ |
Round-trip чистый на **6/6** конфигах (боевой, 3 архива, `14-16-35`, `16-13-28`).
Осталось два `raw`: `8195` (SMS, тело — YAML-якорь `*id002`) и `9500` (type 5, `params` содержит якорь).