46 KiB
title, namespace, type, created, updated, tags, aliases, related
| title | namespace | type | created | updated | tags | aliases | related | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml | personal | how-to | 2026-09-17 | 2026-09-17b |
|
|
|
⚙️ ZONT Config Compiler — конвертеры .txt ⇄ .yml
Двусторонние конвертеры между конфигом контроллера ZONT (.txt) и читаемым YAML.
Позволяют править конфиг руками — имена реле, адреса Modbus, интервалы опроса, датчики, сценарии —
не заходя в UI контроллера.
Этот документ — единственный источник истины по конвертерам, типам объектов и логике сценариев. Ранее было три дока (
family/how-to/zont-config-compiler,family/tech/zont-config-object-types,family/tech/zont-scenario-logic-11109) — сведены в один 2026-09-17.
| Проект (Mac) | /Users/admin/Automation/HA-ZONT-Modbus |
| Repo (private) | https://git.mallexxx.duckdns.org/git_admin/HA-ZONT-Modbus |
| Скрипты | config-to-yml.py (TXT → YAML) · yml-to-config.py (YAML → TXT) · test_roundtrip.py |
| Конфиги | zont_config/ — свежие · zont_config/archive/ — историчные |
| Снять живой конфиг | 🔴 curl -s http://192.168.0.50/config.txt — без авторизации |
| ZONT в общем контуре | family/how-to/home-automation §6 |
1. Формат конфига ZONT
Одна запись = одна строка, разделитель CRLF, кодировка windows-1251:
#Z<id>=<тип>,<поле>,<поле>,…
#S<id>=<значение>
| Префикс | Что это | Пример |
|---|---|---|
#Z<id> |
объект конфига: реле, датчик, сценарий, Modbus-устройство | #Z12=14,'Спальня левый',… |
#S<id> |
системная настройка | #S7=H2000_PRO 723 678 |
- Тип объекта — первое поле после
=. - Строки — в
'одинарных кавычках', списки —[...], числа — как есть. #Z<id>=*— маркер (пустой/унаследованный объект).- Порядок строк значим и сохраняется при конвертации.
2. Как работает
config-to-yml.py — TXT → YAML
- Читает файл, кодировка: UTF-8, при неудаче — windows-1251.
- Парсит строки регекспом
^#([ZS])(\d+)=(.*)$(хвостовые пробелы в payload сохраняются). split_payload()— режет payload по запятым с учётом кавычек и вложенных[…].parse_atom()— пусто/''→None,'строка'→ строка,[…]→ список, иначе int → float → строка. ⚠️ Whole-float (1.0) остаётся float — иначе энкодер напечатает1вместо1.0.- Раскладывает объекты по секциям. Вложенное прячет под родителя: тип 52 → внутрь своего 51.
system_settings(#S…) — в начало файла.
yml-to-config.py — YAML → TXT
- Загружает YAML (UTF-8), собирает строки
#Z<id>=…, в порядке типов. - Формат: числа без кавычек, строки в
'…', bool →0/1, пустая строка →'', списки →[…]. - Разворачивает вложенное: тип 52 → отдельными строками после своего устройства.
validate_config()проверяет структуру до вывода. Ошибки →❌ VALIDATION ERRORS, exit 4, файл не отдаётся.- Вывод: windows-1251, переводы строк CRLF.
Коды возврата
| Скрипт | Код | Значение |
|---|---|---|
config-to-yml.py |
1 | неверное число аргументов |
| 2 | ParseError — неизвестный формат строки или неразбираемое значение |
|
yml-to-config.py |
1 | файл не найден |
| 2 | ConversionError — нет raw или неизвестный тип объекта |
|
| 3 | прочее исключение | |
| 4 | ❌ ошибки валидации (файл не выдан) |
Неизвестный тип — падение, а не тихий пропуск. Молча потерять объект хуже, чем не отдать файл.
🔴 Питфолл: дампер пишет в stdout
config-to-yml.py пишет YAML в stdout; -o не существует. main() принимает только входной .txt.
Вызов python3 config-to-yml.py f.txt -o /tmp/x.yml печатает Использование: zont_to_yaml.py <config.txt>,
делает exit 1, а целевой файл остаётся старым (выглядит как «патч не сработал»).
# ✅ правильно — редирект, и сразу в ЦЕЛЕВОЙ файл, не в /tmp
python3 config-to-yml.py zont_config/config_X.txt > zont_config/config_X.yml
echo "exit=$?"; wc -c zont_config/config_X.yml
3. Как пользоваться
Требования: python3 + PyYAML.
cd /Users/admin/Automation/HA-ZONT-Modbus
# 1. Снять конфиг с контроллера (без авторизации)
curl -s http://192.168.0.50/config.txt -o zont_config/config_$(date +%Y-%m-%d_%H-%M-%S).txt
# 2. TXT → YAML (⚠️ в целевой файл, не в /tmp)
python3 config-to-yml.py zont_config/config_X.txt > zont_config/config_X.yml
# 3. Править YAML — имена, адреса, интервалы, пороги, сценарии
# ⚠️ id объектов и их порядок значимы — не переставлять без нужды
# 4. YAML → TXT
python3 yml-to-config.py zont_config/config_X.yml > /tmp/zont_new.txt
# 5. Проверить целостность
python3 test_roundtrip.py zont_config/config_X.txt
# 6. Залить /tmp/zont_new.txt в контроллер (см. §9)
🔴 Главное правило: raw не трогать
Часть полей декодирована (адрес, интервал, регистры, сценарии), часть лежит как raw / raw_params /
raw_field_N — это страховка от потери данных.
Если у объекта пустой raw, скрипт подставит жёстко зашитые дефолты (тип 0 — 16 полей,
тип 36 — [],[],[],10,0), и настройки потеряются молча.
✅ Правь декодированные поля.
raw-поля не удаляй и не «чисти».
Поддерживаемые типы
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 (их вложенные конфиги) — 25 типов.
➕ конструкции логики 47 / 48 / 50 / 59.
📌 README_converters.md перечисляет 23 — пропускает 0 и 36.
4. Типы объектов
| Тип | Объект | Декодируемые поля | Секция в YAML |
|---|---|---|---|
| 0 | Дискретные датчики (индикаторы реле) | register_ref, name, config |
discrete_sensors |
| 1 | Виртуальные датчики | address, name, register_id, пороги, гистерезис, калибровка |
virtual_sensors |
| 3 | SMS-уведомления | name (тело — raw) |
sms_notifications |
| 4 | Контакты пользователей | name, phones |
user_contacts |
| 5 | Действия (actions) | name, output_ref→target, value, raw_params |
actions |
| 6 | Адаптеры | address, name |
adapters |
| 7 | Радиомодули | address, name |
radio_modules |
| 9 | MQTT-команды | name, target_relay→target, value |
relay_commands |
| 10 | GUI-переключатели | name |
gui_switches |
| 11 | Сценарии | name, steps[], trigger, enabled, days/time/interval_ms |
scenarios |
| 14 | Реле | name, address, state |
relays |
| 16 | Отопительные контуры | name |
heating_circuits |
| 20 | Режимы отопления | name |
heating_modes |
| 24 | Сопроцессоры | address, name |
coprocessors |
| 25 | Отопительные кривые | name |
heating_curves |
| 27 | Датчики температуры | address, name |
temperature_sensors |
| 28 | Таблицы сопротивлений | (raw) | resistance_tables |
| 36 | Конфиги дискретных датчиков | raw |
вложено в discrete_sensors[].config |
| 42 | GUI-вкладки | name |
gui_tabs |
| 45 | Задержка, мс — [45, ms] |
ms |
инлайн как wait: |
| 46 | Шаги сценариев | if/then/else/action, flag |
инлайн в scenarios[].steps[] |
| 47 | Лист дерева условий | op, left, right, value |
инлайн в if |
| 48 | Группа условий И/ИЛИ/НЕ | group, children[] |
инлайн в if |
| 49 | Условия сценариев | object, operator, value |
инлайн в trigger / steps[].if |
| 50 | Маска дней недели | [50, 1, 0, 0, <mask>] — raw |
инлайн как объект-условие |
| 51 | Modbus-устройства | slave_id, name, poll_interval, timeout, registers |
modbus_devices |
| 52 | Modbus-регистры | name, register, bit_width, repeat_period, num_vars |
вложены в устройство 51 |
| 53 | Аналоговые выходы | name, min, max, value, offset, scale, flags, address |
analog_outputs |
| 57 | MQTT-топики | topic, sensors |
mqtt_topics |
| 59 | Мини-скрипт ZONT | descr, args[] |
инлайн в then/else |
🔴 Для каждого типа указаны только реально декодируемые поля; остальное —
rawи пишется как есть.
4.1. Сценарии (11 + 46 + 47 + 48 + 49 + 45 + 59)
| Тип | Роль | Формат строки | В YAML |
|---|---|---|---|
11 |
сценарий | [11, name, [step_ids], days, time, f5, interval_ms, 0] |
scenarios |
46 |
шаг | [46, flag, cond_id, [then_ids], [else_ids]] |
инлайн в steps[] |
49 |
условие | [49, object, operator, value] |
trigger / steps[].if |
45 |
пауза | [45, ms] |
wait: ms |
47 |
сравнение | `[47, op, left, value | right]` |
48 |
группа | [48, logic, [ids]] |
{group, children} |
59 |
мини-скрипт | [59, '<код>', arg1, arg2, flag] |
{descr, args} |
5 |
действие над выходом | [5, '<descr>', output_ref, value, …] |
{descr, target, value, params?} |
9 |
команда (реле/контур/режим) | [9, '<descr>', target, '<value>'] |
{descr, target, value} |
Операторы type 47 (порядок UI <, >, =, <=, >=): 0=<, 1=>, 2==, 3=<=, 4=>=.
Логика type 48: 0=И, 1=ИЛИ, 2=НЕ.
Тип 50: 109 = 0b1101101 = пн, ср, чт, сб, вс. Бит 0 = ПН.
Поле 1 записи 46 (#Z8862=46,1,…) → ключ flag, пишется только если ≠ 0. Семантика неизвестна (1 из 69).
5. 🔴 ФОРМА СЦЕНАРИЯ В YAML (ФИНАЛ)
✅ СТАТУС: закрыта, round-trip байт-в-байт чистый.
661 → 661объектов,686 → 686строк,diff= 0.
5.1. Trigger-сценарий
steps состоит ровно из одного шага типа 46 — условие переносится в trigger: наверх,
у шага остаётся action — голый id действия (тело живёт в своей секции):
- id: 9688
name: 'Автомат.: (н/п) (14/12) ВЫКЛ'
enabled: true
trigger:
id: 9726
object: 9494
value: 0
steps:
- id: 9816
action: 9560
Соответствие конфигу:
#Z9688=11,'Автомат.: (н/п) (14/12) ВЫКЛ',[9816],0,0,1,0,0
#Z9816=46,0,9726,[9560],[] ← шаг: условие 9726, действие 9560
#Z9726=49,9494,1,0 ← триггер: объект 9494, значение 0
#Z9560=5,'Выключить выход 14/12: (н/п)',146032,1,0,0,[],0,0,0,256
| YAML | Строка конфига | Поля |
|---|---|---|
id, name |
#Z9688=11,… |
1, 2 |
enabled |
#Z9688 поле 5 |
not (f5 & 8) — производное |
trigger.id |
#Z9726=49,… |
1 (id условия) |
trigger.object |
#Z9726 |
2 (объект под наблюдением) |
trigger.value |
#Z9726 |
4 (значение; поле 3 = оператор) |
steps[].id |
#Z9816=46,… |
1 (id шага) |
steps[].action |
#Z9816 |
4 (список действий шага) |
| тело действия | #Z9560=5,… |
своя строка, в свою секцию |
5.2. Manual / if-конструкция
trigger: отсутствует, условие живёт в шаге как if:, действия — then (+ else парой),
тела инлайн, вложенность рекурсивна:
- id: 8863
if:
id: 8853
group: and
children: [...]
then:
- id: 8862
flag: 1
if:
id: 8858
group: or
children: [...]
then:
- id: 8859
descr: puts "then-text"
args: [0, 0, 0]
else:
- id: 8861
descr: storeev A "alert"
args: [0, 0, 0]
5.3. 🔴 Правило имени списка действий
У шага есть свой if? |
Ключ | Почему |
|---|---|---|
| да — условие внутри шага | then (+ else парой) |
условие и ветки — единая конструкция if/then/else |
нет — условие поднято в trigger: |
action |
от шага остался только список действий |
🔴 Это не противоречие в требованиях Alex: он говорил про разные шаги. «какого хуя там then блядь?!» — про шаг без
if; «ДА БЛЯДЬ! Then конечно!!!» — про шаг сif.
5.4. Правило trigger
Trigger-сценарий ⟺ steps состоит РОВНО из одного элемента, и этот элемент — запись 46.
| Сценарий | steps |
Тип | trigger: наверху |
|---|---|---|---|
9691 |
[9819] (46) |
trigger | ✅ есть |
9628 |
[9756] (46) |
trigger | ✅ есть |
8547 |
[8550] (46) |
trigger, выкл | ✅ есть |
11109 |
[11827, 11828] — 2 |
manual | ❌ нет, if в шаге |
8456 |
27 элементов, среди них 8863 (46) |
manual | ❌ нет, if в шаге |
⚠️ Наличие шага 46 в
steps≠ trigger. Формула «есть шаг 46 → база 1» давала 2 расхождения (11109,8456). Признак — ровно один элемент, и он 46. Alex: «наличием поляtrigger:».
5.5. Сборка поля 5 (yml-to-config.py)
if scenario.get('trigger'):
f5 = 1
elif scenario.get('interval_ms'):
f5 = 2
else:
f5 = 0
if not scenario.get('enabled', True):
f5 |= 8
✅ 0 расхождений на всех 70 сценариях. Признак — наличие trigger:, не шаг 46 в steps.
5.6. Поле 5 типа 11 — четыре типа + enabled
f5 = тип + 8, если сценарий ВЫКЛЮЧЕН
тип: 0 = manual | schedule 1 = trigger 2 = interval
enabled = not (f5 & 8)
| тип | вкл | выкл | кто |
|---|---|---|---|
trigger |
1 (64 шт) |
9 (8547, 8551) |
все «Автомат.: …» |
manual |
0 (11109) |
8 (8456) |
Передернуть Автомат Котельной |
schedule |
0 (8597) |
8 (было до включения) |
«по расписанию» |
interval |
2 (8599) |
10 (было) |
«по интервалу» |
🔴 Включённые значения
schedule/interval(0/2) добыты тостингом на приборе: Alex включил8597/8599в UI, конфиг снят заново. До этого были известны только выключенные8/10.
⚠️ manual и schedule дают одно число 0 — различаются только полями 3/4 (days_mask/time):
у 8597 = 61/3354, у 8456 = 0/0. Alex: «manual от schedule очевидно отличаются наличием
блядь schedule!». Формат time: (час << 8) | минута; days_mask: бит 0 = ПН.
5.7. ⛔ Запрещено в YAML
| Запрещено | Почему |
|---|---|
type (имя типа) |
имени типа в конфиге нет; «какого хуя ты type вернул в сценарии» |
field5, _f5, kind, _kind, _else |
выдуманные/служебные ключи |
base5, словарь {manual:0, schedule:0, trigger:1, interval:2} |
тот же выдуманный маппинг имён |
run_scenario: <имя> |
подстановка имени из чужого объекта — у ссылки только id |
target_name, target_type, raw_value |
дорисовка парсера, в строке конфига их нет |
_step_id, _trigger_id, _then |
выдуманные служебные ключи |
group/op/condition/operator как ключи сценария |
их в конфиге нет |
✅ РАЗРЕШЕНО: trigger: (выводится из тела — один шаг 46), if/then/else/flag — реальные поля
записи 46, action — имя списка у шага с поднятым условием.
Служебные _-ключи — только на нестандартных случаях (_op при операторе ≠ 1).
📌 Общий принцип: YAML-ключ обязан соответствовать полю строки конфига либо выводиться из тела. Подстановка из другого объекта запрещена.
5.8. 🔴 История формы — 9 кругов (не повторять)
| Круг | Что затащил | Реплика Alex |
|---|---|---|
| 1 | type в сценарии |
«я блядь тебе сказал какого хуя ты type вернул в сценарии» |
| 2 | field5 («якобы вербатим») |
«какой нахуй field5 ЕБАТЬ ТЕБЯ В СРАКУ?! МЫ НАХУЯ ЕГО СНОСИЛИ!!!» |
| 3 | base5 + словарь {manual:0,…} |
«КАКОГО ХУЯ ТЫ БЛЯДЬ КРУГАМИ ТО ХОДИШЬ?!» |
| 4 | вырезал trigger: вообще |
«наличием поля trigger: блядь если ты еблан тупоголовый не можешь догадаться!» |
| 5 | action вместо then у шага с if |
«ты блядь теперь if-then конструкции запорол» |
| 6 | deepcopy вместо pop → анкоры |
«че это за хуяня блядь?! нормально же блядь все было!» |
| 7 | then → action |
«откуда там then блядь» |
| 8 | if убран из dump_step → потеря условия |
«ты блядь теперь if-then конструкции запорол» |
| 9 | action вместо then (повторно) |
«ДА БЛЯДЬ! КАК ТЫ БЛЯДЬ ДУМАЕШЬ?! Then конечно!!!» |
Корень: я подменял решение Alex своим и считал это работой. Когда он говорит «наличием поля X» — это ответ, а не повод искать обходной путь.
Отвергнутые итерации формы (не возвращаться):
| Итерация | Форма | Почему отвергнута |
|---|---|---|
| 1 | blocks + extra_links |
порядок терялся, сценарий = список цифр |
| 2 | steps[].{when, then} + if/group/op |
«а это не if а триггер» |
| 3 | HA-синтаксис (triggers/platform: state) |
«не надо натягивать сову структуры на глобус HA syntax» |
| 4 | trigger + steps[].{id, action} |
✅ принята |
5.9. Ключевые факты о сценарии 11109
вкл virt.Запретить(11191) → выкл Автомат(11030) → пауза 20 с(11824)
→ вкл Автомат(11029) → пауза 60 с(11825) → выкл virt.Запретить(11192) → пауза 0(11826)
Условие 11823: virt.Запретить(11190) == 0 — защита от повторного передёргивания: реле ставится
в 1 первым действием, условие требует 0 → пока реле в 1, сценарий не перезапустится. Минимальный
интервал между передёргиваниями = 80 с.
6. Проверка целостности (round-trip)
cd /Users/admin/Automation/HA-ZONT-Modbus
python3 test_roundtrip.py # свежайший из zont_config/
python3 test_roundtrip.py zont_config/config_X.txt
Exit: 0 чисто / 1 расхождения / 2 ошибка запуска.
Успех: ✅ ROUND-TRIP ЧИСТЫЙ — расхождений нет + счётчики #Z до/после.
Ручная проверка
cd /tmp && rm -rf zont-rt && mkdir zont-rt && cd zont-rt
SRC=/Users/admin/Automation/HA-ZONT-Modbus/zont_config/<файл>.txt
python3 /Users/admin/Automation/HA-ZONT-Modbus/config-to-yml.py "$SRC" > a.yml # проверить exit!
python3 /Users/admin/Automation/HA-ZONT-Modbus/yml-to-config.py a.yml > b.txt
iconv -f cp1251 -t utf-8 "$SRC" | tr -d '\r' | sort > A.txt
iconv -f cp1251 -t utf-8 b.txt | tr -d '\r' | sort > B.txt
diff A.txt B.txt # пусто = чисто
🔴 Сравнивать множеством строк (
sort+diff), НЕ построчно.yml-to-config.pyпишет объекты в порядкеTYPE_ORDER, а в исходнике порядок другой → наивныйdiffдаёт ~56 ложных расхождений.🔴 Проверять
wc -cцелевого файла напрямую, не через пайп.iconv … | grep -cна пустом промежуточном файле даёт ложный «успех» (реальный случай:b.txtбыл 0 байт, а вывод — «598 → 598, чисто»).📌 Кодировка: источник бывает UTF-8, выход всегда windows-1251 → нормализовать
iconvс обеих сторон.⚠️
>затирает целевой файл ещё до старта питона — пустой.ymlрядом с непустым.txtозначает падение конвертера: смотреть stderr.
7. Питфоллы
7.1. Процесс
| # | Питфолл | Как обойти |
|---|---|---|
| 1 | 🔴 Вывод yml-to-config.py — windows-1251 + CRLF |
Редирект в файл, передавать байтами. Не копипастить из терминала |
| 2 | 🔴 Пустой raw у типов 0/36 → молчаливые дефолты |
Не трогать raw*-поля |
| 3 | 🔴 Правка #S… в YAML бессмысленна — они raw_payload |
Системные настройки — только через UI |
| 4 | YAML на входе — строго UTF-8 | Править YAML в UTF-8, .txt не пересохранять |
| 5 | 🔴 Гонять конвертер в ЦЕЛЕВОЙ файл, а не в /tmp |
Проверка в /tmp не проверяет артефакт. Alex видел type: trigger в файле, который я не перегенерировал |
| 6 | 🔴 Не отдавать артефакт, не прочитав его самому | Round-trip «байты сходятся» ≠ «читаемо». Форма blocks проходила round-trip, но 8456 превращался в список цифр — выявил Alex |
| 7 | 🔴 Артефакты — в проект, не в /tmp |
«качай доки в папку в проекте а не в темп» |
| 8 | 🔴 Бэкапы кода — только git | «какой нахуй бэкап скриптов — там в гите все» |
| 9 | 🔴 read_file возвращает контент с номерами строк — не patch-ить им vault |
Обсидиан — только через obsidian-MCP |
| 10 | 🔴 Коммит до проверки Alex | Порядок: коммит ДО → правка → заливка → проверка Alex → коммит ПОСЛЕ |
| 11 | ⚠️ grep -n "id: N" по YAML даёт несколько совпадений |
Объект живёт и в своей секции, и внутри сценария. Номер строки меняется между генерациями |
| 12 | ⚠️ Проверочные скрипты на кириллице падают на cut/awk |
Читать в Python (open(...,'rb').read().decode('cp1251')), не резать шеллом |
7.2. Код — парсер/энкодер
| # | Питфолл | Решение |
|---|---|---|
| 13 | 🔴 emit_step имел ДВЕ ветки для 46 — старая перехватывала новую |
Старая (if 'if' in step:, стр. ~490) стояла выше и читала удалённые action/_else/_kind → 12 объектов из then не регистрировались. Проверять ДОСТИЖИМОСТЬ ветки, а не только её текст |
| 14 | 🔴 result_lines — фильтрованное подмножество lines |
Убрать секцию из TYPE_ORDER → объект пропадёт молча. Симптом: «потеряно 189». Ловится только счётчиком |
| 15 | 🔴 Type 9 собирался дважды | Явный цикл по relay_commands и ветка TYPE_ORDER → Duplicate ID. Убрать явный цикл |
| 16 | 🔴 Тела объектов из then не эмитились → потеря 3 объектов |
11824/11825/11826 есть только как id в списке. Нужен _emit_referenced_bodies(ids) + _body_index |
| 17 | 🔴 z_dict определяется ниже вложенной функции → free variable |
Собственный _body_index (карта id → raw по секциям) рядом с функцией |
| 18 | 🔴 emit_action для вложенного 46 возвращал id без регистрации тела |
if 'action' in node: return aid — тела пауз пропадали, #Z11827 выходил [46,0,11823,[],[]]. → emit_step(node) |
| 19 | 🔴 Операнды не рёбра дерева → терялись | left/right/args не видны обходу. Fixed-point sweep: собирать референсы из raw-тел, докидывать, повторять |
| 20 | 🔴 _is_body_inline плоской проверкой по ключам не работает |
Лист вложенного условия тоже inline. Нужен рекурсивный обход всего тела сценария |
| 21 | 🔴 object_display_name нужен раньше, чем определён |
Вложенная функция видна только ниже вызова → NameError. Вынести на уровень модуля |
| 22 | ⚠️ Имя объекта у типа 1 = '0' |
У типа 1 имя в поле 2, не в поле 1. Спец-случай в object_display_name() |
| 23 | 🔴 Удаление raw_value требует пересчёта кода в энкодере |
Хелпер _encode_type9_value(): True→'1', False→'0', число → str(round((v+273)*10)) |
| 24 | ⚠️ Ветки type 5 / type 9 в энкодере различать по признаку | type 5 — есть value и target > 255; type 9 — нет args |
| 25 | 🔴 Один объект в двух секциях → два разных value |
Раскодировал value в steps[] (5.2), забыл секцию relay_commands ('2782'). Round-trip зелёный — он сравнивает строки, а не смысл. Править все места отображения объекта |
| 26 | 🔴 Правка комментария — не правка кода | Три ответа подряд «готово, зелёный», при том что горячий блок стоял выше моего нового. Мёртвый новый код = ложный зелёный |
| 27 | ⚠️ > в шелле затирает .yml до старта питона |
Проверять exit-код |
| 28 | ⚠️ Один шаг может принадлежать нескольким сценариям | Хелпер _register() — обновляет запись по id, не добавляет дубль |
7.3. Проверка гипотез
| # | Питфолл | Решение |
|---|---|---|
| 29 | 🔴 Гипотезу проверять ПОЛНЫМ прогоном до того, как о ней говорить | Разбор поля 5 занял ~20 итераций. Прогнать по всем 70 и печатать расхождения списком |
| 30 | 🔴 Скрипт проверки может врать молча | Дважды давал «всё сходится» из-за сдвига индекса (parts[4] vs parts[5]). Печатать сырые значения, не только вердикт |
| 31 | 🔴 Не докладывать баг по выводу awk/grep, не проверив вторым инструментом |
awk '/^- id: 9691$/,/^- id: /' вернул одну строку → я объявил «сценарий пустой». Это ошибка диапазона awk |
| 32 | 🔴 Сначала спросить СЛОВА, потом строить модель | Поле 5 разбиралось час вслепую, пока Alex не назвал типы: «типы: manual, trigger, interval, schedule». Спросить «как это называется в UI» |
| 33 | 🔴 «Проверил и снял» — ценный результат, а не провал | Записывать и опровергнутое, чтобы следующая сессия не выводила заново |
| 34 | 🔴 Симптом «вижу старое в файле» = смотреть, ЧТО за файл | Alex трижды видел action: с вложенным - id:. Файл на диске был правильный — он смотрел на другой снапшот (16-13-28, 16-02-18, 14-16-35 — не перегенерированы). Первое действие — grep по ВСЕМ .yml в папке, а не правка кода |
| 35 | 🔴 Не доверять своему grep с многосимвольным шаблоном | Шаблон с альтернацией вернул пустоту на файле, где совпадение было → чуть не доложил «баг исправлен». Перепроверять узким одиночным шаблоном (grep -c 'action: 9561') и глазами по окрестностям |
| 36 | 🔴 grep -c по файлу, где объект есть в двух секциях, завышает счёт |
Сценарий живёт и в scenarios:, и в своей секции. Считать вхождения по смыслу, не по числу строк |
7.5. Доки: правила ведения
| # | Правило | Почему |
|---|---|---|
| 37 | 🔴 Обсидиан — ТОЛЬКО через obsidian-MCP | read_file возвращает контент с номерами строк — patch-ить им vault нельзя. Alex: «какого хуя ты скриптами лезешь в обсидиан» |
| 38 | 🔴 Бэкап доков ПЕРЕД удалением | Удаление через MCP необратимо (This action cannot be undone). Копия в /tmp/zont-docs-backup-<TS>/ |
| 39 | 🔴 Удалил док → почини wikilinks | Бэкап + повторный grep после правок (см. §9.2) |
| 40 | ⚠️ Один док на тему, а не три | Три дока с наложенными слоями «УСТАРЕЛО» невозможно читать. Актуальное + таблица отвергнутого |
7.4. Архитектура энкодера — ключевое
🔴 result_lines — фильтрованное подмножество lines. Объект попадает в вывод, только если его id
вытянут либо через запись в TYPE_ORDER, либо через явный проход. Убрать секцию из TYPE_ORDER ≠
«объект пропадёт» — он пропадёт молча, и это видно только по счётчику round-trip.
Обязано сохраниться байт-в-байт: порядок объектов в файле (45 идёт после 11, но до 46);
порядок ссылок поля 2 сценария; число полей (7 vs 8); _raw_*-поля
(_raw_field_count, _raw_field3, _raw_divider, _raw_links).
8. Заливка конфига обратно — чем и как
Снятие автоматизировано (§3), заливка остаётся ручной — config.txt работает только на чтение.
| Канал | Что умеет | Ограничение |
|---|---|---|
| Утилита по USB | заливка конфига + прошивки | Windows-only, нужен USB-кабель и драйвер |
Облако (my.zont.online) |
правка сценариев/реле через веб-UI | руками, по одному объекту |
Локальный WS ws://192.168.0.50/ws |
запись #S-настроек ({"scmd":"#S<n>=<val>"} + #S15=1) |
#Z-объекты не пишутся |
🔴 Локальный WS даёт запись только
#S(Wi-Fi, MQTT, номер). Сценарии, реле, шаги (#Z11/14/46/49) через него не заливаются.
Утилита H1000 Programmator 2.8.5
curl -sL -o h1000_utility_beta.bin https://lk.zont-online.ru/download/simple/h1000_utility_beta
| Параметр | Значение |
|---|---|
| Версия | 2.8.5 (prgm.2.8.5.exe, 2.9 MB, Delphi/Borland) |
| Интерфейс | USB serial-over-USB (usbser.sys + Hxxxx.inf) |
| Распаковано | ~/rasputin-tmp/zont-util/util_beta/H1000 Programmator/ |
🔴 Питфолл распаковки. macOS
unzipпадает на кириллических именах (write error (disk full?)— на самом деле не disk full). Имена в CP866. Решение — Pythonzipfileс перекодировкойcp437 → cp866.⚠️ Файлы
Configs/*.setвнутри — НЕ конфиги устройства, а UTF-8 JSON-словари подписей интерфейса.
Прошивка — .enc (зашифрован)
https://lk.zont-online.ru/download/firmwares/H2000_PRO_<HW>__<FW>_<PROFILE>.zip
H2000_PRO_723__678_1.zip ← наш контроллер
Правило: префикс серии + двойное подчёркивание перед версией ПО. Одиночные варианты → 404.
| Источник | Значение |
|---|---|
#S7 прибора |
H2000_PRO 723 678 |
| Имя архива | H2000_PRO_723__678_1.zip → h2000_pro_v2_.enc |
→ 723 = плата (HW), 678 = прошивка, 1 = profile_version. 678 — последняя стабильная.
🌐 Облачный API ZONT — конфига не даёт
Облачный API (my.zont.online/api/*, 11 методов) — состояния и история, не конфигурация.
Слово scenario в доке — 0 раз. Методов для типов 11/14/46/49 нет.
Следствие: конвертер .txt ⇄ .yml — единственный путь правки сценариев и реле.
➡️ Полный разбор API, локального WS и прошивок — family/tech/zont-api.
9. Состояние проекта
| Что | Состояние |
|---|---|
| Round-trip | 🟢 ЗЕЛЁНЫЙ, байт-в-байт — 661 → 661, 686 → 686, diff = 0 |
| Форма сценария | ✅ закрыта (§5), оба конвертера переведены |
| Коммит | 823fabd на main — «Scenario YAML shape: bare action ids, no anchors» (3 файла, +200/−1000) |
| Предыдущий | 1cc010a — содержит сломанные версии (анкоры, лишний if, then вместо action); закрыт §9.1 |
| Документация | ✅ три дока сведены в один — personal/projects/zont-config-compiler.md (§9.2) |
| Push | ❌ не сделан |
Проверка на снимке 18-43-24 (файл с диска): trigger: 66 · action: 66 · if: 2 · then: 2 ·
анкоров 3 (законные: [], raw, sensors). type, field5, kind, _f5, _kind, _else — 0 вхождений.
Не в коммите (untracked): 14 скриптов разбора (read_scenarios.py, dump_new_types.py, probe_types.py,
trace_scenarios.py, audit2.py, fit_temp*.py, …, ), папки zont_api_docs/, zont_local_ui_recon/.
9.1. Коммит 1cc010a — ЗАКРЫТО новым коммитом
Коммит 1cc010a содержал сломанные версии конвертеров (PyYAML-анкоры &idNNN/*idNNN 69 шт,
if вместо trigger: в шаге, then вместо action). Решено не через --amend, а новым коммитом:
cd /Users/admin/Automation/HA-ZONT-Modbus
python3 test_roundtrip.py # ✅ зелёный ПЕРЕД коммитом — обязательный шаг
git add config-to-yml.py yml-to-config.py zont_config/config_local_2026-09-17_18-43-24.yml
git commit -m "Scenario YAML shape: bare action ids, no anchors"
# → 823fabd, 3 файла, +200/−1000
📌
--amendне понадобился — история фиксирует обе итерации формы, откат возможен по SHA. В коммит вошли только 3 файла — 10 скриптов разбора и 2 папки остались untracked намеренно.
9.2. Мерж трёх доков в один (2026-09-17)
Документация конвертеров жила в трёх доках с наложенными слоями правок (§5b→§5c→§5j→§5k→§6→§8) и взаимными пометками «УСТАРЕЛО» — читать было невозможно.
| Было | Стало |
|---|---|
family/how-to/zont-config-compiler.md (122 KB, 2240 строк) |
— |
family/tech/zont-config-object-types.md (18 KB) |
→ personal/projects/zont-config-compiler.md |
family/tech/zont-scenario-logic-11109.md (17 KB) |
— |
Как сделано: бэкап всех трёх в /tmp/zont-docs-backup-<TS>/ → собрать единый док → записать →
удалить три → починить wikilinks.
🔴 Обязательный шаг — wikilinks. Удаление дока оставляет битые ссылки в чужих заметках. Найти:
search_files(pattern="<старый-basename>", path="/Users/admin/obsidian"). В этом случае правились:family/tech/zont-api.md(5 мест),family/how-to/gitea-config.md(1),family/how-to/home-automation.md(1).🔴 Проверка — повторный grep, а не «я поменял». После правок прогнать тот же поиск и убедиться, что остались только alias самого нового дока. Alex требует «коммит до → правка → проверка», и для доков правило то же.
Приём схлопывания истории: вместо 5 секций «форма сценария» с взаимными «УСТАРЕЛО» — одна актуальная секция + таблица отвергнутых итераций с репликами Alex. Опровергнутое сохраняется (питфолл 33), но не как альтернативная действующая форма.
10. Осталось не разобрано
⚠️ Остаётся raw / unresolved:
- Тип 50 — маска дней недели:
#Z8548=50,1,0,0,109, где109 = 0b1101101= пн, ср, чт, сб, вс. Alex: «это выбор дней недели, то же самое что ставится в значение var1». Сейчасraw. set var1сargs: [8844, 0, 0]— внутри объекта8844лежит та же маска дней недели, объект не раскрыт: «что какого-то хуя уехало вовне сценария вообще — только там пн, вт, чт, пт, сб, вс».unresolved: trueу#Z8860,#Z8864,#Z8601— этих объектов нет в конфиге.- Тип 3 (SMS
8195) — тело лежит якорем вsms_notifications, не раскрыто. - Тип 11 внутри
stepsдругого сценария — ссылка на сценарий голымid(#Z8456держит11109). - Старые снапшоты
14-16-35.yml/16-02-18.yml— по 2 вхождения старогоtype:, не перегенерированы.
11. Файлы проекта
| Файл | Статус |
|---|---|
config-to-yml.py |
✅ форма §5: trigger: подъём через pop('if'), шаг = {id, [flag], action} или {id, [flag], if, then, [else]} |
yml-to-config.py |
✅ emit_step читает then/action/else/flag; f5 из trigger:/interval_ms + бит 8 |
test_roundtrip.py |
✅ без изменений (в коммите 199f2b1) |
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) |
Не относится к конвертерам (исторический TrueNAS-стек, декомиссирован):
INFRASTRUCTURE.md, docker-compose.yml, docker run.txt, modbus_*_bridge.py,
nodered-flows-*.json, homeassistant/, floorplan/. Актуальный контур — family/how-to/home-automation.
12. Связанные заметки
- family/tech/zont-api — облачный API, локальный WS, прошивки, утилита
- family/how-to/home-automation §6 — ZONT в общем контуре, Modbus slave ID и регистры
- family/how-to/ha-automations — автоматизации HA
- family/how-to/gitea-config — Gitea: креды, создание репо, питфоллы