""" URI parsing and device classification utilities. All functions are pure (no I/O, no DB). """ import re from urllib.parse import urlparse _SERIAL_SCHEMES = {"serial", "com"} _SERVICE_HOSTS = {"localhost", "127.0.0.1"} # Plugin name prefixes whose URIs point at the CAPS server itself, not separate devices. _SKIP_PLUGIN_PREFIXES = ("mnemo", "processor", "recognition", "photo", "voice") def extract_device_info(uri: str) -> tuple[str, str, str] | None: """Return (ip, username, password) from a URI, or None if not a network device. Username and password are extracted from URI credentials (http://user:pass@ip). They may be empty strings if not present in the URI. """ if not uri: return None try: parsed = urlparse(uri) scheme = (parsed.scheme or "").lower() if scheme in _SERIAL_SCHEMES: return None hostname = parsed.hostname if not hostname or hostname in _SERVICE_HOSTS: return None if re.match(r"^\d{1,3}(\.\d{1,3}){3}$", hostname): return hostname, parsed.username or "", parsed.password or "" except Exception: pass return None def extract_ip(uri: str) -> str | None: """Return IP from a URI string, or None if not a network device.""" info = extract_device_info(uri) return info[0] if info else None def cls_to_category(cls: str) -> str: cls_lower = cls.lower() if "camera" in cls_lower or "facedetect" in cls_lower: return "camera" if "controller" in cls_lower or "ovenmk" in cls_lower or "rodos" in cls_lower: return "io_board" if "payment" in cls_lower or "sberpilot" in cls_lower or "payx" in cls_lower: return "bank_terminal" if "payonline" in cls_lower or "kkt" in cls_lower or "fiscal" in cls_lower: return "cash_register" return "other" def cls_to_role(cls: str, plugin_name: str) -> str: cls_lower = cls.lower() name_lower = plugin_name.lower() if "camera" in cls_lower or "camera" in name_lower: if "grz" in name_lower or "plate" in name_lower or "lpr" in name_lower or "frame" in name_lower: return "plate" if "face" in name_lower or "lico" in name_lower: return "face" return "overview" return "other"