diff --git a/doc/architecture.md b/doc/architecture.md index c5a67da..fb0e47a 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -1,144 +1,211 @@ # Архитектура elmAI -> v0.77.0-dev, 7 июня 2026 +> v0.42.0-dev (основной APK) / v0.4.0-dev (raw-реле), 10 июля 2026 ## Общая схема ``` 📱 Android (elmer-android) - │ Bluetooth - ▼ -🔌 ELM327 - │ OBD-ответы - ▼ -📱 Android (ScriptRunnerService) - │ HTTPS POST /api/v1/session/upload + │ + ├── Bluetooth ──── 🔌 ELM327 ──── ECU (OBD-II) + │ + ├── app/ (основное приложение) + │ └── HTTPS POST /api/v1/session/upload + │ + └── raw/ (реле) + └── HTTP-поллинг /api/v1/elm/raw/* + │ ▼ 🌐 Сервер (5.172.178.213) ├── nginx :443 → gunicorn :8000 - ├── obd/ — ELM327 протокол - ├── brain/ — LLM-клиент - ├── api/ — REST, БД, скрипты - └── web/ — точка входа Flask, статика + ├── api/raw_elm.py — командная очередь для реле (SQLite) + ├── api/ — REST, БД, скрипты + ├── brain/ — LLM-клиент + ├── obd/ — ELM327 протокол (Python) + └── web/ — точка входа Flask, статика +``` + +## Два режима работы + +### Режим 1: Основное приложение (`app/`) +Прямая диагностика: телефон → ELM → скрипт → батч → сервер → LLM + +### Режим 2: Raw-реле (`raw/`) +Тупой ретранслятор: сервер диктует команды, телефон передаёт в ELM и возвращает ответы. +Используется для интерактивной диагностики и тестирования. + +``` +Copilot/сервер Android (raw) ELM327 + │ │ │ + ├─ POST /cmd ──────────→│ │ + │ ├─ sendCommand() ─────→│ + │ │←─ raw response ──────┤ + │←─ POST /response ─────┤ │ + │ │ │ + ├─ GET /response?wait=N─→ (поллинг ответа) │ + │←─ {raw: "41 0C ..."}─┤ │ ``` ## Структура сервера ``` elmer/ -├── obd/ # Модуль 1: ELM327 протокол -│ └── protocol.py # AndrOBD — стейт-машина (1:1 копия AndrOBD) -│ # State, Rsp, AdaptiveTiming +├── api/ +│ ├── config.py # Загрузка config.yaml +│ ├── db.py # SQLite (sessions, cars, dtc, command_queue) +│ ├── routes.py # Эндпоинты: script, upload, chat, probe +│ ├── dtc.py # Эндпоинты DTC +│ ├── ping.py # Эндпоинты проверки +│ ├── scripts.py # Сборка диагностических скриптов +│ ├── parser.py # Парсинг ответов ELM327 +│ └── raw_elm.py # Командная очередь для реле (SQLite command_queue) │ -├── brain/ # Модуль 2: LLM-взаимодействие -│ ├── client.py # Diagnoser — HTTP к api.aillm.ru -│ └── prompts.py # SYSTEM_PROMPT для диагностики +├── brain/ +│ ├── client.py # Diagnoser — HTTP к LLM +│ └── prompts.py # SYSTEM_PROMPT для диагностики │ -├── api/ # Модуль 3: REST API + БД -│ ├── config.py # Загрузка config.yaml -│ ├── db.py # SQLite (sessions, cars, dtc) -│ ├── routes.py # Все эндпоинты (5 шт) -│ ├── scripts.py # Сборка диагностических скриптов -│ └── parser.py # Парсинг ответов ELM327 +├── obd/ +│ └── protocol.py # Python-версия AndrOBD стейт-машины │ -├── web/ # Веб-интерфейс -│ ├── app.py # Точка входа Flask -│ ├── templates/index.html -│ └── static/app-debug.apk -│ -├── tools/ # Разработка -│ ├── mock_elm327_v2.py # Мок ELM327 (TCP) -│ └── test_androbd.py # Тесты стейт-машины +├── web/ +│ ├── app.py # Точка входа Flask +│ ├── templates/ +│ │ └── index.html # Страница загрузки APK +│ └── static/ +│ ├── app-debug.apk # Основной APK +│ └── elm-raw-v022.apk # Raw-реле APK │ ├── doc/ # Документация -│ ├── architecture.md # Этот файл -│ ├── roadmap.md -│ └── session-*.md # Логи сессий -│ ├── config.yaml # LLM API key, порты └── requirements.txt ``` -## Взаимодействие модулей - -``` -web/app.py - └─ import api/routes.py - ├─ import api/config.py → config.yaml - ├─ import api/db.py → SQLite - ├─ import api/scripts.py → сборка скриптов - ├─ import api/parser.py → парсинг батча - ├─ import brain/client.py → Diagnoser → api.aillm.ru - └─ import brain/prompts.py → SYSTEM_PROMPT -``` - -Каждый модуль можно тестировать отдельно. Циклических зависимостей нет. - ## API эндпоинты -| Метод | Путь | Описание | Время | -|---|---|---|---| -| GET | /api/v1/ping | Проверка сервера | ~5мс | -| GET | /api/v1/ping-llm | Проверка LLM | ~2с | -| GET | /api/v1/script?mode= | Скрипт диагностики | ~50мс | -| POST | /api/v1/session/upload | Загрузка батча + LLM | ~30-120с | -| POST | /api/v1/chat | Вопрос к LLM | ~5-15с | +### Основные (app/) -## Android (отдельный репо) +| Метод | Путь | Описание | +|---|---|---| +| GET | /api/v1/ping | Проверка сервера | +| GET | /api/v1/ping-llm | Проверка LLM | +| GET | /api/v1/script?mode= | Скрипт диагностики | +| POST | /api/v1/session/upload | Загрузка батча + LLM | +| POST | /api/v1/chat | Вопрос к LLM | + +### Raw-реле (raw/) + +| Метод | Путь | Описание | +|---|---|---| +| POST | /api/v1/elm/raw/hello | Android: «я готов» | +| POST | /api/v1/elm/raw/cmd | Copilot: поставить команду в очередь | +| GET | /api/v1/elm/raw/cmd | Android: забрать команду | +| POST | /api/v1/elm/raw/response | Android: отправить ответ | +| GET | /api/v1/elm/raw/response | Copilot: прочитать ответ (с device_id) | +| GET | /api/v1/elm/raw/status | Статус устройства | + +## SQLite: command_queue + +Таблица для очереди команд raw-реле: + +```sql +CREATE TABLE command_queue ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + device_id TEXT NOT NULL, + seq INTEGER NOT NULL, + cmd TEXT NOT NULL, + status TEXT DEFAULT 'pending', -- pending | done + raw_response TEXT, + elapsed_ms INTEGER, + prompt INTEGER DEFAULT 0, + error TEXT, + created_at TEXT DEFAULT (datetime('now')), + responded_at TEXT +); +``` + +- `hello` чистит старые команды для device_id +- `cmd` (POST) добавляет команду со статусом `pending` +- `cmd` (GET) атомарно забирает pending → обновляет статус +- `response` (POST) сохраняет ответ +- `response` (GET) возвращает ответы с фильтром по device_id и seq + +## Android (отдельный репо: elmer-android) ``` -elmer-android/app/src/main/java/ru/elmer/client/ -├── ElmProtocol.kt # ELM327 стейт-машина -├── ObdDecoder.kt # Декодер PID/DTC/VIN -├── ServerClient.kt # HTTP к серверу (retry 3x) -├── ScriptEngine.kt # Движок скриптов -├── ScriptRunnerService.kt # Фоновая диагностика -├── SessionDb.kt # Локальная история -├── MainActivity.kt # UI -├── TestService.kt # (устарел) -└── ElmForwardService.kt # (устарел) +android/ +├── app/ — основное приложение (диагностика) +│ └── src/.../ru/elmer/client/ +│ ├── ElmProtocol.kt # ELM327 стейт-машина (AndrOBD) +│ ├── ObdDecoder.kt # Декодер PID/DTC/VIN +│ ├── ServerClient.kt # HTTP к серверу +│ ├── ScriptEngine.kt # Движок скриптов +│ ├── ScriptRunnerService.kt # Фоновая диагностика +│ ├── SessionDb.kt # Локальная история +│ └── MainActivity.kt # UI +│ +└── raw/ — реле (ретранслятор команд) + └── src/.../ru/elmer/raw/ + ├── ElmProtocol.kt # 1:1 копия app/ElmProtocol.kt + ├── ElmActor.kt # Single-thread executor + ├── RawRelayService.kt # Foreground-сервис: BT + поллинг + ├── RelayClient.kt # HTTP-клиент к /api/v1/elm/raw/* + └── MainActivity.kt # Минимальный UI (выбор BT, статус) +``` + +## RawRelayService — главный цикл + +``` +relayLoop(): + 1. Bluetooth connect + 2. ElmProtocol.init() # AndrOBD: ATSP0→ATAT1→ATST→ATS0→ATL0→ATE0 + 3. client.hello() # HTTP → /api/v1/elm/raw/hello + 4. while running: + cmd = client.pollCommand() # GET /api/v1/elm/raw/cmd + raw = actor.sendBlocking(cmd, 5000) + client.postResponse(seq, cmd, raw) ``` ## CI/CD и деплой -**Никаких сторонних CI/CD-сервисов (GitHub Actions, GitLab CI, Jenkins и т.д.). Всё вручную.** - ### Сервер (elmer) ``` Локально: git push gitea master - │ - ▼ -Сервер: ssh obdai.ru - cd /opt/elmer && git pull origin master - sudo systemctl restart elmer +Сервер: ssh obdai.ru + cd /opt/elmer && git pull origin master + sudo systemctl restart elmer ``` - Репо: `gitea.services.ngcloud.ru/Nail/elmer` - Ветка: `master` - Сервис: `gunicorn -w 4 -b 127.0.0.1:8000 web.app:app` - Прокси: nginx :443 → 127.0.0.1:8000 -- Конфиг: `/opt/elmer/config.yaml` -### Android APK (elmer-android) +### Android APK + +**Два репо:** `elmer/` (gitea) и `elmer/android/` (github) ``` -Локально: cd android && ./gradlew assembleDebug - scp app/build/outputs/apk/debug/app-debug.apk obdai.ru:/opt/elmer/web/static/ +# Основной APK +cd android && ./gradlew :app:assembleDebug +scp app/build/outputs/apk/debug/app-debug.apk obdai.ru:/opt/elmer/web/static/ + +# Raw-реле APK +cd android && ./gradlew :raw:assembleDebug +scp raw/build/outputs/apk/debug/raw-debug.apk obdai.ru:/opt/elmer/web/static/elm-raw-v022.apk ``` -- Репо: `github.com/Repinoid/elmer-android` -- Сборка: `./gradlew assembleDebug` -- Доставка: `scp` на сервер в `web/static/app-debug.apk` -- Ссылка для пользователей: `https://obdai.ru/elmer.apk` -- APK обновляется **только** при изменениях в Android-коде +- Репо Android: `github.com/Repinoid/elmer-android` +- Ветка: `opus-fixes` +- APK на сервере: `/opt/elmer/web/static/` ### Версионирование | Где | Файл | |-----|------| -| APK | `android/app/build.gradle.kts` → `versionName` | -| Сайт | `web/templates/index.html` | -| Документация | заголовки `.md` файлов | +| Основной APK | `android/app/build.gradle.kts` → `versionName` | +| Raw APK | `android/raw/build.gradle.kts` → `versionName` | +| Сайт (основной) | `/opt/elmer/templates/index.html` | +| Сайт (raw) | `/opt/elmer/templates/index.html` (та же строка) | -**Версию менять одновременно во всех трёх местах.** +**Важно:** Flask использует `/opt/elmer/templates/index.html`. Файл `/opt/elmer/web/templates/index.html` — резервная копия.