czwartek, 14 grudnia 2017

Raspberry pi - Python - Wiegand

Ten post chciałbym poświęcić na opisanie sposobu wykonania Wieganda na Rapsbery Pi w Pythonie.

Znalezione obrazy dla zapytania raspberry pi
[Źródło: https://www.element14.com/community/community/raspberry-pi]

Karty 26 bitów:


W przypadku kart 26 bitowych najczęściej spotykany format wygląda następująco:

P FFFFFFFF NNNNNNNNNNNNNNNN P

Na początku znajduje się bit parzystości liczony z występujących po nim 12 bitów. Końcowy bit jest najczęściej bitem nieparzystości. On jest liczony od bitu na pozycji 13 do końca. Bity oznaczone jako F oznaczają tzw. Facility Code. Jest on przechowywany na 8 bitach. Bity oznaczone jako N to numery karty. Jest on zapisany na 16 bitach.

Karty 32 bitów:


W tym przypadku przeważnie numer karty zapisany jest na wszystkich bitach. Nie występują tutaj żadne bity parzystości.

Karty 37 bitów:


W tym przypadku format karty najczęściej wygląda następująco:

P FFFFFFFFFFFFFFFF NNNNNNNNNNNNNNNNNNN P

Na początku umieszczany jest bit parzystości liczony do bitów na pozycji 18. Bit nieparzystości liczony jest z 19 bitów idąc od końca (w liczenie włączam bit nieparzystości). Po pierwszym bicie parzystości znajduje się tzw FacilityCode. Jest on zapisany na 16 bitach. Następnie znajduje się nr karty, który można zapisać na 19 bitach.

Czyli sposób obliczenia będzie wyglądał tak:

PFFFFFFFFFFFFFFFFNNNNNNNNNNNNNNNNNNNP
PFFFFFFFFFFFFFFFFNN - Obliczenie bitu parzystości
NNNNNNNNNNNNNNNNNNP - Obliczenie bitu nieparzystości

Oznacza to że jeden bit numeru karty będzie wspólny zarówno dla bitu nieparzystości jak i parzystości.

Program Python:


Poniżej przedstawię program wykonany w Pythonie.

Inicjalizacja wyprowadzeń odbywa się podczas wywołania klasy. Linie DATA0 oraz DATA1 obsługiwane są na przerwaniach.

  1.     #####################################
  2.     '''
  3.         Initialize function
  4.     '''
  5.     def __init__(self, name, DATA0, DATA1, TIMEOUT):
  6.         self.name = name
  7.         self.DATA0 = DATA0
  8.         self.DATA1 = DATA1
  9.         Wiegand.TIMEOUT = TIMEOUT
  10.         GPIO.setup(self.DATA0, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  11.         GPIO.setup(self.DATA1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  12.         GPIO.add_event_detect(self.DATA0, GPIO.FALLING, callback=Wiegand.callbackData0)
  13.         GPIO.add_event_detect(self.DATA1, GPIO.FALLING, callback=Wiegand.callbackData1)
  14.         self.t = threading.Timer(self.TIMEOUT, Wiegand.processTagData)
  15.         self.t.start()

Numery pinów oraz czas dla timera podawany jest podczas wywołania funkcji. Piny zdeklarowane są na przerwania od zbocza opadającego.

Poniżej funkcje odpowiedzialne za obsługę przerwania od pinów z linii danych:

  1.     #####################################
  2.     '''
  3.         Function responsible for event in DATA0 line
  4.     '''
  5.     def callbackData0(self):
  6.         if Wiegand.tagNumber == "":
  7.             Wiegand.t = threading.Timer(Wiegand.TIMEOUT, Wiegand.processTagData)
  8.             Wiegand.t.start()
  9.         Wiegand.tagNumber += "0"
  10.     #####################################
  11.     '''
  12.         Function responsible for event in DATA1 line
  13.     '''
  14.     #Funkcja obsługi zdarzenia na linii DATA1
  15.     def callbackData1(self):
  16.         if Wiegand.tagNumber == "":
  17.             Wiegand.t = threading.Timer(Wiegand.TIMEOUT, Wiegand.processTagData)
  18.             Wiegand.t.start()
  19.         Wiegand.tagNumber += "1"

W zależności od wywołanego pinu do ciągu znaków przechowującego wynik z pobranym numerem.

Licznik pozwala na wygenerowanie funkcji sprawdzającej dopiero po odebraniu wszystkich bitów na liniach.

  1.     #####################################
  2.     '''
  3.         PROCESS RECEIVE CARD DATA
  4.     '''
  5.     def processTagData():
  6.         #print for debug
  7.         print("Length: ", len(Wiegand.tagNumber));
  8.         print("procesTag: " + "::" + Wiegand.tagNumber)
  9.         if Wiegand.tagNumber == "":
  10.             return
  11.         elif len(Wiegand.tagNumber) < 26:
  12.             print("less then 26")
  13.             return
  14.         elif len(Wiegand.tagNumber) == 37:
  15.             if Wiegand.verifyParity37(Wiegand.tagNumber) == True:
  16.                 Wiegand.lastCardNumber = Wiegand.binaryToIntWieg37Bit(Wiegand.tagNumber)
  17.                 Wiegand.flag_thereIsCard = 1
  18.                 print("OK37: " + str(Wiegand.lastCardNumber))
  19.         elif len(Wiegand.tagNumber) == 32:
  20.             Wiegand.lastCardNumber = Wiegand.binaryToIntWieg32Bit(Wiegand.tagNumber)
  21.             Wiegand.flag_thereIsCard = 1
  22.             print("OK32: " + str(Wiegand.lastCardNumber))
  23.         elif len(Wiegand.tagNumber) == 26:
  24.             if Wiegand.verifyParity26(Wiegand.tagNumber) == True:
  25.                 Wiegand.lastCardNumber = Wiegand.binaryToInt26Bit(Wiegand.tagNumber)
  26.                 Wiegand.flag_thereIsCard = 1
  27.                 print("OK26: " + str(Wiegand.lastCardNumber))
  28.         Wiegand.tagNumber = ""

W powyższej funkcji sprawdzana jest długość odpowiedzi i w zależności od otrzymanego rodzaju karty wchodzi do odpowiedniej funkcji obrabiającej dane. Jeśli zostanie odebrana ramka danych o błędnej długości, czyli niższej niż 26 bitów. to funkcja zostanie przerwana.

Weryfikacja bitów parzystości oraz nieparzystości dla karty 37 bitowej wygląda następująco:

  1.     #####################################
  2.     '''
  3.         CHECK DATA FRAME CORRECTION
  4.     '''
  5.     def verifyParity37(dataToDecode):
  6.         firstHalf = dataToDecode[0:19]
  7.         secondHalf = dataToDecode[18:]
  8.         print(firstHalf)
  9.         print(secondHalf)
  10.         parts = [firstHalf, secondHalf]
  11.         bitsTo1 = [0, 0]
  12.         index = 0;
  13.         for part in parts:
  14.             bitsTo1[index] = part.count('1')
  15.             index += 1
  16.         if bitsTo1[0] % 2 != 0:
  17.             print("firstERROR")
  18.         if bitsTo1[1] % 2 != 1:
  19.             print("SecondERROR")
  20.         if bitsTo1[0] % 2 != 0 or bitsTo1[1] % 2 != 1:
  21.             print("Frame len: " + str(len(Wiegand.tagNumber)) + " - " +
  22.                     str(Wiegand.binaryToIntWholeValue(Wiegand.tagNumber)) + " -- ERROR")
  23.             return False
  24.         print("verify parity return true")
  25.         return True

Na początku ramka danych dzielona jest na dwie połowy w zależności od tego z jakiej ilości parzystość jest liczona. Następnie zliczane są ilości jedynek. Po nich przechodzimy do weryfikacji poprawności danych. Jeśli wszystko jest w porządku to zwrócona zostaje wartość True.

Teraz poprawność danych dal 26 bitów. Funkcja działa na takiej samej zasadzie różni się tylko zakresem obliczanych danych.

  1.     #####################################
  2.     '''
  3.        CHECK DATA FRAME CORRECTION FOR 26 BITS
  4.    '''
  5.     def verifyParity26(dataToDecode):
  6.         firstHalf = dataToDecode[0:13]
  7.         secondHalf = dataToDecode[13:]
  8.         parts = [firstHalf, secondHalf]
  9.         bitsTo1 = [0, 0]
  10.         index = 0;
  11.         for part in parts:
  12.             bitsTo1[index] = part.count('1')
  13.             index += 1
  14.         if bitsTo1[0]%2 != 0 or bitsTo1[1] % 2 != 1:
  15.             print("Frame len: " + str(len(Wiegand.tagNumber)) + " - " +
  16.                     str(Wiegand.binaryToIntWholeValue(Wiegand.tagNumber)) + " -- FAILED")
  17.             return False
  18.         print("verify parity return true")
  19.         return True

Zamiana numeru karty z ciągu znaków na wartość dziesiętną z obciętymi bitami parzystości oraz nieparzystości:

  1.     #####################################
  2.     '''
  3.        RETURN FULL CARD NUMBER
  4.    '''
  5.     def binaryToIntWholeValue(stringCardNumber):
  6.         stringCardNumber = stringCardNumber[1:-1]
  7.         cardNumberFull = int(stringCardNumber, 2)
  8.         print("Full Number: " + str(cardNumberFull))
  9.         return cardNumberFull

Teraz funkcje dekodujące. Zacznę od 26 bitów:

  1.   #####################################    
  2.     '''
  3.        RETURN CARD NUMBER AND FACILITY CODE
  4.    '''
  5.     def binaryToInt26Bit(stringCardNumber):
  6.         cardNumber = Wiegand.binaryToIntWholeValue(stringCardNumber)
  7.         retCardNumber = cardNumber & 0x00FFFF
  8.         retFacilityCode = (cardNumber & 0xFF0000) >> 16
  9.         return {'fc':retFacilityCode, 'cn':retCardNumber}

Jak wspomniałem wcześniej 8 bitów stanowi tzw. Facility code. Natomiast numer karty zapisany jest na 16 bitach. Funkcja powyżej wyciąga oba numery po czym je zwraca.

Dekodowanie karty 32 bitowej:

  1.     #####################################
  2.     '''
  3.         DECODE 32BIT NUMBER
  4.     '''
  5.     def binaryToIntWieg32Bit(stringCardNumber):
  6.         cardNumber = int(stringCardNumber, 2)
  7.         #Debug
  8.         print("number 32: " + str(cardNumber))
  9.         return cardNumber

Tutaj nie ma bitów parzystości dlatego cały numer zamieniany jest na numer.

Teraz dane 37 bitowe:

  1.     #####################################
  2.     '''
  3.         CARD DATA FOR 37 BIT CARD
  4.     '''
  5.     def binaryToIntWieg37Bit(stringCardNumber):
  6.         cardNumber = Wiegand.binaryToIntWholeValue(stringCardNumber)
  7.         retCardNumber = cardNumber & 0x0007FFFF              #card number
  8.         retFacilityCode = (cardNumber & 0x7FFF80000) >> 19   #facility code
  9.         return {'fc':retFacilityCode, 'cn':retCardNumber}

Po usunięciu bitów parzystości oraz nieparzystości zostaje 35 bitów danych. 19 stanowi numer karty 16 zostało przeznaczone na Facility Code.

W programie głównym aby rozpocząć pobieranie numeru karty należy wywołać następującą instrukcję:

  1. '''
  2.     Wiegand(NAZWA, PIN dla DATA0, PIN DLA DATA1, TIMEOUT)
  3. '''
  4. wiegandApp = Wiegand("Wiegand1", 17, 18, 0.3);

Przedstawiony projekt można pobrać pod tym linkiem z dysku Google. Dane umieszczone w folderze Python.