While Schleife neu beginnen

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
alfchr7
User
Beiträge: 5
Registriert: Sonntag 15. April 2018, 13:19

Hallo,
ich bin relativ neu in Sachen Python-Programmierung und stoße deshalb vermutlich gerade an meine Grenzen. Ich bin mir sicher für einige von euch wird es ein Leichtes sein, mir bei meinem Problem zu helfen.
Ich programmiere zur Zeit eine Art Anwesenheitskontrolle. Die Person legt einen RFID Chip auf und das Skript vermerkt die entsprechende Anwesenheit in einer SQLite Datenbank.
Außerdem werden die Daten in eine Log-Datei geschrieben. Das Skript soll vor dem vermerken der Anwesenheit immer prüfen, ob die RFID ID bereits in der log-Datei steht und falls ja, soll die Schleife abbrechen und auf den nächsten RFID Chip gewartet werden.
Da meine bestelltes RFID Modul noch nicht angekommen ist, ich aber trotzdem schon mal beginnen wollte, gebe ich die entsprechende ID zur Zeit per Hand ein.
Mein Problem ist es nun, aus der Schleife auszubrechen und auf die neue ID zu warten. In meinem Skript löse ich das zur Zeit mit sys.exit(), wodurch aber das ganze Skript beendet wird. Mit break und continue habe ich bereits etwas herumprobiert, allerdings wird der weitere Code dann anschließend trotzdem ausgeführt. Wie kann ich das in diesem Falle vernünftig lösen?

Vielen Dank im Voraus für eure Hilfe!

Code: Alles auswählen

import sqlite3
import time
import sys
import winsound
db = sqlite3.connect('sql-test.db')
c = db.cursor()
timestamp = (time.strftime("%H:%M:%S"))
x = (time.strftime("%d%m%Y"))
y = x.replace(".","")
tagesdatum = "P"+y

while 1:
    #RFID ID erhalten
    rfid = input("RFID: ")

    #RFID in Log pruefen
    logr = open(""+tagesdatum+".log", "r")
    for line in logr:
        if rfid in line.rstrip():
            print("Du bist bereits anwesend gemeldet")
            winsound.Beep(2500, 900) #erzeugt Ton zur visualisierung des Fehlers
            sys.exit() #Programm stoppen, falls User RFID bereits in log

    #RFID in Log schreiben
    logw = open(""+tagesdatum+".log", "a")
    name = c.execute("SELECT Vorname,Nachname FROM attendance WHERE RFID = '"+rfid+"';").fetchone()
    logw.write(time.strftime("%d.%m.%Y") + " | " + timestamp + " | ID: "+rfid+" | Name: "+str(name[0])+" "+str(name[1])+""'\n')
    logw.close()

    #Anwesenheit in DB loggen
    c.execute("UPDATE attendance SET "+tagesdatum+" = '1' WHERE RFID = '"+rfid+"';")
    name = c.execute("SELECT Vorname,Nachname FROM attendance WHERE RFID = '"+rfid+"';").fetchone()
    print("Hallo "+str(name[0])+" "+str(name[1])+"!")
    print("Du hast dich erfolgreich anwesend gemeldet.")
    winsound.Beep(2500, 100) #erzeugt Ton zur visualisierung des erfolgreichen ausfuehrens


#DB schliessen
db.commit()
c.close()
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@alfchr7: log-Dateien sind nur dazu da, Dinge zu protokollieren, um Statistiken zu machen, oder Fehler zu suchen, aber nicht, um einen Status zu speichern und abzufragen. Dazu gibt es die Datenbank.

Das Datum fragst Du ständig ab, die Uhrzeit aber nur bei Programmstart? Macht bei einer Log-Datei wenig Sinn, da man ja den genauen Zeitpunkt gerne wissen möchte.

Dein Datenbank-Design ist schlecht. Man kodiert nicht Informationen in die Feldnamen, hier also das Tagesdatum. Sondern man macht einen Eintrag für jeden Stempelvorgang. Hier also eine Tabelle mit RFID und Zeitpunkt, mehr braucht es nicht.

Niemals Parameter in SQL-Statements per + hineinformatieren, dafür gibt es Patzhalter (die aber bei Spaltennamen nicht funktionieren, weil es nicht vorgesehen ist, dass die variabel sind).

