made everything modular

This commit is contained in:
2025-09-05 01:03:14 +02:00
parent 268dc201bf
commit 0276a3fb3c
21 changed files with 788 additions and 692 deletions

132
app/app.py Normal file
View File

@@ -0,0 +1,132 @@
# app/gui/app.py
from __future__ import annotations
import json, threading, time, tkinter as tk
from tkinter import ttk, messagebox, filedialog
from app.config import load_settings, setup_logging
from app.obd2 import ObdResponder, make_speed_response, make_rpm_response
from app.simulation.simulator import VehicleSimulator
from app.simulation.ui import discover_ui_tabs
from app.gui.trace import TraceView
from app.gui.dashboard import DashboardView
from app.gui.can_panel import CanPanel
def launch_gui():
cfg = load_settings(); setup_logging(cfg)
can_iface = (cfg.get("can", {}).get("interface")) or "can0"
resp_id_raw = (cfg.get("can", {}).get("resp_id")) or "0x7E8"
try: resp_id = int(resp_id_raw, 16) if isinstance(resp_id_raw, str) else int(resp_id_raw)
except Exception: resp_id = 0x7E8
timeout_ms = cfg.get("can", {}).get("timeout_ms", 200)
bitrate = cfg.get("can", {}).get("baudrate", 500000)
# Simulator + OBD2
sim = VehicleSimulator()
responder = ObdResponder(interface=can_iface, resp_id=resp_id, timeout_ms=timeout_ms)
responder.register_pid(0x0D, lambda: make_speed_response(int(round(sim.snapshot().get("speed_kmh", 0)))))
responder.register_pid(0x0C, lambda: make_rpm_response(int(sim.snapshot().get("rpm", 0))))
# Physics thread
running = True
def physics_loop():
last = time.monotonic()
while running:
now = time.monotonic()
dt = min(0.05, max(0.0, now - last)); last = now
sim.update(dt)
time.sleep(0.02)
threading.Thread(target=physics_loop, daemon=True).start()
# --- Tk Window ---------------------------------------------------------
root = tk.Tk(); root.title("OBD-II ECU Simulator SocketCAN")
root.geometry(f"{cfg.get('ui',{}).get('window',{}).get('width',1100)}x{cfg.get('ui',{}).get('window',{}).get('height',720)}")
# ================== Panedwindow-Layout ==================
# Haupt-Split: Links/Rechts (ein senkrechter Trenner)
main_pw = tk.PanedWindow(root, orient="horizontal")
main_pw.pack(fill="both", expand=True)
# linke Spalte
left_pw = tk.PanedWindow(main_pw, orient="vertical")
main_pw.add(left_pw)
# rechte Spalte
right_pw = tk.PanedWindow(main_pw, orient="vertical")
main_pw.add(right_pw)
# --- Callback-Bridge für Rebind: erst später existiert trace_view ---
trace_ref = {"obj": None}
def on_rebind_iface(new_iface: str):
tv = trace_ref["obj"]
if tv:
tv.rebind_interface(new_iface)
# ----- Top-Left: CAN panel -----
can_panel = CanPanel(
parent=left_pw, responder=responder,
initial_iface=can_iface, initial_resp_id=resp_id,
initial_timeout_ms=timeout_ms, initial_bitrate=bitrate,
on_rebind_iface=on_rebind_iface
)
left_pw.add(can_panel.frame)
# ----- Bottom-Left: Trace -----
trace_view = TraceView(parent=left_pw, responder=responder, iface_initial=can_iface)
trace_ref["obj"] = trace_view
left_pw.add(trace_view.frame)
# ----- Top-Right: dynamic tabs -----
nb = ttk.Notebook(right_pw)
ui_tabs = discover_ui_tabs(nb, sim)
for t in ui_tabs:
title = getattr(t, "TITLE", getattr(t, "NAME", t.__class__.__name__))
nb.add(t.frame, text=title)
right_pw.add(nb)
# ----- Bottom-Right: Dashboard -----
dash_view = DashboardView(parent=right_pw, sim=sim, refresh_ms=250)
right_pw.add(dash_view.frame)
# ---------------- Menü (Load/Save) ----------------
menubar = tk.Menu(root); filemenu = tk.Menu(menubar, tearoff=0)
def do_load():
path = filedialog.askopenfilename(filetypes=[("JSON","*.json"),("All","*.*")])
if not path: return
with open(path,"r",encoding="utf-8") as f: data = json.load(f)
for t in ui_tabs:
if hasattr(t, "load_from_config"): t.load_from_config(data)
sim.load_config(data)
messagebox.showinfo("Simulator", "Konfiguration geladen.")
def do_save():
cfg_out = sim.export_config()
for t in ui_tabs:
if hasattr(t, "save_into_config"): t.save_into_config(cfg_out)
path = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("JSON","*.json")])
if not path: return
with open(path,"w",encoding="utf-8") as f: json.dump(cfg_out, f, indent=2)
messagebox.showinfo("Simulator", "Konfiguration gespeichert.")
filemenu.add_command(label="Konfiguration laden…", command=do_load)
filemenu.add_command(label="Konfiguration speichern…", command=do_save)
filemenu.add_separator(); filemenu.add_command(label="Beenden", command=root.destroy)
menubar.add_cascade(label="Datei", menu=filemenu); root.config(menu=menubar)
# Title updater
def tick_title():
snap = sim.snapshot()
root.title(f"OBD-II ECU Simulator RPM {int(snap.get('rpm',0))} | {int(round(snap.get('speed_kmh',0)))} km/h")
try: root.after(300, tick_title)
except tk.TclError: pass
tick_title()
def on_close():
nonlocal running
running = False
try: trace_view.stop()
except Exception: pass
try: responder.stop()
finally:
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_close)
root.mainloop()