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 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"""