Files
OBD2-Simulator/app/simulation/modules/basic.py

142 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# app/simulation/modules/basic.py
from __future__ import annotations
from ..vehicle import Vehicle, Module
import bisect
def _ocv_from_soc(soc: float, table: dict[float, float]) -> float:
# table: {SOC: OCV} unsortiert → linear interpolieren
xs = sorted(table.keys())
ys = [table[x] for x in xs]
s = max(0.0, min(1.0, soc))
i = bisect.bisect_left(xs, s)
if i <= 0: return ys[0]
if i >= len(xs): return ys[-1]
x0, x1 = xs[i-1], xs[i]
y0, y1 = ys[i-1], ys[i]
t = 0.0 if x1 == x0 else (s - x0) / (x1 - x0)
return y0 + t*(y1 - y0)
class BasicModule(Module):
"""
- Zündungslogik inkl. START→ON nach crank_time_s
- Ambient-Temperatur als globale Umweltgröße
- Elektrik:
* Load/Source-Aggregation via Vehicle-Helpers
* Lichtmaschine drehzahlabhängig, Regler auf alternator_reg_v
* Batterie: Kapazität (Ah), Innenwiderstand, OCV(SOC); I_batt > 0 => Entladung
"""
def __init__(self):
self.crank_time_s = 2.7
self._crank_timer = 0.0
def apply(self, v: Vehicle, dt: float) -> None:
# ----- Dashboard registration (unverändert) -----
v.register_metric("ignition", label="Zündung", source="basic", priority=5)
v.register_metric("ambient_c", label="Umgebung", unit="°C", fmt=".1f", source="basic", priority=7)
v.register_metric("battery_voltage", label="Batteriespannung", unit="V", fmt=".2f", source="basic", priority=8)
v.register_metric("elx_voltage", label="ELX-Spannung", unit="V", fmt=".2f", source="basic", priority=10)
v.register_metric("system_voltage", label="Systemspannung", unit="V", fmt=".2f", source="basic", priority=11)
v.register_metric("battery_soc", label="Batterie SOC", unit="", fmt=".2f", source="basic", priority=12)
v.register_metric("battery_current_a", label="Batterie Strom", unit="A", fmt=".2f", source="basic", priority=13)
v.register_metric("alternator_current_a", label="Lima Strom", unit="A", fmt=".2f", source="basic", priority=14)
v.register_metric("elec_load_total_a", label="Verbrauch ges.", unit="A", fmt=".2f", source="basic", priority=15)
# ----- Read config/state -----
econf = v.config.get("electrical", {})
alt_reg_v = float(econf.get("alternator_reg_v", 14.2))
alt_rated_a = float(econf.get("alternator_rated_a", 20.0))
alt_cut_in = int(econf.get("alt_cut_in_rpm", 1500))
alt_full = int(econf.get("alt_full_rpm", 4000))
batt_cap_ah = float(econf.get("battery_capacity_ah", 8.0))
batt_rint = float(econf.get("battery_r_int_ohm", 0.020))
batt_ocv_tbl= dict(econf.get("battery_ocv_v", {})) or {
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
}
ign = v.ensure("ignition", "ON")
rpm = float(v.ensure("rpm", 1200))
soc = float(v.ensure("battery_soc", 0.80))
v.set("ambient_c", float(v.ensure("ambient_c", v.get("ambient_c", 20.0))))
# ----- START auto-fall to ON -----
if ign == "START":
if self._crank_timer <= 0.0:
self._crank_timer = float(self.crank_time_s)
else:
self._crank_timer -= dt
if self._crank_timer <= 0.0:
v.set("ignition", "ON")
ign = "ON"
else:
self._crank_timer = 0.0
# ----- Früh-Exit: OFF/ACC -> Bus AUS, Batterie „ruht“ -----
if ign in ("OFF", "ACC"):
ocv = _ocv_from_soc(soc, batt_ocv_tbl)
# Batterie entspannt sich langsam gegen OCV (optional, super simpel):
# (man kann hier auch gar nichts tun; ich halte batt_v = ocv für okay)
batt_v = ocv
v.set("battery_voltage", round(batt_v, 2))
v.set("elx_voltage", 0.0)
v.set("system_voltage", 0.0)
v.set("battery_current_a", 0.0)
v.set("alternator_current_a", 0.0)
v.set("elec_load_total_a", 0.0)
v.set("battery_soc", round(soc, 3))
return
# ----- ON/START: Elektrik-Bilanz -----
# Beiträge anderer Module summieren
loads_a, sources_a = v.elec_totals()
# Grundlasten (z.B. ECU, Relais)
base_load = 0.5 if ign == "ON" else 0.6 # START leicht höher
loads_a += base_load
# Quellen anderer Module (z.B. DC-DC) können sources_a > 0 machen
# Wir ziehen Quellen von der Last ab was übrig bleibt, muss Lima/Batterie liefern
net_load_a = max(0.0, loads_a - sources_a)
# Lima-Fähigkeit aus rpm
if rpm >= alt_cut_in:
frac = 0.0 if rpm <= alt_cut_in else (rpm - alt_cut_in) / max(1, (alt_full - alt_cut_in))
frac = max(0.0, min(1.0, frac))
alt_cap_a = alt_rated_a * frac
else:
alt_cap_a = 0.0
# Batterie-OCV
ocv = _ocv_from_soc(soc, batt_ocv_tbl)
# Ziel: Regler hält alt_reg_v aber nur, wenn die Lima überhaupt aktiv ist
desired_charge_a = max(0.0, (alt_reg_v - ocv) / max(1e-4, batt_rint)) if alt_cap_a > 0.0 else 0.0
alt_needed_a = net_load_a + desired_charge_a
alt_i = min(alt_needed_a, alt_cap_a)
# Batterie-Bilanz
if alt_cap_a > 0.0 and alt_i >= net_load_a:
# Lima deckt alles; Überschuss lädt Batterie
batt_i = -(alt_i - net_load_a) # negativ = lädt
bus_v = alt_reg_v
else:
# Lima (falls vorhanden) reicht nicht -> Batterie liefert Defizit
deficit = net_load_a - alt_i
batt_i = max(0.0, deficit) # positiv = entlädt
bus_v = ocv - batt_i * batt_rint
# SOC-Update (Ah-Bilanz)
soc = max(0.0, min(1.0, soc - (batt_i * dt) / (3600.0 * max(0.1, batt_cap_ah))))
batt_v = ocv - (batt_i * batt_rint)
# Klammern/Spiegeln
batt_v = max(10.0, min(15.5, batt_v))
bus_v = max(0.0, min(15.5, bus_v))
v.set("battery_voltage", round(batt_v, 2))
v.set("elx_voltage", round(bus_v, 2))
v.set("system_voltage", round(bus_v, 2))
v.set("battery_soc", round(soc, 3))
v.set("battery_current_a", round(batt_i, 2))
v.set("alternator_current_a", round(min(alt_i, alt_cap_a), 2))
v.set("elec_load_total_a", round(net_load_a, 2))