next step

This commit is contained in:
jens 2025-07-13 21:01:19 +02:00
parent 95fb45a636
commit 2103601efa
2 changed files with 211 additions and 9 deletions

View File

@ -155,6 +155,14 @@ class PHControllerGUI(QMainWindow):
plot_controls_layout.addWidget(self.plot_enabled_checkbox) plot_controls_layout.addWidget(self.plot_enabled_checkbox)
plot_controls_layout.addWidget(self.clear_plot_btn) 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() plot_controls_layout.addStretch()
# pH Plot Widget # pH Plot Widget
@ -236,6 +244,14 @@ class PHControllerGUI(QMainWindow):
self.total_volume = 0.0 # Total volume pumped in ml self.total_volume = 0.0 # Total volume pumped in ml
self.last_flow_rate = 0.0 # Last known flow rate 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 # Auto dosing
self.auto_dosing = False self.auto_dosing = False
self.target_volume = 0.0 self.target_volume = 0.0
@ -354,6 +370,7 @@ class PHControllerGUI(QMainWindow):
self.clear_titration_btn.setEnabled(enabled) self.clear_titration_btn.setEnabled(enabled)
self.titration_step_spin.setEnabled(enabled) self.titration_step_spin.setEnabled(enabled)
self.titration_wait_spin.setEnabled(enabled) self.titration_wait_spin.setEnabled(enabled)
self.ph_filter_checkbox.setEnabled(enabled)
def send_command(self, command): def send_command(self, command):
print(command) print(command)
@ -394,6 +411,9 @@ class PHControllerGUI(QMainWindow):
self.last_flow_rate = flow_rate self.last_flow_rate = flow_rate
def send_pump_command(self, state): 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: if state == 1:
self.send_command("<11>") # Pump on self.send_command("<11>") # Pump on
self.pump_status.setText("Pumpe: Ein") 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") 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): def read_serial_data(self):
if not self.serial_connection or not self.serial_connection.is_open: if not self.serial_connection or not self.serial_connection.is_open:
return return
@ -499,15 +599,29 @@ class PHControllerGUI(QMainWindow):
data = line[1:-1] data = line[1:-1]
if data.startswith('#'): # pH value if data.startswith('#'): # pH value
try: try:
ph_value = float(data[1:]) raw_ph_value = float(data[1:])
self.ph_label.setText(f"pH: {ph_value:.2f}")
# Add to plot if recording is enabled # Apply filtering to reduce motor interference
if self.plot_enabled_checkbox.isChecked(): filtered_ph_value = self.filter_ph_value(raw_ph_value)
self.ph_plot.add_ph_value(ph_value)
# Add to titration plot if titration is active if filtered_ph_value is not None:
if self.titration_active or self.auto_titration_active: # Update display with filtered value
titrated_volume = self.total_volume - self.titration_start_volume self.ph_label.setText(f"pH: {filtered_ph_value:.2f}")
self.titration_plot.add_titration_point(titrated_volume, ph_value)
# 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: except ValueError:
pass pass
elif data.startswith('P'): # Pump status elif data.startswith('P'): # Pump status

View 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.