123 lines
4.5 KiB
Python
123 lines
4.5 KiB
Python
# app/simulation/vehicle.py
|
||
from __future__ import annotations
|
||
from dataclasses import dataclass, field
|
||
from typing import Dict, Any, List
|
||
|
||
@dataclass
|
||
class Vehicle:
|
||
"""Dynamic property-bag vehicle."""
|
||
state: Dict[str, Any] = field(default_factory=lambda: {
|
||
"rpm": 1400,
|
||
"speed_kmh": 0.0,
|
||
"gear": 0,
|
||
"throttle_pct": 0,
|
||
"ignition": "OFF",
|
||
# elektrische Live-Werte
|
||
"battery_voltage": 12.6, # Batterie-Klemmenspannung
|
||
"elx_voltage": 0.0, # Bordnetz/Bus-Spannung
|
||
"system_voltage": 12.4, # alias
|
||
"battery_soc": 0.80, # 0..1
|
||
"battery_current_a": 0.0, # + entlädt, – lädt
|
||
"alternator_current_a": 0.0, # von Lima geliefert
|
||
"elec_load_total_a": 0.0, # Summe aller Verbraucher
|
||
"ambient_c": 20.0,
|
||
})
|
||
|
||
config: Dict[str, Any] = field(default_factory=lambda: {
|
||
"vehicle": {
|
||
"type": "motorcycle",
|
||
"mass_kg": 210.0,
|
||
"abs": True,
|
||
"tcs": False,
|
||
},
|
||
# Elektrik-Parameter (global)
|
||
"electrical": {
|
||
"battery_capacity_ah": 8.0,
|
||
"battery_r_int_ohm": 0.020, # ~20 mΩ
|
||
# sehr einfache OCV(SOC)-Kennlinie
|
||
"battery_ocv_v": { # bei ~20°C
|
||
0.0: 11.8, 0.1: 12.0, 0.2: 12.1, 0.3: 12.2, 0.4: 12.3,
|
||
0.5: 12.45, 0.6: 12.55, 0.7: 12.65, 0.8: 12.75, 0.9: 12.85,
|
||
1.0: 12.95
|
||
},
|
||
"alternator_reg_v": 14.2,
|
||
"alternator_rated_a": 20.0, # Nennstrom
|
||
"alt_cut_in_rpm": 1500, # ab hier fängt sie an zu liefern
|
||
"alt_full_rpm": 4000, # ab hier volle Kapazität
|
||
},
|
||
})
|
||
|
||
dtc: Dict[str, bool] = field(default_factory=dict)
|
||
dashboard_specs: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
||
|
||
# accumulator für dieses Sim-Frame
|
||
_elec_loads_a: Dict[str, float] = field(default_factory=dict)
|
||
_elec_sources_a: Dict[str, float] = field(default_factory=dict)
|
||
|
||
# ---- helpers for modules ----
|
||
def get(self, key: str, default: Any = None) -> Any:
|
||
return self.state.get(key, default)
|
||
|
||
def set(self, key: str, value: Any) -> None:
|
||
self.state[key] = value
|
||
|
||
def ensure(self, key: str, default: Any) -> Any:
|
||
return self.state.setdefault(key, default)
|
||
|
||
# Dashboard registry (wie gehabt)
|
||
def register_metric(self, key: str, *, label: str | None = None, unit: str | None = None,
|
||
fmt: str | None = None, source: str | None = None,
|
||
priority: int = 100, overwrite: bool = False) -> None:
|
||
spec = self.dashboard_specs.get(key)
|
||
if spec and not overwrite:
|
||
if label and not spec.get("label"): spec["label"] = label
|
||
if unit and not spec.get("unit"): spec["unit"] = unit
|
||
if fmt and not spec.get("fmt"): spec["fmt"] = fmt
|
||
if source and not spec.get("source"): spec["source"] = source
|
||
if spec.get("priority") is None: spec["priority"] = priority
|
||
return
|
||
self.dashboard_specs[key] = {
|
||
"key": key, "label": label or key, "unit": unit, "fmt": fmt,
|
||
"source": source, "priority": priority,
|
||
}
|
||
|
||
def dashboard_snapshot(self) -> Dict[str, Any]:
|
||
return {"specs": dict(self.dashboard_specs), "values": dict(self.state)}
|
||
|
||
def snapshot(self) -> Dict[str, Any]:
|
||
return dict(self.state)
|
||
|
||
# ---- Electrical frame helpers ----
|
||
def elec_reset_frame(self) -> None:
|
||
self._elec_loads_a.clear()
|
||
self._elec_sources_a.clear()
|
||
|
||
def elec_add_load(self, name: str, amps: float) -> None:
|
||
# positive Werte = Stromaufnahme
|
||
self._elec_loads_a[name] = max(0.0, float(amps))
|
||
|
||
def elec_add_source(self, name: str, amps: float) -> None:
|
||
# positive Werte = Einspeisung
|
||
self._elec_sources_a[name] = max(0.0, float(amps))
|
||
|
||
def elec_totals(self) -> tuple[float, float]:
|
||
return sum(self._elec_loads_a.values()), sum(self._elec_sources_a.values())
|
||
|
||
class Module:
|
||
def apply(self, v: Vehicle, dt: float) -> None:
|
||
pass
|
||
|
||
class Orchestrator:
|
||
def __init__(self, vehicle: Vehicle):
|
||
self.vehicle = vehicle
|
||
self.modules: List[Module] = []
|
||
|
||
def add(self, m: Module):
|
||
self.modules.append(m)
|
||
|
||
def update(self, dt: float):
|
||
# Pro Frame die Electrical-Recorder nullen
|
||
self.vehicle.elec_reset_frame()
|
||
for m in self.modules:
|
||
m.apply(self.vehicle, dt)
|