Sekunden bzw. Rundenzeiten aufzählen und ausgeben lassen

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Shinigami
User
Beiträge: 1
Registriert: Dienstag 7. Mai 2019, 10:55

Hey, ich habe vollgendes Skript,

Code: Alles auswählen

import time
import picamera as PiCamera
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(20,GPIO.IN)   #Lichtschranke1
GPIO.setup(21,GPIO.IN)   #Lichtschranke2

rZeit1=0.00              #Rundenzeit Bahn 1
rZeit2=0.00              #Rundenzeit Bahn 2

kamera = picamera.PiCamera()    #Kamera Einstellungen und Auflösungen anpassen
kamera.resolution = (640, 480)


print("Strg+C beendet Programm")
try:
        while True:                                     #Endlosschleife
                
            if GPIO.input(20)==1:                       #Abfrage Lichtschranke 1
                    print("Rundenzeit Bahn 1: ")        #Rundenzeit Bahn 1 in Konsole ausgeben
                    print(rZeit1)
                    print("Sekunden")
                    rZeit1=0.00                         #Rundenzeit Bahn 1 zurücksetzten               
                    
            if GPIO.input(21)==1:                       #Abfrage Lichtschranke 2
                    print("Rundenzeit Bahn 2: ")        #Rundenzeit Bahn 2 in Konsole ausgeben
                    print(rZeit2)
                    print("Sekunden")
                    rZeit2=0.00                         #Rundenzeit Bahn 2 zurücksetzen

#ab hier bitte keine änderungen vornehmen!!!!
            time.sleep(0.01)                            #0.05 Sekunden warten
            rZeit1=rZeit1+0.01                          #Rundenzeit Bahn 1 um 0.01 Sekunden erhöhen
            rZeit2=rZeit2+0.01                          #Rundenzeit Bahn 1 um 0.01 Sekunden erhöhen

except KeyboardInterrupt:
    GPIO.cleanup()
    kamera.close()
 

nun möchte ich, das wenn die Lichtschranken 4 mal geschaltet haben, soll ein Video mit der Rpi kamera gestartet werden. nun weiß ich leider nicht mehr weiter ;D
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es ist deutlich besser fuer eine solche Zeitmessung Ereignisse zu verwenden. Ausserdem solltest du RPI.GPIO in die Tonne kloppen, und das ganze mit pigpio oder wenigstens gpiozero machen, da RPI.GPIO sehr veraltet ist.

Mit pigpio wuerde man Ereignisse so erfassen:

http://abyz.me.uk/rpi/pigpio/python.html#callback

Dann enthaelt dein Code einen signifikanten Fehler: du schlaefst 0.1 Sekunden und nimmst an, dass das auch exakt die Zeit ist, die verflossen ist. Das stimmt nicht. Das OS garantiert nicht, das ein sleep praezise 0.1 Sekunde wartet. Dein Code wird also massiv divergierende Zeiten erfassen, und das abhaengig davon, was sonst so im System los ist.

Stattdessen solltest du die Zeit zum Zeitpunkt der Ausloesung bestimmen, und die Rundenzeit dann als Differenz der letzten zwei Zeitpunkte. Auch da hilft pigpio, weil es mit seinem Argument "tick" an den callback und der Funktion http://abyz.me.uk/rpi/pigpio/python.htm ... o.tickDiff es erlaubt, die Zeitdifferenz praezise zu bestimmen. Und das sogar, wenn das System unter groesserer Last steht.

Und zu guter letzt: wenn du Runden zaehlen willst, dann musst du eben Runden zaehlen. Einmal pro Bahn. Es bietet sich an, eine Klasse zu schreiben, die alles was notwendig ist fuer eine Bahn macht. Und dann pro Bahn instanziiert wird.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Shinigami: Noch ein paar Anmerkungen:

Es wäre schön wenn Du Code zeigen würdest der auch funktioniert beziehungsweise nicht an einer ganz anderen Stelle mit einer Fehlermeldung abbricht als das Problem was Du beschreibst. In Zeile 12 wird hier nämlich ein `NameError` ausgelöst, weil `picamera` nicht definiert ist. Du importierst das Modul und benennst es dabei mit ``as`` nach `PiCamera` um – was keinen Sinn macht, und auch gegen die Namenskonventionen verstösst.

