Kontakt

DIY Raspberry Pi Stechuhr mit NFC - Teil 2

by Manuel on 16.02.2018

Die Fortsetzung unseres Neos/Flow Meetups zur Entwicklung einer mit NFC gesteuerten Raspberry Pi Stechuhr lies, im Vergleich zum korrespondierenden Blog-Post, nicht lange auf sich warten. Entschuldigung für die Verzögerung!

Recap Teil 1

In Teil 1 unserer Raspberry-Pi-Bastelaktion trafen wir auf ein unerwartetes Problem:

  • Lesen eigens beschriebener Chips mit dem NFC-Modul RFID RC522

Nach über 4h abendlicher Bastelei waren wir nicht in der Lage dies schnell zu beheben.

Zur Erinnerung und Auffrischung geht es hier zu Teil 1 der Raspberry Pi Stechuhr.

Während des zweiten Bastel-Abends trafen wir auf weitere technische Herausforderungen, die wir fernab solcher Veranstaltungen und parallel zur Entstehung von Teil 2 lösen mussten.

Problem 1: Auslesen eigens beschriebener RFID-Chips mit NFC-Modul RFID RC522

In Teil 1 kamen wir zu dem Schluss, uns nach einer neuen NFC Bibliothek zum Auslesen der Chips umzuschauen, da die ursprünglich verwendete unsere selbstbeschriebenen im NDEF Format nicht lesen konnte.

Nachdem wir einige Nächte darüber geschlafen hatten, fiel uns eine einfachere Lösung ein - die interne Seriennummer der RFID-Chips. 

Jeder RFID-Chip besitzt eine eineindeutige Seriennummer. Diese lesen wir nun aus und ordnen sie innerhalb des Flow Controllers dem jeweiligen Sandstormer zu.

Da es sich bei dieser Zuordnung um keine sicherheitsrelevanten Informationen wie bspw. Passwörter oder SSH-Keys handelt, nutzten wir kurzerhand die settings.yaml

  1. Sandstorm:
  2.   WorkingTime:
  3.   people:
  4.   sandstormer-name:
  5.   rfidToken: 'ab-cd-ef-91'
  6.   firstWorkDay: '2015-07-06'
  7.   workingHoursPerDay:
  8.   - hours: 6
  9.   until: '2015-09-30'
  10.   - hours: 8
  11.   until: 'now'
  12.   holidaysPerYear: 30

Raspberry Pi samt Touchscreen im Lego-Gehäuse

Problem 2: Browser gezielt steuern

Analog unserer bisherigen Zeiterfassung soll auch der Raspberry Pi bei Anhalten des individuellen RFID Chips die korrespondierende Zeiterfassungs-Website des Sandstormers öffnen.

Standardmäßig ist es jedoch nicht möglich einzelne Browsertabs zu steuern und sie neue URLs aufrufen zu lassen, wie in unserem Fall nötig. Der Browser kann lediglich beim initialen Start oder Öffnen eines neuen Tabs eine spezifische URL öffnen. Da der Raspberry Pi durch seine weniger potente Hardware jedoch sehr lange brauch, um Firefox zu schließen und wieder zu starten, ist dies keine Option für den operativen Betrieb. Auch das Öffnen dutzender Tabs verlangsamt den Pi sukzessive und macht ihn damit unbrauchbar.

Die Lösung liefert das Addon Remote Control for Firefox. Es erweitert den Raspberry Pi Firefox um die benötigte Funktionalität. Via Telnet schicken wir JS Code an den Pi, die die Seite laden bzw. neuladen lassen.

Der angepasste Python Code sieht nun folgendermaßen aus:

#!/usr/bin/env python
# -*- coding: utf8 -*-
 
import RPi.GPIO as GPIO
import MFRC522
import signal
import datetime
import telnetlib
 
 
class Control(object):
    def __init__(self, port=32000):
        self.host = 'localhost'
        self.port = port
 
    def reload(self):
        tn = telnetlib.Telnet(self.host, self.port)
        tn.write("reload\n")
        result = tn.read_until('\n')
        return result
 
    def load(self, url):
        tn = telnetlib.Telnet(self.host, self.port)
        tn.write('window.location="%s"' % url)
        result = tn.read_until('\n')
        return result
 
 
continue_reading = True
 
