# elmAI — Changelog / Полное описание проекта > Файл для нового агента: прочитай — и ты в курсе всего. > Актуально: v0.59.0-dev, 7 июня 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.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/` - `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.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 (исправлен мусор `\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. Деплой ```bash # Сервер 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-слой (устарела) |