Auch das ``as`` beim `GPIO`-Import macht keinen Sinn, denn Du willst das Modul beim importieren ja gar nicht umbenennen. Dafür ist ``as`` aber da.

Eingerückt wird in Python mit vier Leerzeichen pro Ebene. Nicht mehr, nicht weniger.

Wenn man die falsche Einrückung gerade rückt erkennt man auch gleich schön das Problem mit nett ausgerichteten Kommentaren am Zeilenende – die sind danach nicht mehr nett ausgerichtet und machen deswegen noch einmal zusätzliche Arbeit. Blöd ist auch wenn man eine Codezeile verändert und die dadurch so lang wird, das man in den Zeilen davor und danach die Kommentare wieder nett ausrichten muss. Das führt zu Änderungen an Zeilen an denen inhaltlich eigentlich gar nichts geändert wurde. Das versteckt in Diffs dann die eigentliche Änderung.

Durch die Kommentare sind auch viele Zeilen zu lang. Das führt dann entweder dazu dass man nicht alles lesen kann, weil rechts abgeschnitten, oder das die Kommentare unschön in die nächste Zeile umgebrochen werden, wenn man die immer noch üblichen 80 Zeichen pro Zeile anzeigen lässt (Editor, E-Mail, Webseite, Terminal, …).

Für das `GPIO.cleanup()` muss der ``try``-Block a) grösser gefasst sein, und b) gehören die Aufräumarbeiten dann auch in einen ``finally``-Zweig, denn das muss ja *immer* ausgeführt werden, zum Beispiel auch wenn im ``try`` eine andere Ausnahme als `KeyboardInterrupt` ausgelöst wird.

Für so magische Zahlen wie Pin-Nummern definiert man Konstanten. Dann lassen die sich leicht und fehlerfrei ändern, ohne das man die überall im Code suchen und ändern muss, und so eine Konstante hat dann auch einen Namen an dem der Leser ablesen kann wofür die magische Zahl eigentlich steht. Damit kann man dann auch gleich Kommentare einsparen die das erklären.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Und man sollte keine kryptischen Abkürzungen verwenden. Auch das spart wieder unnötige Kommentare. Denn wenn `rZeit1` `rundenzeit_1` heissen würde, bräuchte man nicht überall kommentieren das da etwas mit einer Rundenzeit gemacht wird.

Nummerieren von Namen ist auch nicht gut, denn in der Regel heisst das man will sich da entweder bessere Namen ausdenken, oder gar keine Einzelwerte sondern eine Datenstruktur verwenden. Oft eine Liste. In diesem Fall eigendlich eine Liste welche die von `__deets__` erwähnten Objekte enthält.

0.00 ist letztlich das gleiche wie 0. Das immer mit genau zwei Nachkommastellen zu schreiben, suggeriert eine Genauigkeit, die so nicht gegeben ist.

`PiCamera`-Objekte sind Kontextmanager – das sollte man wenn möglich mit ``with`` verwenden statt `close()` irgendwo selbst aufzurufen.

Kommentare sollten einen Mehrwert über den Code bieten. Faustregel: Kommentare beschreiben nicht *was* der Code tut, denn das steht da ja bereits als Code, sondern *warum* er das (so) tut, sofern das nicht offensichtlich ist. Bei einer ``while True:``-Schleife zu kommentieren, das es sich um eine Endlosschleife handelt ist sinnfrei.

Wirklich schlecht sind Kommentare die dem Code wiedersprechen, denn dann weiss der Leser nicht ob der Code oder der Kommentar falsch ist. Und das wo Kommentare doch dazu da sind nicht-offensichtliche, also etwas kompliziertere Sachen zu erklären. Das heisst der Leser steht dann oft auch zusätzlich vor dem Problem entscheiden zu müssen ob es nicht auch an ihm liegt das er zwischen Code und Kommentar eine Diskrepanz sieht. Oder er glaubt einfach einem falschen Kommentar und bekommt dann später Probleme wenn die Annahmen die er deswegen gemacht hat, gar nicht stimmen.

