MainCode/adalm1000_logger.py aktualisiert
Key-Improvements:
Korrekte Session-Handling:
session.start() immer mit explizitem Device-Index
Konsistente Initialisierung bei allen Funktionen
Robustere Geräteauswahl:
Serialnummern-basierte Wiedererkennung beim Reconnect
Sichere Index-Prüfungen
Bessere Fehlerbehandlung:
Explizite Cleanup-Schritte
Zustandserhaltung bei Neustarts
Klarere Statusmeldungen:
Seriennummern in UI-Elementen
Detaillierte Fehlermeldungen
D+C
This commit is contained in:
parent
a26f6b8fe5
commit
e443408824
@ -33,13 +33,16 @@ class MeasurementThread(QThread):
|
|||||||
self.filter_window_size = 10
|
self.filter_window_size = 10
|
||||||
self.voltage_window = []
|
self.voltage_window = []
|
||||||
self.current_window = []
|
self.current_window = []
|
||||||
self.start_time = time.time()
|
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
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Continuous measurement loop"""
|
"""Continuous measurement loop"""
|
||||||
self._running = True
|
self._running = True
|
||||||
|
if self.start_time is None: # Nur setzen wenn noch nicht gesetzt
|
||||||
|
self.start_time = time.time()
|
||||||
|
|
||||||
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)
|
||||||
@ -816,7 +819,8 @@ class BatteryTester(QMainWindow):
|
|||||||
# Reset Downsampling
|
# Reset Downsampling
|
||||||
self.downsample_factor = 1
|
self.downsample_factor = 1
|
||||||
self.downsample_counter = 0
|
self.downsample_counter = 0
|
||||||
# Clear data buffers
|
|
||||||
|
# Clear all data buffers
|
||||||
with self.plot_mutex:
|
with self.plot_mutex:
|
||||||
self.time_data.clear()
|
self.time_data.clear()
|
||||||
self.voltage_data.clear()
|
self.voltage_data.clear()
|
||||||
@ -824,6 +828,26 @@ class BatteryTester(QMainWindow):
|
|||||||
if hasattr(self, 'phase_data'):
|
if hasattr(self, 'phase_data'):
|
||||||
self.phase_data.clear()
|
self.phase_data.clear()
|
||||||
|
|
||||||
|
# Also clear display buffers
|
||||||
|
if hasattr(self, 'display_time_data'):
|
||||||
|
self.display_time_data.clear()
|
||||||
|
self.display_voltage_data.clear()
|
||||||
|
self.display_current_data.clear()
|
||||||
|
|
||||||
|
# Reset aggregation buffer
|
||||||
|
self.aggregation_buffer = {
|
||||||
|
'time': [], 'voltage': [], 'current': [],
|
||||||
|
'count': 0, 'last_plot_time': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear measurement thread buffers if it exists
|
||||||
|
if hasattr(self, 'measurement_thread'):
|
||||||
|
self.measurement_thread.voltage_window.clear()
|
||||||
|
self.measurement_thread.current_window.clear()
|
||||||
|
with self.measurement_thread.measurement_queue.mutex:
|
||||||
|
self.measurement_thread.measurement_queue.queue.clear()
|
||||||
|
self.measurement_thread.start_time = time.time()
|
||||||
|
|
||||||
# Reset capacities and timing
|
# Reset capacities and timing
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
self.last_update_time = self.start_time
|
self.last_update_time = self.start_time
|
||||||
@ -847,6 +871,13 @@ class BatteryTester(QMainWindow):
|
|||||||
if self.record_button.isChecked():
|
if self.record_button.isChecked():
|
||||||
# Start recording
|
# Start recording
|
||||||
try:
|
try:
|
||||||
|
# Reset previous data
|
||||||
|
self.reset_test()
|
||||||
|
|
||||||
|
# Reset measurement timing
|
||||||
|
if hasattr(self, 'measurement_thread'):
|
||||||
|
self.measurement_thread.start_time = time.time()
|
||||||
|
|
||||||
if self.create_cycle_log_file():
|
if self.create_cycle_log_file():
|
||||||
self.record_button.setText("Stop Recording")
|
self.record_button.setText("Stop Recording")
|
||||||
self.status_bar.showMessage("Live recording started")
|
self.status_bar.showMessage("Live recording started")
|
||||||
@ -855,11 +886,11 @@ class BatteryTester(QMainWindow):
|
|||||||
self.start_live_monitoring()
|
self.start_live_monitoring()
|
||||||
else:
|
else:
|
||||||
self.record_button.setChecked(False)
|
self.record_button.setChecked(False)
|
||||||
self.current_cycle_file = None # Ensure it's None if creation failed
|
self.current_cycle_file = None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error starting recording: {e}")
|
print(f"Error starting recording: {e}")
|
||||||
self.record_button.setChecked(False)
|
self.record_button.setChecked(False)
|
||||||
self.current_cycle_file = None # Ensure it's None on error
|
self.current_cycle_file = None
|
||||||
QMessageBox.critical(self, "Error", f"Failed to start recording:\n{str(e)}")
|
QMessageBox.critical(self, "Error", f"Failed to start recording:\n{str(e)}")
|
||||||
else:
|
else:
|
||||||
# Stop recording
|
# Stop recording
|
||||||
@ -922,37 +953,54 @@ 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 the ADALM1000 device with continuous measurement"""
|
"""Initialize ADALM1000 with proper device selection and session handling"""
|
||||||
try:
|
try:
|
||||||
# Clean up any existing session
|
# Clean up existing session
|
||||||
if hasattr(self, 'session'):
|
if hasattr(self, 'session'):
|
||||||
try:
|
try:
|
||||||
self.session.end()
|
self.session.end()
|
||||||
del self.session
|
del self.session
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
print(f"Error cleaning up session: {e}")
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(0.5) # Brief pause for USB re-enumeration
|
||||||
|
|
||||||
|
# Initialize new session
|
||||||
self.session = pysmu.Session(ignore_dataflow=True, queue_size=10000)
|
self.session = pysmu.Session(ignore_dataflow=True, queue_size=10000)
|
||||||
if not self.session.devices:
|
if not self.session.devices:
|
||||||
raise Exception("No ADALM1000 detected - check connections")
|
raise DeviceDisconnectedError("No ADALM1000 devices detected")
|
||||||
|
|
||||||
|
# Populate device selector
|
||||||
|
self.device_combo.clear()
|
||||||
|
for dev in self.session.devices:
|
||||||
|
self.device_combo.addItem(dev.serial)
|
||||||
|
|
||||||
|
# Select first device by default
|
||||||
self.dev = self.session.devices[0]
|
self.dev = self.session.devices[0]
|
||||||
|
self.device_combo.setCurrentIndex(0)
|
||||||
|
|
||||||
|
# Configure channels
|
||||||
self.dev.channels['A'].mode = pysmu.Mode.HI_Z
|
self.dev.channels['A'].mode = pysmu.Mode.HI_Z
|
||||||
self.dev.channels['B'].mode = pysmu.Mode.HI_Z
|
self.dev.channels['B'].mode = pysmu.Mode.HI_Z
|
||||||
self.dev.channels['A'].constant(0)
|
self.dev.channels['A'].constant(0)
|
||||||
self.dev.channels['B'].constant(0)
|
self.dev.channels['B'].constant(0)
|
||||||
|
|
||||||
self.session.start(0)
|
# Start session for the selected device
|
||||||
|
device_index = self.session.devices.index(self.dev)
|
||||||
|
self.session.start(device_index)
|
||||||
|
|
||||||
self.status_light.setStyleSheet(f"background-color: green; border-radius: 10px;")
|
# Update UI
|
||||||
self.connection_label.setText("Connected")
|
self.status_light.setStyleSheet("background-color: green; border-radius: 10px;")
|
||||||
self.status_bar.showMessage("Device connected | Ready to measure")
|
self.connection_label.setText(f"Connected: {self.dev.serial}")
|
||||||
|
self.status_bar.showMessage(f"Ready - Device {self.dev.serial}")
|
||||||
self.session_active = True
|
self.session_active = True
|
||||||
self.start_button.setEnabled(True)
|
self.start_button.setEnabled(True)
|
||||||
|
|
||||||
# Start measurement thread
|
# Start measurement thread
|
||||||
|
if hasattr(self, 'measurement_thread'):
|
||||||
|
self.measurement_thread.stop()
|
||||||
|
self.measurement_thread.wait(500)
|
||||||
|
|
||||||
self.measurement_thread = MeasurementThread(self.dev, self.interval)
|
self.measurement_thread = MeasurementThread(self.dev, self.interval)
|
||||||
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)
|
||||||
@ -961,6 +1009,47 @@ class BatteryTester(QMainWindow):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.handle_device_error(str(e))
|
self.handle_device_error(str(e))
|
||||||
|
|
||||||
|
def change_device(self, index):
|
||||||
|
"""Safely switch to another ADALM1000 device"""
|
||||||
|
if not self.session_active or index < 0 or index >= len(self.session.devices):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Stop current operations
|
||||||
|
self.stop_test()
|
||||||
|
|
||||||
|
# Stop measurement thread
|
||||||
|
if hasattr(self, 'measurement_thread'):
|
||||||
|
self.measurement_thread.stop()
|
||||||
|
self.measurement_thread.wait(500)
|
||||||
|
|
||||||
|
# Switch to new device
|
||||||
|
self.dev = self.session.devices[index]
|
||||||
|
|
||||||
|
# Reconfigure channels
|
||||||
|
self.dev.channels['A'].mode = pysmu.Mode.HI_Z
|
||||||
|
self.dev.channels['B'].mode = pysmu.Mode.HI_Z
|
||||||
|
self.dev.channels['A'].constant(0)
|
||||||
|
self.dev.channels['B'].constant(0)
|
||||||
|
|
||||||
|
# Restart session for new device
|
||||||
|
device_index = self.session.devices.index(self.dev)
|
||||||
|
self.session.start(device_index)
|
||||||
|
|
||||||
|
# Update UI
|
||||||
|
self.device_combo.setCurrentIndex(index)
|
||||||
|
self.connection_label.setText(f"Connected: {self.dev.serial}")
|
||||||
|
self.status_bar.showMessage(f"Switched to device {self.dev.serial}")
|
||||||
|
|
||||||
|
# Restart measurement
|
||||||
|
self.measurement_thread = MeasurementThread(self.dev, self.interval)
|
||||||
|
self.measurement_thread.update_signal.connect(self.update_measurements)
|
||||||
|
self.measurement_thread.error_signal.connect(self.handle_device_error)
|
||||||
|
self.measurement_thread.start()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.handle_device_error(f"Device switch failed: {str(e)}")
|
||||||
|
|
||||||
@pyqtSlot(float, float, float)
|
@pyqtSlot(float, float, float)
|
||||||
def update_measurements(self, voltage, current, current_time):
|
def update_measurements(self, voltage, current, current_time):
|
||||||
try:
|
try:
|
||||||
@ -1085,19 +1174,29 @@ class BatteryTester(QMainWindow):
|
|||||||
|
|
||||||
def update_status(self):
|
def update_status(self):
|
||||||
"""Update status information periodically"""
|
"""Update status information periodically"""
|
||||||
|
now = time.time() # Define 'now' at the start of the method
|
||||||
|
|
||||||
if self.test_running or hasattr(self, 'record_button') and self.record_button.isChecked():
|
if self.test_running or hasattr(self, 'record_button') and self.record_button.isChecked():
|
||||||
if self.time_data:
|
if self.time_data:
|
||||||
current_time = self.time_data[-1] # Verwenden Sie den gespeicherten relativen Zeitstempel
|
current_time = self.time_data[-1]
|
||||||
if len(self.time_data) > 1:
|
if len(self.time_data) > 1:
|
||||||
delta_t = self.time_data[-1] - self.time_data[-2] # Korrekte Zeitdifferenz
|
delta_t = self.time_data[-1] - self.time_data[-2]
|
||||||
if delta_t > 0: # Nur positive Differenzen berücksichtigen
|
if delta_t > 0:
|
||||||
current_current = abs(self.current_data[-1])
|
current_current = abs(self.current_data[-1])
|
||||||
self.capacity_ah += current_current * delta_t / 3600
|
self.capacity_ah += current_current * delta_t / 3600
|
||||||
self.capacity_label.setText(f"{self.capacity_ah:.4f}")
|
self.capacity_label.setText(f"{self.capacity_ah:.4f}")
|
||||||
|
|
||||||
# Logging (1x per second)
|
# Logging (1x per second)
|
||||||
if hasattr(self, 'log_writer') and hasattr(self, 'current_cycle_file') and self.current_cycle_file is not None:
|
if (hasattr(self, 'log_writer') and
|
||||||
if self.time_data and not self.current_cycle_file.closed and (now - self._last_log_time >= 1.0):
|
hasattr(self, 'current_cycle_file') and
|
||||||
|
self.current_cycle_file is not None and
|
||||||
|
not self.current_cycle_file.closed):
|
||||||
|
|
||||||
|
# Initialize last log time if not exists
|
||||||
|
if not hasattr(self, '_last_log_time'):
|
||||||
|
self._last_log_time = now
|
||||||
|
|
||||||
|
if self.time_data and (now - self._last_log_time >= 1.0):
|
||||||
try:
|
try:
|
||||||
current_time = self.time_data[-1]
|
current_time = self.time_data[-1]
|
||||||
voltage = self.voltage_data[-1]
|
voltage = self.voltage_data[-1]
|
||||||
@ -1449,19 +1548,12 @@ class BatteryTester(QMainWindow):
|
|||||||
def start_live_monitoring(self):
|
def start_live_monitoring(self):
|
||||||
"""Start live monitoring mode"""
|
"""Start live monitoring mode"""
|
||||||
try:
|
try:
|
||||||
# Clear previous data
|
# Reset everything completely
|
||||||
with self.plot_mutex:
|
self.reset_test()
|
||||||
self.time_data.clear()
|
|
||||||
self.voltage_data.clear()
|
|
||||||
self.current_data.clear()
|
|
||||||
|
|
||||||
# Reset timing and measurements
|
# Reset measurement timing
|
||||||
if hasattr(self, 'measurement_thread'):
|
if hasattr(self, 'measurement_thread'):
|
||||||
self.measurement_thread.start_time = time.time()
|
self.measurement_thread.start_time = time.time()
|
||||||
self.start_time = time.time()
|
|
||||||
self.last_update_time = self.start_time
|
|
||||||
self.capacity_ah = 0.0
|
|
||||||
self.energy = 0.0
|
|
||||||
|
|
||||||
# Set monitoring flags
|
# Set monitoring flags
|
||||||
self.test_running = True
|
self.test_running = True
|
||||||
@ -1490,6 +1582,7 @@ class BatteryTester(QMainWindow):
|
|||||||
def create_cycle_log_file(self):
|
def create_cycle_log_file(self):
|
||||||
"""Create a new log file for the current test"""
|
"""Create a new log file for the current test"""
|
||||||
try:
|
try:
|
||||||
|
self._last_log_time = time.time()
|
||||||
# Close previous file if exists
|
# Close previous file if exists
|
||||||
if hasattr(self, 'current_cycle_file') and self.current_cycle_file:
|
if hasattr(self, 'current_cycle_file') and self.current_cycle_file:
|
||||||
try:
|
try:
|
||||||
@ -1858,35 +1951,36 @@ class BatteryTester(QMainWindow):
|
|||||||
|
|
||||||
def reset_plot(self):
|
def reset_plot(self):
|
||||||
"""Completely reset the plot - clears all data and visuals"""
|
"""Completely reset the plot - clears all data and visuals"""
|
||||||
# 1. Clear line data
|
# Clear line data
|
||||||
self.line_voltage.set_data([], [])
|
self.line_voltage.set_data([], [])
|
||||||
self.line_current.set_data([], [])
|
self.line_current.set_data([], [])
|
||||||
|
|
||||||
# 2. Clear data buffers
|
# Reset axes with appropriate ranges
|
||||||
self.time_data.clear()
|
|
||||||
self.voltage_data.clear()
|
|
||||||
self.current_data.clear()
|
|
||||||
if hasattr(self, 'phase_data'):
|
|
||||||
self.phase_data.clear()
|
|
||||||
|
|
||||||
# 3. Reset axes with appropriate ranges
|
|
||||||
voltage_padding = 0.2
|
voltage_padding = 0.2
|
||||||
min_voltage = 0
|
min_voltage = 0
|
||||||
max_voltage = 5.0 # Max voltage for ADALM1000
|
max_voltage = 5.0 # Max voltage for ADALM1000
|
||||||
|
|
||||||
self.ax.set_xlim(0, 10) # Reset X axis
|
self.ax.set_xlim(0, 10) # Reset X axis
|
||||||
self.ax.set_ylim(min_voltage, max_voltage)
|
self.ax.set_ylim(min_voltage, max_voltage)
|
||||||
|
self.ax.set_xlabel('Time (s)', color=self.fg_color)
|
||||||
|
self.ax.set_ylabel("Voltage (V)", color='#00BFFF')
|
||||||
|
self.ax.set_title('Battery Test', color=self.fg_color)
|
||||||
|
self.ax.tick_params(axis='x', colors=self.fg_color)
|
||||||
|
self.ax.tick_params(axis='y', labelcolor='#00BFFF')
|
||||||
|
self.ax.grid(True, color='#4C566A')
|
||||||
|
|
||||||
# Reset twin axis (current)
|
# Reset twin axis (current)
|
||||||
current_padding = 0.05
|
current_padding = 0.05
|
||||||
self.ax2.set_xlim(0, 10)
|
self.ax2.set_xlim(0, 10)
|
||||||
self.ax2.set_ylim(-0.25 - current_padding, 0.25 + current_padding)
|
self.ax2.set_ylim(-0.25 - current_padding, 0.25 + current_padding)
|
||||||
|
self.ax2.set_ylabel("Current (A)", color='r')
|
||||||
|
self.ax2.tick_params(axis='y', labelcolor='r')
|
||||||
|
|
||||||
# 4. Clear any matplotlib internal caches
|
# Redraw legends
|
||||||
self.fig.canvas.draw_idle()
|
self.ax.legend(loc='upper left', bbox_to_anchor=(0.01, 0.99))
|
||||||
self.fig.canvas.flush_events()
|
self.ax2.legend(loc='upper right', bbox_to_anchor=(0.99, 0.99))
|
||||||
|
|
||||||
# 5. Force immediate redraw
|
# Force immediate redraw
|
||||||
self.canvas.draw()
|
self.canvas.draw()
|
||||||
|
|
||||||
def update_status_and_plot(self):
|
def update_status_and_plot(self):
|
||||||
@ -2037,36 +2131,71 @@ class BatteryTester(QMainWindow):
|
|||||||
QTimer.singleShot(1000, self.reconnect_device)
|
QTimer.singleShot(1000, self.reconnect_device)
|
||||||
|
|
||||||
def reconnect_device(self):
|
def reconnect_device(self):
|
||||||
"""Reconnect the device with proper cleanup"""
|
"""Robust reconnection handler with device persistence"""
|
||||||
self.status_bar.showMessage("Attempting to reconnect...")
|
self.status_bar.showMessage("Reconnecting...")
|
||||||
|
|
||||||
|
# Remember current selection
|
||||||
|
current_serial = self.dev.serial if hasattr(self, 'dev') else None
|
||||||
|
|
||||||
|
# Cleanup existing connection
|
||||||
if hasattr(self, 'session'):
|
if hasattr(self, 'session'):
|
||||||
try:
|
try:
|
||||||
if self.session_active:
|
self.session.end()
|
||||||
self.session.end()
|
except Exception as e:
|
||||||
del self.session
|
print(f"Error ending session: {e}")
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.test_running = False
|
|
||||||
self.continuous_mode = False
|
|
||||||
self.measuring = False
|
|
||||||
|
|
||||||
if hasattr(self, 'measurement_thread'):
|
if hasattr(self, 'measurement_thread'):
|
||||||
self.measurement_thread.stop()
|
self.measurement_thread.stop()
|
||||||
|
self.measurement_thread.wait(500)
|
||||||
|
|
||||||
time.sleep(1.5)
|
time.sleep(1) # Allow for USB reinitialization
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.init_device()
|
# Reinitialize session
|
||||||
if self.session_active:
|
self.session = pysmu.Session(ignore_dataflow=True, queue_size=10000)
|
||||||
self.status_bar.showMessage("Reconnected successfully")
|
if not self.session.devices:
|
||||||
return
|
raise DeviceDisconnectedError("No devices available")
|
||||||
except Exception as e:
|
|
||||||
print(f"Reconnect failed: {e}")
|
|
||||||
|
|
||||||
self.status_bar.showMessage("Reconnect failed - will retry...")
|
# Repopulate device list
|
||||||
QTimer.singleShot(2000, self.reconnect_device)
|
self.device_combo.clear()
|
||||||
|
for dev in self.session.devices:
|
||||||
|
self.device_combo.addItem(dev.serial)
|
||||||
|
|
||||||
|
# Try to reselect previous device
|
||||||
|
target_index = 0
|
||||||
|
if current_serial:
|
||||||
|
for i, dev in enumerate(self.session.devices):
|
||||||
|
if dev.serial == current_serial:
|
||||||
|
target_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
self.dev = self.session.devices[target_index]
|
||||||
|
self.device_combo.setCurrentIndex(target_index)
|
||||||
|
|
||||||
|
# Configure and start
|
||||||
|
self.dev.channels['A'].mode = pysmu.Mode.HI_Z
|
||||||
|
self.dev.channels['B'].mode = pysmu.Mode.HI_Z
|
||||||
|
self.dev.channels['A'].constant(0)
|
||||||
|
self.dev.channels['B'].constant(0)
|
||||||
|
|
||||||
|
device_index = self.session.devices.index(self.dev)
|
||||||
|
self.session.start(device_index)
|
||||||
|
|
||||||
|
# Update UI
|
||||||
|
self.status_light.setStyleSheet("background-color: green; border-radius: 10px;")
|
||||||
|
self.connection_label.setText(f"Reconnected: {self.dev.serial}")
|
||||||
|
self.status_bar.showMessage(f"Device {self.dev.serial} ready")
|
||||||
|
self.session_active = True
|
||||||
|
|
||||||
|
# Restart measurement
|
||||||
|
self.measurement_thread = MeasurementThread(self.dev, self.interval)
|
||||||
|
self.measurement_thread.update_signal.connect(self.update_measurements)
|
||||||
|
self.measurement_thread.error_signal.connect(self.handle_device_error)
|
||||||
|
self.measurement_thread.start()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.status_bar.showMessage("Reconnect failed - retrying...")
|
||||||
|
QTimer.singleShot(2000, self.reconnect_device)
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
"""Clean up on window close"""
|
"""Clean up on window close"""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user