""" Конфигурация OBD2 эмулятора. Поддержка физических CAN интерфейсов (can0, can1) и виртуальных (vcan0). """ import json from pathlib import Path from typing import Literal from pydantic import BaseModel, Field, field_validator class CANConfig(BaseModel): """Конфигурация CAN интерфейса.""" interface: str = Field(default="vcan0", description="CAN interface name") bitrate: int = Field(default=500000, description="CAN bitrate (ignored for vcan)") @field_validator("interface") @classmethod def validate_interface(cls, v: str) -> str: valid_prefixes = ("can", "vcan") if not any(v.startswith(prefix) for prefix in valid_prefixes): raise ValueError(f"Interface must start with: {valid_prefixes}") return v @property def is_virtual(self) -> bool: """Check if interface is virtual.""" return self.interface.startswith("vcan") class VehicleConfig(BaseModel): """Конфигурация симулируемого автомобиля.""" # Базовые параметры двигателя idle_rpm: int = Field(default=850, ge=600, le=1200) max_rpm: int = Field(default=6500, ge=4000, le=9000) redline_rpm: int = Field(default=6000, ge=3500, le=8500) max_torque_nm: float = Field(default=350.0, ge=100, le=800, description="Max torque in Nm") # Температуры ambient_temp: float = Field(default=20.0, ge=-40, le=50) target_coolant_temp: float = Field(default=90.0, ge=80, le=105) # Топливо fuel_tank_capacity: float = Field(default=58.0, ge=30, le=100) initial_fuel_level: float = Field(default=75.0, ge=0, le=100) # Характеристики авто max_speed: int = Field(default=220, ge=100, le=350) engine_displacement: float = Field(default=2.0, description="Engine displacement in liters") class SimulatorConfig(BaseModel): """Конфигурация симулятора.""" update_rate_hz: int = Field(default=10, ge=1, le=100, description="Vehicle state update rate") scenario: Literal["idle", "city", "highway", "warmup", "manual"] = Field(default="idle") class LoggingConfig(BaseModel): """Конфигурация логирования.""" level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = Field(default="INFO") format: str = Field(default="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s") log_can_frames: bool = Field(default=False, description="Log all CAN frames (verbose)") class Config(BaseModel): """Главная конфигурация приложения.""" can: CANConfig = Field(default_factory=CANConfig) vehicle: VehicleConfig = Field(default_factory=VehicleConfig) simulator: SimulatorConfig = Field(default_factory=SimulatorConfig) logging: LoggingConfig = Field(default_factory=LoggingConfig) def load_config(config_path: Path | None = None) -> Config: """Загрузка конфигурации из файла или defaults.""" if config_path and config_path.exists(): with open(config_path, "r", encoding="utf-8") as f: data = json.load(f) return Config.model_validate(data) return Config() # Глобальный экземпляр конфигурации _config: Config | None = None def get_config() -> Config: """Получить глобальную конфигурацию.""" global _config if _config is None: config_path = Path(__file__).parent / "config.json" _config = load_config(config_path if config_path.exists() else None) return _config def set_config(config: Config) -> None: """Установить глобальную конфигурацию.""" global _config _config = config # Прокси для удобного доступа class _ConfigProxy: def __getattr__(self, name): return getattr(get_config(), name) config = _ConfigProxy()