"""Состояния и классификация ответов ELM327 (AndrOBD STAT/RSP_ID). State — состояния стейт-машины (UNDEFINED → INITIALIZING → READY → BUSY → ...) Rsp — классификация сырых ответов ELM327 """ from enum import Enum, auto class State(Enum): """Состояния стейт-машины (AndrOBD STAT). UNDEFINED → INITIALIZING → READY — нормальный путь. BUSY — во время выполнения команды. ERROR/DISCONNECTED — ошибка, требуется восстановление. """ UNDEFINED = auto() INITIALIZING = auto() READY = auto() BUSY = auto() ERROR = auto() DISCONNECTED = auto() class Rsp: """Классификация ответов ELM327 (AndrOBD RSP_ID). Каждый сырой ответ классифицируется: - PROMPT (`>`) — готов к следующей команде - OK — команда выполнена - SEARCHING — идёт поиск протокола - BUS_ERROR/BUS_BUSY/CAN_ERROR/STOPPED — ошибка шины → DISCONNECTED - ERROR/DATA_ERROR/BUFFER_FULL — ошибка → warm start - UNKNOWN — данные (ответ на PID/DTC) """ PROMPT = ">" OK = "OK" SEARCHING = "SEARCHING" NODATA = "NODATA" ERROR = "ERROR" UNABLE = "UNABLE" BUS_BUSY = "BUS BUSY" BUS_ERROR = "BUS ERROR" CAN_ERROR = "CAN ERROR" BUS_INIT = "BUS INIT" STOPPED = "STOPPED" DATA_ERROR = "DATA ERROR" BUFFER_FULL= "BUFFER FULL" RX_ERROR = "RX ERROR" UNKNOWN = "" @classmethod def identify(cls, raw: str) -> str: """Определяет тип ответа ELM327 по сырой строке.""" u = raw.upper().strip() # NO DATA бывает как "NODATA" так и "NO DATA" if u.replace(" ", "") == "NODATA": return cls.NODATA for tag in (cls.SEARCHING, cls.ERROR, cls.UNABLE, cls.BUS_BUSY, cls.BUS_ERROR, cls.CAN_ERROR, cls.BUS_INIT, cls.STOPPED, cls.DATA_ERROR, cls.BUFFER_FULL, cls.RX_ERROR, cls.OK): if u.startswith(tag): return tag if raw.strip() == ">": return cls.PROMPT return cls.UNKNOWN