Files
obsidian-vault/personal/tech/modbus-rtu-sniffer-debugging.md
T

7.2 KiB
Raw Blame History

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 msT3.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 (накопления нет).