Endlosschleife Python Firmware Skript nach scannen eines RFID Tags

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Kreckelneckel
User
Beiträge: 1
Registriert: Donnerstag 18. Mai 2023, 20:15

Hallo liebe Forenmitglieder,
Mein Projekt für mein Fachabi ist gerade im Endspurt. Es geht um ein Reservationssystem wo man Meetingräume reservieren kann und wo nur die wo den Meetingraum reserviert haben die Möglichkeit haben die Tür des Meetingraumes mittels eines RFID-Tags zu öffnen. Es handelt sich um eine Holztür mit einem raspi 4b welcher an eine Leiterplatte gebunden ist. Hällt man die Karte oder den RFID Tag gegen den MFRC522 RFID Scanner so öffnet sich die Tür insofern man für den Raum zugelassen ist.
Ich bin mittlerweile so weit dass sich die Tür öffnen lässt allerdings führt sich das Skript in endlosschleife aus sobald ich entweder den RFID Tag oder die Karte gegen den Scanner halte. Das heisst, halte ich die Karte gegen und ich werden zu dem Raum zugelassen so geht die LED an, es piepst 3 mal, das Magnetschloss öffnet sich, nach 5 Sekunden schliesst es sich wieder und das ganze geht von vorne los. Ich kann dann auch keine karten mehr scannen. Damit das skript wieder normal läuft muss ich foo.service (systemd welche die Firmware automatisch startet) auf dem Raspi stoppen.

Kann mir vielleicht jemand helfen?

Hier der Python Code der Firmware:

Code: Alles auswählen

#!/usr/bin/env python

import RPi.GPIO as GPIO
from mfrc522 import SimpleMFRC522

import requests, json, socket
import time, board, neopixel

WEBSERVICE_ENDPOINT = "http://192.168.178.98/includes/controller.php"
HTTP_REQUEST_TIMEOUT_SECONDS = 5    # number of seconds before interrupting http request

RGB_LED_PIN = board.D18             # data in pin of first rgb led
RGB_NUM_LEDS = 2                    # number of rgb leds chained together
RGB_ORDER = neopixel.RGB            # pin ordering of rgb leds

SOLENOID_PIN = 5                    # pin where the solenoid lock is connected to
SOLENOID_TIMEOUT = 5                # time in seconds that the lock stays open

EMERGENCY_BUTTON_PIN = 17           # pin where the emergency button is connected to

BUZZER_PIN = 6                      # buzzer pin
BUZZER_TIMEOUT = 0.05               # time in seconds between two beeps


# this function uses the beeper to emit a number of short beeps
def beep( nbrOfBeeps ):
    for i in range(0, nbrOfBeeps):
        # enable buzzer
        GPIO.output(BUZZER_PIN, GPIO.HIGH)
        time.sleep(BUZZER_TIMEOUT)

        # disable buzzer
        GPIO.output(BUZZER_PIN, GPIO.LOW)
        time.sleep(BUZZER_TIMEOUT)

# this function displays a color on the first rgb led
def showColor( color ):
    pixels[0] = color
    pixels.show()

# this function opens the solenoid lock
def openLock():
    GPIO.output(SOLENOID_PIN, GPIO.HIGH)

# this function closes the solenoid lock
def closeLock():
    GPIO.output(SOLENOID_PIN, GPIO.LOW)

# this function sends a GET request to the webservice endpoint with optional http parameters
def getJSON( httpParameters={} ):
    payload = httpParameters
    print("sending request to ", WEBSERVICE_ENDPOINT, " with rfid=\'", httpParameters['rfid'], "\' and hostname=\'", httpParameters['hostname'], "\'", sep='')
    response = requests.get(WEBSERVICE_ENDPOINT, params=payload, timeout=HTTP_REQUEST_TIMEOUT_SECONDS)
    data = response.json()
    return data


