diff --git a/MainCode/adalm1000_logger.py b/MainCode/adalm1000_logger.py index 02c0978..2301995 100644 --- a/MainCode/adalm1000_logger.py +++ b/MainCode/adalm1000_logger.py @@ -77,12 +77,16 @@ class DeviceManager: self.test_phase = "Idle" # Add these new attributes for recording state + self.start_time = time.time() # Initialize with current time + self.last_update_time = self.start_time self.is_recording = False self.log_file = None self.log_writer = None self._last_log_time = 0 self.log_dir = os.path.expanduser("~/adalm1000/logs") os.makedirs(self.log_dir, exist_ok=True) + self.start_time = None + self.last_measurement_time = None def reset_data(self): """Reset all data buffers and statistics for the device""" @@ -237,38 +241,55 @@ class MeasurementThread(QThread): self.wait(500) def run(self): + logger.info(f"MeasurementThread STARTED for {self.parent_manager.serial}") if not self.dev_manager.dev: + logger.error("No device available") self.error_signal.emit("Device not initialized") return - logger.info(f"MeasurementThread for {self.parent_manager.serial} starting") + + # Initialize timing + self.dev_manager.start_time = time.time() + while self._running: try: - # Get measurement under lock - voltage, current, ts = self.dev_manager.safe_call(self._do_measure_once) - self.measurement_queue.put((self.parent_manager.serial, voltage, current, ts)) - self.update_signal.emit(self.parent_manager.serial, voltage, current, ts) - except DeviceDisconnectedError: - logger.error(f"Device {self.parent_manager.serial} disconnected during measurement") - self.error_signal.emit(f"Device {self.parent_manager.serial} disconnected") - # Attempt reconnect - try: - logger.info("Attempting to reconnect...") - ok = self.dev_manager.reopen_with_backoff() - if ok: - logger.info(f"Reconnected to {self.parent_manager.serial}") - self.error_signal.emit(f"Reconnected to {self.parent_manager.serial}") - else: - logger.error(f"Permanent failure for {self.parent_manager.serial}") - self.error_signal.emit(f"Permanent failure for {self.parent_manager.serial}") - break - except Exception as e: - logger.exception(f"Reconnect failed: {e}") - time.sleep(2.0) + start_time = time.time() + + # Get measurement + samples = self.dev_manager.dev.get_samples(1) + if not samples: + time.sleep(0.01) + continue + + sample = samples[0] + voltage = sample[0][0] + current = sample[0][1] + current_time = time.time() + + # Validate before sending + if not (0 <= voltage <= 5.0) or not (-0.2 <= current <= 0.2): + continue + + # Emit update + self.update_signal.emit( + self.parent_manager.serial, + voltage, + current, + current_time + ) + + # Maintain precise timing + elapsed = time.time() - start_time + sleep_time = max(0.001, self.interval - elapsed) + time.sleep(sleep_time) + + except (USBError, pysmu.exceptions.USBError) as e: + logger.error(f"USB Error: {str(e)}") + self.error_signal.emit("USB communication error") + time.sleep(1) + except Exception as e: - logger.exception(f"Measurement error: {e}") + logger.error(f"Measurement error: {str(e)}") time.sleep(0.5) - time.sleep(self.interval) - logger.info(f"MeasurementThread for {self.parent_manager.serial} exiting") def _do_measure_once(self): """Actual measurement code protected by DeviceManager lock""" @@ -559,6 +580,8 @@ class BatteryTester(QMainWindow): self.active_device = None self.last_logged_phase = None self.global_recording = False + self.debug_counter = 0 + self.last_debug_time = time.time() # Color scheme - MUST BE DEFINED FIRST self.bg_color = "#2E3440" @@ -626,6 +649,16 @@ class BatteryTester(QMainWindow): self.status_timer.timeout.connect(self.update_status_and_plot) self.status_timer.start(1000) #every second + def print_device_status(self): + """Debug method to print current device states""" + for serial, device in self.devices.items(): + print(f"\nDevice: {serial}") + print(f"Active: {device == self.active_device}") + print(f"Start Time: {getattr(device, 'start_time', 'NOT SET')}") + print(f"Data Points: {len(device.time_data)}") + print(f"Last Voltage: {device.voltage_data[-1] if device.voltage_data else 'NONE'}") + print(f"Thread Running: {device.measurement_thread.isRunning() if hasattr(device, 'measurement_thread') else 'NO THREAD'}") + def setup_ui(self): """Configure the user interface with all elements properly organized""" # Main widget and layout @@ -1429,8 +1462,10 @@ class BatteryTester(QMainWindow): except Exception as e: print(f"Manual init failed: {e}") + @safe_execute def change_device(self, index): - if not self.session_active or index < 0: + """Handle switching between connected devices""" + if not self.session_active or index < 0: return serial = self.device_combo.itemText(index) @@ -1443,51 +1478,62 @@ class BatteryTester(QMainWindow): # Disconnect signals from old device's measurement thread if old_device and old_device.measurement_thread: try: - # Safely disconnect signals - old_device.measurement_thread.update_signal.disconnect(self.update_measurements) - old_device.measurement_thread.error_signal.disconnect(self.handle_device_error) - except TypeError: - # Signals weren't connected - safe to ignore + with self.plot_mutex: # Ensure thread-safe disconnection + old_device.measurement_thread.update_signal.disconnect(self.update_measurements) + old_device.measurement_thread.error_signal.disconnect(self.handle_device_error) + if old_device.is_recording: + self.finalize_device_log_file(old_device) + except (TypeError, RuntimeError): + # Signals weren't connected or already disconnected - safe to ignore pass # Activate new device self.active_device = self.devices[serial] + dev = self.active_device # Ensure measurement thread exists - if not hasattr(self.active_device, 'measurement_thread'): - # Create measurement thread if missing - self.active_device.measurement_thread = MeasurementThread( - self.active_device, - self.interval, - self.active_device - ) + if not hasattr(dev, 'measurement_thread'): + dev.measurement_thread = MeasurementThread(dev, self.interval, dev) # Get reference to new thread - new_thread = self.active_device.measurement_thread + new_thread = dev.measurement_thread # Connect signals for new device - new_thread.update_signal.connect(self.update_measurements) - new_thread.error_signal.connect(self.handle_device_error) + with self.plot_mutex: + new_thread.update_signal.connect(self.update_measurements) + new_thread.error_signal.connect(self.handle_device_error) - # Start measurement if not running + # Start measurement if not running (with thread safety) if not new_thread.isRunning(): new_thread.start() + # Initialize device time tracking if needed + if not hasattr(dev, 'start_time'): + dev.start_time = time.time() + # Update UI with current device data self.update_ui_from_active_device() - # Preserve recording state for old device + # Handle recording state transition if old_device and old_device.is_recording: - # Ensure old device continues recording - if not old_device.measurement_thread.isRunning(): - old_device.measurement_thread.start() + # Finalize old device recording + self.finalize_device_log_file(old_device) - # Update recording button for new device - self.record_button.setChecked(self.active_device.is_recording) - self.record_button.setText("Stop Recording" if self.active_device.is_recording else "Start Recording") + # Set up recording for new device if global recording is enabled + if self.global_recording and not dev.is_recording: + self.start_live_monitoring(dev) + + # Update recording button state + self.record_button.setChecked(dev.is_recording) + self.record_button.setText("■ Stop Recording" if dev.is_recording else "● Start Recording") self.apply_button_style() + # Reset plot for new device + self.reset_plot() + + # Update status self.status_bar.showMessage(f"Switched to device: {serial}") + self.set_connection_status(f"Connected: {serial}", self.status_colors["connected"]) def update_ui_from_active_device(self): dev = self.active_device @@ -1522,75 +1568,67 @@ class BatteryTester(QMainWindow): @safe_execute @pyqtSlot(str, float, float, float) def update_measurements(self, serial, voltage, current, current_time): - """Update measurements for a specific device identified by serial""" - # Get device from serial - device = self.devices.get(serial) - if not device: - return + """Debugged measurement update handler""" + try: + device = self.devices.get(serial) + if not device: + logger.error(f"Device {serial} not found") + return + + # Ensure timing is initialized + if not hasattr(device, 'start_time') or device.start_time is None: + device.start_time = current_time + logger.debug(f"Initialized start time for {serial}") + + # Calculate elapsed time safely + try: + elapsed = current_time - device.start_time + except TypeError: + logger.error(f"Invalid timing - current: {current_time}, start: {device.start_time}") + device.start_time = current_time + elapsed = 0 + + # Validate measurements + if not (0 <= voltage <= 5.0) or not (-0.2 <= current <= 0.2): + logger.warning(f"Invalid values - V: {voltage:.3f}, I: {current:.3f}") + return + + # Update data buffers + device.time_data.append(elapsed) + device.voltage_data.append(voltage) + device.current_data.append(current) - # Update device data - device.time_data.append(current_time) - device.voltage_data.append(voltage) - device.current_data.append(current) - - # Calculate metrics - power = voltage * abs(current) - if len(device.time_data) > 1: - delta_t = device.time_data[-1] - device.time_data[-2] - device.capacity_ah += abs(current) * delta_t / 3600 # Ah - device.energy += power * delta_t / 3600 # Wh - - # Update display buffers - device.display_time_data.append(current_time) - device.display_voltage_data.append(voltage) - device.display_current_data.append(current) - - # Update UI only if this is the active device - if device == self.active_device: - self.voltage_label.setText(f"{voltage:.4f}") - self.current_label.setText(f"{abs(current):.4f}") - self.capacity_label.setText(f"{device.capacity_ah:.4f}") - self.energy_label.setText(f"{device.energy:.4f}") - self.time_label.setText(self.format_time(current_time)) + # Update display buffers + device.display_time_data.append(elapsed) + device.display_voltage_data.append(voltage) + device.display_current_data.append(current) - # Update plot if needed (only for active device) - if time.time() - getattr(device, '_last_plot_update', 0) > 0.5: # Throttle updates - device._last_plot_update = time.time() + # Trim display buffers if needed + if len(device.display_time_data) > 1000: + device.display_time_data.popleft() + device.display_voltage_data.popleft() + device.display_current_data.popleft() + + # Only update UI for active device + if device == self.active_device: + self.voltage_label.setText(f"{voltage:.4f}") + self.current_label.setText(f"{abs(current):.4f}") + self.time_label.setText(self.format_time(elapsed)) + + # Calculate metrics if we have enough data + if len(device.time_data) > 1: + delta_t = device.time_data[-1] - device.time_data[-2] + device.capacity_ah += abs(current) * delta_t / 3600 + device.energy += (voltage * abs(current)) * delta_t / 3600 + self.capacity_label.setText(f"{device.capacity_ah:.4f}") + self.energy_label.setText(f"{device.energy:.4f}") + + # Force plot update self.update_plot() - - # Handle recording for this device - now = time.time() - if device.is_recording and device.log_writer and device.time_data: - if now - device._last_log_time >= 1.0: # Log at 1Hz - try: - device.log_writer.writerow([ - f"{current_time:.2f}", - f"{voltage:.6f}", - f"{current:.6f}", - f"{device.capacity_ah:.6f}", - f"{power:.6f}", - f"{device.energy:.6f}", - device.test_phase - ]) - device.log_file.flush() - device._last_log_time = now - except Exception as e: - print(f"Log write error for device {serial}: {e}") - # Attempt to close and reopen log file - try: - if device.log_file: - device.log_file.close() - except: - pass - device.log_file = None - device.log_writer = None - device.is_recording = False - - # Update UI if this is the active device - if device == self.active_device: - self.record_button.setChecked(False) - self.record_button.setText("Start Recording") - self.apply_button_style() + + except Exception as e: + logger.error(f"Critical error in update_measurements: {str(e)}") + traceback.print_exc() def adjust_downsampling(self): current_length = len(self.time_data) @@ -2655,39 +2693,34 @@ class BatteryTester(QMainWindow): self.update_plot() def update_plot(self): - """Fixed plot method with safe attribute access""" + """Debugged plot update""" + if not self.active_device: + logger.warning("No active device in update_plot") + return + try: - if not self.active_device: - return - - # Create local copies of data safely dev = self.active_device with self.plot_mutex: if not dev.display_time_data: + logger.warning(f"No data to plot for {dev.serial}") 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 self.line_voltage.set_data(x_data, y1_data) self.line_current.set_data(x_data, y2_data) - # Auto-scale when needed - if len(x_data) > 1: + # Auto-scale only when significant changes occur + if len(x_data) > 1 and x_data[-1] - x_data[0] > 1.0: self.auto_scale_axes() - # Force redraw self.canvas.draw_idle() + logger.debug(f"Plot updated for {dev.serial} with {len(x_data)} points") except Exception as e: - print(f"Plot error: {e}") - # Attempt to recover - try: - self.reset_plot() - except: - pass + logger.error(f"Plot update failed: {str(e)}") def auto_scale_axes(self): """Auto-scale plot axes with appropriate padding and strict boundaries"""