212 lines
8.1 KiB
Python
212 lines
8.1 KiB
Python
# ---------------------------------------------------------------------------
|
||
# Verbindungs-Panel
|
||
# ---------------------------------------------------------------------------
|
||
import serial
|
||
from serial.tools.list_ports import comports
|
||
from PyQt5.QtCore import pyqtSignal
|
||
from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QTabWidget, QWidget, QFormLayout, QComboBox, QPushButton, \
|
||
QHBoxLayout, QLineEdit, QSpinBox
|
||
|
||
from lru_store import LruStore
|
||
from transport_network import TcpTransport
|
||
from transport_serial import SerialTransport
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Stil-Konstante
|
||
# ---------------------------------------------------------------------------
|
||
FIELD_STYLE = """
|
||
QLineEdit, QComboBox, QSpinBox {
|
||
background-color: #0d1b2a;
|
||
color: #cccccc;
|
||
border: 1px solid #2a4a6a;
|
||
padding: 3px 6px;
|
||
border-radius: 3px;
|
||
}
|
||
QComboBox QAbstractItemView {
|
||
background-color: #0d1b2a;
|
||
color: #cccccc;
|
||
selection-background-color: #1a3a5c;
|
||
}
|
||
"""
|
||
|
||
class ConnectionPanel(QGroupBox):
|
||
connect_requested = pyqtSignal(object, str)
|
||
disconnect_requested = pyqtSignal()
|
||
|
||
def __init__(self, parent=None):
|
||
super().__init__("Verbindung", parent)
|
||
self.setStyleSheet("""
|
||
QGroupBox { color: #7ab7d4; border: 1px solid #2a4a6a;
|
||
border-radius: 4px; margin-top: 8px; padding: 6px; }
|
||
QGroupBox::title { subcontrol-origin: margin; left: 8px; }
|
||
""" + FIELD_STYLE)
|
||
self._lru = LruStore(max_size=5)
|
||
self._build_ui()
|
||
|
||
def _build_ui(self):
|
||
root = QVBoxLayout(self)
|
||
|
||
self._tabs = QTabWidget()
|
||
self._tabs.setStyleSheet("""
|
||
QTabBar::tab { background: #1a3a5c; color: #cccccc;
|
||
padding: 4px 12px; border-radius: 3px; }
|
||
QTabBar::tab:selected { background: #2a5a8c; color: #ffffff; }
|
||
""")
|
||
|
||
# ── Tab: Seriell ─────────────────────────────────────────────────────
|
||
serial_widget = QWidget()
|
||
serial_form = QFormLayout(serial_widget)
|
||
serial_form.setContentsMargins(6, 6, 6, 6)
|
||
|
||
self._port_combo = QComboBox()
|
||
self._port_combo.setMinimumWidth(200)
|
||
refresh_btn = QPushButton("⟳")
|
||
refresh_btn.setFixedWidth(28)
|
||
refresh_btn.clicked.connect(self._refresh_ports)
|
||
port_row = QHBoxLayout()
|
||
port_row.addWidget(self._port_combo)
|
||
port_row.addWidget(refresh_btn)
|
||
serial_form.addRow("Port:", port_row)
|
||
|
||
self._baud_combo = QComboBox()
|
||
for b in ("9600", "19200", "38400", "57600", "115200", "230400", "460800"):
|
||
self._baud_combo.addItem(b)
|
||
self._baud_combo.setCurrentText("9600")
|
||
serial_form.addRow("Baudrate:", self._baud_combo)
|
||
|
||
self._tabs.addTab(serial_widget, "🔌 Seriell")
|
||
|
||
# ── Tab: TCP/IP ───────────────────────────────────────────────────────
|
||
tcp_widget = QWidget()
|
||
tcp_layout = QVBoxLayout(tcp_widget)
|
||
tcp_layout.setContentsMargins(6, 6, 6, 6)
|
||
tcp_layout.setSpacing(6)
|
||
|
||
# LRU-Gruppe
|
||
lru_group = QGroupBox("Letzte Verbindungen")
|
||
lru_group.setStyleSheet("""
|
||
QGroupBox { color: #7ab7d4; border: 1px solid #2a4a6a;
|
||
border-radius: 3px; margin-top: 6px; padding: 4px; }
|
||
QGroupBox::title { subcontrol-origin: margin; left: 6px; }
|
||
""" + FIELD_STYLE)
|
||
lru_layout = QHBoxLayout(lru_group)
|
||
lru_layout.setContentsMargins(4, 10, 4, 4)
|
||
|
||
self._lru_combo = QComboBox()
|
||
self._lru_combo.setMinimumWidth(220)
|
||
self._lru_combo.setPlaceholderText("– Frühere Verbindung wählen –")
|
||
self._lru_combo.currentIndexChanged.connect(self._on_lru_selected)
|
||
lru_layout.addWidget(self._lru_combo, stretch=1)
|
||
|
||
clear_btn = QPushButton("🗑")
|
||
clear_btn.setFixedWidth(28)
|
||
clear_btn.setToolTip("Liste leeren")
|
||
clear_btn.clicked.connect(self._clear_lru)
|
||
lru_layout.addWidget(clear_btn)
|
||
|
||
tcp_layout.addWidget(lru_group)
|
||
|
||
# Manuelle Eingabe
|
||
tcp_form = QFormLayout()
|
||
tcp_form.setSpacing(6)
|
||
|
||
self._tcp_host = QLineEdit()
|
||
self._tcp_host.setPlaceholderText("IP-Adresse oder Hostname")
|
||
tcp_form.addRow("Host:", self._tcp_host)
|
||
|
||
self._tcp_port = QSpinBox()
|
||
self._tcp_port.setRange(1, 65535)
|
||
self._tcp_port.setValue(4001)
|
||
tcp_form.addRow("Port:", self._tcp_port)
|
||
|
||
self._tcp_timeout = QSpinBox()
|
||
self._tcp_timeout.setRange(1, 30)
|
||
self._tcp_timeout.setValue(5)
|
||
self._tcp_timeout.setSuffix(" s")
|
||
tcp_form.addRow("Timeout:", self._tcp_timeout)
|
||
|
||
tcp_layout.addLayout(tcp_form)
|
||
tcp_layout.addStretch()
|
||
|
||
self._tabs.addTab(tcp_widget, "🌐 TCP/IP")
|
||
|
||
root.addWidget(self._tabs)
|
||
|
||
# Verbinden-Knopf
|
||
btn_row = QHBoxLayout()
|
||
self._connect_btn = QPushButton("▶ Verbinden")
|
||
self._connect_btn.clicked.connect(self._on_connect_clicked)
|
||
btn_row.addStretch()
|
||
btn_row.addWidget(self._connect_btn)
|
||
root.addLayout(btn_row)
|
||
|
||
self._refresh_ports()
|
||
self._refresh_lru_combo()
|
||
|
||
# ── LRU ──────────────────────────────────────────────────────────────────
|
||
|
||
def _refresh_lru_combo(self):
|
||
self._lru_combo.blockSignals(True)
|
||
self._lru_combo.clear()
|
||
for entry in self._lru.entries():
|
||
self._lru_combo.addItem(entry)
|
||
self._lru_combo.setCurrentIndex(-1)
|
||
self._lru_combo.blockSignals(False)
|
||
|
||
def _on_lru_selected(self, index: int):
|
||
if index < 0:
|
||
return
|
||
entry = self._lru_combo.currentText()
|
||
if not entry:
|
||
return
|
||
try:
|
||
host, port = self._lru.parse(entry)
|
||
self._tcp_host.setText(host)
|
||
self._tcp_port.setValue(port)
|
||
except (ValueError, IndexError):
|
||
pass
|
||
|
||
def _clear_lru(self):
|
||
self._lru.clear()
|
||
self._refresh_lru_combo()
|
||
|
||
# ── Seriell ───────────────────────────────────────────────────────────────
|
||
|
||
def _refresh_ports(self):
|
||
self._port_combo.clear()
|
||
for p in comports():
|
||
self._port_combo.addItem(f"{p.device} – {p.description}", p.device)
|
||
if self._port_combo.count() == 0:
|
||
self._port_combo.addItem("Kein Port gefunden", None)
|
||
|
||
# ── Verbinden ─────────────────────────────────────────────────────────────
|
||
|
||
def _on_connect_clicked(self):
|
||
if self._connect_btn.text().startswith("■"):
|
||
self.disconnect_requested.emit()
|
||
return
|
||
|
||
if self._tabs.currentIndex() == 0:
|
||
port = self._port_combo.currentData()
|
||
baud = int(self._baud_combo.currentText())
|
||
if not port:
|
||
return
|
||
factory = lambda p=port, b=baud: SerialTransport(p, b)
|
||
label = f"{port} @ {baud} Baud"
|
||
else:
|
||
host = self._tcp_host.text().strip()
|
||
port = self._tcp_port.value()
|
||
timeout = self._tcp_timeout.value()
|
||
if not host:
|
||
return
|
||
self._lru.add(host, port)
|
||
self._refresh_lru_combo()
|
||
factory = lambda h=host, p=port, t=timeout: TcpTransport(h, p, t)
|
||
label = f"{host}:{port}"
|
||
|
||
self.connect_requested.emit(factory, label)
|
||
|
||
def set_connected(self, connected: bool):
|
||
self._connect_btn.setText("■ Trennen" if connected else "▶ Verbinden")
|
||
self._tabs.setEnabled(not connected)
|