starting to implement realistic Vehicle simulation

This commit is contained in:
2025-09-04 15:03:11 +02:00
parent 4c41be706d
commit 6a9d27c6cf
17 changed files with 1468 additions and 250 deletions

122
app/simulation/vehicle.py Normal file
View File

@@ -0,0 +1,122 @@
# 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)