# ============================= # app/simulation/ui/engine.py # ============================= from __future__ import annotations import tkinter as tk from tkinter import ttk from app.simulation.modules.engine import ENGINE_DEFAULTS from app.simulation.ui import UITab class EngineTab(UITab): NAME = "engine" TITLE = "Motor" PRIO = 10 def __init__(self, parent, sim): self.sim = sim self.frame = ttk.Frame(parent, padding=8) for c in (0,1,2,3): self.frame.columnconfigure(c, weight=1) # ---------- Linke Spalte ---------- rowL = 0 def L(lbl, var, 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 == "combo": ttk.Combobox(self.frame, textvariable=var, state="readonly", values=values or [], width=w).grid(row=rowL, column=1, sticky="w") rowL += 1 self.idle = tk.IntVar(); L("Leerlauf [RPM]", self.idle) self.maxrpm = tk.IntVar(); L("Max RPM", self.maxrpm) self.rise = tk.IntVar(); L("Anstieg [RPM/s]", self.rise) self.fall = tk.IntVar(); L("Abfall [RPM/s]", self.fall) self.curve = tk.StringVar(); L("Gaspedal-Kennlinie", self.curve, kind="combo", values=["linear","progressive","aggressive"]) ttk.Separator(self.frame).grid(row=rowL, column=0, columnspan=2, sticky="ew", pady=(8,6)); rowL += 1 self.power = tk.DoubleVar(); L("Motorleistung [kW]", self.power) self.tqpeak = tk.DoubleVar(); L("Drehmoment-Peak [RPM]", self.tqpeak) ttk.Separator(self.frame).grid(row=rowL, column=0, columnspan=2, sticky="ew", pady=(8,6)); rowL += 1 self.st_nom = tk.DoubleVar(); L("Starter Nenn-RPM", self.st_nom) self.st_vmin= tk.DoubleVar(); L("Starter min. Spannung [V]", self.st_vmin) self.st_thr = tk.DoubleVar(); L("Start-Schwelle [RPM]", self.st_thr) self.stall = tk.DoubleVar(); L("Stall-Grenze [RPM]", self.stall) ttk.Separator(self.frame).grid(row=rowL, column=0, columnspan=2, sticky="ew", pady=(8,6)); rowL += 1 self.o_idle = tk.DoubleVar(); L("Öldruck Leerlauf [bar]", self.o_idle) self.o_slope= tk.DoubleVar(); L("Öldruck Steigung [bar/krpm]", self.o_slope) self.o_floor= tk.DoubleVar(); L("Öldruck Boden [bar]", self.o_floor) # ---------- Rechte Spalte ---------- rowR = 0 def R(lbl, var, 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") elif kind == "scale": s = ttk.Scale(self.frame, from_=0.0, to=100.0, variable=var, command=lambda _=None: self._on_pedal_change()) s.grid(row=rowR, column=3, sticky="ew") rowR += 1 self.dk_idle = tk.DoubleVar(); R("DK min Leerlauf [%]", self.dk_idle) self.dk_over = tk.DoubleVar(); R("DK Schub [%]", self.dk_over) self.dk_tau = tk.DoubleVar(); R("DK Zeitkonstante [s]", self.dk_tau) self.tq_kp = tk.DoubleVar(); R("Torque-Kp", self.tq_kp) self.tq_ki = tk.DoubleVar(); R("Torque-Ki", self.tq_ki) ttk.Separator(self.frame).grid(row=rowR, column=2, columnspan=2, sticky="ew", pady=(8,6)); rowR += 1 self.jit_idle= tk.DoubleVar(); R("Jitter Leerlauf [±RPM]", self.jit_idle) self.jit_high= tk.DoubleVar(); R("Jitter hoch [±RPM]", self.jit_high) self.jit_tau = tk.DoubleVar(); R("Jitter-Zeitkonstante [s]", self.jit_tau) self.jit_off = tk.DoubleVar(); R("Jitter aus unter [RPM]", self.jit_off) ttk.Separator(self.frame).grid(row=rowR, column=2, columnspan=2, sticky="ew", pady=(8,6)); rowR += 1 self.amb_c = tk.DoubleVar(); R("Umgebung [°C]", self.amb_c) self.cold_k = tk.DoubleVar(); R("Kalt-Leerlauf +/°C [RPM/°C]", self.cold_k) self.cold_max=tk.DoubleVar(); R("Kalt-Leerlauf max [RPM]", self.cold_max) ttk.Separator(self.frame).grid(row=rowR, column=2, columnspan=2, sticky="ew", pady=(8,6)); rowR += 1 self.pedal = tk.DoubleVar(); R("Gaspedal [%]", self.pedal, kind="scale") # ---------- Buttons ---------- rowBtns = max(rowL, rowR) + 1 btn = ttk.Frame(self.frame); btn.grid(row=rowBtns, column=0, columnspan=4, sticky="w", pady=(8,0)) ttk.Button(btn, text="Aktualisieren", command=self.refresh).pack(side="left") ttk.Button(btn, text="Anwenden", command=self.apply).pack(side="left", padx=(8,0)) self.refresh() def _on_pedal_change(self): try: self.sim.v.set("throttle_pedal_pct", float(self.pedal.get())) except: pass def refresh(self): e = dict(ENGINE_DEFAULTS); e.update(self.sim.v.config.get("engine", {})) # links self.idle.set(e["idle_rpm"]) self.maxrpm.set(e["max_rpm"]) self.rise.set(e["rpm_rise_per_s"]) self.fall.set(e["rpm_fall_per_s"]) self.curve.set(e["throttle_curve"]) self.power.set(e["engine_power_kw"]) self.tqpeak.set(e["torque_peak_rpm"]) self.st_nom.set(e["starter_rpm_nominal"]) self.st_vmin.set(e["starter_voltage_min"]) self.st_thr.set(e["start_rpm_threshold"]) self.stall.set(e["stall_rpm"]) 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"]) # rechts self.dk_idle.set(e["throttle_plate_idle_min_pct"]) self.dk_over.set(e["throttle_plate_overrun_pct"]) self.dk_tau.set(e["throttle_plate_tau_s"]) self.tq_kp.set(e["torque_ctrl_kp"]) self.tq_ki.set(e["torque_ctrl_ki"]) self.jit_idle.set(e["rpm_jitter_idle_amp_rpm"]) self.jit_high.set(e["rpm_jitter_high_amp_rpm"]) self.jit_tau.set(e["rpm_jitter_tau_s"]) self.jit_off.set(e["rpm_jitter_off_threshold_rpm"]) self.amb_c.set(e["coolant_ambient_c"]) self.cold_k.set(e["idle_cold_gain_per_deg"]) self.cold_max.set(e["idle_cold_gain_max"]) self.pedal.set(e["throttle_pedal_pct"]) self._on_pedal_change() def apply(self): cfg = {"engine": { "idle_rpm": int(self.idle.get()), "max_rpm": int(self.maxrpm.get()), "rpm_rise_per_s": int(self.rise.get()), "rpm_fall_per_s": int(self.fall.get()), "throttle_curve": self.curve.get(), "engine_power_kw": float(self.power.get()), "torque_peak_rpm": float(self.tqpeak.get()), "starter_rpm_nominal": float(self.st_nom.get()), "starter_voltage_min": float(self.st_vmin.get()), "start_rpm_threshold": float(self.st_thr.get()), "stall_rpm": float(self.stall.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.dk_idle.get()), "throttle_plate_overrun_pct": float(self.dk_over.get()), "throttle_plate_tau_s": float(self.dk_tau.get()), "torque_ctrl_kp": float(self.tq_kp.get()), "torque_ctrl_ki": float(self.tq_ki.get()), "rpm_jitter_idle_amp_rpm": float(self.jit_idle.get()), "rpm_jitter_high_amp_rpm": float(self.jit_high.get()), "rpm_jitter_tau_s": float(self.jit_tau.get()), "rpm_jitter_off_threshold_rpm": float(self.jit_off.get()), "coolant_ambient_c": float(self.amb_c.get()), "idle_cold_gain_per_deg": float(self.cold_k.get()), "idle_cold_gain_max": float(self.cold_max.get()), "throttle_pedal_pct": float(self.pedal.get()), }} self.sim.load_config(cfg)