lastUid = [0, 0, 0, 0, 0]
timeToDelete = 0
 
 
# Capture SIGINT for cleanup when the script is aborted
def end_read(signal, frame):
    global continue_reading
    continue_reading = False
    GPIO.cleanup()
 
 
# Hook the SIGINT
signal.signal(signal.SIGINT, end_read)
 
# Create an objecet of the class MFRC522
MIFAREReader = MFRC522.MFRC522()
 
c = Control()
 
# This loop keeps checking for chips. If one is near it will get the UID and authenticate
while continue_reading:
    (status, TagType) = MIFAREReader.MFRC522_Request(MIFAREReader.PICC_REQIDL)
 
    # If a card is found
    if timeToDelete != 0 and timeToDelete < datetime.datetime.now():
        timeToDelete = 0
        lastUid = [0, 0, 0, 0, 0]
        c.load('https://www.your-url.de')
 
    # Get the UID of the card
    (status, uid) = MIFAREReader.MFRC522_Anticoll()
 
    # If we have the UID, continue
    if status == MIFAREReader.MI_OK:
        if lastUid != uid:
            uidStrings = []
            for part in uid:
                uidStrings.append(str(hex(part)).replace('0x', ''))
            # we only use the first 4 entries
            uidStrings.pop(4)
            uidString = '-'.join(uidStrings)
 
            lastUid = uid
            timeToDelete = datetime.datetime.now() + datetime.timedelta(0, 5)
 
            url = "http://www.your-url.de/workingtime/dongle/toggle.html?rfidToken=" + uidString
            c.load(url)
Wartezeit heißt Zeit für Kicker

Wartezeit heißt Kickerzeit

Problem 3: Kubernetes

Parallel zum Projekt zogen wir unsere komplette Infrastruktur um. Statt Heroku setzen wir nun auf Kubernetes.

Unser Intranet ist durch einen SSO (Single Sign-on) geschützt, welches bisher keine Ausnahmen für externe Zugriffe außerhalb der bekannten Strukturen erlaubte. Dahingehend mussten wir unsere Nginx Ingress Controller Konfiguration anpassen.

Dabei haben wir einige Stolpersteine mitgenommen. In der Konfiguration wurde "ssl: true" aus der Build-Variablen entfernt. Dies hatte zur Folge, dass der "tls"-Block aus Ingress entfernt wurde und es daher keine SSL-Konfiguration gab.

Der 2. Ingress muss darüber hinaus auch als SSL markiert werden, was den Einsatz von "kubernetes.io/tls-acme" notwendig macht sowie den "tls:"-Block, der sagt, für welche Domains Zertifikate ausgestellt werden sollen.

Erschwert wurde das Gesamtkonstruktut dadurch, dass Kube-Lego deprecated ist und auf Kubernetes 1.9 nicht mehr supported wird. Dahingehend mussten wir alternativ erst auf cert-manager updaten.

Problem 4: Skript im Autostart

Last but not least - Der Pi soll ohne weiteres Zutun unsererseits nach einem Neustart von alleine wieder einsatzbereit sein. Dafür wurde Firefox in den Autostart gesetzt und unser eigener System Service frankWalter.service implementiert (s.u.)

Um den System Service ausführen zu können, wird ein neuer Benutzer benötigt. Dabei ist auch darauf zu achten, dass der Nutzer die Rechte hat den Code ausführen zu dürfen.

  1. [Unit]
  2. Description=Python Sandstorm time clock
  3. After=syslog.target network-online.target
  4.  
  5. [Service]
  6. Type=simple
  7. User=pi
  8. ExecStart=/utl/timeClock.py
  9. Restart=on-failure
  10. RestartSec=10
  11. KillMode=process
  12.  
  13. [Install]
  14. WantedBy=multi-user.target

Raspberry Pi Installation

  1. Kopieren von timeClock.py und MFRC522.py nach /utl/
  2. Kopieren von frankWalter.service nach /etc/systemd/system
  3. Leserechte für timeClock.py für User einrichten
  4. Firefox installieren: sudo apt-get install firefox-esr
    1. Addon Remote Control for Firefox installieren
    2. systemctl daemon-reload
    3. systemctl enable frankWalter
    4. systemctl start frankWalter
  5. Firefox in den Autostart integrieren
  6. Fertig!

Fertig!

Die Zeiterfassung eines jeden Sandstormers lässt sich nun bequem per RFID-Chip starten und stoppen!

Für weitere Fragen stehen wir euch gern zur Verfügung!