MainCode/adalm1000_logger.py aktualisiert

still crashing after a while
D
This commit is contained in:
Jan 2025-08-07 01:59:33 +02:00
parent 7c72e508d7
commit ab4506cf27

View File

@ -3,6 +3,7 @@ import os
import time
import csv
import threading
import traceback
from datetime import datetime
import numpy as np
import matplotlib
@ -12,9 +13,11 @@ from matplotlib.figure import Figure
from collections import deque
from queue import Queue, Full, Empty
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel,
QPushButton, QLineEdit, QCheckBox, QFrame, QMessageBox, QFileDialog, QComboBox)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QGridLayout, QLabel, QPushButton, QLineEdit, QCheckBox,
QFrame, QMessageBox, QFileDialog, QComboBox)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtSlot, QObject, QThread
from PyQt5.QtGui import QDoubleValidator
from PyQt5 import sip
import pysmu
@ -74,17 +77,28 @@ class DeviceManager:
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) # Pass self as parent_manager
self.stop_measurement() # Ensure any existing thread is stopped
self.measurement_thread = MeasurementThread(self.dev, interval, self)
self.measurement_thread.start()
self.is_running = True
def stop_measurement(self):
if self.measurement_thread:
self.measurement_thread.stop()
self.measurement_thread.wait(500)
try:
# Disconnect signals first
try:
self.measurement_thread.update_signal.disconnect()
self.measurement_thread.error_signal.disconnect()
except (RuntimeError, TypeError):
pass
# Then stop the thread
self.measurement_thread.stop()
if not self.measurement_thread.wait(500):
self.measurement_thread.terminate()
self.measurement_thread = None
except Exception as e:
print(f"Error stopping measurement thread: {e}")
self.is_running = False
def reset_data(self):
@ -112,7 +126,7 @@ class MeasurementThread(QThread):
update_signal = pyqtSignal(float, float, float)
error_signal = pyqtSignal(str)
def __init__(self, device, interval, parent_manager): # Add parent_manager
def __init__(self, device, interval, parent_manager):
super().__init__()
self.device = device
self.interval = interval
@ -122,13 +136,18 @@ class MeasurementThread(QThread):
self.current_window = []
self.start_time = None
self.measurement_queue = Queue(maxsize=1)
self.current_direction = 1 # 1 for source, -1 for sink
self.current_direction = 1
self.parent_manager = parent_manager
def stop(self):
self._running = False
if not self.wait(500): # Wait up to 500ms for clean shutdown
self.terminate()
def run(self):
"""Continuous measurement loop with enhanced error handling"""
self._running = True
self.start_time = time.time()
self.start_time = time.time() # This gets reset when mode changes
consecutive_errors = 0
max_consecutive_errors = 5
@ -213,10 +232,6 @@ class MeasurementThread(QThread):
"""Set current direction (1 for source, -1 for sink)"""
self.current_direction = direction
def stop(self):
self._running = False
self.wait(500)
class TestSequenceWorker(QObject):
finished = pyqtSignal()
update_phase = pyqtSignal(str)
@ -590,14 +605,14 @@ class BatteryTester(QMainWindow):
self.status_timer.start(1000) #every second
def setup_ui(self):
"""Configure the user interface"""
"""Configure the user interface with all elements properly organized"""
# Main widget and layout
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.main_layout = QVBoxLayout(self.central_widget)
self.main_layout.setContentsMargins(10, 10, 10, 10)
# Mode and device selection
# Mode and device selection frame
mode_frame = QFrame()
mode_frame.setFrameShape(QFrame.StyledPanel)
mode_frame.setStyleSheet(f"QFrame {{ border: 1px solid {self.accent_color}; border-radius: 5px; }}")
@ -674,7 +689,7 @@ class BatteryTester(QMainWindow):
display_frame.setStyleSheet(f"QFrame {{ border: 1px solid {self.accent_color}; border-radius: 5px; }}")
display_layout = QGridLayout(display_frame)
# Measurement values - common for all modes
# Measurement values
measurement_labels = [
("Voltage", "V"), ("Current", "A"), ("Test Phase", ""),
("Elapsed Time", "s"), ("Capacity", "Ah"), ("Power", "W"),
@ -730,123 +745,103 @@ class BatteryTester(QMainWindow):
self.params_frame.setStyleSheet(f"QFrame {{ border: 1px solid {self.accent_color}; border-radius: 5px; }}")
self.params_layout = QGridLayout(self.params_frame)
# Common parameters
self.capacity = 0.2
self.capacity_label_input = QLabel("Battery Capacity (Ah):")
self.capacity_label_input.setStyleSheet(f"color: {self.fg_color};")
self.params_layout.addWidget(self.capacity_label_input, 0, 0)
self.capacity_input = QLineEdit("0.2")
self.capacity_input.setStyleSheet(f"background-color: #3B4252; color: {self.fg_color};")
self.capacity_input.setFixedWidth(60)
self.params_layout.addWidget(self.capacity_input, 0, 1)
# Add parameter inputs
row = 0
# C-rate for test
self.c_rate = 0.1
self.c_rate_label = QLabel("Test C-rate:")
# Battery Capacity
self.capacity_label = QLabel("Capacity (Ah):")
self.capacity_label.setStyleSheet(f"color: {self.fg_color};")
self.params_layout.addWidget(self.capacity_label, row, 0)
self.capacity_input = QLineEdit("1.0")
self.capacity_input.setValidator(QDoubleValidator(0.001, 100, 3))
self.params_layout.addWidget(self.capacity_input, row, 1)
row += 1
# C-Rate
self.c_rate_label = QLabel("C-Rate:")
self.c_rate_label.setStyleSheet(f"color: {self.fg_color};")
self.params_layout.addWidget(self.c_rate_label, 1, 0)
self.params_layout.addWidget(self.c_rate_label, row, 0)
self.c_rate_input = QLineEdit("0.1")
self.c_rate_input.setStyleSheet(f"background-color: #3B4252; color: {self.fg_color};")
self.c_rate_input.setFixedWidth(40)
self.params_layout.addWidget(self.c_rate_input, 1, 1)
self.c_rate_input.setValidator(QDoubleValidator(0.01, 1, 2))
self.params_layout.addWidget(self.c_rate_input, row, 1)
row += 1
c_rate_note = QLabel("(e.g., 0.2 for C/5)")
c_rate_note.setStyleSheet(f"color: {self.fg_color};")
self.params_layout.addWidget(c_rate_note, 1, 2)
# Discharge cutoff (used in Discharge and Cycle modes)
self.discharge_cutoff = 0.9
self.discharge_cutoff_label = QLabel("Discharge Cutoff (V):")
self.discharge_cutoff_label.setStyleSheet(f"color: {self.fg_color};")
self.params_layout.addWidget(self.discharge_cutoff_label, 2, 0)
self.discharge_cutoff_input = QLineEdit("0.9")
self.discharge_cutoff_input.setStyleSheet(f"background-color: #3B4252; color: {self.fg_color};")
self.discharge_cutoff_input.setFixedWidth(60)
self.params_layout.addWidget(self.discharge_cutoff_input, 2, 1)
# Charge cutoff (only for Cycle mode)
self.charge_cutoff = 1.43
# Charge Cutoff Voltage
self.charge_cutoff_label = QLabel("Charge Cutoff (V):")
self.charge_cutoff_label.setStyleSheet(f"color: {self.fg_color};")
self.params_layout.addWidget(self.charge_cutoff_label, 3, 0)
self.params_layout.addWidget(self.charge_cutoff_label, row, 0)
self.charge_cutoff_input = QLineEdit("1.43")
self.charge_cutoff_input.setStyleSheet(f"background-color: #3B4252; color: {self.fg_color};")
self.charge_cutoff_input.setFixedWidth(60)
self.params_layout.addWidget(self.charge_cutoff_input, 3, 1)
self.charge_cutoff_label.hide()
self.charge_cutoff_input.hide()
self.charge_cutoff_input.setValidator(QDoubleValidator(0.1, 5.0, 3))
self.params_layout.addWidget(self.charge_cutoff_input, row, 1)
row += 1
# Rest time (only for Cycle mode)
self.rest_time = 0.25
self.rest_time_label = QLabel("Rest Time (hours):")
# Discharge Cutoff Voltage
self.discharge_cutoff_label = QLabel("Discharge Cutoff (V):")
self.discharge_cutoff_label.setStyleSheet(f"color: {self.fg_color};")
self.params_layout.addWidget(self.discharge_cutoff_label, row, 0)
self.discharge_cutoff_input = QLineEdit("0.01")
self.discharge_cutoff_input.setValidator(QDoubleValidator(0.1, 5.0, 3))
self.params_layout.addWidget(self.discharge_cutoff_input, row, 1)
row += 1
# Rest Time
self.rest_time_label = QLabel("Rest Time (h):")
self.rest_time_label.setStyleSheet(f"color: {self.fg_color};")
self.params_layout.addWidget(self.rest_time_label, 4, 0)
self.rest_time_input = QLineEdit("0.25")
self.rest_time_input.setStyleSheet(f"background-color: #3B4252; color: {self.fg_color};")
self.rest_time_input.setFixedWidth(60)
self.params_layout.addWidget(self.rest_time_input, 4, 1)
self.rest_time_label.hide()
self.rest_time_input.hide()
self.params_layout.addWidget(self.rest_time_label, row, 0)
# Test conditions input
self.test_conditions_label = QLabel("Test Conditions/Chemistry:")
self.rest_time_input = QLineEdit("0.5")
self.rest_time_input.setValidator(QDoubleValidator(0.1, 24, 1))
self.params_layout.addWidget(self.rest_time_input, row, 1)
row += 1
# Test Conditions
self.test_conditions_label = QLabel("Test Conditions:")
self.test_conditions_label.setStyleSheet(f"color: {self.fg_color};")
self.params_layout.addWidget(self.test_conditions_label, 5, 0)
self.test_conditions_input = QLineEdit("")
self.test_conditions_input.setStyleSheet(f"background-color: #3B4252; color: {self.fg_color};")
self.test_conditions_input.setFixedWidth(120)
self.params_layout.addWidget(self.test_conditions_input, 5, 1)
self.params_layout.addWidget(self.test_conditions_label, row, 0)
self.test_conditions_input = QLineEdit("Room Temperature")
self.params_layout.addWidget(self.test_conditions_input, row, 1)
controls_layout.addWidget(self.params_frame, 1)
# Button frame
# Button frame with single toggle button
button_frame = QFrame()
button_frame.setFrameShape(QFrame.NoFrame)
button_layout = QVBoxLayout(button_frame)
button_layout.setContentsMargins(0, 0, 0, 0)
# Start/Stop buttons
self.start_button = QPushButton("START")
self.start_button.setStyleSheet(f"""
# Single toggle button (Start/Stop)
self.toggle_button = QPushButton("START")
self.toggle_button.setCheckable(True)
self.toggle_button.setStyleSheet(f"""
QPushButton {{
background-color: {self.accent_color};
color: {self.fg_color};
font-weight: bold;
padding: 6px;
border-radius: 4px;
min-width: 80px;
}}
QPushButton:disabled {{
background-color: #4C566A;
color: #D8DEE9;
}}
""")
self.start_button.clicked.connect(self.start_test)
button_layout.addWidget(self.start_button)
self.stop_button = QPushButton("STOP")
self.stop_button.setStyleSheet(f"""
QPushButton {{
QPushButton:checked {{
background-color: {self.warning_color};
color: {self.fg_color};
font-weight: bold;
padding: 6px;
border-radius: 4px;
}}
QPushButton:disabled {{
background-color: #4C566A;
color: #D8DEE9;
}}
""")
self.stop_button.clicked.connect(self.stop_test)
self.stop_button.setEnabled(False)
button_layout.addWidget(self.stop_button)
self.toggle_button.clicked.connect(self.toggle_test)
button_layout.addWidget(self.toggle_button)
# Continuous mode checkbox (only for Cycle mode)
self.continuous_mode_check = QCheckBox("Continuous Mode")
self.continuous_mode_check.setChecked(True)
self.continuous_mode_check.setStyleSheet(f"color: {self.fg_color};")
button_layout.addWidget(self.continuous_mode_check)
self.continuous_mode_check.stateChanged.connect(self.handle_continuous_mode_change)
self.continuous_mode_check.hide()
# Record button for Live mode
@ -896,55 +891,72 @@ class BatteryTester(QMainWindow):
}}
""")
# Set initial mode
self.current_mode = "Live Monitoring"
self.mode_combo.setCurrentText(self.current_mode)
self.change_mode(self.current_mode) # Initialize UI for live mode
def toggle_test(self):
"""Toggle between start and stop based on button state"""
if self.toggle_button.isChecked():
# Button shows "STOP" - run start logic
self.toggle_button.setText("STOP")
self.start_test()
else:
# Button shows "START" - run stop logic
self.toggle_button.setText("START")
self.stop_test()
def change_mode(self, mode_name):
"""Change between different test modes"""
self.current_mode = mode_name
self.stop_test() # Stop any current operation
# Hide all optional parameters first
self.charge_cutoff_label.hide()
self.charge_cutoff_input.hide()
self.discharge_cutoff_label.hide()
self.discharge_cutoff_input.hide()
self.rest_time_label.hide()
self.rest_time_input.hide()
self.continuous_mode_check.hide()
self.record_button.hide()
# Show/hide mode-specific UI elements
show_charge = mode_name in ["Cycle Test", "Charge Test"]
show_discharge = mode_name in ["Cycle Test", "Discharge Test"]
show_rest = mode_name == "Cycle Test"
# Show mode-specific parameters
self.charge_cutoff_label.setVisible(show_charge)
self.charge_cutoff_input.setVisible(show_charge)
self.discharge_cutoff_label.setVisible(show_discharge)
self.discharge_cutoff_input.setVisible(show_discharge)
self.rest_time_label.setVisible(show_rest)
self.rest_time_input.setVisible(show_rest)
# Continuous mode checkbox only for cycle test
self.continuous_mode_check.setVisible(mode_name == "Cycle Test")
# Record button only for live monitoring
self.record_button.setVisible(mode_name == "Live Monitoring")
# Set button text based on mode
if mode_name == "Cycle Test":
self.charge_cutoff_label.show()
self.charge_cutoff_input.show()
self.discharge_cutoff_label.show()
self.discharge_cutoff_input.show()
self.rest_time_label.show()
self.rest_time_input.show()
self.continuous_mode_check.show()
self.start_button.setText("START CYCLE TEST")
self.start_button.setEnabled(True) # Explicitly enable
self.toggle_button.setText("START CYCLE TEST")
elif mode_name == "Discharge Test":
self.discharge_cutoff_label.show()
self.discharge_cutoff_input.show()
self.start_button.setText("START DISCHARGE")
self.start_button.setEnabled(True) # Explicitly enable
self.toggle_button.setText("START DISCHARGE")
elif mode_name == "Charge Test":
self.charge_cutoff_label.show()
self.charge_cutoff_input.show()
self.start_button.setText("START CHARGE")
self.start_button.setEnabled(True) # Explicitly enable
self.toggle_button.setText("START CHARGE")
elif mode_name == "Live Monitoring":
self.record_button.show()
self.start_button.setText("START MONITORING")
# Only enable start button if device is connected
self.start_button.setEnabled(self.session_active)
self.toggle_button.setText("START") # Will be hidden anyway
self.toggle_button.hide()
# Reset measurement state
self.reset_test()
# Reset button state
self.toggle_button.setChecked(False)
# Reset measurement state and zero the time
if self.active_device:
dev = self.active_device
dev.reset_data() # This clears all data buffers
# Reset the measurement thread's start time
if hasattr(dev, 'measurement_thread'):
dev.measurement_thread.start_time = time.time()
# Reset UI displays
self.capacity_label.setText("0.0000")
self.energy_label.setText("0.0000")
self.cycle_label.setText("0")
self.phase_label.setText("Idle")
self.time_label.setText("00:00:00")
# Reset plot
self.reset_plot()
self.status_bar.showMessage(f"Mode changed to {mode_name}")
@ -1092,7 +1104,7 @@ class BatteryTester(QMainWindow):
self.session_active = True
self.connection_label.setText(f"Connected: {first_serial}")
self.status_light.setStyleSheet("background-color: green; border-radius: 10px;")
self.start_button.setEnabled(True)
self.toggle_button.setEnabled(True)
# Connect measurement signals
self.measurement_thread = self.active_device.measurement_thread
@ -1180,7 +1192,7 @@ class BatteryTester(QMainWindow):
self.status_light.setStyleSheet("background-color: orange; border-radius: 10px;")
self.connection_label.setText("Simulated Devices")
self.session_active = True
self.start_button.setEnabled(True)
self.toggle_button.setEnabled(True)
QMessageBox.warning(self, "Simulation Mode",
"Using simulated devices - real hardware not detected")
@ -1195,23 +1207,45 @@ class BatteryTester(QMainWindow):
if serial not in self.devices:
return
# Stoppe aktuelles Gerät
# Stop current device
if self.active_device:
self.active_device.stop_measurement()
if self.measurement_thread:
self.measurement_thread.update_signal.disconnect()
try:
# Disconnect signals safely
if hasattr(self.active_device, 'measurement_thread'):
thread = self.active_device.measurement_thread
try:
if thread.isRunning():
thread.stop()
thread.wait(500)
thread.update_signal.disconnect()
thread.error_signal.disconnect()
except (RuntimeError, TypeError) as e:
print(f"Signal disconnect warning: {e}")
except Exception as e:
print(f"Error stopping thread: {e}")
except Exception as e:
print(f"Error stopping previous device: {e}")
# Neues Gerät aktivieren
# Activate new device
self.active_device = self.devices[serial]
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)
# Starte Messung erst NACH Verbindung der Signale
self.active_device.start_measurement(self.interval)
# Connect signals to new device
if hasattr(self.active_device, 'measurement_thread'):
try:
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)
# UI aktualisieren
# Start measurement only AFTER connecting signals
if not self.measurement_thread.isRunning():
self.active_device.start_measurement(self.interval)
except Exception as e:
print(f"Error connecting to new device: {e}")
return
# Update UI with current device data
self.update_ui_from_active_device()
self.status_bar.showMessage(f"Switched to device: {serial}")
def update_ui_from_active_device(self):
dev = self.active_device
@ -1245,23 +1279,22 @@ class BatteryTester(QMainWindow):
@pyqtSlot(float, float, float)
def update_measurements(self, voltage, current, current_time):
if not self.active_device:
return
dev = 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
dev = self.active_device
with self.plot_mutex:
dev_manager.time_data.append(current_time)
dev_manager.voltage_data.append(voltage)
dev_manager.current_data.append(current)
dev.time_data.append(current_time)
dev.voltage_data.append(voltage)
dev.current_data.append(current)
# Aggregation für Anzeige (wie bisher)
# Aggregation for display
agg_buf = dev.aggregation_buffer
agg_buf['time'].append(current_time)
agg_buf['voltage'].append(voltage)
@ -1269,7 +1302,7 @@ class BatteryTester(QMainWindow):
agg_buf['count'] += 1
now = time.time()
if now - dev_manager.aggregation_buffer['last_plot_time'] >= 0.1:
if now - agg_buf['last_plot_time'] >= 0.1:
agg_time = np.mean(agg_buf['time'])
agg_voltage = np.mean(agg_buf['voltage'])
agg_current = np.mean(agg_buf['current'])
@ -1282,12 +1315,12 @@ class BatteryTester(QMainWindow):
'time': [], 'voltage': [], 'current': [], 'count': 0, 'last_plot_time': now
}
# UI-Labels aktualisieren
# Update UI labels
self.voltage_label.setText(f"{voltage:.4f}")
self.current_label.setText(f"{abs(current):.4f}")
self.time_label.setText(self.format_time(current_time))
# Kapazität berechnen
# Calculate capacity if we have enough data points
if len(dev.time_data) > 1:
delta_t = dev.time_data[-1] - dev.time_data[-2]
power = voltage * abs(current)
@ -1296,7 +1329,7 @@ class BatteryTester(QMainWindow):
self.capacity_label.setText(f"{dev.capacity_ah:.4f}")
self.energy_label.setText(f"{dev.energy:.4f}")
# Plot nur alle 100ms aktualisieren
# Update plot periodically
now = time.time()
if not hasattr(self, '_last_plot_update'):
self._last_plot_update = 0
@ -1326,17 +1359,22 @@ class BatteryTester(QMainWindow):
def update_status(self):
"""Update status information periodically"""
now = time.time() # Define 'now' at the start of the method
now = time.time()
if self.test_running or hasattr(self, 'record_button') and self.record_button.isChecked():
if self.time_data:
current_time = self.time_data[-1]
if len(self.time_data) > 1:
delta_t = self.time_data[-1] - self.time_data[-2]
if not self.active_device:
return
dev = self.active_device
if self.test_running or (hasattr(self, 'record_button') and self.record_button.isChecked()):
if dev.time_data:
current_time = dev.time_data[-1]
if len(dev.time_data) > 1:
delta_t = dev.time_data[-1] - dev.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}")
current_current = abs(dev.current_data[-1])
dev.capacity_ah += current_current * delta_t / 3600
self.capacity_label.setText(f"{dev.capacity_ah:.4f}")
# Logging (1x per second)
if (hasattr(self, 'log_writer') and
@ -1344,37 +1382,36 @@ class BatteryTester(QMainWindow):
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):
if dev.time_data and (now - self._last_log_time >= 1.0):
try:
current_time = self.time_data[-1]
voltage = self.voltage_data[-1]
current = self.current_data[-1]
current_time = dev.time_data[-1]
voltage = dev.voltage_data[-1]
current = dev.current_data[-1]
if self.current_mode == "Cycle Test":
self.log_writer.writerow([
f"{current_time:.4f}",
f"{voltage:.6f}",
f"{current:.6f}",
self.test_phase,
f"{self.capacity_ah:.4f}",
f"{self.charge_capacity:.4f}",
f"{self.coulomb_efficiency:.1f}",
f"{self.cycle_count}"
dev.test_phase,
f"{dev.capacity_ah:.4f}",
f"{dev.charge_capacity:.4f}",
f"{dev.coulomb_efficiency:.1f}",
f"{dev.cycle_count}"
])
else:
self.log_writer.writerow([
f"{current_time:.4f}",
f"{voltage:.6f}",
f"{current:.6f}",
self.test_phase if hasattr(self, 'test_phase') else "Live",
f"{self.capacity_ah:.4f}",
dev.test_phase if hasattr(dev, 'test_phase') else "Live",
f"{dev.capacity_ah:.4f}",
f"{voltage * current:.4f}", # Power
f"{self.energy:.4f}", # Energy
f"{self.cycle_count}" if hasattr(self, 'cycle_count') else "1"
f"{dev.energy:.4f}", # Energy
f"{dev.cycle_count}" if hasattr(dev, 'cycle_count') else "1"
])
self.current_cycle_file.flush()
self._last_log_time = now
@ -1392,47 +1429,14 @@ class BatteryTester(QMainWindow):
"""Start the selected test mode using the active device"""
if not self.active_device:
QMessageBox.warning(self, "No Device", "No ADALM1000 device selected.")
self.toggle_button.setChecked(False)
return
dev_manager = self.active_device
dev = dev_manager.dev
# Clean up any previous test
if hasattr(self, 'test_sequence_worker') and self.test_sequence_worker is not None:
try:
self.test_sequence_worker.stop()
except:
pass
self.test_sequence_worker.deleteLater()
if hasattr(self, 'test_sequence_thread') and self.test_sequence_thread is not None:
self.test_sequence_thread.quit()
self.test_sequence_thread.wait(500)
self.test_sequence_thread.deleteLater()
del self.test_sequence_thread
if hasattr(self, 'discharge_worker') and self.discharge_worker is not None:
try:
self.discharge_worker.stop()
except:
pass
self.discharge_worker.deleteLater()
if hasattr(self, 'discharge_thread') and self.discharge_thread is not None:
self.discharge_thread.quit()
self.discharge_thread.wait(500)
self.discharge_thread.deleteLater()
del self.discharge_thread
if hasattr(self, 'charge_worker') and self.charge_worker is not None:
try:
self.charge_worker.stop()
except:
pass
self.charge_worker.deleteLater()
if hasattr(self, 'charge_thread') and self.charge_thread is not None:
self.charge_thread.quit()
self.charge_thread.wait(500)
self.charge_thread.deleteLater()
del self.charge_thread
self.cleanup_test_threads()
# Reset test state for active device
self.reset_test()
@ -1463,6 +1467,7 @@ class BatteryTester(QMainWindow):
dev.channels['B'].constant(0)
except Exception as e:
QMessageBox.critical(self, "Device Error", f"Failed to reset device: {e}")
self.toggle_button.setChecked(False)
return
# Reset test variables
@ -1480,8 +1485,7 @@ class BatteryTester(QMainWindow):
# Update UI
self.phase_label.setText(dev_manager.test_phase)
self.start_button.setEnabled(False)
self.stop_button.setEnabled(True)
self.toggle_button.setText("STOP")
# Get parameters from UI
try:
@ -1495,7 +1499,7 @@ class BatteryTester(QMainWindow):
self.stop_test()
return
# Create log file (now includes device serial)
# Create log file
if not self.create_cycle_log_file():
self.stop_test()
return
@ -1667,8 +1671,8 @@ class BatteryTester(QMainWindow):
self.test_phase = "Initial Discharge"
self.phase_label.setText(self.test_phase)
self.start_button.setEnabled(False)
self.stop_button.setEnabled(True)
self.toggle_button.setChecked(True)
self.toggle_button.setText("STOP")
self.status_bar.showMessage(f"Cycle test started | Current: {test_current:.4f}A")
# Create log file
@ -1703,8 +1707,9 @@ class BatteryTester(QMainWindow):
except Exception as e:
QMessageBox.critical(self, "Error", str(e))
# Ensure buttons are in correct state if error occurs
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.toggle_button.setChecked(False)
self.toggle_button.setText("START")
self.toggle_button.setEnabled(True)
def start_discharge_test(self):
"""Start the battery discharge test"""
@ -1808,8 +1813,9 @@ class BatteryTester(QMainWindow):
except Exception as e:
QMessageBox.critical(self, "Error", str(e))
# Ensure buttons are in correct state if error occurs
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.toggle_button.setChecked(False)
self.toggle_button.setText("START")
self.toggle_button.setEnabled(True)
def start_charge_test(self):
"""Start the battery charge test"""
@ -1912,8 +1918,9 @@ class BatteryTester(QMainWindow):
except Exception as e:
QMessageBox.critical(self, "Error", str(e))
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.toggle_button.setChecked(False)
self.toggle_button.setText("START")
self.toggle_button.setEnabled(True)
def start_live_monitoring(self):
"""Start live monitoring mode for the active device"""
@ -1949,6 +1956,9 @@ class BatteryTester(QMainWindow):
self.stop_button.setEnabled(True)
self.start_button.setEnabled(False)
# Hide the toggle button in Live mode
self.toggle_button.hide()
# Status
self.status_bar.showMessage(f"Live monitoring started | Device: {dev.serial}")
@ -2101,8 +2111,14 @@ class BatteryTester(QMainWindow):
self.active_device.test_phase = "Idle"
self.phase_label.setText("Idle")
self.stop_button.setEnabled(False)
self.start_button.setEnabled(True)
# Reset button state
self.toggle_button.setChecked(False)
if self.current_mode == "Cycle Test":
self.toggle_button.setText("START CYCLE TEST")
elif self.current_mode == "Discharge Test":
self.toggle_button.setText("START DISCHARGE")
elif self.current_mode == "Charge Test":
self.toggle_button.setText("START CHARGE")
if self.current_mode == "Live Monitoring":
self.status_bar.showMessage("Live monitoring stopped")
@ -2226,8 +2242,9 @@ class BatteryTester(QMainWindow):
# 7. Reset UI and state
self.request_stop = False
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.toggle_button.setChecked(False)
self.toggle_button.setText("START")
self.toggle_button.setEnabled(True)
# 8. Show completion message if test wasn't stopped by user
if not self.request_stop:
@ -2285,8 +2302,9 @@ class BatteryTester(QMainWindow):
import traceback
traceback.print_exc()
# Ensure we don't leave the UI in a locked state
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.toggle_button.setChecked(False)
self.toggle_button.setText("START")
self.toggle_button.setEnabled(True)
self.status_bar.showMessage("Error during test finalization")
def reset_plot(self):
@ -2580,7 +2598,7 @@ class BatteryTester(QMainWindow):
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)
self.toggle_button.setEnabled(True)
return
except DeviceDisconnectedError as e:
@ -2640,4 +2658,6 @@ if __name__ == "__main__":
window.show()
app.exec_()
except Exception as e:
import traceback
traceback.print_exc()
QMessageBox.critical(None, "Fatal Error", f"Application failed: {str(e)}")