Files
elmer/doc/architecture.md
T

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_id
  • cmd (POST) добавляет команду со статусом pending
  • cmd (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.ktsversionName
Raw APK android/raw/build.gradle.ktsversionName
Сайт (основной) /opt/elmer/templates/index.html
Сайт (raw) /opt/elmer/templates/index.html (та же строка)

Важно: Flask использует /opt/elmer/templates/index.html. Файл /opt/elmer/web/templates/index.html — резервная копия.