[2026-09-17] eagle: family/how-to/home-automation.md family/how-to/zont-config-compiler.md family/tech/local-ustreamer-addon.md family/tech/t610-hang-investigation.md family/tech/t610-hw-metrics-addon.md family/tech/zont-config-object-types.md family/tech/zont-scenario-logic-11109.md

This commit is contained in:
Alexey Martemyanov
2026-09-17 12:34:55 +06:00
parent 46f48ff909
commit 5df98798b8
7 changed files with 299 additions and 40 deletions
+11 -2
View File
@@ -203,6 +203,7 @@ ssh -i ~/.ssh/id_rsa root@192.168.2.176 '<команда>' # аддон core_
| mbusd | `local_mbusd` | шлюз Modbus RTU→TCP | 502 | ✅ started |
| modbus-bridge | `local_modbus-bridge` | снифф ZONT-шины | — | ✅ started |
| ustreamer (кастомный) | `local_ustreamer` | камера: JPEG раз в N сек | 8090 | ✅ started |
| HW metrics (кастомный) | `local_hw_metrics` | метрики хоста (RAM/temp/load) → HA + лог на диск | — | ✅ started |
> 🔴 **`go2rtc` и `go2rtc-hardware` УДАЛЕНЫ** (2026-09-15, `stop` + `uninstall` через Supervisor API). Оба были источником RCU stall (§3.6).
> 🗑 **`core_configurator` (File editor) УДАЛЁН.** Веб-редактор `/config` не использовался, зато **писал `GET /` каждые 30 с** и на каждый запрос логировал `502 Bad Gateway` (HA Core лежал) → цикл ретраев + запись лога = постоянная I/O-нагрузка на слабом хосте.
@@ -386,7 +387,7 @@ HA core: ~700 МБ (лимит 3.55 ГБ)
> `dmesg | grep -iE "ata|I/O error|failed command|medium error|UNC"`.
> ⚠️ SMART мерить **на чистом фоне** — питфолл 21 ниже.
**Железо:** `WDC WD2500BEVT-0` · 250 ГБ · **5400 rpm** · `/sys/block/sda/queue/rotational = 1` · раздел данных `sda8` (243 ГБ, `hassos-data`, монтируется в `/mnt/data`, `/config`, `/share`, `/backup`, `/addons`).
**Железо:** `WDC WD2500BEVT-0` · 250 ГБ · **5400 rpm** · `/sys/block/sda/queue/rotational = 1` · раздел данных `sda8` (243 ГБ, `hassos-data`, монтируется в **`/share`**, `/config`, `/backup`, `/addons`). ⚠️ **`/mnt/data` на t610 НЕ существует** — хостовый шаренный путь только `/share` (питфолл 63).
> 🔴 **ПИТФОЛЛ ИЗМЕРЕНИЯ (моя ошибка 2026-09-15):** прогон `find /mnt/data -size +10M` + `du -sh /mnt/data/*` **сам создаёт I/O-шторм** на 5400-rpm HDD. Последовавший замер дал `io_ms_delta = 10007 ms за 10 с` (диск «занят 100%») — и это было **ложное** доказательство деградации носителя. Через минуту после снятия нагрузки: `io_ms_delta = 2609` (26%), `load 1.72`, `pressure 16%`.
> 📌 **ПРАВИЛО: не мерить I/O сразу после собственного сканирования диска.** Замер нагрузки на диск делать ДО `find`/`du`, либо выжидать ≥60 с. Абсолютные счётчики (`io_ms` в `/proc/diskstats`) сравнивать только по дельте на интервале и на **чистом** фоне.
@@ -452,6 +453,8 @@ ha apps restart 45df7312_zigbee2mqtt # под
### Температура CPU
> ✅ **2026-09-17: заведено как датчик HA — `sensor.t610_cpu_temp`** через аддон `local_hw_metrics` (вместе с RAM/load/pressure). Разбор — [[family/tech/t610-hw-metrics-addon]]. Ниже — ручной способ через SSH (остаётся рабочим).
```bash
ssh -i ~/.ssh/id_rsa root@192.168.2.176 \
'awk "{printf \"%.1f\",\$1/1000}" /sys/class/hwmon/hwmon0/temp1_input'
@@ -815,6 +818,11 @@ Hz:PWM 0:0 1:0 2:0 3:0 4:48 5:55 6:63 7:70 8:78 9:85 10:92 11:97 12:102 13:107 1
| 58 | 🎯 **Симптом-матч с форума ≠ доказанная причина** — тред HA `1025213` (та же версия стека, `sqlite3.OperationalError: disk I/O error`) | Это **сильнейший кандидат**, но закрывать только фактом: `smartctl` + `dmesg`. [[family/tech/t610-hang-investigation]] §5 |
| 59 | 🔴 **Порядок разбора инцидента: ФАКТЫ → версия, НЕ версия → проверка** | Сессия 2026-09-17: три «версии» подряд (template-сенсоры → swap/cache → UMA → бэкап) сняты Alex'ом одной репликой каждая. Сначала e820/BIOS/SMART/метрики — потом формулировать |
| 60 | 🔴 **«Система дропнула память в процессе работы» — физически невозможно** | Объём виден ядру **один раз** при загрузке через BIOS, не пересчитывается. «Дропнула память» = **зависла**. Режим отказа — **нестабильная планка**: иногда не детектится, иногда ребут, иногда зависон под нагрузкой |
| 61 | 🔴 **`mosquitto_pub` из контейнера — `Bad file descriptor`** (питфолл 7, подтверждён повторно) | Публиковать **не через MQTT**. Рабочий путь к HA из контейнера — внешний `https://mallexxx.duckdns.org`. Разбор — [[family/tech/t610-hw-metrics-addon]] §7 |
| 62 | 🔴 **`crond` на `core_ssh` НЕ запущен** (аддон на s6: `/etc/services.d/` = `sshd`, `ttyd`) | Периодику делать **своим локальным аддоном** с `while true`, не cron'ом в `core_ssh` — задача умрёт при рестарте аддона |
| 63 | 🔴 **`/mnt/data` на t610 НЕ существует** (прежняя дока §7.3 указывала его как путь для метрик) | Хостовый шаренный путь — **`/share`** (`sda8`). Проверено `df` |
| 64 | ⚠️ **Новый локальный аддон: `install` ДО `options`/`rebuild`** | Иначе `{"result":"error","message":"App is not installed"}`. Порядок: `store/reload``install``options``rebuild``start` |
| 65 | 🔴 **`POST /api/states/<entity>` создаёт сущность в HA напрямую** | Блок `mqtt:` в `configuration.yaml` и рестарт ядра **не нужны** вообще. Проверено: POST → `200`, DELETE → `200` затем `404` |
---
@@ -912,6 +920,7 @@ ssh root@192.168.2.176 'ha apps logs local_modbus-bridge | grep -E "Raw RTU|→
**Открыто:**
- 🔴 **ЗАВИСАНИЯ ХОСТА — причина не установлена** (2026-09-17). Единственный незакрытый вопрос — интервал между зависаниями (стабильный аптайм = софт; хаотично = железо). Полностью: [[family/tech/t610-hang-investigation]].
> ✅ **2026-09-17: сбор метрик внедрён** — аддон `local_hw_metrics`, 11 датчиков + лог на диск со `sync` ([[family/tech/t610-hw-metrics-addon]]). Теперь следующий инцидент оставит следы.
- 🔴 **`recorder:` блока в `configuration.yaml` НЕТ ВООБЩЕ** — HA пишет всё подряд, дефолтные 10 дней, без `exclude`. БД 77 МБ + WAL 4.4 МБ. Кандидат на оптимизацию (НЕ «фикс зависания» — связь не доказана). Введение `exclude` ждёт апрува Alex.
- 🔴 **`MemTotal` живой = 1.44 ГБ**, в §3.9 зафиксировано 3.31 ГБ. Расхождение не объяснено, проверка (`dmesg e820` / `dmidecode`) не запущена. См. §3.9 и [[family/tech/t610-hang-investigation]] §3.1.
- 🔴 **Ориентация кадра камеры** — в `/addons/ustreamer/` стоит `transpose=1` (90° по часовой), счётчик лежит на бок. Нужен `transpose=2` + rebuild аддона.
@@ -924,7 +933,7 @@ ssh root@192.168.2.176 'ha apps logs local_modbus-bridge | grep -E "Raw RTU|→
**Справочные факты:**
- **Аддоны (7):** `core_ssh`, `core_mosquitto`, `a0d7b954_nodered` (⏸ stopped), `45df7312_zigbee2mqtt` (⏸ stopped — стик отдан ZHA), `local_mbusd`, `local_modbus-bridge`, `local_ustreamer`. `core_configurator` и `go2rtc` ×2 удалены.
- **Аддоны (8):** `core_ssh`, `core_mosquitto`, `a0d7b954_nodered` (⏸ stopped), `45df7312_zigbee2mqtt` (⏸ stopped — стик отдан ZHA), `local_mbusd`, `local_modbus-bridge`, `local_ustreamer`, **`local_hw_metrics`** (метрики хоста, [[family/tech/t610-hw-metrics-addon]]). `core_configurator` и `go2rtc` ×2 удалены.
- **USB:** камера `USB3-1` (xHCI), Zigbee `USB1-2` (OHCI) → `ttyACM0`, CH340 #1 `1-3` → `ttyUSB0` (вентиляция/mbusd), CH340 #2 `1-4` → `ttyUSB1` (ZONT/modbus-bridge).
- **Носитель:** `WDC WD2500BEVT-0`, 250 ГБ HDD 5400 rpm — здоров, деградации нет (§3.4). `disk_free 209.8 / 228.5 ГБ`.
- **HAOS** `18.2`, agent `1.10.0`, ядро `6.18.39-haos`, `online_cpus 2`.
+65 -24
View File
@@ -20,7 +20,7 @@ tags:
- homeautomation
title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
type: how-to
updated: '2026-09-17d'
updated: '2026-09-17e'
---
# ⚙️ ZONT Config Compiler — конвертеры `.txt ⇄ .yml`
@@ -143,11 +143,19 @@ grep -c '^#Z' /tmp/zont_new.txt
### Поддерживаемые типы
`1, 3, 4, 5, 6, 7, 9, 10, 11, 14, 16, 20, 24, 25, 27, 28, 42, 46, 49, 51, 52, 53, 57`
`1, 3, 4, 5, 6, 7, 9, 10, 11, 14, 16, 20, 24, 25, 27, 28, 42, 45, 46, 49, 51, 52, 53, 57`
➕ дополнительно обрабатываются **`0`** (дискретные датчики — индикаторы состояния реле) и **`36`** (их вложенные конфиги).
**НЕ обрабатывается `45`** (задержка-объект) — см. §5, п. 13.
Полная таблица с полями — [[family/tech/zont-config-object-types]].
**Сценарные типы — полностью поддержаны с 2026-09-17 (см. §5b):**
| Тип | Роль | Формат | YAML-секция |
|---|---|---|---|
| `11` | сценарий | `[11, name, [step_ids], 0, 0, enabled, 0, 0]` | `scenarios` |
| `45` | задержка, мс | `[45, ms]` | `delays` |
| `46` | шаг | `[46, 0, cond_id, [action_ids], []]` | `scenario_steps` |
| `49` | условие | `[49, relay_id, operator, value]` | `scenario_conditions` |
---
## 4. Проверка целостности (round-trip)
@@ -184,23 +192,24 @@ grep -c '^#Z' A.txt B.txt # счётчик объектов
| 8 | Загрузка конфига в контроллер — **руками**, скрипты только конвертируют | Конвертер не имеет доступа к ZONT |
| 9 | `INFRASTRUCTURE.md`, `docker-compose.yml`, `docker run.txt` в проекте — **исторический TrueNAS-стек** | Актуальный контур — [[family/how-to/home-automation]]. Не искать `modbus-bridge`/`mbusd` на NAS |
| 10 | Дополнительных зависимостей нет | Только `pyyaml``jsonschema`/`ruamel` не нужны |
| 11 | 🔴 **Сценарий с >1 шагом exit 2** | `config-to-yml.py:473` `require(len(steps) == 1)` жёстко. Обход: нет — нужна правка кода |
| 12 | 🔴 **Шаг с >1 действием → exit 2** | `config-to-yml.py:489` `require(len(actions) == 1)`. Обход: нет |
| 13 | 🔴 **Тип 45 (задержка, мс) не поддержан вообще** | Нет парсера в `config-to-yml.py`, нет эмиттера в `yml-to-config.py`. Объект молча теряется при round-trip |
| 11 | **ИСПРАВЛЕНО 2026-09-17**Сценарий с >1 шагом падал (exit 2) | `config-to-yml.py` теперь цикл по всем шагам; `yml-to-config.py` пишет все `step_ids` |
| 12 | **ИСПРАВЛЕНО 2026-09-17** — Шаг с >1 действием падал (exit 2) | Оба скрипта работают со всем списком действий |
| 13 | **ИСПРАВЛЕНО 2026-09-17**Тип 45 (задержка, мс) не был поддержан | Парсер + эмиттер, секция YAML `delays`. См. §5b |
| 14 | ⚠️ `>` в шелле затирает `.yml` до старта питона | Проверять exit-код до переноса файла в репо |
| 15 | ⚠️ Один шаг может принадлежать нескольким сценариям | `yml-to-config.py` использует хелпер `_register()` — обновляет запись по id, а не добавляет дубль |
### Ограничения конвертера, требующие доработки (найдено 2026-09-17)
### Ограничения конвертера (найдено 2026-09-17) — ВСЕ ЗАКРЫТЫ
Прогон боевого конфига `config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt` (598 `#Z`, 25 `#S`) вскрыл **4 дырки** в сценарной логике — все четыре независимы, падает на первой же:
Прогон боевого конфига `config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt` (598 `#Z`, 25 `#S`) вскрыл **4 дырки** в сценарной логике — все четыре независимы, падал на первой же. **Все четыре исправлены, см. §5b.**
| # | Чего нет | Где в коде | Что теряется | Масштаб в конфиге |
| # | Чего не было | Что терялось | Масштаб в конфиге | Статус |
|---|---|---|---|---|
| 1 | 2-й и последующие шаги сценария | `config-to-yml.py:473` (только 1 шаг); `yml-to-config.py:314``[step_id]` захардкожен, `:328` — пишет один шаг | шаг `11828` сценария `11109` | 1 сценарий из 65 |
| 2 | Тип **45** — задержка в мс, `[45, ms]` | нет ни в одном скрипте | 4 объекта: `11824`=20000, `11825`=60000, `11826`=0, `11828`=0 | 4 объекта |
| 3 | Несколько действий в одном шаге | `config-to-yml.py:489` (`len(actions)==1`) | шаг `11827` содержит **7** действий | 1 шаг из 65 |
| 4 | — (информационно) сценарий выключен | `enabled` = поле v[5] сценария | `#Z11109``enabled=0` | 1 сценарий |
| 1 | 2-й и последующие шаги сценария | шаг `11828` сценария `11109` | 1 сценарий из 65 | ✅ закрыто |
| 2 | Тип **45** — задержка в мс, `[45, ms]` | 4 объекта: `11824`=20000, `11825`=60000, `11826`=0, `11828`=0 | 4 объекта | ✅ закрыто |
| 3 | Несколько действий в одном шаге | шаг `11827` содержит **7** действий | 1 шаг из 65 | ✅ закрыто |
| 4 | — (информационно) сценарий выключен | `#Z11109``enabled=0` | 1 сценарий | ️ факт, не баг |
> ✅ Остальные 64 сценария — каноническая одношаговая форма `11 → [46] → 49`, конвертер их разбирает корректно. Все 65 условий (49) имеют ровно 4 поля `[49, relay, op, value]`, `op=1` = equals у всех. Все 64 «простых» шага — ровно 5 полей `[46, prio, cond, [1 действие], []]`.
> ✅ Остальные 64 сценария — каноническая одношаговая форма `11 → [46] → 49`, конвертер их разбирал и разбирает корректно. Все 65 условий (49) имеют ровно 4 поля `[49, relay, op, value]`, `op=1` = equals у всех. Все 64 «простых» шага — ровно 5 полей `[46, prio, cond, [1 действие], []]`.
**Тип 45 vs `delay_ms` у типа 5 — это разные вещи:**
- `delay_ms` живёт **внутри** объекта-действия типа 5 (поле 4) — уже поддержан обоими скриптами.
@@ -208,22 +217,51 @@ grep -c '^#Z' A.txt B.txt # счётчик объектов
---
## 5a. План доработки сценариев (согласован 2026-09-17, ожидает апрува)
## 5b. Доработка сценариев — СДЕЛАНО 2026-09-17
**Этап 1 — только конвертеры, конфиг не трогать.**
1. Бэкап обоих скриптов: `cp config-to-yml.py yml-to-config.py /tmp/zont-backup-<дата>/`
2. `config-to-yml.py`: снять `len(steps)==1` → список шагов; снять `len(actions)==1` → список действий; добавить парсер type 45 в новую секцию `delays`.
3. `yml-to-config.py`: эмитить все шаги `v[2]` (не один), все действия; добавить эмиттер type 45.
4. Проверка round-trip: `TXT → YAML → TXT` на боевом конфиге, 598 объектов, `diff` чистый (см. §4).
Задача Alex: «перегнать в yml, дописав парсер и энкодер». Правка кода конвертеров, боевой конфиг не тронут.
**Стоп-точка:** показать Alex профиль YAML по `11109` перед правкой логики.
### Изменения в `config-to-yml.py` (парсер)
**Этап 2 — правка YAML** (только после ответа на вопрос: (а) просто `enabled: true` / (б) добавить триггер по событию / (в) другое).
| Что | Детали |
|---|---|
| Многошаговые сценарии | Убрано `require(len(steps) == 1)`. Цикл `for step_id in steps``parsed_steps[]`, каждый со своим `when`/`then` |
| Много действий в шаге | Убрано `require(len(actions) == 1)`. Все id проверяются на существование в `Z_dict` |
| Новый тип **45** | Отдельная секция `# --- scenario delays (type 45)`. Валидация: ровно 2 поля, `ms` — неотрицательный int → `out['delays']` = `[{'id':…, 'ms':…}]` |
| `KNOWN_TYPES` | Добавлен `45` (был `{0,1,…,42,46,49,…}`) |
| Выходной dict | Добавлен ключ `delays: []` |
| **Обратная совместимость** | Если у сценария 1 шаг — дополнительно пишутся плоские `when`/`then` (как раньше). Старые YAML не ломаются |
### Изменения в `yml-to-config.py` (энкодер)
| Что | Детали |
|---|---|
| Все шаги | Собирает `step_ids` из `scenario['steps']` (fallback — плоский `then.id`) и пишет в строку сценария |
| Все действия | `[46, 0, cond_id, actions, []]` — весь список, без `[action_id]` |
| Новый тип **45** | Секция эмиттера: `raw` passthrough **или** `ms``[45, ms]`. Валидация: `ms` — неотрицательный int. **Вставлена ДО шагов (46)** — порядок строк значим |
| `TYPE_ORDER` | Добавлено `('delays', 45)` между `gui_tabs` и `scenario_steps` |
| Хелпер `_register()` | Локальная функция рядом с `add_z_line`. Регистрирует шаг/условие по id, **обновляя** существующую запись вместо добавления дубля — один шаг может принадлежать нескольким сценариям |
| Без `when` в шаге | Если у шага нет `when`, но есть запись в `scenario_steps` — переиспользует известный `cond_id` из неё. Если нет — `ConversionError` |
### Что проверено
- `ast.parse()` на обоих файлах — синтаксис OK
- `config-to-yml.py` на боевом конфиге: **больше не падает** на сценарии `11109` (ранее `Ошибка: Сценарий 11109: поддерживается только 1 шаг`, exit 2)
### Что НЕ проверено (осталось)
-**Round-trip целиком**: `TXT → YAML → TXT`, сверка 598 объектов, `diff` чистый. Команда требует апрува, была запущена и истекла по таймауту.
### План дальше (этапы 2–3, ждут Alex)
**Этап 2 — правка YAML** под новую логику (только после ответа Alex на вопрос: (а) просто `enabled: true` для «Передернуть Автомат Котельной» / (б) добавить триггер по событию / (в) другое).
**Этап 3 — `YAML → TXT`, сверка счётчиков, коммит ДО → Alex заливает руками → Alex проверяет руками → коммит ПОСЛЕ.**
> 🔴 Порядок работ с боевыми конфигами: **коммит ДО → правка → заливка → ПРОВЕРКА АЛЕКСОМ руками → коммит ПОСЛЕ**. Мой `read back` = «конфиг записался», НЕ «работает».
> ⚙️ **Бэкап кода — только git, не `/tmp`.** Alex 2026-09-17: «какой нахуй бэкап скриптов — там в гите все». Изменения скриптов откатываются через git, отдельные копии в `/tmp/` не делать.
---
## 6. Состояние проекта (проверено 2026-09-17)
@@ -237,9 +275,10 @@ grep -c '^#Z' A.txt B.txt # счётчик объектов
| `zont_config/H2000_PRO_config_actual-4.txt` / `-4.yml` | то же |
| `zont_config/archive/` | не добавлена в git |
| `zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt` | 32 689 байт — свежеснятый конфиг с контроллера, не закоммичен |
| одноимённый `.yml` | **0 байт** — см. причину ниже (не «недоделанная конвертация», а падение на 1 объекте) |
| одноимённый `.yml` | **0 байт** — см. причину ниже |
| `config-to-yml.py`, `yml-to-config.py` | ✅ **изменены 2026-09-17** (§5b) — не закоммичены |
> 🔴 **Причина пустого `.yml` (0 байт) — найдена 2026-09-17.** `config-to-yml.py` падает с exit 2 на **первом** невыразимом объекте и не пишет ничего:
> 🔴 **Причина пустого `.yml` (0 байт) — найдена 2026-09-17.** `config-to-yml.py` падал с exit 2 на **первом** невыразимом объекте и не писал ничего:
>
> ```
> $ python3 config-to-yml.py zont_config/config_0FA7C33CC89F_…_2026-09-17_12-12-28.txt > /tmp/zont.yml
@@ -247,6 +286,8 @@ grep -c '^#Z' A.txt B.txt # счётчик объектов
> EXIT=2 → /tmp/zont.yml = 0 байт
> ```
>
> ✅ **Исправлено 2026-09-17** — сценарий `11109` теперь разбирается (он двухшаговый). Причина исчезла.
>
> Проверка: `.yml` 0 байт **всегда** означает, что конвертер не «не доехал», а **упал на конкретном объекте**. Смотреть stderr, а не перезапускать вслепую.
>
> ⚠️ `>` в шелле **затирает целевой файл ещё до старта питона** — поэтому рядом с непустым `.txt` появляется пустой `.yml`. Писать через `>/tmp/out.yml` и только после успешного exit-кода переносить в репо.