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

6.0 KiB
Raw Blame History

2026-06-10 — Speed-test ELM327, адаптивный интервал, деплой v0.93.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