Files
elmer/CHANGELOG.md
T
2026-06-10 20:53:01 +04:00

20 KiB
Raw Blame History

elmAI — Changelog / Полное описание проекта

Файл для нового агента: прочитай — и ты в курсе всего. Актуально: v0.95.0-dev, 10 июня 2026


1. Идентификация

Поле Значение
Название elmAI (ранее Elmer, elmAI rebrand в конце мая)
Суть OBD2-диагностика: Android → ELM327 → LLM (анализ ошибок)
Репозиторий сервера gitea.services.ngcloud.ru/Nail/elmer
Репозиторий Android github.com/Repinoid/elmer-android (отдельный!)
Сервер (prod) obdai.ru (5.172.178.213)
Язык сервера Python 3 + Flask + gunicorn
Язык клиента Kotlin, minSdk 24
LLM-провайдер api.aillm.ru (OpenAI-совместимый)
База данных SQLite (WAL mode)

2. Архитектура (master — продакшен, актуально)

📱 Android → ELM327 (Bluetooth SPP)
    │
    ▼ ScriptRunnerService (фоновая служба)
    │  выполняет скрипт: OBD-команды → ждёт ответы → пишет лог
    │  HTTPS POST /api/v1/session/upload
    ▼
🌐 obdai.ru (nginx :443 → gunicorn :8000)
    │
    ├── web/app.py          — точка входа Flask
    ├── api/routes.py       — 5 эндпоинтов
    ├── api/db.py           — SQLite (sessions, cars, dtc, params)
    ├── api/parser.py       — парсинг батча ELM-ответов (VIN, DTC, PID)
    ├── api/scripts.py      — сборка диагностических скриптов
    ├── api/config.py       — загрузка config.yaml (c lru_cache)
    ├── brain/client.py     — Diagnoser: HTTP к api.aillm.ru
    ├── brain/prompts.py    — SYSTEM_PROMPT (10 правил, табличный формат)
    └── obd/protocol.py     — AndrOBD-стейт-машина (1:1 копия ElmProt.java)

Модули подробно

api/ — REST + БД + парсинг

5 эндпоинтов:

Эндпоинт Метод Что делает Время
/api/v1/ping GET {"ok": true} — проверка сервера ~5ms
/api/v1/ping-llm GET Проверка LLM (кэш 60с, глобальная переменная) ~2s
/api/v1/script?mode=full GET Выдача скрипта диагностики (JSON со steps) ~50ms
/api/v1/session/upload POST Приём батча + LLM-анализ, идемпотентность ~30-120s
/api/v1/chat POST Свободный вопрос к LLM (с историей) ~5-15s

Ключевые особенности:

  • upload_session(): принимает responses (массив {cmd, raw, decoded}) + request_id (UUID для идемпотентности). Если request_id уже есть в БД — возвращает кэшированный ответ (200).
  • LLM fallback: если нет API key или LLM ошибка — возвращает format_no_llm() (сырые данные без анализа).
  • _build_diagnosis_prompt(): собирает промпт из VIN, DTC stored/pending, параметров, raw_log. Требует глубокого разбора.
  • /chat: передаёт историю как массив messages[{role, content}], срез последних 10.
  • config.load()@lru_cache(maxsize=1), сбрасывать рестартом процесса.

База данных (SQLite, WAL):

  • sessions — сводная таблица (клиент, ELM, авто, LLM, request_id, response_json для кэша)
  • cars — VIN → id (уникальные)
  • diagnostic_tokens, llm_messages, ecu_parameters, dtc_codes — детальные таблицы (не используются в upload, только legacy)
  • PRAGMA journal_mode=WAL, busy_timeout=30000, check_same_thread=False

Парсер (parser.py):

  • parse_batch(responses){vin, dtc_stored[], dtc_pending[], parameters[{name,value}], raw_log[]}
  • Парсит из decoded поля, fallback из сырого HEX (490201..., 43..., 47...)
  • DTC из HEX: декодирует P/B/C/U коды из байтов после 43/47
  • PID: всё что с : в decoded, кроме VIN/DTC/ELM/Protocol

Скрипты (scripts.py):

  • build_full_script(): 7 PID (ОЖ, RPM, скорость, дроссель, нагрузка, STFT, LTFT)
  • build_default_script(): 1 PID (0105 — температура ОЖ) для отладки

