MainCode/adalm1000_logger.py aktualisiert
it works but only after switching device D
This commit is contained in:
parent
084d1be8fa
commit
716955900b
@ -77,12 +77,16 @@ class DeviceManager:
|
|||||||
self.test_phase = "Idle"
|
self.test_phase = "Idle"
|
||||||
|
|
||||||
# Add these new attributes for recording state
|
# 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.is_recording = False
|
||||||
self.log_file = None
|
self.log_file = None
|
||||||
self.log_writer = None
|
self.log_writer = None
|
||||||
self._last_log_time = 0
|
self._last_log_time = 0
|
||||||
self.log_dir = os.path.expanduser("~/adalm1000/logs")
|
self.log_dir = os.path.expanduser("~/adalm1000/logs")
|
||||||
os.makedirs(self.log_dir, exist_ok=True)
|
os.makedirs(self.log_dir, exist_ok=True)
|
||||||
|
self.start_time = None
|
||||||
|
self.last_measurement_time = None
|
||||||
|
|
||||||
def reset_data(self):
|
def reset_data(self):
|
||||||
"""Reset all data buffers and statistics for the device"""
|
"""Reset all data buffers and statistics for the device"""
|
||||||
@ -237,38 +241,55 @@ class MeasurementThread(QThread):
|
|||||||
self.wait(500)
|
self.wait(500)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
logger.info(f"MeasurementThread STARTED for {self.parent_manager.serial}")
|
||||||
if not self.dev_manager.dev:
|
if not self.dev_manager.dev:
|
||||||
|
logger.error("No device available")
|
||||||
self.error_signal.emit("Device not initialized")
|
self.error_signal.emit("Device not initialized")
|
||||||
return
|
return
|
||||||
logger.info(f"MeasurementThread for {self.parent_manager.serial} starting")
|
|
||||||
|
# Initialize timing
|
||||||
|
self.dev_manager.start_time = time.time()
|
||||||
|
|
||||||
while self._running:
|
while self._running:
|
||||||
try:
|
try:
|
||||||
# Get measurement under lock
|
start_time = time.time()
|
||||||
voltage, current, ts = self.dev_manager.safe_call(self._do_measure_once)
|
|
||||||
self.measurement_queue.put((self.parent_manager.serial, voltage, current, ts))
|
# Get measurement
|
||||||
self.update_signal.emit(self.parent_manager.serial, voltage, current, ts)
|
samples = self.dev_manager.dev.get_samples(1)
|
||||||
except DeviceDisconnectedError:
|
if not samples:
|
||||||
logger.error(f"Device {self.parent_manager.serial} disconnected during measurement")
|
time.sleep(0.01)
|
||||||
self.error_signal.emit(f"Device {self.parent_manager.serial} disconnected")
|
continue
|
||||||
# Attempt reconnect
|
|
||||||
try:
|
sample = samples[0]
|
||||||
logger.info("Attempting to reconnect...")
|
voltage = sample[0][0]
|
||||||
ok = self.dev_manager.reopen_with_backoff()
|
current = sample[0][1]
|
||||||
if ok:
|
current_time = time.time()
|
||||||
logger.info(f"Reconnected to {self.parent_manager.serial}")
|
|
||||||
self.error_signal.emit(f"Reconnected to {self.parent_manager.serial}")
|
# Validate before sending
|
||||||
else:
|
if not (0 <= voltage <= 5.0) or not (-0.2 <= current <= 0.2):
|
||||||
logger.error(f"Permanent failure for {self.parent_manager.serial}")
|
continue
|
||||||
self.error_signal.emit(f"Permanent failure for {self.parent_manager.serial}")
|
|
||||||
break
|
# 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:
|
except Exception as e:
|
||||||
logger.exception(f"Reconnect failed: {e}")
|
logger.error(f"Measurement error: {str(e)}")
|
||||||
time.sleep(2.0)
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(f"Measurement error: {e}")
|
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
time.sleep(self.interval)
|
|
||||||
logger.info(f"MeasurementThread for {self.parent_manager.serial} exiting")
|
|
||||||
|
|
||||||
def _do_measure_once(self):
|
def _do_measure_once(self):
|
||||||
"""Actual measurement code protected by DeviceManager lock"""
|
"""Actual measurement code protected by DeviceManager lock"""
|
||||||
@ -559,6 +580,8 @@ class BatteryTester(QMainWindow):
|
|||||||
self.active_device = None
|
self.active_device = None
|
||||||
self.last_logged_phase = None
|
self.last_logged_phase = None
|
||||||
self.global_recording = False
|
self.global_recording = False
|
||||||
|
self.debug_counter = 0
|
||||||
|
self.last_debug_time = time.time()
|
||||||
|
|
||||||
# Color scheme - MUST BE DEFINED FIRST
|
# Color scheme - MUST BE DEFINED FIRST
|
||||||
self.bg_color = "#2E3440"
|
self.bg_color = "#2E3440"
|
||||||
@ -626,6 +649,16 @@ class BatteryTester(QMainWindow):
|
|||||||
self.status_timer.timeout.connect(self.update_status_and_plot)
|
self.status_timer.timeout.connect(self.update_status_and_plot)
|
||||||
self.status_timer.start(1000) #every second
|
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):
|
def setup_ui(self):
|
||||||
"""Configure the user interface with all elements properly organized"""
|
"""Configure the user interface with all elements properly organized"""
|
||||||
# Main widget and layout
|
# Main widget and layout
|
||||||
@ -1429,7 +1462,9 @@ class BatteryTester(QMainWindow):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Manual init failed: {e}")
|
print(f"Manual init failed: {e}")
|
||||||
|
|
||||||
|
@safe_execute
|
||||||
def change_device(self, index):
|
def change_device(self, index):
|
||||||
|
"""Handle switching between connected devices"""
|
||||||
if not self.session_active or index < 0:
|
if not self.session_active or index < 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1443,51 +1478,62 @@ class BatteryTester(QMainWindow):
|
|||||||
# Disconnect signals from old device's measurement thread
|
# Disconnect signals from old device's measurement thread
|
||||||
if old_device and old_device.measurement_thread:
|
if old_device and old_device.measurement_thread:
|
||||||
try:
|
try:
|
||||||
# Safely disconnect signals
|
with self.plot_mutex: # Ensure thread-safe disconnection
|
||||||
old_device.measurement_thread.update_signal.disconnect(self.update_measurements)
|
old_device.measurement_thread.update_signal.disconnect(self.update_measurements)
|
||||||
old_device.measurement_thread.error_signal.disconnect(self.handle_device_error)
|
old_device.measurement_thread.error_signal.disconnect(self.handle_device_error)
|
||||||
except TypeError:
|
if old_device.is_recording:
|
||||||
# Signals weren't connected - safe to ignore
|
self.finalize_device_log_file(old_device)
|
||||||
|
except (TypeError, RuntimeError):
|
||||||
|
# Signals weren't connected or already disconnected - safe to ignore
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Activate new device
|
# Activate new device
|
||||||
self.active_device = self.devices[serial]
|
self.active_device = self.devices[serial]
|
||||||
|
dev = self.active_device
|
||||||
|
|
||||||
# Ensure measurement thread exists
|
# Ensure measurement thread exists
|
||||||
if not hasattr(self.active_device, 'measurement_thread'):
|
if not hasattr(dev, 'measurement_thread'):
|
||||||
# Create measurement thread if missing
|
dev.measurement_thread = MeasurementThread(dev, self.interval, dev)
|
||||||
self.active_device.measurement_thread = MeasurementThread(
|
|
||||||
self.active_device,
|
|
||||||
self.interval,
|
|
||||||
self.active_device
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get reference to new thread
|
# Get reference to new thread
|
||||||
new_thread = self.active_device.measurement_thread
|
new_thread = dev.measurement_thread
|
||||||
|
|
||||||
# Connect signals for new device
|
# Connect signals for new device
|
||||||
|
with self.plot_mutex:
|
||||||
new_thread.update_signal.connect(self.update_measurements)
|
new_thread.update_signal.connect(self.update_measurements)
|
||||||
new_thread.error_signal.connect(self.handle_device_error)
|
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():
|
if not new_thread.isRunning():
|
||||||
new_thread.start()
|
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
|
# Update UI with current device data
|
||||||
self.update_ui_from_active_device()
|
self.update_ui_from_active_device()
|
||||||
|
|
||||||
# Preserve recording state for old device
|
# Handle recording state transition
|
||||||
if old_device and old_device.is_recording:
|
if old_device and old_device.is_recording:
|
||||||
# Ensure old device continues recording
|
# Finalize old device recording
|
||||||
if not old_device.measurement_thread.isRunning():
|
self.finalize_device_log_file(old_device)
|
||||||
old_device.measurement_thread.start()
|
|
||||||
|
|
||||||
# Update recording button for new device
|
# Set up recording for new device if global recording is enabled
|
||||||
self.record_button.setChecked(self.active_device.is_recording)
|
if self.global_recording and not dev.is_recording:
|
||||||
self.record_button.setText("Stop Recording" if self.active_device.is_recording else "Start 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()
|
self.apply_button_style()
|
||||||
|
|
||||||
|
# Reset plot for new device
|
||||||
|
self.reset_plot()
|
||||||
|
|
||||||
|
# Update status
|
||||||
self.status_bar.showMessage(f"Switched to device: {serial}")
|
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):
|
def update_ui_from_active_device(self):
|
||||||
dev = self.active_device
|
dev = self.active_device
|
||||||
@ -1522,75 +1568,67 @@ class BatteryTester(QMainWindow):
|
|||||||
@safe_execute
|
@safe_execute
|
||||||
@pyqtSlot(str, float, float, float)
|
@pyqtSlot(str, float, float, float)
|
||||||
def update_measurements(self, serial, voltage, current, current_time):
|
def update_measurements(self, serial, voltage, current, current_time):
|
||||||
"""Update measurements for a specific device identified by serial"""
|
"""Debugged measurement update handler"""
|
||||||
# Get device from serial
|
try:
|
||||||
device = self.devices.get(serial)
|
device = self.devices.get(serial)
|
||||||
if not device:
|
if not device:
|
||||||
|
logger.error(f"Device {serial} not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update device data
|
# Ensure timing is initialized
|
||||||
device.time_data.append(current_time)
|
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.voltage_data.append(voltage)
|
||||||
device.current_data.append(current)
|
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
|
# Update display buffers
|
||||||
device.display_time_data.append(current_time)
|
device.display_time_data.append(elapsed)
|
||||||
device.display_voltage_data.append(voltage)
|
device.display_voltage_data.append(voltage)
|
||||||
device.display_current_data.append(current)
|
device.display_current_data.append(current)
|
||||||
|
|
||||||
# Update UI only if this is the active device
|
# 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:
|
if device == self.active_device:
|
||||||
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(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.capacity_label.setText(f"{device.capacity_ah:.4f}")
|
||||||
self.energy_label.setText(f"{device.energy:.4f}")
|
self.energy_label.setText(f"{device.energy:.4f}")
|
||||||
self.time_label.setText(self.format_time(current_time))
|
|
||||||
|
|
||||||
# Update plot if needed (only for active device)
|
# Force plot update
|
||||||
if time.time() - getattr(device, '_last_plot_update', 0) > 0.5: # Throttle updates
|
|
||||||
device._last_plot_update = time.time()
|
|
||||||
self.update_plot()
|
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:
|
except Exception as e:
|
||||||
print(f"Log write error for device {serial}: {e}")
|
logger.error(f"Critical error in update_measurements: {str(e)}")
|
||||||
# Attempt to close and reopen log file
|
traceback.print_exc()
|
||||||
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()
|
|
||||||
|
|
||||||
def adjust_downsampling(self):
|
def adjust_downsampling(self):
|
||||||
current_length = len(self.time_data)
|
current_length = len(self.time_data)
|
||||||
@ -2655,39 +2693,34 @@ class BatteryTester(QMainWindow):
|
|||||||
self.update_plot()
|
self.update_plot()
|
||||||
|
|
||||||
def update_plot(self):
|
def update_plot(self):
|
||||||
"""Fixed plot method with safe attribute access"""
|
"""Debugged plot update"""
|
||||||
try:
|
|
||||||
if not self.active_device:
|
if not self.active_device:
|
||||||
|
logger.warning("No active device in update_plot")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create local copies of data safely
|
try:
|
||||||
dev = self.active_device
|
dev = self.active_device
|
||||||
with self.plot_mutex:
|
with self.plot_mutex:
|
||||||
if not dev.display_time_data:
|
if not dev.display_time_data:
|
||||||
|
logger.warning(f"No data to plot for {dev.serial}")
|
||||||
return
|
return
|
||||||
|
|
||||||
x_data = list(dev.display_time_data)
|
x_data = list(dev.display_time_data)
|
||||||
y1_data = list(dev.display_voltage_data)
|
y1_data = list(dev.display_voltage_data)
|
||||||
y2_data = list(dev.display_current_data)
|
y2_data = list(dev.display_current_data)
|
||||||
|
|
||||||
# Update plot data
|
|
||||||
self.line_voltage.set_data(x_data, y1_data)
|
self.line_voltage.set_data(x_data, y1_data)
|
||||||
self.line_current.set_data(x_data, y2_data)
|
self.line_current.set_data(x_data, y2_data)
|
||||||
|
|
||||||
# Auto-scale when needed
|
# Auto-scale only when significant changes occur
|
||||||
if len(x_data) > 1:
|
if len(x_data) > 1 and x_data[-1] - x_data[0] > 1.0:
|
||||||
self.auto_scale_axes()
|
self.auto_scale_axes()
|
||||||
|
|
||||||
# Force redraw
|
|
||||||
self.canvas.draw_idle()
|
self.canvas.draw_idle()
|
||||||
|
logger.debug(f"Plot updated for {dev.serial} with {len(x_data)} points")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Plot error: {e}")
|
logger.error(f"Plot update failed: {str(e)}")
|
||||||
# Attempt to recover
|
|
||||||
try:
|
|
||||||
self.reset_plot()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def auto_scale_axes(self):
|
def auto_scale_axes(self):
|
||||||
"""Auto-scale plot axes with appropriate padding and strict boundaries"""
|
"""Auto-scale plot axes with appropriate padding and strict boundaries"""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user