Für Dein eigentliches Problem brauchst Du Funktionen.

Das ganz könnte dann so aussehen:

Code: Alles auswählen

import sqlite3
from datetime import datetime as DateTime
import winsound

LOGFILE = "{%Y%m%d}.log"

def write_log(now, rfid, name):
    with open(LOGFILE.format(now), "a") as logw:
        logw.write("{0:%d.%m.%Y} | {0:%H:%M:%S} | ID: {1} | Name: {2[0]} {2[1]}\n".format(now, rfid, name))

def login(rfid):
    now = DateTime.now()
    db = sqlite3.connect('sql-test.db')
    c = db.cursor()
    c.execute("SELECT Vorname, Nachname FROM user WHERE RFID = ?", [rfid])
    name = c.fetchone()

    c.execute("SELECT max(Zeitpunkt) FROM attendance WHERE RFID = ?", [rfid])
    zeitpunkt = c.fetchone()
    # hier der Test, ob heute schon eingeloggt.
    if now.date() == zeitpunkt.date():
        result = False
    else:
        c.execute("INSERT INTO attendance (RFID, Zeitpunkt) VALUES (?, ?)", [rfid, now])
        db.commit()
        write_log(now, rfid, name)
        result = True

    print("Hallo {} {}!".format(*name))
    if result:
        print("Du hast dich erfolgreich anwesend gemeldet.")
    else:
        print("Du bis bereits anwesend gemeldet.")
    return True

while True:
    #RFID ID erhalten
    rfid = input("RFID: ")
    if login(rfid):
        winsound.Beep(2500, 100) #erzeugt Ton zur visualisierung des erfolgreichen ausfuehrens
alfchr7
User
Beiträge: 5
Registriert: Sonntag 15. April 2018, 13:19

Vielen Dank für deine ausführliche Hilfe!
Das mit der Datenbank kam mir auch irgendwie falsch vor, ich konnte den Fehler allerdings nicht erkennen. Ich habe nun zwei Tabellen angelegt (user und attendance) und diese entsprechend deiner Codevorlage entworfen.
Nachdem ich mich nun mit deinem Code auseinandergesetzt habe, glaube ich auch, diesen zu verstehen. Beim Testen erhalte ich jedoch noch die beiden folgenden Fehler:

Code: Alles auswählen

Traceback (most recent call last):
  File "G:/Programme (x86)/IntelliJ/PyCharm/Projects/forum.py", line 42, in <module>
    if login(rfid):
  File "G:/Programme (x86)/IntelliJ/PyCharm/Projects/forum.py", line 23, in login
    if now.date() == zeitpunkt.date():
AttributeError: 'tuple' object has no attribute 'date'
Beim ersten Fehler weiß ich gar nicht woran das liegen könnte.
Der zweite hat, laut google, etwas mit einem nicht vorhandene import zu tun. Allerdings wurde ja datetime importiert.

Vielleicht könntest du (oder jemand anderes) mir noch kurz erklären, wie ich diese Fehler vermeiden kann.
Vielen Dank!
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Abfrage des Zeitpunkts liefert auch ein Tuple, daher muß man das entpacken:

Code: Alles auswählen

zeitpunkt, = c.fetchone()
alfchr7
User
Beiträge: 5
Registriert: Sonntag 15. April 2018, 13:19

Nach ein wenig herumprobieren habe ich nun eine zufriedenstellende Lösung gefunden und kann auf Basis deine Codes das Projekt weiterentwickeln. Danke!
alfchr7
User
Beiträge: 5
Registriert: Sonntag 15. April 2018, 13:19

Hallo!

Nach langer Zeit melde ich mich jetzt noch ein mal zurück. Durch einige Bestellungen aus Asien, die leider lange auf sich haben warten lassen, geriet mein Projekt zwischenzeitlich etwas ins Stocken. Inzwischen habe ich jedoch alle Teile erhalten und konnte erfolgreich einen Raspberry Pi Zero mit einem RFID Modul verbinden und auch zum Laufen bekommen.
An sich funktioniert alles so, wie ich es mir vorstelle.
Folgendes Problem habe ich noch:
Es wird ein Name aus der Datenbank gelesen und soll ausgegeben werden (wie oben: print("Hallo {} {}!".format(*name))). Wenn dieser Name jedoch Umlaute enthält, bricht das Programm ab und gibt folgenden Fehler aus:

