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:

 

sc3.png

 

Und wenn Sie Ihren Namen eingeben, sollte es dazu zwei Dateien geben – people.xml:

sc3.png

und pi.xml:

sc5.png

 

 

In meinem nächsten Beitrag erstellen wir gemeinsam einen Admin-Bereich, damit wir die verfügbaren Aktivitäten bearbeiten können.