import os from pathlib import Path from typing import Any, Dict, List, Optional import yaml from pydantic import BaseModel, Field class DatabaseConfig(BaseModel): dialect: str = "sqlite" host: str = "localhost" port: int = 3306 username: str = "root" password: str = "password" name: str = "security_monitor" echo: bool = False @property def url(self) -> str: if self.dialect == "sqlite": return f"sqlite:///{self.name}.db" return f"mysql+pymysql://{self.username}:{self.password}@{self.host}:{self.port}/{self.name}" class ModelConfig(BaseModel): engine_path: str = "models/yolo11n_fp16_480.engine" pt_model_path: str = "models/yolo11n.pt" imgsz: List[int] = [480, 480] conf_threshold: float = 0.5 iou_threshold: float = 0.45 device: int = 0 batch_size: int = 8 half: bool = True class StreamConfig(BaseModel): buffer_size: int = 2 reconnect_delay: float = 3.0 timeout: float = 10.0 fps_limit: int = 30 class InferenceConfig(BaseModel): queue_maxlen: int = 100 event_queue_maxlen: int = 1000 worker_threads: int = 4 class AlertConfig(BaseModel): snapshot_path: str = "data/alerts" cooldown_sec: int = 300 image_quality: int = 85 class ROIConfig(BaseModel): default_types: List[str] = ["polygon", "line"] max_points: int = 50 class WorkingHours(BaseModel): start: List[int] = Field(default_factory=lambda: [8, 30]) end: List[int] = Field(default_factory=lambda: [17, 30]) class AlgorithmsConfig(BaseModel): leave_post_threshold_sec: int = 360 leave_post_confirm_sec: int = 30 leave_post_return_sec: int = 5 intrusion_check_interval_sec: float = 1.0 intrusion_direction_sensitive: bool = False class LoggingConfig(BaseModel): level: str = "INFO" format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" file: str = "logs/app.log" max_size: str = "100MB" backup_count: int = 5 class MonitoringConfig(BaseModel): enabled: bool = True port: int = 9090 path: str = "/metrics" class LLMConfig(BaseModel): enabled: bool = False api_key: str = "" base_url: str = "" model: str = "qwen-vl-max" timeout: int = 30 class Config(BaseModel): database: DatabaseConfig = Field(default_factory=DatabaseConfig) model: ModelConfig = Field(default_factory=ModelConfig) stream: StreamConfig = Field(default_factory=StreamConfig) inference: InferenceConfig = Field(default_factory=InferenceConfig) alert: AlertConfig = Field(default_factory=AlertConfig) roi: ROIConfig = Field(default_factory=ROIConfig) working_hours: List[WorkingHours] = Field(default_factory=lambda: [ WorkingHours(start=[8, 30], end=[11, 0]), WorkingHours(start=[12, 0], end=[17, 30]) ]) algorithms: AlgorithmsConfig = Field(default_factory=AlgorithmsConfig) logging: LoggingConfig = Field(default_factory=LoggingConfig) monitoring: MonitoringConfig = Field(default_factory=MonitoringConfig) llm: LLMConfig = Field(default_factory=LLMConfig) _config: Optional[Config] = None def load_config(config_path: Optional[str] = None) -> Config: """加载配置文件""" global _config if _config is not None: return _config if config_path is None: config_path = os.environ.get( "CONFIG_PATH", str(Path(__file__).parent / "config.yaml") ) if os.path.exists(config_path): with open(config_path, "r", encoding="utf-8") as f: yaml_config = yaml.safe_load(f) or {} else: yaml_config = {} _config = Config(**yaml_config) return _config def get_config() -> Config: """获取配置单例""" if _config is None: return load_config() return _config def reload_config(config_path: Optional[str] = None) -> Config: """重新加载配置""" global _config _config = None return load_config(config_path)