170 lines
5.3 KiB
Python
170 lines
5.3 KiB
Python
"""
|
|
api/raw_elm.py — Эндпоинты сырого взаимодействия с ELM327.
|
|
|
|
POST /api/v1/elm/raw — отправить команду, получить сырой ответ
|
|
POST /api/v1/elm/raw/batch — отправить несколько команд
|
|
GET /api/v1/elm/raw/mode — проверить/установить режим (normal/raw)
|
|
|
|
Режим RAW:
|
|
- Все остальные эндпоинты (/script, /session/upload) ОТКЛЮЧЕНЫ
|
|
- Только сырые команды через /elm/raw
|
|
- Никакой пробинг, классификация, стейт-машина — только send/read
|
|
"""
|
|
|
|
import logging
|
|
from flask import jsonify, request, Blueprint
|
|
|
|
logger = logging.getLogger("elmer.raw_api")
|
|
|
|
# Глобальное состояние — raw_mode
|
|
_raw_mode = False
|
|
_raw_elm = None # экземпляр RawELM, если есть локальное соединение
|
|
|
|
|
|
def is_raw_mode() -> bool:
|
|
"""Включён ли режим сырого взаимодействия."""
|
|
return _raw_mode
|
|
|
|
|
|
def set_raw_mode(on: bool):
|
|
"""Включить/выключить режим сырого взаимодействия."""
|
|
global _raw_mode
|
|
_raw_mode = on
|
|
logger.info(f"RawELM mode: {'ON' if on else 'OFF'}")
|
|
|
|
|
|
def set_raw_elm(instance):
|
|
"""Установить экземпляр RawELM для локального соединения."""
|
|
global _raw_elm
|
|
_raw_elm = instance
|
|
|
|
|
|
bp = Blueprint("raw_elm", __name__)
|
|
|
|
|
|
@bp.route("/api/v1/elm/raw", methods=["POST"])
|
|
def raw_command():
|
|
"""Отправить одну сырую команду в ELM327.
|
|
|
|
Body:
|
|
{
|
|
"cmd": "0105", // команда (без \\r)
|
|
"timeout_ms": 500, // опционально, default 500
|
|
"drain_first": false // опционально, очистить буфер перед командой
|
|
}
|
|
|
|
Returns:
|
|
{
|
|
"cmd": "0105",
|
|
"raw": "41 05 5C",
|
|
"prompt": true,
|
|
"elapsed_ms": 48,
|
|
"bytes": 8,
|
|
"error": null
|
|
}
|
|
"""
|
|
if not _raw_elm:
|
|
return jsonify({"error": "no local ELM connection", "hint": "ELM327 не подключён к серверу локально"}), 503
|
|
|
|
data = request.get_json(silent=True)
|
|
if not data or "cmd" not in data:
|
|
return jsonify({"error": "missing 'cmd'"}), 400
|
|
|
|
cmd = data["cmd"].strip()
|
|
if not cmd:
|
|
return jsonify({"error": "empty cmd"}), 400
|
|
|
|
timeout = data.get("timeout_ms")
|
|
drain_first = data.get("drain_first", False)
|
|
|
|
if drain_first:
|
|
_raw_elm.drain()
|
|
|
|
result = _raw_elm.send(cmd, timeout=timeout)
|
|
return jsonify(result)
|
|
|
|
|
|
@bp.route("/api/v1/elm/raw/batch", methods=["POST"])
|
|
def raw_batch():
|
|
"""Отправить несколько команд подряд.
|
|
|
|
Body:
|
|
{
|
|
"cmds": ["0105", "010C", "010D"],
|
|
"timeout_ms": 500,
|
|
"drain_between": false // очищать буфер между командами
|
|
}
|
|
|
|
Returns:
|
|
{"results": [...], "total_elapsed_ms": 1234}
|
|
"""
|
|
if not _raw_elm:
|
|
return jsonify({"error": "no local ELM connection"}), 503
|
|
|
|
data = request.get_json(silent=True)
|
|
if not data or "cmds" not in data:
|
|
return jsonify({"error": "missing 'cmds'"}), 400
|
|
|
|
cmds = data["cmds"]
|
|
if len(cmds) > 100:
|
|
return jsonify({"error": "too many commands (max 100)"}), 400
|
|
|
|
timeout = data.get("timeout_ms")
|
|
drain_between = data.get("drain_between", False)
|
|
|
|
import time
|
|
t0 = time.time()
|
|
results = []
|
|
for cmd in cmds:
|
|
if drain_between:
|
|
_raw_elm.drain()
|
|
results.append(_raw_elm.send(cmd, timeout=timeout))
|
|
|
|
total_elapsed = int((time.time() - t0) * 1000)
|
|
return jsonify({"results": results, "total_elapsed_ms": total_elapsed})
|
|
|
|
|
|
@bp.route("/api/v1/elm/raw/drain", methods=["POST"])
|
|
def raw_drain():
|
|
"""Очистить входной буфер ELM327. Возвращает что было выброшено."""
|
|
if not _raw_elm:
|
|
return jsonify({"error": "no local ELM connection"}), 503
|
|
|
|
result = _raw_elm.drain()
|
|
return jsonify(result)
|
|
|
|
|
|
@bp.route("/api/v1/elm/raw/available", methods=["GET"])
|
|
def raw_available():
|
|
"""Сколько байт ждёт во входном буфере."""
|
|
if not _raw_elm:
|
|
return jsonify({"error": "no local ELM connection"}), 503
|
|
|
|
return jsonify({"available": _raw_elm.available()})
|
|
|
|
|
|
@bp.route("/api/v1/elm/raw/log", methods=["GET"])
|
|
def raw_log():
|
|
"""Вся история команд в сыром режиме."""
|
|
if not _raw_elm:
|
|
return jsonify({"log": []})
|
|
|
|
return jsonify({"log": _raw_elm.log, "count": len(_raw_elm.log)})
|
|
|
|
|
|
@bp.route("/api/v1/elm/raw/mode", methods=["GET", "POST"])
|
|
def raw_mode_control():
|
|
"""GET: проверить режим. POST: включить/выключить raw mode.
|
|
|
|
POST body: {"raw_mode": true/false}
|
|
"""
|
|
global _raw_mode
|
|
|
|
if request.method == "POST":
|
|
data = request.get_json(silent=True) or {}
|
|
on = data.get("raw_mode", False)
|
|
set_raw_mode(on)
|
|
return jsonify({"raw_mode": _raw_mode, "has_local_elm": _raw_elm is not None})
|
|
|
|
return jsonify({"raw_mode": _raw_mode, "has_local_elm": _raw_elm is not None})
|