Links zu vorherigen Tutorials: Projektvorstellun, Die Einrichtung der NFC
Hier geht es zum ersten Eintrag „Projektvorstellung“ und zum zweiten Eintrag „Das NXP NFC-Explorer-Board in der Praxis.“
In meinem vorigen Blogeintrag bin ich bereits darauf eingegangen, worum es bei diesem Thema geht und wie sich NXP in Windeseile einrichten lässt.
In diesem Beitrag erläutere ich Ihnen, wie Sie die NFC allgemein und ihren Code klassifizieren können und wie so aus den vielen Einzelteilen – der Datenein- und -ausgabe, dem Auslesen von NFC-Karten etc. – ein großes Ganzes wird.
Wir beginnen mit einem ganz grundlegenden Stub – geben Sie Folgendes im Pi Terminal ein:
sudo nano nfc.py
Geben Sie anschließend den folgenden Code ein – ich erkläre Ihnen, was Sie wo beachten müssen:
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):
return None
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):
return None
def SavePi(self):
return None
def LoadPeople(self):
return None
def SavePeople(self):
return None
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):
return None
Was haben wir hier also? An erster Stelle kommt „__init__“, unser Initialisierungsbefehl. Dieser wird aufgerufen, wenn eine Kopie dieser Klassifizierung zu einem späteren Zeitpunkt ausgeführt wird. Hier sollten Sie alle Variablen, die zur Klassifizierung gehören, einbeziehen. Führen Sie außerdem die Methoden aus, die von vornherein erforderlich sind.
Der einzige andere Stub, den wir ausfüllen, ist „Load“. Dieser ist eine generische Methode, mit der wir sämtliche erforderlichen xml-Dateien öffnen können – wir verwenden xml, um die Daten für unser System zu speichern.
Die anderen Methoden ermöglichen uns die Erstellung einer Datenklasse, die alle Bereiche abdeckt – so stehen uns Lade- und Speichermethoden für die xml-Ausgabe (Load/SavePi und Load/SavePeople) und auch CRUD-Methoden (Create, Review, Update, Delete, also Erstellen, Prüfen, Aktualisieren und Löschen) für die Variablen innerhalb der Klasse zur Nutzung zu einem späteren Zeitpunkt zur Verfügung.
Lassen Sie uns den Stub also Schritt für Schritt befüllen. Los geht es mit Load/SavePi – die Pi Datei befindet sich dort, wo wir Aktivitäten und Informationen zu der Station, an der wir gerade arbeiten, speichern:
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
old_achievements=self.LoadPi()
if(len(dom.getElementsByTagName("pi"))==0):
pi=dom.createElement("pi")
id=0
pi.setAttribute("ID",str(id))
top.appendChild(pi)
else:
pitag=dom.getElementsByTagName("pi")[0]
if old_achievements != self.achievements:
try:
os.remove(self.pifile)
except:
pass
dom=self.Load(self.pi,"piSyst")
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)
Hier kommt eine kompakte Bibliothek namens „minidom“ zum Einsatz – DOM steht hierbei für Document Object Model – die zur Verarbeitung von xml-Dateien dient. Mit der minidom können xmls in DOMs umgewandelt werden, aus denen wir wiederum Daten prüfen und abrufen können.
Beginnend mit dem LoadPi lädt der Algorithmus das DOM aus der Lademethode und versucht, ein Pi Tag aufzurufen – dieses Tag kommt in jedem Dokument vor und sollte daher immer auch das einzige sein.
Wenn das Tag nicht gefunden werden kann, bedeutet das, dass wir diese Station bisher noch nicht verwendet haben. Daher speichert das System das Pi und erstellt uns gleichzeitig ein Pi Tag.
Nach der Bewältigung dieses kleinen Problems machen wir weiter und versuchen nun, Aktivitäten zu suchen – hier ein paar kurze Erläuterungen:
- Jedes Pi mit einer eigenen ID wird auch als eigene Aktivität eingestuft: Diese werden zwar nicht als Aktivitäten aufgezählt, da sie in der Datei selbst keine Fragen und Antworten erfordern, aber das System fügt sie automatisch dem Aktivitätenverzeichnis hinzu.
- Für jedes Pi können Aktivitäten auch direkt vom Admin erstellt werden. Das geht derzeit zwar nur im Frage- und Antwortenformat, allerdings sind immer mehrere Antworten möglich.
Das System ruft jede Frage und jede Antwort einzeln auf und erstellt dazu jeweils einen Eintrag im Aktivitätenverzeichnis. Diese Einträge werden nach ihren eigenständigen IDs sortiert.
Der Einfachheit halber habe ich mich dazu entschlossen, die gesamte Datei zu löschen und neu zu erstellen. So erhalte ich meiner Ansicht nach eine besser aufgebaute Syntax, und um alle Einträge zu aktualisieren, müssen wir sowieso den gesamten Stamm durchgehen.
Jetzt lassen Sie uns zum Laden und Speichern von Benutzern, die ihre NFC-Karten eingelesen haben, übergehen:
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)
Die Struktur für Benutzer ist etwas einfacher: Jeder Benutzer wird als <Person ID="NFC_ID"></Person> hinzugefügt und verfügt über <achievement>0</achievement>, das für die bisher gesammelten Aktivitäten-IDs steht.
Da bei der Speichermethode keine Möglichkeit besteht, einen Benutzer zu entfernen oder zu löschen, müssen wir hier nur prüfen, ob ein Benutzer bereits über eine bestimmte ID in seinem Aktivitätenverzeichnis verfügt. Das ist nicht von allerhöchster Bedeutung, da der Lade-Code sowieso nach Duplikaten sucht, aber wir sollten hier einfach nochmal selbst sichergehen.
Jetzt, da wir über all die erforderliche „Infrastruktur“ verfügen, um die Daten aus dem Lesegerät zu speichern und zu laden, können wir die Kartenlesemethode erstellen:
def ReadCard(self):
id=nxppy.rad_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":[]}
if id in self.people.keys():
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) + " 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]
Hier wird es nun etwas komplexer, da uns jetzt richtige Daten vorliegen.
Als Erstes überprüfen wir, ob sich eine einzulesene Karte in der Nähe des Lesegeräts befindet. Falls nicht, passiert nichts.
Falls ja, und falls sich diese Person nicht in der Liste an Benutzern wiederfindet, die zuvor an dieser Station waren, dann bitten wir sie darum, sich in unsere Datenbank einzutragen.
Falls diese Person zuvor bereits erfasst worden ist, dann schauen wir uns jede einzelne Aktivität an und überprüfen, ob diese Aktivitäten bereits erfasst wurden. Falls nicht, und falls es sich nur um Pi
Aktivitäten handelt, dann fügen Sie diese sofort hinzu und informieren Sie den Benutzer darüber. Falls eine Frage vorliegt, dann bitten wir den Benutzer um die entsprechende Antwort und überprüfen anschließend, ob alle Antworten korrekt sind.
Zum Schluss speichern wir die Datei und fügen den Benutzereintrag wieder in das Verzeichnis ein.
Jetzt können wir diese Klassifizierung also verwenden – die CRUD-Methoden (Erstellen, Prüfen, Aktualisieren und Löschen) nehmen wir uns vor, wenn wir ein Admin-Portal anlegen – außer „Prüfen“, da dieser Teilvorgang auch gut für die NFC-Reader-Datei geeignet wäre:
def GetAchievement(self, id):
if id in self.achievements.keys():
return self.achievements[id]
else:
return None
Diese Datei schließen wir nun – geben Sie erst STRG+X und danach Y ein, und drücken Sie auf Eingabe.
Geben Sie jetzt Folgendes im Terminal ein:
sudo nano read.py
Nehmen Sie außerdem folgende Eingaben vor:
from nfc import NFC
self=NFC('pi.xml','people.xml')
person=self.ReadCard()
if person != None:
print "Hello " + person["name"]
print "you have collected ",len(person["achievements"])," achievements"
for a in person["achievements"]:
print "ID: ", a
achievement=self.GetAchievement(a)
if "Description" in achievement.keys():
print "Description: ", achievement['Description']
Das ist jetzt wieder etwas komplexer als die vorigen Schritte. Wir haben jetzt unsere eben erstellte Klassifizierung importiert, ausgeführt (self=NFC('pi.xml','people.xml')) und den Benutzer erfasst.
Jetzt prüfen wir, ob hierzu ein Eintrag vorliegt und informieren den Benutzer entsprechend darüber. Beachten Sie, dass es unterself.ReadCard mehrere Anzeigeanweisungen gibt. Diese werden zuvor angezeigt und informieren den Benutzer über die Ergebnisse eines bestimmten Scans.
if "Description" in achievement.keys():
print "Description: ", achievement['Description']
Wir haben hier etwas Spielraum für alle Eventualitäten gelassen, damit wir diese Datei später nicht anpassen müssen:
sudo python read.py
Grund hierfür ist, dass wir bei den Schritten 5 und 6 später eine Beschreibung speichern müssen, die allerdings aus der Web-API und nicht
aus der xml-Datei gezogen wird.
Jetzt können Sie diese Datei schließen (STRG+X, Y und Eingabe) und geben Folgendes ein:
sudo python read.py
Ziehen Sie dabei Ihre NFC-Karte über das Lesegerät.
Jetzt sollte Ihnen Folgendes angezeigt werden:
Und wenn Sie Ihren Namen eingeben, sollte es dazu zwei Dateien geben – people.xml:
und pi.xml:
In meinem nächsten Beitrag erstellen wir gemeinsam einen Admin-Bereich, damit wir die verfügbaren Aktivitäten bearbeiten können.










