20 KiB
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.apk→send_from_directory("static", "app-debug.apk")
3. Версии и история
Система версионирования
- Сервер:
web/templates/index.html(два места: подзаголовок и подпись APK) - Android:
android/app/build.gradle.kts→versionName - Документация: заголовки
.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, APIPUT /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_idbrain/client.py— обработка ошибок (различает 429, 5xx, Timeout), DEFAULT_MODELobd/protocol.py—reset_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 (исправлен мусор
\rvs\r\n) - Фиксированный debug.keystore (пароль
android, aliasandroiddebugkey) 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.propertiesElmProtocol.sendCommand()— не затирать ERROR, дренаж буфераSessionDb.onUpgrade()— ALTER TABLE вместо DROP TABLEMainActivity— троттлинг/ping-llm(не чаще 60с), убрать дублирующий receiverScriptRunnerService— 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. Правила работы (критически важно!)
- Вопрос в любой форме → только ответить. НИЧЕГО НЕ ПРЕДПРИНИМАТЬ. Только прямые императивы («сделай», «исправь», «напиши») — команда к действию.
- Не выдумывать инфраструктуру. Никаких Docker, K8s. GitHub Actions можно (Android).
- Читать документацию перед действиями.
doc/architecture.md— канонический источник. - Не редактировать отчёты Опуса (
doc/opus-review*.md— только для чтения). - После правок: коммит → пуш → (если сервер) деплой через SSH.
- Версию менять в трёх местах:
index.html,build.gradle.kts, доки. - Не редактировать файлы 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 (планировалось)
- RPM (010C)
- MAF (0110)
- STFT (0106)
- LTFT (0107)
- 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 секунды, нажмите СТОП.»
Процедура для водителя
- Дождаться ДИАГНОСТИКА → зелёный
- Нажать СТАРТ
- Плавно газ до ~3000 об/мин
- Резко сбросить газ
- Подождать 3-4 секунды (без нажатий) — ЭБУ корректирует смесь
- СТОП
- ➤ (Send) — отправка на сервер
Зачем ждать 3-4 секунды после сброса
- MAF падает → STFT резко уходит в минус/плюс
- ЭБУ пытается стабилизировать смесь
- LTFT начинает подстраиваться
- Именно эти 3-4 секунды — самое ценное для анализа
Планы
- Скорость — потом через GPS (не через OBD)
Адаптивный интервал если ELM быстрее (v2.x)✅ Сделано в v0.93.0- Логирование в историю каждого теста