starting to implement realistic Vehicle simulation
This commit is contained in:
122
app/simulation/vehicle.py
Normal file
122
app/simulation/vehicle.py
Normal 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)
|
Reference in New Issue
Block a user