neraly complete Model of Driving done, but needs tweaking

This commit is contained in:
2025-09-05 14:54:29 +02:00
parent 0276a3fb3c
commit 6108413d7e
12 changed files with 1469 additions and 726 deletions

View File

@@ -6,194 +6,204 @@ from __future__ import annotations
import tkinter as tk
from tkinter import ttk
from typing import Dict, Any
from app.simulation.ui import UITab
from app.simulation.ui import UITab
class BasicTab(UITab):
NAME = "basic"
TITLE = "Basisdaten"
PRIO = 10
"""Basis-Fahrzeug-Tab (Zündung & Elektrik)."""
def __init__(self, parent, sim):
self.sim = sim
self.frame = ttk.Frame(parent, padding=8)
self.frame.columnconfigure(1, weight=1)
for c in (0,1,2,3): self.frame.columnconfigure(c, weight=1)
row = 0
# Vehicle basics -----------------------------------------------------------
ttk.Label(self.frame, text="Fahrzeugtyp").grid(row=row, column=0, sticky="w"); row+=1
self.type_var = tk.StringVar(value=self.sim.v.config.get("vehicle", {}).get("type", "motorcycle"))
ttk.Combobox(self.frame, textvariable=self.type_var, state="readonly",
values=["motorcycle", "car", "truck"], width=16)\
.grid(row=row-1, column=1, sticky="w")
# ---------- Linke Spalte ----------
rowL = 0
def L(lbl, var=None, w=12, kind="entry", values=None):
nonlocal rowL
ttk.Label(self.frame, text=lbl).grid(row=rowL, column=0, sticky="w")
if kind == "entry":
ttk.Entry(self.frame, textvariable=var, width=w).grid(row=rowL, column=1, sticky="w")
elif kind == "label":
ttk.Label(self.frame, textvariable=var).grid(row=rowL, column=1, sticky="w")
elif kind == "combo":
ttk.Combobox(self.frame, textvariable=var, state="readonly", values=values or [], width=w)\
.grid(row=rowL, column=1, sticky="w")
elif kind == "check":
ttk.Checkbutton(self.frame, variable=var).grid(row=rowL, column=1, sticky="w")
elif kind == "radio":
f = ttk.Frame(self.frame); f.grid(row=rowL, column=1, sticky="w")
for i,(t,vv) in enumerate(values or []):
ttk.Radiobutton(f, text=t, value=vv, variable=var, command=self._apply_ign)\
.grid(row=0, column=i, padx=(0,6))
rowL += 1
ttk.Label(self.frame, text="Gewicht [kg]").grid(row=row, column=0, sticky="w"); row+=1
self.mass_var = tk.DoubleVar(value=float(self.sim.v.config.get("vehicle", {}).get("mass_kg", 210.0)))
ttk.Entry(self.frame, textvariable=self.mass_var, width=10).grid(row=row-1, column=1, sticky="w")
# Vehicle
self.type = tk.StringVar(); L("Fahrzeugtyp", self.type, kind="combo", values=["motorcycle","car","truck"])
self.mass = tk.DoubleVar(); L("Gewicht [kg]", self.mass)
self.abs = tk.BooleanVar(); L("ABS vorhanden", self.abs, kind="check")
self.tcs = tk.BooleanVar(); L("ASR/Traktionskontrolle", self.tcs, kind="check")
self.abs_var = tk.BooleanVar(value=bool(self.sim.v.config.get("vehicle", {}).get("abs", True)))
ttk.Checkbutton(self.frame, text="ABS vorhanden", variable=self.abs_var)\
.grid(row=row, column=0, columnspan=2, sticky="w"); row+=1
ttk.Separator(self.frame).grid(row=rowL, column=0, columnspan=2, sticky="ew", pady=(8,6)); rowL += 1
self.tcs_var = tk.BooleanVar(value=bool(self.sim.v.config.get("vehicle", {}).get("tcs", False)))
ttk.Checkbutton(self.frame, text="ASR/Traktionskontrolle", variable=self.tcs_var)\
.grid(row=row, column=0, columnspan=2, sticky="w"); row+=1
# Environment / Ignition
self.amb = tk.DoubleVar(); L("Umgebung [°C]", self.amb)
self.ign = tk.StringVar(); L("Zündung", self.ign, kind="radio",
values=[("OFF","OFF"),("ACC","ACC"),("ON","ON"),("START","START")])
ttk.Separator(self.frame).grid(row=row, column=0, columnspan=2, sticky="ew", pady=(6,6)); row+=1
ttk.Separator(self.frame).grid(row=rowL, column=0, columnspan=2, sticky="ew", pady=(8,6)); rowL += 1
# Ambient -----------------------------------------------------------------
ttk.Label(self.frame, text="Umgebung [°C]").grid(row=row, column=0, sticky="w"); row+=1
self.ambient_var = tk.DoubleVar(value=float(self.sim.snapshot().get("ambient_c", 20.0)))
ttk.Entry(self.frame, textvariable=self.ambient_var, width=10)\
.grid(row=row-1, column=1, sticky="w")
# Live links (Labels)
self.batt_v = tk.StringVar(); L("Batterie [V]", self.batt_v, kind="label")
self.elx_v = tk.StringVar(); L("ELX/Bus [V]", self.elx_v, kind="label")
self.soc = tk.StringVar(); L("SOC [0..1]", self.soc, kind="label")
# Ignition ----------------------------------------------------------------
ttk.Label(self.frame, text="Zündung").grid(row=row, column=0, sticky="w"); row+=1
self.ign_var = tk.StringVar(value=str(self.sim.snapshot().get("ignition", "ON")))
ign_frame = ttk.Frame(self.frame); ign_frame.grid(row=row-1, column=1, sticky="w")
for i, state in enumerate(["OFF", "ACC", "ON", "START"]):
ttk.Radiobutton(ign_frame, text=state, value=state,
variable=self.ign_var, command=self._apply_ign)\
.grid(row=0, column=i, padx=(0,6))
# ---------- Rechte Spalte ----------
rowR = 0
def R(lbl, var=None, w=12, kind="entry"):
nonlocal rowR
ttk.Label(self.frame, text=lbl).grid(row=rowR, column=2, sticky="w")
if kind == "entry":
ttk.Entry(self.frame, textvariable=var, width=w).grid(row=rowR, column=3, sticky="w")
elif kind == "label":
ttk.Label(self.frame, textvariable=var).grid(row=rowR, column=3, sticky="w")
rowR += 1
# Live Electrical ----------------------------------------------------------
ttk.Label(self.frame, text="Batterie [V]").grid(row=row, column=0, sticky="w"); row+=1
self.batt_v_var = tk.StringVar(value=f"{self.sim.snapshot().get('battery_voltage', 12.6):.2f}")
ttk.Label(self.frame, textvariable=self.batt_v_var).grid(row=row-1, column=1, sticky="w")
# Live rechts (Labels)
self.ibatt = tk.StringVar(); R("I Batterie [A] (+entlädt)", self.ibatt, kind="label")
self.ialt = tk.StringVar(); R("I Lima [A]", self.ialt, kind="label")
self.load_elx= tk.StringVar(); R("Last ELX [A]", self.load_elx, kind="label")
self.load_bat= tk.StringVar(); R("Last Batterie [A]", self.load_bat, kind="label")
self.load_tot= tk.StringVar(); R("Last gesamt [A]", self.load_tot, kind="label")
ttk.Label(self.frame, text="ELX/Bus [V]").grid(row=row, column=0, sticky="w"); row+=1
self.elx_v_var = tk.StringVar(value=f"{self.sim.snapshot().get('elx_voltage', 0.0):.2f}")
ttk.Label(self.frame, textvariable=self.elx_v_var).grid(row=row-1, column=1, sticky="w")
ttk.Separator(self.frame).grid(row=rowR, column=2, columnspan=2, sticky="ew", pady=(8,6)); rowR += 1
ttk.Label(self.frame, text="SOC [0..1]").grid(row=row, column=0, sticky="w"); row+=1
self.soc_var = tk.StringVar(value=f"{self.sim.snapshot().get('battery_soc', 0.8):.2f}")
ttk.Label(self.frame, textvariable=self.soc_var).grid(row=row-1, column=1, sticky="w")
# Electrical config
self.bcap = tk.DoubleVar(); R("Batt Kap. [Ah]", self.bcap)
self.brint = tk.DoubleVar(); R("Batt R_int [Ω]", self.brint)
self.alt_v = tk.DoubleVar(); R("Reglerspannung [V]", self.alt_v)
self.alt_a = tk.DoubleVar(); R("Lima Nennstrom [A]", self.alt_a)
self.alt_ci = tk.IntVar(); R("Cut-In RPM", self.alt_ci)
self.alt_fc = tk.IntVar(); R("Full-Cap RPM", self.alt_fc)
self.alt_eta= tk.DoubleVar(); R("Lima η_mech [-]", self.alt_eta)
self.alt_rat= tk.DoubleVar(); R("Lima Übersetzung [-]", self.alt_rat)
self.alt_d0 = tk.DoubleVar(); R("Lima Drag Grund [Nm]", self.alt_d0)
self.alt_d1 = tk.DoubleVar(); R("Lima Drag /krpm [Nm]", self.alt_d1)
ttk.Label(self.frame, text="I Batterie [A] (+entlädt)").grid(row=row, column=0, sticky="w"); row+=1
self.ibatt_var = tk.StringVar(value=f"{self.sim.snapshot().get('battery_current_a', 0.0):.2f}")
ttk.Label(self.frame, textvariable=self.ibatt_var).grid(row=row-1, column=1, sticky="w")
# ---------- Buttons ----------
rowBtns = max(rowL, rowR) + 1
btnrow = ttk.Frame(self.frame); btnrow.grid(row=rowBtns, column=0, columnspan=4, sticky="w", pady=(8,0))
ttk.Button(btnrow, text="Aktualisieren", command=self.refresh).pack(side="left")
ttk.Button(btnrow, text="Anwenden", command=self.apply).pack(side="left", padx=(8,0))
ttk.Label(self.frame, text="I Lima [A]").grid(row=row, column=0, sticky="w"); row+=1
self.ialt_var = tk.StringVar(value=f"{self.sim.snapshot().get('alternator_current_a', 0.0):.2f}")
ttk.Label(self.frame, textvariable=self.ialt_var).grid(row=row-1, column=1, sticky="w")
self.refresh()
ttk.Label(self.frame, text="Last gesamt [A]").grid(row=row, column=0, sticky="w"); row+=1
self.load_var = tk.StringVar(value=f"{self.sim.snapshot().get('elec_load_total_a', 0.0):.2f}")
ttk.Label(self.frame, textvariable=self.load_var).grid(row=row-1, column=1, sticky="w")
ttk.Separator(self.frame).grid(row=row, column=0, columnspan=2, sticky="ew", pady=(6,6)); row+=1
# Electrical config --------------------------------------------------------
econf = self.sim.v.config.get("electrical", {})
ttk.Label(self.frame, text="Batt Kap. [Ah]").grid(row=row, column=0, sticky="w"); row+=1
self.bcap = tk.DoubleVar(value=float(econf.get("battery_capacity_ah", 8.0)))
ttk.Entry(self.frame, textvariable=self.bcap, width=10).grid(row=row-1, column=1, sticky="w")
ttk.Label(self.frame, text="Batt R_int [Ω]").grid(row=row, column=0, sticky="w"); row+=1
self.brint = tk.DoubleVar(value=float(econf.get("battery_r_int_ohm", 0.020)))
ttk.Entry(self.frame, textvariable=self.brint, width=10).grid(row=row-1, column=1, sticky="w")
ttk.Label(self.frame, text="Reglerspannung [V]").grid(row=row, column=0, sticky="w"); row+=1
self.alt_v = tk.DoubleVar(value=float(econf.get("alternator_reg_v", 14.2)))
ttk.Entry(self.frame, textvariable=self.alt_v, width=10).grid(row=row-1, column=1, sticky="w")
ttk.Label(self.frame, text="Lima Nennstrom [A]").grid(row=row, column=0, sticky="w"); row+=1
self.alt_a = tk.DoubleVar(value=float(econf.get("alternator_rated_a", 20.0)))
ttk.Entry(self.frame, textvariable=self.alt_a, width=10).grid(row=row-1, column=1, sticky="w")
ttk.Label(self.frame, text="Cut-In RPM").grid(row=row, column=0, sticky="w"); row+=1
self.alt_cutin = tk.IntVar(value=int(econf.get("alt_cut_in_rpm", 1500)))
ttk.Entry(self.frame, textvariable=self.alt_cutin, width=10).grid(row=row-1, column=1, sticky="w")
ttk.Label(self.frame, text="Full-Cap RPM").grid(row=row, column=0, sticky="w"); row+=1
self.alt_full = tk.IntVar(value=int(econf.get("alt_full_rpm", 4000)))
ttk.Entry(self.frame, textvariable=self.alt_full, width=10).grid(row=row-1, column=1, sticky="w")
# Apply --------------------------------------------------------------------
ttk.Button(self.frame, text="Anwenden", command=self.apply)\
.grid(row=row, column=0, pady=(8,0), sticky="w")
# periodic UI refresh
self._tick()
def _tick(self):
# ------------ Logic ------------
def refresh(self):
snap = self.sim.snapshot()
# Live-Werte
self.batt_v_var.set(f"{snap.get('battery_voltage', 0):.2f}")
self.elx_v_var.set(f"{snap.get('elx_voltage', 0):.2f}")
self.soc_var.set(f"{snap.get('battery_soc', 0.0):.2f}")
self.ibatt_var.set(f"{snap.get('battery_current_a', 0.0):.2f}")
self.ialt_var.set(f"{snap.get('alternator_current_a', 0.0):.2f}")
self.load_var.set(f"{snap.get('elec_load_total_a', 0.0):.2f}")
vcfg = dict(self.sim.v.config.get("vehicle", {}))
ecfg = dict(self.sim.v.config.get("electrical", {}))
# START→ON aus dem Modul spiegeln
curr_ign = snap.get("ignition")
if curr_ign and curr_ign != self.ign_var.get():
self.ign_var.set(curr_ign)
# Vehicle
self.type.set(vcfg.get("type", "motorcycle"))
self.mass.set(float(vcfg.get("mass_kg", 210.0)))
self.abs.set(bool(vcfg.get("abs", True)))
self.tcs.set(bool(vcfg.get("tcs", False)))
try:
self.frame.after(200, self._tick)
except tk.TclError:
pass
# Env / Ign
self.amb.set(float(snap.get("ambient_c", 20.0)))
self.ign.set(str(snap.get("ignition", "ON")))
# Live left
self.batt_v.set(f"{float(snap.get('battery_voltage', 12.6)):.2f}")
self.elx_v.set(f"{float(snap.get('elx_voltage', 0.0)):.2f}")
self.soc.set(f"{float(snap.get('battery_soc', 0.80)):.2f}")
# Live right
self.ibatt.set(f"{float(snap.get('battery_current_a', 0.0)):.2f}")
self.ialt.set(f"{float(snap.get('alternator_current_a', 0.0)):.2f}")
self.load_elx.set(f"{float(snap.get('elec_load_elx_a', 0.0)):.2f}")
self.load_bat.set(f"{float(snap.get('elec_load_batt_a', 0.0)):.2f}")
self.load_tot.set(f"{float(snap.get('elec_load_total_a', 0.0)):.2f}")
# Electrical config
self.bcap.set(float(ecfg.get("battery_capacity_ah", 8.0)))
self.brint.set(float(ecfg.get("battery_r_int_ohm", 0.020)))
self.alt_v.set(float(ecfg.get("alternator_reg_v", 14.2)))
self.alt_a.set(float(ecfg.get("alternator_rated_a", 20.0)))
self.alt_ci.set(int(ecfg.get("alt_cut_in_rpm", 1500)))
self.alt_fc.set(int(ecfg.get("alt_full_rpm", 4000)))
self.alt_eta.set(float(ecfg.get("alternator_mech_efficiency", 0.55)))
self.alt_rat.set(float(ecfg.get("alternator_pulley_ratio", 1.0)))
self.alt_d0.set(float(ecfg.get("alternator_drag_nm_idle", 0.15)))
self.alt_d1.set(float(ecfg.get("alternator_drag_nm_per_krpm", 0.05)))
def _apply_ign(self):
# Zündung live setzen
self.sim.v.set("ignition", self.ign_var.get())
self.sim.v.set("ignition", self.ign.get())
def apply(self):
# Ambient in State (wirkt sofort auf Thermik, andere Module lesen das)
try:
self.sim.v.set("ambient_c", float(self.ambient_var.get()))
except Exception:
pass
# Umgebung sofort in den State (wirkt auf Thermik)
try: self.sim.v.set("ambient_c", float(self.amb.get()))
except: pass
cfg = {
"vehicle": {
"type": self.type_var.get(),
"mass_kg": float(self.mass_var.get()),
"abs": bool(self.abs_var.get()),
"tcs": bool(self.tcs_var.get()),
"type": self.type.get(),
"mass_kg": float(self.mass.get()),
"abs": bool(self.abs.get()),
"tcs": bool(self.tcs.get()),
},
"electrical": {
"battery_capacity_ah": float(self.bcap.get()),
"battery_r_int_ohm": float(self.brint.get()),
"alternator_reg_v": float(self.alt_v.get()),
"alternator_rated_a": float(self.alt_a.get()),
"alt_cut_in_rpm": int(self.alt_cutin.get()),
"alt_full_rpm": int(self.alt_full.get()),
"alt_cut_in_rpm": int(self.alt_ci.get()),
"alt_full_rpm": int(self.alt_fc.get()),
"alternator_mech_efficiency": float(self.alt_eta.get()),
"alternator_pulley_ratio": float(self.alt_rat.get()),
"alternator_drag_nm_idle": float(self.alt_d0.get()),
"alternator_drag_nm_per_krpm": float(self.alt_d1.get()),
}
}
self.sim.load_config(cfg)
# Save/Load Hooks für Gesamt-Export
def save_into_config(self, out: Dict[str, Any]) -> None:
out.setdefault("vehicle", {})
out["vehicle"].update({
"type": self.type_var.get(),
"mass_kg": float(self.mass_var.get()),
"abs": bool(self.abs_var.get()),
"tcs": bool(self.tcs_var.get()),
out.setdefault("vehicle", {}).update({
"type": self.type.get(),
"mass_kg": float(self.mass.get()),
"abs": bool(self.abs.get()),
"tcs": bool(self.tcs.get()),
})
out.setdefault("electrical", {})
out["electrical"].update({
out.setdefault("electrical", {}).update({
"battery_capacity_ah": float(self.bcap.get()),
"battery_r_int_ohm": float(self.brint.get()),
"alternator_reg_v": float(self.alt_v.get()),
"alternator_rated_a": float(self.alt_a.get()),
"alt_cut_in_rpm": int(self.alt_cutin.get()),
"alt_full_rpm": int(self.alt_full.get()),
"alt_cut_in_rpm": int(self.alt_ci.get()),
"alt_full_rpm": int(self.alt_fc.get()),
"alternator_mech_efficiency": float(self.alt_eta.get()),
"alternator_pulley_ratio": float(self.alt_rat.get()),
"alternator_drag_nm_idle": float(self.alt_d0.get()),
"alternator_drag_nm_per_krpm": float(self.alt_d1.get()),
})
def load_from_config(self, cfg: Dict[str, Any]) -> None:
vcfg = cfg.get("vehicle", {})
self.type_var.set(vcfg.get("type", self.type_var.get()))
self.mass_var.set(vcfg.get("mass_kg", self.mass_var.get()))
self.abs_var.set(vcfg.get("abs", self.abs_var.get()))
self.tcs_var.set(vcfg.get("tcs", self.tcs_var.get()))
ecfg = cfg.get("electrical", {})
vcfg = cfg.get("vehicle", {}); ecfg = cfg.get("electrical", {})
self.type.set(vcfg.get("type", self.type.get()))
self.mass.set(vcfg.get("mass_kg", self.mass.get()))
self.abs.set(vcfg.get("abs", self.abs.get()))
self.tcs.set(vcfg.get("tcs", self.tcs.get()))
self.bcap.set(ecfg.get("battery_capacity_ah", self.bcap.get()))
self.brint.set(ecfg.get("battery_r_int_ohm", self.brint.get()))
self.alt_v.set(ecfg.get("alternator_reg_v", self.alt_v.get()))
self.alt_a.set(ecfg.get("alternator_rated_a", self.alt_a.get()))
self.alt_cutin.set(ecfg.get("alt_cut_in_rpm", self.alt_cutin.get()))
self.alt_full.set(ecfg.get("alt_full_rpm", self.alt_full.get()))
# wichtig: NICHT self.sim.load_config(cfg) hier!
self.alt_ci.set(ecfg.get("alt_cut_in_rpm", self.alt_ci.get()))
self.alt_fc.set(ecfg.get("alt_full_rpm", self.alt_fc.get()))
self.alt_eta.set(ecfg.get("alternator_mech_efficiency", self.alt_eta.get()))
self.alt_rat.set(ecfg.get("alternator_pulley_ratio", self.alt_rat.get()))
self.alt_d0.set(ecfg.get("alternator_drag_nm_idle", self.alt_d0.get()))
self.alt_d1.set(ecfg.get("alternator_drag_nm_per_krpm", self.alt_d1.get()))
# wichtig: hier KEIN sim.load_config()