From 9f645e2b37fcae2e86fbe107214afe0190397639 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 23 Sep 2026 15:53:16 +0600 Subject: [PATCH] [2026-09-23] eagle: family/tech/zont-api.md personal/projects/zont-config-compiler.md --- family/tech/zont-api.md | 50 ++++++++++++++++- personal/projects/zont-config-compiler.md | 67 ++++++++++++++++++++++- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/family/tech/zont-api.md b/family/tech/zont-api.md index 0efe437e..c3f4e976 100644 --- a/family/tech/zont-api.md +++ b/family/tech/zont-api.md @@ -19,12 +19,13 @@ tags: - reference title: "🌐 ZONT — облачный API, локальный UI, прошивки" type: reference -updated: 2026-09-17c +updated: 2026-09-23 (§5bis — модель режимов отопления в API: старая/новая схема, `thermostat_ext_modes_config`, нумерация зон. Оракул для неразобранного типа 20 конфига) --- # 🌐 ZONT — облачный API, локальный UI и прошивки > **Раздел §1–§7** — облачный API `my.zont.online` (исследован 2026-09-17). +> **Раздел §5bis** — модель режимов отопления в API (оракул для типа 20 конфига). > **Раздел §6bis** — локальный интерфейс контроллера (там реально есть конфиг). > **Раздел §10** — настроечная утилита и прошивки. @@ -186,6 +187,53 @@ POST https://my.zont.online/api/load_data --- +## 5bis. 🌡️ Модель режимов отопления в API (оракул для типа 20) + +> **Зачем:** тип 20 конфига (`heating_modes`) не имеет размеченных полей. Единственный источник +> семантики — глава «Управление отоплением (ZONT H)» в родной доке: +> `~/Automation/HA-ZONT-Modbus/zont_api_docs/zont_api_docs.txt:2646-2850`. +> Разбор применения — `personal/projects/zont-config-compiler.md` §4.2. + +**Две схемы, НЕ совместимые между собой.** Прибор работает **либо** по старой, **либо** по новой; +API отдаёт поля обеих ради обратной совместимости, **новые имеют приоритет**. + +| Модель прошивки | Поддержка нового формата | +|---|---| +| ZONT H-1, H-1V, H-1B, H-2 | ≥ `xx:82` | +| ZONT H-1000, H-2000 | любая | + +**Старый формат:** +- `thermostat_mode` — строка: `econom` (Эконом) ‖ `comfort` (Комфорт) ‖ `schedule` (Расписание) ‖ `idle` (Антизаморозка/выключен). +- `thermostat_mode_temps` — `{econom, comfort, idle}` (числа °C) + `full_off` (bool; `true` → вместо Антизаморозки используется режим «Выключен»). + +**Новый формат:** +- `thermostat_ext_mode` (int) — номер **текущего** режима = индекс в `thermostat_ext_modes_config`. +- `thermostat_ext_modes_config` — **объект, НЕ массив**. Ключи = номера режимов с нуля. + Каждый режим: + | Поле | Тип | Смысл | + |---|---|---| + | `active` | bool | режим настроен (существует) | + | `hidden` | bool | кнопка скрыта на странице управления | + | `name` | string | название режима | + | `schedule_number` | int\|null | `null` — расписание не используется; `1` — недельное; `2..8` — расписание пн..вс (день недели) | + | `zone_temp` | object | zone → `null` (не задана, напр. режим по расписанию) ‖ `false` (зона отключена) ‖ число (°C) | + | `zone_sensors` | object | zone → номер слота целевого датчика (`thermometers[].slot`) ‖ `null` (датчик по умолчанию) | + +- **Нумерация зон:** `0` = **ГВС**, `1` = основная отопительная зона, `2+` = дополнительные. + (В `thermometers[].functions[].zone` — наоборот: основная зона = `1`.) + +**Смежное:** +- `tempstep` (float) — шаг кнопок −/+ в веб-интерфейсе и приложении. +- `tempschedule` — `week` (ключи `"0".."6"`: 0 = пн … 6 = вс, массивы из 24 температур) ‖ `day` (не используется приборами). +- `thermometers[]` — `uuid`, `is_assigned_to_slot` (при `false` датчик игнорировать), `slot`, `serial`, `name`, `color`, `functions[]` (`f` ∈ `control`/`control-reserve`/`heat`/`outside`, + `zone`), `limits{low,high}`, `last_value_time`, `last_state` (`ok`/`malfunction`), `last_value`. +- OpenTherm: `ot_enabled`, `ot_mode` (`analog`), `ot_max_setpoint`, `ot_min_setpoint`, `ot_dhw_setpoint`, `ot_config` (`["ch","dhw"]`), `ot_save_params`, `ot_max_ml`, `ot_min_wp`. + +> ⚠️ В родной доке отдельно оговорено: составной параметр можно менять **частично** — не указанные +> поля сохраняют прежнее значение («была изменена только целевая температура режима Комфорт»). +> Также: для новых прошивок число в запросе = **номер задаваемого пользователем режима**, а не индекс. + +--- + ## 6. Ответ на вопрос «доделывать или переделать с 0» **Переделать с 0 — не на что.** API не предоставляет доступа к сценариям/реле, поэтому diff --git a/personal/projects/zont-config-compiler.md b/personal/projects/zont-config-compiler.md index b41512fc..5ae67f8a 100644 --- a/personal/projects/zont-config-compiler.md +++ b/personal/projects/zont-config-compiler.md @@ -3,7 +3,7 @@ title: ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml namespace: personal type: how-to created: '2026-09-17' -updated: '2026-09-18 (круг 53 ФИНАЛ + §36: покрытие HA↔bridge↔ZONT подтверждено ПОЛНЫМ — заводить нечего. «ТП: Гардеробная» = ПЕРЕИМЕНОВАНИЕ существующего device slave 100 (#Z8381/#Z8382), НЕ новый slave 113 — артефакт 648 строк, изменены 2 имени, коммит dd8c262. 🔴 главные уроки: (1) датчик УЖЕ был в ZONT под именем «Tuya Zigbee Thermal Sensor» — проверять по slave_id, НЕ по имени; (2) `dining_temperature_2` = уже заведённый «Температура гостиная» (оба slave 101) — имя не идентификатор; (3) h2000_pro_* это сам ZONT, заводить некуда. Круг 52 (slave 113) — тупиковая ветка, откачена. Заливка в контроллер НЕ сделана и не требуется без команды)' +updated: '2026-09-23 (§4.2 — тип 20 «Режимы отопления»: разведка проведена, разметка НЕ подтверждена. В конфиге РОВНО ОДИН объект `#Z8574=20,...`, байт-в-байт одинаков во всех 17 дампах включая H2000 archive; поля не размечены ни в одном конвертере (`config-to-yml.py:1457`, `yml-to-config.py:1767`). Оракул — `zont_api_docs.txt:2646-2850` (`thermostat_ext_modes_config`, зона 0 = ГВС). Ждём UI-эксперимент Alex: ГВС 45 °C → дамп → `3200`→`4500`?) 2026-09-18 (круг 53 ФИНАЛ + §36: покрытие HA↔bridge↔ZONT подтверждено ПОЛНЫМ — заводить нечего. «ТП: Гардеробная» = ПЕРЕИМЕНОВАНИЕ существующего device slave 100 (#Z8381/#Z8382), НЕ новый slave 113 — артефакт 648 строк, изменены 2 имени, коммит dd8c262. 🔴 главные уроки: (1) датчик УЖЕ был в ZONT под именем «Tuya Zigbee Thermal Sensor» — проверять по slave_id, НЕ по имени; (2) `dining_temperature_2` = уже заведённый «Температура гостиная» (оба slave 101) — имя не идентификатор; (3) h2000_pro_* это сам ZONT, заводить некуда. Круг 52 (slave 113) — тупиковая ветка, откачена. Заливка в контроллер НЕ сделана и не требуется без команды)' tags: - personal - zont @@ -413,7 +413,7 @@ echo "=== pass=$pass fail=$fail" | **11** | **Сценарии** | `name`, `steps[]`, `trigger`, `enabled`, `days`/`time`/`interval_ms` | `scenarios` | | 14 | Реле | `name`, `address`, `state` | `relays` | | 16 | Отопительные контуры | `name` | `heating_circuits` | -| 20 | Режимы отопления | `name` | `heating_modes` | +| 20 | Режимы отопления | `name` | `heating_modes` — 🔶 **поля не размечены**, см. §4.2 | | 24 | Сопроцессоры | `address`, `name` | `coprocessors` | | 25 | Отопительные кривые | `name` | `heating_curves` | | 27 | Датчики температуры | `address`, `name` | `temperature_sensors` | @@ -457,6 +457,69 @@ echo "=== pass=$pass fail=$fail" 🔴 **Поле 5 записи 59 = признак литерала** — «в предыдущем поле число, а не id объекта»: `1` у `set`/`objcmd`, `2` у `expr`. **Производное**, в YAML не пишется, энкодер ставит сам (§21.2.1). +### 4.2. 🔶 Тип 20 (Режимы отопления) — ЕДИНСТВЕННЫЙ неразобранный тип + +> **Статус (2026-09-23): разведка проведена, разметка НЕ подтверждена. Ждём данных от Alex (UI-эксперимент).** + +**Что есть.** В конфиге прибора всегда **ровно ОДИН** объект типа 20, и строка **байт-в-байт +одинакова во всех 17 дампах** — от `config_0FA7C33CC89F_…_12-12-28` до `config_local_2026-09-18_11-41-36`, +включая `zont_config/archive/H2000_PRO_config_actual-{2,3,4}.txt`: + +``` +#Z8574=20,'Режим отопления',[8669],[3200],[0],0,[0],10,0,0,0,0 +``` + +В YAML (`config_local_2026-09-18_11-41-36.yml:1342`): +```yaml +heating_modes: +- id: 8574 + name: Режим отопления + raw: [20, Режим отопления, [8669], [3200], [0], 0, [0], 10, 0, 0, 0, 0] +``` + +**Что не сделано.** Оба конвертера пишут только `id` + `name` + `raw[]`, поля **не размечены** — +в отличие от контуров (16), реле (14), modbus (51/52). Точки правки: +`config-to-yml.py:1457` (декодер), `yml-to-config.py:1767` (энкодер, ветка `raw`-only). + +**Оракул.** Родная дока ZONT — `zont_api_docs/zont_api_docs.txt:2646-2850`, глава +«Управление отоплением (ZONT H)». Новая схема режимов = `thermostat_ext_modes_config`: +объект (не массив!), ключи = номера режимов с нуля, значения — объекты режима с полями +`active` (bool), `hidden` (bool), `name` (string), `schedule_number` (null=нет ‖ 1=недельное ‖ 2..8=день недели), +`zone_temp` (zone → `null` ‖ `false` ‖ число °C), `zone_sensors` (zone → slot ‖ `null`). +**Зона 0 = ГВС**, 1 = основная, 2+ = дополнительные. Текущий режим — `thermostat_ext_mode` (индекс). +Старый формат: `thermostat_mode` ∈ {`econom`,`comfort`,`schedule`,`idle`} + `thermostat_mode_temps`. +🔴 Прибор работает **либо** по старой, **либо** по новой схеме, они не совмещаются; для совместимости +API отдаёт старые поля тоже. + +**Гипотеза сопоставления полей** (⚠️ НЕ подтверждена): + +| # | Значение | Кандидат | +|---|---|---| +| 0 | `20` | type tag | +| 1 | `'Режим отопления'` | `name` ✅ | +| 2 | `[8669]` | зоны/контуры (`8669` = «Контур ГВС», тип 16) | +| 3 | `[3200]` | `zone_temp` — `3200` = **32.00 °C** (raw ×100), зона ГВС | +| 4 | `[0]` | `zone_sensors` — `0` = датчик по умолчанию | +| 5 | `0` | `active` / `hidden` битмаск | +| 7 | `10` | **? — якоря нет** | +| 8–11 | `0,0,0,0` | padding / off | + +**Однозначность.** Поле 3 — единственное, где «32.00 °C» неотличимо от кода; поле 7 — вообще без якоря. + +**Метод снятия неоднозначности (предложен Alex, ждёт выполнения).** Alex в веб-морде ZONT +ставит текущему режиму целевую температуру ГВС = **45 °C** → снять свежий дамп +(`curl -s http://192.168.0.50/config.txt -o zont_config/config_local_.txt`) → сравнить строку. +Если `3200` → `4500` — разметка поля 3 подтверждена, размечаем. + +**Развилка.** Если в UI **нет кнопки добавить второй режим** — новый формат прибору недоступен, +секция навсегда останется одной `raw`-строкой; тогда правильнее её **не декодировать**, а пометить +служебной (иначе получим мёртвый код). + +**Доп. факт.** id `3200` в конфиге **отсутствует** как объект (`#Z3200=` не существует) — +значит это литерал-значение, а не ссылка. `8669` существует (тип 16, «Контур ГВС»). +Всего типов в дампе `11-41-36`: 52×101, 5×66, 49×65, 46×65, 11×65, 53×64, 9×42, 14×40, +10×22, 51×19, 36×13, 1×13, 0×13, 16×12, 45×4, 28×3, 27×3, 6×2, 24×2, 7/57/42/4/3/25/20×1. + --- ## 5. 🔴 ФОРМА СЦЕНАРИЯ В YAML (ФИНАЛ)