Werte filtern

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
stefan-dd
User
Beiträge: 6
Registriert: Dienstag 3. Januar 2017, 15:19

Hallo,
ich bin blutiger Anfänger und möchte meine Script für die Füllstandsmessung verfeinern. Es treten immer wieder mal Ausreiser auf, die ich herausfiltern möchte.
Ich speichere den letzten Weg ab und lese ihn beim nächsten Start wieder ein und definiere den zulässigen Wertebereit mit wegmin und wegmax.
Daraufhin habe ich eine if Schleife aufgebaut wo gut und schlecht entschieden wird. Das funktioniert alles.

Code: Alles auswählen

    if distance > wegmin and distance < wegmax:
      print("Messwert: %1.1f" % distance)
      print("gut")
      sum = sum + values[i]
      time.sleep(0.05)
    else: 
      print("Messwert: %1.1f" % distance)
      print("Fehler")
      time.sleep(0.05)
Es sollen nur die Werte aus dem 1. Zweig ausgewertet werden, wenn ein Fehler auftritt, soll der Wert nicht gewertet werden oder der Durchlauf neu beginnen.
Wie realisiert man die Wünsche?

Code: Alles auswählen

#!/usr/bin/python

import RPi.GPIO as GPIO
import time
import datetime
import sys
import os
import ftplib
import fhem

fh = fhem.Fhem("192.168.1.30")
temp = fh.get_dev_reading("OWX_28_FF2BA3821502", "temperature")
temperatur = float(temp)
print temperatur

wegalt = fh.get_dev_reading("Zisterne_Inhalt","messstrecke")
old = float(wegalt)
print old

wegmax  =  (old) + 10
print wegmax

wegmin  =  (old) - 10
print wegmin



now = time.ctime()
parsed = time.strptime(now)
zeit = time.strftime("%Y-%m-%d_%H:%M:%S", parsed)

# GPIOs fuer den US-Sensor
TRIG = 26
ECHO = 19

# Dauer Trigger-Impuls
PULSE = 0.00001

# Anzahl Messwerte fuer Mittelwertbildung
BURST = 10

# Schallgeschwindigkeit/2
SPEED_2 = 331.5

#Abstand Sensor
SENSORABSTAND = 11.5

#Wasserhoehe
WASSERHOEHE = 203

# BCM GPIO-Referenen verwenden (anstelle der Pin-Nummern)
# und GPIO-Eingang definieren
GPIO.setmode(GPIO.BCM)
GPIO.setup(TRIG,GPIO.OUT)
GPIO.setup(ECHO,GPIO.IN)
GPIO.remove_event_detect(ECHO)
GPIO.output(TRIG, False)
time.sleep(1)                   # Setup-Zeit fuer Sensor

stopp = 0                       # Variableninit
start = 0
distance = 0

def pulse():                    # Funktion zum Starten der Messung
  global start
  global stopp
  global distance

  GPIO.output(TRIG, True)       # Triggerimpuls  erzeugen
  time.sleep(PULSE)
  GPIO.output(TRIG, False)
  stopp = 0                     # Werte auf 0 setzen
  start = 0
  distance = 0                  # und Event starten


def measure(x):                 # Callback-Funktion fuer ECHO
  global start
  global stopp
  global distance
  if GPIO.input(ECHO) == 1:     # steigende Flanke, Startzeit speichern
    start = time.time()
  else:                         # fallende Flanke, Endezeit speichern
    stopp = time.time()
    delta = stopp - start       # Zeitdifferenz und Entfernung berechnen
    distance = delta * (SPEED_2 + 0.5814 * temperatur) / 0.02


def measure_range():            # Bildet Mittelwert von BURST Messungen
  values = []
  sum = 0
  for i in range(0, BURST):
    pulse()                     # Messung starten
    time.sleep(0.040)           # Warten, bis Messung zuende
    values.append(distance)     # Wert im Array speichern und aufsummieren

    if distance > wegmin and distance < wegmax:
      print("Messwert: %1.1f" % distance)
      print("gut")
      sum = sum + values[i]
      time.sleep(0.05)
    else: 
      print("Messwert: %1.1f" % distance)
      print("Fehler")
      time.sleep(0.05)
  
  return sum/BURST;             # Mittelwert zurueckgeben


