""" obd/classifier.py — Классификация ответов ELM327 и определение уровня. Отдельный сервис: - classify(raw) → (tag: str, is_ok: bool) - determine_level(responses: dict) → int (-1/0/1/2) Использует obd/commands.py для списков команд по уровням. """ import logging from typing import Dict, Optional, Tuple from obd.state import Rsp from obd.commands import L0_NAMES, L1_NAMES, L2_NAMES logger = logging.getLogger("elmer.classifier") def classify(raw: str) -> Tuple[str, bool]: """Классифицирует сырой ответ ELM327. Returns: (tag, is_ok) tag: Rsp.OK / Rsp.ERROR / Rsp.UNKNOWN / ... is_ok: True если устройство ответило нормально (OK или данные) """ tag = Rsp.identify(raw) is_ok = tag in (Rsp.OK, Rsp.UNKNOWN, Rsp.NODATA, Rsp.SEARCHING) return (tag, is_ok) def determine_level(responses: Dict[str, str]) -> dict: """Определяет уровень устройства по ответам на пробинг-команды. Args: responses: {cmd_name: raw_response} — ответы на команды пробинга. Returns: Профиль: {level, elm_version, protocol, voltage, supported, unsupported, errors} """ result: dict = { "level": -1, "elm_version": None, "protocol": None, "voltage": None, "supported": [], "unsupported": [], "errors": [], } # ── Уровень 0 ────────────────────────────────── l0_ok = True for cmd in L0_NAMES: raw = responses.get(cmd, "") tag, ok = classify(raw) if ok and raw: result["supported"].append(cmd) if cmd == "ATI": result["elm_version"] = raw.strip() elif cmd == "ATDPN": result["protocol"] = raw.strip() elif cmd == "ATRV": result["voltage"] = raw.strip() else: result["unsupported"].append(cmd) result["errors"].append(f"{cmd}: {tag if tag else 'no response'}") l0_ok = False if not l0_ok: logger.warning("classifier: L0 failed") return result result["level"] = 0 # ── Уровень 1 ────────────────────────────────── l1_ok = True for cmd in L1_NAMES: raw = responses.get(cmd, "") tag, ok = classify(raw) if ok and raw: result["supported"].append(cmd) else: result["unsupported"].append(cmd) l1_ok = False if not l1_ok: return result result["level"] = 1 # ── Уровень 2 ────────────────────────────────── l2_ok = True for cmd in L2_NAMES: raw = responses.get(cmd, "") tag, ok = classify(raw) if ok and raw: result["supported"].append(cmd) else: result["unsupported"].append(cmd) l2_ok = False if l2_ok: result["level"] = 2 logger.info(f"classifier: level={result['level']}") return result