docs: СЫРОЙ КОД AndrOBD — 10 лет продакшена, золотой стандарт ELM/BT
This commit is contained in:
@@ -543,3 +543,168 @@ outputStream.flush();
|
||||
| **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 → готов к работе
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user