GPIO.add_event_detect(ECHO, GPIO.BOTH, callback=measure)
Dist = measure_range()

inhalt = 10000 - (Dist - SENSORABSTAND) * 10000 / WASSERHOEHE
inhaltger = round (inhalt, -1)
print zeit,("Inhalt = %.0f l" % inhaltger)

distanceRet = ("%.0f" % inhaltger)        
os.system('perl /opt/fhem/fhem.pl 7072 "setreading Zisterne_Inhalt wasserpegel '+str(distanceRet)+'"')
messstrecke = ("%.0f" % distance)        
os.system('perl /opt/fhem/fhem.pl 7072 "setreading Zisterne_Inhalt messstrecke '+str(messstrecke)+'"')

time.sleep(5)
GPIO.cleanup()
print("Bye!")
Zuletzt geändert von Anonymous am Sonntag 2. Juli 2017, 14:16, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

stefan-dd hat geschrieben:ich bin blutiger Anfänger und möchte meine Script für die Füllstandsmessung verfeinern.
Wenn Du ein Anfänger bist, dann gewöhne Dir doch gleich die Verwendung von 4 Leerzeichen als Einrückung an und gewöhne Dir an, möglichst auf globale Variablen zu verzichten, denn diese sollten eine Ausnahme bleiben und nicht gehäuft verwendet werden. Was ist das für eine Python-Version, wenn sich da print mit und ohne Klammern abwechseln? Warum nimmst Du keine aktuelle Version als Anfänger?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@stefan-dd: es gibt keine if-Schleifen! ftplib importiert aber nicht verwendet. Man sollte nicht Anweisungen, Konstanten und Funktionsdefinitionen mischen; als erste kommen Importe, dann Konstanten, dann Funktionsdefinitionen, und alle Anweisungen, die jetzt noch auf oberster Ebene stehen gehören in eine Funktion, die üblicherweise main genannt wird. Zeile 28ff: warum liest Du das aktuelle Datum als String mit ctime, um es dann über strptime in eine Zahl und dann mit strftime wieder in einen String umzuwanden? Mindestens die zwei ersten Zeilen sind überflüssig. Zeile 56: nach einem setup sollte es nicht nötig sein, irgendwelche Events zu entfernen. Vergiss gleich wieder, dass es soetwas wie global gibt. Schreibe Funktionen, die Rückgabewerte haben. Für Callbacks brauchst Du eine Queue, um Werte sicher an das Hauptprogramm übergeben zu können. Zeile 86: Warum wurde SPEED2 als Konstante definiert, 0.5814 aber nicht? Zur Lesbarkeit sollten alle Zahlenwerte, die nicht auf den ersten Blick klar sind, einen Namen bekommen. Zeile 91: sum ist der Name einer häufig eingesetzten Funktion, die sollte nicht durch eine Varable überschrieben werden.Zeile 100: warum verwendest Du hier den Listeneintrag, statt die ursprüngliche Variable distance, wie in den Zeilen davor? values wird dann gar nicht mehr gebraucht und kann weg. Zeile 118/120: statt os.system und dem Zusammenformatieren einer Kommandozeile solltest Du subprocess.call_check verwenden und die Argumente als Liste übergeben. Ist der Sprachwechsel und damit verbunden der Aufruf eines externen Programms wirklich nötig, oder könnte man das, was fhem.pl macht auch in einer Pythonfunktion schreiben?
BlackJack

@stefan-dd: Erst einmal ganz wichtig: http://if-schleife.de/

Dann möchte ich die Aussage von Melewo bezüglich ``global`` verstärken. ``global`` ist so gut wie nie eine Lösung sondern so gut wie immer ein Problem. Insbesondere hier hast Du ``global`` und drei globale Variablen die zusammen gehören und nebenläufig verändert werden — das ist nicht irgendwie unschön, das ist schlicht falsch, weil das so gar nicht sicher ist. Das muss man entweder ordentlich absichern, mit den Sperrmechanismen die `threading` zur Verfügung stellt, oder einer `Queue.Queue`, oder man macht das ohne die Nebenläufigigkeit, denn wie ich das sehe löst Du die Messung ja mit einem GPIO-Pin aus, musst also nicht *ständig* auf Flanken reagieren können, sondern nur auf die nach dem Puls‽

