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