# 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 // установить таймаут ``` **Обработка ошибок — детально:** ``` 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 { 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 // 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 ``` **Диапазон: 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 → готов к работе ```