8.3 KiB
8.3 KiB
Архитектура elmAI
v0.42.0-dev (основной APK) / v0.4.0-dev (raw-реле), 10 июля 2026
Общая схема
📱 Android (elmer-android)
│
├── 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
├── 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/
├── 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/
│ ├── client.py # Diagnoser — HTTP к LLM
│ └── prompts.py # SYSTEM_PROMPT для диагностики
│
├── obd/
│ └── protocol.py # Python-версия AndrOBD стейт-машины
│
├── web/
│ ├── app.py # Точка входа Flask
│ ├── templates/
│ │ └── index.html # Страница загрузки APK
│ └── static/
│ ├── app-debug.apk # Основной APK
│ └── elm-raw-v022.apk # Raw-реле APK
│
├── doc/ # Документация
├── config.yaml # LLM API key, порты
└── requirements.txt
API эндпоинты
Основные (app/)
| Метод | Путь | Описание |
|---|---|---|
| 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-реле:
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_idcmd(POST) добавляет команду со статусомpendingcmd(GET) атомарно забирает pending → обновляет статусresponse(POST) сохраняет ответresponse(GET) возвращает ответы с фильтром по device_id и seq
Android (отдельный репо: elmer-android)
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 и деплой
Сервер (elmer)
Локально: git push gitea master
Сервер: 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
Android APK
Два репо: elmer/ (gitea) и elmer/android/ (github)
# Основной 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
- Репо Android:
github.com/Repinoid/elmer-android - Ветка:
opus-fixes - APK на сервере:
/opt/elmer/web/static/
Версионирование
| Где | Файл |
|---|---|
| Основной 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 — резервная копия.