Code: Alles auswählen

Traceback (most recent call last):
  File "attendance-pi.py", line 69, in <module>
    login(str(id))
  File "attendance-pi.py", line 55, in login
    print("Hallo {} {}!".format(*name))
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf6' in position 2: ordinal not in range(128)
Die Funktion, in der der Fehler entsteht, sieht wie folgt aus:

Code: Alles auswählen

def login(id):
    now = (time.strftime("%d.%m.%Y"))		#Tagesdatum in now uebergeben
    db = sqlite3.connect('sql-test.db')
    c = db.cursor()
    #ueberpruefen ob id vorhanden
    c.execute("SELECT count(*) FROM user WHERE RFID = ?", [id])	#Anzahl des Vorkommens der RFID in DB
    proof = c.fetchone()
    exist = str(proof[0])


    c.execute("SELECT Vorname, Nachname FROM user WHERE RFID = ?", [id])
    name = c.fetchone()
    zeit = c.execute("SELECT max(Zeitpunkt) FROM attendance WHERE RFID = ?", [id])
    zeit2 = zeit.fetchone()

    #zeitpunkt = c.fetchone()
    # hier der Test, ob heute schon eingeloggt.
    timedate = str(zeit2[0])
    if timedate == now or exist == '0':
        result = False
    #if now.date() == zeitpunkt.date():
        #result = False
    else:
        c.execute("INSERT INTO attendance (RFID, Zeitpunkt) VALUES (?, ?)", [id, now])
        db.commit()
        write_log(now, id, name)
        result = True


    if result:
        print("Hallo {} {}!".format(*name))							#ZEILE 55 <-----------------------
        print("Du hast dich erfolgreich anwesend gemeldet.")
    else:
        if timedate == now:
            print("Hallo {} {}!".format(*name))
            print("Du bist bereits angemeldet.")
        else:
            print("Entweder du bist nicht registriert oder dein Chip ist beschaedigt.")
    return True
Ich habe viel gegooglet, verschiedene Dinge (z.B. .encode('utf-8')) getestet, hatte jedoch keinen Erfolg.
Teste ich die Sqlite-Abfrage an meinem Windows 7 PC (ohne RFID Modul, dann mit manueller Eingabe der entsprechenden ID) klappt alles bestens und auch Namen mit Umlauten werden ausgegeben.
Ich hoffe mir kann wieder jemand helfen.

Vielen Dank im Voraus!
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Sieht so aus, als ob Du Python2 benutzt, und da will .format einen Unicodestring, wenn die Parameter Unicode sind.

Code: Alles auswählen

print(u"Hallo {} {}!".format(*name))
Die SQL-Abfrage nach der Anzahl ist überflüssig, da die nächste Abfrage ja name == None liefert, sollte der Datensatz nicht existieren. Eine Zahl in einen String umzuwandeln um ihn dann mit einer String '0' zu vergleichen ist auch ziemlich umständlich. `zeit` ist überflüssig, da es identisch ist mit `c` und es ein spezielles SQLite-Feature ist, dass execute ein cursor-Objekt zurückliefert, das man wegen Portabilität am besten nicht nutzt. Dann fällt auch das beliebige 2 bei zeit2 weg.
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

Hallo zusammen.
Interessantes Projekt, da ich demnächst ähnliches brauche. Versuche ich das auch zu testen und zu verstehen (Grundlagenforschung :lol: ).
Ich habe die Version von Sirius 15. April 2018 genommen. Habe 2 Zeilen ergänzt wo ich Aktuelle- und DB-Zeit inkl. Datentypen ausgebe.
Untenstehende Fehlermeldung wird ausgegeben.

Code: Alles auswählen

    print('Aktuelle Zeit:', now, type(now))
    print('DB-Zeitpunkt:', zeitpunkt, type(zeitpunkt))

Code: Alles auswählen

