starting to implement realistic Vehicle simulation
This commit is contained in:
12
app/tabs/__init__.py
Normal file
12
app/tabs/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# =============================
|
||||
# app/tabs/__init__.py
|
||||
# =============================
|
||||
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Protocol, Dict, Any
|
||||
|
||||
class SimTab(Protocol):
|
||||
frame: any
|
||||
def save_into_config(self, out: Dict[str, Any]) -> None: ...
|
||||
def load_from_config(self, cfg: Dict[str, Any]) -> None: ...
|
192
app/tabs/basic.py
Normal file
192
app/tabs/basic.py
Normal file
@@ -0,0 +1,192 @@
|
||||
# app/tabs/basic.py
|
||||
from __future__ import annotations
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Dict, Any
|
||||
|
||||
class BasicTab:
|
||||
"""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)
|
||||
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
ttk.Separator(self.frame).grid(row=row, column=0, columnspan=2, sticky="ew", pady=(6,6)); row+=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")
|
||||
|
||||
# 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))
|
||||
|
||||
# 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")
|
||||
|
||||
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.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")
|
||||
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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):
|
||||
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}")
|
||||
|
||||
# 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)
|
||||
|
||||
try:
|
||||
self.frame.after(200, self._tick)
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
def _apply_ign(self):
|
||||
# Zündung live setzen
|
||||
self.sim.v.set("ignition", self.ign_var.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
|
||||
|
||||
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()),
|
||||
},
|
||||
"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()),
|
||||
}
|
||||
}
|
||||
self.sim.load_config(cfg)
|
||||
|
||||
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("electrical", {})
|
||||
out["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()),
|
||||
})
|
||||
|
||||
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", {})
|
||||
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!
|
77
app/tabs/dashboard.py
Normal file
77
app/tabs/dashboard.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# app/tabs/dashboard.py
|
||||
from __future__ import annotations
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
class DashboardTab:
|
||||
"""Zeigt dynamisch alle im Vehicle registrierten Dashboard-Metriken."""
|
||||
def __init__(self, parent, sim):
|
||||
self.sim = sim
|
||||
self.frame = ttk.Frame(parent, padding=8)
|
||||
self.tree = ttk.Treeview(self.frame, columns=("label","value","unit","key","source"), show="headings", height=12)
|
||||
self.tree.heading("label", text="Parameter")
|
||||
self.tree.heading("value", text="Wert")
|
||||
self.tree.heading("unit", text="Einheit")
|
||||
self.tree.heading("key", text="Key")
|
||||
self.tree.heading("source",text="Modul")
|
||||
self.tree.column("label", width=180, anchor="w")
|
||||
self.tree.column("value", width=120, anchor="e")
|
||||
self.tree.column("unit", width=80, anchor="w")
|
||||
self.tree.column("key", width=180, anchor="w")
|
||||
self.tree.column("source",width=100, anchor="w")
|
||||
self.tree.grid(row=0, column=0, sticky="nsew")
|
||||
sb = ttk.Scrollbar(self.frame, orient="vertical", command=self.tree.yview)
|
||||
self.tree.configure(yscrollcommand=sb.set)
|
||||
sb.grid(row=0, column=1, sticky="ns")
|
||||
self.frame.columnconfigure(0, weight=1)
|
||||
self.frame.rowconfigure(0, weight=1)
|
||||
|
||||
self._last_keys = None
|
||||
self._tick()
|
||||
|
||||
def _format_value(self, val, fmt):
|
||||
if fmt:
|
||||
try:
|
||||
return f"{val:{fmt}}"
|
||||
except Exception:
|
||||
return str(val)
|
||||
return str(val)
|
||||
|
||||
def _tick(self):
|
||||
snap = self.sim.v.dashboard_snapshot()
|
||||
specs = snap["specs"]
|
||||
values = snap["values"]
|
||||
|
||||
keys = sorted(specs.keys(), key=lambda k: (specs[k].get("priority", 999), specs[k].get("label", k)))
|
||||
if keys != self._last_keys:
|
||||
# rebuild table
|
||||
for item in self.tree.get_children():
|
||||
self.tree.delete(item)
|
||||
for k in keys:
|
||||
spec = specs[k]
|
||||
lbl = spec.get("label", k)
|
||||
unit = spec.get("unit", "")
|
||||
src = spec.get("source", "")
|
||||
val = self._format_value(values.get(k, ""), spec.get("fmt"))
|
||||
self.tree.insert("", "end", iid=k, values=(lbl, val, unit, k, src))
|
||||
self._last_keys = keys
|
||||
else:
|
||||
# update values only
|
||||
for k in keys:
|
||||
spec = specs[k]
|
||||
val = self._format_value(values.get(k, ""), spec.get("fmt"))
|
||||
try:
|
||||
self.tree.set(k, "value", val)
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
try:
|
||||
self.frame.after(200, self._tick)
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
# Config-API no-ops (für Konsistenz mit anderen Tabs)
|
||||
def save_into_config(self, out): # pragma: no cover
|
||||
pass
|
||||
def load_from_config(self, cfg): # pragma: no cover
|
||||
pass
|
41
app/tabs/dtc.py
Normal file
41
app/tabs/dtc.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# =============================
|
||||
# app/tabs/dtc.py
|
||||
# =============================
|
||||
|
||||
from __future__ import annotations
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Dict, Any
|
||||
|
||||
DTC_LIST = [
|
||||
("P0300", "Random/Multiple Cylinder Misfire"),
|
||||
("P0130", "O2 Sensor Circuit (Bank1-Sensor1)"),
|
||||
("C0035", "Wheel Speed Sensor LF"),
|
||||
("U0121", "Lost Communication With ABS")
|
||||
]
|
||||
|
||||
class DtcTab:
|
||||
def __init__(self, parent, sim):
|
||||
self.sim = sim
|
||||
self.frame = ttk.Frame(parent, padding=8)
|
||||
self.vars: Dict[str, tk.BooleanVar] = {}
|
||||
row = 0
|
||||
ttk.Label(self.frame, text="Diagnose-Flags (Demo)", style="Header.TLabel").grid(row=row, column=0, sticky="w"); row += 1
|
||||
for code, label in DTC_LIST:
|
||||
var = tk.BooleanVar(value=False)
|
||||
ttk.Checkbutton(self.frame, text=f"{code} – {label}", variable=var).grid(row=row, column=0, sticky="w")
|
||||
self.vars[code] = var; row += 1
|
||||
ttk.Button(self.frame, text="Alle löschen", command=self.clear_all).grid(row=row, column=0, sticky="w", pady=(8,0))
|
||||
|
||||
def clear_all(self):
|
||||
for v in self.vars.values(): v.set(False)
|
||||
|
||||
def save_into_config(self, out: Dict[str, Any]) -> None:
|
||||
out.setdefault("dtc", {})
|
||||
out["dtc"].update({code: bool(v.get()) for code, v in self.vars.items()})
|
||||
|
||||
def load_from_config(self, cfg: Dict[str, Any]) -> None:
|
||||
dtc = cfg.get("dtc", {})
|
||||
for code, v in self.vars.items():
|
||||
v.set(bool(dtc.get(code, False)))
|
||||
self.sim.load_config(cfg)
|
176
app/tabs/engine.py
Normal file
176
app/tabs/engine.py
Normal file
@@ -0,0 +1,176 @@
|
||||
# app/tabs/engine.py
|
||||
from __future__ import annotations
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Dict, Any
|
||||
# Wichtig: Defaults aus dem Modul importieren
|
||||
from app.simulation.modules.engine import ENGINE_DEFAULTS
|
||||
|
||||
class EngineTab:
|
||||
def __init__(self, parent, sim):
|
||||
self.sim = sim
|
||||
self.frame = ttk.Frame(parent, padding=8)
|
||||
self.frame.columnconfigure(1, weight=1)
|
||||
|
||||
# ------------- Widgets anlegen (OHNE Defaultwerte eintragen) --------------
|
||||
row = 0
|
||||
ttk.Label(self.frame, text="Leerlauf [RPM]").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.idle_var = tk.IntVar(); ttk.Entry(self.frame, textvariable=self.idle_var, width=10)\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Label(self.frame, text="Max RPM").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.maxrpm_var = tk.IntVar(); ttk.Entry(self.frame, textvariable=self.maxrpm_var, width=10)\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Label(self.frame, text="Anstieg [RPM/s]").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.rise_var = tk.IntVar(); ttk.Entry(self.frame, textvariable=self.rise_var, width=10)\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Label(self.frame, text="Abfall [RPM/s]").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.fall_var = tk.IntVar(); ttk.Entry(self.frame, textvariable=self.fall_var, width=10)\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Label(self.frame, text="Gaspedal-Kennlinie").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.thr_curve = tk.StringVar()
|
||||
ttk.Combobox(self.frame, textvariable=self.thr_curve, state="readonly",
|
||||
values=["linear","progressive","aggressive"])\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Separator(self.frame).grid(row=row, column=0, columnspan=2, sticky="ew", pady=(8,6)); row+=1
|
||||
|
||||
# Leistung
|
||||
ttk.Label(self.frame, text="Motorleistung [kW]").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.power_kw = tk.DoubleVar(); ttk.Entry(self.frame, textvariable=self.power_kw, width=10)\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Label(self.frame, text="Drehmoment-Peak [RPM]").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.peak_rpm = tk.DoubleVar(); ttk.Entry(self.frame, textvariable=self.peak_rpm, width=10)\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Separator(self.frame).grid(row=row, column=0, columnspan=2, sticky="ew", pady=(8,6)); row+=1
|
||||
|
||||
# Starter
|
||||
ttk.Label(self.frame, text="Starter Nenn-RPM").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.starter_nom = tk.DoubleVar(); ttk.Entry(self.frame, textvariable=self.starter_nom, width=10)\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Label(self.frame, text="Starter min. Spannung [V]").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.starter_vmin = tk.DoubleVar(); ttk.Entry(self.frame, textvariable=self.starter_vmin, width=10)\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Label(self.frame, text="Start-Schwelle [RPM]").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.start_th = tk.DoubleVar(); ttk.Entry(self.frame, textvariable=self.start_th, width=10)\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Label(self.frame, text="Stall-Grenze [RPM]").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.stall_rpm = tk.DoubleVar(); ttk.Entry(self.frame, textvariable=self.stall_rpm, width=10)\
|
||||
.grid(row=row-1, column=1, sticky="w")
|
||||
|
||||
ttk.Separator(self.frame).grid(row=row, column=0, columnspan=2, sticky="ew", pady=(8,6)); row+=1
|
||||
|
||||
# Thermik (analog – Variablen ohne Defaults anlegen) ...
|
||||
self.amb_c = tk.DoubleVar(); self.c_warm = tk.DoubleVar(); self.c_cool = tk.DoubleVar()
|
||||
self.o_warm = tk.DoubleVar(); self.o_cool = tk.DoubleVar()
|
||||
self.cold_gain = tk.DoubleVar(); self.cold_gain_max = tk.DoubleVar()
|
||||
# (Labels/Entries spar ich hier ab – wie gehabt weiterführen)
|
||||
|
||||
# Öl, DBW, Jitter, Pedal
|
||||
self.o_idle = tk.DoubleVar(); self.o_slope = tk.DoubleVar(); self.o_floor = tk.DoubleVar()
|
||||
self.plate_idle_min = tk.DoubleVar(); self.plate_overrun = tk.DoubleVar(); self.plate_tau = tk.DoubleVar()
|
||||
self.torque_kp = tk.DoubleVar(); self.torque_ki = tk.DoubleVar()
|
||||
self.jitter_idle = tk.DoubleVar(); self.jitter_high = tk.DoubleVar()
|
||||
self.jitter_tau = tk.DoubleVar(); self.jitter_off = tk.DoubleVar()
|
||||
|
||||
ttk.Label(self.frame, text="Gaspedal [%]").grid(row=row, column=0, sticky="w"); row+=1
|
||||
self.pedal_var = tk.DoubleVar()
|
||||
self.pedal_scale = ttk.Scale(self.frame, from_=0.0, to=100.0, variable=self.pedal_var)
|
||||
self.pedal_scale.grid(row=row-1, column=1, sticky="ew")
|
||||
|
||||
# Buttons
|
||||
row += 1
|
||||
btnrow = ttk.Frame(self.frame); btnrow.grid(row=row, column=0, columnspan=2, 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))
|
||||
|
||||
# Zum Start einmal „live“ laden:
|
||||
self.refresh()
|
||||
|
||||
# liest IMMER effektiv: config.get(key, ENGINE_DEFAULTS[key])
|
||||
def refresh(self):
|
||||
e = dict(ENGINE_DEFAULTS)
|
||||
e.update(self.sim.v.config.get("engine", {})) # Config über default mergen
|
||||
|
||||
self.idle_var.set(e["idle_rpm"])
|
||||
self.maxrpm_var.set(e["max_rpm"])
|
||||
self.rise_var.set(e["rpm_rise_per_s"])
|
||||
self.fall_var.set(e["rpm_fall_per_s"])
|
||||
self.thr_curve.set(e["throttle_curve"])
|
||||
self.power_kw.set(e["engine_power_kw"])
|
||||
self.peak_rpm.set(e["torque_peak_rpm"])
|
||||
|
||||
self.starter_nom.set(e["starter_rpm_nominal"])
|
||||
self.starter_vmin.set(e["starter_voltage_min"])
|
||||
self.start_th.set(e["start_rpm_threshold"])
|
||||
self.stall_rpm.set(e["stall_rpm"])
|
||||
|
||||
self.amb_c.set(e["coolant_ambient_c"])
|
||||
self.c_warm.set(e["coolant_warm_rate_c_per_s"])
|
||||
self.c_cool.set(e["coolant_cool_rate_c_per_s"])
|
||||
self.o_warm.set(e["oil_warm_rate_c_per_s"])
|
||||
self.o_cool.set(e["oil_cool_rate_c_per_s"])
|
||||
self.cold_gain.set(e["idle_cold_gain_per_deg"])
|
||||
self.cold_gain_max.set(e["idle_cold_gain_max"])
|
||||
|
||||
self.o_idle.set(e["oil_pressure_idle_bar"])
|
||||
self.o_slope.set(e["oil_pressure_slope_bar_per_krpm"])
|
||||
self.o_floor.set(e["oil_pressure_off_floor_bar"])
|
||||
|
||||
self.plate_idle_min.set(e["throttle_plate_idle_min_pct"])
|
||||
self.plate_overrun.set(e["throttle_plate_overrun_pct"])
|
||||
self.plate_tau.set(e["throttle_plate_tau_s"])
|
||||
self.torque_kp.set(e["torque_ctrl_kp"])
|
||||
self.torque_ki.set(e["torque_ctrl_ki"])
|
||||
|
||||
self.jitter_idle.set(e["rpm_jitter_idle_amp_rpm"])
|
||||
self.jitter_high.set(e["rpm_jitter_high_amp_rpm"])
|
||||
self.jitter_tau.set(e["rpm_jitter_tau_s"])
|
||||
self.jitter_off.set(e["rpm_jitter_off_threshold_rpm"])
|
||||
|
||||
self.pedal_var.set(e["throttle_pedal_pct"])
|
||||
|
||||
def apply(self):
|
||||
# Nur hier wird geschrieben
|
||||
cfg = {"engine": {
|
||||
"idle_rpm": int(self.idle_var.get()),
|
||||
"max_rpm": int(self.maxrpm_var.get()),
|
||||
"rpm_rise_per_s": int(self.rise_var.get()),
|
||||
"rpm_fall_per_s": int(self.fall_var.get()),
|
||||
"throttle_curve": self.thr_curve.get(),
|
||||
"engine_power_kw": float(self.power_kw.get()),
|
||||
"torque_peak_rpm": float(self.peak_rpm.get()),
|
||||
"starter_rpm_nominal": float(self.starter_nom.get()),
|
||||
"starter_voltage_min": float(self.starter_vmin.get()),
|
||||
"start_rpm_threshold": float(self.start_th.get()),
|
||||
"stall_rpm": float(self.stall_rpm.get()),
|
||||
"coolant_ambient_c": float(self.amb_c.get()),
|
||||
"coolant_warm_rate_c_per_s": float(self.c_warm.get()),
|
||||
"coolant_cool_rate_c_per_s": float(self.c_cool.get()),
|
||||
"oil_warm_rate_c_per_s": float(self.o_warm.get()),
|
||||
"oil_cool_rate_c_per_s": float(self.o_cool.get()),
|
||||
"idle_cold_gain_per_deg": float(self.cold_gain.get()),
|
||||
"idle_cold_gain_max": float(self.cold_gain_max.get()),
|
||||
"oil_pressure_idle_bar": float(self.o_idle.get()),
|
||||
"oil_pressure_slope_bar_per_krpm": float(self.o_slope.get()),
|
||||
"oil_pressure_off_floor_bar": float(self.o_floor.get()),
|
||||
"throttle_plate_idle_min_pct": float(self.plate_idle_min.get()),
|
||||
"throttle_plate_overrun_pct": float(self.plate_overrun.get()),
|
||||
"throttle_plate_tau_s": float(self.plate_tau.get()),
|
||||
"torque_ctrl_kp": float(self.torque_kp.get()),
|
||||
"torque_ctrl_ki": float(self.torque_ki.get()),
|
||||
"rpm_jitter_idle_amp_rpm": float(self.jitter_idle.get()),
|
||||
"rpm_jitter_high_amp_rpm": float(self.jitter_high.get()),
|
||||
"rpm_jitter_tau_s": float(self.jitter_tau.get()),
|
||||
"rpm_jitter_off_threshold_rpm": float(self.jitter_off.get()),
|
||||
"throttle_pedal_pct": float(self.pedal_var.get()),
|
||||
}}
|
||||
self.sim.load_config(cfg)
|
66
app/tabs/gearbox.py
Normal file
66
app/tabs/gearbox.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# =============================
|
||||
# app/tabs/gearbox.py
|
||||
# =============================
|
||||
|
||||
from __future__ import annotations
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Dict, Any, List
|
||||
|
||||
class GearboxTab:
|
||||
def __init__(self, parent, sim):
|
||||
self.sim = sim
|
||||
self.frame = ttk.Frame(parent, padding=8)
|
||||
self.frame.columnconfigure(1, weight=1)
|
||||
|
||||
ttk.Label(self.frame, text="Gänge (inkl. Leerlauf als 0)").grid(row=0, column=0, sticky="w")
|
||||
self.gears_var = tk.IntVar(value=6)
|
||||
ttk.Spinbox(self.frame, from_=1, to=10, textvariable=self.gears_var, width=6, command=self._rebuild_ratios).grid(row=0, column=1, sticky="w")
|
||||
|
||||
self.reverse_var = tk.BooleanVar(value=False)
|
||||
ttk.Checkbutton(self.frame, text="Rückwärtsgang vorhanden", variable=self.reverse_var).grid(row=1, column=0, columnspan=2, sticky="w")
|
||||
|
||||
ttk.Label(self.frame, text="km/h pro 1000 RPM je Gang").grid(row=2, column=0, sticky="w", pady=(6,0))
|
||||
self.ratio_frame = ttk.Frame(self.frame); self.ratio_frame.grid(row=3, column=0, columnspan=2, sticky="ew")
|
||||
self.ratio_vars: List[tk.DoubleVar] = []
|
||||
self._rebuild_ratios()
|
||||
|
||||
ttk.Button(self.frame, text="Anwenden", command=self.apply).grid(row=4, column=0, pady=(8,0), sticky="w")
|
||||
|
||||
def _rebuild_ratios(self):
|
||||
for w in self.ratio_frame.winfo_children(): w.destroy()
|
||||
self.ratio_vars.clear()
|
||||
n = int(self.gears_var.get())
|
||||
for i in range(1, n+1):
|
||||
ttk.Label(self.ratio_frame, text=f"Gang {i}").grid(row=i-1, column=0, sticky="w")
|
||||
v = tk.DoubleVar(value= [12.0,19.0,25.0,32.0,38.0,45.0][i-1] if i-1 < 6 else 45.0)
|
||||
ttk.Entry(self.ratio_frame, textvariable=v, width=8).grid(row=i-1, column=1, sticky="w", padx=(6,12))
|
||||
self.ratio_vars.append(v)
|
||||
|
||||
def apply(self):
|
||||
ratios = [float(v.get()) for v in self.ratio_vars]
|
||||
cfg = {"gearbox": {
|
||||
"num_gears": int(self.gears_var.get()),
|
||||
"reverse": bool(self.reverse_var.get()),
|
||||
"kmh_per_krpm": [0.0] + ratios # index 0 reserved for neutral
|
||||
}}
|
||||
self.sim.load_config(cfg)
|
||||
|
||||
def save_into_config(self, out: Dict[str, Any]) -> None:
|
||||
out.setdefault("gearbox", {})
|
||||
out["gearbox"].update({
|
||||
"num_gears": int(self.gears_var.get()),
|
||||
"reverse": bool(self.reverse_var.get()),
|
||||
"kmh_per_krpm": [0.0] + [float(v.get()) for v in self.ratio_vars]
|
||||
})
|
||||
|
||||
def load_from_config(self, cfg: Dict[str, Any]) -> None:
|
||||
g = cfg.get("gearbox", {})
|
||||
n = int(g.get("num_gears", self.gears_var.get()))
|
||||
self.gears_var.set(n); self.reverse_var.set(g.get("reverse", self.reverse_var.get()))
|
||||
self._rebuild_ratios()
|
||||
ratios = g.get("kmh_per_krpm") or ([0.0] + [v.get() for v in self.ratio_vars])
|
||||
for i, v in enumerate(self.ratio_vars, start=1):
|
||||
try: v.set(float(ratios[i]))
|
||||
except Exception: pass
|
||||
self.sim.load_config(cfg)
|
Reference in New Issue
Block a user