Files
OBD2-Simulator/app/simulation/ui/cooling.py

150 lines
6.7 KiB
Python

# =============================
# app/simulation/ui/cooling.py
# =============================
from __future__ import annotations
import tkinter as tk
from tkinter import ttk
from app.simulation.modules.cooling import COOLING_DEFAULTS
from app.simulation.ui import UITab
class CoolingTab(UITab):
NAME = "cooling"
TITLE = "Kühlung"
PRIO = 11
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":
cb = ttk.Combobox(self.frame, textvariable=var, state="readonly", values=values or [])
cb.grid(row=rowL, column=1, sticky="w")
elif kind == "check":
ttk.Checkbutton(self.frame, variable=var).grid(row=rowL, column=1, sticky="w")
rowL += 1
self.t_open = tk.DoubleVar(); L("Thermostat öffnet ab [°C]", self.t_open)
self.t_full = tk.DoubleVar(); L("Thermostat voll offen [°C]", self.t_full)
self.rad_base = tk.DoubleVar(); L("Radiator-Basis [W/K]", self.rad_base)
self.ram_gain = tk.DoubleVar(); L("Fahrtwind-Zuwachs [W/K pro km/h]", self.ram_gain)
self.amb_c = tk.DoubleVar(); L("Umgebung [°C]", self.amb_c)
self.Cc = tk.DoubleVar(); L("Wärmekapazität Kühlmittel [J/K]", self.Cc)
self.Coil = tk.DoubleVar(); L("Wärmekapazität Öl [J/K]", self.Coil)
ttk.Separator(self.frame).grid(row=rowL, column=0, columnspan=2, sticky="ew", pady=(8,6)); rowL += 1
# Versorgung & Nachlauf (links)
self.feed = tk.StringVar()
self.afteren = tk.BooleanVar()
self.afterth = tk.DoubleVar()
self.aftermax= tk.DoubleVar()
L("Lüfter-Versorgung", self.feed, kind="combo", values=["elx", "battery"])
L("Nachlauf aktiv", self.afteren, kind="check")
L("Nachlauf-Schwelle [°C]", self.afterth)
L("Nachlauf max. Zeit [s]", self.aftermax)
# ---------- Rechte Spalte ----------
rowR = 0
def R(lbl, var, w=12):
nonlocal rowR
ttk.Label(self.frame, text=lbl).grid(row=rowR, column=2, sticky="w")
ttk.Entry(self.frame, textvariable=var, width=w).grid(row=rowR, column=3, sticky="w")
rowR += 1
self.f1_on = tk.DoubleVar(); R("Lüfter 1 EIN [°C]", self.f1_on)
self.f1_off = tk.DoubleVar(); R("Lüfter 1 AUS [°C]", self.f1_off)
self.f2_on = tk.DoubleVar(); R("Lüfter 2 EIN [°C]", self.f2_on)
self.f2_off = tk.DoubleVar(); R("Lüfter 2 AUS [°C]", self.f2_off)
ttk.Separator(self.frame).grid(row=rowR, column=2, columnspan=2, sticky="ew", pady=(8,6)); rowR += 1
self.f1_w = tk.DoubleVar(); R("Lüfter 1 Leistung [W]", self.f1_w)
self.f2_w = tk.DoubleVar(); R("Lüfter 2 Leistung [W]", self.f2_w)
self.f1_air = tk.DoubleVar(); R("Lüfter 1 Luftstrom [W/K]", self.f1_air)
self.f2_air = tk.DoubleVar(); R("Lüfter 2 Luftstrom [W/K]", self.f2_air)
ttk.Separator(self.frame).grid(row=rowR, column=2, columnspan=2, sticky="ew", pady=(8,6)); rowR += 1
self.Uoc = tk.DoubleVar(); R("Öl↔Kühlmittel Kopplung [W/K]", self.Uoc)
self.Uoil = tk.DoubleVar(); R("Öl→Umgebung [W/K]", self.Uoil)
self.frac = tk.DoubleVar(); R("Motorwärme→Kühlmittel [%]", self.frac)
# ---------- 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))
self.refresh()
def refresh(self):
c = dict(COOLING_DEFAULTS)
c.update(self.sim.v.config.get("cooling", {}))
# links
self.t_open.set(c["thermostat_open_c"])
self.t_full.set(c["thermostat_full_c"])
self.rad_base.set(c["rad_base_u_w_per_k"])
self.ram_gain.set(c["ram_air_gain_per_kmh"])
self.amb_c.set(self.sim.v.get("ambient_c", 20.0))
self.Cc.set(c["coolant_thermal_cap_j_per_k"])
self.Coil.set(c["oil_thermal_cap_j_per_k"])
# Versorgung & Nachlauf
self.feed.set(c.get("fan_power_feed", "elx"))
self.afteren.set(bool(c.get("fan_afterrun_enable", False)))
self.afterth.set(float(c.get("fan_afterrun_threshold_c", 105.0)))
self.aftermax.set(float(c.get("fan_afterrun_max_s", 300.0)))
# rechts
self.f1_on.set(c["fan1_on_c"]); self.f1_off.set(c["fan1_off_c"])
self.f2_on.set(c["fan2_on_c"]); self.f2_off.set(c["fan2_off_c"])
self.f1_w.set(c["fan1_power_w"]); self.f2_w.set(c["fan2_power_w"])
self.f1_air.set(c["fan1_airflow_gain"]); self.f2_air.set(c["fan2_airflow_gain"])
self.Uoc.set(c["oil_coolant_u_w_per_k"])
self.Uoil.set(c["oil_to_amb_u_w_per_k"])
self.frac.set(c["engine_heat_frac_to_coolant"]*100.0)
def apply(self):
cfg = {"cooling": {
# links
"thermostat_open_c": float(self.t_open.get()),
"thermostat_full_c": float(self.t_full.get()),
"rad_base_u_w_per_k": float(self.rad_base.get()),
"ram_air_gain_per_kmh": float(self.ram_gain.get()),
"coolant_thermal_cap_j_per_k": float(self.Cc.get()),
"oil_thermal_cap_j_per_k": float(self.Coil.get()),
# Versorgung & Nachlauf
"fan_power_feed": self.feed.get(),
"fan_afterrun_enable": bool(self.afteren.get()),
"fan_afterrun_threshold_c": float(self.afterth.get()),
"fan_afterrun_max_s": float(self.aftermax.get()),
# rechts
"fan1_on_c": float(self.f1_on.get()),
"fan1_off_c": float(self.f1_off.get()),
"fan2_on_c": float(self.f2_on.get()),
"fan2_off_c": float(self.f2_off.get()),
"fan1_power_w": float(self.f1_w.get()),
"fan2_power_w": float(self.f2_w.get()),
"fan1_airflow_gain": float(self.f1_air.get()),
"fan2_airflow_gain": float(self.f2_air.get()),
"oil_coolant_u_w_per_k": float(self.Uoc.get()),
"oil_to_amb_u_w_per_k": float(self.Uoil.get()),
"engine_heat_frac_to_coolant": float(self.frac.get())/100.0,
}}
self.sim.load_config(cfg)