[2026-09-14] eagle: Modbus/RTU_Framing_Source_Analysis.md family/how-to/home-automation.md family/how-to/nodered-ventilation.md family/how-to/truenas-infrastructure.md family/plans/t610-home-automation.md
This commit is contained in:
1 parent
a3cd302573
commit
a1f5d1b04e
5 files changed
+157
-17
No files matched your search
@@ -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/регистров
|
||||
@@ -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 сам ждёт устройство.
|
||||
|
||||
## Карта регистров контроллера вентиляторов
|
||||
|
||||
|
||||
@@ -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'ом не ставилась.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:<token>@…` — светился в выводах команд). Детали — §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/<token>/`), наружу не выпускается. Домен `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 (диски/пулы, для контекста)
|
||||
Reference in new issue
Block a user