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

339 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`, 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.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-слой (устарела) |
---
## 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
- Логирование в историю каждого теста