[2026-09-15] eagle: family/how-to/home-automation.md family/tech/zigbee-t610-z2m-i-zha.md personal/tech/modbus-rtu-sniffer-debugging.md
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
title: "🏠 Домашняя автоматизация"
|
||||
aliases: [Умный дом, Home automation, HA, Zigbee, Modbus, вентиляция, t610, RCU stall, USB reset]
|
||||
tags: [family, how-to, smarthome]
|
||||
updated: 2026-09-15 (ночь-19: §9 — разбор бага CO2 в modbus-bridge: все датчики исправны, виноват мусорный `correction_offset` в `config.template.tmpl`; план фикса утверждён)
|
||||
updated: 2026-09-15 (ночь-22: ✅ §9 — баг CO2 ЗАКРЫТ: убраны мусорные `correction_offset` в `config.template.tmpl`, rebuild аддона, проверено 847/599 ppm; сводки исправны, править не нужно)
|
||||
---
|
||||
|
||||
# 🏠 Домашняя автоматизация
|
||||
@@ -719,6 +719,7 @@ Hz:PWM 0:0 1:0 2:0 3:0 4:48 5:55 6:63 7:70 8:78 9:85 10:92 11:97 12:102 13:107 1
|
||||
| 40 | 🔴 **`correction_offset` в `config.template.tmpl` молча ломает CO2** — остаётся от старых попыток парсинга | Live-значения CO2 смотреть в `mosquitto_sub -t 'modbus/#'`, а не в сущностях HA. Если сырое float32 правдоподобно в ppm (напр. 846.9) → коррекция НЕ нужна, снять её. См. §9 |
|
||||
| 41 | 🔴 **`sensor.*_summary` показывает мусор вида `1692° 780ppm`, но датчик под ним исправен** | Смотреть **сырые** `sensor.<room>_temperature`/`_co2`, не шаблонную сводку. Сводка склеивает атрибуты своим шаблоном — её кривизна не означает поломку датчика. См. §9 |
|
||||
| 42 | ⚠️ **Живой конфиг сниффа — НЕ в `/addons/modbus-bridge/config.yaml`** | `config.yaml` = только опции аддона (device/baud). Регистры и коррекции — в `data/config.template.tmpl` → `run.sh` рендерит из него `/app/config.yml` при старте |
|
||||
| 43 | 🔴 **На хосте t610 `perl` НЕТ** — `perl: command not found` | Правки текста делать `awk` (или писать файл локально и `scp`). `sed`/`perl` недоступны |
|
||||
|
||||
---
|
||||
|
||||
@@ -750,13 +751,29 @@ bedroom_co2: correction_offset: -495 → 589.5 − 495 = 94.5 ❌ (в HA
|
||||
|
||||
**Сырой float32 УЖЕ в ppm** — 846.9 ppm для детской и 589.5 ppm для спальни правдоподобны. Обе коррекции — **мусор от старых попыток парсинга** (подбирались, когда парсер читал мало байт). Температура/влажность работают правильно, потому что их `correction_offset: -4.5` подобран верно.
|
||||
|
||||
### План фикса (утверждён Alex'ом: «Были чинить конечно»)
|
||||
### ✅ ФИКС ВЫПОЛНЕН (2026-09-15, ночь-22:19–22:25) — подтверждён фактом
|
||||
|
||||
1. Бэкап `data/config.template.tmpl` → `.bak-co2fix-<datetime>`
|
||||
2. Убрать `correction_offset: -925` у `kids_co2` и `correction_offset: -495` у `bedroom_co2` (**обе строки целиком**, не править значения)
|
||||
3. ⚠️ **`rebuild` аддона, НЕ `restart`** — `restart` не пересобирает образ, старый `/app/config.template.yml` останется (питфолл §3.7). Команда: `ha apps rebuild local_modbus-bridge` → `start`
|
||||
4. Проверка фактом: `mosquitto_sub -h core-mosquitto -t 'modbus/#'` → `kids/co2 ≈ 847`, `bedroom/co2 ≈ 589`
|
||||
5. Второй шаг: починить шаблоны `sensor.*_summary` в `/config/configuration.yaml` (добавить `|default(0)`, см. `~/tmp-t610/fix_summary_defaults.sh`)
|
||||
1. ✅ Бэкап: `data/config.template.tmpl.bak-co2fix-20260915-221932`
|
||||
2. ✅ Удалены **обе строки целиком** (awk-скрипт, не perl — **на хосте t610 perl НЕТ**, `perl: command not found`):
|
||||
- `kids_co2` → `correction_offset: -925` убрана
|
||||
- `bedroom_co2` → `correction_offset: -495` убрана
|
||||
3. ✅ `ha apps rebuild local_modbus-bridge` → `start` (`rebuild` ОБЯЗАТЕЛЕН — шаблон впекается в образ)
|
||||
4. ✅ **Проверка пройдена:** из лога аддона
|
||||
```
|
||||
Sniff: kids_co2 = 847.26 → MQTT publish: modbus/sensors/kids/co2 = 847.26 [OK]
|
||||
Sniff: bedroom_co2 = 599.73 → MQTT publish: modbus/sensors/bedroom/co2 = 599.73 [OK]
|
||||
```
|
||||
В HA: `sensor.kids_co2 = 848.25`, `sensor.bedroom_co2 = 599.33` (были **−76** и **94**) ✅
|
||||
5. ⛔ **Второй шаг НЕ НУЖЕН — ложная тревога.** Шаблоны `sensor.*_summary` исправны:
|
||||
```
|
||||
sensor.dining_summary = "24° 519ppm" ✅
|
||||
sensor.kids_summary = "24° 849ppm" ✅
|
||||
sensor.bedroom_summary = "25° 599ppm" ✅
|
||||
sensor.dining_air_summary = "25tvoc 6pm" ✅ (tvoc=25, pm10=6)
|
||||
```
|
||||
Датчики под ними всегда были верными. Правка `configuration.yaml` **НЕ вносилась** — и не должна.
|
||||
|
||||
> 📌 **Мораль:** оба «битых элемента» (`dining` и сводки) оказались ошибками чтения, а не поломками. Реально сломан был **только CO2 у kids/bedroom** — один параметр из 15. Диагноз ставить по **сырым** `modbus/sensors/*` из MQTT и по байтам в логе аддона, а НЕ по производным сущностям и шаблонным строкам.
|
||||
|
||||
### 🧭 Как диагностировать (проверенный порядок)
|
||||
|
||||
@@ -776,12 +793,15 @@ ssh root@192.168.2.176 'ha apps logs local_modbus-bridge | grep -E "Raw RTU|→
|
||||
|
||||
## 8. Текущее состояние
|
||||
|
||||
> 🕐 Обновлено **2026-09-15, ночь-5** — найден корень «камера в HA не обновляется» (Generic Camera смотрит на мёртвые порты 1984/8554).
|
||||
> 🕐 Обновлено **2026-09-15, ночь-22** — баг CO2 закрыт (§9), Zigbee переехал на ZHA, камера на `local_ustreamer`.
|
||||
|
||||
- ✅ **MODBUS CO2 ПОЧИНЕН (2026-09-15, ночь-22):** в `data/config.template.tmpl` убраны мусорные `correction_offset: -925` (kids_co2) и `-495` (bedroom_co2); `rebuild` + `start` аддона. Было **−76 / 94**, стало **848 / 599 ppm**. Все три slave (dining/kids/bedroom) публикуют каждые ~10 с, значения правдоподобны. Бэкап `.bak-co2fix-20260915-221932`. Детали — §9.
|
||||
- ✅ **Сводки `sensor.*_summary` ИСПРАВНЫ** — `dining 24° 519ppm`, `kids 24° 849ppm`, `bedroom 25° 599ppm`, `dining_air 25tvoc 6pm`. Правка `configuration.yaml` **не вносилась** (ложная тревога). ⚠️ Прежняя запись про `TemplateError: round got invalid input 'unknown'` на `sensor.bedroom_summary` — при живых данных не воспроизводится (ошибка была из-за `unavailable` до фикса).
|
||||
|
||||
- ✅ **КАМЕРА ПОЧИНЕНА (архитектурно):** оба аддона go2rtc (обычный + hardware) **удалены** через Supervisor API. Вместо них — собственный аддон **`local_ustreamer`** v2.0.0, порт **8090**: `GET /frame` → JPEG 480×640, ffmpeg поднимается **только при клиенте** и снимает **один кадр**. Реалтайм-транскода H.264 больше нет → причина RCU stall устранена. См. §3.6, §4.
|
||||
- ✅ **Хост стабилен:** load 0.76, `pressure/io` 3.26 %, 100 % idle. Проверено после сноса.
|
||||
- ✅ **Все `.bak-*` снесены с t610:** `go2rtc.yaml.bak-*` (3), `/config/*.bak-*` (11), `/config/zigbee2mqtt/*.bak-*` (6) — итого **20 файлов**. Проверено: остатков нет.
|
||||
- ✅ **Список аддонов после чистки (8):** `45df7312_zigbee2mqtt`, `a0d7b954_nodered`, `core_configurator`, `core_mosquitto`, `core_ssh`, `local_mbusd`, `local_modbus-bridge`, `local_ustreamer`.
|
||||
- ✅ **Список аддонов после чистки (7):** `core_ssh`, `core_mosquitto`, `a0d7b954_nodered` (⏸ stopped), `45df7312_zigbee2mqtt` (⏸ stopped — стик отдан ZHA), `local_mbusd`, `local_modbus-bridge`, `local_ustreamer`. `core_configurator` удалён (ночь-6); `go2rtc` ×2 удалены (ночь-4).
|
||||
- 🔴 **ОТКРЫТО, ПЕРВОЕ:** **ориентация кадра неправильная** — в фильтре `/addons/ustreamer/` стоит `transpose=1` (90° по часовой), счётчик лежит на бок. Нужен **`transpose=2`** + rebuild аддона.
|
||||
- 🔴 **ОТКРЫТО, ВТОРОЕ:** **камера в HA не обновляется — КОРЕНЬ НАЙДЕН, правка НЕ внесена.** Generic Camera (`entry_id 01M2FX50K72X2RSYY549QSG3XP`, сущность `camera.192_168_2_176`) настроена на мёртвые адреса: `still_image_url = http://192.168.2.176:1984/api/frame.jpeg?src=usb_camera_h264` (порт **1984** — удалённый go2rtc) и `stream_source = rtsp://192.168.2.176:8554/usb_camera_h264` (RTSP-сервера нет). Отсюда `404 Not Found` в логе. Менять на `still_image_url: http://192.168.2.176:8090/frame`, `stream_source` — пусто. Опции — в `/config/.storage/core.config_entries`, **не в `configuration.yaml`**. См. §4
|
||||
- ⚠️ **ОТКРЫТО:** осиротевший `/config/go2rtc.yaml` (1085 байт) остался — go2rtc удалён, читать нечем. Снести?
|
||||
@@ -797,7 +817,7 @@ ssh root@192.168.2.176 'ha apps logs local_modbus-bridge | grep -E "Raw RTU|→
|
||||
- **Потребление по аддонам (2026-09-15):** Node-RED **197 МБ (13 %)** — крупнейший; HA Core 212 МБ (14 %); Supervisor 77 МБ (5 %); modbus-bridge 20 МБ; Mosquitto 19 МБ; go2rtc ×2 по 3.4 МБ; mbusd 0.8 МБ.
|
||||
- **HAOS** `18.2`, agent `1.10.0`, ядро `6.18.39-haos`, `online_cpus 2`. `ha host info` из аддона работает.
|
||||
- **`unavailable`:** 8 — 7 на slave 10 (AT2-вентиляторы, блок закомментирован; задача снята) + `todo.shopping_list` (системная).
|
||||
- **Zigbee:** 16 устройств (по `configuration.yaml`), все интервью SUCCESSFUL. Конфиг `/config/zigbee2mqtt/configuration.yaml`, `serial.port` — **by-id**.
|
||||
- **Zigbee: ПЕРЕЕХАЛ на ZHA (2026-09-15).** Z2M ⏸ stopped (данные целы в `/config/zigbee2mqtt/`), стик отдан ZHA. **17 устройств + координатор в ZHA**, 15 переименованы, зоны восстановлены. Кнопка `wireless_light_switch_bed` (TS0041) шлёт `remote_button_short_press` только после `zha/devices/reconfigure`. Полный итог — [[family/tech/zigbee-t610-z2m-i-zha]].
|
||||
- **`toilet_1_floor_temperature`:** ✅ 24.4 °C / 47.7 % / bat 100 %, зона Туалет.
|
||||
- **Открытый дефект:** свет кабинета мигает при перезагрузке HA (`office_pass_switch_*`, `platform: state` без `to`). Разбор — [[family/how-to/ha-automations]].
|
||||
- **`slave 20`** (газ-котёл): состояние через bridge не читается.
|
||||
|
||||
@@ -4,7 +4,7 @@ created: '2026-09-15'
|
||||
updated: '2026-09-15 (ночь-18: 🟢 ЗАКРЫТО ПОЛНОСТЬЮ. 4 сироты-автоматизации УДАЛЕНЫ, `unavailable` = 0. Свет лестницы переименован (`light.light_stairs_left`/`_right`), 2 сценария подсветки лестницы ПЕРЕСОЗДАНЫ на новом датчике и `on`. Кнопка подтверждена фактом: 4 нажатия → 8 событий `remote_button_short_press`. Всего автоматизаций 14: 13 `on`, 1 `off` (`Ventilation automation on` — так и было задумано, `last_triggered` 12 марта; ⚠️ попытка включить её была ОТКАЧЕНА по требованию Alex — вне рамок Zigbee-задачи)'
|
||||
type: tech
|
||||
namespace: family
|
||||
status: 🟢 ЗАКРЫТО (2026-09-15 ночь-18). ZHA: 17 устройств, 14 автоматизаций (13 `on`, 1 `off` намеренно), `unavailable` = 0. Кнопка спальни шлёт события, диммер рулится. Свет лестницы + 2 сценария подсветки работают. 🔵 Вне рамок Zigbee: `sensor.dining_summary`/`_air_summary` — косметические шаблонные сводки (сами датчики исправны). 🔴 Разбор бага CO2 в modbus-bridge (мусорный `correction_offset`, план фикса) — в [[family/how-to/home-automation]] §9.
|
||||
status: 🟢 ЗАКРЫТО (2026-09-15 ночь-18). ZHA: 17 устройств, 14 автоматизаций (13 `on`, 1 `off` намеренно), `unavailable` = 0. Кнопка спальни шлёт события, диммер рулится. Свет лестницы + 2 сценария подсветки работают. 🔵 Вне рамок Zigbee (закрыто ночь-22): баг CO2 в modbus-bridge — мусорные `correction_offset` убраны, 848/599 ppm, сводки `sensor.*_summary` оказались исправны. Детали — [[family/how-to/home-automation]] §9.
|
||||
tags:
|
||||
- t610
|
||||
- haos
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Modbus RTU sniffer / RS-485 — отладка
|
||||
|
||||
> Создано 2026-09-15. Зеркало скила `modbus-rtu-sniffer-debugging`.
|
||||
> Применение на живом железе: [[family/how-to/home-automation]] §9 (баг CO2), `<addons>/modbus-bridge`.
|
||||
|
||||
## Когда это нужно
|
||||
|
||||
Пассивный Modbus RTU сниффер:
|
||||
- публикует часть датчиков, часть — нет;
|
||||
- датчик **отвечает в шине** (валидный CRC), но bridge его не публикует;
|
||||
- чтения приходят **разрезанными** на куски;
|
||||
- лог **замирает** на часы, а процесс жив (`started`);
|
||||
- **или** кадр собирается нормально, но значение абсурдное (CO2 отрицательный, 1692 °C) — см. раздел «Неверные значения».
|
||||
|
||||
## Ключевое правило (канон, не фольклор)
|
||||
|
||||
Кадры Modbus RTU разделяются **тишиной в шине**, а не длиной. Кадр завершён
|
||||
только после **≥ T3.5 (3.5 символа)** тишины.
|
||||
|
||||
```
|
||||
t0 = (1 start + 8 data + 2 stop) / baudrate # сек на символ
|
||||
T3.5 = 3.5 * t0 при baud <= 19200
|
||||
T3.5 = 1.75 ms (фикс.) при baud > 19200
|
||||
```
|
||||
|
||||
На **9600 бод**: `t0 = 11/9600 = 1.146 ms` → **T3.5 = 4.01 ms**.
|
||||
|
||||
## Классический баг: сироты в буфере
|
||||
|
||||
Симптом: 12-байтные ответы публикуются, длинный (19 байт) — никогда.
|
||||
|
||||
1. Чтения разрезают кадр между итерациями.
|
||||
2. **Сирота** от предыдущего кадра залипает в начале буфера → сдвиг выравнивания.
|
||||
3. Сканер стартует с `frame[0] = 0x00` → CRC не совпадает никогда.
|
||||
4. Буфер чистится только при **успешном** матче → мусор копится вечно.
|
||||
|
||||
**Доказательство:** ответ датчика ЕСТЬ в шине с валидным CRC → это баг читателя, не устройства.
|
||||
|
||||
**Фикс — порядок обязателен: сначала гейт по тишине, потом скан, потом сброс мусора.**
|
||||
Скан во время приёма байт или очистка до скана — молча ломает всё.
|
||||
|
||||
## 🟠 Неверные значения: кадр парсится, число всё равно мусор
|
||||
|
||||
Отдельный класс багов. Кадр собрался, CRC OK, температура/влажность верные —
|
||||
но одно поле читается абсурдно (−76 ppm CO2, 1692 °C). Баг **не в парсере, а в конфиге**.
|
||||
|
||||
Цепочка:
|
||||
1. Раньше был баг фрейминга → парсер читал мало/не те байты.
|
||||
2. Симптом «починили» **ручной подгонкой `correction_offset`** (напр. `-925`).
|
||||
3. Фрейминг позже починили правильно → сырое значение уже в нужных единицах.
|
||||
4. Оставшийся `correction_offset` теперь **портит корректное значение**:
|
||||
`846.9 ppm − 925 = −78`. Молча, без ошибок.
|
||||
|
||||
**Как отличить от бага парсинга:**
|
||||
|
||||
```
|
||||
1. Взять сырые байты ответа из лога аддона ("Raw RTU: ...").
|
||||
2. Декодировать поле вручную (float32 BE / int16 + divider).
|
||||
3. Сырое значение уже правдоподобно?
|
||||
ДА → баг в correction_offset. УДАЛИТЬ, а не переподбирать.
|
||||
НЕТ → реальный баг offset'ов поля, править их.
|
||||
```
|
||||
|
||||
**Правило: коррекция — это мелкая калибровка (доли единицы), НЕ число размером с показание.**
|
||||
`-925` на ~850 ppm — красный флаг. `-4.5` на 28 °C — нормальная калибровка.
|
||||
Большие offset'ы = окаменевшие workaround'ы.
|
||||
|
||||
Также сверять **имя поля с реальностью**: конфиг звал slave 2 «Bedroom», а MQTT-топики — `kids/*`.
|
||||
Верить живому MQTT-топику, а не `device_name` в конфиге — имена дрейфуют.
|
||||
|
||||
**Проверять по СЫРОМУ источнику, а не по производной сущности.**
|
||||
`sensor.x_summary` (шаблон, склеивающий другие сенсоры) может выглядеть мусором,
|
||||
когда значения под ним исправны — и наоборот. Смотреть `modbus/sensors/<room>/<param>`
|
||||
на брокере или сырые байты, прежде чем объявлять датчик сломанным.
|
||||
Доклад «сводка сломана» вместо «датчик сломан» — потерянная сессия.
|
||||
|
||||
**Конфиг лежит там, где шаблон, а не там, где опции.**
|
||||
Для HA-аддонов: `config.yaml` = только опции (device, baud). Карта регистров и коррекции —
|
||||
в `data/config.template.tmpl`, откуда `run.sh` рендерит `/app/config.yml` при старте.
|
||||
Правка `config.yaml` не делает ничего. Шаблон **впекается в образ** → нужен `rebuild`, не `restart`.
|
||||
|
||||
**Инструменты на целевом хосте.** Минимальные HAOS/BusyBox хосты **не имеют `perl`**.
|
||||
Правка текста там → `awk`, либо писать новый файл локально и `scp`. Всегда `diff`
|
||||
старого с новым и проверять, что diff непустой, перед перезаписью.
|
||||
|
||||
## Питфоллы
|
||||
|
||||
- **Не хардкодить T3.5** — выводить из baud.
|
||||
- **Не сканировать, пока байты ещё идут** — иначе не отличить «частичный кадр» от «битого».
|
||||
- **`timeout` pyserial ≠ таймаут кадра.** T3.5 — мера тишины в шине, из baud.
|
||||
- **Одиночный байт — это норма**, а не поломка.
|
||||
- **Не винить устройство раньше логов сырых байт.**
|
||||
- **Dockerfile HA-аддона обычно `COPY app.py`** → правка файла без `rebuild` ничего не меняет.
|
||||
- **Диагностика за env-флагом требует rebuild** для переключения (env_vars в манифесте нет) — ставить в `run.sh` и пересобирать.
|
||||
|
||||
## Чеклист проверки
|
||||
|
||||
1. Сырой лог показывает целевой кадр целиком (`[RAW n]`).
|
||||
2. `Slave: N Func: 0x3 CRC OK: True`.
|
||||
3. Значения опубликованы (MQTT `[OK]` / HA push).
|
||||
4. Независимая проверка — читать **потребителя** (HA API states), а не только лог продюсера.
|
||||
5. Счётчики: `BUF-LEFT` = 0 (накопления нет).
|
||||
Reference in New Issue
Block a user