# app/gui/can_panel.py from __future__ import annotations import json, subprocess, tkinter as tk from tkinter import ttk, messagebox from app.can import ( list_can_ifaces, link_up, link_down, link_state, link_kind, have_cap_netadmin ) class CanPanel: """Fixes CAN- & Responder-Panel (oben links).""" def __init__(self, parent, responder, initial_iface: str, initial_resp_id: int, initial_timeout_ms: int, initial_bitrate: int, on_rebind_iface=None): self.responder = responder self.on_rebind_iface = on_rebind_iface or (lambda iface: None) self.frame = ttk.LabelFrame(parent, text="CAN & Settings", padding=8) for i in range(3): self.frame.columnconfigure(i, weight=1) # Interface ttk.Label(self.frame, text="Interface").grid(row=0, column=0, sticky="w") self.iface_var = tk.StringVar(value=initial_iface) self.iface_dd = ttk.Combobox(self.frame, textvariable=self.iface_var, values=list_can_ifaces() or [initial_iface], state="readonly", width=14) self.iface_dd.grid(row=0, column=1, sticky="ew", padx=(6,0)) ttk.Button(self.frame, text="Refresh", command=self._refresh_ifaces).grid(row=0, column=2, sticky="w") # RESP-ID ttk.Label(self.frame, text="RESP-ID (hex)").grid(row=1, column=0, sticky="w") self.resp_var = tk.StringVar(value=f"0x{initial_resp_id:03X}") ttk.Entry(self.frame, textvariable=self.resp_var, width=10).grid(row=1, column=1, sticky="w", padx=(6,0)) # Timeout ttk.Label(self.frame, text="Timeout (ms)").grid(row=2, column=0, sticky="w") self.to_var = tk.IntVar(value=int(initial_timeout_ms)) ttk.Spinbox(self.frame, from_=10, to=5000, increment=10, textvariable=self.to_var, width=10)\ .grid(row=2, column=1, sticky="w", padx=(6,0)) # Bitrate ttk.Label(self.frame, text="Bitrate").grid(row=3, column=0, sticky="w") self.br_var = tk.IntVar(value=int(initial_bitrate)) ttk.Spinbox(self.frame, from_=20000, to=1000000, increment=10000, textvariable=self.br_var, width=12)\ .grid(row=3, column=1, sticky="w", padx=(6,0)) self.set_params = tk.BooleanVar(value=True) ttk.Checkbutton(self.frame, text="Bitrate beim UP setzen", variable=self.set_params)\ .grid(row=3, column=2, sticky="w") self.kind_label = ttk.Label(self.frame, text=f"Kind: {link_kind(initial_iface)}", style="Small.TLabel") self.kind_label.grid(row=4, column=0, columnspan=3, sticky="w", pady=(4,0)) # Buttons btns = ttk.Frame(self.frame); btns.grid(row=5, column=0, columnspan=3, sticky="ew", pady=(8,0)) ttk.Button(btns, text="Link UP", command=self._do_link_up).grid(row=0, column=0, sticky="w") ttk.Button(btns, text="Link DOWN", command=self._do_link_down).grid(row=0, column=1, sticky="w", padx=(6,0)) ttk.Button(btns, text="Responder Rebind", command=self._do_rebind).grid(row=0, column=2, sticky="w", padx=(12,0)) ttk.Label(self.frame, text=f"CAP_NET_ADMIN: {'yes' if have_cap_netadmin() else 'no'}", style="Small.TLabel").grid(row=6, column=0, columnspan=3, sticky="w", pady=(6,0)) # ---- actions ---- def _refresh_ifaces(self): lst = list_can_ifaces() if not lst: messagebox.showwarning("Interfaces", "Keine can*/vcan* Interfaces gefunden.") return self.iface_dd.config(values=lst) def _do_link_up(self): iface = self.iface_var.get() try: self.kind_label.config(text=f"Kind: {link_kind(iface)}") if link_state(iface) == "UP": messagebox.showinfo("CAN", f"{iface} ist bereits UP"); return link_up(iface, bitrate=self.br_var.get(), fd=False, set_params=self.set_params.get()) try: out = subprocess.check_output(["ip","-details","-json","link","show",iface], text=True) info = json.loads(out)[0] bt = (info.get("linkinfo", {}) or {}).get("info_data", {}).get("bittiming") or {} br = bt.get("bitrate"); sp = bt.get("sample-point") if br: messagebox.showinfo("CAN", f"{iface} ist UP @ {br} bit/s (SP {sp})") except Exception: pass except Exception as e: messagebox.showerror("CAN", f"Link UP fehlgeschlagen:\n{e}") def _do_link_down(self): iface = self.iface_var.get() try: if link_state(iface) == "DOWN": messagebox.showinfo("CAN", f"{iface} ist bereits DOWN"); return link_down(iface); messagebox.showinfo("CAN", f"{iface} ist DOWN") except Exception as e: messagebox.showerror("CAN", f"Link DOWN fehlgeschlagen:\n{e}") def _do_rebind(self): iface = self.iface_var.get() try: new_resp = int(self.resp_var.get(), 16) except Exception: messagebox.showerror("RESP-ID", "Bitte gültige Hex-Zahl, z.B. 0x7E8"); return try: self.responder.rebind(interface=iface, resp_id=new_resp, timeout_ms=self.to_var.get()) self.on_rebind_iface(iface) # TraceView umhängen messagebox.showinfo("CAN", f"Responder neu gebunden: {iface}, RESP 0x{new_resp:03X}") except Exception as e: messagebox.showerror("CAN", f"Rebind fehlgeschlagen:\n{e}")