next step
This commit is contained in:
parent
95fb45a636
commit
2103601efa
132
gui/main.py
132
gui/main.py
@ -155,6 +155,14 @@ class PHControllerGUI(QMainWindow):
|
||||
|
||||
plot_controls_layout.addWidget(self.plot_enabled_checkbox)
|
||||
plot_controls_layout.addWidget(self.clear_plot_btn)
|
||||
|
||||
# pH Filter Control
|
||||
self.ph_filter_checkbox = QCheckBox("pH-Filter (Motorstörungen)")
|
||||
self.ph_filter_checkbox.setChecked(True)
|
||||
self.ph_filter_checkbox.setToolTip("Filtert pH-Sprünge beim Motor Ein-/Ausschalten")
|
||||
self.ph_filter_checkbox.toggled.connect(self.toggle_ph_filter)
|
||||
plot_controls_layout.addWidget(self.ph_filter_checkbox)
|
||||
|
||||
plot_controls_layout.addStretch()
|
||||
|
||||
# pH Plot Widget
|
||||
@ -236,6 +244,14 @@ class PHControllerGUI(QMainWindow):
|
||||
self.total_volume = 0.0 # Total volume pumped in ml
|
||||
self.last_flow_rate = 0.0 # Last known flow rate
|
||||
|
||||
# pH filtering for motor interference
|
||||
self.ph_history = deque(maxlen=5) # Store last 5 pH values
|
||||
self.motor_state_changed_time = None # Track when motor state changed
|
||||
self.motor_interference_duration = 3.0 # seconds to filter after motor state change
|
||||
self.ph_filter_enabled = True # Enable/disable pH filtering
|
||||
self.outlier_threshold = 2.0 # pH units - values changing more than this are considered outliers
|
||||
self.interference_threshold = 1.0 # pH units - stricter threshold during motor interference
|
||||
|
||||
# Auto dosing
|
||||
self.auto_dosing = False
|
||||
self.target_volume = 0.0
|
||||
@ -354,6 +370,7 @@ class PHControllerGUI(QMainWindow):
|
||||
self.clear_titration_btn.setEnabled(enabled)
|
||||
self.titration_step_spin.setEnabled(enabled)
|
||||
self.titration_wait_spin.setEnabled(enabled)
|
||||
self.ph_filter_checkbox.setEnabled(enabled)
|
||||
|
||||
def send_command(self, command):
|
||||
print(command)
|
||||
@ -394,6 +411,9 @@ class PHControllerGUI(QMainWindow):
|
||||
self.last_flow_rate = flow_rate
|
||||
|
||||
def send_pump_command(self, state):
|
||||
# Record motor state change time for interference filtering
|
||||
self.motor_state_changed_time = datetime.datetime.now()
|
||||
|
||||
if state == 1:
|
||||
self.send_command("<11>") # Pump on
|
||||
self.pump_status.setText("Pumpe: Ein")
|
||||
@ -487,6 +507,86 @@ class PHControllerGUI(QMainWindow):
|
||||
|
||||
self.status_bar.showMessage(f"Automatische Dosierung läuft... Ziel: {volume} ml")
|
||||
|
||||
def toggle_ph_filter(self, enabled):
|
||||
"""Enable or disable pH filtering"""
|
||||
self.ph_filter_enabled = enabled
|
||||
if enabled:
|
||||
self.status_bar.showMessage("pH-Filter aktiviert - Motorstörungen werden gefiltert")
|
||||
else:
|
||||
self.status_bar.showMessage("pH-Filter deaktiviert - Rohe pH-Werte werden angezeigt")
|
||||
# Clear history when disabling filter
|
||||
self.ph_history.clear()
|
||||
|
||||
def configure_ph_filter(self, interference_duration=3.0, outlier_threshold=2.0, interference_threshold=1.0):
|
||||
"""
|
||||
Configure pH filter parameters
|
||||
|
||||
Args:
|
||||
interference_duration: Seconds to apply enhanced filtering after motor state change
|
||||
outlier_threshold: pH units - normal outlier detection threshold
|
||||
interference_threshold: pH units - stricter threshold during motor interference
|
||||
"""
|
||||
self.motor_interference_duration = interference_duration
|
||||
self.outlier_threshold = outlier_threshold
|
||||
self.interference_threshold = interference_threshold
|
||||
|
||||
if self.ph_filter_enabled:
|
||||
self.status_bar.showMessage(f"pH-Filter konfiguriert: Störzeit={interference_duration}s, "
|
||||
f"Normal={outlier_threshold}pH, Störung={interference_threshold}pH")
|
||||
|
||||
def filter_ph_value(self, ph_value):
|
||||
"""
|
||||
Filter pH values to reduce motor interference and outliers
|
||||
Returns the filtered pH value or None if value should be ignored
|
||||
"""
|
||||
# If filtering is disabled, return raw value
|
||||
if not self.ph_filter_enabled:
|
||||
return ph_value
|
||||
|
||||
current_time = datetime.datetime.now()
|
||||
|
||||
# Add to history
|
||||
self.ph_history.append(ph_value)
|
||||
|
||||
# If motor state changed recently, apply enhanced filtering
|
||||
if (self.motor_state_changed_time and
|
||||
(current_time - self.motor_state_changed_time).total_seconds() < self.motor_interference_duration):
|
||||
|
||||
# During motor interference period, use more aggressive filtering
|
||||
if len(self.ph_history) >= 3:
|
||||
# Use median filter to remove spikes
|
||||
recent_values = list(self.ph_history)[-3:]
|
||||
filtered_value = sorted(recent_values)[1] # Median of last 3 values
|
||||
|
||||
# Additional outlier detection during interference period
|
||||
if len(self.ph_history) >= 5:
|
||||
older_values = list(self.ph_history)[:-1] # All except current
|
||||
avg_older = sum(older_values) / len(older_values)
|
||||
|
||||
# If current value deviates more than threshold from recent average
|
||||
if abs(ph_value - avg_older) > self.interference_threshold:
|
||||
# Use the median instead of the raw value
|
||||
return filtered_value
|
||||
|
||||
return filtered_value
|
||||
else:
|
||||
# Not enough history during interference, return current value
|
||||
return ph_value
|
||||
|
||||
# Normal operation - light filtering
|
||||
if len(self.ph_history) >= 2:
|
||||
# Simple outlier detection
|
||||
previous_value = self.ph_history[-2]
|
||||
|
||||
# If change is more than threshold, it's likely an outlier
|
||||
if abs(ph_value - previous_value) > self.outlier_threshold:
|
||||
# Use moving average of last few values instead
|
||||
if len(self.ph_history) >= 3:
|
||||
recent_values = list(self.ph_history)[:-1] # Exclude current outlier
|
||||
return sum(recent_values) / len(recent_values)
|
||||
|
||||
return ph_value
|
||||
|
||||
def read_serial_data(self):
|
||||
if not self.serial_connection or not self.serial_connection.is_open:
|
||||
return
|
||||
@ -499,15 +599,29 @@ class PHControllerGUI(QMainWindow):
|
||||
data = line[1:-1]
|
||||
if data.startswith('#'): # pH value
|
||||
try:
|
||||
ph_value = float(data[1:])
|
||||
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)
|
||||
# Add to titration plot if titration is active
|
||||
if self.titration_active or self.auto_titration_active:
|
||||
titrated_volume = self.total_volume - self.titration_start_volume
|
||||
self.titration_plot.add_titration_point(titrated_volume, ph_value)
|
||||
raw_ph_value = float(data[1:])
|
||||
|
||||
# Apply filtering to reduce motor interference
|
||||
filtered_ph_value = self.filter_ph_value(raw_ph_value)
|
||||
|
||||
if filtered_ph_value is not None:
|
||||
# Update display with filtered value
|
||||
self.ph_label.setText(f"pH: {filtered_ph_value:.2f}")
|
||||
|
||||
# Show filtering status if motor interference is active
|
||||
if (self.motor_state_changed_time and
|
||||
(datetime.datetime.now() - self.motor_state_changed_time).total_seconds() < self.motor_interference_duration):
|
||||
if abs(raw_ph_value - filtered_ph_value) > 0.1:
|
||||
self.status_bar.showMessage(f"pH gefiltert: {raw_ph_value:.2f} → {filtered_ph_value:.2f} (Motorstörung)")
|
||||
|
||||
# Add to plot if recording is enabled
|
||||
if self.plot_enabled_checkbox.isChecked():
|
||||
self.ph_plot.add_ph_value(filtered_ph_value)
|
||||
|
||||
# Add to titration plot if titration is active
|
||||
if self.titration_active or self.auto_titration_active:
|
||||
titrated_volume = self.total_volume - self.titration_start_volume
|
||||
self.titration_plot.add_titration_point(titrated_volume, filtered_ph_value)
|
||||
except ValueError:
|
||||
pass
|
||||
elif data.startswith('P'): # Pump status
|
||||
|
||||
88
pH_Filter_Dokumentation.md
Normal file
88
pH_Filter_Dokumentation.md
Normal file
@ -0,0 +1,88 @@
|
||||
# pH-Filter für Motorstörungen - Dokumentation
|
||||
|
||||
## Problem
|
||||
Beim Ein- und Ausschalten des Pumpmotors können kurzzeitige starke pH-Sprünge auftreten. Diese sind meist durch elektromagnetische Störungen (EMI) oder Spannungsspitzen verursacht und verfälschen die Messwerte.
|
||||
|
||||
## Lösung
|
||||
Die Software wurde um einen intelligenten pH-Filter erweitert, der solche Störungen automatisch erkennt und filtert.
|
||||
|
||||
## Features
|
||||
|
||||
### 1. Automatische Störungserkennung
|
||||
- Der Filter erkennt automatisch, wann der Motor ein- oder ausgeschaltet wird
|
||||
- Für 3 Sekunden nach einer Motorschaltung wird verstärkte Filterung angewendet
|
||||
|
||||
### 2. Mehrstufige Filterung
|
||||
|
||||
#### Normale Betriebszeit:
|
||||
- **Ausreißer-Erkennung**: pH-Sprünge > 2.0 Einheiten werden als Ausreißer erkannt
|
||||
- **Gleitender Mittelwert**: Ausreißer werden durch den Durchschnitt der letzten Werte ersetzt
|
||||
|
||||
#### Motor-Störzeit (3 Sekunden nach Schalten):
|
||||
- **Median-Filter**: Verwendet den Median der letzten 3 Werte
|
||||
- **Verschärfte Schwelle**: pH-Sprünge > 1.0 Einheiten werden gefiltert
|
||||
- **Status-Anzeige**: Zeigt in der Statusleiste an, wenn gefiltert wird
|
||||
|
||||
### 3. Benutzersteuerung
|
||||
- **Ein/Aus-Schalter**: Checkbox "pH-Filter (Motorstörungen)" im Plot-Bereich
|
||||
- **Echtzeit-Feedback**: Status wird in der Statusleiste angezeigt
|
||||
- **Transparenz**: Zeigt sowohl rohe als auch gefilterte Werte an
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### Standard-Parameter:
|
||||
```python
|
||||
# Störungszeit nach Motorschaltung
|
||||
motor_interference_duration = 3.0 # Sekunden
|
||||
|
||||
# Normale Ausreißer-Schwelle
|
||||
outlier_threshold = 2.0 # pH-Einheiten
|
||||
|
||||
# Verschärfte Schwelle während Störzeit
|
||||
interference_threshold = 1.0 # pH-Einheiten
|
||||
```
|
||||
|
||||
### Anpassung der Parameter:
|
||||
```python
|
||||
# Beispiel für empfindlichere Filterung
|
||||
gui.configure_ph_filter(
|
||||
interference_duration=5.0, # Längere Störzeit
|
||||
outlier_threshold=1.5, # Empfindlichere normale Filterung
|
||||
interference_threshold=0.5 # Sehr empfindliche Störzeit-Filterung
|
||||
)
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
1. **Aktivierung**: Checkbox "pH-Filter (Motorstörungen)" aktivieren (Standard: Ein)
|
||||
2. **Betrieb**: Filter arbeitet automatisch im Hintergrund
|
||||
3. **Überwachung**: Statusleiste zeigt Filteraktivität an
|
||||
4. **Deaktivierung**: Checkbox deaktivieren für ungefilterte Rohwerte
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Algorithmus:
|
||||
1. **Wertspeicher**: Letzte 5 pH-Werte werden gespeichert
|
||||
2. **Zeitüberwachung**: Motor-Schaltzeit wird aufgezeichnet
|
||||
3. **Dynamische Filterung**:
|
||||
- Normal: Einfache Ausreißer-Erkennung
|
||||
- Störzeit: Median-Filter + verschärfte Schwellen
|
||||
|
||||
### Vorteile:
|
||||
- ✅ Reduziert falsche pH-Sprünge erheblich
|
||||
- ✅ Erhält echte pH-Änderungen
|
||||
- ✅ Transparente Anzeige der Filterung
|
||||
- ✅ Ein-/ausschaltbar je nach Bedarf
|
||||
- ✅ Konfigurierbare Parameter
|
||||
|
||||
### Anwendungsfälle:
|
||||
- **Titrationsmessungen**: Vermeidet falsche Äquivalenzpunkte
|
||||
- **Kontinuierliche Überwachung**: Glättere pH-Kurven
|
||||
- **Automatische Dosierung**: Zuverlässigere pH-Regelung
|
||||
|
||||
## Beispiel-Ausgabe
|
||||
```
|
||||
Status: pH gefiltert: 8.45 → 7.82 (Motorstörung)
|
||||
```
|
||||
|
||||
Dies zeigt, dass ein pH-Sprung von 8.45 auf den gefilterten Wert 7.82 reduziert wurde.
|
||||
Loading…
Reference in New Issue
Block a user