MainCode/adalm1000_logger.py aktualisiert

One Device works the it crashes
D
This commit is contained in:
Jan 2025-08-07 00:38:36 +02:00
parent c2f91999c2
commit d3d8ad2c5d

View File

@ -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.QtCore import Qt, QTimer, pyqtSignal, pyqtSlot, QObject, QThread
from PyQt5 import sip from PyQt5 import sip
import pysmu 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: class DeviceManager:
def __init__(self, dev): def __init__(self, dev):
@ -70,11 +53,31 @@ class DeviceManager:
'time': [], 'voltage': [], 'current': [], 'count': 0, 'last_plot_time': 0 '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): def start_measurement(self, interval=0.1):
if self.measurement_thread and self.measurement_thread.isRunning(): if self.measurement_thread and self.measurement_thread.isRunning():
self.measurement_thread.stop() 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.measurement_thread.start()
self.is_running = True self.is_running = True
@ -109,7 +112,7 @@ class MeasurementThread(QThread):
update_signal = pyqtSignal(float, float, float) update_signal = pyqtSignal(float, float, float)
error_signal = pyqtSignal(str) error_signal = pyqtSignal(str)
def __init__(self, device, interval=0.1): def __init__(self, device, interval, parent_manager): # Add parent_manager
super().__init__() super().__init__()
self.device = device self.device = device
self.interval = interval self.interval = interval
@ -120,47 +123,35 @@ class MeasurementThread(QThread):
self.start_time = None self.start_time = None
self.measurement_queue = Queue(maxsize=1) self.measurement_queue = Queue(maxsize=1)
self.current_direction = 1 # 1 for source, -1 for sink self.current_direction = 1 # 1 for source, -1 for sink
self.parent_manager = parent_manager
def run(self): def run(self):
"""Continuous measurement loop""" """Continuous measurement loop with enhanced error handling"""
self._running = True 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: while self._running:
try: try:
samples = self.device.read(self.filter_window_size, 500, True) samples = self.device.read(self.filter_window_size, 500, True)
# --- VOLLSTÄNDIGER DEBUG-REPLACEMENT AB HIER --- # --- Handle empty samples ---
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'}")
if not samples: if not samples:
print("DEBUG: FEHLER - Keine Samples empfangen!") consecutive_errors += 1
raise DeviceDisconnectedError("No samples received") 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
# Zeige die komplette Struktur der ersten 3 Samples # Reset error counter on successful read
for i, sample in enumerate(samples[:3]): consecutive_errors = 0
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")
# --- Process samples ---
current_time = time.time() - self.start_time current_time = time.time() - self.start_time
# Get voltage from Channel B (HI_Z mode) and current from Channel A # 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) voltage = np.mean(self.voltage_window)
current = np.mean(self.current_window) current = np.mean(self.current_window)
# Validate measurements # Validate measurements before processing
if voltage is None or not (-1.0 <= voltage <= 6.0): if not (0.0 <= voltage <= 5.0): # ADALM1000 voltage range
raise ValueError(f"Invalid voltage: {voltage}V") raise ValueError(f"Voltage out of range: {voltage:.4f}V")
if not (-0.25 <= current <= 0.25): if not (-0.25 <= current <= 0.25): # ADALM1000 current range
raise ValueError(f"Invalid current: {current}A") raise ValueError(f"Current out of range: {current:.4f}A")
# Emit update # Emit update
self.update_signal.emit(voltage, current, current_time) self.update_signal.emit(voltage, current, current_time)
@ -191,14 +182,32 @@ class MeasurementThread(QThread):
try: try:
self.measurement_queue.put_nowait((voltage, current)) self.measurement_queue.put_nowait((voltage, current))
except Full: 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)) 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: except Exception as e:
self.error_signal.emit(f"Read error: {str(e)}") self.error_signal.emit(f"Read error: {str(e)}")
consecutive_errors += 1
time.sleep(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): def set_direction(self, direction):
"""Set current direction (1 for source, -1 for sink)""" """Set current direction (1 for source, -1 for sink)"""
@ -1039,7 +1048,7 @@ class BatteryTester(QMainWindow):
self.main_layout.addWidget(self.canvas, 1) self.main_layout.addWidget(self.canvas, 1)
def init_device(self): def init_device(self):
"""Initialize all ADALM1000 with proper permission handling""" """Initialize ADALM1000 devices with proper error handling"""
try: try:
# Cleanup previous session # Cleanup previous session
if hasattr(self, 'session'): if hasattr(self, 'session'):
@ -1047,30 +1056,34 @@ class BatteryTester(QMainWindow):
self.session.end() self.session.end()
except: except:
pass 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: if not self.session.devices:
self.status_bar.showMessage("No ADALM1000 devices found") self.handle_no_devices()
self.session_active = False
self.active_device = None # Sicherstellen, dass es None ist
return return
self.session.start(0) self.session.start(0)
self.devices.clear() self.devices = {}
for dev in self.session.devices: for dev in self.session.devices:
manager = DeviceManager(dev) manager = DeviceManager(dev)
manager.start_measurement(interval=self.interval) manager.start_measurement(interval=self.interval)
self.devices[dev.serial] = manager self.devices[dev.serial] = manager
self.session.start(0) # Select first device
first_serial = next(iter(self.devices.keys()))
# Erstes Gerät aktivieren
first_serial = self.session.devices[0].serial
self.active_device = self.devices[first_serial] self.active_device = self.devices[first_serial]
# UI aktualisieren # Update UI
self.device_combo.clear() self.device_combo.clear()
for serial in self.devices: for serial in self.devices:
self.device_combo.addItem(serial) self.device_combo.addItem(serial)
@ -1081,13 +1094,26 @@ class BatteryTester(QMainWindow):
self.status_light.setStyleSheet("background-color: green; border-radius: 10px;") self.status_light.setStyleSheet("background-color: green; border-radius: 10px;")
self.start_button.setEnabled(True) 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 = self.active_device.measurement_thread
self.measurement_thread.update_signal.connect(self.update_measurements) self.measurement_thread.update_signal.connect(self.update_measurements)
self.measurement_thread.error_signal.connect(self.handle_device_error) self.measurement_thread.error_signal.connect(self.handle_device_error)
except Exception as e: 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): def request_usb_permissions(self):
"""Handle USB permission issues with user interaction""" """Handle USB permission issues with user interaction"""
@ -1219,7 +1245,9 @@ class BatteryTester(QMainWindow):
@pyqtSlot(float, float, float) @pyqtSlot(float, float, float)
def update_measurements(self, voltage, current, current_time): 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 return
dev_manager = self.active_device dev_manager = self.active_device
@ -2301,16 +2329,20 @@ class BatteryTester(QMainWindow):
self.update_plot() self.update_plot()
def update_plot(self): def update_plot(self):
"""More robust plotting with error handling""" """Fixed plot method with safe attribute access"""
try: try:
# Create local copies of data safely if not self.active_device:
with self.plot_mutex:
if not self.display_time_data:
return return
x_data = np.array(self.display_time_data) # Create local copies of data safely
y1_data = np.array(self.display_voltage_data) with self.plot_mutex:
y2_data = np.array(self.display_current_data) dev = self.active_device
if not dev.display_time_data:
return
x_data = list(dev.display_time_data)
y1_data = list(dev.display_voltage_data)
y2_data = list(dev.display_current_data)
# Update plot data # Update plot data
self.line_voltage.set_data(x_data, y1_data) self.line_voltage.set_data(x_data, y1_data)
@ -2326,7 +2358,10 @@ class BatteryTester(QMainWindow):
except Exception as e: except Exception as e:
print(f"Plot error: {e}") print(f"Plot error: {e}")
# Attempt to recover # Attempt to recover
try:
self.reset_plot() self.reset_plot()
except:
pass
def auto_scale_axes(self): def auto_scale_axes(self):
"""Auto-scale plot axes with appropriate padding and strict boundaries""" """Auto-scale plot axes with appropriate padding and strict boundaries"""
@ -2360,24 +2395,30 @@ class BatteryTester(QMainWindow):
@pyqtSlot(str) @pyqtSlot(str)
def handle_device_error(self, error_msg): def handle_device_error(self, error_msg):
"""Handle device errors gracefully""" """Enhanced error handling with recovery"""
print(f"EXAKTER FEHLER: {error_msg}") print(f"DEVICE ERROR: {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)
# Clear device-specific data ONLY if we have an active device # Special handling for USB errors
if self.active_device: if "USB" in error_msg or "LIBUSB" in error_msg.upper():
data = self.active_device error_msg += "\n\nCheck USB connection and permissions"
with self.plot_mutex: self.status_light.setStyleSheet("background-color: red;")
data.time_data.clear() else:
data.voltage_data.clear() self.status_light.setStyleSheet("background-color: orange;")
data.current_data.clear()
self.update_plot() 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) @pyqtSlot(str)
def update_test_phase(self, phase_text): def update_test_phase(self, phase_text):
@ -2434,33 +2475,86 @@ class BatteryTester(QMainWindow):
QTimer.singleShot(1000, self.reconnect_device) QTimer.singleShot(1000, self.reconnect_device)
def reconnect_device(self): def reconnect_device(self):
"""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:
if hasattr(self, 'measurement_thread'): if hasattr(self, 'measurement_thread'):
self.measurement_thread.stop() self.measurement_thread.stop()
self.measurement_thread.wait(500) if not self.measurement_thread.wait(1000): # Timeout 1s
self.measurement_thread.terminate()
except Exception as e:
print(f"Error stopping measurement thread: {e}")
# 2. Alte Session bereinigen
if hasattr(self, 'session'):
try: try:
self.session = PatchedSession(ignore_dataflow=True, queue_size=10000) self.session.end()
if not self.session.devices: except Exception as e:
raise DeviceDisconnectedError("No devices found") print(f"Error ending session: {e}")
finally:
del self.session
new_serials = {dev.serial for dev in self.session.devices} # 3. Neue Session mit Fortschrittsfeedback
old_serials = set(self.devices.keys()) retry_count = 0
max_retries = 3
reconnect_delay = 2000 # ms
# Neue hinzufügen while retry_count < max_retries:
for dev in self.session.devices: try:
if dev.serial not in self.devices: 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 = DeviceManager(dev)
manager.start_measurement(self.interval) manager.start_measurement(self.interval)
self.devices[dev.serial] = manager new_devices[serial] = manager
# Entfernte entfernen # Nicht mehr vorhandene Geräte entfernen
for serial in list(self.devices.keys()): for serial, manager in old_devices.items():
if serial not in new_serials: if serial not in current_devices:
self.devices[serial].stop_measurement() try:
del self.devices[serial] manager.stop_measurement()
except Exception as e:
print(f"Error stopping device {serial}: {e}")
# Aktualisiere UI self.devices = new_devices
current_serial = self.active_device.serial if self.active_device else None
# 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() self.device_combo.clear()
for serial in self.devices: for serial in self.devices:
self.device_combo.addItem(serial) self.device_combo.addItem(serial)
@ -2468,18 +2562,42 @@ class BatteryTester(QMainWindow):
if current_serial in self.devices: if current_serial in self.devices:
self.device_combo.setCurrentText(current_serial) self.device_combo.setCurrentText(current_serial)
self.active_device = self.devices[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: else:
first = next(iter(self.devices)) raise DeviceDisconnectedError("No valid devices available")
self.device_combo.setCurrentText(first)
self.active_device = self.devices[first]
# Signalverbindungen herstellen
if hasattr(self.active_device, 'measurement_thread'):
self.measurement_thread = 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.update_signal.connect(self.update_measurements)
self.measurement_thread.error_signal.connect(self.handle_device_error)
self.connection_label.setText(f"Reconnected: {self.active_device.serial}") # Erfolgsmeldung
self.status_bar.showMessage("Reconnected all devices") 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: except Exception as e:
QTimer.singleShot(2000, self.reconnect_device) 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): def closeEvent(self, event):
"""Clean up on window close""" """Clean up on window close"""