# Анализ динамического сбоя 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.