You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
8.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# components/uart/uart_logger_ui.py
# Minimal UART Logger UI (no commands/table) — connect and stream logs.
import config.config as config
from PyQt6.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QComboBox,
QSplitter,
QLabel,
)
from PyQt6.QtCore import Qt, QTimer
from PyQt6.QtCore import QObject, pyqtSignal
# project-local deps
from components.console.console_ui import console_widget
from components.console.console_registry import log_main_console
import components.items.elements as elements
from components.uart.uart_logic import UART_logic
from components.scheduler.coordinator import (
uart_capture_active,
uart_capture_remaining_ms,
)
class _SafeConsoleProxy(QObject):
log_signal = pyqtSignal(str, str) # level, message
def __init__(self, console):
super().__init__()
self.log_signal.connect(console.log)
def __call__(self, level, msg):
self.log_signal.emit(level, msg)
class UartLoggerWidget(QWidget):
"""
Super-lean UART logger:
- Port + serial params
- Connect / Disconnect
- Auto-start continuous logger on connect
- One console
- Capture indicator (global coordinator)
"""
def __init__(self, parent=None):
super().__init__(parent)
self.uart_logic = UART_logic()
self.comboboxes = {}
self.connection_status = False
# console + have uart_logic log into it
self.console = console_widget()
# IMPORTANT: keep a strong reference so it isnt GCd
self._uart_console_proxy = _SafeConsoleProxy(self.console)
self.uart_logic.set_logger(self._uart_console_proxy)
self.init_ui()
self._wire_signals()
self._setup_capture_guard()
self.disconnected_enable_status()
# ---------------- UI ----------------
def init_ui(self):
# === Top Control Row ===
top_controls = QWidget()
top_controls_layout = QHBoxLayout(top_controls)
top_controls_layout.setContentsMargins(0, 0, 0, 0)
top_controls_layout.setSpacing(12)
top_controls_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
# Port + refresh
self.comboboxes["port"] = QComboBox()
self.comboboxes["port"].addItems(self.uart_logic.get_channels())
top_controls_layout.addWidget(
elements.label_and_widget("Port", self.comboboxes["port"])
)
self.button_refresh = elements.create_icon_button(
config.REFRESH_BUTTON_ICON_LINK, icon_size=30, border_size=4
)
top_controls_layout.addWidget(self.button_refresh)
# Serial params
self.comboboxes["baudrate"] = QComboBox()
self.comboboxes["baudrate"].addItems(self.uart_logic.get_baud_rates())
top_controls_layout.addWidget(
elements.label_and_widget("Baudrate", self.comboboxes["baudrate"])
)
self.comboboxes["data_bits"] = QComboBox()
self.comboboxes["data_bits"].addItems(self.uart_logic.get_data_bits())
top_controls_layout.addWidget(
elements.label_and_widget("Data Bits", self.comboboxes["data_bits"])
)
self.comboboxes["stop_bits"] = QComboBox()
self.comboboxes["stop_bits"].addItems(self.uart_logic.get_stop_bits())
top_controls_layout.addWidget(
elements.label_and_widget("Stop Bits", self.comboboxes["stop_bits"])
)
self.comboboxes["parity"] = QComboBox()
self.comboboxes["parity"].addItems(self.uart_logic.get_parity())
top_controls_layout.addWidget(
elements.label_and_widget("Parity", self.comboboxes["parity"])
)
# Connect / Disconnect
self.button_connect = QPushButton("Connect")
top_controls_layout.addWidget(
elements.label_and_widget("", self.button_connect)
)
self.button_disconnect = QPushButton("Disconnect")
top_controls_layout.addWidget(
elements.label_and_widget("", self.button_disconnect)
)
# Capture status label
self.label_capture = QLabel("Capture: idle")
top_controls_layout.addWidget(
elements.label_and_widget("Status", self.label_capture)
)
# === Console only ===
console_stack_widget = QWidget()
console_stack_layout = QVBoxLayout(console_stack_widget)
console_stack_layout.setContentsMargins(0, 0, 0, 0)
console_stack_layout.setSpacing(4)
console_stack_layout.addWidget(self.console)
# === Splitter (kept for consistency, only console pane) ===
splitter = QSplitter(Qt.Orientation.Horizontal)
splitter.addWidget(console_stack_widget)
splitter.setStretchFactor(0, 1)
# === Main Layout ===
main_layout = QVBoxLayout()
main_layout.setContentsMargins(4, 4, 4, 4)
main_layout.setSpacing(6)
main_layout.addWidget(top_controls)
main_layout.addWidget(splitter, stretch=1)
self.setLayout(main_layout)
def _wire_signals(self):
self.button_refresh.clicked.connect(self.refresh)
self.button_connect.clicked.connect(self.connect)
self.button_disconnect.clicked.connect(self.disconnect)
# ---------- Capture guard: label updates ----------
def _setup_capture_guard(self):
self._guard_timer = QTimer(self)
self._guard_timer.setInterval(50) # 20 Hz
self._guard_timer.timeout.connect(self._update_capture_label)
self._guard_timer.start()
def _update_capture_label(self):
if uart_capture_active():
rem = uart_capture_remaining_ms()
self.label_capture.setText(f"Capture: listening… {rem} ms")
else:
self.label_capture.setText("Capture: idle")
# ---- UI state toggles ----
def disconnected_enable_status(self):
for combo in self.comboboxes.values():
if combo.property("inversedGray"):
elements.set_enabled_state(False, combo, grayOut=True)
else:
elements.set_enabled_state(True, combo, grayOut=False)
elements.set_enabled_state(False, self.button_disconnect, grayOut=True)
elements.set_enabled_state(True, self.button_connect, grayOut=False)
def connected_enable_status(self):
for combo in self.comboboxes.values():
if combo.property("inversedGray"):
elements.set_enabled_state(True, combo, grayOut=False)
else:
elements.set_enabled_state(False, combo, grayOut=True)
elements.set_enabled_state(True, self.button_disconnect, grayOut=False)
elements.set_enabled_state(False, self.button_connect, grayOut=True)
# ---- Actions ----
def connect(self):
log_main_console("info", "🔗 Connecting (logger)…")
port = self.comboboxes["port"].currentText()
baudrate = int(self.comboboxes["baudrate"].currentText())
data_bits = int(self.comboboxes["data_bits"].currentText())
stop_bits = float(self.comboboxes["stop_bits"].currentText())
parity_label = self.comboboxes["parity"].currentText() # "Even", "None", "Odd"
parity_short = parity_label[0].upper() # E/N/O for your uart_logic
ok = self.uart_logic.connect(
port=port,
baudrate=baudrate,
data_bits=data_bits,
stop_bits=stop_bits,
parity=parity_short,
)
if ok:
# auto-start continuous logger (hex output)
self.uart_logic.start_logger(hex_output=True)
self.connected_enable_status()
self.connection_status = True
else:
elements.flash_button(
self.button_connect, flash_style="background-color: red;"
)
def disconnect(self):
log_main_console("info", "🔌 Disconnecting (logger)…")
if self.uart_logic.disconnect():
self.disconnected_enable_status()
self.connection_status = False
self.refresh(silent=True)
self.uart_logic.stop_logger()
else:
elements.flash_button(
self.button_disconnect, flash_style="background-color: red;"
)
def refresh(self, silent: bool = False):
log_main_console("info", "🔄 Refreshing ports…")
self.comboboxes["port"].clear()
ports = self.uart_logic.get_channels()
if ports:
self.comboboxes["port"].addItems(ports)
if not silent:
elements.flash_button(self.button_refresh)
log_main_console("success", "✅ Ports refreshed.")
else:
elements.flash_button(
self.button_refresh, flash_style="background-color: red;"
)
log_main_console("warning", "⚠️ No UART ports found.")
# helpful for diagnostics
def get_current_config(self):
return {key: cb.currentText() for key, cb in self.comboboxes.items()}