# Modbus RTU sniffer / RS-485 — отладка > Создано 2026-09-15. Зеркало скила `modbus-rtu-sniffer-debugging`. > Применение на живом железе: [[family/how-to/home-automation]] §9 (баг CO2), `/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//` на брокере или сырые байты, прежде чем объявлять датчик сломанным. Доклад «сводка сломана» вместо «датчик сломан» — потерянная сессия. **Конфиг лежит там, где шаблон, а не там, где опции.** Для 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 (накопления нет).