diff --git a/MainCode/adalm1000_logger.py b/MainCode/adalm1000_logger.py index 801be9b..2fa3d24 100644 --- a/MainCode/adalm1000_logger.py +++ b/MainCode/adalm1000_logger.py @@ -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"""