import platform import shutil import socket from pathlib import Path APP_NAME = "Car UI" VERSION = "0.1.0-dev" BUILD_DATE = "dev" GIT_HASH = "dev" DEVICE_MODEL = "Raspberry Pi" DEFAULT_SOUND_VOLUME = 100 DEFAULT_PREMUTE_VOLUME = 10 DEFAULT_DUCKING_VOLUME = 35 DEV_MODE_ENABLE = (Path(__file__).resolve().parent / "dev_mode_enable").exists() def get_device_model() -> str: for path in ("/proc/device-tree/model", "/sys/firmware/devicetree/base/model"): try: with open(path, "rb") as f: raw = f.read().strip(b"\x00").strip() if raw: return raw.decode("utf-8", errors="replace") except OSError: continue return DEVICE_MODEL def get_os_pretty_name() -> str: for path in ("/etc/os-release", "/usr/lib/os-release"): try: with open(path, "r", encoding="utf-8") as f: for line in f: if line.startswith("PRETTY_NAME="): value = line.split("=", 1)[1].strip().strip('"') return value or "—" except OSError: continue return platform.system() or "—" def get_kernel_version() -> str: return platform.release() or "—" def _read_meminfo() -> dict[str, int]: info: dict[str, int] = {} try: with open("/proc/meminfo", "r", encoding="utf-8") as f: for line in f: if ":" not in line: continue key, rest = line.split(":", 1) parts = rest.strip().split() if not parts: continue try: value_kb = int(parts[0]) except ValueError: continue info[key] = value_kb * 1024 except OSError: return {} return info def _format_bytes(num_bytes: int) -> str: if num_bytes <= 0: return "—" gib = num_bytes / (1024 ** 3) if gib >= 1: return f"{gib:.1f} GB" mib = num_bytes / (1024 ** 2) return f"{mib:.0f} MB" def get_ram_info() -> str: info = _read_meminfo() total = info.get("MemTotal", 0) free = info.get("MemFree", 0) if total <= 0: return "—" return f"{_format_bytes(free)} / {_format_bytes(total)}" def get_disk_info() -> str: try: usage = shutil.disk_usage("/") except OSError: return "—" return f"{_format_bytes(usage.used)} / {_format_bytes(usage.total)}" def get_serial_number() -> str: for path in ("/proc/device-tree/serial-number",): try: with open(path, "rb") as f: raw = f.read().strip(b"\x00").strip() if raw: return raw.decode("utf-8", errors="replace") except OSError: continue try: with open("/proc/cpuinfo", "r", encoding="utf-8") as f: for line in f: if line.lower().startswith("serial"): return line.split(":", 1)[1].strip() or "—" except OSError: pass return "—" def get_ip_address() -> str: try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: sock.connect(("8.8.8.8", 80)) ip = sock.getsockname()[0] finally: sock.close() if ip and not ip.startswith("127."): return ip except OSError: pass try: ip = socket.gethostbyname(socket.gethostname()) if ip and not ip.startswith("127."): return ip except OSError: pass return "—" def get_cpu_temp() -> str: for path in ("/sys/class/thermal/thermal_zone0/temp",): try: with open(path, "r", encoding="utf-8") as f: raw = f.read().strip() if raw: temp_c = float(raw) / 1000.0 return f"{temp_c:.1f} C" except (OSError, ValueError): continue return "—" def get_display_resolution() -> str: for path in ("/sys/class/graphics/fb0/virtual_size", "/sys/class/graphics/fb0/modes"): try: with open(path, "r", encoding="utf-8") as f: raw = f.read().strip() if not raw: continue if "x" in raw: first_line = raw.splitlines()[0] return first_line.split(" ", 1)[0] if "," in raw: width, height = raw.split(",", 1) return f"{width.strip()}x{height.strip()}" except OSError: continue return "—"