Sonstige Anmerkungen zum Quelltext: `datetime`, `sys`, und `ftplib` werden importiert, aber nicht verwendet.

Auf Modulebene steht Programm, das sollte nicht sein. Dort sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Das die Definitionen der Konstanten mitten in Code stehen, ist unübersichtlich.

Kommentare sollten dem Leser einen Mehrwert geben und nicht einfach nur das offensichtliche wiederholen das schon im Code steht. Bei einer Konstanten `WASSERHOEHE` den Kommentar ``# Wasserhoehe`` zu schreiben ist sinnlos. Faustregel: Nicht kommentieren *was* der Code macht, denn das steht ja schon im Code, sondern *warum* er das so macht — sofern das nicht auch aus dem Code ersichtlich ist.

Manche Kommentare lassen sich auch durch bessere Namen vermeiden, in dem man beispielsweise die abstraktere Bedeutung für einen Namen wählt. Beispiel währe `pulse()` das beschreibt was technich gemacht wird, während den Leser an der Stelle vom Aufruf eher interessiert warum es gemacht wird, nämlich etwas wie `trigger_measurement()`.

`sum()` ist der Name einer eingebauten Funktion.

Arrays sind etwas anderes als Listen, darum sollte man die Begriffe nicht durcheinander bringen. Mit Arrays sind in Python in 99% der Fälle Numpy-Arrays gemeint, aber in 0% Listen.

Wenn genau das gleiche am Anfang von allen Zweigen in ``if``/``elif``/``else`` gemacht wird, dann gehört das *einmal* *vor* dieses Konstrukt. Analoges gilt für etwas das am Ende aller Zweige passiert — das gehört *einmal* *hinter* das Konstrukt.

Das Semikolon schliesst in Python keine Anweisungen ab, sondern trennt mehrere Anweisungen in einer Zeile. Da man das üblicherweise nicht macht, ist das Semikolon nur sehr selten in Python-Quelltexten zu sehen.

Wenn Du Aussreisser ignorieren möchtest, dann musst Du Die auch wirklich komplett ignorieren. Also nicht nur nicht aufaddieren, sondern auch beim berechnen des Mittelwertes nicht bei der Anzahl berücksichtigen. Warum sammelst Du die Werte eigentlich in einer Liste die dann am Ende überhaupt nicht verwendet wird?

Man muss nicht jedes Zwischenergebnis an einen Namen binden und sich dadurch dann schlechte Namen einhandeln. `weg_alt` ist ja schon nicht schlecht um den alten Weg zu beschreiben, `old` dagegen fehlt dann die Information was das altes ist. An der Stelle fällt auch die Deutsch/Englisch-Mischung auf: ist verwirrend, sollte man einheitlich machen.

`zeit` wird unnötig kompliziert erstellt. Statt erst die Zeit als Zeichenkette zu ermitteln und die dann zu parsen, hätte man gleich `localtime()` verwenden können. Und *das* könnte man dann auch gleich weglassen weil `time.strftime()` automatisch die aktuelle, lokale Zeit verwendet, wenn man nur die Formatzeichenkette angibt. `zeit` wird auch ein gutes Stück von dem Code der den Wert dann tatsächlich erst verwendet definiert. Das sollte man näher zusammenrücken.

`remove_event_detect()` ohne das vorher `add_event_detect()` aufegerufen wurde macht keinen Sinn.

Den `GPIO.cleanup()`-Aufruf sollte man mit einem ``try``/``finally`` sicherstellen.

Der Name `Dist` sollte eigentlich `distance` lauten. Was wieder ein Zeichen dafür ist warum globale Variablen so verdammt scheusslich sind. Du kommst da auch selbst Durcheinander, denn ich vermute statt dem globalen `distance` hättest Du an das Perl-Skript eigentlich `Dist` übergeben wollen.

Werte in Zeichenketten formatieren macht man heute eher mit der `format()`-Methode, statt ``%`` und mehrere Argumente für die `print()`-Funktion zu verwenden.

