diff --git a/MainCode/adalm1000_logger.py b/MainCode/adalm1000_logger.py index f06611c..a19e608 100644 --- a/MainCode/adalm1000_logger.py +++ b/MainCode/adalm1000_logger.py @@ -33,13 +33,16 @@ class MeasurementThread(QThread): self.filter_window_size = 10 self.voltage_window = [] self.current_window = [] - self.start_time = time.time() + self.start_time = None self.measurement_queue = Queue(maxsize=1) self.current_direction = 1 # 1 for source, -1 for sink def run(self): """Continuous measurement loop""" self._running = True + if self.start_time is None: # Nur setzen wenn noch nicht gesetzt + self.start_time = time.time() + while self._running: try: samples = self.device.read(self.filter_window_size, 500, True) @@ -816,13 +819,34 @@ class BatteryTester(QMainWindow): # Reset Downsampling self.downsample_factor = 1 self.downsample_counter = 0 - # Clear data buffers + + # Clear all data buffers with self.plot_mutex: self.time_data.clear() self.voltage_data.clear() self.current_data.clear() if hasattr(self, 'phase_data'): 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 self.start_time = time.time() @@ -847,6 +871,13 @@ class BatteryTester(QMainWindow): if self.record_button.isChecked(): # Start recording 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(): self.record_button.setText("Stop Recording") self.status_bar.showMessage("Live recording started") @@ -855,11 +886,11 @@ class BatteryTester(QMainWindow): self.start_live_monitoring() else: 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: print(f"Error starting recording: {e}") 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)}") else: # Stop recording @@ -922,37 +953,54 @@ class BatteryTester(QMainWindow): self.main_layout.addWidget(self.canvas, 1) def init_device(self): - """Initialize the ADALM1000 device with continuous measurement""" + """Initialize ADALM1000 with proper device selection and session handling""" try: - # Clean up any existing session + # Clean up existing session if hasattr(self, 'session'): try: self.session.end() del self.session - except: - pass - - time.sleep(1) + except Exception as e: + print(f"Error cleaning up session: {e}") + + time.sleep(0.5) # Brief pause for USB re-enumeration + # Initialize new session self.session = pysmu.Session(ignore_dataflow=True, queue_size=10000) 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.device_combo.setCurrentIndex(0) + + # Configure 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) - - self.session.start(0) - self.status_light.setStyleSheet(f"background-color: green; border-radius: 10px;") - self.connection_label.setText("Connected") - self.status_bar.showMessage("Device connected | Ready to measure") + # Start session for the selected device + 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"Connected: {self.dev.serial}") + self.status_bar.showMessage(f"Ready - Device {self.dev.serial}") self.session_active = True self.start_button.setEnabled(True) # 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.update_signal.connect(self.update_measurements) self.measurement_thread.error_signal.connect(self.handle_device_error) @@ -961,6 +1009,47 @@ class BatteryTester(QMainWindow): except Exception as 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) def update_measurements(self, voltage, current, current_time): try: @@ -1085,19 +1174,29 @@ class BatteryTester(QMainWindow): def update_status(self): """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.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: - delta_t = self.time_data[-1] - self.time_data[-2] # Korrekte Zeitdifferenz - if delta_t > 0: # Nur positive Differenzen berücksichtigen + delta_t = self.time_data[-1] - self.time_data[-2] + if delta_t > 0: current_current = abs(self.current_data[-1]) self.capacity_ah += current_current * delta_t / 3600 self.capacity_label.setText(f"{self.capacity_ah:.4f}") # Logging (1x per second) - if hasattr(self, 'log_writer') and hasattr(self, 'current_cycle_file') and self.current_cycle_file is not None: - if self.time_data and not self.current_cycle_file.closed and (now - self._last_log_time >= 1.0): + if (hasattr(self, 'log_writer') and + 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: current_time = self.time_data[-1] voltage = self.voltage_data[-1] @@ -1449,20 +1548,13 @@ class BatteryTester(QMainWindow): def start_live_monitoring(self): """Start live monitoring mode""" try: - # Clear previous data - with self.plot_mutex: - self.time_data.clear() - self.voltage_data.clear() - self.current_data.clear() + # Reset everything completely + self.reset_test() - # Reset timing and measurements + # Reset measurement timing if hasattr(self, 'measurement_thread'): 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 self.test_running = True self.test_phase = "Live Monitoring" @@ -1490,6 +1582,7 @@ class BatteryTester(QMainWindow): def create_cycle_log_file(self): """Create a new log file for the current test""" try: + self._last_log_time = time.time() # Close previous file if exists if hasattr(self, 'current_cycle_file') and self.current_cycle_file: try: @@ -1858,35 +1951,36 @@ class BatteryTester(QMainWindow): def reset_plot(self): """Completely reset the plot - clears all data and visuals""" - # 1. Clear line data + # Clear line data self.line_voltage.set_data([], []) self.line_current.set_data([], []) - # 2. Clear data buffers - 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 + # Reset axes with appropriate ranges voltage_padding = 0.2 min_voltage = 0 max_voltage = 5.0 # Max voltage for ADALM1000 self.ax.set_xlim(0, 10) # Reset X axis 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) current_padding = 0.05 self.ax2.set_xlim(0, 10) 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 - self.fig.canvas.draw_idle() - self.fig.canvas.flush_events() + # Redraw legends + self.ax.legend(loc='upper left', bbox_to_anchor=(0.01, 0.99)) + self.ax2.legend(loc='upper right', bbox_to_anchor=(0.99, 0.99)) - # 5. Force immediate redraw + # Force immediate redraw self.canvas.draw() def update_status_and_plot(self): @@ -2037,36 +2131,71 @@ class BatteryTester(QMainWindow): QTimer.singleShot(1000, self.reconnect_device) def reconnect_device(self): - """Reconnect the device with proper cleanup""" - self.status_bar.showMessage("Attempting to reconnect...") + """Robust reconnection handler with device persistence""" + 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'): try: - if self.session_active: - self.session.end() - del self.session - except: - pass - - self.test_running = False - self.continuous_mode = False - self.measuring = False + self.session.end() + except Exception as e: + print(f"Error ending session: {e}") if hasattr(self, 'measurement_thread'): self.measurement_thread.stop() + self.measurement_thread.wait(500) - time.sleep(1.5) + time.sleep(1) # Allow for USB reinitialization try: - self.init_device() - if self.session_active: - self.status_bar.showMessage("Reconnected successfully") - return - except Exception as e: - print(f"Reconnect failed: {e}") + # Reinitialize session + self.session = pysmu.Session(ignore_dataflow=True, queue_size=10000) + if not self.session.devices: + raise DeviceDisconnectedError("No devices available") + + # Repopulate device list + 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) - self.status_bar.showMessage("Reconnect failed - will retry...") - QTimer.singleShot(2000, self.reconnect_device) + 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): """Clean up on window close"""