208 lines
9.2 KiB
Markdown
208 lines
9.2 KiB
Markdown
# 2026-06-10 — Speed-test ELM327, адаптивный интервал, валидация, деплой v0.94.0-dev
|
||
|
||
## Проблема
|
||
|
||
Динамический тест (START/STOP) использует жёстко заданный интервал 250мс для опроса 3 PID (RPM, MAF, STFT). Реальные тесты на машине показали:
|
||
|
||
- **Session #47** (3 PID × 250ms): 94% ошибок — ELM327 v1.5 не успевает
|
||
- **Session #48** (3 PID × 250ms): первые 15 сэмплов ок, потом все пустые — буфер ELM переполняется
|
||
- **Session #45** (5 PID × 500ms, старый): после ~20 сэмплов тоже падает
|
||
|
||
**Корень:** 3 команды занимают ~240-300ms на ELM327 v1.5. При интервале 250ms пауза между батчами ≈ 0ms. Буфер UART переполняется, ELM перестаёт отвечать.
|
||
|
||
Также: в `api/db.py` не было защиты `threading.Lock` — 20 конкурентных записей в БД давали 8 ошибок.
|
||
|
||
---
|
||
|
||
## Решения
|
||
|
||
### 1. Threading lock в Database
|
||
|
||
`api/db.py` — добавлен `threading.Lock()`, обёрнуты все write-методы (`save_session`, `save_dtc_scan`, `save_device_profile`).
|
||
|
||
Было: `self.conn.execute()` + `self.conn.commit()` без блокировки → 8/20 ошибок при конкурентном доступе.
|
||
Стало: `with self._lock:` → 0 ошибок.
|
||
|
||
Коммит: `fix: threading lock in Database for concurrent writes`
|
||
|
||
### 2. Speed-test ELM327 — адаптивный интервал
|
||
|
||
#### Концепция
|
||
|
||
При первом подключении нового ELM327 (уникальный BT MAC) — замерить скорость ответа на разных PID, сохранить в профиль. При последующих запусках использовать сохранённое значение для расчёта интервала.
|
||
|
||
#### Сервер — `api/db.py`
|
||
|
||
Добавлена колонка `response_time_ms INTEGER DEFAULT 250` в таблицу `device_profiles`:
|
||
|
||
```sql
|
||
CREATE TABLE device_profiles (
|
||
mac TEXT PRIMARY KEY,
|
||
level INTEGER NOT NULL,
|
||
elm_version TEXT,
|
||
elm_desc TEXT,
|
||
protocol TEXT,
|
||
voltage TEXT,
|
||
response_time_ms INTEGER DEFAULT 250, -- <-- NEW
|
||
supported TEXT,
|
||
unsupported TEXT,
|
||
errors TEXT,
|
||
first_seen TEXT,
|
||
last_seen TEXT
|
||
);
|
||
```
|
||
|
||
Миграция для старых БД:
|
||
```sql
|
||
ALTER TABLE device_profiles ADD COLUMN response_time_ms INTEGER DEFAULT 250;
|
||
```
|
||
|
||
`save_device_profile()` обновлена: принимает и сохраняет `response_time_ms`.
|
||
|
||
#### Сервер — `api/routes.py`
|
||
|
||
Добавлен эндпоинт:
|
||
```
|
||
PUT /api/v1/elm/profile/<mac>
|
||
Body: {"response_time_ms": 180}
|
||
```
|
||
|
||
Позволяет Android-клиенту обновить скорость ELM в профиле.
|
||
|
||
#### Android — `ElmChecker.kt`
|
||
|
||
Добавлен метод `measureResponseTime(elm: ElmProtocol, log: (String) -> Unit): Int`:
|
||
|
||
```
|
||
Алгоритм:
|
||
1. Выбрать 3 PID: 010C (RPM), 0110 (MAF), 0106 (STFT)
|
||
2. Каждый PID послать 3 раза
|
||
3. Замерить round-trip время для каждого
|
||
4. Усреднить
|
||
5. Вернуть среднее в миллисекундах
|
||
6. Логировать в UI: "⏱ Тест скорости: 010C — 82ms, 89ms, 78ms"
|
||
```
|
||
|
||
Вызывается после connectAndInit(), перед стартом динамического теста.
|
||
|
||
#### Android — `MainActivity.kt` — `startDynamicRecording()`
|
||
|
||
В流程 добавлен speed-test между статическим пробросом PID и динамическим сбором:
|
||
|
||
```
|
||
1. Статика: пробуем 9 PID, log в UI
|
||
2. Speed-test: 3 PID × 3 раза, замер времени
|
||
→ "⏱ Тест скорости ELM..."
|
||
→ " RPM: 82ms 89ms 78ms (среднее 83ms)"
|
||
→ " MAF: 95ms 91ms 88ms (среднее 91ms)"
|
||
→ " STFT: 79ms 82ms 85ms (среднее 82ms)"
|
||
→ " Среднее по всем: 85ms"
|
||
3. Расчёт интервала: max(250, avg_response_time × 3 × 1.5)
|
||
→ "📡 Интервал опроса: 383ms (запас 50%)"
|
||
4. Сохранение response_time_ms на сервер
|
||
5. Запуск DynamicCollector с вычисленным интервалом
|
||
```
|
||
|
||
Формула интервала:
|
||
```
|
||
interval = max(250, avg_response_time × num_pids × 1.5)
|
||
```
|
||
|
||
Где:
|
||
- `avg_response_time` — среднее время ответа ELM на одну команду (ms)
|
||
- `num_pids` — количество PID в динамическом тесте (3)
|
||
- `1.5` — запас 50% на вариативность
|
||
- `250` — минимальный интервал (для быстрых ELM327 v2.x)
|
||
|
||
Если профиль уже существует (повторный запуск) — speed-test пропускается, интервал берётся из профиля. По кнопке «принудительно» можно перезамерить.
|
||
|
||
---
|
||
|
||
## Итог тестов
|
||
|
||
После фикса threading lock:
|
||
```
|
||
134 ✅ / 0 ❌ — все тесты проходят
|
||
```
|
||
|
||
---
|
||
|
||
## Файлы
|
||
|
||
| Файл | Что изменено |
|
||
|------|-------------|
|
||
| `api/db.py` | threading lock, response_time_ms колонка, миграция |
|
||
| `api/routes.py` | PUT /api/v1/elm/profile/<mac> |
|
||
| `android/.../ElmChecker.kt` | measureResponseTime() |
|
||
| `android/.../MainActivity.kt` | speed-test перед динамикой, адаптивный интервал |
|
||
| `android/.../ServerClient.kt` | saveProfile() — отправка response_time_ms |
|
||
| `android/.../DynamicCollector.kt` | intervalMs параметр (уже есть) |
|
||
| `android/app/build.gradle.kts` | versionName = "0.93.0-dev" |
|
||
| `web/templates/index.html` | v0.93.0-dev |
|
||
|
||
---
|
||
|
||
## 3. Валидация speed-test (v0.94.0-dev)
|
||
|
||
### Проблема
|
||
Если ELM327 плохо вставлен в OBD-разъём (контакт болтается), замеры скорости — мусор: часть команд падает с `(err)`, время прыгает от 10ms до 900ms. Сохранять такой профиль нельзя.
|
||
|
||
### Решение
|
||
`ElmChecker.kt` — `measureResponseTime()` возвращает `SpeedTestResult`:
|
||
|
||
```kotlin
|
||
data class SpeedTestResult(
|
||
val perPidAvg: List<Int>,
|
||
val batchTime: Int,
|
||
val reliable: Boolean,
|
||
val message: String
|
||
)
|
||
```
|
||
|
||
**Критерии отбраковки (reliable=false):**
|
||
1. Любой замер отклоняется от среднего по своему PID >50%
|
||
2. Любая команда вернула `(err)` или пустой ответ
|
||
3. Все замеры <20ms (ELM не отвечает, мусор)
|
||
|
||
При `reliable=false` — профиль **не сохраняется**, интервал 250ms по умолчанию.
|
||
|
||
### Quick-check при каждом connect
|
||
`ElmChecker.kt` — добавлен `quickCheck(): Int?`:
|
||
|
||
```
|
||
При каждом клике на светофор ELM:
|
||
1. Загрузить профиль с сервера
|
||
2. Послать 010C (RPM), замерить время
|
||
3. Сравнить с профилем:
|
||
- расхождение <50% → "✅ ELM стабилен: ~85ms (профиль 256ms)"
|
||
- расхождение >50% → "⚠️ Скорость ELM изменилась: было 256ms, сейчас ~510ms"
|
||
4. Если профиля нет → полный speed-test (3 PID × 3 раза)
|
||
```
|
||
|
||
### Принудительный перетест
|
||
Клик на 🔵 ELM → переинициализация → quick-check. Если нужно полностью перемерить — очистить `response_time_ms` в `device_profiles` на сервере.
|
||
|
||
---
|
||
|
||
## Итоговая логика
|
||
|
||
| Ситуация | При connect | При СТАРТ |
|
||
|----------|------------|-----------|
|
||
| Новый ELM (нет профиля) | Полный speed-test 3 PID × 3 → сохранить | Интервал из профиля |
|
||
| Знакомый ELM, контакт ок | Quick-check: "стабилен" | Интервал из профиля |
|
||
| Знакомый ELM, контакт плохой | Quick-check: "изменилась" | Интервал 250ms (дефолт) |
|
||
| После переподключения | Quick-check → сверка | Интервал из профиля если ок |
|
||
|
||
## Файлы (v0.94.0-dev)
|
||
|
||
| Файл | Что изменено |
|
||
|------|-------------|
|
||
| `android/.../ElmChecker.kt` | `SpeedTestResult`, `quickCheck()`, валидация |
|
||
| `android/.../MainActivity.kt` | Speed-test при инициализации, quick-check |
|
||
| `android/.../ServerClient.kt` | `getProfileResponseTime()`, `saveProfile()` |
|
||
| `api/db.py` | `response_time_ms` колонка, threading lock |
|
||
| `api/routes.py` | `PUT /api/v1/elm/profile/<mac>` |
|
||
| `android/app/build.gradle.kts` | versionName = "0.94.0-dev" |
|
||
| `web/templates/index.html` | v0.94.0-dev |
|
||
| `doc/history/2026-06-10.md` | Этот файл |
|