docs: архитектура — raw-реле, command_queue, два режима
This commit is contained in:
+156
-89
@@ -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` — резервная копия.
|
||||
|
||||
Reference in New Issue
Block a user