Links zu vorherigen Tutorials: Eins, Zwei und Drei.
Im letzten Beitrag zu unserem PiPassport Projekt haben wir gemeinsam eine Klassifizierung für NFCs erstellt, mit denen wir einzelne Benutzereinträge sowie auch
Aktivitäten an verschiedenen Stationen speichern und laden können.
Nach dem letzten Tutorial sollte Ihre nfc.py-Datei in etwa so aussehen:
- import nxppy
- import os,json,requests
- from xml.dom import minidom
- class NFC(object):
- def __init__(self,pifile,peoplefile):
- self.pi=pifile
- self.peoplef=peoplefile
- self.achievements=self.LoadPi()
- self.people=self.LoadPeople()
- def ReadCard(self):
- id=nxppy.read_mifare()
- if id == None:
- return None
- else:
- if id not in self.people.keys():
- print "new ID :", id
- name=raw_input('Please enter your name:')
- self.people[id]={"name":name,"achievements":[]}
- else:
- for aid in self.achievements.keys():
- if aid not in self.people[id]['achievements']:
- if self.achievements[aid]['question']==None:
- self.people[id]['achievements'].append(aid)
- print "Achievement unlocked!"
- else:
- print self.achievements[aid]['question']
- ans=[]
- for a in range(len(self.achievements[aid]['answers'])):
- answer=raw_input('Enter answer:')
- ans.append(answer)
- correct=0
- for an in range(len(ans)):
- found=False
- for answ in self.achievements[aid]['answers']:
- if answ==ans[an]:
- found=True
- break
- if not found:
- print "answer " + str(ans[an]) + " incorrect!"
- else:
- correct+=1
- if correct == len(self.achievements[aid]['answers']):
- print "achievement unlocked!"
- self.people[id]['achievements'].append(aid)
- self.SavePeople()
- return self.people[id]
- def Load(self,file,type):
- if os.path.exists(file):
- source=open(file)
- try:
- dom1=minidom.parse(source)
- except:
- impl=minidom.getDOMImplementation()
- dom1=impl.createDocument(None,type,None)
- return dom1
- else:
- impl=minidom.getDOMImplementation()
- doc=impl.createDocument(None, type,None)
- return doc
- def LoadPi(self):
- dom=self.Load(self.pi,"piSyst")
- try:
- pi_tag=dom.getElementsByTagName("pi")[0]
- except:
- self.SavePi()
- dom=self.Load(self.pi,"piSyst")
- pi_tag=dom.getElementsByTagName("pi")[0]
- a_tags=pi_tag.getElementsByTagName("achievement")
- achievements={}
- achievements[pi_tag.getAttribute("ID")]={"question":None,"answers":None}
- for a in a_tags:
- id=a.getAttribute("ID")
- qtag=a.getElementsByTagName("question")[0]
- question=qtag.childNodes[0].data
- atag=a.getElementsByTagName("answer")
- answers=[]
- for an in atag:
- answers.append(an.childNodes[0].data)
- achievements[id]={"question":question,"answers":answers}
- return achievements
- def SavePi(self):
- dom=self.Load(self.pi,"piSyst")
- top=dom.documentElement
- if(len(dom.getElementsByTagName("pi"))==0):
- pi=dom.createElement("pi")
- id=0
- pi.setAttribute("ID",str(id))
- top.appendChild(pi)
- file=open(self.pi,'w')
- dom.writexml(file)
- else:
- old_achievements=self.LoadPi()
- pitag=dom.getElementsByTagName("pi")[0]
- if old_achievements != self.achievements:
- try:
- os.remove(self.pi)
- except Exception, e:
- print str(e)
- pass
- dom=self.Load(self.pi,"piSyst")
- top=dom.documentElement
- pitag=dom.createElement("pi")
- id=0
- pitag.setAttribute("ID",str(id))
- top.appendChild(pitag)
- for id,a in self.achievements.iteritems():
- if a["question"]!=None:
- ac=dom.createElement("achievement")
- ac.setAttribute("ID",id)
- q=dom.createElement("question")
- text=dom.createTextNode(str(a["question"]))
- q.appendChild(text)
- ac.appendChild(q)
- for answer in a["answers"]:
- ans=dom.createElement("answer")
- txt=dom.createTextNode(str(answer))
- ans.appendChild(txt)
- ac.appendChild(ans)
- pitag.appendChild(ac)
- file=open(self.pi,'w')
- dom.writexml(file)
- def LoadPeople(self):
- dom=self.Load(self.peoplef,"People")
- p_tags=dom.getElementsByTagName("person")
- people={}
- for p in p_tags:
- id=p.getAttribute("ID")
- ntag=p.getElementsByTagName("name")[0]
- name=ntag.childNodes[0].data
- atag=p.getElementsByTagName("achievement")
- achievements=[]
- for a in atag:
- if a.childNodes[0].data not in achievements:
- achievements.append(a.childNodes[0].data)
- people[id]={"name":name,"achievements":achievements}
- return people
- def SavePeople(self):
- dom=self.Load(self.peoplef,"People")
- top=dom.documentElement
- old_p=self.LoadPeople()
- people_tags=top.getElementsByTagName("person")
- for id,p in self.people.iteritems():
- if id in old_p.keys():
- for pe in people_tags:
- if pe.getAttribute("ID")==id:
- for achievement in p["achievements"]:
- if achievement not in old_p[id]["achievements"]:
- ac_tag=dom.createElement("achievement")
- ac_text=dom.createTextNode(achievement)
- ac_tag.appendChild(ac_text)
- pe.appendChild(ac_tag)
- else:
- peep=dom.createElement("person")
- peep.setAttribute("ID",id)
- n=dom.createElement("name")
- text=dom.createTextNode(str(p["name"]))
- n.appendChild(text)
- peep.appendChild(n)
- for achievement in p["achievements"]:
- ac=dom.createElement("achievement")
- txt=dom.createTextNode(str(achievement))
- ac.appendChild(txt)
- peep.appendChild(ac)
- top.appendChild(peep)
- file=open(self.peoplef,'w')
- dom.writexml(file)
- def AddAchievement(self,desc,question,answers):
- return None
- def UpdateAchievement(self,updated_entry):
- return None
- def DeleteAchievement(self,id):
- return None
- def GetAchievement(self, id):
- if id in self.achievements.keys():
- return self.achievements[id]
- else:
- return None
Jetzt, da Sie über eine Infrastruktur verfügen, machen wir mit den letzten beiden Methoden-Stubs weiter. Diese ermöglichen es uns, Aktivitäten anzupassen.
Da die Dateiverarbeitung an einer anderen Stelle stattfindet, sind die weiteren Schritte relativ einfach:
- def AddAchievement(self,desc,question,answers):
- ids=[int(i) for i in self.achievements.keys()]
- sorted_ids=sorted(ids)
- id=sorted_ids[-1]+1
- self.achievements[str(id)]={"question":question,"answers":answers,"Description":desc}
- self.SavePi()
- def UpdateAchievement(self,updated_entry):
- if id in self.achievements.keys():
- self.achievements[id]=updated_entry
- else:
- print "Achievement not found"
- def DeleteAchievement(self,id):
- if id in self.achievements.keys():
- del self.achievements[id]
- else:
- print "Achievement not found"
Wir fangen ganz oben an: „Add“ (Erstellen) stellt ganz einfach anhand der höchsten ID fest, welche ID als nächstes kommt. Dazu zieht dieser Befehl ein Verzeichnis aller Aktivitäten-IDs heran, ordnet diese aufsteigend an,
nimmt dann den letzten Eintrag (sorted_ids[-1]) und fügt ihm eine 1 hinzu.
Diesen Eintrag fügen wir nun hinzu und speichern die Datei.
„Update“ (Aktualisieren) ist sogar noch einfacher – dieser Befehl findet einen Eintrag und tauscht ihn mit einer anderen Verzeichnisreferenz aus, die von der Admin-Seite vervollständigt wird.
„Delete“ (Löschen) prüft, ob eine ID vorliegt, und löscht sie.
Jetzt können wir nfc.py schließen und eine Admin-Seite erstellen. Drücken Sie also STRG+X, Y und Eingabe.
Geben Sie anschließend sudo nano admin.py und folgenden Stub ein:
- from nfc import NFC
- class UI(object):
- def __init__(self,pi,people):
- self.NFC=NFC(pi,people)
- self.CRUD={1:self.View,2:self.Create,3:self.Update,4:self.Delete,5:self.Quit}
- def Menu(self):
- print "Welcome to xyz admin."
- print "1. View Achievements"
- print "2. Add Achievements"
- print "3. Update Achievements"
- print "4. Remove Achievements"
- print "5. Quit"
- valid=False
- while not valid:
- input=raw_input('Enter an option:')
- validate=self.ValidateChoice(input)
- if validate == True:
- valid=True
- else:
- print validate
- self.CRUD[int(input)]()
- def View(self):
- return None
- def Create(self):
- return None
- def Update(self):
- return None
- def Delete(self):
- return None
- def Quit(self):
- return None
- def ValidateChoice(self,item):
- inte=self.ValidateInt(item)
- if not inte:
- return "Entry not an integer"
- if int(item) not in self.CRUD.keys():
- return str(item)+ " not in list"
- else:
- return True
- def ValidateInt(self,item):
- try:
- val=int(item)
- return True
- except:
- return False
- def ProcessEntry(self,string):
- valid=False
- while not valid:
- id=raw_input('Please enter a valid ' +string+ ': ')
- valid=self.ValidateInt(id)
- if not valid:
- print "Invalid number"
- return id
- self=UI('pi.xml','people.xml')
- self.Menu()
Wir erstellen nun eine menügesteuerte Benutzerschnittstelle. Diese ist vergleichbar mit automatisierten Telefonansagen wie „Drücken Sie die 1 hierfür, die 2 dafür und die 3, um mit einem Ansprechpartner verbunden zu werden“.
Da wir sie jedoch lesen können, ist es wesentlich einfacher, damit zu arbeiten. Alle Optionen werden im Menü() angezeigt. Hier kann der Benutzer dann seine Auswahl treffen.
Falls der Benutzer eine ungültige Auswahl trifft, wird dies sofort durch die ValidateChoice-Methode gekennzeichnet. Diese Methode überprüft, ob ein Eintrag ein Integer ist und ob sich die ID im CRUD-Verzeichnis wiederfindet.
Das CRUD-Verzeichnis, das wir beim „init“ festgelegt haben (self.CRUD), ist ziemlich speziell, da es nicht über Daten verfügt, sondern jeder ID eine Methode hinzufügt. So werden Switch-Case-Anweisungen in Python ersetzt,
die nicht existieren – in Programmiersprachen wie C, C++, C#, Java und vielen weiteren funktioniert ein Switch-Case wie folgt:
- value=user_input;
- switch(value){
- case 0:
- DoSomething();
- break
- case 1:
- DoSomethingElse();
- break;
- default:
- DoSomethingIfAllOtherOptionsArentChosen();
- break;
- }
So kann der Code in viele verschiedene Richtungen ausgelegt werden, je nachdem, welche Auswahl getroffen wird. Das kann Python zwar nicht, aber unser Verzeichnis funktioniert ganz genau so. Sobald wir wissen, dass die Benutzereingabe gültig ist, wird dieses Verzeichnis also aufgerufen.
Die andere Methode, auf die ich bisher noch nicht eingegangen bin – ProcessEntry – wird immer dann aufgerufen, wenn wir sie benötigen. Das ermöglicht es uns, einen Benutzer immer wieder um eine gültige Eingabe zu bitten, bis endlich eine für uns verwertbare Eingabe erfolgt ist.
Diese Methode können wir allerdings nicht in unserem Menü verwenden, da wir nicht nur einen gültigen Integer benötigen, sondern der Eintrag auch in einer bestimmten Liste vorkommen muss, was für spätere Einträge jedoch nicht gilt.
Wenn der Benutzer jetzt jedoch eine Auswahl eingibt, wird das Programm beendet, da die Methoden hier nichts ausrichten können. Lassen Sie sie uns also befüllen:
- def View(self):
- input=raw_input('Display all achievements? (y/n)')
- if input.lower()=='y':
- entries=self.NFC.achievements
- for id, a in entries.iteritems():
- print "ID: ", id
- for id, entry in a.iteritems():
- print id, ": ", entry
- else:
- id=self.ProcessEntry('Achievement ID')
- entry=self.NFC.GetAchievement(id)
- if entry is None:
- print "Achievement not found"
- else:
- for id, e in entry.iteritems():
- print id, " : ", e
- def Create(self):
- num=int(self.ProcessEntry('number of achievements'))
- for i in range(num):
- desc=raw_input('Achievement description:')
- question=raw_input('Question:')
- vint=False
- ansint=int(self.ProcessEntry('number of answers'))
- answers=[]
- for i in range(ansint):
- answer=raw_input('Enter answer '+str(i)+':')
- answers.append(answer)
- self.NFC.AddAchievement(desc,question,answers)
Die Methode „View“ (Prüfen) ruft entweder alle Aktivitäten ab, geht jede davon einzeln durch und zeigt dabei die verschiedenen Werte innerhalb des Verzeichnisses an, oder aber sie lässt den Benutzer eine Aktivität anhand einer ID wählen und zeigt diese an.
„Create“ (Erstellen) fordert erst eine Reihe Aktivitäten mithilfe der ProcessEntry-Methode an. Anschließend muss für jede Aktivität ein Eintrag angegeben werden, bis alle Aktivitäten erstellt worden sind.
Hier haben wir eine Beschreibung in Form einer Art Stub – diese wird üblicherweise unter der API gespeichert, aber wenn Sie sie allein nutzen, dann können Sie auch einfach die xml entsprechend anpassen und die Beschreibung hier speichern.
Weiter geht's:
- def Update(self):
- id=self.ProcessEntry('Achievement ID')
- entry=self.NFC.GetAchievement(id)
- new_e={}
- if entry is not None:
- for id in entry.keys():
- if id != "answers":
- update=raw_input('Enter update for '+id+': ')
- new_e[id]=update
- else:
- answers=[]
- for a in entry["answers"]:
- u=raw_input('Enter update for answer '+a+': ')
- answers.append(u)
- new_e[id]=answers
- self.NFC.UpdateAchievement(new_e)
- print "Update successful!"
- else:
- print "Update failed"
- def Delete(self):
- id=self.ProcessEntry('Achievement ID')
- entry=self.NFC.GetAchievement(id)
- for key, e in entry.iteritems():
- print key, ":", e
- yn=raw_input('Confirm delete? (y/n)')
- if yn.lower()=='y' or yn.lower()=="yes":
- self.NFC.DeleteAchievement(id)
- else:
- return None
Dieser Abschnitt ist ziemlich selbsterklärend: Unter „Update“ (Aktualisieren) wird ein Eintrag eingegeben, nach dem gesucht werden soll. Die Methode beginnt dann mit der Suche und legt für jeden Eintrag einen neuen Eintrag an.
Wahrscheinlich könnte man auch den vorherigen Eintrag für jeden Punkt ausgeben:
- if id != "answer":
- print "previous " +id+ ":", entry[id]
- update=raw_input('Enter update for '+id+': ')
„Delete“ (Löschen) hat eine sehr ähnliche Funktion – die Methode ruft einen Eintrag auf, gibt ihn aus und fordert Sie dann auf, zu bestätigen, dass der Eintrag wirklich gelöscht werden soll.
Dies sind Methoden, die wie benötigen, um eine ganz grundlegende Schnittstelle zu erstellen. Für diese Datei sollten eigentlich nur sehr weniger Anpassungen erforderlich sein, da Aktualisierungen für das Laden
und Speichern von Daten von by nfc.py durchgeführt werden.
Wenn Sie die Datei jetzt schließen und ausführen, sollten Sie über ein praktisches Menü verfügen, mit dem Sie Aktivitäten nach Belieben aktualisieren und anpassen können.
Im nächsten Beitrag bewegen wir uns kurz weg vom Pi und gehen genauer auf das Erstellen einer einfachen Web-API mit Azure ein. Wenn Sie gerne einen anderen Dienst nutzen möchten, dann können Sie diesen Beitrag natürlich einfach überspringen. Das letzte Tutorial sollten Sie trotzdem problemlos umsetzen können.
[Pssst... wenn Ihnen das ewige Copy&Paste zu langweilig ist, dann ist dieses Github-Repository genau das Richtige für Sie. Es ist wahrscheinlich einfacher in der Anwendung, da die Einzüge in Python oft verrutschen, aber trotzdem gilt: Lesen Sie alles aufmerksam, schummeln Sie nicht und überspringen Sie keine Schritte!]