# this function returns whether the rfid batch passed as parameter can open the lock
def canRfidOpenLock( rfid ):
    #prepare http parameters with rfid and hostname
    payload = {'rfid': rfid, 'hostname': socket.gethostname()}

    # send http request to the webservice endpoint
    data = getJSON(payload)

    # check whether json response contains the name "allowed" and see if its value is true
    if ("allowed" in data and data['allowed']):
        return True
    else:
        return False

# this function defines the routine when the emergency button has been pressed
def emergencyButtonRoutine( channel ):
    print("Emergency button pressed!")
    beep( 3 )
    permissionGrantedRoutine()

# this function defines the routine when an exception occurs such as:
# webservice not reachable, http timeout, problem parsing json data ...
def runExceptionRoutine():
    print("Exception happened!")
    beep(3)
    for i in range(0, 3):
        showColor((255, 0, 0)) # show red on first rgb led
        time.sleep(0.5)
        showColor((0, 0, 0))  # turn first rgb off
        time.sleep(0.5)




# this function defines the routine when the firmware is launched
def runStartupRoutine():

    closeLock()

    for i in range(0, 5):
        showColor((0, 255, 0)) # show green on first rgb led
        time.sleep(0.5)
        showColor((0, 0, 0))  # turn first rgb off
        time.sleep(0.5)

# this function defines the routine when permission has been granted to open the lock
def permissionGrantedRoutine():
    print("door access granted")
    showColor((0, 255, 0))
    beep(1)
    openLock()
    time.sleep(SOLENOID_TIMEOUT)
    closeLock()
    showColor((0, 0, 0))

# this function defines the routine when permission has been denied to open the lock
def permissionDeniedRoutine():
    print("door access denied")
    beep(2);
    showColor((255, 0, 0))
    time.sleep(2.0)
    beep(2)
    showColor((0, 0, 0))

# use BCM numbering (GPIO numbers instead of hardware pins)
GPIO.setmode(GPIO.BCM)

# activate the RFID reader
reader = SimpleMFRC522()

# activate the RGB leds
pixels = neopixel.NeoPixel(RGB_LED_PIN, RGB_NUM_LEDS, brightness=0.5, auto_write=False, pixel_order=RGB_ORDER)

# setup the solenoid pin as output pin
GPIO.setup(SOLENOID_PIN, GPIO.OUT)

# setup the buzzer pin as output pin
GPIO.setup(BUZZER_PIN, GPIO.OUT)

