Files
elmer/doc/history/2026-06-10.md
T

208 lines
9.2 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.
# 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` | Этот файл |