docs: СЫРОЙ КОД AndrOBD — 10 лет продакшена, золотой стандарт ELM/BT
This commit is contained in:
@@ -543,3 +543,168 @@ outputStream.flush();
|
|||||||
| **Kotlin-интеграция** | AndroidOBD + CarScanApp | Чистый API, современный код |
|
| **Kotlin-интеграция** | AndroidOBD + CarScanApp | Чистый API, современный код |
|
||||||
| **PID/DTC парсинг** | AndrOBD | Все формулы, все режимы |
|
| **PID/DTC парсинг** | AndrOBD | Все формулы, все режимы |
|
||||||
| **KWP2000 (VW)** | KWP Logger | Если Phaeton на старом протоколе |
|
| **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