# setup the emergency button pin as input pin with pulldown resistor
GPIO.setup(EMERGENCY_BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

# define interrupt when the button is pressed with a bouncing time of 100msecs
GPIO.add_event_detect(EMERGENCY_BUTTON_PIN, GPIO.RISING, callback=emergencyButtonRoutine, bouncetime=100)

try:
    runStartupRoutine()
    while True:
        #id, text = reader.read() # try to read an rfid batch. this is blocking!

        # we use the non-blocking read so we can still react on the temperature values
        id, text = reader.read_no_block() # try to read an rfid batch. this is not blocking!

        # check if we could read an rfid batch
  if (id != None):
            # HACK ALERT: NeoPixel code when executed at boot crashes when user logs in
            # Source: https://www.reddit.com/r/raspberry_pi/comments/rj03vk/systemd_service_python_script_stops_after_ssh/.compact
            # Solution is to recreate NeoPixel object
            pixels.deinit()
            pixels = neopixel.NeoPixel(RGB_LED_PIN, RGB_NUM_LEDS, brightness=0.5, auto_write=False, pixel_order=RGB_ORDER)

            print("RFID detected: ", id)
            showColor((0, 0, 255)) # show blue on first rgb led
            time.sleep(1)
            try:
                answer = canRfidOpenLock(id) # ask webservice if door can be unlocked
                if (answer):
                    permissionGrantedRoutine() # open lock
                else:
                    permissionDeniedRoutine() # keep lock closed

            except requests.exceptions.RequestException as err: # HTTP exceptions
                print("Connection error: ", str(err))
                runExceptionRoutine()
            except ValueError as err: # JSON parsing exceptions
                print("Error parsing json: ", str(err))
                runExceptionRoutine()

        time.sleep(0.5) # sleep to reduce cpu load
except:
    GPIO.cleanup()
    raise
    
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Kreckelneckel: ``as`` beim Importieren ist zum umbenennen, `GPIO` wird aber gar nicht umbenannt.

Üblich ist eine `import`-Anweisung pro Modul.

Das `json`-Modul wird importiert, aber nirgends verwendet.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Man sollte bei jedem Kommentar auch überlegen ob man den Code nicht so schreiben kann, das sich eine extra Erklärung erübrigt. Statt beispielsweise zu kommentieren welche Farbe da angezeigt wird, wäre es besser statt ”magischer” Zahlen, sinnvoll benannte Konstanten für die Farben zu definieren.

Die Kommentare bei den Funktionen sollten eher Docstrings sein. Und da sollte nicht noch einmal drin stehen, dass es sich um eine Funktion handelt. Das sieht man doch am Code.

Kommentare sollen Fragen klären, nicht neue aufwerfen. In dem Code werden keine Temperaturen verarbeitet, entsprechend verwirrend ist der Kommentar dazu.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Davon ist dann `showColor()` direkt betroffen, aber auch andere Funktionen indirekt, weil Funktionen (und Methoden) alles was sie ausser Konstanten benötigen, als Argument(e) übergeben bekommen.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus bestehen. Der Name soll dem Leser vermitteln was der Wert dahinter im Programm bedeutet, nicht zum rätseln zwingen.

`id` ist der Name einer eingebauten Funktion, den sollte man nicht an einen anderen Wert binden.

Um ``if``-Bedingungen gehören keine unnötigen Klammern und Anweisungen werden nicht mit einem Semikolon abgeschlossen.

Der Default-Startwert von `range()` ist 0.

Per Konvention wird der Name `_` verwendet, wenn man aus syntaktischen Gründen einen Namen schreiben muss, den Wert aber nicht benötigt. Beispielsweise für eine Laufvariable in einer Schleife wo es nur auf die Anzahl der Wiederholungen ankommt.

Das ``try`` im Hauptprogramm sollte alles umfassen das etwas mit `GPIO` anstellt und das ``except`` bastelt hier ein ``finally`` nach, nur umständlicher und nicht so leicht als solches erkennbar.

Der Namenszusatz `routine` bei Funktionen ist unnütz, denn am Ende ist ja jede Funktion so etwas wie eine Routine, also bringt das dem Leser keinen Mehrwert.

Die `getJSON()`-Funktion ist überflüssig. Der Defaultwert für das Argument ist ausserdem unsinnig, denn man *muss* da was übergeben, denn *der* Wert wird unweigerlich zu einem `KeyError` führen. Womit die Behauptung „optional“ im Kommentar falsch ist. Es macht auch keinen Sinn ein Argument an einen weiteren Namen zu binden um dann den selben Wert unter zwei verschiedenen Namen anzusprechen. Ganz grundsätzlich muss man auch nicht jeden kleinen Zwischenwert an einen Namen binden.

Wenn man ein ``if``/``else`` hat wo in den beiden Zweigen einfach nur `True` und `False` zurückgegeben werden, dann ist das ``if``/``else`` sinnlos, denn die ``if``-Bedingung ist ja bereits der Wert den man zurückgeben will.

Bei der Antwort vom HTTP-Server sollte man prüfen ob es keine Fehlermeldung war.

Das `print()` mit den vielen \' und ``sep=""`` ist ziemlich schlecht lesbar. Dafür gibt es f-Zeichenkettenliterale.

Ein ``try``-Block sollte so wenig wie möglich enthalten, insbesondere wenn dann in einem ``except`` so etwas supergenerisches wie `ValueError` behandelt wird. Das könnte ja von überall aus dem ``try``-Block kommen.

Die LED wird zweimal zum blinken gebracht, mit sehr ähnlichem Code. Das wäre ein Kandidat für eine weitere Funktion.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import socket
import time
from functools import partial

import board
import neopixel
import requests
from mfrc522 import SimpleMFRC522
from RPi import GPIO

WEBSERVICE_ENDPOINT = "http://192.168.178.98/includes/controller.php"
HTTP_REQUEST_TIMEOUT = 5

RGB_LED_PIN = board.D18
RGB_LED_COUNT = 2
RGB_ORDER = neopixel.RGB

LOCK_PIN = 5
LOCK_OPEN_TIME = 5  # in seconds

EMERGENCY_BUTTON_PIN = 17

BUZZER_PIN = 6
BUZZER_DURATION = 0.05  # in seconds

BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)

