From f215cff21a218cb954f386aeee4088efacb58e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Wed, 3 Jun 2026 06:36:03 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20CHANGELOG.md=20=E2=80=94=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D0=BE=D0=B5=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=B0=D0=B3=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f3c392a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,255 @@ +# elmAI — Changelog / Полное описание проекта + +> Файл для нового агента: прочитай — и ты в курсе всего. +> Актуально: v0.36.0-dev, 31 мая 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.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 — всё вручную) +- Правило №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` — продакшен. +**НИКАКИХ** GitHub Actions, CI/CD сервисов, Docker, Kubernetes. + +--- + +## 6. Правила работы (критически важно!) + +1. **Вопрос в любой форме → только ответить. НИЧЕГО НЕ ПРЕДПРИНИМАТЬ.** Только прямые императивы («сделай», «исправь», «напиши») — команда к действию. +2. **Не выдумывать инфраструктуру.** Никаких GitHub Actions, Docker, K8s, CI/CD. +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-слой (устарела) |