fix: remove harmful OBD retries and improve ELM v1.5 stability

This commit is contained in:
Repinoid
2026-06-13 10:21:51 +04:00
parent e380efa98c
commit 097740f348
+86
View File
@@ -0,0 +1,86 @@
# Анализ деградации ELM-кода в v1.9.0 и план исправления
## 1. Почему v1.9.0 стал ХУЖЕ (ни одного ответа)?
Главная причина — **отключение `drainInput()` в `write()`** совмещенное с агрессивной работой `exec()`.
* В v1.3.0 `drainInput()` вызывался перед каждой командой. Если ELM "подлагивал" и присылал ответ на предыдущую команду с опозданием, `drainInput()` вычищал этот мусор, и `read()` начинал ждать с чистого листа.
* В v1.9.0 `drainInput()` выключен. Если первая команда батча (`010C`) дает таймаут, `exec()` делает **10 RETRY**.
* Сценарий катастрофы:
1. Послали `010C`. Тайм-аут.
2. Послали `010C` (retry 1). В этот момент ELM прислал ответ на #1.
3. `read()` в retry 1 видит ответ от #1, но ELM уже занят обработкой #2.
4. Следующие команды накладываются друг на друга. Буфер Bluetooth забивается ответами, которые не соответствуют командам.
5. ELM v1.5 "захлебывается" потоком данных в TX-буфере и перестает отвечать на новые команды вовсе.
## 2. Нужен ли ATWS перед динамикой?
**Да, но с нюансами.**
* `ATWS` (Warm Start) гарантирует, что ELM забыл про "SEARCHING" и сбросил внутренние ошибки протокола.
* **Ожидание после `ATWS`:** Минимум **1000мс** (на v1.3 было 300мс — мало, на v1.9 вообще убрали).
* **ВАЖНО:** После `ATWS` нужно заново послать `ATE0`, `ATL0`, `ATS0`, так как Warm Start сбрасывает их в дефолт.
## 3. Нужен ли drainInput() в write()?
**Да, обязательно**. Но не просто "чистка всего", а **синхронизация**.
Для ELM327 принцип "запрос-ответ" свят. Мы не имеем права слать новую команду, пока не получили `>` от предыдущей ИЛИ не дождались таймаута И вычистили буфер.
## 4. Почему статика работает?
В статике между командами проходят секунды (пока поток UI обработает, пока лог запишется). Этого времени хватает ELM, чтобы "прочихаться". В динамике команды идут пачками, что превращает любую задержку адаптера в фатальный рассинхрон.
## 5. Retry в `exec()` с переповтором команды — ГЛАВНЫЙ БАГ.
Повторная отправка OBD-команды (`010C`) при таймауте — это **яд для ELM327**.
Команда OBD — это не просто AT-запрос. ELM должен отправить запрос в CAN-шину, дождаться ответа от ЭБУ, отформатировать его и выдать. Если мы шлем `010C` второй раз, когда ELM еще ждет ответа от CAN, он может либо зависнуть, либо выдать `BUFFER FULL`.
---
# Необходимые правки кода
### 1. `ElmProtocol.kt`: Убрать вредный retry OBD-команд
```kotlin
// В методе exec():
// БЫЛО:
for (i in 0 until MAX_RETRIES) {
write(cmd) // ← НЕЛЬЗЯ ПЕРЕПОСЫЛАТЬ OBD-команду!
// ...
}
// НАДО:
private fun exec(cmd: String, timeout: Long): String {
write(cmd)
var t = timeout
try {
return handle(read(t))
} catch (_: TimeoutException) {
increaseTimeout()
// Очищаем буфер, так как команда "протухла"
drainInput()
return ""
}
}
```
### 2. `ElmProtocol.kt`: Вернуть `drainInput()` в `write()`
Без него мы читаем ответы на позапрошлые команды.
### 3. `DynamicCollector.kt`: Добавить микро-паузу между PID
ELM v1.5 физически не может переключаться между PID быстрее чем за 100-200мс.
```kotlin
// В цикле for (step in steps):
val raw = elm.sendCommand(step.cmd)
Thread.sleep(150) // Пауза для стабильности v1.5
```
### 4. `MainActivity.kt`: Правильная инициализация перед тестом
```kotlin
// Перед стартом DynamicCollector:
elmProto.sendCommand("ATWS")
Thread.sleep(1200) // Ждем инициализации
elmProto.sendCommand("ATE0") // Эхо выкл
elmProto.sendCommand("ATL0") // Переносы выкл
elmProto.sendCommand("ATS0") // Пробелы выкл (КРИТИЧНО для скорости)
elmProto.drainInput()
```