diff --git a/Modbus/RTU_Framing_Source_Analysis.md b/Modbus/RTU_Framing_Source_Analysis.md index 7bfbe36f..62026159 100644 --- a/Modbus/RTU_Framing_Source_Analysis.md +++ b/Modbus/RTU_Framing_Source_Analysis.md @@ -1,6 +1,8 @@ # Modbus RTU Frame Delimiting: pymodbus vs libmodbus (source analysis) -Версия/источники зафиксированы на момент анализа. Локальные копии скачанных файлов: `/Users/admin/modbus_src/`. +> Источник кода — реальные файлы с GitHub, скачаны при разборе бага `modbus-bridge` +> (см. [[family/plans/t610-home-automation]] §5-кватер-З и §5-кватер-Ж). +> Локальные копии: `/Users/admin/modbus_src/`. ## Границы (URL) @@ -26,3 +28,106 @@ pymodbus v3.0.2/3.4.1 `ModbusRtuFramer`: `_min_frame_size = 4`, `_hsize = 0x01`. ``` libmodbus: `MODBUS_RTU_MAX_ADU_LENGTH 256`, `_MODBUS_RTU_HEADER_LENGTH 1`, `_MODBUS_RTU_CHECKSUM_LENGTH 2`. + +## Формула T3.5 (pymodbus `client/serial.py`) + +```python +self._t0 = float(1 + bytesize + stopbits) / baudrate # 3.6.9 (в 3.4.1: 1+8+2 захардкожено) +if baudrate > 19200: + self.silent_interval = 1.75 / 1000 # мс +else: + self.inter_byte_timeout = 1.5 * self._t0 + self.silent_interval = 3.5 * self._t0 +``` + +**При 9600 бод:** `t0 = 11/9600 = 1.146 мс` → `T3.5 = 4.01 мс`, `T1.5 = 1.72 мс`. + +⚠️ В pymodbus **фреймер таймингами не занимается** — T3.5 живёт в серийном клиенте. Фреймер делает чистый байтовый парсинг. + +## Как делится кадр — три подхода + +| Проект | Делимитация | Мусор в буфере | +|---|---|---| +| **libmodbus** (эталон, C) | **по byte-count из функции**: читает адрес+функцию → `compute_meta_length_after_function` → `compute_data_length_after_meta` (берёт byte-count из кадра) | `tcflush(TCIOFLUSH)` при bad CRC (`MODBUS_ERROR_RECOVERY_PROTOCOL`) | +| **pymodbus dev** (`framer/rtu.py`) | CRC-hunting: `for used_len in range(data_len)` — сдвиг на байт, допускает мусор **до и после** кадра | ждёт/сдвигается, не дропает целиком | +| **pymodbus классич.** (v3.0) | длина из `calculateRtuFrameSize()` (byte-count) | **`resetFrame()` → буфер в `b""` целиком** при bad CRC/bad UID | + +### libmodbus — ключевой код (`src/modbus.c`, `_modbus_receive_msg`) + +```c +step = _STEP_FUNCTION; +length_to_read = ctx->backend->header_length + 1; +while (length_to_read != 0) { + rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read); + ... + rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read); + msg_length += rc; + length_to_read -= rc; + if (length_to_read == 0) { + switch (step) { + case _STEP_FUNCTION: + length_to_read = compute_meta_length_after_function(msg[header_length], msg_type); + ... + case _STEP_META: + length_to_read = compute_data_length_after_meta(ctx, msg, msg_type); + if ((msg_length + length_to_read) > ctx->backend->max_adu_length) { + errno = EMBBADDATA; // "too many data" — защита от переполнения + return -1; + } + step = _STEP_DATA; + } + } +} +return ctx->backend->check_integrity(ctx, msg, msg_length); +``` + +`compute_data_length_after_meta` (MSG_CONFIRMATION): `length = msg[header_length + 1]` — **byte-count прямо из кадра**, +2 на CRC. + +### pymodbus классический — `resetFrame()` (тот самый сброс) + +```python +def resetFrame(self): + """Reset the entire message frame. + It is hard to know if we are simply out of sync or if there is + an error in the stream as we have no way to check the start or + end of the message (python just doesn't have the resolution to + check for millisecond delays). + """ + self._buffer = b"" + self._header = {"uid": 0x00, "len": 0, "crc": b"\x00\x00"} +``` + +### pymodbus dev — hunting-режим (`framer/rtu.py`, `decode`) + +```python +for used_len in range(data_len): + if data_len - used_len < self.MIN_SIZE: + return 0, 0, 0, self.EMPTY # мало данных — ждём + dev_id = int(data[used_len]) + if self.device_ids and dev_id not in self.device_ids: + return data_len, 0, 0, self.EMPTY + if not (pdu_class := self.decoder.lookupPduClass(data[used_len:])): + continue # сдвиг на байт (мусор перед кадром) + if not (size := pdu_class.calculateRtuFrameSize(data[used_len:])): + return 0, dev_id, 0, self.EMPTY + if data_len < used_len + size: + return 0, dev_id, 0, self.EMPTY # кадр не готов — ждём + for test_len in range(data_len, used_len + size - 1, -1): + start_crc = test_len - 2 + ... + if not FramerRTU.check_CRC(data[used_len:start_crc], crc_val): + continue # мусор ПОСЛЕ кадра — пробуем короче + return data_len, dev_id, 0, data[used_len + 1 : start_crc] +``` + +## Выводы для нашего сниффера (`modbus-bridge`) + +1. **Пассивный сниффер T3.5 нужен обязательно** — мы не мастер, не знаем длину ожидаемого ответа заранее. +2. **Мусор нельзя копить.** Канон (pymodbus `resetFrame`, libmodbus `tcflush`) — **сбрасывать** битый буфер, а не сдвигать бесконечно. +3. **Дроп только ПОСЛЕ паузы.** Пока на шине нет тишины ≥T3.5 — кадр ещё может продолжаться, байты копим. +4. Наш фикс (коммит `3748feb` в `git_admin/HA-ZONT-Modbus`) = T3.5-гейт + `resetFrame`-семантика + hunting-скан сохранён. + +## Связанные заметки + +- [[family/plans/t610-home-automation]] — основной док миграции, §5-кватер-Ж/З (диагноз и фикс) +- [[family/how-to/home-automation]] — карта slave/регистров diff --git a/family/how-to/home-automation.md b/family/how-to/home-automation.md index 37117848..f849cbbe 100644 --- a/family/how-to/home-automation.md +++ b/family/how-to/home-automation.md @@ -217,7 +217,7 @@ ZONT relays > **ℹ️ Про «виртуальные sensor 101/102/103» — механика (уточнено 2026-09-14):** > 101/102/103 — это **виртуальные Modbus-slaves**, которые контейнер `modbus-bridge` подставляет на шине: реальные 485-датчики **Гостиная=1, Детская=2, Спальня=3** сниффятся, и их значения выдаются ZONT'у как датчики 101/102/103 (регистр 100). ZONT (Modbus master) опрашивает их как внешние датчики. > **🔑 Уточнение 2026-09-14 (по логу, см. [[family/plans/t610-home-automation]] §5-кватер-Д):** bridge работает **двусторонне** — он не только публикует в MQTT, но и **отвечает ZONT'у** под адресами 101/102/103. В логе это видно явно: `Slave: 101 → sniff:dining_temperature = 0 [00 00]`, `Slave: 102 → sniff:kids_temperature = 25.26`, `Slave: 103 → sniff:bedroom_temperature = 25.12`. **По гостиной (101) bridge отдаёт `0`**, потому что реальный датчик гостиной он **не снифит** (в логе `Sniff:` только `kids_*`/`bedroom_*`; `Slave: 1` у ZONT читает 7 регистров с адреса 2 — это внутренние параметры, не датчик). **Поэтому** `modbus/sensors/dining/*` не публикуется и `sensor.dining_*` в HA = `unknown`. -> Если `modbus-bridge` не запущен/не слушает шину → ZONT показывает эти датчики **«недоступные»**. На TrueNAS известная первопричина была гонка docker/udev после рестарта (см. `[[family/plans/zont-modbus-bridge-udev-race-protection]]`); **на t610 неактуально** — Supervisor сам ждёт устройство. +> Если `modbus-bridge` не запущен/не слушает шину → ZONT показывает эти датчики **«недоступные»**. На TrueNAS известная первопричина была гонка docker/udev после рестарта (см. `[[family/how-to/zont-modbus-bridge-udev-race-protection]]`); **на t610 неактуально** — Supervisor сам ждёт устройство. ## Карта регистров контроллера вентиляторов diff --git a/family/how-to/nodered-ventilation.md b/family/how-to/nodered-ventilation.md index 216225c2..e7d9cbd4 100644 --- a/family/how-to/nodered-ventilation.md +++ b/family/how-to/nodered-ventilation.md @@ -144,3 +144,10 @@ Gate: current_state (manual override → block automation). ## Связанные заметки - [[family/how-to/home-automation]] — общая автоматизация дома +- [[family/plans/t610-home-automation]] — миграция на t610, §5-кватер-Г (перенос flows) и §5-кватер-З (фикс датчика столовой) +- [[Modbus/RTU_Framing_Source_Analysis]] — канон сборки RTU-кадров (почему `dining` молчал и как починили) + +## Статус данных для автоматики (2026-09-14) + +✅ **Все три комнаты (`bedroom`, `kids`, `dining`) публикуют данные в MQTT.** Датчик столовой (`slave 1`) был сломан багом сборки кадров в `modbus-bridge` — починен 2026-09-14 (см. [[family/plans/t610-home-automation]] §5-кватер-З). До фикса автоматика вентиляции по CO₂ работала без данных столовой. +⚠️ **`fan.fan_at2_1/2` (slave 10) — `unavailable`** (блок закомментирован в `configuration.yaml`) → исполнительный контур вентиляторов AT2 не работает. Задача по slave 10 Alex'ом не ставилась. diff --git a/family/how-to/truenas-infrastructure.md b/family/how-to/truenas-infrastructure.md index 973fbe37..9e72f83d 100644 --- a/family/how-to/truenas-infrastructure.md +++ b/family/how-to/truenas-infrastructure.md @@ -30,7 +30,7 @@ > - `restart: unless-stopped` НЕ перезапускает такой контейнер (отказ до старта процесса). > - Следствие: modbus-bridge не отвечает на шине → 485-датчики в ZONT отображаются **«недоступные»**. > - Ручной фикс: `docker start modbus-bridge mbusd` (после готовности tty-симлинков). -> - Защита от рецидива (план, НЕ внедрён — ждёт OK Alex): `family/plans/zont-modbus-bridge-udev-race-protection.md` — POSTINIT init script с ожиданием симлинков + `docker start`. +> - Защита от рецидива (план, НЕ внедрён — ждёт OK Alex): `family/how-to/zont-modbus-bridge-udev-race-protection.md` — POSTINIT init script с ожиданием симлинков + `docker start`. > **⚠️ ДРУГОЕ (открытое):** пул `DEST` (IronWolf) на 2026-08-24 `DEGRADED` — разобраться, почему (отвалился mirror-член). TimeMachine (~277G) не возвращён (бэкапы macOS, вероятно не нужен — решение Alex не подтверждено). SSH с Mac по 192.168.2.197 нестабилен — использовать `mallexxx.duckdns.org`. > ## 🔒 ИСТОРИЯ: статус до восстановления (2026-08-24) @@ -342,7 +342,7 @@ error gathering device information while adding custom device "/dev/ttyZONT": no **Ручной фикс:** `docker start modbus-bridge mbusd` (после того как симлинки созданы). -**Защита от рецидива (план, НЕ внедрён, ждёт OK Alex):** `family/plans/zont-modbus-bridge-udev-race-protection.md` — POSTINIT init script `/mnt/RED_2TB/system/start-modbus.sh`, который ждёт появления `ttyZONT`/`ttyVent` (≤25с) и делает `docker start modbus-bridge mbusd`. +**Защита от рецидива (план, НЕ внедрён, ждёт OK Alex):** `family/how-to/zont-modbus-bridge-udev-race-protection.md` — POSTINIT init script `/mnt/RED_2TB/system/start-modbus.sh`, который ждёт появления `ttyZONT`/`ttyVent` (≤25с) и делает `docker start modbus-bridge mbusd`. ### USB device aliases diff --git a/family/plans/t610-home-automation.md b/family/plans/t610-home-automation.md index f283ba14..2c82b280 100644 --- a/family/plans/t610-home-automation.md +++ b/family/plans/t610-home-automation.md @@ -9,7 +9,7 @@ ## 1. Состояние на 2026-09-14 (актуализировано 15:33 → дополнено вечерней сессией) **Этап 1 ✅ · Этап 2 ✅ · Этап 3 ✅ ЗАКРЫТ.** Этап 4 — **ПОЧТИ ЗАКРЫТ (3 из 4):** ① Caddy → t610 (`mallexxx.duckdns.org` → HA на t610, HTTP 200); ② Node-RED flows перенесены, `Connected to HA`, наружу не выпущен (решение Alex «оставляем так», доступ через ingress); ③ **ZONT MQTT-редирект переключён на t610** (DNAT на роутере `.197`→`.176`, живой поток идёт). **ОСТАЛОСЬ:** погасить/отключить сервисы TrueNAS (заблокировано — Caddy на TrueNAS держит точку входа) + хвосты (static IP, бэкап). -**Последняя верификация: 2026-09-14 (вечер-6) — ФИКС СДЕЛАН, ГОСТИНАЯ РАБОТАЕТ. ✅✅ ЗАКРЫТО.** Причина (вечер-5, фактом): кадры приходят разорванными, байты-сироты `00` копились в голове буфера → сдвиг выравнивания → 19-байтный ответ гостиной отбрасывался молча. **Фикс (вечер-6): сборка кадров по канону — T3.5-разграничение (пауза ≥3.5 символа = конец кадра) + сброс битого буфера (= `resetFrame` в pymodbus)**, коммит `3748feb` → Gitea → scp → `ha apps rebuild`. **Итог: все 7 полей `dining` публикуются, `dining_summary`/`dining_air_summary` ожили, `unavailable` 10 → 8** (остались только slave 10 «снято Alex'ом» + `todo.shopping_list`). **Загадка «замерзания» лога на 7 ч — тот же баг, устранена.** Диагностика включена (`MODBUS_DEBUG_RAW=1`), выключить после наблюдения. Детали — §5-кватер-З, §11 (журнал вечер-6). +**Последняя верификация: 2026-09-14 (вечер-7) — ФИКС ПОДТВЕРЖДЁН, ДИАГНОСТИКА СНЯТА. ✅✅ ЗАКРЫТО.** Причина (вечер-5, фактом): кадры приходят разорванными, байты-сироты `00` копились в голове буфера → сдвиг выравнивания → 19-байтный ответ гостиной отбрасывался молча. **Фикс (вечер-6): сборка кадров по канону — T3.5-разграничение (пауза ≥3.5 символа = конец кадра) + сброс битого буфера (= `resetFrame` в pymodbus)**, коммит `3748feb` → Gitea → scp → `ha apps rebuild`. **Итог: все 7 полей `dining` публикуются, `dining_summary`/`dining_air_summary` ожили, `unavailable` 10 → 8** (остались только slave 10 «снято Alex'ом» + `todo.shopping_list`). **Загадка «замерзания» лога на 7 ч — тот же баг, устранена.** Диагностика ✅ снята (вечер-7), `dining` стабилен. Детали — §5-кватер-З, §11 (журнал вечер-6/7). | Что | Факт | |---|---| @@ -1364,9 +1364,10 @@ sensor.dining_air_summary = 31tvoc 3pm ← БЫЛ TemplateError → ож 4. **Правку в продакшене делать через git-репо, не «на месте»** (сначала коммит, потом scp+rebuild) — даёт чистую историю и откат. 5. **Перед правкой сложной логики — офлайн-тест на реальных данных** (`test_framing.py`) дешевле и быстрее, чем rebuild+деплой-цикл (~1 мин каждый). -#### 📌 Осталось (по этой задаче) +#### 📌 Итог по задаче — ЗАКРЫТА -- **Диагностика ВКЛЮЧЕНА** (`MODBUS_DEBUG_RAW=1` в `run.sh` на t610) — понаблюдать за стабильностью `dining`, затем **выключить**: закомментировать строку в `run.sh` → `ha apps rebuild local_modbus-bridge`. Ждёт команды Alex. +- **Диагностика ✅ СНЯТА 2026-09-14 (вечер-7).** Наблюдение показало стабильность: `dining` 7 публикаций за окно против 6 у `kids`/`bedroom` (тот же порядок), `BUF-DROP` = 1 (единичный, не накопление), `[RAW*]` в логе = 0. `MODBUS_DEBUG_RAW=1` закомментирован в `run.sh` → `ha apps rebuild local_modbus-bridge` → рестарт. Проверено: лог чистый, все 7 полей `dining` публикуются. + - **Как включить снова при отладке framing:** раскомментировать `export MODBUS_DEBUG_RAW=1` в `run.sh` → rebuild → рестарт. - **Крон-наблюдение** за `dining` (не пропадает ли снова) — не заводилось. **Git-коммиты (репо `git_admin/HA-ZONT-Modbus`, ветка `main`):** @@ -1720,7 +1721,7 @@ curl -s -X POST -H "$HDR" -H "Content-Type: application/json" \ | ~~1b~~ | ~~`verify` в заслонках~~ — **✅ ОПРОВЕРГНУТО:** заслонки ожили при том же `verify`. Не причина. | — | | ~~1c~~ | ~~ZONT-шина на других гнёздах~~ — **✅ СДЕЛАНО:** физический тест доказал (ZONT = гнездо 4, вентиляция = гнездо 3), привязки аддонов обменяны, док приведён в соответствие. Осталась только проверка «① подтвердить у Alex» — **закрыто**: он сам это и тестировал. | — | | ~~2~~ | ~~Раскомментировать slave 10 (AT2 fans)~~ — **СНЯТО с плана (Alex 2026-09-14): задачи по slave 10 НЕ БЫЛО.** | — | -| ~~3~~ | ~~**`sensor.dining_summary` / `dining_air_summary`**~~ — **✅✅ РЕШЕНО 2026-09-14 (вечер-6, §5-кватер-З).** Фикс сборки кадров по канону (T3.5-разграничение + сброс битого буфера, коммит `3748feb` → Gitea → scp → `ha apps rebuild`). **Все 7 полей `dining` публикуются, оба summary ожили, `unavailable` 10→8.** Осталось: выключить диагностику (`MODBUS_DEBUG_RAW`) после наблюдения | ✅ закрыто | +| ~~3~~ | ~~**`sensor.dining_summary` / `dining_air_summary`**~~ — **✅✅ РЕШЕНО 2026-09-14 (вечер-6, §5-кватер-З).** Фикс сборки кадров по канону (T3.5-разграничение + сброс битого буфера, коммит `3748feb` → Gitea → scp → `ha apps rebuild`). **Все 7 полей `dining` публикуются, оба summary ожили, `unavailable` 10→8.** Диагностика ✅ снята 2026-09-14 (вечер-7), `dining` стабилен | ✅ закрыто | | ~~3-гт~~ | ~~**Gitea remote для `~/Automation/HA-ZONT-Modbus`**~~ — **✅ СДЕЛАНО 2026-09-14 (см. §5-кватер-Е «GITEA»).** Репо `git_admin/HA-ZONT-Modbus` создан через API (**private**), remote добавлен (чистый URL без токена), токен вынесен в `~/.git-credentials` (chmod 600) + `credential.helper=store`, первый push прошёл (`7e0b281`, ветка `main`). **Осталось:** ⚠️ ротировать/вынести токен из НАМЕРТВО открытого remote у `nolvu-landing` (`https://git_admin:@…` — светился в выводах команд). Детали — §5-кватер-Е | ✅ сделано | | 4 | **Камера** — найти образ/папку, поднять на t610, поправить upstream в Caddy | отдельно | | ~~5~~ | ~~**Этап 4:** Caddy upstream → t610~~ — **✅ ЧАСТЬ «CADDY» ЗАКРЫТА 2026-09-14 (см. §5-кватер-Б/В):** Caddyfile залит (Alex подменил файл + `restart caddy`), `mallexxx.duckdns.org` → **HTTP 200** (HA на t610, подтверждено Alex'ом), попутно исправлен `trusted_proxies` в `.storage/http` (§5-кватер-В-1). **ОСТАЛОСЬ из Этапа 4:** ① `nodered.*` — починить порт (см. задачу 5-нр ниже); ② GPON-редирект → t610; ③ ZONT MQTT → t610. **🔴 Архитектурный вывод: Caddy НЕ переносить** (17 из 20 доменов — сервисы TrueNAS) | — | @@ -1765,20 +1766,20 @@ curl -s -X POST -H "$HDR" -H "Content-Type: application/json" \ | Шаг | Задача | Риск | Действие | |---|---|---|---| | ~~A1~~ | ~~№3: `\|default(0)`~~ — **❌ СНЯТ ОКОНЧАТЕЛЬНО** | — | Причина: данные на шине есть, bridge их теряет (§5-кватер-Д). Формула не чинится | -| ~~**A0**~~ | ~~**№3: починка приёма кадра bridge** (приоритет сессии)~~ | — | **✅✅ ВЫПОЛНЕНО ЦЕЛИКОМ 2026-09-14 вечер-6 (§5-кватер-З):** Шаг 1 (логирование, вечер-5) дал механизм; **Шаг 2 — фикс сборки кадров по канону (T3.5 + `resetFrame`), коммит `3748feb` → Gitea → scp → `ha apps rebuild` → `restart`.** Проверено офлайн-тестом (4/4) и на живом: **7/7 полей `dining` в HA, оба summary ожили, `unavailable` 10→8.** Осталось: выключить диагностику после наблюдения | +| ~~**A0**~~ | ~~**№3: починка приёма кадра bridge** (приоритет сессии)~~ | — | **✅✅ ВЫПОЛНЕНО ЦЕЛИКОМ 2026-09-14 вечер-6 (§5-кватер-З):** Шаг 1 (логирование, вечер-5) дал механизм; **Шаг 2 — фикс сборки кадров по канону (T3.5 + `resetFrame`), коммит `3748feb` → Gitea → scp → `ha apps rebuild` → `restart`.** Проверено офлайн-тестом (4/4) и на живом: **7/7 полей `dining` в HA, оба summary ожили, `unavailable` 10→8.** Диагностика снята (вечер-7) | | ~~B1~~ | ~~**№5 (Этап 4): ДОЛИТЬ Caddyfile**~~ — **✅ СДЕЛАН + `trusted_proxies` фикс** | — | Alex подменил файл + `docker restart caddy`. `mallexxx.duckdns.org` → 200. Затем `.storage/http` → `trusted_proxies += 192.168.2.197/32` (§5-кватер-В) | -| **B2** | **Node-RED наружу** — выставить порт аддона и поправить Caddy | 🔶 средний | Опции `a0d7b954_nodered`: `{ "1880/tcp": 11880 }`, `host_network` убрать → рестарт → Caddy `nodered.*` → `192.168.2.176:11880` + reload. **Сначала ответить: нужен ли пустой t610-Node-RED?** (§5-кватер-В-2) | +| ~~**B2**~~ | ~~**Node-RED наружу** — выставить порт аддона и поправить Caddy~~ | — | **✅ СНЯТО (2026-09-14 вечер-7):** решение Alex — «оставляем так». Node-RED доступен через **ingress** HA (`http://192.168.2.176/api/hassio_ingress//`), наружу не выпускается. Домен `nodered.*` не используется. См. §5-кватер-Г и `[[family/how-to/nodered-ventilation]]` | | **B3** | **Этап 4, остаток:** GPON-редирект → t610, ZONT MQTT → t610 | 🔴 высокий | Трогает живое → окно + согласование | | A2 | **№7**: Static IP для t610 | ✅ низкий | На роутере `192.168.2.2` (SSH root) привязать MAC `9c:8e:99:ef:3f:c5` → `192.168.2.176`. Сервисы не трогаются | | A3 | **№8**: Бэкап конфигов t610 | ✅ низкий | `tar` конфигов t610 (`/config`, **`.storage/http`**, Caddyfile) → Mac + TrueNAS + коммит в Gitea `git.mallexxx.duckdns.org` | **Вариант B — Этап 4 целиком** (№5: Caddy upstream → t610 + GPON-редирект → t610 + ZONT MQTT → t610). 🔴 Трогает рабочее → нужно окно и согласование с Alex. **Caddy-часть СДЕЛАНА (§5-кватер-Б/В).** -**Открытые вопросы к Alex:** -1. **Node-RED:** на t610 **пустой** (124 B flows), на TrueNAS — с потоками. Нужен ли t610-Node-RED, или переносить flows? Порт выставляем? (§5-кватер-В-2) -2. **Датчик столовой:** почему отдаёт 0 — копать ZONT сейчас или в бэклог? (§5-кватер-А) -3. **«Замерзание» лога bridge** — копать сейчас или отложить. -4. **Этап 4 / погасить TrueNAS:** где живёт точка входа, если Caddy нельзя уносить с TrueNAS? (§5-кватер-Б) +**Открытые вопросы к Alex — ✅ ВСЕ ЗАКРЫТЫ по итогу 2026-09-14 (вечер-7):** +1. ~~**Node-RED:** на t610 пустой…~~ → **✅ РЕШЕНО (вечер-2, §5-кватер-Г):** flows перенесены (68 узлов), узел server `addon: true`. Порт **не выставляем** — решение Alex «оставляем так», доступ через ingress. Задача B2 снята. +2. ~~**Датчик столовой:** почему отдаёт 0 — копать?~~ → **✅ РЕШЕНО (вечер-5/6, §5-кватер-Ж/З):** причина **не** в ZONT/шине (ответ был, CRC валиден) — баг сборки кадров в `modbus-bridge`. Фикс коммит `3748feb`. Все 7 полей `dining` живы в HA. +3. ~~**«Замерзание» лога bridge**~~ → **✅ РЕШЕНО:** тот же баг сборки кадров (байты-сироты копились в буфере, `[BUF-LEFT 1] 00` ×20). Устранён фиксом `3748feb`. +4. **Этап 4 / погасить TrueNAS:** где живёт точка входа, если Caddy нельзя уносить с TrueNAS? (§5-кватер-Б) — **открыт**: Caddy оставлен на TrueNAS осознанно (17 из 20 доменов — сервисы TrueNAS). --- @@ -1823,7 +1824,9 @@ docker restart caddy scp ~/Automation/HA-ZONT-Modbus/modbus_ha_bridge.py root@192.168.2.176:/addons/modbus-bridge/ ssh root@192.168.2.176 'ha apps rebuild local_modbus-bridge && ha apps restart local_modbus-bridge' # ⚠️ Dockerfile: COPY modbus_ha_bridge.py /app/ → БЕЗ rebuild правка не применится -# снятие диагностики: откатить ad6345a → scp бэкап .bak-prelogging-* → rebuild → restart +# диагностика УЖЕ СНЯТА (2026-09-14 вечер-7): MODBUS_DEBUG_RAW закомментирован в run.sh +# ВКЛЮЧИТЬ снова при отладке framing: раскомментировать -> rebuild -> restart +# ПОЛНЫЙ откат фикса (если понадобится): git revert 3748feb -> scp файла -> rebuild -> restart ``` **Бэкапы на t610:** `/config/automations.yaml.bak-*` (последний: `.bak-20260914-131541` — перед снятием `not_from`), `/config/configuration.yaml.bak-http-*`, `/config/.storage/http.bak-*`, `/addons/modbus-bridge/*.bak-hex-*`. @@ -2101,12 +2104,34 @@ ssh root@192.168.2.176 'ha apps rebuild local_modbus-bridge && ha apps restart l **Закрыто:** задача №3 (`dining_summary`) ✅, план A0 ✅, **загадка «замерзание лога на 7 ч»** — тот же баг, устранена ✅. -**Не сделано:** выключить диагностику `MODBUS_DEBUG_RAW` после наблюдения (ждёт команды Alex). +**Сделано:** диагностика выключена 2026-09-14 (вечер-7) — `dining` стабилен (7 публикаций vs 6 у kids/bedroom), `[RAW*]`=0, лог чистый. Правка `run.sh` → `rebuild` → `restart`. > 📌 **Процессный урок (положительный):** Alex дал **гипотезу + просьбу проверить канон** — агент **не гадал**, а: (1) нашёл реальный код libmodbus/pymodbus, (2) офлайн-тест на реальных байтах **до** деплоя, (3) деплой + верификация через независимый источник (HA API, а не только лог самого bridge). Схема «гипотеза → канон → офлайн-тест → деплой → независимая проверка» сработала с первого раза. > ⚠️ **Питфолл Hermes (трижды за сессию):** inline `$( … | jq … )` / `$( … | sed … )` + токены → **маскируются и ломаются**. Только **скрипт файлом**. > ⚠️ **Питфолл `ha apps info --raw-json`:** путь `.data.options.ha_token`, не `.options`. Значение `ha_token` в выводе маскируется → использовать программно. +### 2026-09-14 (вечер-7: снятие диагностики + приведение доков в актуал) + +**Контекст:** Alex — *«Проверь и выключай диаг. Доки в актуал. Обязательно убедись что есть доки по всей automation папке и залинковано всё»*. + +**Что сделано:** + +| Шаг | Факт | +|---|---| +| 🔍 Проверка стабильности | `dining` — **7 публикаций** за окно против **6** у `kids`/`bedroom` (тот же порядок); `BUF-DROP` = 1 (единичный, не накопление); `[RAW*]` в логе = **0** → фикс держится | +| 🔧 Снятие диагностики | `export MODBUS_DEBUG_RAW=1` закомментирован в `run.sh` → `ha apps rebuild` + `restart`. Проверено: лог чистый, 7/7 полей `dining` публикуются | +| 📚 Аудит доков | Проверены все доки по автоматике. **`~/Automation` — НЕ папка HA-автоматики**, а общая свалка скриптов (Xcode-скриптлеты, yt-сабы, xliff, zoom); HA-проект там один — `HA-ZONT-Modbus` | +| 🔴 Битая ссылка | `family/how-to/home-automation.md` ссылался на `[[family/plans/zont-modbus-bridge-udev-race-protection]]`, а файл лежит в `family/how-to/` → **исправлено** | +| 🔗 Изолированный док | `Modbus/RTU_Framing_Source_Analysis.md` (ресёрч канона) — **0 входящих ссылок** → дописан целиком + залинкован из плана и `nodered-ventilation` | +| 🔗 Линковка | План → добавлены `[[Modbus/RTU_Framing_Source_Analysis]]`, `[[family/how-to/nodered-ventilation]]`, `[[family/how-to/truenas-sata-ports-and-zfs-pools]]` | +| ✅ Уже было актуально | `family/how-to/gitea-config.md` — `HA-ZONT-Modbus` в таблице репо + питфолл про токен в URL; `family/how-to/nodered-ventilation.md` — статус переноса flows | + +**Что менялось в железе:** только `run.sh` аддона `modbus-bridge` на t610 (снятие env-флага) → rebuild + restart. Код `modbus_ha_bridge.py` не трогался. + +**Проверено фактами:** лог bridge (dining 7/7), счётчики `[RAW*]`/`BUF-DROP`, наличие/отсутствие файлов доков и их ссылок. + +> ⚠️ **Питфолл (Hermes):** `execute_code` — это Python-песочница; вызов bash через `subprocess` там лишний (и упал на отсутствии `psutil`). **Для grep/поиска — `terminal` + `grep`, не Python.** + --- ## Связанные заметки @@ -2116,3 +2141,6 @@ ssh root@192.168.2.176 'ha apps rebuild local_modbus-bridge && ha apps restart l - [[family/how-to/truenas-infrastructure]] — docker-стек TrueNAS - [[family/how-to/zont-modbus-bridge-udev-race-protection]] — гонка udev (на t610 неактуальна) - [[family/how-to/gitea-config]] — Gitea (для git-бэкапа конфигов) +- [[Modbus/RTU_Framing_Source_Analysis]] — канон сборки RTU-кадров (pymodbus/libmodbus), обоснование фикса §5-кватер-З +- [[family/how-to/nodered-ventilation]] — автоматика вентиляции в Node-RED (flows на t610) +- [[family/how-to/truenas-sata-ports-and-zfs-pools]] — железо TrueNAS (диски/пулы, для контекста)