Files
obd_emulator/src/config.py
Alexander Poletaev 2ec05d2e9d Add UDS protocol support (ISO 14229) for ECU emulator
Implement UDS Service 0x22 (Read Data By Identifier) with support for:
- Engine ECU (0x7E0): boost pressure, fuel rail pressure, lambda, torque,
  wastegate position, ignition timing, knock correction per cylinder
- DSG Transmission (0x7E1): current gear, selector position, oil temp
- Instrument Cluster (0x714): RPM, ambient light sensor

Also adds ECU_EMULATOR_SPEC.md with full protocol documentation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 16:28:55 +03:00

106 lines
3.9 KiB
Python

"""
Конфигурация 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()