7.2 KiB
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 байт) — никогда.
- Чтения разрезают кадр между итерациями.
- Сирота от предыдущего кадра залипает в начале буфера → сдвиг выравнивания.
- Сканер стартует с
frame[0] = 0x00→ CRC не совпадает никогда. - Буфер чистится только при успешном матче → мусор копится вечно.
Доказательство: ответ датчика ЕСТЬ в шине с валидным CRC → это баг читателя, не устройства.
Фикс — порядок обязателен: сначала гейт по тишине, потом скан, потом сброс мусора. Скан во время приёма байт или очистка до скана — молча ломает всё.
🟠 Неверные значения: кадр парсится, число всё равно мусор
Отдельный класс багов. Кадр собрался, CRC OK, температура/влажность верные — но одно поле читается абсурдно (−76 ppm CO2, 1692 °C). Баг не в парсере, а в конфиге.
Цепочка:
- Раньше был баг фрейминга → парсер читал мало/не те байты.
- Симптом «починили» ручной подгонкой
correction_offset(напр.-925). - Фрейминг позже починили правильно → сырое значение уже в нужных единицах.
- Оставшийся
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.
- Не сканировать, пока байты ещё идут — иначе не отличить «частичный кадр» от «битого».
timeoutpyserial ≠ таймаут кадра. T3.5 — мера тишины в шине, из baud.- Одиночный байт — это норма, а не поломка.
- Не винить устройство раньше логов сырых байт.
- Dockerfile HA-аддона обычно
COPY app.py→ правка файла безrebuildничего не меняет. - Диагностика за env-флагом требует rebuild для переключения (env_vars в манифесте нет) — ставить в
run.shи пересобирать.
Чеклист проверки
- Сырой лог показывает целевой кадр целиком (
[RAW n]). Slave: N Func: 0x3 CRC OK: True.- Значения опубликованы (MQTT
[OK]/ HA push). - Независимая проверка — читать потребителя (HA API states), а не только лог продюсера.
- Счётчики:
BUF-LEFT= 0 (накопления нет).