Zum Glück sieht man sehr schnell dass ``time.sleep(0.01)`` gar nicht 0.05 Sekunden wartet. Andererseits ist das auch so eine Trivialität die man nicht kommentiert.

Man sieht hier aber auch wieder ganz gut warum man keine magischen Zahlen verwenden sollte, zumindest nicht wenn man die mehr als einmal im Code stehen hat. Denn wenn man diese 0.01 ändern möchte, muss man auch wieder daran denken die überall zu ändern.

Den Rückgabewert von `GPIO.input()` kann man direkt als Wahrheitswert verwenden, den braucht man nicht noch einmal mit einer Konstanten vergleichen.

Dein Code (also *mit* den grundsätzlichen Problemen die __deets__ angesprochen hat) überarbeitet und ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import time

from picamera import PiCamera
from RPi import GPIO

PHOTO_SENSOR_PIN_A = 20
PHOTO_SENSOR_PIN_B = 21
LAP_PRECISION = 0.01  # in seconds.


def main():
    print('Strg+C beendet Programm')
    try:
        GPIO.setmode(GPIO.BCM)
        GPIO.setup([PHOTO_SENSOR_PIN_A, PHOTO_SENSOR_PIN_B], GPIO.IN)

        rundenzeit_1 = 0
        rundenzeit_2 = 0

        with PiCamera() as camera:
            camera.resolution = (640, 480)
            while True:
                if GPIO.input(PHOTO_SENSOR_PIN_A):
                    print('Rundenzeit Bahn 1: ')
                    print(rundenzeit_1)
                    print('Sekunden')
                    rundenzeit_1 = 0

                if GPIO.input(PHOTO_SENSOR_PIN_B):
                    print('Rundenzeit Bahn 2: ')
                    print(rundenzeit_2)
                    print('Sekunden')
                    rundenzeit_2 = 0

                time.sleep(LAP_PRECISION)
                rundenzeit_1 += LAP_PRECISION
                rundenzeit_2 += LAP_PRECISION

    except KeyboardInterrupt:
        pass
    finally:
        GPIO.cleanup()


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich habe mal ein kleines Beispiel gebaut fuer einen solchen Laptimer, das eigentlich mit dem pigpio Modul mehr oder minder funktionieren sollte:

Code: Alles auswählen


class MockPiGpio:

    RISING_EDGE = 1

    def __init__(self):
        self._callbacks = []
        self._tick = 0

    # pigpio functionality
    def callback(self, pin, trigger, callback):
        self._callbacks.append((pin, trigger, callback))

    def get_current_tick(self):
        return self._tick

    def tickDiff(self, earlier, later):
        return later - earlier

    # utility functions to drive the mock
    def trigger(self, pin, value, tick):
        self.tick(tick)
        for pin, trigger, callback in self._callbacks:
            if value:
                if trigger in (self.RISING_EDGE,):
                    callback(pin, value, tick)

    def tick(self, tick):
        self._tick = tick


pigpio = MockPiGpio


class LapTimer:

    def __init__(self, pi, track_gpio):
        pi.callback(22, pigpio.RISING_EDGE, self._callback)
        self._timestamps = [pi.get_current_tick()]
        self._pi = pi

    def _callback(self, gpio, level, tick):
        self._timestamps.append(tick)

    @property
    def laps(self):
        return len(self._timestamps) - 1

    @property
    def laptimes(self):
        return [
            self._pi.tickDiff(earlier, later)
            for earlier, later in zip(
                    self._timestamps[:-1],
                    self._timestamps[1:]
                )
        ]


def main():
    pi = MockPiGpio()
    track_one_timer = LapTimer(pi, 22)
    pi.trigger(22, 1, 100)
    pi.trigger(22, 1, 200)
    print(track_one_timer.laps)
    print(track_one_timer.laptimes)

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