`os.system()` sollte man nicht verwenden. Zum Aufruf externer Programme gibt es das `subprocess`-Modul.

Ich lande dann als Zwischenergebnis (ungetestet) und mit den Problemen/Fehlern wegen der Nebenläufigkeit, ungefähr hier:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
import time
from datetime import datetime as DateTime
from functools import partial
from subprocess import call

import fhem
from RPi import GPIO

# GPIOs fuer den Ultraschall-Sensor
TRIGGER_PIN = 26
ECHO_PIN = 19
 
# Dauer Trigger-Impuls
PULSE = 0.00001
 
# Anzahl Messwerte fuer Mittelwertbildung
BURST = 10
 
# Schallgeschwindigkeit/2
SPEED_2 = 331.5
 
SENSORABSTAND = 11.5
WASSERHOEHE = 203

 
def trigger_measurement():
    """Starte Messung."""
    # 
    # FIXME Die globalen Variablen + Nebenläufigeit sind ein Fehler.
    # 
    global start
    global stopp
    global distance

    GPIO.output(TRIGGER_PIN, True)       # Triggerimpuls  erzeugen
    time.sleep(PULSE)
    GPIO.output(TRIGGER_PIN, False)
    start = stopp = distance = 0
 
 
def measure(temperatur, _pin):
    """Callback-Funktion für den :data:`ECHO_PIN`."""
    # 
    # FIXME Die globalen Variablen + Nebenläufigeit sind ein Fehler.
    # 
    global start
    global stopp
    global distance

    if GPIO.input(ECHO_PIN) == 1:
        start = time.time()
    else:
        stopp = time.time()
        delta = stopp - start
        distance = delta * (SPEED_2 + 0.5814 * temperatur) / 0.02
 
 
def measure_range(weg_min, weg_max):
    """Bilde Mittelwert von :data:`BURST` Messungen."""
    # 
    # FIXME Die globalen Variablen + Nebenläufigeit sind ein Fehler.
    # 
    count = total = 0
    for _ in range(0, BURST):
        trigger_measurement()
        time.sleep(0.04)  # Warten, bis Messung zuende ist.
        print('Messwert: {0:1.1f}'.format(distance))
        if weg_min <= distance < weg_max:
            print('gut')
            count += 1
            total += distance
        else:
            print('Fehler')
        time.sleep(0.05)
    # 
    # TODO Wie mit dem Fall umgehen das `count` 0 sein könnte?
    # 
    return total / count


def set_reading(name, value):
    # 
    # TODO Eventuell `check_call()` verwenden.
    # 
    call(
        [
            'perl',
            '/opt/fhem/fhem.pl',
            '7072',
            'setreading Zisterne_Inhalt {0} {1:.0f}'.format(name, value)
        ]
    )
 

def main():
    fh = fhem.Fhem('192.168.1.30')
    temperatur = float(fh.get_dev_reading('OWX_28_FF2BA3821502', 'temperature'))
    print(temperatur)
    weg_alt = float(fh.get_dev_reading('Zisterne_Inhalt', 'messstrecke'))
    print(weg_alt)
    weg_min, weg_max = weg_alt - 10, weg_alt + 10
     
    GPIO.setmode(GPIO.BCM)
    try:
        GPIO.setup(TRIGGER_PIN, GPIO.OUT, initial=False)
        GPIO.setup(ECHO_PIN, GPIO.IN)
        time.sleep(1)

        GPIO.add_event_detect(
            ECHO_PIN, GPIO.BOTH, callback=partial(measure, temperatur)
        )
        distance = measure_range(weg_min, weg_max)
         
        inhalt = round(
            10000 - (distance - SENSORABSTAND) * 10000 / WASSERHOEHE, -1
        )
        print(
            '{0:%Y-%m-%d_%H:%M:%S} Inhalt = {1:.0f} l'.format(
                DateTime.now(), inhalt
            )
        )
        
        set_reading('wasserpegel', inhalt)
        # 
        # TODO Oder tatsächlich das globale `distance`!?
        # 
        set_reading('messstrecke', distance)

        time.sleep(5)
    finally:
        GPIO.cleanup()
    print('Bye!')


if __name__ == '__main__':
    main()
Antworten