first session run correctly

main
Kynsight 1 month ago
parent a520d8f17f
commit 0277eec76a

@ -177,6 +177,13 @@ class ConfigureInterfaceWidget(QWidget):
container = QWidget()
layout = QVBoxLayout()
# Enable Logger checkbox
self.logger_enable = QCheckBox("Enable UART Logger")
self.logger_enable.setChecked(True) # Default enabled
self.logger_enable.setToolTip("Uncheck to skip logger port configuration (command port only)")
self.logger_enable.stateChanged.connect(self._on_logger_enable_changed)
layout.addWidget(self.logger_enable)
uart_group = QGroupBox("UART Logger Settings")
form = QFormLayout()
@ -349,7 +356,7 @@ class ConfigureInterfaceWidget(QWidget):
SELECT profile_name,
uart_command_port, uart_command_baud, uart_command_data_bits,
uart_command_stop_bits, uart_command_parity, uart_command_timeout_ms,
uart_logger_port, uart_logger_baud, uart_logger_data_bits,
uart_logger_enable, uart_logger_port, uart_logger_baud, uart_logger_data_bits,
uart_logger_stop_bits, uart_logger_parity, uart_logger_timeout_ms,
uart_logger_packet_detect_enable, uart_logger_packet_detect_start,
uart_logger_packet_detect_length, uart_logger_packet_detect_end,
@ -369,25 +376,29 @@ class ConfigureInterfaceWidget(QWidget):
self.cmd_parity.setCurrentText(row[5] or "N")
self.cmd_timeout.setValue(row[6] or 1000)
# UART Logger Enable
logger_enabled = bool(row[7]) if row[7] is not None else True # Default enabled
self.logger_enable.setChecked(logger_enabled)
# UART Logger
self._set_combo_value(self.log_port, row[7])
self.log_baud.setCurrentText(str(row[8] or 115200))
self.log_data_bits.setValue(row[9] or 8)
self.log_stop_bits.setValue(row[10] or 1)
self.log_parity.setCurrentText(row[11] or "N")
self.log_timeout.setValue(row[12] or 1000)
self._set_combo_value(self.log_port, row[8])
self.log_baud.setCurrentText(str(row[9] or 115200))
self.log_data_bits.setValue(row[10] or 8)
self.log_stop_bits.setValue(row[11] or 1)
self.log_parity.setCurrentText(row[12] or "N")
self.log_timeout.setValue(row[13] or 1000)
# Packet detection
self.packet_enable.setChecked(bool(row[13]))
self.packet_start.setText(row[14] or "")
self.packet_length.setValue(row[15] or 17)
self.packet_end.setText(row[16] or "")
self.packet_enable.setChecked(bool(row[14]))
self.packet_start.setText(row[15] or "")
self.packet_length.setValue(row[16] or 17)
self.packet_end.setText(row[17] or "")
# I2C
self.i2c_bus.setCurrentText(row[17] or "")
self.i2c_address.setText(row[18] or "")
self.i2c_register.setText(row[19] or "")
self.i2c_length.setValue(row[20] or 2)
self.i2c_bus.setCurrentText(row[18] or "")
self.i2c_address.setText(row[19] or "")
self.i2c_register.setText(row[20] or "")
self.i2c_length.setValue(row[21] or 2)
self.current_profile_id = profile_id
self.btn_delete.setEnabled(True)
@ -458,7 +469,11 @@ class ConfigureInterfaceWidget(QWidget):
QMessageBox.warning(self, "Error", "Profile name required")
return
# Validate: UART Command and UART Logger must use different ports
# Get logger enable state
logger_enabled = self.logger_enable.isChecked()
# Validate: UART Command and UART Logger must use different ports (only if logger enabled)
if logger_enabled:
cmd_port = self.cmd_port.currentData() or self.cmd_port.currentText()
log_port = self.log_port.currentData() or self.log_port.currentText()
@ -481,6 +496,7 @@ class ConfigureInterfaceWidget(QWidget):
self.cmd_stop_bits.value(),
self.cmd_parity.currentText(),
self.cmd_timeout.value(),
int(logger_enabled), # uart_logger_enable
self.log_port.currentData() or self.log_port.currentText(),
int(self.log_baud.currentText()),
self.log_data_bits.value(),
@ -502,7 +518,7 @@ class ConfigureInterfaceWidget(QWidget):
UPDATE interface_profiles SET
profile_name=?, uart_command_port=?, uart_command_baud=?,
uart_command_data_bits=?, uart_command_stop_bits=?, uart_command_parity=?,
uart_command_timeout_ms=?, uart_logger_port=?, uart_logger_baud=?,
uart_command_timeout_ms=?, uart_logger_enable=?, uart_logger_port=?, uart_logger_baud=?,
uart_logger_data_bits=?, uart_logger_stop_bits=?, uart_logger_parity=?,
uart_logger_timeout_ms=?, uart_logger_packet_detect_enable=?,
uart_logger_packet_detect_start=?, uart_logger_packet_detect_length=?,
@ -517,13 +533,13 @@ class ConfigureInterfaceWidget(QWidget):
INSERT INTO interface_profiles (
profile_name, uart_command_port, uart_command_baud,
uart_command_data_bits, uart_command_stop_bits, uart_command_parity,
uart_command_timeout_ms, uart_logger_port, uart_logger_baud,
uart_command_timeout_ms, uart_logger_enable, uart_logger_port, uart_logger_baud,
uart_logger_data_bits, uart_logger_stop_bits, uart_logger_parity,
uart_logger_timeout_ms, uart_logger_packet_detect_enable,
uart_logger_packet_detect_start, uart_logger_packet_detect_length,
uart_logger_packet_detect_end, i2c_port, i2c_slave_address,
i2c_slave_read_register, i2c_slave_read_length
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", values)
self.current_profile_id = cursor.lastrowid
msg = "Profile created"
@ -536,6 +552,26 @@ class ConfigureInterfaceWidget(QWidget):
except Exception as e:
QMessageBox.critical(self, "Error", f"Save failed: {str(e)}")
def _on_logger_enable_changed(self, state):
"""Handle logger enable checkbox change."""
enabled = (state == Qt.CheckState.Checked.value)
# Enable/disable all logger fields
self.log_port.setEnabled(enabled)
self.log_baud.setEnabled(enabled)
self.log_data_bits.setEnabled(enabled)
self.log_stop_bits.setEnabled(enabled)
self.log_parity.setEnabled(enabled)
self.log_timeout.setEnabled(enabled)
# Enable/disable packet detection
self.packet_enable.setEnabled(enabled)
self.packet_start.setEnabled(enabled)
self.packet_length.setEnabled(enabled)
self.packet_end.setEnabled(enabled)
self._enable_save()
def _enable_save(self):
"""Enable save button."""
self.btn_save.setEnabled(True)

@ -8,7 +8,7 @@ Visual command sequence builder.
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel,
QLineEdit, QPushButton, QListWidget, QListWidgetItem,
QMessageBox, QSplitter, QFormLayout, QSpinBox, QComboBox
QMessageBox, QSplitter, QFormLayout, QSpinBox, QComboBox, QCheckBox
)
from PyQt6.QtCore import Qt, pyqtSignal
import json
@ -84,6 +84,15 @@ class ConfigureSessionWidget(QWidget):
form.addRow("Profile Name:", self.name_input)
self.desc_input = QLineEdit()
form.addRow("Description:", self.desc_input)
# Print RX checkbox
self.print_rx_checkbox = QCheckBox("Print Command RX (disable packet detection & DB saving)")
self.print_rx_checkbox.setToolTip(
"Enable: Print raw RX buffer to data monitor, skip database saving\n"
"Disable: Normal packet detection and database logging"
)
form.addRow("", self.print_rx_checkbox)
layout.addLayout(form)
# Add command section
@ -170,7 +179,7 @@ class ConfigureSessionWidget(QWidget):
def _load_profile_details(self, profile_id: int):
"""Load profile into editor."""
cursor = self.db_conn.execute(
"SELECT profile_name, description, command_sequence FROM session_profiles WHERE profile_id = ?",
"SELECT profile_name, description, command_sequence, print_command_rx FROM session_profiles WHERE profile_id = ?",
(profile_id,)
)
row = cursor.fetchone()
@ -178,6 +187,10 @@ class ConfigureSessionWidget(QWidget):
self.name_input.setText(row[0] or "")
self.desc_input.setText(row[1] or "")
# Load print_command_rx checkbox
print_rx = bool(row[3]) if len(row) > 3 and row[3] is not None else False
self.print_rx_checkbox.setChecked(print_rx)
# Parse JSON and populate sequence
try:
json_obj = json.loads(row[2] or "{}")
@ -267,6 +280,7 @@ class ConfigureSessionWidget(QWidget):
"""Handle add button."""
self.name_input.clear()
self.desc_input.clear()
self.print_rx_checkbox.setChecked(False) # Default to disabled
self.command_sequence = []
self._update_sequence_list()
@ -320,6 +334,7 @@ class ConfigureSessionWidget(QWidget):
return
desc = self.desc_input.text().strip()
print_rx = 1 if self.print_rx_checkbox.isChecked() else 0
# Build JSON
json_obj = {
@ -334,15 +349,15 @@ class ConfigureSessionWidget(QWidget):
if self.current_profile_id:
self.db_conn.execute(
"UPDATE session_profiles SET profile_name=?, description=?, "
"command_sequence=?, last_modified=datetime('now') WHERE profile_id=?",
(name, desc, json_text, self.current_profile_id)
"command_sequence=?, print_command_rx=?, last_modified=datetime('now') WHERE profile_id=?",
(name, desc, json_text, print_rx, self.current_profile_id)
)
msg = "Profile updated"
else:
cursor = self.db_conn.execute(
"INSERT INTO session_profiles (profile_name, description, command_sequence) "
"VALUES (?, ?, ?)",
(name, desc, json_text)
"INSERT INTO session_profiles (profile_name, description, command_sequence, print_command_rx) "
"VALUES (?, ?, ?, ?)",
(name, desc, json_text, print_rx)
)
self.current_profile_id = cursor.lastrowid
msg = "Profile created"

Binary file not shown.

@ -86,6 +86,7 @@ CREATE TABLE IF NOT EXISTS "interface_profiles" (
"uart_command_timeout_ms" INTEGER,
-- UART Logger Interface
"uart_logger_enable" BOOLEAN DEFAULT 1,
"uart_logger_mode" TEXT,
"uart_logger_port" TEXT,
"uart_logger_baud" INTEGER,
@ -116,6 +117,7 @@ CREATE TABLE IF NOT EXISTS "session_profiles" (
"profile_id" INTEGER PRIMARY KEY AUTOINCREMENT,
"profile_name" TEXT UNIQUE NOT NULL,
"description" TEXT,
"print_command_rx BOOLEAN DEFAULT 0;
"command_sequence" TEXT,
"created_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
"last_modified" TIMESTAMP

@ -76,7 +76,8 @@ class RunExecutor:
run_no: int,
command_id: int,
command_hex: str,
uart_port: UARTPort,
uart_command_port: UARTPort,
uart_logger_port: Optional[UARTPort],
i2c_port: Optional[I2CHandle],
packet_config: PacketConfig,
stop_timeout_ms: int = 5000,
@ -91,7 +92,8 @@ class RunExecutor:
run_no: Run number (1, 2, 3, ...)
command_id: UART command ID from database
command_hex: Command hex string (e.g., "DD 22 50 48...")
uart_port: UART port (already open and reader running)
uart_command_port: UART command port (TX/RX for commands)
uart_logger_port: UART logger port (RX for telemetry, None if disabled)
i2c_port: I2C port (optional, for angle readings)
packet_config: Packet detection configuration
stop_timeout_ms: Maximum wait time for stop condition
@ -103,17 +105,19 @@ class RunExecutor:
- error_msg: Error message if status="error", empty otherwise
"""
try:
# Clear previous packets
uart_clear_detected_packets(uart_port)
# Clear previous packets (only if logger port exists)
if uart_logger_port:
uart_clear_detected_packets(uart_logger_port)
self.i2c_readings.clear()
# Record run start time
run_start_ns = time.time_ns()
# ================================================================
# 1. Configure packet detection with callback
# 1. Configure packet detection with callback (LOGGER PORT)
# ================================================================
if uart_logger_port and packet_config.enable:
# Create callback for I2C triggering
def on_uart_packet_detected(timestamp_ns: int):
"""
@ -147,27 +151,29 @@ class RunExecutor:
on_packet_callback=on_uart_packet_detected if i2c_port else None
)
# Start listening with packet detection
status = uart_start_listening_with_packets(uart_port, packet_config_with_callback)
# Start listening with packet detection on LOGGER PORT
status = uart_start_listening_with_packets(uart_logger_port, packet_config_with_callback)
if status != UARTStatus.OK:
return ("error", 0, "Failed to start UART packet detection")
# ================================================================
# 2. Send UART command
# 2. Send UART command (COMMAND PORT)
# ================================================================
# Parse hex string to bytes
command_bytes = self._parse_hex_string(command_hex)
if not command_bytes:
uart_stop_listening(uart_port)
if uart_logger_port:
uart_stop_listening(uart_logger_port)
return ("error", 0, f"Invalid command hex string: {command_hex}")
# Send command
status, written = uart_write(uart_port, command_bytes)
# Send command via COMMAND PORT
status, written = uart_write(uart_command_port, command_bytes)
if status != UARTStatus.OK:
uart_stop_listening(uart_port)
if uart_logger_port:
uart_stop_listening(uart_logger_port)
return ("error", 0, "Failed to send UART command")
# Emit TX data (command sent)
@ -187,8 +193,8 @@ class RunExecutor:
# ================================================================
if not packet_config.enable:
# No packet detection - read raw buffer
status_read, raw_data = uart_read_buffer(uart_port)
# No packet detection - read raw buffer from COMMAND PORT (ACK/response)
status_read, raw_data = uart_read_buffer(uart_command_port)
if status_read == UARTStatus.OK and raw_data:
# Emit RX data
@ -196,14 +202,19 @@ class RunExecutor:
hex_rx = ' '.join(f'{b:02X}' for b in raw_data)
raw_data_callback("RX", hex_rx)
# Stop listening
uart_stop_listening(uart_port)
# Stop listening on logger port (if active)
if uart_logger_port:
uart_stop_listening(uart_logger_port)
# ================================================================
# 4. Get detected packets
# 4. Get detected packets (from LOGGER PORT if exists)
# ================================================================
uart_packets = uart_get_detected_packets(uart_port)
uart_packets = []
if uart_logger_port:
uart_packets = uart_get_detected_packets(uart_logger_port)
else:
uart_packets = []
packet_count = len(uart_packets)
if packet_count == 0 and packet_config.enable:
@ -243,9 +254,10 @@ class RunExecutor:
return ("success", packet_count, "")
except Exception as e:
# Stop listening if still active
# Stop listening if still active (logger port)
try:
uart_stop_listening(uart_port)
if uart_logger_port:
uart_stop_listening(uart_logger_port)
except:
pass
@ -395,7 +407,8 @@ def execute_run(
run_no: int,
command_id: int,
command_hex: str,
uart_port: UARTPort,
uart_command_port: UARTPort,
uart_logger_port: Optional[UARTPort],
i2c_port: Optional[I2CHandle],
packet_config: PacketConfig,
stop_timeout_ms: int = 5000,
@ -411,7 +424,8 @@ def execute_run(
run_no: Run number
command_id: UART command ID
command_hex: Command hex string
uart_port: UART port (open and ready)
uart_command_port: UART command port (TX/RX for commands)
uart_logger_port: UART logger port (RX for telemetry, optional)
i2c_port: I2C port (optional)
packet_config: Packet detection configuration
stop_timeout_ms: Stop condition timeout
@ -427,7 +441,8 @@ def execute_run(
run_no=run_no,
command_id=command_id,
command_hex=command_hex,
uart_port=uart_port,
uart_command_port=uart_command_port,
uart_logger_port=uart_logger_port,
i2c_port=i2c_port,
packet_config=packet_config,
stop_timeout_ms=stop_timeout_ms,

@ -131,7 +131,8 @@ class Session(QObject):
self.session_profile_id: Optional[int] = None
# Hardware ports (managed by this class)
self.uart_port: Optional[UARTPort] = None
self.uart_command_port: Optional[UARTPort] = None # For sending commands (TX/RX)
self.uart_logger_port: Optional[UARTPort] = None # For telemetry packets (RX only, optional)
self.i2c_handle: Optional[I2CHandle] = None
self.packet_config: Optional[PacketConfig] = None
@ -184,7 +185,7 @@ class Session(QObject):
profile_name,
uart_command_port, uart_command_baud, uart_command_data_bits,
uart_command_stop_bits, uart_command_parity, uart_command_timeout_ms,
uart_logger_port, uart_logger_baud, uart_logger_data_bits,
uart_logger_enable, uart_logger_port, uart_logger_baud, uart_logger_data_bits,
uart_logger_stop_bits, uart_logger_parity, uart_logger_timeout_ms,
uart_logger_packet_detect_enable, uart_logger_packet_detect_start,
uart_logger_packet_detect_length, uart_logger_packet_detect_end,
@ -210,22 +211,23 @@ class Session(QObject):
'uart_command_parity': row[5],
'uart_command_timeout_ms': row[6],
# UART Logger interface
'uart_logger_port': row[7],
'uart_logger_baud': row[8],
'uart_logger_data_bits': row[9],
'uart_logger_stop_bits': row[10],
'uart_logger_parity': row[11],
'uart_logger_timeout_ms': row[12],
'uart_logger_enable': bool(row[7]) if row[7] is not None else True,
'uart_logger_port': row[8],
'uart_logger_baud': row[9],
'uart_logger_data_bits': row[10],
'uart_logger_stop_bits': row[11],
'uart_logger_parity': row[12],
'uart_logger_timeout_ms': row[13],
# Packet detection
'packet_detect_enable': row[13],
'packet_detect_start': row[14],
'packet_detect_length': row[15],
'packet_detect_end': row[16],
'packet_detect_enable': row[14],
'packet_detect_start': row[15],
'packet_detect_length': row[16],
'packet_detect_end': row[17],
# I2C configuration
'i2c_port': row[17],
'i2c_slave_address': row[18],
'i2c_slave_read_register': row[19],
'i2c_slave_read_length': row[20]
'i2c_port': row[18],
'i2c_slave_address': row[19],
'i2c_slave_read_register': row[20],
'i2c_slave_read_length': row[21]
}
# ===================================================================
@ -233,7 +235,7 @@ class Session(QObject):
# ===================================================================
cursor = self.db_conn.execute("""
SELECT profile_name, command_sequence, description
SELECT profile_name, command_sequence, description, print_command_rx
FROM session_profiles
WHERE profile_id = ?
""", (session_profile_id,))
@ -246,6 +248,7 @@ class Session(QObject):
self.session_profile_id = session_profile_id
profile_name = row[0]
command_sequence_json = row[1]
self.print_command_rx = bool(row[3]) if len(row) > 3 else False
# Parse JSON command sequence
try:
@ -319,8 +322,8 @@ class Session(QObject):
Open UART and I2C ports based on interface configuration.
This method:
1. Opens UART logger port (for packet detection)
2. Starts UART reader thread
1. Opens UART Command port (TX/RX - for sending commands, always needed)
2. Opens UART Logger port (RX - for telemetry, optional)
3. Opens I2C port (for angle readings)
4. Creates PacketConfig from interface profile
@ -329,44 +332,104 @@ class Session(QObject):
"""
try:
# ===================================================================
# 1. Open UART Logger Port (for packet detection)
# 1. Open UART Command Port (TX/RX - ALWAYS NEEDED)
# ===================================================================
# Create UART config (only device and baudrate are required)
uart_config = UARTConfig(
# Create UART command config
cmd_uart_config = UARTConfig(
device=self.interface_config['uart_command_port'],
baudrate=self.interface_config['uart_command_baud']
)
# Create UART command port
status, self.uart_command_port = uart_create(cmd_uart_config)
if status != UARTStatus.OK or self.uart_command_port is None:
self.error_occurred.emit(f"Failed to create UART command port")
return False
# Open UART command port
status = uart_open(self.uart_command_port)
if status != UARTStatus.OK:
self.error_occurred.emit(f"Failed to open UART command port {cmd_uart_config.device}")
return False
# Start UART command reader thread
status = uart_start_reader(self.uart_command_port)
if status != UARTStatus.OK:
uart_close(self.uart_command_port)
self.uart_command_port = None
self.error_occurred.emit("Failed to start UART command reader")
return False
self.status_changed.emit(f"UART Command opened: {cmd_uart_config.device}")
# ===================================================================
# 2. Open UART Logger Port (RX - OPTIONAL)
# ===================================================================
if self.interface_config['uart_logger_enable']:
# Logger enabled - open logger port
# Create UART logger config
log_uart_config = UARTConfig(
device=self.interface_config['uart_logger_port'],
baudrate=self.interface_config['uart_logger_baud']
)
# Create UART port
status, self.uart_port = uart_create(uart_config)
# Create UART logger port
status, self.uart_logger_port = uart_create(log_uart_config)
if status != UARTStatus.OK or self.uart_port is None:
self.error_occurred.emit(f"Failed to create UART port")
if status != UARTStatus.OK or self.uart_logger_port is None:
self.error_occurred.emit(f"Failed to create UART logger port")
# Close command port before returning
uart_stop_reader(self.uart_command_port)
uart_close(self.uart_command_port)
self.uart_command_port = None
return False
# Open UART port
status = uart_open(self.uart_port)
# Open UART logger port
status = uart_open(self.uart_logger_port)
if status != UARTStatus.OK:
self.error_occurred.emit(f"Failed to open UART port {uart_config.device}")
self.error_occurred.emit(f"Failed to open UART logger port {log_uart_config.device}")
# Close command port before returning
uart_stop_reader(self.uart_command_port)
uart_close(self.uart_command_port)
self.uart_command_port = None
return False
# Start UART reader thread
status = uart_start_reader(self.uart_port)
# Start UART logger reader thread
status = uart_start_reader(self.uart_logger_port)
if status != UARTStatus.OK:
uart_close(self.uart_port)
self.uart_port = None
self.error_occurred.emit("Failed to start UART reader")
uart_close(self.uart_logger_port)
self.uart_logger_port = None
# Close command port before returning
uart_stop_reader(self.uart_command_port)
uart_close(self.uart_command_port)
self.uart_command_port = None
self.error_occurred.emit("Failed to start UART logger reader")
return False
self.status_changed.emit(f"UART opened: {uart_config.device}")
self.status_changed.emit(f"UART Logger opened: {log_uart_config.device}")
else:
# Logger disabled - skip logger port
self.uart_logger_port = None
self.status_changed.emit("UART Logger disabled (command port only)")
# ===================================================================
# 2. Create PacketConfig from interface profile
# 3. Create PacketConfig from interface profile
# ===================================================================
if self.interface_config['packet_detect_enable']:
# Check if print_command_rx is enabled (session profile override)
if hasattr(self, 'print_command_rx') and self.print_command_rx:
# Force packet detection OFF - just print TX/RX to data monitor
self.packet_config = PacketConfig(enable=False)
elif not self.interface_config['uart_logger_enable']:
# Logger disabled - no packet detection
self.packet_config = PacketConfig(enable=False)
elif self.interface_config['packet_detect_enable']:
# Parse hex markers
start_marker = bytes.fromhex(
self.interface_config['packet_detect_start'].replace(' ', '')
@ -387,7 +450,7 @@ class Session(QObject):
self.packet_config = PacketConfig(enable=False)
# ===================================================================
# 3. Open I2C Port (optional - for angle readings)
# 4. Open I2C Port (optional - for angle readings)
# ===================================================================
if self.interface_config['i2c_port']:
@ -444,7 +507,11 @@ class Session(QObject):
# 2. Create PacketConfig from interface profile
# ===================================================================
if self.interface_config['packet_detect_enable']:
# Check if print_command_rx is enabled (session profile override)
if hasattr(self, 'print_command_rx') and self.print_command_rx:
# Force packet detection OFF - just print TX/RX to data monitor
self.packet_config = PacketConfig(enable=False)
elif self.interface_config['packet_detect_enable']:
# Parse hex markers
start_marker = bytes.fromhex(
self.interface_config['packet_detect_start'].replace(' ', '')
@ -509,12 +576,19 @@ class Session(QObject):
Ensures clean shutdown of hardware interfaces.
"""
try:
# Close UART
if self.uart_port:
uart_stop_reader(self.uart_port)
uart_close(self.uart_port)
self.uart_port = None
self.status_changed.emit("UART closed")
# Close UART Command port
if self.uart_command_port:
uart_stop_reader(self.uart_command_port)
uart_close(self.uart_command_port)
self.uart_command_port = None
self.status_changed.emit("UART Command closed")
# Close UART Logger port
if self.uart_logger_port:
uart_stop_reader(self.uart_logger_port)
uart_close(self.uart_logger_port)
self.uart_logger_port = None
self.status_changed.emit("UART Logger closed")
# Close I2C
if self.i2c_handle:
@ -659,7 +733,8 @@ class Session(QObject):
run_no=cmd_index,
command_id=cmd['command_id'],
command_hex=cmd['hex_string'],
uart_port=self.uart_port,
uart_command_port=self.uart_command_port,
uart_logger_port=self.uart_logger_port,
i2c_port=self.i2c_handle, # Note: run.py calls it i2c_port but it's actually I2CHandle
packet_config=self.packet_config,
stop_timeout_ms=5000,

@ -33,6 +33,7 @@ from PyQt6.QtCore import Qt, pyqtSlot
from PyQt6.QtGui import QFont
from session import Session
from session_worker import SessionWorker
from database.init_database import DatabaseManager
@ -93,6 +94,9 @@ class SessionWidget(QWidget):
# Create session object
self.session = Session(db_manager)
# Worker thread for non-blocking execution
self.worker = None
# Connect signals from session to widget slots
self._connect_signals()
@ -332,20 +336,20 @@ class SessionWidget(QWidget):
self.session_profile_combo.setEnabled(False)
self.session_name_input.setEnabled(False)
# Start session
success = self.session.start_session()
# Create worker thread for non-blocking execution
# Pass database path for thread-local connection
self.worker = SessionWorker(self.session, self.db_manager.db_path)
if success:
# Enable pause/stop buttons
# Connect worker signals
self.worker.finished.connect(self._on_worker_finished)
# Start worker thread
self.worker.start()
# Enable pause/stop buttons immediately
# (worker is running, session will start soon)
self.pause_button.setEnabled(True)
self.stop_button.setEnabled(True)
else:
# Re-enable start button on failure
self.start_button.setEnabled(True)
self.load_button.setEnabled(True)
self.interface_profile_combo.setEnabled(True)
self.session_profile_combo.setEnabled(True)
self.session_name_input.setEnabled(True)
def _on_pause_clicked(self):
"""Handle pause button click."""
@ -354,14 +358,54 @@ class SessionWidget(QWidget):
def _on_resume_clicked(self):
"""Handle resume button click."""
self.session.resume_session()
# Button states will be updated by signal handlers
# Disable resume button
self.resume_button.setEnabled(False)
# Create worker thread for resume
# Pass database path for thread-local connection
self.worker = SessionWorker(self.session, self.db_manager.db_path, resume_mode=True)
# Connect worker signals
self.worker.finished.connect(self._on_worker_finished)
# Start worker thread
self.worker.start()
# Enable pause/stop buttons
self.pause_button.setEnabled(True)
self.stop_button.setEnabled(True)
def _on_stop_clicked(self):
"""Handle stop button click."""
self.session.stop_session()
# Button states will be updated by signal handlers
@pyqtSlot(bool)
def _on_worker_finished(self, success: bool):
"""
Handle worker thread finished.
Called when session execution completes (success or failure).
Re-enables UI controls for next session.
"""
# Cleanup worker
if self.worker:
self.worker = None
# Re-enable controls
self.start_button.setEnabled(True)
self.load_button.setEnabled(True)
self.interface_profile_combo.setEnabled(True)
self.session_profile_combo.setEnabled(True)
self.session_name_input.setEnabled(True)
# Disable pause/stop buttons
self.pause_button.setEnabled(False)
self.stop_button.setEnabled(False)
if not success:
self._log_error("Session execution failed")
# =========================================================================
# SIGNAL HANDLERS (SLOTS)
# =========================================================================

@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""
Session Worker Thread - vzug-e-hinge
====================================
QThread worker for non-blocking session execution.
This worker runs session.start_session() or session.resume_session()
in a background thread, preventing GUI freezing and enabling
real-time status updates.
IMPORTANT: Creates thread-local database connection to avoid SQLite
threading errors.
Author: Kynsight
Version: 1.1.0
Date: 2025-11-09
"""
from PyQt6.QtCore import QThread, pyqtSignal
import sqlite3
class SessionWorker(QThread):
"""
Worker thread for session execution.
Runs session.start_session() or resume_session() in background
to prevent GUI blocking. Emits finished signal when session completes.
CRITICAL: Creates a new database connection in the worker thread
to avoid SQLite's "objects created in a thread can only be used
in that same thread" error.
"""
# Signals
finished = pyqtSignal(bool) # success/failure
def __init__(self, session, db_path, resume_mode=False):
"""
Initialize worker.
Args:
session: Session instance to execute
db_path: Path to database file (for thread-local connection)
resume_mode: True to resume, False to start (default)
"""
super().__init__()
self.session = session
self.db_path = db_path
self.resume_mode = resume_mode
def run(self):
"""
Execute session in background thread.
This method runs in a separate thread, keeping the GUI responsive.
Creates a thread-local database connection to avoid SQLite errors.
All session signals (status_changed, command_started, etc.) are
automatically thread-safe thanks to Qt's signal/slot mechanism.
"""
try:
# Create thread-local database connection
thread_db_conn = sqlite3.connect(self.db_path)
# Replace session's database connection with thread-local one
old_conn = self.session.db_conn
self.session.db_conn = thread_db_conn
try:
if self.resume_mode:
# Resume paused session
self.session.resume_session()
success = True
else:
# Start new session
success = self.session.start_session()
# Emit result
self.finished.emit(success)
finally:
# Close thread-local connection
thread_db_conn.close()
# Restore original connection
self.session.db_conn = old_conn
except Exception as e:
print(f"[ERROR] Worker exception: {e}")
self.finished.emit(False)

Binary file not shown.
Loading…
Cancel
Save