from dataclasses import dataclass, field from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] @dataclass(frozen=True) class AppSettings: """FastAPI 基础配置。""" title: str = "Raspberry Pi Camera API" version: str = "0.1.0" description: str = "用于树莓派相机抓拍的 FastAPI 服务。" host: str = "0.0.0.0" port: int = 8002 @dataclass(frozen=True) class CameraSettings: """树莓派相机配置。""" camera_index: int = 0 width: int = 2048 height: int = 2048 frame_format: str = "RGB888" jpeg_quality: int = 95 warmup_seconds: float = 0.05 enable_continuous_autofocus: bool = True enable_auto_white_balance: bool = True @dataclass(frozen=True) class MqttSettings: """MQTT 配置。""" broker: str = "192.168.77.132" port: int = 1883 keep_alive_interval: int = 60 client_id_prefix: str = "arm_camera_api_" request_timeout_seconds: float = 40.0 connect_timeout_seconds: float = 5.0 default_cycles: int = 1 command_topic: str = "arm_card_dealer/command" camera_response_topic: str = "arm_card_dealer/camera/response" status_topic: str = "arm_card_dealer/status" error_topic: str = "arm_card_dealer/error" camera_command_topic: str = "arm_card_dealer/camera/command" @dataclass(frozen=True) class StaticSettings: """静态目录配置。""" root_dir: Path = field(default_factory=lambda: PROJECT_ROOT / "static") capture_dir_name: str = "captures" front_filename: str = "front.jpg" back_filename: str = "back.jpg" index_filename: str = "index.html" @property def capture_dir(self) -> Path: return self.root_dir / self.capture_dir_name @property def front_image_path(self) -> Path: return self.capture_dir / self.front_filename @property def back_image_path(self) -> Path: return self.capture_dir / self.back_filename @property def front_relative_path(self) -> Path: return Path(self.capture_dir_name) / self.front_filename @property def back_relative_path(self) -> Path: return Path(self.capture_dir_name) / self.back_filename @property def index_path(self) -> Path: return self.root_dir / self.index_filename def ensure_directories(self) -> None: self.capture_dir.mkdir(parents=True, exist_ok=True) @dataclass(frozen=True) class CloudSettings: """MinIO 与卡牌识别服务配置。""" minio_endpoint: str = "192.168.77.249:9000" minio_access_key: str = "pZEwCGnpNN05KPnmC2Yh" minio_secret_key: str = "KfJRuWiv9pVxhIMcFqbkv8hZT9SnNTZ6LPx592D4" minio_secure: bool = False minio_bucket: str = "grading" minio_base_prefix: str = "raspi_img_data" recognize_api_url: str = "http://192.168.77.249:18084/internal/any/card/image/rerank" recognize_timeout_seconds: float = 15.0 data_prefix: str = "http://192.168.31.114/reverseSearch/sample/zip?file_path=" @property def minio_public_base_url(self) -> str: return f"http://{self.minio_endpoint}/{self.minio_bucket}/{self.minio_base_prefix}" @dataclass(frozen=True) class Settings: """项目总配置。""" app: AppSettings = field(default_factory=AppSettings) camera: CameraSettings = field(default_factory=CameraSettings) mqtt: MqttSettings = field(default_factory=MqttSettings) static: StaticSettings = field(default_factory=StaticSettings) cloud: CloudSettings = field(default_factory=CloudSettings) settings = Settings()