Files
elmer/doc/elm-reference.md
T

711 lines
28 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 Communication Patterns — анализ 5+14 проектов
> **Цель:** понять как РЕАЛЬНО работают проекты с ELM327, выбрать лучшие паттерны для Elmer.
> **Дата:** 2026-05-27
> **Источники:** 5 LLM-проектов + 14 традиционных OBD2 Android-проектов
---
## Часть 1: Проекты с LLM (ELM → LLM → диагноз)
### Сводная таблица
| | OBD-Droid | OBD2AI | Automotive-AI | obd2-mcp-server | Vehicle-Diag-Assist |
|---|---|---|---|---|---|
| **Язык** | Java | Kotlin | Python | Python | C (W600) + Python |
| **Платформа** | Android | Android | Desktop | Desktop/Claude MCP | Embedded (MCU) |
| **LLM** | ChatGPT | gpt-5-mini | GPT-3.5/4 | Claude (MCP) | DeepSeek/Claude |
| **Чтение** | Побайтово, 1мс | kotlin-obd lib | readline() | Побайтово (BLE/SPP) | UART, семафор |
| **UUID** | 00001101... | 00001101... | N/A (pyserial) | BLE + serial | N/A (UART) |
| **Baud** | — | — | config.py | 38400 (auto-retry) | 38400 8N1 |
| **Timeout** | Адаптивный 5с | 400мс fix | 1с | 20с connect / 2с config | 2000мс |
| **Инит** | ATD→ATE0→ATL0→ATS0→ATH1→... | ATZ→ATE0→ATL0→ATSP0 | N/A | ATZ→ATE0→ATL0→ATS0→ATH1→ATCAF1→ATAT1→ATST64→ATSP0 | ATZ→... |
| **Ретраи** | requeue + SETPROT | 3 strikes → stop | Нет | [2,5,10]с backoff | Нет |
| **Simulator** | Встроенный demo | Нет | ELM327-emulator | Mock mode (Ford) | Gradio + HW sim |
| **DTC база** | Встроенная | Нет | Нет | 1937 Ford + generic | Нет |
---
## 1. OBD-Droid (Wal33D) — Java Android ⭐ ЛУЧШИЙ
### 1.1. StreamHandler.java — побайтовый I/O
```java
// ЧТЕНИЕ: побайтово, сон 1мс между проверками
public void run() {
while (true) {
if (in.available() > 0) {
if ((chr = in.read()) > 0) {
processRxChar(chr);
} else break;
} else {
Thread.sleep(1); // ← 1 миллисекунда!
}
}
}
// ОБРАБОТКА СИМВОЛОВ: '>' = такой же разделитель как CR/LF!
private void processRxChar(int chr) {
switch (chr) {
case 32: break; // пробел — игнорируем
case '>': // промпт ELM
message += (char) chr;
// fall through — НЕ отдельный случай!
case 10: // LF
case 13: // CR
messageHandler.handleTelegram(message.toCharArray());
message = "";
break;
default:
message += (char) chr;
}
}
// ОТПРАВКА: BufferedWriter с буфером 1 байт = flush на каждом байте
out = new BufferedWriter(new OutputStreamWriter(outStream), 1);
public int writeTelegram(final char[] buffer, int type, Object id) {
new Thread(() -> {
String msg = new String(buffer) + "\r"; // ELM ждёт CR
out.write(msg.toCharArray());
out.flush(); // немедленный flush из-за буфера 1 байт
}).start();
return buffer.length;
}
```
**Ключевые выводы:**
- `>` — НЕ спецсигнал «можно слать дальше». Это просто разделитель строк, как CR/LF.
- Буфер 1 байт на запись = каждый байт сразу уходит в порт.
- Отправка в отдельном потоке (не блокирует чтение).
### 1.2. ElmProt.java — стейт-машина протокола
**RSP_ID — все возможные ответы ELM327:**
```java
enum RSP_ID {
PROMPT(">"), OK("OK"), MODEL("ELM"),
NODATA("NODATA"), SEARCH("SEARCHING"),
ERROR("ERROR"), NOCONN("UNABLE"), NOCONN2("NABLETO"),
CANERROR("CANERROR"), BUSBUSY("BUSBUSY"),
BUSERROR("BUSERROR"), BUSINIERR("BUSINIT:ERR"),
BUSINIERR2("BUSINIT:BUS"), BUSINIERR3("BUSINIT:...ERR"),
FBERROR("FBERROR"), DATAERROR("DATAERROR"),
BUFFERFULL("BUFFERFULL"), STOPPED("STOPPED"),
RXERROR("<"), QMARK("?"),
UNKNOWN("");
}
```
**STAT — состояния соединения:**
```java
UNDEFINED INITIALIZING INITIALIZED ECU_DETECT ECU_DETECTED
ECU_SELECTED CONNECTING CONNECTED
// Ошибки:
NODATA, STOPPED, DISCONNECTED, BUSERROR, DATAERROR, RXERROR, ERROR
```
**Инициализация (после ATZ → MODEL):**
```
ATD // defaults
ATE0 // echo off
ATL0 // line feeds off
ATS0 // spaces off
ATH1 // headers ON (для обнаружения ЭБУ)
ATDP // узнать протокол
ATSPA1 // протокол AUTO
ATAT1 // adaptive timing ON
ATST<value> // установить таймаут
```
**Обработка ошибок — детально:**
```
SEARCHING → статус CONNECTING (не ошибка!)
NODATA → увеличить OBD timeout + переустановить протокол
ERROR → WARMSTART (ATWS)
DATAERROR → WARMSTART
RXERROR → WARMSTART
BUFFERFULL→ WARMSTART
BUS ERROR → DISCONNECTED → переустановить протокол + ретрай последней команды
UNABLE → DISCONNECTED → переустановить протокол + ретрай
```
**Мульти-фрейм ISO-TP:**
```
Формат: "0:4100..." — первая строка с длиной
"1:4100..." — продолжение
charsExpected = байт_длины * 2 (каждый байт = 2 hex символа)
```
### 1.3. BluetoothCommService.java — BT SPP
```java
final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
// Первая попытка: стандартный RFCOMM
tmp = device.createRfcommSocketToServiceRecord(SPP_UUID); // secure
// или
tmp = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID); // insecure
// FALLBACK: reflection-based RFCOMM channel 1 (для глючных адаптеров)
Method m = clazz.getMethod("createRfcommSocket", paramTypes);
Object[] params = new Object[]{1}; // channel 1
sockFallback = (BluetoothSocket) m.invoke(device, params);
```
**Ключевой вывод:** Есть fallback на reflection-based RFCOMM channel 1 — для дешёвых китайских клонов!
---
## 2. OBD2AI (catsmoker) — Kotlin Android
### 2.1. BluetoothHelper
```kotlin
val sppUuid: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
suspend fun connectToDevice(deviceAddress: String): Pair<InputStream, OutputStream> {
val device = bluetoothAdapter?.getRemoteDevice(deviceAddress)
bluetoothSocket = device.createRfcommSocketToServiceRecord(sppUuid).apply {
bluetoothAdapter.cancelDiscovery()
connect()
}
return Pair(socket.inputStream, socket.outputStream)
}
```
### 2.2. ObdHelper — инициализация и команды
```kotlin
// Инициализация: фиксированные задержки, БЕЗ ожидания '>'
suspend fun initializeObd() = withContext(Dispatchers.IO) {
suspend fun sendRawCommand(command: String) {
out.write((command + "\r").toByteArray())
out.flush()
delay(400) // ← 400мс после КАЖДОЙ команды
}
sendRawCommand("ATZ") // сброс
sendRawCommand("ATE0") // эхо выкл
sendRawCommand("ATL0") // line feeds выкл
sendRawCommand("ATSP0") // авто-протокол
delay(1000) // дополнительная пауза после инита
// Очистка буфера
if (`in`.available() > 0) {
val buffer = ByteArray(`in`.available())
`in`.read(buffer)
}
}
```
**Используется библиотека `kotlin-obd` (eltonvs):**
```kotlin
// Для стандартных команд — библиотека
obdConnection = ObdDeviceConnection(inputStream, outputStream)
val result = connection.run(TroubleCodesCommand())
// Для нестандартных — ручной парсинг
class MyRPMCommand : ObdCommand() {
override val pid = "0C"
override val handler = { it: ObdRawResponse ->
val rawValue = it.processedValue
val identifier = "410C"
val aHex = rawValue.substring(index + 4, index + 6)
val bHex = rawValue.substring(index + 6, index + 8)
((a * 256) + b) / 4 // формула RPM
}
}
```
### 2.3. Live Data Monitoring
```kotlin
suspend fun startLiveDataMonitoring() = withContext(Dispatchers.IO) {
var errorCount = 0
while (isMonitoring.get()) {
try {
val speed = runCommand(MySpeedCommand())
val rpm = runCommand(MyRPMCommand())
val temp = runCommand(MyCoolantTempCommand())
errorCount = 0
delay(800) // 800мс между циклами
} catch (e: Exception) {
errorCount++
if (errorCount >= 3) break // 3 ошибки подряд = стоп
delay(1000)
}
}
}
```
---
## 3. Automotive-AI (Eloquent-Algorithmics) — Python Desktop
### 3.1. ELM327 через pyserial
```python
# config.py
SERIAL_PORT = "/dev/ttyUSB0" # или COM3 на Windows
BAUD_RATE = 38400
# Подключение
ser = serial.Serial(port=SERIAL_PORT, baudrate=BAUD_RATE, timeout=1)
# Отправка команды
def send_command(ser, command):
ser.write((command + "\r\n").encode()) # CRLF терминатор
response = ser.readline().decode().strip()
response = response.replace("\r", "").replace(">", "")
return response
```
**Ключевые отличия от OBD-Droid:**
- `readline()` вместо побайтового чтения — ПРОЩЕ, но менее надёжно
- `\r\n` вместо просто `\r`
- `timeout=1` — ждёт 1 секунду на readline
- Убирает `>` из ответа (не использует как разделитель)
### 3.2. Парсинг ответов
```python
# RPM: 010C → 41 0C HH LL
if cmd == "010C":
value = (int(response.split()[2], 16) * 256 +
int(response.split()[3], 16)) / 4
# Coolant: 0105 → 41 05 XX
if cmd == "0105":
value = int(response.split()[2], 16) - 40 # -40 offset
# VIN: 0902
vin_response = parse_vin_response(response)
vehicle_data = decode_vin(vin_response)
```
---
## 4. obd2-mcp-server (petrpatek) — Python Claude MCP ⭐ САМЫЙ СВЕЖИЙ
### 4.1. BLE + Serial подключение
```
Поддерживает:
- BLE (vLinker FD, STN чип) — асинхронный, asyncio.Lock
- Serial (classic Bluetooth SPP) — синхронный, pyserial
Baud rate auto-retry: [500k, 115.2k, 38.4k, 9.6k]
BLE: 30-секундный keepalive heartbeat (без него адаптер засыпает через ~120с)
```
### 4.2. Инициализация (САМАЯ ПОЛНАЯ)
```python
ATZ # сброс
ATE0 # эхо выкл
ATL0 # line feeds выкл
ATS0 # пробелы выкл
ATH1 # заголовки CAN ВКЛ (для обнаружения ЭБУ)
ATCAF1 # CAN auto-formatting ON
ATAT1 # adaptive timing ON
ATST64 # timeout = 64*4ms = 256ms
ATSP0 # авто-протокол
# Для STN адаптеров (OBDlink):
ATPP 0E SV 00 # отключить сон
ATPP 0E ON # включить
```
### 4.3. Ретраи и таймауты
```python
MAX_RETRIES = 3
RETRY_BACKOFF = [2, 5, 10] # секунды
CONNECT_TIMEOUT = 20 # секунд
PROTOCOL_TIMEOUT = 12 # секунд
CONFIG_TIMEOUT = 2 # секунды
```
### 4.4. Очистка ответа
```python
def _clean_elm_response(raw: str) -> str:
# Убирает: промпт ">", эхо команд, "SEARCHING...", пустые строки
...
```
### 4.5. DTC база данных
```
- 1937 Ford-специфичных кодов
- Generic OBD-II коды (P, B, C, U)
- Ленивая загрузка по бренду
- Скрапинг с troublecodes.net
```
---
## 5. Vehicle-Diagnostic-Assistant (castlebbs) — Embedded C + Python
### 5.1. Аппаратная архитектура
```
W600 MCU ←UART1 38400 8N1→ ELM327 чип → CAN → Авто
↕ HTTP/MCP
LangChain Agent (Python) → DeepSeek / Claude
```
### 5.2. ELM327 Driver (C)
```c
// elm327.c
int elm327_send_command(const char* cmd, char* resp, int len, int timeout) {
// Пишет команду + \r в UART1
// Ждёт ответ через FreeRTOS semaphore (прерывание по приёму)
// Таймаут по умолчанию: 2000мс
// Макс. длина ответа: 512 байт
}
// Hybrid simulation mode:
// AT команды → реальный ELM327
// OBD команды → симуляция (если включена)
```
### 5.3. Поддерживаемые режимы OBD
```
Mode 01: live data (30+ PID)
Mode 03: stored DTC (формат 43 XX XX XX XX)
Mode 04: clear DTC (44)
Mode 07: pending DTC (47)
Mode 09: vehicle info (VIN, calibration ID)
```
### 5.4. PID формулы (Mode 01)
| PID | Формула | Пример |
|-----|---------|--------|
| 0C (RPM) | `(A*256+B)/4` | 0x1AF8 → 1726 |
| 0D (Speed) | `A` (km/h) | 0x00 → 0 |
| 05 (ECT) | `A-40` (°C) | 0x5A → 50 |
| 04 (Load) | `(A*100)/255` (%) | 0x40 → 25.1 |
| 10 (MAF) | `((A*256)+B)/100` (g/s) | — |
| 2F (Fuel) | `(A*100)/255` (%) | — |
### 5.5. Safe formula evaluation
```python
def calculate_obd_value(raw_response, formula):
# LLM вызывает этот tool для расчёта значений
# safe_eval() — ограниченный eval (только +-*/ и переменные A,B,C,D)
hex_bytes = raw_response.replace("41 XX ", "").split()
A, B, C, D = [int(x, 16) for x in hex_bytes]
return safe_eval(formula, {"A": A, "B": B, "C": C, "D": D})
```
---
## СРАВНИТЕЛЬНЫЙ АНАЛИЗ: Что взять для Elmer
### Инициализация ELM327
| Проект | Последовательность | Задержки |
|--------|-------------------|----------|
| OBD-Droid | ATD→ATE0→ATL0→ATS0→ATH1→ATDP→ATSPA1→ATAT1→ATST | Стейт-машина, нет фикс. задержек |
| OBD2AI | ATZ→ATE0→ATL0→ATSP0 | 400мс после каждой |
| obd2-mcp | ATZ→ATE0→ATL0→ATS0→ATH1→ATCAF1→ATAT1→ATST64→ATSP0 | async, по ответам |
| Automotive-AI | Нет явной инициализации | — |
**Рекомендация для Elmer:** взять последовательность obd2-mcp-server (самая полная) + задержки OBD2AI (400мс) + ATH0 вместо ATH1 (для чистых ответов без CAN-заголовков).
### Чтение ответов
| Проект | Метод | Плюсы | Минусы |
|--------|-------|-------|--------|
| OBD-Droid | Побайтово, 1мс sleep | Макс. контроль | Сложный код |
| OBD2AI | kotlin-obd lib | Готовое решение | Зависимость от библиотеки |
| Automotive-AI | `ser.readline()` | Простой код | Менее надёжно |
**Рекомендация для Elmer:** для Android — побайтовое чтение как у OBD-Droid (уже есть в TestService). Для Python-мока/сервера — `readline()` достаточно для тестов.
### Обработка ошибок
| Ошибка | OBD-Droid | OBD2AI | obd2-mcp |
|--------|-----------|--------|----------|
| SEARCHING | Статус CONNECTING | — | Пропустить, ждать |
| NO DATA | Увеличить timeout | — | Вернуть пусто |
| BUS ERROR | DISCONNECTED + retry | — | — |
| UNABLE | DISCONNECTED + retry | — | — |
| ERROR | WARMSTART (ATWS) | — | — |
| RX ERROR | WARMSTART | 3 strikes → stop | — |
**Рекомендация для Elmer:** SEARCHING = ждать + увеличить таймаут. NO DATA = пропустить PID. BUS ERROR/UNABLE = одна попытка reconnect + retry. ERROR = WARMSTART.
### Тайминги
| Проект | Между командами | Инит | Таймаут ответа |
|--------|-----------------|------|----------------|
| OBD-Droid | Нет (стейт-машина) | Стейт-машина | 5000мс адаптивный |
| OBD2AI | 400мс fix | 1000мс после всех | ? (внутри lib) |
| Automotive-AI | Нет | Нет | 1000мс (readline) |
| obd2-mcp | По ответам | По ответам | 2000-20000мс |
| castlebbs | По семафору | — | 2000мс |
**Рекомендация для Elmer:** 400мс между командами (как OBD2AI) + адаптивный таймаут от 2000мс с возможностью увеличения (как OBD-Droid).
### BT подключение (Android)
| Проект | Метод | Fallback |
|--------|-------|----------|
| OBD-Droid | `createRfcommSocketToServiceRecord` secure + insecure | Reflection RFCOMM channel 1 |
| OBD2AI | `createRfcommSocketToServiceRecord` | Нет |
**Рекомендация для Elmer:** взять fallback на reflection channel 1 из OBD-Droid — критично для дешёвых клонов.
---
## ИТОГ: Что реализовать в elmer-android
### Приоритет 1 (обязательно)
- [ ] Побайтовое чтение с паузой 1мс (StreamHandler.java)
- [ ] `>` = разделитель строк, НЕ спецсигнал
- [ ] Fallback RFCOMM channel 1 (BluetoothCommService.java)
- [ ] Фиксированные задержки 400мс между командами (OBD2AI)
- [ ] Очистка буфера после инициализации
### Приоритет 2 (важно)
- [ ] Обработка SEARCHING, NO DATA, BUS ERROR
- [ ] 3-strike retry для live monitoring
- [ ] Адаптивный таймаут (базовый 5000мс)
### Приоритет 3 (для production)
- [ ] WARMSTART при ERROR/DATAERROR
- [ ] Мульти-фрейм ISO-TP
- [ ] DTC база (можно с obd2-mcp-server)
- [ ] Экспоненциальный backoff для ретраев
---
## Часть 2: Традиционные OBD2 Android-проекты (БЕЗ LLM)
> Только ELM327 ↔ Android Bluetooth SPP. Именно они интересны для слоя коммуникации — оттестированы годами на тысячах машин.
### Топ-10 батл-тестед проектов
| # | Проект | URL | Язык | Создатель | Обновлён | ⭐ | DTC | Live | VIN | Примечания |
|---|--------|-----|------|-----------|----------|-------|-----|------|-----|-----------|
| 1 | **AndrOBD** | [fr3ts0n/AndrOBD](https://github.com/fr3ts0n/AndrOBD) | Java | Sepp Seidel | Апр 2025 | 1993 | ✅ | ✅ | ✅ | **Король.** 10+ лет, MQTT, графики, плагины, многоязычный |
| 2 | **AndroidOBD** | [barnhill/AndroidOBD](https://github.com/barnhill/AndroidOBD) | Kotlin | Brian Barnhill | Ноя 2025 | 79 | ✅ | ✅ | ✅ | **Библиотека.** Хороший API для интеграции |
| 3 | **Java OBD** | [Tomiwa-Ot/obd](https://github.com/Tomiwa-Ot/obd) | Java | Tomiwa O. | Март 2025 | 30 | ✅ | ✅ | ✓ | **Библиотека.** SPP + USB. Опубликована на JitPack. Async |
| 4 | **ObdGraphs** | [tzebrowski/ObdGraphs](https://github.com/tzebrowski/ObdGraphs) | Kotlin | Tomek Żebrowski | Апр 2025 | 53 | ✅ | ✅ | ✅ | Графики в реальном времени. Проф. дизайн |
| 5 | **CarScanApp** | [midnightyoff/CarScanApp](https://github.com/midnightyoff/CarScanApp) | Kotlin | midnightyoff | Окт 2025 | 2 | ✅ | ✅ | ✅ | **Свежий.** Compose, DTC read/clear, maintenance log |
| 6 | **Elm327** | [takyonxxx/Elm327](https://github.com/takyonxxx/Elm327) | Java | Türkay Biliyor | Окт 2023 | 48 | ✅ | ✅ | ✅ | В Play Store. WiFi + BT. 22 форка |
| 7 | **CarBusInterface** | [theksmith/CarBusInterface](https://github.com/theksmith/CarBusInterface) | Java | Kristoffer Smith | Янв 2016 | 291 | ✅ | ✅ | ✅ | Старый, боевой. 77 форков. Raw command interface |
| 8 | **BT OBD-II Diag Tool** | [fussek/Bluetooth-OBD-II-Diagnostic-Tool](https://github.com/fussek/Bluetooth-OBD-II-Diagnostic-Tool) | Java | Sebastian Fussek | Сент 2022 | 21 | ✅ | ✅ | ✅ | Bachelor thesis. Хорошая документация + расчёты PID |
| 9 | **obd-scanner-android** | [ETSoftwareStudio/obd-scanner-android](https://github.com/ETSoftwareStudio/obd-scanner-android) | Kotlin | ETSoftware | Апр 2025 | 2 | ✅ | ✅ | ✓ | Jetpack Compose + Hilt. Образец архитектуры |
| 10 | **KWP Logger** | [bri3d/kwp-android-logger](https://github.com/bri3d/kwp-android-logger) | Java | Brian Ledbetter | Фев 2016 | 28 | ✅ | ✅ | ✓ | **KWP2000 для VW/Audi.** Специалист по протоколам |
### Что между ними общего (подтверждено всеми 14 проектами)
**Bluetooth SPP:**
```java
UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(SPP_UUID);
socket.connect();
```
**Инициализация ELM327 (единый стандарт):**
```
ATZ → сброс
ATE0 → эхо выкл
ATL0 → line feeds выкл
ATS0 → пробелы выкл (или ATS1 — с пробелами)
ATH0/1 → заголовки CAN
ATSP0 → авто-протокол
```
**Отправка команд (единый стандарт):**
```java
outputStream.write((cmd + "\r").getBytes());
outputStream.flush();
```
**Чтение ответов — два лагеря:**
- **Лагерь 1 (AndrOBD, CarBusInterface):** побайтово, по 1мс
- **Лагерь 2 (AndroidOBD, Elm327):** `BufferedReader.readLine()`
### Кого изучать в первую очередь для Elmer
| Для чего | Проект | Почему |
|----------|--------|--------|
| **BT ↔ ELM слой** | AndrOBD + CarBusInterface | 10 лет отладки, все краевые случаи |
| **Kotlin-интеграция** | AndroidOBD + CarScanApp | Чистый API, современный код |
| **PID/DTC парсинг** | AndrOBD | Все формулы, все режимы |
| **KWP2000 (VW)** | KWP Logger | Если Phaeton на старом протоколе |
---
## Часть 3: СЫРОЙ КОД AndrOBD — золотой стандарт (1993 ⭐, 10 лет в продакшене)
### 3.1. Bluetooth SPP подключение (BtCommService.java)
```java
final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
// Стандартный socket
if (secure) {
tmp = device.createRfcommSocketToServiceRecord(SPP_UUID);
} else {
tmp = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
}
// FALLBACK: reflection RFCOMM channel 1 (для китайских клонов!)
catch (IOException e) {
Class<?> clazz = mmSocket.getRemoteDevice().getClass();
Method m = clazz.getMethod("createRfcommSocket", Integer.TYPE);
sockFallback = (BluetoothSocket) m.invoke(mmSocket.getRemoteDevice(), 1);
mmSocket = sockFallback;
mmSocket.connect(); // пробуем снова
}
```
### 3.2. 🚨 КРИТИЧЕСКИ: 500мс пауза после BT connect
```java
// AndrOBD issue #233 — без этой паузы Android теряет данные!
Thread.sleep(500); // CRITICAL: Fix for Android Bluetooth timing
// Только после этого запускаем worker thread
mBtWorkerThread = new BtWorkerThread(socket, socketType);
mBtWorkerThread.start();
```
### 3.3. Инициализация ELM327 (точный порядок)
```java
private void initialize() {
setStatus(STAT.INITIALIZING);
// 1. Кастомные init-команды (если есть)
cmdQueue.addAll(customInitCommands);
// 2. Установить протокол (AUTO или конкретный)
pushCommand(CMD.SETPROT, preferredProtocol.ordinal()); // ATSP<n>
// 3. Инициализировать адаптивный тайминг
mAdaptiveTiming.initialize(); // ATAT1
// 4. Ускорить протокол (убрать пробелы и переводы строк)
pushCommand(CMD.SETSPACES, 0); // ATS0
pushCommand(CMD.SETLINEFEED, 0); // ATL0
// 5. Выключить эхо
pushCommand(CMD.ECHO, 0); // ATE0
}
// → ждёт "ELM" ответ → затем 0100 (запрос поддерживаемых PID)
```
### 3.4. Адаптивный таймаут (ГЕНИАЛЬНО)
```java
// Константы
ELM_TIMEOUT_MAX = 1000 // мс — максимум
ELM_TIMEOUT_DEFAULT = 200 // мс — стартовое значение
ELM_TIMEOUT_RES = 4 // мс — шаг изменения
ELM_TIMEOUT_MIN = 12 // мс — минимум
// На каждый NODATA → увеличить таймаут на 4мс
// На каждый успех → уменьшить таймаут на 4мс (но не ниже learned_min)
void adapt(boolean increaseTimeout) {
if (increaseTimeout) {
if (elmMsgTimeout + 4 < 1000)
setElmMsgTimeout(elmMsgTimeout + 4);
} else {
if (elmMsgTimeout - 4 >= learnedMin)
setElmMsgTimeout(elmMsgTimeout - 4);
}
}
// Каждое изменение отправляет: ATST<timeout/4>
```
**Диапазон: 12мс → 1000мс, шаг 4мс. Старт: 200мс.**
### 3.5. Обработка ответов (handleTelegram)
```java
// 1. Фильтр эха
if (lastTxMsg.equalsIgnoreCase(bufferStr))
return 0; // проигнорировать
// 2. Определить тип ответа
switch (getResponseId(bufferStr)) {
case PROMPT: // ">" — конец ответа
case MODEL: // "ELM" → вызвать initialize()
case SEARCHING: // идёт поиск
case NODATA: // нет данных
case ERROR: // ошибка
case NOCONN: // "UNABLE"
case BUSERROR: // ошибка шины
// ... ещё 10+
}
```
### 3.6. Обработка ошибок (production-grade)
```
BUS ERROR / UNABLE / CAN ERROR:
→ DISCONNECTED
→ переставить в очередь последнюю команду
→ сбросить протокол (ATSP)
→ переинициализировать тайминг
→ закрыть протокол (ATPC)
DATA ERROR / RX ERROR / BUFFER FULL:
→ WARM START (ATWS) — мягкий перезапуск
NO DATA:
→ увеличить адаптивный таймаут
→ переустановить протокол
STOPPED:
→ переставить последнюю команду в очередь
```
### 3.7. Мульти-фрейм (ISO-TP для VIN и длинных ответов)
```java
// Формат: "014" = 1 строка, 14 hex символов
// "0:4902015756..." — первая строка с длиной
// "1:5A5A314B5A41..." — продолжение
if (buffer[0] == '0' && buffer.length == 3) {
charsExpected = Integer.valueOf(bufferStr, 16) * 2; // 0x14 = 20 байт
lastRxMsg = "";
responsePending = true;
}
if (bufferStr.indexOf(':') >= 0) {
lastRxMsg += bufferStr.substring(idx + 1);
}
if (lastRxMsg.length() >= charsExpected) {
result = handleDataMessage(lastRxMsg); // готово
}
```
### 3.8. Итоговый процесс подключения (хронология)
```
1. BT socket connect()
2. Thread.sleep(500) ← КРИТИЧЕСКИ! Без этого — потеря данных.
3. BtWorkerThread.start()
4. StreamHandler.run() — побайтовое чтение
5. ATSP0 → ATAT1 → ATS0 → ATL0 → ATE0
6. Ждём "ELM" → статус INITIALIZED
7. 0100 → опрос поддерживаемых PID
8. ECU detect → выбор ECU → готов к работе
```