docs: запрос Claude v2 — СТРОГО только анализ, код не менять

This commit is contained in:
Repinoid
2026-06-13 10:27:30 +04:00
parent 097740f348
commit bc928defdb
+255 -51
View File
@@ -1,73 +1,277 @@
# Запрос к Claude Sonnet — поиск багов в ELM-коде
# Запрос к Claude Sonnet — ТОЛЬКО АНАЛИЗ
## Контекст
## ⛔ ЗАПРЕЩЕНО МЕНЯТЬ КОД ⛔
## ⛔ НЕ ДЕЛАТЬ КОММИТЫ ⛔
## ⛔ НЕ ПРАВИТЬ ФАЙЛЫ ⛔
## ⛔ ТОЛЬКО АНАЛИЗ — вывод в файл `doc/claude-analysis-elm-v2.md` ⛔
Android-приложение для OBD2-диагностики через ELM327 Bluetooth.
Стек: Kotlin, minSdk 24.
Ты — эксперт по ELM327 и OBD2. Тебе дан код Android-приложения (Kotlin). НАЙДИ БАГИ, ОБЪЯСНИ, ДАЙ РЕКОМЕНДАЦИИ. Код не менять.
**Проблема:** динамический тест (опрос 3 PID в цикле) — ELM327 v1.5 перестаёт отвечать.
---
### Данные тестов (реальные сессии из БД)
## Проблема
**v1.3.0-dev (#54):** статика работает, ATWS + 300ms перед динамикой
Динамический тест: опрос 3 PID (010C RPM, 0110 MAF, 0106 STFT) в цикле. ELM327 v1.5 замолкает.
### Реальные данные с машины
**v1.3.0-dev** (drainInput в каждой write, ATWS+300ms):
```
0106 (STFT): 1 ok / 18 попыток
010C (RPM): 1 ok / 18 попыток
0110 (MAF): 0 ok / 18 попыток
Первые 2 ответа — данные, затем 16 пустых.
0106: 1/18 ok 010C: 1/18 ok 0110: 0/18 ok
Первые 2 ответа — данные, дальше 16 пустых.
```
**v1.9.0-dev (#56):** без ATWS, без drain, без sleep(350)
**v1.9.0-dev** (drainInput отключён, без ATWS):
```
0106 (STFT): 0 ok / 15 попыток ← СТАЛО ХУЖЕ
010C (RPM): 0 ok / 15 попыток
0110 (MAF): 0 ok / 15 попыток
ВСЕ 15 ответов пустые. ELM вообще не отвечает.
0106: 0/15 ok 010C: 0/15 ok 0110: 0/15 ok ← СТАЛО ХУЖЕ
ВСЕ 15 пустые.
```
### Что изменилось в v1.9.0
1. Убрали `ATWS` перед динамикой (заменили на `ATE0`/`ATL0`/`ATS0`)
2. Убрали `Thread.sleep(350)` между командами внутри батча
3. Добавили `setDrainBeforeWrite(false)` — drainInput() не вызывается в write()
4. Вызвали `drainInput()` один раз перед стартом DynamicCollector
**Статическая диагностика** — одиночные PID — работает идеально.
### Статика работает идеально (сессия #55, v1.9.0-dev)
Все 9 PID (0104-0111) — ok. Проблема ТОЛЬКО в DynamicCollector.
---
## Код
Файлы лежат в `android/app/src/main/java/ru/elmer/client/`:
Все файлы в `android/app/src/main/java/ru/elmer/client/`.
### ElmProtocol.kt (elm/ElmProtocol.kt) — ПОЛНОСТЬЮ ВАЖЕН
Стейт-машина AndrOBD. Методы: `sendCommand()``exec()``write()``read()`.
Ключевое: `write()` вызывает `drainInput()` перед каждой командой.
`exec()` делает до 10 retry при таймауте (каждый retry = новый write()).
### 1. ElmProtocol.kt (elm/ElmProtocol.kt) — Стейт-машина AndrOBD
### DynamicCollector.kt (script/DynamicCollector.kt) — ПОЛНОСТЬЮ ВАЖЕН
Цикл опроса: `while(running) { for(step in steps) { elm.sendCommand(step.cmd) } sleep(interval) }`
Сейчас без sleep между командами внутри батча.
```kotlin
package ru.elmer.client.elm
### MainActivity.kt (ui/MainActivity.kt) — строки 344-470
Метод `startDynamicRecording()`. Поток выполнения:
1. `checker.ensureConnected()` — переподключение если нужно
2. Статический проброс 9 PID (каждый через sendCommand)
3. ATE0/ATL0/ATS0 — переинициализация
4. `elmProto.drainInput()` — однократная очистка
5. `elmProto.setDrainBeforeWrite(false)` — отключаем drain
6. `DynamicCollector.start()` — цикл опроса
7. После stop: `elmProto.setDrainBeforeWrite(true)`
import android.util.Log
import java.io.InputStream
import java.io.OutputStream
class ElmProtocol(
private val input: InputStream,
private val output: OutputStream
) {
companion object {
private const val TAG = "ElmProto"
private const val POLL_DELAY = 1L
private const val INIT_TIMEOUT = 10000L
private const val DEF_TIMEOUT = 500L
private const val TIMEOUT_MIN = 50L
private const val TIMEOUT_MAX = 2000L
private const val TIMEOUT_STEP = 20L
private const val TIMEOUT_RES = 4
private const val MAX_RETRIES = 10
}
private enum class State { UNDEFINED, INITIALIZING, READY, BUSY, ERROR, DISCONNECTED }
private var state = State.UNDEFINED
private var timeoutMs = DEF_TIMEOUT
private var learnedMin = TIMEOUT_MIN
fun init() {
state = State.INITIALIZING
write("ATSP0"); tryRead(4000); drainInput()
write("ATAT1"); tryRead(2000); drainInput()
updateAtst()
write("ATS0"); tryRead(2000); drainInput()
write("ATL0"); tryRead(2000); drainInput()
write("ATE0"); tryRead(2000); drainInput()
state = State.READY
}
fun sendCommand(cmd: String): String {
if (state == State.ERROR || state == State.DISCONNECTED) recover()
state = State.BUSY
val result = exec(cmd, timeoutMs)
if (state == State.BUSY) state = State.READY
return result
}
private fun exec(cmd: String, timeout: Long): String {
write(cmd)
var t = timeout
for (i in 0 until MAX_RETRIES) {
try {
return handle(read(t))
} catch (_: TimeoutException) {
if (state == State.INITIALIZING) t += 1000
else { increaseTimeout(); t = timeoutMs }
}
}
state = State.ERROR
return ""
}
private fun handle(raw: String): String {
val u = raw.uppercase().trim()
when {
u.startsWith("SEARCHING") -> {}
u.startsWith("OK") -> decreaseTimeout()
u.startsWith("NODATA") || u.startsWith("NO DATA") -> { increaseTimeout(); updateAtst() }
isBusError(u) -> {
state = State.DISCONNECTED; resetTimeout(); updateAtst()
write("ATPC"); tryRead(3000); write("ATSP0"); tryRead(3000)
}
u.startsWith("ERROR") && !u.startsWith("DATA ERROR") -> { state = State.ERROR; write("ATWS"); tryRead(3000) }
isDataError(u) -> { state = State.ERROR; write("ATWS"); tryRead(3000) }
else -> decreaseTimeout()
}
return raw
}
private fun recover() {
state = State.INITIALIZING
write("ATWS"); tryRead(2000); drainInput()
write("ATSP0"); tryRead(2000); drainInput()
write("ATE0"); tryRead(2000); drainInput()
state = State.READY
}
private fun write(cmd: String) {
drainInput()
output.write((cmd + "\r").toByteArray())
output.flush()
}
private fun drainInput() {
while (input.available() > 0) input.read()
}
@Throws(TimeoutException::class)
private fun read(timeout: Long): String {
val dl = System.currentTimeMillis() + timeout
val sb = StringBuilder()
val lines = mutableListOf<String>()
var gotPrompt = false
while (System.currentTimeMillis() < dl) {
if (input.available() > 0) {
val b = input.read()
if (b == -1) break
when (b) {
62 -> { push(sb, lines); gotPrompt = true; break }
13 -> push(sb, lines)
10, 32 -> {}
else -> sb.append(b.toChar())
}
} else { Thread.sleep(POLL_DELAY) }
}
push(sb, lines)
if (!gotPrompt) throw TimeoutException("timeout ${timeout}ms")
return lines.joinToString("\n")
}
private fun tryRead(timeout: Long) { try { read(timeout) } catch (_: TimeoutException) {} }
private fun push(sb: StringBuilder, lines: MutableList<String>) {
if (sb.isNotEmpty()) { lines.add(sb.toString()); sb.clear() }
}
private fun increaseTimeout() { if (timeoutMs + TIMEOUT_STEP < TIMEOUT_MAX) timeoutMs += TIMEOUT_STEP }
private fun decreaseTimeout() { if (timeoutMs - TIMEOUT_STEP >= learnedMin) timeoutMs -= TIMEOUT_STEP }
private fun resetTimeout() { timeoutMs = DEF_TIMEOUT }
fun resetAdaptiveTiming() { timeoutMs = DEF_TIMEOUT }
private fun updateAtst() {
val v = (timeoutMs / TIMEOUT_RES).toInt().coerceAtLeast(1)
write("ATST${v.toString(16).uppercase().padStart(2, '0')}")
tryRead(2000); drainInput()
}
private fun isBusError(s: String) = listOf("UNABLE","BUS BUSY","BUS ERROR","CAN ERROR","BUS INIT","STOPPED").any { s.startsWith(it) }
private fun isDataError(s: String) = listOf("DATA ERROR","BUFFER FULL","RX ERROR").any { s.startsWith(it) }
}
class TimeoutException(message: String) : Exception(message)
```
### 2. DynamicCollector.kt (script/DynamicCollector.kt)
```kotlin
package ru.elmer.client.script
import ru.elmer.client.elm.ElmProtocol
import ru.elmer.client.elm.ObdDecoder
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.concurrent.thread
class DynamicCollector(
private val elm: ElmProtocol,
private val steps: List<ElmStep>,
private val intervalMs: Long,
private val onSample: (sampleIndex: Int) -> Unit,
private val onLog: (msg: String) -> Unit
) {
data class ElmStep(val id: String, val cmd: String, val desc: String)
private val running = AtomicBoolean(false)
private val samples = mutableListOf<List<SampleResponse>>()
private var threadRef: Thread? = null
data class SampleResponse(val stepId: String, val cmd: String, val raw: String, val decoded: String, val ts: Long = 0)
fun start() {
running.set(true)
val startTs = System.currentTimeMillis()
threadRef = thread(name = "DynamicCollector", isDaemon = true) {
var idx = 0
while (running.get()) {
val t0 = System.currentTimeMillis()
val batch = mutableListOf<SampleResponse>()
for (step in steps) {
if (!running.get()) break
try {
val raw = elm.sendCommand(step.cmd)
val dec = ObdDecoder.decode(step.cmd, raw)
batch.add(SampleResponse(step.id, step.cmd, raw, dec, System.currentTimeMillis() - startTs))
} catch (e: Exception) {
batch.add(SampleResponse(step.id, step.cmd, "(err)", e.message ?: "error", System.currentTimeMillis() - startTs))
}
Thread.sleep(350)
}
if (batch.isNotEmpty()) { synchronized(samples) { samples.add(batch) }; onSample(idx); idx++ }
val elapsed = System.currentTimeMillis() - t0
val sleep = intervalMs - elapsed
if (sleep > 0 && running.get()) Thread.sleep(sleep)
}
}
}
fun stop(): List<List<SampleResponse>> {
running.set(false)
try { threadRef?.join(3000) } catch (_: Exception) {}
return synchronized(samples) { samples.toList() }
}
fun isRunning(): Boolean = running.get()
}
```
### 3. MainActivity.kt — startDynamicRecording() (фрагмент)
```kotlin
// v1.10.0-dev — текущая версия
private fun startDynamicRecording() {
thread(name = "DynamicTest", isDaemon = true) {
checker.ensureConnected()
val elmProto = checker.getElm()!!
// Статика — 9 PID по одному (работает)
for ((pid, desc) in staticCmds) {
elmProto.sendCommand("01$pid")
}
// Подготовка к динамике
try { elmProto.sendCommand("ATWS") } catch (_: Exception) {}
Thread.sleep(800)
// Динамика: 3 PID, интервал 500ms
val dynSteps = listOf("010C" to "RPM", "0110" to "MAF", "0106" to "STFT")
.map { ElmStep(it.second, it.first, it.second) }
DynamicCollector(elmProto, dynSteps, 500L, ...).start()
}
}
```
---
## Вопросы
1. **Почему v1.9.0 стал ХУЖЕ чем v1.3.0?** В v1.3.0 было ATWS+300ms и ELM отвечал хотя бы на первые 2 команды. В v1.9.0 — ни одного ответа.
1. **exec()** делает retry с ПОВТОРНОЙ ОТПРАВКОЙ команды — не забивает ли это ELM327?
2. **drainInput()** в write() — почему без него (v1.9.0) стало ХУЖЕ?
3. **ATWS** — нужен ли? Сколько ждать?
4. **sleep(350)** между PID — правильно или избыточно?
5. Почему статика работает а динамика нет?
2. **Нужен ли ATWS перед динамикой?** Если да — сколько ждать после него?
3. **Нужен ли drainInput() в write()?** Или достаточно одного drain'а перед стартом? Не съедает ли drainInput() ответы от медленного ELM?
4. **Почему статика работает а динамика нет?** Разница: статика = одиночные sendCommand с паузами между шагами. Динамика = 3 sendCommand подряд в цикле.
5. **Правильно ли `exec()` делает retry с повторной отправкой команды?** Не забивает ли это ELM327?
Выдай СВОЙ анализ и КОНКРЕТНЫЕ исправления в файл `doc/claude-analysis-elm-v2.md`.
Не пиши общие рекомендации — дай точные правки кода.
## ⛔ НАПОМИНАНИЕ: НЕ МЕНЯТЬ КОД, НЕ КОММИТИТЬ. ТОЛЬКО АНАЛИЗ В ФАЙЛ doc/claude-analysis-elm-v2.md ⛔