brain/ — LLM

  • Diagnoser(api_key, model="gpt-oss-120b", base_url, timeout=180)
  • diagnose(system, user_prompt, history=None) → str
  • Ошибки: LLMError с безопасным для клиента сообщением (без деталей)
  • Различает: Timeout, HTTP 429 («слишком много запросов»), HTTP 5xx, HTTP 4xx
  • SYSTEM_PROMPT: 10 правил, формат ответа — таблицы, степени уверенности в %, план действий по приоритету
  • Модели: gpt-oss-120b (основная), qwen3-6-27b-fp8 (быстрая, но с CoT leak bug)

obd/protocol.py — ELM327 стейт-машина (AndrOBD)

Состояния: UNDEFINED → INITIALIZING → READY → BUSY → READY, ошибка → ERROR/DISCONNECTED

Классификация ответов (Rsp.identify):

Ответ Тип Реакция
> PROMPT Конец ответа
OK OK Уменьшить таймаут
SEARCHING... SEARCHING Нормально при ините
NODATA NODATA Увеличить таймаут, ATST
UNABLE/BUS BUSY/CAN ERROR BUS ERROR DISCONNECTED → ATPC → ATSP0
ERROR/DATA ERROR/BUFFER FULL ERROR ATWS (warm start)
Всё остальное DATA Успех, уменьшить таймаут

Ключевые особенности (1:1 с AndrOBD):

  • Побайтовое чтение с поллингом 1ms (НЕ readLine!)
  • > (0x3E) — не спецсигнал, а разделитель строк как CR/LF
  • Адаптивный таймаут: 50-2000ms, шаг 20ms, ATST = timeout/4
  • _write(): reset_input_buffer() перед записью — чистит хвосты
  • Инициализация: ATSP0 → ATAT1 → ATST → ATS0 → ATL0 → ATE0 (без ATZ)
  • BUS ERROR recovery: ATPC → ATSP0
  • _read(): требует > перед возвратом, иначе TimeoutError

web/app.py — точка входа

  • sys.path.insert(0, корень_проекта) — чтобы импортировать api/, brain/, obd/
  • config = load() — глобально
  • register_api(app) — подключает эндпоинты
  • /elmer.apksend_from_directory("static", "app-debug.apk")

3. Версии и история

Система версионирования

  • Сервер: web/templates/index.html (два места: подзаголовок и подпись APK)
  • Android: android/app/build.gradle.ktsversionName
  • Документация: заголовки .md файлов
  • Менять одновременно во всех местах

История версий (сервер)

v0.93.0-dev (10 июня 2026)

  • Speed-test ELM327: при первом подключении нового ELM — замер скорости ответа на 3 PID (RPM, MAF, STFT) × 3 раза каждый
  • Адаптивный интервал: динамический тест использует max(250, avg_response × 3 × 1.5) вместо жёстких 250ms
  • Профиль устройства: колонка response_time_ms в device_profiles, API PUT /api/v1/elm/profile/<mac>
  • Fix: threading.Lock() в Database — 0 ошибок при 20 конкурентных записях (было 8/20)
  • UI: прогресс speed-теста показывается пользователю
  • Деплой v0.93.0-dev на obdai.ru

v0.48.0 (7 июня 2026)

  • Пробинг ELM327: трехуровневый каскад (L0/L1/L2)
  • Рефакторинг obd/: разделение на независимые сервисы
    • commands.py — каталог всех AT-команд
    • classifier.py — классификация ответов + определение уровня
    • connection.py — транспортный слой (SerialTransport)
  • Fix: init() больше не шлёт ATAT1/ATST (висли на клонах v1.5)
  • Скрипты: три уровня (build_script_l0/l1/l2)
  • БД: таблица device_profiles по BT MAC
  • API: POST /api/v1/elm/probe, GET /api/v1/elm/profile/<mac>
  • api/routes.py/script?level=0|1|2

v0.36.0-dev (31 мая 2026)

  • Ребрендинг Elmer → elmAI (лого, сайт)
  • Opus review: серверные фиксы (WAL, идемпотентность, таймауты LLM, кэш ping-llm)
  • api/config.py@lru_cache, api/db.py — контекстный менеджер + request_id
  • brain/client.py — обработка ошибок (различает 429, 5xx, Timeout), DEFAULT_MODEL
  • obd/protocol.pyreset_input_buffer(), не затирать ERROR
  • CI/CD: GitHub Actions для Android APK (сборка + деплой на сервер)
  • Правило №1 в .instructions.md: вопрос → только ответ, никаких действий

