366 lines
14 KiB
Python
366 lines
14 KiB
Python
# target_simulator/gui/connection_settings_window.py
|
|
"""
|
|
Toplevel window for configuring Target and LRU connections.
|
|
"""
|
|
import tkinter as tk
|
|
from tkinter import ttk, messagebox
|
|
|
|
|
|
class ConnectionSettingsWindow(tk.Toplevel):
|
|
"""A dialog for configuring connection settings."""
|
|
|
|
def __init__(self, master, config_manager, connection_config):
|
|
super().__init__(master)
|
|
self.master_view = master
|
|
self.config_manager = config_manager
|
|
self.connection_config = connection_config
|
|
|
|
self.title("Connection Settings")
|
|
self.transient(master)
|
|
self.grab_set()
|
|
self.resizable(False, False)
|
|
|
|
self._create_widgets()
|
|
self._load_settings()
|
|
self.protocol("WM_DELETE_WINDOW", self._on_cancel)
|
|
|
|
# Center the window on the main view
|
|
self.update_idletasks()
|
|
main_x = master.winfo_rootx()
|
|
main_y = master.winfo_rooty()
|
|
main_w = master.winfo_width()
|
|
main_h = master.winfo_height()
|
|
# Use requested size so the dialog is large enough to show all widgets.
|
|
# Some test doubles or minimal masters may not implement winfo_reqwidth/
|
|
# winfo_reqheight, so fall back to winfo_width/height when necessary.
|
|
try:
|
|
win_w = self.winfo_reqwidth()
|
|
win_h = self.winfo_reqheight()
|
|
except Exception:
|
|
win_w = self.winfo_width()
|
|
win_h = self.winfo_height()
|
|
x = main_x + (main_w // 2) - (win_w // 2)
|
|
y = main_y + (main_h // 2) - (win_h // 2)
|
|
self.geometry(f"{win_w}x{win_h}+{x}+{y}")
|
|
|
|
def _load_settings(self):
|
|
# --- Load Target Settings ---
|
|
target_cfg = self.connection_config.get("target", {})
|
|
target_sfp_cfg = target_cfg.get("sfp", {})
|
|
# Normalize connection type so it matches the radiobutton values used elsewhere
|
|
t = (target_cfg.get("type", "SFP") or "").lower()
|
|
if t == "serial":
|
|
self.target_vars["conn_type"].set("Serial")
|
|
else:
|
|
self.target_vars["conn_type"].set(t.upper() or "SFP")
|
|
self.target_vars["tftp_ip"].set(
|
|
target_cfg.get("tftp", {}).get("ip", "127.0.0.1")
|
|
)
|
|
self.target_vars["tftp_port"].set(target_cfg.get("tftp", {}).get("port", 69))
|
|
self.target_vars["serial_port"].set(
|
|
target_cfg.get("serial", {}).get("port", "COM1")
|
|
)
|
|
self.target_vars["serial_baud"].set(
|
|
target_cfg.get("serial", {}).get("baudrate", 9600)
|
|
)
|
|
self.target_vars["sfp_ip"].set(target_sfp_cfg.get("ip", "127.0.0.1"))
|
|
self.target_vars["sfp_port"].set(target_sfp_cfg.get("port", 60003))
|
|
self.target_vars["sfp_local_port"].set(target_sfp_cfg.get("local_port", 60002))
|
|
self.target_vars["sfp_use_json"].set(
|
|
target_sfp_cfg.get("use_json_protocol", False)
|
|
)
|
|
|
|
self.target_vars["sfp_prediction_offset"].set(
|
|
target_sfp_cfg.get("prediction_offset_ms", 0.0)
|
|
)
|
|
|
|
# Select the correct notebook tab for target
|
|
try:
|
|
tab_idx = {"SFP": 0, "TFTP": 1, "Serial": 2}[
|
|
self.target_vars["conn_type"].get()
|
|
]
|
|
self.target_vars["notebook"].select(tab_idx)
|
|
except Exception:
|
|
pass
|
|
|
|
# --- Load LRU Settings ---
|
|
lru_cfg = self.connection_config.get("lru", {})
|
|
lru_sfp_cfg = lru_cfg.get("sfp", {})
|
|
t = (lru_cfg.get("type", "SFP") or "").lower()
|
|
if t == "serial":
|
|
self.lru_vars["conn_type"].set("Serial")
|
|
else:
|
|
self.lru_vars["conn_type"].set(t.upper() or "SFP")
|
|
self.lru_vars["tftp_ip"].set(lru_cfg.get("tftp", {}).get("ip", "127.0.0.1"))
|
|
self.lru_vars["tftp_port"].set(lru_cfg.get("tftp", {}).get("port", 69))
|
|
self.lru_vars["serial_port"].set(lru_cfg.get("serial", {}).get("port", "COM1"))
|
|
self.lru_vars["serial_baud"].set(
|
|
lru_cfg.get("serial", {}).get("baudrate", 9600)
|
|
)
|
|
self.lru_vars["sfp_ip"].set(lru_sfp_cfg.get("ip", "127.0.0.1"))
|
|
self.lru_vars["sfp_port"].set(lru_sfp_cfg.get("port", 60003))
|
|
self.lru_vars["sfp_local_port"].set(lru_sfp_cfg.get("local_port", 60002))
|
|
self.lru_vars["sfp_use_json"].set(lru_sfp_cfg.get("use_json_protocol", False))
|
|
|
|
self.lru_vars["sfp_prediction_offset"].set(
|
|
lru_sfp_cfg.get("prediction_offset_ms", 0.0)
|
|
)
|
|
|
|
# Select the correct notebook tab for lru
|
|
try:
|
|
tab_idx = {"SFP": 0, "TFTP": 1, "Serial": 2}[
|
|
self.lru_vars["conn_type"].get()
|
|
]
|
|
self.lru_vars["notebook"].select(tab_idx)
|
|
except Exception:
|
|
pass
|
|
|
|
def _create_widgets(self):
|
|
# Main content frame (scrolling not required here but keep expandable)
|
|
main_frame = ttk.Frame(self, padding="10")
|
|
main_frame.pack(fill=tk.BOTH, expand=True)
|
|
|
|
target_frame = ttk.LabelFrame(main_frame, text="Target Connection")
|
|
target_frame.pack(fill=tk.X, expand=True, pady=5, padx=5)
|
|
self.target_vars = self._create_connection_panel(target_frame)
|
|
|
|
lru_frame = ttk.LabelFrame(main_frame, text="LRU Connection")
|
|
lru_frame.pack(fill=tk.X, expand=True, pady=5, padx=5)
|
|
self.lru_vars = self._create_connection_panel(lru_frame)
|
|
|
|
# Keep action buttons fixed at the bottom of the dialog so they are
|
|
# not pushed out of view by variable-height content in the main_frame.
|
|
sep = ttk.Separator(self, orient=tk.HORIZONTAL)
|
|
sep.pack(fill=tk.X, side=tk.BOTTOM, pady=(0, 5))
|
|
|
|
button_frame = ttk.Frame(self)
|
|
button_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=8, padx=10)
|
|
ttk.Button(button_frame, text="Save", command=self._on_save).pack(
|
|
side=tk.RIGHT, padx=5
|
|
)
|
|
ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(
|
|
side=tk.RIGHT, padx=5
|
|
)
|
|
|
|
def _create_connection_panel(self, parent_frame):
|
|
vars = {}
|
|
|
|
# Top row: label + combobox to choose connection type
|
|
type_row = ttk.Frame(parent_frame)
|
|
type_row.pack(fill=tk.X, padx=5, pady=(5, 10))
|
|
|
|
vars["conn_type"] = tk.StringVar()
|
|
ttk.Label(type_row, text="Type:").pack(side=tk.LEFT)
|
|
type_combo = ttk.Combobox(
|
|
type_row,
|
|
textvariable=vars["conn_type"],
|
|
state="readonly",
|
|
values=["SFP", "TFTP", "Serial"],
|
|
width=10,
|
|
)
|
|
type_combo.pack(side=tk.LEFT, padx=6)
|
|
|
|
# Notebook with a tab per connection type
|
|
notebook = ttk.Notebook(parent_frame)
|
|
notebook.pack(fill=tk.X, expand=False, padx=5, pady=2)
|
|
vars["notebook"] = notebook
|
|
|
|
# Frames for each tab
|
|
sfp_frame = ttk.Frame(notebook, padding=5)
|
|
tftp_frame = ttk.Frame(notebook, padding=5)
|
|
serial_frame = ttk.Frame(notebook, padding=5)
|
|
|
|
notebook.add(sfp_frame, text="SFP")
|
|
notebook.add(tftp_frame, text="TFTP")
|
|
notebook.add(serial_frame, text="Serial")
|
|
|
|
def _on_tab_changed(event):
|
|
idx = notebook.index(notebook.select())
|
|
vars["conn_type"].set({0: "SFP", 1: "TFTP", 2: "Serial"}.get(idx, "SFP"))
|
|
|
|
def _on_type_changed(event=None):
|
|
name = vars["conn_type"].get()
|
|
idx = {"SFP": 0, "TFTP": 1, "Serial": 2}.get(name, 0)
|
|
notebook.select(idx)
|
|
|
|
notebook.bind("<<NotebookTabChanged>>", _on_tab_changed)
|
|
type_combo.bind("<<ComboboxSelected>>", _on_type_changed)
|
|
|
|
# --- TFTP Widgets ---
|
|
tftp_grid = tftp_frame
|
|
tftp_grid.columnconfigure(1, weight=1)
|
|
ttk.Label(tftp_grid, text="IP Address:").grid(
|
|
row=0, column=0, sticky=tk.W, pady=2
|
|
)
|
|
vars["tftp_ip"] = tk.StringVar()
|
|
ttk.Entry(tftp_grid, textvariable=vars["tftp_ip"]).grid(
|
|
row=0, column=1, sticky=tk.EW, padx=5
|
|
)
|
|
ttk.Label(tftp_grid, text="Port:").grid(row=1, column=0, sticky=tk.W, pady=2)
|
|
vars["tftp_port"] = tk.IntVar()
|
|
ttk.Spinbox(
|
|
tftp_grid, from_=1, to=65535, textvariable=vars["tftp_port"], width=7
|
|
).grid(row=1, column=1, sticky=tk.W, padx=5)
|
|
|
|
# --- Serial Widgets ---
|
|
serial_grid = serial_frame
|
|
serial_grid.columnconfigure(1, weight=1)
|
|
ttk.Label(serial_grid, text="COM Port:").grid(
|
|
row=0, column=0, sticky=tk.W, pady=2
|
|
)
|
|
vars["serial_port"] = tk.StringVar()
|
|
ttk.Combobox(
|
|
serial_grid,
|
|
textvariable=vars["serial_port"],
|
|
values=[f"COM{i}" for i in range(1, 17)],
|
|
).grid(row=0, column=1, sticky=tk.EW, padx=5)
|
|
ttk.Label(serial_grid, text="Baud Rate:").grid(
|
|
row=1, column=0, sticky=tk.W, pady=2
|
|
)
|
|
vars["serial_baud"] = tk.IntVar()
|
|
ttk.Combobox(
|
|
serial_grid,
|
|
textvariable=vars["serial_baud"],
|
|
values=[9600, 19200, 38400, 57600, 115200],
|
|
width=7,
|
|
).grid(row=1, column=1, sticky=tk.W, padx=5)
|
|
|
|
# --- SFP Widgets ---
|
|
sfp_grid = sfp_frame
|
|
sfp_grid.columnconfigure(1, weight=1)
|
|
ttk.Label(sfp_grid, text="Server IP:").grid(
|
|
row=0, column=0, sticky=tk.W, pady=2
|
|
)
|
|
vars["sfp_ip"] = tk.StringVar()
|
|
ttk.Entry(sfp_grid, textvariable=vars["sfp_ip"]).grid(
|
|
row=0, column=1, sticky=tk.EW, padx=5
|
|
)
|
|
ttk.Label(sfp_grid, text="Server Port:").grid(
|
|
row=1, column=0, sticky=tk.W, pady=2
|
|
)
|
|
vars["sfp_port"] = tk.IntVar()
|
|
ttk.Spinbox(
|
|
sfp_grid, from_=1, to=65535, textvariable=vars["sfp_port"], width=7
|
|
).grid(row=1, column=1, sticky=tk.W, padx=5)
|
|
ttk.Label(sfp_grid, text="Local Port:").grid(
|
|
row=2, column=0, sticky=tk.W, pady=2
|
|
)
|
|
vars["sfp_local_port"] = tk.IntVar()
|
|
ttk.Spinbox(
|
|
sfp_grid, from_=1, to=65535, textvariable=vars["sfp_local_port"], width=7
|
|
).grid(row=2, column=1, sticky=tk.W, padx=5)
|
|
|
|
# Prediction Offset widget on row 3
|
|
ttk.Label(sfp_grid, text="Prediction Offset (ms):").grid(
|
|
row=3, column=0, sticky=tk.W, pady=2
|
|
)
|
|
vars["sfp_prediction_offset"] = tk.DoubleVar()
|
|
ttk.Spinbox(
|
|
sfp_grid, from_=0, to=1000, increment=1, textvariable=vars["sfp_prediction_offset"], width=7
|
|
).grid(row=3, column=1, sticky=tk.W, padx=5)
|
|
|
|
# JSON protocol checkbox moved to row 4
|
|
vars["sfp_use_json"] = tk.BooleanVar()
|
|
ttk.Checkbutton(
|
|
sfp_grid, text="Use JSON Protocol", variable=vars["sfp_use_json"]
|
|
).grid(row=4, column=0, columnspan=2, sticky=tk.W, pady=(5, 0))
|
|
|
|
# place test button to the right of the notebook area
|
|
test_row = ttk.Frame(parent_frame)
|
|
test_row.pack(fill=tk.X, padx=5, pady=(2, 8))
|
|
test_button = ttk.Button(
|
|
test_row,
|
|
text="Test Connection",
|
|
command=lambda: self._test_connection(vars),
|
|
)
|
|
test_button.pack(side=tk.RIGHT)
|
|
|
|
return vars
|
|
|
|
# Note: previous implementation used pack/grid switching per type. The UI
|
|
# now uses a Notebook with tabs and a Combobox to select the active type.
|
|
|
|
def _test_connection(self, vars):
|
|
conn_type = vars["conn_type"].get()
|
|
result = False
|
|
config = {}
|
|
|
|
try:
|
|
if conn_type == "TFTP":
|
|
from target_simulator.core.tftp_communicator import TFTPCommunicator
|
|
|
|
config = {"ip": vars["tftp_ip"].get(), "port": vars["tftp_port"].get()}
|
|
result = TFTPCommunicator.test_connection(config)
|
|
elif conn_type == "Serial":
|
|
from target_simulator.core.serial_communicator import SerialCommunicator
|
|
|
|
config = {
|
|
"port": vars["serial_port"].get(),
|
|
"baudrate": vars["serial_baud"].get(),
|
|
}
|
|
result = SerialCommunicator.test_connection(config)
|
|
elif conn_type == "SFP":
|
|
from target_simulator.core.sfp_communicator import SFPCommunicator
|
|
|
|
config = {"local_port": vars["sfp_local_port"].get()}
|
|
result = SFPCommunicator.test_connection(config)
|
|
except Exception as e:
|
|
messagebox.showerror("Test Error", f"An error occurred: {e}", parent=self)
|
|
return
|
|
|
|
if result:
|
|
messagebox.showinfo(
|
|
"Connection Test",
|
|
f"{conn_type} connection test successful!",
|
|
parent=self,
|
|
)
|
|
else:
|
|
messagebox.showerror(
|
|
"Connection Test", f"{conn_type} connection test failed.", parent=self
|
|
)
|
|
|
|
def _on_save(self):
|
|
new_config = {
|
|
"target": {
|
|
"type": self.target_vars["conn_type"].get().lower(),
|
|
"tftp": {
|
|
"ip": self.target_vars["tftp_ip"].get(),
|
|
"port": self.target_vars["tftp_port"].get(),
|
|
},
|
|
"serial": {
|
|
"port": self.target_vars["serial_port"].get(),
|
|
"baudrate": self.target_vars["serial_baud"].get(),
|
|
},
|
|
"sfp": {
|
|
"ip": self.target_vars["sfp_ip"].get(),
|
|
"port": self.target_vars["sfp_port"].get(),
|
|
"local_port": self.target_vars["sfp_local_port"].get(),
|
|
"use_json_protocol": self.target_vars["sfp_use_json"].get(),
|
|
"prediction_offset_ms": self.target_vars["sfp_prediction_offset"].get(),
|
|
},
|
|
},
|
|
"lru": {
|
|
"type": self.lru_vars["conn_type"].get().lower(),
|
|
"tftp": {
|
|
"ip": self.lru_vars["tftp_ip"].get(),
|
|
"port": self.lru_vars["tftp_port"].get(),
|
|
},
|
|
"serial": {
|
|
"port": self.lru_vars["serial_port"].get(),
|
|
"baudrate": self.lru_vars["serial_baud"].get(),
|
|
},
|
|
"sfp": {
|
|
"ip": self.lru_vars["sfp_ip"].get(),
|
|
"port": self.lru_vars["sfp_port"].get(),
|
|
"local_port": self.lru_vars["sfp_local_port"].get(),
|
|
"use_json_protocol": self.lru_vars["sfp_use_json"].get(),
|
|
"prediction_offset_ms": self.lru_vars["sfp_prediction_offset"].get(),
|
|
},
|
|
},
|
|
}
|
|
self.master_view.update_connection_settings(new_config)
|
|
self.destroy()
|
|
|
|
def _on_cancel(self):
|
|
self.destroy()
|