6.1 KiB
Анализ причин отказа ELM327 v1.5 при динамическом опросе
Анализ кода ElmProtocol.kt, DynamicCollector.kt и ElmChecker.kt выявил ряд критических проблем, которые в совокупности приводят к "зависанию" адаптера ELM327 (особенно дешевых клонов v1.5) при переходе к быстрому циклу опроса.
Ответы на вопросы
1. Почему ELM327 v1.5 замолкает после первых 2 ответов?
Основная причина — десинхронизация и переполнение буфера.
- ATWS прямо перед циклом: Команда
ATWS(Warm Start) сбрасывает микроконтроллер. Ему требуется время на инициализацию (обычно 500-1000 мс). Код вMainActivityждет всего 300 мс. Первые команды010Cприлетают, когда ELM еще "просыпается" или находится в неопределенном состоянии. - Эффект домино в
exec(): Если первый PID в цикле (010C) не успел ответить вовремя,execвозвращает пустую строку, но ELM продолжает обработку. Следующий вызовsendCommandчерезwrite()делаетdrainInput(), удаляя запоздавший ответ, и посылает новую команду. Для клона v1.5 типична ситуация, когда он "захлебывается", если получает новую команду, не закончив передачу предыдущего ответа или символа>.
2. Может ли drainInput() в write() съедать ответ?
Да, и это главная проблема надежности.
Если ELM327 ответил на 50 мс позже таймаута, данные уже лежат в буфере Bluetooth-сокета. Вызов write() для следующей команды в цикле безусловно их очищает. В итоге read() следующей команды видит пустоту, провоцируя новые таймауты и ретраи. Происходит рассинхрон: приложение ждет ответ на команду B, а ELM (если не завис) шлет ответ на команду A.
3. Критична ли последовательность: static -> speed-test -> ATWS -> dynamic?
Последовательность перегружена сбросами.
ATWSсбрасывает настройкиATAT1,ATSP,ATL0и т.д., которые были установлены вinit().- После
ATWSпротокол может вернуться кAUTO(ATSP0), что заставляет ELM тратить время на "SEARCHING..." при первом же запросе010C. Это гарантированный таймаут в динамическом тесте.
4. Нужно ли переподключать ELM вместо ATWS?
Переподключать Bluetooth-сокет не обязательно, но вместо ATWS лучше вызвать серию настроечных команд, гарантирующих состояние:
ATE0(эхо выкл)ATL0(переносы строк выкл)ATS0(пробелы выкл) — крайне важно для скорости и предотвращения переполнения буфера.ATSP X(принудительная установка протокола, найденного вcheckDevice), чтобы исключить стадию поиска.
5. Как правильно реализовать динамический опрос для v1.5?
Для минимизации ошибок на медленных адаптерах:
- Убрать
Thread.sleep(350)внутри циклаfor (step in steps). Пауза должна быть только между пакетами (батчами) PID, если нужно ограничить частоту. Внутри батча команды должны идти максимально плотно: послал -> дождался>-> сразу следующий. - Увеличить таймаут для v1.5. 500 мс — это предел для v1.5. Первичный запрос (особенно после сброса) может занимать до 1500 мс.
- Оптимизировать
read(): v1.5 очень чувствителен к таймингам. ТекущийThread.sleep(1)вread()— это хорошо, но логикаdrainInputдолжна быть перемещена: чистить буфер нужно только один раз перед стартом всего динамического теста, а не перед каждой командой.
Рекомендации по исправлению
- В
ElmProtocol.kt:- Сделать
drainInput()опциональным параметром вwrite()или убрать его изsendCommandпо умолчанию. - В
handle()при полученииNODATAилиERRORне делатьATWSмгновенно, так как это убивает сессию опроса.
- Сделать
- В
DynamicCollector.kt:- Удалить
Thread.sleep(350)в циклеfor. Вместо этого полагаться на таймаутыElmProtocol.
- Удалить
- В
MainActivity.kt:- Убрать
ATWSперед стартом. Если нужен сброс — использоватьATZи ждать 2 секунды, после чего заново прогнать весьinit(). - Перед запуском
DynamicCollectorзафиксировать протокол:elmProto.sendCommand("ATSP" + currentProtocol).
- Убрать