v0.35.0-dev (29-30 мая 2026)

  • Рефакторинг архитектуры: elmer/api/ + brain/ + obd/
  • Удалён мёртвый код (elmer/diagnose.py, elmer/elm.py, elmer/prompts.py, web/raw_endpoint.py)
  • doc/architecture.md — полное описание структуры
  • obd/protocol.py — вынесен из elm_proto, доработан

v0.13.0-dev (28 мая 2026) — AndrOBD стейт-машина

  • Стейт-машина 1:1 с AndrOBD (ElmProt.java)
  • 5 багов исправлено (команды подряд без пауз, ATST не читал ответ, частичный read, таймаут 200ms, BUS ERROR recovery)
  • AdaptiveTiming (500ms start, 50-2000ms range, ATST)
  • OkHttp timeout 30→120с
  • tools/mock_elm327_v2.py — мок с реалистичными задержками
  • tools/test_androbd.py — тест стейт-машины (2/3 зелёные)

v0.11.0-prod (25-27 мая 2026) — fat-client архитектура

  • Fat-client: телефон сам гоняет протокол, сервер только батч-анализ
  • Скрипт диагностики: GET /api/v1/script
  • Загрузка батча: POST /api/v1/session/upload
  • Эндпоинт /chat
  • Исследование 19 ELM/BT проектов → AndrOBD = золотой стандарт
  • doc/elm-reference.md — 1100+ строк паттернов
  • Домен obdai.ru, решение НЕ деплоить до стабильного ELM↔Android

v0.x — ранние версии (fat-client ветка)

  • TestService.kt — зелёная кнопка: самостоятельный прогон протокола (100% работает)
  • ElmForwardService.kt — транспорт BT/TCP ↔ HTTP (проблемы: deadlock, паузы)
  • Побайтовое чтение в mock (исправлен мусор \r vs \r\n)
  • Фиксированный debug.keystore (пароль android, alias androiddebugkey)
  • AndroidManifest.xml: usesCleartextTraffic="true"

4. Android-клиент (отдельный репо)

Структура

app/src/main/java/ru/elmer/client/
├── elm/
│   └── ElmProtocol.kt        — ELM327 стейт-машина (AndrOBD)
├── obd/
│   └── ObdDecoder.kt         — декодер PID/DTC/VIN
├── server/
│   └── ServerClient.kt       — HTTP к серверу (retry 3x, OkHttp)
├── script/
│   ├── ScriptEngine.kt       — движок скриптов
│   └── ScriptRunnerService.kt — фоновая диагностика
├── db/
│   └── SessionDb.kt          — локальная SQLite история
└── ui/
    └── MainActivity.kt       — UI + кнопки

Что НЕ ДОРАБОТАНО (по opus-fix-plan, этап 3-4):

  • request_id на клиенте (UUID до цикла ретраев, в JSON + заголовок Idempotency-Key)
  • X-Api-Key через BuildConfig.API_KEY из local.properties
  • ElmProtocol.sendCommand() — не затирать ERROR, дренаж буфера
  • SessionDb.onUpgrade() — ALTER TABLE вместо DROP TABLE
  • MainActivity — троттлинг /ping-llm (не чаще 60с), убрать дублирующий receiver
  • ScriptRunnerService — null intent → stopSelf, try/finally для progress.stop()
  • Exponential backoff в ретраях

5. Деплой

# Сервер
ssh obdai.ru "cd /opt/elmer && git pull origin master && sudo systemctl restart elmer"

# APK (локально)
cd android && ./gradlew assembleDebug
scp app/build/outputs/apk/debug/app-debug.apk obdai.ru:/opt/elmer/web/static/app-debug.apk

Сервер: gunicorn -w 4 -b 127.0.0.1:8000 web.app:app, nginx :443 → :8000, SSL certbot. Ветка: master — продакшен. Никаких Docker, Kubernetes. Сервер на голом железе. Android APK: GitHub Actions → сборка → авто-деплой на сервер.


6. Правила работы (критически важно!)

  1. Вопрос в любой форме → только ответить. НИЧЕГО НЕ ПРЕДПРИНИМАТЬ. Только прямые императивы («сделай», «исправь», «напиши») — команда к действию.
  2. Не выдумывать инфраструктуру. Никаких Docker, K8s. GitHub Actions можно (Android).
  3. Читать документацию перед действиями. doc/architecture.md — канонический источник.
  4. Не редактировать отчёты Опуса (doc/opus-review*.md — только для чтения).
  5. После правок: коммит → пуш → (если сервер) деплой через SSH.
  6. Версию менять в трёх местах: index.html, build.gradle.kts, доки.
  7. Не редактировать файлы Android-репо (elmer-android) — это отдельный репо.

