arduino_ph_controller/gui/main.py

252 lines
10 KiB
Python

import sys
import serial
import serial.tools.list_ports
from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
QWidget, QLabel, QPushButton, QComboBox, QDoubleSpinBox,
QGroupBox, QStatusBar, QMessageBox)
from PyQt6.QtCore import QTimer, Qt
from PyQt6.QtGui import QFont
class PHControllerGUI(QMainWindow):
def __init__(self):
super().__init__()
self.serial_connection = None
self.setWindowTitle("Arduino pH Controller")
self.setGeometry(100, 100, 600, 400)
# Central Widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# Connection Group
connection_group = QGroupBox("Verbindung")
connection_layout = QHBoxLayout()
self.port_combo = QComboBox()
self.refresh_ports()
self.connect_btn = QPushButton("Verbinden")
self.connect_btn.clicked.connect(self.toggle_connection)
connection_layout.addWidget(QLabel("Port:"))
connection_layout.addWidget(self.port_combo, 1)
connection_layout.addWidget(self.connect_btn)
connection_group.setLayout(connection_layout)
# Status Group
status_group = QGroupBox("Status")
status_layout = QHBoxLayout()
self.ph_label = QLabel("pH: --")
self.ph_label.setFont(QFont('Arial', 24))
self.ph_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.pump_status = QLabel("Pumpe: Aus")
self.pump_status.setFont(QFont('Arial', 16))
self.pump_status.setAlignment(Qt.AlignmentFlag.AlignCenter)
status_layout.addWidget(self.ph_label)
status_layout.addWidget(self.pump_status)
status_group.setLayout(status_layout)
# Control Group
control_group = QGroupBox("Steuerung")
control_layout = QVBoxLayout()
# Pump Control
pump_control_layout = QHBoxLayout()
self.pump_on_btn = QPushButton("Pumpe Ein")
self.pump_on_btn.clicked.connect(lambda: self.send_pump_command(1))
self.pump_off_btn = QPushButton("Pumpe Aus")
self.pump_off_btn.clicked.connect(lambda: self.send_pump_command(0))
pump_control_layout.addWidget(self.pump_on_btn)
pump_control_layout.addWidget(self.pump_off_btn)
# Tube Selection
tube_layout = QHBoxLayout()
self.tube_combo = QComboBox()
self.tube_combo.addItems([
"0.13 mm", "0.19 mm", "0.25 mm", "0.38 mm", "0.44 mm",
"0.51 mm", "0.57 mm", "0.64 mm", "0.76 mm", "0.89 mm",
"0.95 mm", "1.02 mm", "1.09 mm", "1.14 mm", "1.22 mm",
"1.30 mm", "1.42 mm", "1.52 mm", "1.65 mm", "1.75 mm",
"1.85 mm", "2.06 mm", "2.29 mm", "2.54 mm", "2.79 mm", "3.17 mm"
])
self.tube_combo.currentIndexChanged.connect(self.send_tube_command)
tube_layout.addWidget(QLabel("Schlauch:"))
tube_layout.addWidget(self.tube_combo, 1)
# Calibration
calibration_layout = QHBoxLayout()
self.cal_ph4_btn = QPushButton("Kalibrieren pH4")
self.cal_ph7_btn = QPushButton("Kalibrieren pH7")
self.cal_ph4_btn.clicked.connect(lambda: self.send_calibration_command(4))
self.cal_ph7_btn.clicked.connect(lambda: self.send_calibration_command(7))
calibration_layout.addWidget(self.cal_ph4_btn)
calibration_layout.addWidget(self.cal_ph7_btn)
# Auto Mode
auto_mode_layout = QHBoxLayout()
self.volume_spin = QDoubleSpinBox()
self.volume_spin.setRange(0.1, 1000)
self.volume_spin.setValue(10)
self.volume_spin.setSuffix(" ml")
self.auto_dose_btn = QPushButton("Automatisch dosieren")
self.auto_dose_btn.clicked.connect(self.start_auto_dose)
auto_mode_layout.addWidget(QLabel("Menge:"))
auto_mode_layout.addWidget(self.volume_spin)
auto_mode_layout.addWidget(self.auto_dose_btn)
# Add to control layout
control_layout.addLayout(pump_control_layout)
control_layout.addLayout(tube_layout)
control_layout.addLayout(calibration_layout)
control_layout.addLayout(auto_mode_layout)
control_group.setLayout(control_layout)
# Add groups to main layout
main_layout.addWidget(connection_group)
main_layout.addWidget(status_group)
main_layout.addWidget(control_group)
# Status Bar
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.status_bar.showMessage("Bereit")
# Timer for reading data
self.read_timer = QTimer()
self.read_timer.timeout.connect(self.read_serial_data)
self.read_timer.start(1000) # Update every second
# Enable/disable controls based on connection
self.set_controls_enabled(False)
def refresh_ports(self):
self.port_combo.clear()
ports = serial.tools.list_ports.comports()
for port in ports:
self.port_combo.addItem(port.device)
def toggle_connection(self):
if self.serial_connection and self.serial_connection.is_open:
self.close_connection()
else:
self.open_connection()
def open_connection(self):
port = self.port_combo.currentText()
if not port:
QMessageBox.critical(self, "Fehler", "Kein Port ausgewählt!")
return
try:
self.serial_connection = serial.Serial(port, 9600, timeout=1)
self.connect_btn.setText("Trennen")
self.status_bar.showMessage(f"Verbunden mit {port}")
self.set_controls_enabled(True)
# Request initial data
self.send_command("<114>") # Request pH value
self.send_command("<113>") # Request tube size
except Exception as e:
QMessageBox.critical(self, "Fehler", f"Verbindungsfehler: {str(e)}")
def close_connection(self):
if self.serial_connection and self.serial_connection.is_open:
self.serial_connection.close()
self.serial_connection = None
self.connect_btn.setText("Verbinden")
self.status_bar.showMessage("Getrennt")
self.set_controls_enabled(False)
self.ph_label.setText("pH: --")
self.pump_status.setText("Pumpe: --")
def set_controls_enabled(self, enabled):
self.pump_on_btn.setEnabled(enabled)
self.pump_off_btn.setEnabled(enabled)
self.tube_combo.setEnabled(enabled)
self.cal_ph4_btn.setEnabled(enabled)
self.cal_ph7_btn.setEnabled(enabled)
self.auto_dose_btn.setEnabled(enabled)
self.volume_spin.setEnabled(enabled)
def send_command(self, command):
if self.serial_connection and self.serial_connection.is_open:
try:
self.serial_connection.write(command.encode('utf-8'))
except Exception as e:
self.status_bar.showMessage(f"Fehler beim Senden: {str(e)}")
def send_pump_command(self, state):
if state == 1:
self.send_command("<111>") # Pump on
self.pump_status.setText("Pumpe: Ein")
else:
self.send_command("<112>") # Pump off
self.pump_status.setText("Pumpe: Aus")
def send_tube_command(self, index):
# The Arduino expects the tube index (0-25)
self.send_command(f"<11{index+3}>") # <11[3-28]>
def send_calibration_command(self, ph):
if ph == 4:
self.send_command("<114>") # Start pH4 calibration
QMessageBox.information(self, "Kalibrierung",
"pH4 Kalibrierung gestartet. Sensor in pH4-Lösung tauchen und warten bis der Wert stabil ist.")
else:
self.send_command("<115>") # Start pH7 calibration
QMessageBox.information(self, "Kalibrierung",
"pH7 Kalibrierung gestartet. Sensor in pH7-Lösung tauchen und warten bis der Wert stabil ist.")
def start_auto_dose(self):
volume = self.volume_spin.value()
# Calculate pump run time based on tube size and volume
# This would need to be implemented based on your specific setup
QMessageBox.information(self, "Automatische Dosierung",
f"Es werden {volume} ml dosiert. Bitte warten...")
# Implementation would need to send appropriate commands to Arduino
def read_serial_data(self):
if not self.serial_connection or not self.serial_connection.is_open:
return
try:
while self.serial_connection.in_waiting:
line = self.serial_connection.readline().decode('utf-8').strip()
if line.startswith('<') and line.endswith('>'):
data = line[1:-1]
if data.startswith('#'): # pH value
try:
ph_value = float(data[1:])
self.ph_label.setText(f"pH: {ph_value:.2f}")
except ValueError:
pass
elif data.startswith('P'): # Pump status
pump_state = data[1:]
self.pump_status.setText(f"Pumpe: {'Ein' if pump_state == '1' else 'Aus'}")
elif data.startswith('T'): # Tube index
tube_index = int(data[1:])
self.tube_combo.setCurrentIndex(tube_index)
except Exception as e:
self.status_bar.showMessage(f"Lesefehler: {str(e)}")
def closeEvent(self, event):
self.close_connection()
event.accept()
def main():
app = QApplication(sys.argv)
window = PHControllerGUI()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()