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

209 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Анализ динамического сбоя 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.