Files
elmer/doc/dynamic-diagnostics-analysis-2026-06-14.md
T

16 KiB
Raw Blame History

Анализ динамического сбоя ELM327

Дата: 2026-06-14

Контекст

Проверено:

  • статическая диагностика работает стабильно;
  • чтение VIN работает;
  • чтение DTC работает;
  • последовательное чтение нескольких PID в статическом режиме работает;
  • ELM327 выдерживает не менее 9 PID подряд в статике;
  • увеличение пауз до 4000 мс не устраняет проблему;
  • автоподбор таймингов не устраняет проблему;
  • сбой проявляется только в динамическом сборе данных через ScriptEngine.

Это сильно сужает пространство причин. Проблема почти наверняка не в "скорости ELM вообще", не в "ECU не успевает" и не в банальном "надо ещё увеличить задержку".

Что объясняет факты лучше всего

1. Несовпадение между тем, как ElmChecker и ScriptEngine общаются с ELM

Вероятность: высокая.

Смысл гипотезы: статический путь и динамический путь используют не одинаковую коммуникационную последовательность. Ломается не ELM как таковой, а конкретный сценарий: кто пишет, кто читает, когда читают, что считается окончанием ответа, как очищается буфер, как меняются состояния.

Что подтверждает:

  • статический путь работает полностью;
  • динамический ломается только в ScriptEngine;
  • один и тот же адаптер выдерживает длинную серию PID в статике;
  • в проектных заметках уже зафиксировано, что проблема может быть именно в различии между ElmChecker и ScriptEngine, а не в тайминге как таковом.

Что противоречит:

  • если в удачном и неудачном сценарии полностью совпадают AT-команды, порядок команд, чтение и ожидание конца ответа.

Быстрый эксперимент:

  • снять полный лог команд и сырых ответов для успешного статического пути и для динамического пути;
  • сравнить именно последовательность TX/RX, а не распарсенные значения;
  • проверить, расходится ли сценарий уже до первого PID.

2. InputStream не дочитывается до символа ">", и следующий запрос попадает в хвост прошлого ответа

Вероятность: высокая.

Смысл гипотезы: динамический читатель завершает чтение раньше, чем ELM реально закончил ответ. В буфере остаётся промпт > или другой хвост, и следующий запрос читает не чистый ответ, а остаток прошлого цикла.

Что подтверждает:

  • это прямо совпадает с типовым режимом отказа ELM327;
  • в проектных заметках символ > отдельно выделен как конец ответа;
  • в обсуждениях проекта уже встречалась версия, что ответы смешиваются и хвост остаётся в буфере;
  • статика может это маскировать, потому что между командами там больше естественных пауз.

Что противоречит:

  • если сырые логи показывают, что каждый ответ полностью доходит до > и следующий запрос стартует только после этого;
  • если после сбоя буфер точно пуст.

Быстрый эксперимент:

  • включить сырой дамп RX/TX без парсинга;
  • для первого сбойного цикла проверить, присутствует ли > в сыром потоке полностью;
  • перед следующим запросом проверить, не остаётся ли в InputStream ничего, кроме уже считанного ответа.

3. Остатки данных в буфере ломают синхронизацию между командами

Вероятность: высокая.

Смысл гипотезы: чтение и запись идут корректно по отдельности, но между ними нет надёжной очистки буфера. В результате следующий запрос потребляет не только свежий ответ, но и мусор: эхо, переносы строк, старые байты, задержавшийся ответ предыдущего PID.

Что подтверждает:

  • проектные заметки отдельно говорят, что drainInput раньше был механизмом синхронизации запрос/ответ;
  • после отключения drainInput в одной из версий стало хуже;
  • описан эффект сдвига: ответ на команду N прочитан как ответ на N+1;
  • статический сценарий выдерживает это лучше из-за более редкой частоты обращений.

Что противоречит:

  • если перед каждым запросом в динамическом цикле буфер гарантированно очищается и при этом проблема остаётся;
  • если в логах нет признаков мусора, эха или сдвига границ ответов.

Быстрый эксперимент:

  • один раз до старта динамики и один раз перед вторым запросом вывести количество доступных байт в InputStream и содержимое остатка;
  • сравнить результат между успешным статическим и неудачным динамическим прогоном;
  • проверить, есть ли хвосты после первого ответа.

4. ScriptEngine выполняет не тот же state machine, что ElmChecker

Вероятность: средняя.

Смысл гипотезы: проблема не в самом Bluetooth и не в самом ELM, а в том, что динамический движок переходит между состояниями раньше или иначе, чем ElmChecker. Например, команда считается завершённой по временному признаку, а не по фактическому окончанию ответа.