def beep(count):
    for _ in range(count):
        try:
            GPIO.output(BUZZER_PIN, GPIO.HIGH)
            time.sleep(BUZZER_DURATION)
        finally:
            GPIO.output(BUZZER_PIN, GPIO.LOW)
            time.sleep(BUZZER_DURATION)


def show_color(pixels, color):
    """
    Display a color in the first RGB LED.
    """
    pixels[0] = color
    pixels.show()


def blink_led(pixels, color, count):
    for _ in range(count):
        show_color(pixels, color)
        time.sleep(0.5)
        show_color(pixels, BLACK)
        time.sleep(0.5)


open_lock = partial(GPIO.output, LOCK_PIN, GPIO.HIGH)
close_lock = partial(GPIO.output, LOCK_PIN, GPIO.LOW)


def startup(pixels):
    close_lock()
    blink_led(pixels, GREEN, 5)


def is_user_allowed_to_open(rfid):
    hostname = socket.gethostname()
    print(
        f"sending request to {WEBSERVICE_ENDPOINT} with {rfid=} and {hostname=}"
    )
    response = requests.get(
        WEBSERVICE_ENDPOINT,
        params={"hostname": hostname, "rfid": rfid},
        timeout=HTTP_REQUEST_TIMEOUT,
    )
    response.raise_for_status()
    return response.json().get("allowed", False)


def show_error(pixels):
    print("Exception happened!")
    beep(3)
    blink_led(pixels, RED, 3)


def grant_access(pixels):
    print("door access granted")
    show_color(pixels, GREEN)
    beep(1)
    open_lock()
    time.sleep(LOCK_OPEN_TIME)
    close_lock()
    show_color(pixels, BLACK)


def deny_access(pixels):
    print("door access denied")
    beep(2)
    show_color(pixels, RED)
    time.sleep(2)
    beep(2)
    show_color(pixels, BLACK)


def on_emergency_button(pixels, _channel=None):
    print("Emergency button pressed!")
    beep(3)
    grant_access(pixels)


def main():
    try:
        GPIO.setmode(GPIO.BCM)
        reader = SimpleMFRC522()
        pixels = neopixel.NeoPixel(
            RGB_LED_PIN,
            RGB_LED_COUNT,
            brightness=0.5,
            auto_write=False,
            pixel_order=RGB_ORDER,
        )
        GPIO.setup([LOCK_PIN, BUZZER_PIN], GPIO.OUT)
        GPIO.setup(EMERGENCY_BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
        GPIO.add_event_detect(
            EMERGENCY_BUTTON_PIN,
            GPIO.RISING,
            callback=partial(on_emergency_button, pixels),
            bouncetime=100,
        )
        startup(pixels)
        while True:
            rfid, _ = reader.read()
            if rfid is not None:
                print("RFID detected:", rfid)
                show_color(pixels, BLUE)
                time.sleep(1)
                try:
                    is_allowed = is_user_allowed_to_open(rfid)
                except requests.exceptions.RequestException as error:
                    print("Connection error:", error)
                    show_error(pixels)
                except ValueError as error:
                    print("Error parsing json:", error)
                    show_error(pixels)
                else:
                    if is_allowed:
                        grant_access(pixels)
                    else:
                        deny_access(pixels)

    finally:
        GPIO.cleanup()


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten