# 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/ 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`: ```kotlin data class SpeedTestResult( val perPidAvg: List, 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/` | | `android/app/build.gradle.kts` | versionName = "0.94.0-dev" | | `web/templates/index.html` | v0.94.0-dev | | `doc/history/2026-06-10.md` | Этот файл |