Что подтверждает:

  • пользователь отдельно выделил риск state machine;
  • динамический режим содержит свои шаги, цикл и внутренние переходы;
  • статический путь короче и проще, поэтому ошибки state machine там могут не проявляться;
  • уже были замечания, что в таких сценариях рассинхрон появляется раньше, чем кажется.

Что противоречит:

  • если логически и по времени state transitions происходят только после полного ответа ELM;
  • если обе машины выполняют одинаковый сценарий завершения команды.

Быстрый эксперимент:

  • на одном прогоне логировать каждое состояние до и после отправки команды;
  • отметить момент, когда реально получен >;
  • проверить, не уходит ли ScriptEngine в следующий шаг до фактического конца ответа.

5. Доступ к одному сокету или одному InputStream идёт из двух потоков

Вероятность: средняя.

Смысл гипотезы: чтение или запись в динамике пересекаются с другим потоком, который тоже читает или пишет тот же канал. Для ELM это критично: поток байтов становится недетерминированным, и команда может лишиться части ответа.

Что подтверждает:

  • пользователь отдельно попросил проверить конкурентный доступ к сокету;
  • динамический режим по определению более многопоточен: цикл, сбор данных, UI, возможные фоновые операции;
  • статический путь может не задевать гонку из-за более редкой частоты и меньшего числа активных операций.

Что противоречит:

  • если трасса покажет строго одного читателя и одного писателя на весь жизненный цикл соединения;
  • если динамика воспроизводится даже в полностью однопоточном прогоне.

Быстрый эксперимент:

  • вывести thread id для каждого read и write;
  • проверить, нет ли второго consumer на InputStream;
  • сравнить идентичность владельца сокета в статике и динамике.

6. Неправильная последовательность команд, а не неправильная пауза

Вероятность: средняя-низкая.

Смысл гипотезы: дело не в длительности ожидания как таковой, а в том, что динамический сценарий отправляет команды в другом порядке или с другим набором служебных AT-команд, чем успешный статический сценарий. Тогда ELM оказывается в другом режиме, и дальнейшая обработка ломается.

Что подтверждает:

  • в проекте есть отдельные сценарии для статической диагностики, тестового скрипта и динамики;
  • серверный build_test_script и build_dynamic_script действительно строят разные последовательности;
  • в заметках по ELM отдельно обсуждаются последствия ATWS, ATE0/ATL0/ATS0 и различий в инит-последовательности.

Что противоречит:

  • если сравнение трасс покажет полностью одинаковый init и только разный темп;
  • если тот же набор команд в статике и динамике повторяет поломку только из-за способа выполнения, а не порядка.

Быстрый эксперимент:

  • распечатать полный список команд, которые реально уходят в ELM в обоих режимах;
  • сравнить not only PID, но и все AT-команды, входы в state machine и возможные reset-команды;
  • проверить, совпадает ли стартовая инициализация побайтно.

Что менее вероятно

Adaptive timing как первопричина

Вероятность: низкая.

Почему низкая:

  • уже проверяли увеличение пауз до 4000 мс;
  • уже проверяли автоподбор;
  • одиночные запросы и статический набор PID работают.

Вывод: adaptive timing может усиливать или маскировать проблему, но не выглядит корнем сбоя.

Просто "мало ждать"

Вероятность: низкая.

Почему низкая:

  • паузы уже увеличивали;
  • первый запрос проходит, второй ломается;
  • для обычного ELM327 это больше похоже на ошибку синхронизации, чем на нехватку миллисекунд.

Итоговая интерпретация

Новое мнение хорошо согласуется с уже собранными фактами. Оно сдвигает фокус с "таймингов вообще" на более узкий класс проблем:

  • границы ответа ELM, особенно символ >;
  • остатки в InputStream;
  • различие между ElmChecker и ScriptEngine;
  • state machine, которая может идти вперёд раньше времени;
  • возможная конкуренция за сокет или поток чтения.

Главный вывод: если статический путь стабилен, а динамический ломается даже при больших паузах, то первичная причина почти наверняка находится не в задержках, а в чтении потока, границах ответа и разнице в сценарии исполнения.

Порядок расследования

  1. Снять сырой RX/TX лог без парсинга для статического и динамического режима.
  2. Проверить, доходит ли каждый ответ до > и не остаются ли байты в буфере перед следующим запросом.
  3. Сопоставить полную последовательность команд ElmChecker и ScriptEngine.
  4. Подтвердить или опровергнуть второй consumer на сокете/InputStream.
  5. Проверить, не идёт ли state machine вперёд до фактического завершения ответа.

Краткий вывод

Наиболее правдоподобно, что проблема не в скорости ELM, а в том, как динамический сценарий читает и синхронизирует поток ответов. Внутри этого класса причин самые сильные кандидаты: неполное дочитывание до >, остатки в InputStream, и различие между ElmChecker и ScriptEngine.