#Ausgabe
RFID: 67
Aktuelle Zeit: 2018-06-08 23:37:03.700442 <class 'datetime.datetime'>
DB-Zeitpunkt: 2018-06-08 23:35:12.280626 <class 'str'>

#Fehlermeldung
Traceback (most recent call last):
  File "c:\Python36\projects\rfid_reader.py", line 44, in <module>
    if login(rfid):
  File "c:\Python36\projects\rfid_reader.py", line 26, in login
    if now.date() == zeitpunkt.date():
AttributeError: 'str' object has no attribute 'date'
PS C:\Python36\projects>
Meine Frage: In der Datenbank habe ich die "Zeit" als DATETIME definiert. Wird aber beim auslesen mit:

Code: Alles auswählen

    c.execute("SELECT max(Zeitpunkt) FROM attendance WHERE RFID = ?", [rfid])
    zeitpunkt, = c.fetchone()
als String an zeitpunkt übergeben. Deswegen scheitert ja der Rest.
:roll: :roll: :roll:
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Tja, was auch immer die Entwickler vom `sqlite3`-Modul dazu verleitet hat DATETIME auf Python-Seite *nicht* in `datetime`-Objekte zu wandeln…  Deklariere die entsprechenden Spalten als TIMESTAMP, dann geht's. Oder verwende SQLAlchemy statt direkt SQL zu schreiben, da geht dann auch DATETIME so wie man es erwarten würde.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Nur ein Hinweis, Sqlite3 möchte seine Texte utf8 kodiert haben, laut Doku.Dummerweise überprüft man das nicht und erzwingt das nicht. Ich hatte schon einige Tools gesehen, die das einem übel genommen haben.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
kussji
User
Beiträge: 78
Registriert: Mittwoch 16. Mai 2018, 09:58

Problem mit DATETIME:
Okey dann wissen wir jetzt woran das liegt.
Viele Wege führen nach Rom - mein Weg sieht jetzt so aus, dass ich den String in DATETIME konvertiere.

Code: Alles auswählen

    c.execute("SELECT max(Zeitpunkt) FROM attendance WHERE RFID = ?", [rfid])
    zeitpunkt_db, = c.fetchone()
    zeitpunkt = DateTime.strptime(zeitpunkt_db, '%Y-%m-%d %H:%M:%S.%f')
habe in der Datenbank DATETIME gelassen
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kussji: Klar führen viele Wege nach Rom, aber ich nehme lieber den Bus als zu Fuss zu gehen. ;-) Und ``import this`` sagt ja unter anderem „There should be one-- and preferably only one --obvious way to do it.“

Meiner sähe wahrscheinlich so aus:

Code: Alles auswählen

    timestamp = (
        session.query(sa.func.max(Attendance.timestamp))
            .filter_by(rfid=rfid)
            .one()
    )
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
alfchr7
User
Beiträge: 5
Registriert: Sonntag 15. April 2018, 13:19

Sirius3 hat geschrieben: Freitag 8. Juni 2018, 15:49 Sieht so aus, als ob Du Python2 benutzt, und da will .format einen Unicodestring, wenn die Parameter Unicode sind.

Code: Alles auswählen

print(u"Hallo {} {}!".format(*name))
Die SQL-Abfrage nach der Anzahl ist überflüssig, da die nächste Abfrage ja name == None liefert, sollte der Datensatz nicht existieren. Eine Zahl in einen String umzuwandeln um ihn dann mit einer String '0' zu vergleichen ist auch ziemlich umständlich. `zeit` ist überflüssig, da es identisch ist mit `c` und es ein spezielles SQLite-Feature ist, dass execute ein cursor-Objekt zurückliefert, das man wegen Portabilität am besten nicht nutzt. Dann fällt auch das beliebige 2 bei zeit2 weg.
Hallo Sirius3,
danke für deine - erneut - schnelle Hilfe! So habe ich es nun zum Laufen bekommen. Die von dir angesprochenen Probleme werde ich noch anpassen. Zur Zeit läuft alles so, wie es soll.

@kusji: Falls bedarf besteht kann ich auch gerne meinen kompletten Code posten (auch wenn sich so mancher vielleicht an der ein oder anderen Stelle an den Kopf fassen wird :D)
Antworten