9.2 KiB
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:
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
);
Миграция для старых БД:
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/ |
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:
data class SpeedTestResult(
val perPidAvg: List<Int>,
val batchTime: Int,
val reliable: Boolean,
val message: String
)
Критерии отбраковки (reliable=false):
- Любой замер отклоняется от среднего по своему PID >50%
- Любая команда вернула
(err)или пустой ответ - Все замеры <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 |
Этот файл |