"""Persisted user defaults (ports, scope IP, save directory). One flat JSON file, one dataclass. ``save()`` always writes every field, so a partial UI update can never silently drop another field's saved value (which is exactly what the old dict-based writer did to helios_port). """ from __future__ import annotations import json import logging from dataclasses import asdict, dataclass, fields from pathlib import Path logger = logging.getLogger(__name__) DEFAULTS_PATH = Path(__file__).resolve().parent.parent / "aui_defaults.json" @dataclass class ScanDefaults: t3r_port: str = "/dev/ttyUSB0" bbd_port: str = "/dev/ttyUSB1" oscope_ip: str = "192.168.0.1" save_dir: str = str(DEFAULTS_PATH.parent / "scans") helios_port: str = "/dev/ttyUSB2" burst_mode: bool = False strict_rows: bool = False @classmethod def load(cls, path: Path = DEFAULTS_PATH) -> "ScanDefaults": """Load defaults, tolerating a missing/corrupt file and unknown keys.""" if path.exists(): try: with open(path) as f: data = json.load(f) known = {f.name for f in fields(cls)} return cls(**{k: v for k, v in data.items() if k in known}) except (OSError, ValueError, TypeError) as e: logger.warning("Could not load %s (%s); using fallback defaults", path, e) inst = cls() inst.save(path) return inst def save(self, path: Path = DEFAULTS_PATH) -> None: try: with open(path, "w") as f: json.dump(asdict(self), f, indent=2) except OSError as e: logger.warning("Could not save defaults to %s: %s", path, e)