""" 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})