MainCode/adalm1000_logger.py aktualisiert
One Device works the it crashes D
This commit is contained in:
parent
c2f91999c2
commit
d3d8ad2c5d
@ -17,23 +17,6 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QH
|
||||
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtSlot, QObject, QThread
|
||||
from PyQt5 import sip
|
||||
import pysmu
|
||||
import pysmu.libsmu
|
||||
from pysmu.exceptions import SessionError
|
||||
|
||||
class PatchedSession(pysmu.Session):
|
||||
def __init__(self, ignore_dataflow=False, queue_size=0):
|
||||
try:
|
||||
super().__init__(ignore_dataflow=ignore_dataflow, queue_size=queue_size)
|
||||
except SessionError as e:
|
||||
if "failed scanning" in str(e):
|
||||
print("Warning: Session initialization quirk, continuing...")
|
||||
else:
|
||||
raise
|
||||
finally: # Ensure scan happens regardless of exception
|
||||
self.scan() # Move scan into finally block
|
||||
|
||||
if not self.devices:
|
||||
print("No devices found after manual scan")
|
||||
|
||||
class DeviceManager:
|
||||
def __init__(self, dev):
|
||||
@ -70,11 +53,31 @@ class DeviceManager:
|
||||
'time': [], 'voltage': [], 'current': [], 'count': 0, 'last_plot_time': 0
|
||||
}
|
||||
|
||||
self.consecutive_read_errors = 0 # Track read failures
|
||||
self.max_consecutive_errors = 5 # Threshold before reset
|
||||
|
||||
def handle_read_error(self, increment=1):
|
||||
"""Handle read errors with automatic reset"""
|
||||
self.consecutive_read_errors += increment
|
||||
|
||||
if self.consecutive_read_errors >= self.max_consecutive_errors:
|
||||
try:
|
||||
print("Resetting device due to persistent errors")
|
||||
self.dev.session.end()
|
||||
time.sleep(0.5)
|
||||
self.dev.session = PatchedSession()
|
||||
self.dev.session.start(0)
|
||||
return True # Recovery attempted
|
||||
except Exception as e:
|
||||
print(f"Device reset failed: {e}")
|
||||
return False # Unrecoverable error
|
||||
return True # Under threshold - continue
|
||||
|
||||
def start_measurement(self, interval=0.1):
|
||||
if self.measurement_thread and self.measurement_thread.isRunning():
|
||||
self.measurement_thread.stop()
|
||||
|
||||
self.measurement_thread = MeasurementThread(self.dev, interval)
|
||||
|
||||
self.measurement_thread = MeasurementThread(self.dev, interval, self) # Pass self as parent_manager
|
||||
self.measurement_thread.start()
|
||||
self.is_running = True
|
||||
|
||||
@ -109,7 +112,7 @@ class MeasurementThread(QThread):
|
||||
update_signal = pyqtSignal(float, float, float)
|
||||
error_signal = pyqtSignal(str)
|
||||
|
||||
def __init__(self, device, interval=0.1):
|
||||
def __init__(self, device, interval, parent_manager): # Add parent_manager
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.interval = interval
|
||||
@ -120,47 +123,35 @@ class MeasurementThread(QThread):
|
||||
self.start_time = None
|
||||
self.measurement_queue = Queue(maxsize=1)
|
||||
self.current_direction = 1 # 1 for source, -1 for sink
|
||||
self.parent_manager = parent_manager
|
||||
|
||||
def run(self):
|
||||
"""Continuous measurement loop"""
|
||||
"""Continuous measurement loop with enhanced error handling"""
|
||||
self._running = True
|
||||
if self.start_time is None:
|
||||
self.start_time = time.time()
|
||||
self.start_time = time.time()
|
||||
consecutive_errors = 0
|
||||
max_consecutive_errors = 5
|
||||
|
||||
while self._running:
|
||||
try:
|
||||
samples = self.device.read(self.filter_window_size, 500, True)
|
||||
|
||||
# --- VOLLSTÄNDIGER DEBUG-REPLACEMENT AB HIER ---
|
||||
print(f"\n{'='*50}")
|
||||
print(f"DEBUG: Neue Messung - Zeit: {time.time():.3f}")
|
||||
print(f"DEBUG: Anzahl empfangener Samples: {len(samples) if samples else 'None'}")
|
||||
|
||||
# --- Handle empty samples ---
|
||||
if not samples:
|
||||
print("DEBUG: FEHLER - Keine Samples empfangen!")
|
||||
raise DeviceDisconnectedError("No samples received")
|
||||
|
||||
# Zeige die komplette Struktur der ersten 3 Samples
|
||||
for i, sample in enumerate(samples[:3]):
|
||||
print(f"DEBUG: Sample[{i}] = {sample}")
|
||||
if len(sample) >= 2:
|
||||
print(f" Channel A: Strom={sample[0][1]:.6f}A, Spannung={sample[0][0]:.6f}V")
|
||||
print(f" Channel B: Strom={sample[1][1]:.6f}A, Spannung={sample[1][0]:.6f}V")
|
||||
|
||||
# Berechne raw_voltage und zeige Zwischenergebnisse
|
||||
voltage_values = [s[1][0] for s in samples]
|
||||
raw_voltage = np.mean(voltage_values)
|
||||
print(f"DEBUG: Channel B Spannung - Werte: {voltage_values}, Mittelwert: {raw_voltage:.6f}V")
|
||||
|
||||
# Berechne raw_current und zeige Zwischenergebnisse
|
||||
current_values = [s[0][1] for s in samples]
|
||||
raw_current = np.mean(current_values) * self.current_direction
|
||||
print(f"DEBUG: Channel A Strom - Werte: {current_values}, Mittelwert: {np.mean(current_values):.6f}A, mit Richtung: {raw_current:.6f}A")
|
||||
print(f"{'='*50}")
|
||||
# --- DEBUG-ENDE ---
|
||||
|
||||
if not samples:
|
||||
raise DeviceDisconnectedError("No samples received")
|
||||
consecutive_errors += 1
|
||||
if consecutive_errors >= max_consecutive_errors:
|
||||
# Attempt device reset through parent manager
|
||||
if hasattr(self, 'parent_manager'):
|
||||
if not self.parent_manager.handle_read_error():
|
||||
raise DeviceDisconnectedError("Persistent read failures")
|
||||
consecutive_errors = 0 # Reset after handling
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
# Reset error counter on successful read
|
||||
consecutive_errors = 0
|
||||
|
||||
# --- Process samples ---
|
||||
current_time = time.time() - self.start_time
|
||||
|
||||
# Get voltage from Channel B (HI_Z mode) and current from Channel A
|
||||
@ -178,11 +169,11 @@ class MeasurementThread(QThread):
|
||||
voltage = np.mean(self.voltage_window)
|
||||
current = np.mean(self.current_window)
|
||||
|
||||
# Validate measurements
|
||||
if voltage is None or not (-1.0 <= voltage <= 6.0):
|
||||
raise ValueError(f"Invalid voltage: {voltage}V")
|
||||
if not (-0.25 <= current <= 0.25):
|
||||
raise ValueError(f"Invalid current: {current}A")
|
||||
# Validate measurements before processing
|
||||
if not (0.0 <= voltage <= 5.0): # ADALM1000 voltage range
|
||||
raise ValueError(f"Voltage out of range: {voltage:.4f}V")
|
||||
if not (-0.25 <= current <= 0.25): # ADALM1000 current range
|
||||
raise ValueError(f"Current out of range: {current:.4f}A")
|
||||
|
||||
# Emit update
|
||||
self.update_signal.emit(voltage, current, current_time)
|
||||
@ -191,14 +182,32 @@ class MeasurementThread(QThread):
|
||||
try:
|
||||
self.measurement_queue.put_nowait((voltage, current))
|
||||
except Full:
|
||||
pass
|
||||
pass # It's OK to skip if queue is full
|
||||
|
||||
# Adaptive sleep based on interval
|
||||
time.sleep(max(0.05, self.interval))
|
||||
|
||||
except DeviceDisconnectedError as e:
|
||||
self.error_signal.emit(f"Device disconnected: {str(e)}")
|
||||
break
|
||||
except ValueError as e:
|
||||
# Skip invalid measurements but log first occurrence
|
||||
if consecutive_errors == 0:
|
||||
self.error_signal.emit(f"Measurement error: {str(e)}")
|
||||
consecutive_errors += 1
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
self.error_signal.emit(f"Read error: {str(e)}")
|
||||
consecutive_errors += 1
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# Handle persistent errors
|
||||
if consecutive_errors >= max_consecutive_errors:
|
||||
if hasattr(self, 'parent_manager'):
|
||||
if not self.parent_manager.handle_read_error():
|
||||
self.error_signal.emit("Critical error - stopping measurement")
|
||||
break
|
||||
consecutive_errors = 0
|
||||
|
||||
def set_direction(self, direction):
|
||||
"""Set current direction (1 for source, -1 for sink)"""
|
||||
@ -1039,7 +1048,7 @@ class BatteryTester(QMainWindow):
|
||||
self.main_layout.addWidget(self.canvas, 1)
|
||||
|
||||
def init_device(self):
|
||||
"""Initialize all ADALM1000 with proper permission handling"""
|
||||
"""Initialize ADALM1000 devices with proper error handling"""
|
||||
try:
|
||||
# Cleanup previous session
|
||||
if hasattr(self, 'session'):
|
||||
@ -1047,30 +1056,34 @@ class BatteryTester(QMainWindow):
|
||||
self.session.end()
|
||||
except:
|
||||
pass
|
||||
del self.session
|
||||
|
||||
self.session = PatchedSession(ignore_dataflow=True, queue_size=10000)
|
||||
self.session = pysmu.Session(ignore_dataflow=True, queue_size=10000)
|
||||
|
||||
# Retry mechanism with progress feedback
|
||||
retry_count = 0
|
||||
while not self.session.devices and retry_count < 3:
|
||||
self.status_bar.showMessage(f"Scanning for devices... (Attempt {retry_count + 1}/3)")
|
||||
QApplication.processEvents() # Update UI
|
||||
time.sleep(1)
|
||||
self.session.scan() # Manual scan
|
||||
retry_count += 1
|
||||
|
||||
if not self.session.devices:
|
||||
self.status_bar.showMessage("No ADALM1000 devices found")
|
||||
self.session_active = False
|
||||
self.active_device = None # Sicherstellen, dass es None ist
|
||||
self.handle_no_devices()
|
||||
return
|
||||
|
||||
|
||||
self.session.start(0)
|
||||
self.devices.clear()
|
||||
self.devices = {}
|
||||
for dev in self.session.devices:
|
||||
manager = DeviceManager(dev)
|
||||
manager.start_measurement(interval=self.interval)
|
||||
self.devices[dev.serial] = manager
|
||||
|
||||
self.session.start(0)
|
||||
|
||||
# Erstes Gerät aktivieren
|
||||
first_serial = self.session.devices[0].serial
|
||||
# Select first device
|
||||
first_serial = next(iter(self.devices.keys()))
|
||||
self.active_device = self.devices[first_serial]
|
||||
|
||||
# UI aktualisieren
|
||||
# Update UI
|
||||
self.device_combo.clear()
|
||||
for serial in self.devices:
|
||||
self.device_combo.addItem(serial)
|
||||
@ -1081,13 +1094,26 @@ class BatteryTester(QMainWindow):
|
||||
self.status_light.setStyleSheet("background-color: green; border-radius: 10px;")
|
||||
self.start_button.setEnabled(True)
|
||||
|
||||
# Starte Mess-Update für aktives Gerät
|
||||
# Connect measurement signals
|
||||
self.measurement_thread = self.active_device.measurement_thread
|
||||
self.measurement_thread.update_signal.connect(self.update_measurements)
|
||||
self.measurement_thread.error_signal.connect(self.handle_device_error)
|
||||
|
||||
except Exception as e:
|
||||
self.handle_device_error(str(e))
|
||||
self.handle_device_error(f"Initialization failed: {str(e)}")
|
||||
|
||||
def handle_no_devices(self):
|
||||
"""Handle case when no devices are found"""
|
||||
self.session_active = False
|
||||
self.active_device = None
|
||||
self.status_bar.showMessage("No ADALM1000 devices found")
|
||||
self.status_light.setStyleSheet("background-color: red; border-radius: 10px;")
|
||||
self.start_button.setEnabled(False)
|
||||
self.device_combo.clear()
|
||||
|
||||
# Show reconnect button
|
||||
self.reconnect_btn.setEnabled(True)
|
||||
self.reconnect_btn.setVisible(True)
|
||||
|
||||
def request_usb_permissions(self):
|
||||
"""Handle USB permission issues with user interaction"""
|
||||
@ -1219,7 +1245,9 @@ class BatteryTester(QMainWindow):
|
||||
|
||||
@pyqtSlot(float, float, float)
|
||||
def update_measurements(self, voltage, current, current_time):
|
||||
if not self.active_device:
|
||||
# Add measurement validation
|
||||
if not self.validate_measurements(voltage, current):
|
||||
print(f"Invalid measurement: V={voltage:.4f}, I={current:.4f}")
|
||||
return
|
||||
|
||||
dev_manager = self.active_device
|
||||
@ -2301,16 +2329,20 @@ class BatteryTester(QMainWindow):
|
||||
self.update_plot()
|
||||
|
||||
def update_plot(self):
|
||||
"""More robust plotting with error handling"""
|
||||
"""Fixed plot method with safe attribute access"""
|
||||
try:
|
||||
if not self.active_device:
|
||||
return
|
||||
|
||||
# Create local copies of data safely
|
||||
with self.plot_mutex:
|
||||
if not self.display_time_data:
|
||||
dev = self.active_device
|
||||
if not dev.display_time_data:
|
||||
return
|
||||
|
||||
x_data = np.array(self.display_time_data)
|
||||
y1_data = np.array(self.display_voltage_data)
|
||||
y2_data = np.array(self.display_current_data)
|
||||
x_data = list(dev.display_time_data)
|
||||
y1_data = list(dev.display_voltage_data)
|
||||
y2_data = list(dev.display_current_data)
|
||||
|
||||
# Update plot data
|
||||
self.line_voltage.set_data(x_data, y1_data)
|
||||
@ -2326,7 +2358,10 @@ class BatteryTester(QMainWindow):
|
||||
except Exception as e:
|
||||
print(f"Plot error: {e}")
|
||||
# Attempt to recover
|
||||
self.reset_plot()
|
||||
try:
|
||||
self.reset_plot()
|
||||
except:
|
||||
pass
|
||||
|
||||
def auto_scale_axes(self):
|
||||
"""Auto-scale plot axes with appropriate padding and strict boundaries"""
|
||||
@ -2360,24 +2395,30 @@ class BatteryTester(QMainWindow):
|
||||
|
||||
@pyqtSlot(str)
|
||||
def handle_device_error(self, error_msg):
|
||||
"""Handle device errors gracefully"""
|
||||
print(f"EXAKTER FEHLER: {error_msg}")
|
||||
self.status_bar.showMessage(f"Device error: {error_msg}")
|
||||
self.status_light.setStyleSheet("background-color: red; border-radius: 10px;")
|
||||
self.session_active = False
|
||||
self.test_running = False
|
||||
self.measuring = False
|
||||
self.start_button.setEnabled(False)
|
||||
self.stop_button.setEnabled(False)
|
||||
"""Enhanced error handling with recovery"""
|
||||
print(f"DEVICE ERROR: {error_msg}")
|
||||
|
||||
# Clear device-specific data ONLY if we have an active device
|
||||
if self.active_device:
|
||||
data = self.active_device
|
||||
with self.plot_mutex:
|
||||
data.time_data.clear()
|
||||
data.voltage_data.clear()
|
||||
data.current_data.clear()
|
||||
self.update_plot()
|
||||
# Special handling for USB errors
|
||||
if "USB" in error_msg or "LIBUSB" in error_msg.upper():
|
||||
error_msg += "\n\nCheck USB connection and permissions"
|
||||
self.status_light.setStyleSheet("background-color: red;")
|
||||
else:
|
||||
self.status_light.setStyleSheet("background-color: orange;")
|
||||
|
||||
self.status_bar.showMessage(f"Device error: {error_msg}")
|
||||
|
||||
# Attempt automatic recovery for non-critical errors
|
||||
if "No samples" in error_msg or "timed out" in error_msg:
|
||||
QTimer.singleShot(1000, self.reconnect_device)
|
||||
|
||||
def validate_measurements(self, voltage, current):
|
||||
"""Filter out invalid measurements"""
|
||||
# Fix negative values caused by connection issues
|
||||
if voltage < 0 or not (0 <= voltage <= 5.0):
|
||||
return False
|
||||
if abs(current) > 0.3: # Beyond ADALM1000's ±200mA range
|
||||
return False
|
||||
return True
|
||||
|
||||
@pyqtSlot(str)
|
||||
def update_test_phase(self, phase_text):
|
||||
@ -2434,52 +2475,129 @@ class BatteryTester(QMainWindow):
|
||||
QTimer.singleShot(1000, self.reconnect_device)
|
||||
|
||||
def reconnect_device(self):
|
||||
if hasattr(self, 'measurement_thread'):
|
||||
self.measurement_thread.stop()
|
||||
self.measurement_thread.wait(500)
|
||||
"""Robuste Geräte-Neuerkennung mit vollständigem Reset und Statusfeedback"""
|
||||
self.status_bar.showMessage("Starting device reconnection...")
|
||||
self.status_light.setStyleSheet("background-color: orange; border-radius: 10px;")
|
||||
QApplication.processEvents() # Sofortiges UI-Update erzwingen
|
||||
|
||||
# 1. Vorhandene Verbindungen sauber beenden
|
||||
try:
|
||||
self.session = PatchedSession(ignore_dataflow=True, queue_size=10000)
|
||||
if not self.session.devices:
|
||||
raise DeviceDisconnectedError("No devices found")
|
||||
|
||||
new_serials = {dev.serial for dev in self.session.devices}
|
||||
old_serials = set(self.devices.keys())
|
||||
|
||||
# Neue hinzufügen
|
||||
for dev in self.session.devices:
|
||||
if dev.serial not in self.devices:
|
||||
manager = DeviceManager(dev)
|
||||
manager.start_measurement(self.interval)
|
||||
self.devices[dev.serial] = manager
|
||||
|
||||
# Entfernte entfernen
|
||||
for serial in list(self.devices.keys()):
|
||||
if serial not in new_serials:
|
||||
self.devices[serial].stop_measurement()
|
||||
del self.devices[serial]
|
||||
|
||||
# Aktualisiere UI
|
||||
current_serial = self.active_device.serial if self.active_device else None
|
||||
self.device_combo.clear()
|
||||
for serial in self.devices:
|
||||
self.device_combo.addItem(serial)
|
||||
|
||||
if current_serial in self.devices:
|
||||
self.device_combo.setCurrentText(current_serial)
|
||||
self.active_device = self.devices[current_serial]
|
||||
else:
|
||||
first = next(iter(self.devices))
|
||||
self.device_combo.setCurrentText(first)
|
||||
self.active_device = self.devices[first]
|
||||
|
||||
self.measurement_thread = self.active_device.measurement_thread
|
||||
self.measurement_thread.update_signal.connect(self.update_measurements)
|
||||
|
||||
self.connection_label.setText(f"Reconnected: {self.active_device.serial}")
|
||||
self.status_bar.showMessage("Reconnected all devices")
|
||||
if hasattr(self, 'measurement_thread'):
|
||||
self.measurement_thread.stop()
|
||||
if not self.measurement_thread.wait(1000): # Timeout 1s
|
||||
self.measurement_thread.terminate()
|
||||
except Exception as e:
|
||||
QTimer.singleShot(2000, self.reconnect_device)
|
||||
print(f"Error stopping measurement thread: {e}")
|
||||
|
||||
# 2. Alte Session bereinigen
|
||||
if hasattr(self, 'session'):
|
||||
try:
|
||||
self.session.end()
|
||||
except Exception as e:
|
||||
print(f"Error ending session: {e}")
|
||||
finally:
|
||||
del self.session
|
||||
|
||||
# 3. Neue Session mit Fortschrittsfeedback
|
||||
retry_count = 0
|
||||
max_retries = 3
|
||||
reconnect_delay = 2000 # ms
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
self.status_bar.showMessage(f"Scanning for devices (Attempt {retry_count + 1}/{max_retries})...")
|
||||
QApplication.processEvents()
|
||||
|
||||
# Neue Session erstellen
|
||||
self.session = pysmu.Session(ignore_dataflow=True, queue_size=10000)
|
||||
|
||||
# Manueller Scan mit Timeout
|
||||
scan_success = False
|
||||
for _ in range(2): # Max 2 Scan-Versuche
|
||||
self.session.scan()
|
||||
if self.session.devices:
|
||||
scan_success = True
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
if not scan_success:
|
||||
raise DeviceDisconnectedError("No devices detected")
|
||||
|
||||
# 4. Geräteliste aktualisieren
|
||||
current_devices = {dev.serial: dev for dev in self.session.devices}
|
||||
old_devices = self.devices.copy() if hasattr(self, 'devices') else {}
|
||||
|
||||
# Neue Geräte hinzufügen
|
||||
new_devices = {}
|
||||
for serial, dev in current_devices.items():
|
||||
if serial in old_devices:
|
||||
# Bestehendes Gerät wiederverwenden
|
||||
new_devices[serial] = old_devices[serial]
|
||||
else:
|
||||
# Neues Gerät initialisieren
|
||||
self.status_bar.showMessage(f"Initializing device {serial}...")
|
||||
QApplication.processEvents()
|
||||
manager = DeviceManager(dev)
|
||||
manager.start_measurement(self.interval)
|
||||
new_devices[serial] = manager
|
||||
|
||||
# Nicht mehr vorhandene Geräte entfernen
|
||||
for serial, manager in old_devices.items():
|
||||
if serial not in current_devices:
|
||||
try:
|
||||
manager.stop_measurement()
|
||||
except Exception as e:
|
||||
print(f"Error stopping device {serial}: {e}")
|
||||
|
||||
self.devices = new_devices
|
||||
|
||||
# 5. Aktives Gerät auswählen
|
||||
current_serial = self.active_device.serial if (hasattr(self, 'active_device') and self.active_device) else None
|
||||
|
||||
# UI aktualisieren
|
||||
self.device_combo.clear()
|
||||
for serial in self.devices:
|
||||
self.device_combo.addItem(serial)
|
||||
|
||||
if current_serial in self.devices:
|
||||
self.device_combo.setCurrentText(current_serial)
|
||||
self.active_device = self.devices[current_serial]
|
||||
elif self.devices:
|
||||
first_serial = next(iter(self.devices))
|
||||
self.device_combo.setCurrentText(first_serial)
|
||||
self.active_device = self.devices[first_serial]
|
||||
else:
|
||||
raise DeviceDisconnectedError("No valid devices available")
|
||||
|
||||
# Signalverbindungen herstellen
|
||||
if hasattr(self.active_device, 'measurement_thread'):
|
||||
self.measurement_thread = self.active_device.measurement_thread
|
||||
self.measurement_thread.update_signal.connect(self.update_measurements)
|
||||
self.measurement_thread.error_signal.connect(self.handle_device_error)
|
||||
|
||||
# Erfolgsmeldung
|
||||
self.connection_label.setText(f"Connected: {self.active_device.serial}")
|
||||
self.status_bar.showMessage("Successfully reconnected devices")
|
||||
self.status_light.setStyleSheet("background-color: green; border-radius: 10px;")
|
||||
self.start_button.setEnabled(True)
|
||||
return
|
||||
|
||||
except DeviceDisconnectedError as e:
|
||||
retry_count += 1
|
||||
if retry_count < max_retries:
|
||||
self.status_bar.showMessage(f"Reconnect failed: {e}. Retrying in {reconnect_delay/1000}s...")
|
||||
QApplication.processEvents()
|
||||
time.sleep(reconnect_delay/1000)
|
||||
else:
|
||||
self.status_bar.showMessage(f"Reconnect failed after {max_retries} attempts")
|
||||
self.status_light.setStyleSheet("background-color: red; border-radius: 10px;")
|
||||
QTimer.singleShot(reconnect_delay, self.attempt_reconnect)
|
||||
except Exception as e:
|
||||
print(f"Critical reconnect error: {traceback.format_exc()}")
|
||||
self.status_bar.showMessage(f"Critical error: {str(e)}")
|
||||
self.status_light.setStyleSheet("background-color: red; border-radius: 10px;")
|
||||
QTimer.singleShot(reconnect_delay, self.attempt_reconnect)
|
||||
return
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Clean up on window close"""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user