diagram eigefügt
This commit is contained in:
parent
74e4b5f199
commit
7ba87e9d36
149
gui/main.py
149
gui/main.py
@ -6,6 +6,11 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout
|
|||||||
QGroupBox, QStatusBar, QMessageBox, QCheckBox)
|
QGroupBox, QStatusBar, QMessageBox, QCheckBox)
|
||||||
from PyQt6.QtCore import QTimer, Qt
|
from PyQt6.QtCore import QTimer, Qt
|
||||||
from PyQt6.QtGui import QFont
|
from PyQt6.QtGui import QFont
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
import datetime
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
class PHControllerGUI(QMainWindow):
|
class PHControllerGUI(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -13,7 +18,7 @@ class PHControllerGUI(QMainWindow):
|
|||||||
self.serial_connection = None
|
self.serial_connection = None
|
||||||
self.updating_tube_combo = False # Flag to prevent recursive tube commands
|
self.updating_tube_combo = False # Flag to prevent recursive tube commands
|
||||||
self.setWindowTitle("Arduino pH Controller")
|
self.setWindowTitle("Arduino pH Controller")
|
||||||
self.setGeometry(100, 100, 600, 400)
|
self.setGeometry(100, 100, 900, 800) # Larger window for plot
|
||||||
|
|
||||||
# Central Widget
|
# Central Widget
|
||||||
central_widget = QWidget()
|
central_widget = QWidget()
|
||||||
@ -136,6 +141,30 @@ class PHControllerGUI(QMainWindow):
|
|||||||
main_layout.addWidget(status_group)
|
main_layout.addWidget(status_group)
|
||||||
main_layout.addWidget(control_group)
|
main_layout.addWidget(control_group)
|
||||||
|
|
||||||
|
# pH Plot Group
|
||||||
|
plot_group = QGroupBox("pH-Verlauf")
|
||||||
|
plot_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
# Plot controls
|
||||||
|
plot_controls_layout = QHBoxLayout()
|
||||||
|
self.clear_plot_btn = QPushButton("Diagramm löschen")
|
||||||
|
self.clear_plot_btn.clicked.connect(self.clear_ph_plot)
|
||||||
|
self.plot_enabled_checkbox = QCheckBox("pH-Aufzeichnung")
|
||||||
|
self.plot_enabled_checkbox.setChecked(True)
|
||||||
|
|
||||||
|
plot_controls_layout.addWidget(self.plot_enabled_checkbox)
|
||||||
|
plot_controls_layout.addWidget(self.clear_plot_btn)
|
||||||
|
plot_controls_layout.addStretch()
|
||||||
|
|
||||||
|
# pH Plot Widget
|
||||||
|
self.ph_plot = PHPlotWidget()
|
||||||
|
|
||||||
|
plot_layout.addLayout(plot_controls_layout)
|
||||||
|
plot_layout.addWidget(self.ph_plot)
|
||||||
|
plot_group.setLayout(plot_layout)
|
||||||
|
|
||||||
|
main_layout.addWidget(plot_group)
|
||||||
|
|
||||||
# Status Bar
|
# Status Bar
|
||||||
self.status_bar = QStatusBar()
|
self.status_bar = QStatusBar()
|
||||||
self.setStatusBar(self.status_bar)
|
self.setStatusBar(self.status_bar)
|
||||||
@ -260,6 +289,8 @@ class PHControllerGUI(QMainWindow):
|
|||||||
self.auto_dose_btn.setEnabled(enabled)
|
self.auto_dose_btn.setEnabled(enabled)
|
||||||
self.volume_spin.setEnabled(enabled)
|
self.volume_spin.setEnabled(enabled)
|
||||||
self.reset_volume_checkbox.setEnabled(enabled)
|
self.reset_volume_checkbox.setEnabled(enabled)
|
||||||
|
self.clear_plot_btn.setEnabled(enabled)
|
||||||
|
self.plot_enabled_checkbox.setEnabled(enabled)
|
||||||
|
|
||||||
def send_command(self, command):
|
def send_command(self, command):
|
||||||
print(command)
|
print(command)
|
||||||
@ -432,6 +463,9 @@ class PHControllerGUI(QMainWindow):
|
|||||||
try:
|
try:
|
||||||
ph_value = float(data[1:])
|
ph_value = float(data[1:])
|
||||||
self.ph_label.setText(f"pH: {ph_value:.2f}")
|
self.ph_label.setText(f"pH: {ph_value:.2f}")
|
||||||
|
# Add to plot if recording is enabled
|
||||||
|
if self.plot_enabled_checkbox.isChecked():
|
||||||
|
self.ph_plot.add_ph_value(ph_value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
elif data.startswith('P'): # Pump status
|
elif data.startswith('P'): # Pump status
|
||||||
@ -488,6 +522,119 @@ class PHControllerGUI(QMainWindow):
|
|||||||
self.close_connection()
|
self.close_connection()
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
def clear_ph_plot(self):
|
||||||
|
"""Clear the pH plot data"""
|
||||||
|
self.ph_plot.clear_data()
|
||||||
|
self.status_bar.showMessage("pH-Diagramm gelöscht")
|
||||||
|
|
||||||
|
class PHPlotWidget(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.figure = Figure(figsize=(8, 4))
|
||||||
|
self.canvas = FigureCanvas(self.figure)
|
||||||
|
self.ax = self.figure.add_subplot(111)
|
||||||
|
|
||||||
|
# Data storage (keep last 300 points = 10 minutes at 2-second intervals)
|
||||||
|
self.times = deque(maxlen=300)
|
||||||
|
self.ph_values = deque(maxlen=300)
|
||||||
|
self.start_time = None # Track when recording started
|
||||||
|
|
||||||
|
# Setup plot
|
||||||
|
self.ax.set_xlabel('Zeit')
|
||||||
|
self.ax.set_ylabel('pH-Wert')
|
||||||
|
self.ax.set_title('pH-Verlauf')
|
||||||
|
self.ax.grid(True, alpha=0.3)
|
||||||
|
self.ax.set_ylim(0, 14) # pH range 0-14
|
||||||
|
|
||||||
|
# Layout
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(self.canvas)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# Plot line
|
||||||
|
self.line, = self.ax.plot([], [], 'b-', linewidth=2, label='pH-Wert')
|
||||||
|
self.ax.legend()
|
||||||
|
|
||||||
|
# Initial draw
|
||||||
|
self.canvas.draw()
|
||||||
|
|
||||||
|
def add_ph_value(self, ph_value):
|
||||||
|
"""Add new pH value with current timestamp"""
|
||||||
|
current_time = datetime.datetime.now()
|
||||||
|
|
||||||
|
# Set start time on first value
|
||||||
|
if self.start_time is None:
|
||||||
|
self.start_time = current_time
|
||||||
|
|
||||||
|
# Calculate relative time in seconds
|
||||||
|
relative_time = (current_time - self.start_time).total_seconds()
|
||||||
|
|
||||||
|
self.times.append(relative_time)
|
||||||
|
self.ph_values.append(ph_value)
|
||||||
|
self.update_plot()
|
||||||
|
|
||||||
|
def update_plot(self):
|
||||||
|
"""Update the plot with current data"""
|
||||||
|
if len(self.times) > 0 and len(self.ph_values) > 0:
|
||||||
|
# Update line data
|
||||||
|
self.line.set_data(self.times, self.ph_values)
|
||||||
|
|
||||||
|
# Auto-scale x-axis to show recent data
|
||||||
|
if len(self.times) > 1:
|
||||||
|
self.ax.set_xlim(min(self.times), max(self.times))
|
||||||
|
|
||||||
|
# Auto-scale y-axis around data with some margin
|
||||||
|
if len(self.ph_values) > 0:
|
||||||
|
min_ph = min(self.ph_values)
|
||||||
|
max_ph = max(self.ph_values)
|
||||||
|
margin = 0.5
|
||||||
|
self.ax.set_ylim(max(0, min_ph - margin), min(14, max_ph + margin))
|
||||||
|
|
||||||
|
# Format x-axis for time display (seconds/minutes/hours)
|
||||||
|
self.format_time_axis()
|
||||||
|
|
||||||
|
# Redraw
|
||||||
|
self.canvas.draw()
|
||||||
|
|
||||||
|
def format_time_axis(self):
|
||||||
|
"""Format the x-axis to show time in appropriate units"""
|
||||||
|
if len(self.times) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
max_time = max(self.times)
|
||||||
|
|
||||||
|
if max_time < 120: # Less than 2 minutes - show seconds
|
||||||
|
self.ax.set_xlabel('Zeit (Sekunden)')
|
||||||
|
# No need to change tick formatting for seconds
|
||||||
|
elif max_time < 7200: # Less than 2 hours - show minutes
|
||||||
|
self.ax.set_xlabel('Zeit (Minuten)')
|
||||||
|
# Convert tick labels to minutes
|
||||||
|
import matplotlib.ticker as ticker
|
||||||
|
def format_minutes(x, pos):
|
||||||
|
return f'{x/60:.1f}'
|
||||||
|
self.ax.xaxis.set_major_formatter(ticker.FuncFormatter(format_minutes))
|
||||||
|
else: # Show hours
|
||||||
|
self.ax.set_xlabel('Zeit (Stunden)')
|
||||||
|
# Convert tick labels to hours
|
||||||
|
import matplotlib.ticker as ticker
|
||||||
|
def format_hours(x, pos):
|
||||||
|
return f'{x/3600:.1f}'
|
||||||
|
self.ax.xaxis.set_major_formatter(ticker.FuncFormatter(format_hours))
|
||||||
|
|
||||||
|
def clear_data(self):
|
||||||
|
"""Clear all data points"""
|
||||||
|
self.times.clear()
|
||||||
|
self.ph_values.clear()
|
||||||
|
self.start_time = None # Reset start time
|
||||||
|
self.line.set_data([], [])
|
||||||
|
self.ax.set_xlim(0, 1)
|
||||||
|
self.ax.set_ylim(0, 14)
|
||||||
|
self.ax.set_xlabel('Zeit') # Reset to default label
|
||||||
|
# Reset tick formatter to default
|
||||||
|
import matplotlib.ticker as ticker
|
||||||
|
self.ax.xaxis.set_major_formatter(ticker.ScalarFormatter())
|
||||||
|
self.canvas.draw()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
window = PHControllerGUI()
|
window = PHControllerGUI()
|
||||||
|
|||||||
@ -1,2 +1,4 @@
|
|||||||
PyQt6==6.9.0
|
PyQt6==6.7.0
|
||||||
pyserial==3.5
|
pyserial==3.5
|
||||||
|
matplotlib==3.7.2
|
||||||
|
numpy<2.0.0
|
||||||
Loading…
Reference in New Issue
Block a user