refactor: архитектура сервера — obd/ brain/ api/

- obd/protocol.py — ELM327 стейт-машина AndrOBD (подробные докстринги)
- brain/client.py — LLM-клиент Diagnoser
- brain/prompts.py — системные промпты
- api/db.py — SQLite (sessions + старые таблицы)
- api/scripts.py — сборка диагностических скриптов
- api/parser.py — парсинг ответов ELM327
- api/routes.py — все REST-эндпоинты
- api/config.py — загрузка конфига
- web/app.py — точка входа Flask (упрощена)

Удалено: elmer/elm.py, elmer/elm_proto.py, web/raw_endpoint.py
This commit is contained in:
Repinoid
2026-05-29 17:24:33 +03:00
parent 2028c7ff0e
commit 2db6cb68a8
16 changed files with 1209 additions and 866 deletions
+13 -76
View File
@@ -1,33 +1,31 @@
"""Elmer Web UI — Flask-приложение для локального тестирования.
"""elmAI — точка входа Flask-приложения.
Заодно прототип будущего серверного API.
Запуск: DEEPSEEK_API_KEY=sk-... python web/app.py
Открыть: http://localhost:5005
Запуск через gunicorn:
gunicorn -w 4 -b 127.0.0.1:8000 web.app:app
Структура модулей:
api/ — REST-эндпоинты, БД, скрипты
brain/ — LLM-клиент, промпты
obd/ — ELM327-протокол (AndrOBD)
"""
import sys
import logging
from pathlib import Path
# Добавляем корень проекта в PYTHONPATH
# Добавляем корень проекта в PYTHONPATH для импорта api/, brain/, obd/
sys.path.insert(0, str(Path(__file__).parent.parent))
from flask import Flask, jsonify, redirect, render_template, request, send_from_directory
from elmer.config import load
from elmer.db import Database
from elmer.diagnose import Diagnoser
from elmer.elm import ELM327
from elmer.prompts import SYSTEM_PROMPT, build_user_prompt
from web.raw_endpoint import register as register_raw_endpoint
from web.script_endpoint import register as register_script_endpoint
from api.config import load
from api.routes import register as register_api
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(message)s")
app = Flask(__name__)
config = load()
register_raw_endpoint(app)
register_script_endpoint(app)
register_api(app)
@app.route("/")
@@ -42,67 +40,6 @@ def download_apk():
return send_from_directory("static", "app-debug.apk", as_attachment=True, download_name="elmer.apk")
@app.route("/api/diagnose", methods=["POST"])
def diagnose():
"""Подключается к ELM327, читает данные, отправляет в LLM, возвращает результат."""
elm_cfg = config["elm327"]
# ── 1. ELM327 ────────────────────────────────
try:
elm = ELM327(port=elm_cfg["port"], baudrate=elm_cfg["baudrate"])
except Exception as e:
return jsonify({"error": f"Не удалось открыть порт {elm_cfg['port']}: {e}"}), 500
if not elm.init():
elm.close()
return jsonify({"error": "ELM327 не ответил на ATZ"}), 500
# ── 2. VIN ──────────────────────────────────
vin = elm.read_vin()
if not vin:
elm.close()
return jsonify({"error": "Не удалось прочитать VIN (режим 09 не поддерживается?)"}), 500
# ── 3. Ошибки ───────────────────────────────
dtc_codes = elm.read_dtc_codes("03") + elm.read_dtc_codes("07")
# ── 4. Параметры ────────────────────────────
parameters = elm.read_all_pids(config.get("pids"))
elm.close()
# ── 5. DeepSeek ─────────────────────────────
api_key = config["deepseek"]["api_key"]
if not api_key:
return jsonify({"error": "DEEPSEEK_API_KEY не задан"}), 500
diagnoser = Diagnoser(
api_key=api_key,
model=config["deepseek"].get("model", "deepseek-chat"),
)
user_prompt = build_user_prompt(vin, dtc_codes, parameters)
answer = diagnoser.diagnose(SYSTEM_PROMPT, user_prompt)
# ── 6. SQLite ───────────────────────────────
db = Database()
car_id = db.get_or_create_car(vin)
token_id = db.create_token(car_id)
for dtc in dtc_codes:
db.add_dtc(token_id, dtc["code"], dtc.get("description", ""), dtc["status"])
for p in parameters:
db.add_parameter(token_id, p["pid_code"], p["name"], p["value"], p["unit"])
db.add_llm_message(token_id, "user", user_prompt)
db.add_llm_message(token_id, "assistant", answer)
return jsonify({
"vin": vin,
"dtc_codes": dtc_codes,
"parameters": parameters,
"diagnosis": answer,
"token_id": token_id,
})
if __name__ == "__main__":
print(f"🌐 Elmer Web: http://localhost:5005")
print(f"🌐 elmAI Web: http://localhost:5005")
app.run(host="0.0.0.0", port=5005, debug=False)