7. Известные архитектурные решения

  • Почему не Docker: владелец принципиально против. Всё вручную через git + systemd.
  • Почему fat-client (скрипт + батч): в движении связи с сервером нет. Телефон сам гоняет протокол, потом заливает данные.
  • Почему AndrOBD (не своё): AndrOBD — 10 лет продакшена, 1993, вылизанный протокол. Копировать 1:1, не изобретать.
  • Почему не PostgreSQL: SQLite достаточно для одного сервера. Миграция будет когда-нибудь потом.
  • Почему obdai.ru не в продакшене: пока не отлажен ELM↔Android на 100%. Телефон + ноутбук в одной WiFi — быстрее и надёжнее.

8. Ветки

Ветка Описание
master Продакшен (актуальная: v0.36.0-dev)
opus-fixes Правки по отчётам Опуса (31 мая; влита в master)
arch-refactor Рефакторинг elmer/ → api/ brain/ obd/
fat-client Старая fat-client архитектура (устарела)
androbd-proto Прототип AndrOBD стейт-машины (устарела)
elm-layer-v2 Старый ELM-слой (устарела)

9. Динамический тест — START/STOP (v0.93+, 10.06.2026)

Цель

Выявить потерю мощности, подсос воздуха, забитый фильтр, проблемы смеси — ловля STFT/LTFT на сбросе газа.

5 быстрых PID (планировалось)

  1. RPM (010C)
  2. MAF (0110)
  3. STFT (0106)
  4. LTFT (0107)
  5. TPS (0111)

→ После тестов #43-#45 выяснилось: ELM327 v1.5 не успевает 5 PID за 250ms (данные склеиваются). → После тестов #47-#48 (v0.93.0-dev): даже 3 PID × 250ms — 94% ошибок, ELM перестаёт отвечать после ~15 сэмплов. → Решение (текущее, v0.93.0): Speed-test при первом подключении ELM + адаптивный интервал.

Текущая стратегия (v0.93+)

Этап 0 — Speed-test (только при первом подключении нового ELM)

  • После инициализации ELM: замерить время ответа на 010C, 0110, 0106 — каждый 3 раза
  • Показать пользователю: "⏱ Тест скорости: RPM 82ms MAF 91ms STFT 82ms"
  • Сохранить response_time_ms в профиль устройства (по BT MAC)
  • Интервал = max(250, avg_response × 3 × 1.5)
  • При повторных запусках — использовать сохранённое значение

Этап 1 — Статика (перед СТАРТ)

  • Снять все доступные PID по одному разу
  • Определить какие PID отвечают, какие нет (7F 01 12)
  • Запомнить неподдерживаемые — больше не опрашивать
  • Время: ~3-4 секунды

Этап 2 — Динамика (250ms)

  • 3 PID: RPM (010C), MAF (0110), STFT (0106)
  • LTFT, TPS, MAP, Load, coolant, IAT — один раз в статике

Этап 3 — Контроль качества

  • После СТОП проверить количество сэмплов и % ошибок
  • Если < 6-8 сэмплов или > 30% errors — сообщить водителю:

    «Слишком быстро. Нажмите СТАРТ, плавно наберите ~3000 об/мин, сбросьте газ, подождите 3-4 секунды, нажмите СТОП.»

Процедура для водителя

  1. Дождаться ДИАГНОСТИКА → зелёный
  2. Нажать СТАРТ
  3. Плавно газ до ~3000 об/мин
  4. Резко сбросить газ
  5. Подождать 3-4 секунды (без нажатий) — ЭБУ корректирует смесь
  6. СТОП
  7. ➤ (Send) — отправка на сервер

Зачем ждать 3-4 секунды после сброса

  • MAF падает → STFT резко уходит в минус/плюс
  • ЭБУ пытается стабилизировать смесь
  • LTFT начинает подстраиваться
  • Именно эти 3-4 секунды — самое ценное для анализа

Планы

  • Скорость — потом через GPS (не через OBD)
  • Адаптивный интервал если ELM быстрее (v2.x) Сделано в v0.93.0
  • Логирование в историю каждого теста