This commit is contained in:
2026-01-29 17:17:14 +03:00
parent f54778528e
commit 69acad18ca
16 changed files with 2418 additions and 0 deletions

104
src/config.py Normal file
View File

@@ -0,0 +1,104 @@
"""
Конфигурация 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)
# Температуры
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()