Files
obsidian-vault/personal/tech/modbus-rtu-sniffer-debugging.md
T
Taiga b53dd70a15 [2026-09-26 09:10:28] syncthing: personal/tech/archive-org-collection-harvest.md personal/tech/balda-go-fx-pitfalls.md personal/tech/bilety-oxota-interaktiv.md personal/tech/cloudflare-tunnel-ssh-published-route.md personal/tech/deepseek-language-drift.md personal/tech/docker-data-root-mount-race.md personal/tech/docker-mac-disk-issues.md personal/tech/docker_colima_setup.md personal/tech/eagle-dashboard-launchd.md personal/tech/hermes-agent-improvements.md personal/tech/hermes-browser-toolsets.md personal/tech/hermes-fork-vs-upstream.md personal/tech/hermes-git-repo.md personal/tech/hermes-memory-architecture.md personal/tech/hermes-secret-redaction-bypass.md personal/tech/hermes-self-improvement-review.md personal/tech/home-network-config.md personal/tech/inter-site-throughput-taiga-to-mac.md personal/tech/inter-site-throughput-taiga-to-mac.md.bak-nedstab2 personal/tech/jellyfin-db-schema-mismatch.md personal/tech/karabiner-keyboard-config.md personal/tech/kraken-media-inventory-facts.md personal/tech/kraken-truenas-media-copies.md personal/tech/lampa-jellyfin-plugin.md personal/tech/lampac-deployment.md personal/tech/lampac-torrserver.md personal/tech/llm-model-selection-2026.md personal/tech/mac-print-shared-services.md personal/tech/modbus-rtu-sniffer-debugging.md personal/tech/multistream-single-flow-limit.md personal/tech/multistream-single-flow-limit.md.bak-nedstab2 personal/tech/music-tags-repair-truenas.md personal/tech/obsidian-mcp-ecosystem.md personal/tech/original-xbox-2025-guide.md personal/tech/prowlarr-flaresolverr-cloudflare.md personal/tech/radarr-sonarr-phantom-records.md personal/tech/roundtrip-key-verification.md personal/tech/ryujinx-mac-setup.md personal/tech/skill-arr-media-stack-setup-additions.md personal/tech/skill-docker-data-root-mount-race.md personal/tech/skill-lampac-kraken-deploy.md personal/tech/tirith-trust.md personal/tech/truenas-local-tree-comparison.md personal/tech/truenas-nfs4-acl-and-arrmultiuser.md personal/tech/truenas-zfs-panic-recovery.md personal/tech/vault-doc-pruning.md personal/tech/vless-space-subscription-egress.md personal/tech/watchlist-sync-feed.md personal/tech/watchtower-docker-updates.md personal/tech/xray-outbound-subscription-3xui.md personal/tech/xray-reverse-limits-private-network.md personal/tech/xray-reverse-tunnel-kraken-truenas.md personal/tech/xray-reverse-tunnel-kraken-truenas.md.bak-nedstab2 personal/tech/yandex-music-api.md
2026-09-26 09:10:28 +00:00

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