Wstęp.
Ponieważ często tu zaglądam, to chciałbym się też podzielić swoim projektem. Może się komuś przyda, a może jakieś sugestie się pojawią, które pomogą usprawnić ten projekt.
Założenia i cel :
Celem projektu jest wyświetlenie strony WWW w zależności od odczytanego taga RFID, a ponieważ kupiłem czytnik PN532 to z nim się trochę pomęczyłem, jak wyszło …. . Sam projekt ma być częścią większej całości gdzie Raspberry będzie pracowało jako kiosk informacyjny (chrome w trybie kiosk ) – bez użycia myszki i klawiatury – a dane właśnie będą wyświetlane na podstawie przyłożenia karty do terminala.
Podłączenie fizyczne do Raspberry pi 3 :
Moduł, który kupiłem pozwala na podłączenie poprzez SPI, I2C oraz UART. Sprawdziłem wszystkie 3 i najlepiej wyszło UART. SPI nie chciało działać z biblioteką libnfc, a I2C działało widocznie wolniej (choć to ocena subiektywna) niż UART.
Samo podłączenie jest proste – ustawiamy zworki w tryb UART i podłączamy zasilanie oraz piny TX/RX według schematu :
1 2 3 4 5 6 |
RasPi - PN532 ----------------------- GND - GND 5v - VCC 14(TXD) - RXD 15(RXD) - TXD |
Oprogramowanie (libnfc) :
Do obsługi tego PN532 użyłem biblioteki libnfc, która pozawala na obsługę nie tylko tego czytnika, ale również innych czytników (w tym “gotowców” podpinanych pod port USB). Aby z niej jednak skorzystać należy najpierw “otworzyć” komunikacje UART. Robimy to następująco :
1 2 3 4 5 6 7 |
// w konsoli wpisujemy root@raspberrypi:~# raspi-config // w opcji "5 Interfacing Options" // wybieramy opcje "P6 Serial" // ustanawiamy : "Would you like a login shell to be accessible over serial?" na Nie // a "Would you like the serial port hardware to be enabled?" na Tak // Wychodzimy i resetujemy |
Aby ją zainstalować pobieramy ze strony : https://github.com/nfc-tools/libnfc
1 2 3 4 |
apt-get install libusb-dev cd /home/pi wget https://github.com/nfc-tools/libnfc/releases/download/libnfc-1.7.1/libnfc-1.7.1.tar.bz2 tar -xf libnfc-1.7.1.tar.bz2 |
i instalujemy
1 2 3 4 |
cd libnfc-1.7.1 ./configure --prefix=/usr --sysconfdir=/etc make make install |
a następnie należy skonfigurować w pliku : /etc/nfc/libnfc.conf
tworzymy plik konfiguracyjny
1 2 |
root@raspberrypi:/home/pi/libnfc-1.7.1# mkdir /etc/nfc/ root@raspberrypi:/home/pi/libnfc-1.7.1# cp libnfc.conf.sample /etc/nfc/libnfc.conf |
i ustawiamy dostępne czytniki (w tym przypadku UART)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# Allow device auto-detection (default: true) # Note: if this auto-detection is disabled, user has to set manually a device # configuration using file or environment variable allow_autoscan = true # Allow intrusive auto-detection (default: false) # Warning: intrusive auto-detection can seriously disturb other devices # This option is not recommended, user should prefer to add manually his device. allow_intrusive_scan = false # Set log level (default: error) # Valid log levels are (in order of verbosity): 0 (none), 1 (error), 2 (info), 3 (debug) # Note: if you compiled with --enable-debug option, the default log level is "debug" log_level = 1 # Manually set default device (no default) # To set a default device, you must set both name and connstring for your device # Note: if autoscan is enabled, default device will be the first device available in device list. #device.name = "_PN532_SPI" #device.connstring = "pn532_spi:/dev/spidev0.0:500000" #device.name = "_PN532_I2c" #device.connstring = "pn532_i2c:/dev/i2c-1" device.name = "_PN532_UART" device.connstring = "pn532_uart:/dev/ttyS0" #device.connstring = "pn532_uart:/dev/ttyAMA0" |
Należy tylko zwrócić uwagę na ostatnią i przedostatnią linię. Dla RasPi_3 UART jest na porcie “ttyS0”, dla RasPi_2 na “ttyAMA0”
Jeśli wszytko poszło jak trzeba powinniśmy już zobaczyć urządzenie i odczytać tag :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
## spawdzamy czytnik root@raspberrypi:~# nfc-list nfc-list uses libnfc 1.7.1 NFC device: pn532_uart:/dev/ttyS0 opened ## sprawdzamy odczyt TAG-u root@raspberrypi:~# nfc-poll nfc-poll uses libnfc 1.7.1 NFC reader: pn532_uart:/dev/ttyS0 opened NFC device will poll during 30000 ms (20 pollings of 300 ms for 5 modulations) ISO/IEC 14443A (106 kbps) target: ATQA (SENS_RES): 00 04 UID (NFCID1): 23 95 65 d9 SAK (SEL_RES): 08 nfc_initiator_target_is_present: Target Released Waiting for card removing...done. |
Ale to dopiero połowa sukcesu – trzeba to jeszcze “pożenić” z serwerem WWW.
Do wykonania tego użyłem języka C wykorzystując libnfc do odczytu TAGu i zapisu w pliku, z którego to pliku serwer WWW będzie mógł odczytać dane. Ponieważ to mój pierwszy program w języku C to zadanie wydawało się karkołomne, ale z pomocą przykładów zwartych w bibliotece – poszło dość sprawnie (choć nie wiem czy do końca poprawnie).
Oprogramowanie (libnfc – moja wersja odczytu tagu) :
w folderze : /home/pi/libnfc-1.7.1/examples/doc/ dodałem plik mielmar.c, który zawiera :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
/** na podstawie quick_start_example1.c To compile this simple example: $ gcc -o mielmar mielmar.c -lnfc */ #include <stdio.h> #include <stdlib.h> #include <nfc/nfc.h> #include <unistd.h> char uID[20] =""; char Last_uID[20] =" "; static void SaveToFile(){ if (strcmp(Last_uID,uID)!=0) { //zapis do pliku FILE *f = fopen("/var/www/html/ramdisk/rfid.html", "w"); if (f == NULL){ printf("Error opening file!\n"); exit(1); } fprintf(f, "{ \"uID\": \"%s\" } \n", uID); fclose(f); // koniec zapisu sprintf(Last_uID,"%s",uID); } } static void print_hex(const uint8_t *pbtData, const size_t szBytes) { size_t szPos=szBytes; for (szPos = szBytes; szPos >0; szPos--) { sprintf(uID,"%s%02x", uID,pbtData[szPos-1]); } printf("%s\n",uID); } /// /// MAIN /// int main(int argc, const char *argv[]) { /* Setup daemon */ int i=0; int zm_daemon=1; printf("\n Program to lissen and write uID from RFID (write to RAMDISK '/var/www/html/ramdisk/rfid.html') \n"); // RAMDISK in /etc/fstab add line --> tmpfs /var/www/html/ramdisk tmpfs rw,nodev,nosuid,size=512 0 0 printf(" Usage : %s [--nodaemon]\n\n",argv[0]); for (i=1; i< argc; i++) { if (strcmp(argv[i] ,"--nodaemon") == 0) zm_daemon=0; } if (zm_daemon==1) { printf(" daemon mode : ON \n"); daemon(1,1); } else { printf(" daemon mode : OFF \n"); } /* Setup nfc_device */ nfc_device *pnd; nfc_target nt; // Allocate only a pointer to nfc_context nfc_context *context; // Initialize libnfc and set the nfc_context nfc_init(&context); if (context == NULL) { printf(" Unable to init libnfc (malloc)\n"); exit(EXIT_FAILURE); } // Display libnfc version const char *acLibnfcVersion = nfc_version(); (void)argc; printf(" %s uses libnfc %s\n\n", argv[0], acLibnfcVersion); // Open, using the first available NFC device which can be in order of selection: // - default device specified using environment variable or // - first specified device in libnfc.conf (/etc/nfc) or // - first specified device in device-configuration directory (/etc/nfc/devices.d) or // - first auto-detected (if feature is not disabled in libnfc.conf) device pnd = nfc_open(context, NULL); if (pnd == NULL) { printf("ERROR: %s\n", "Unable to open NFC device."); exit(EXIT_FAILURE); } // Set opened NFC device to initiator mode if (nfc_initiator_init(pnd) < 0) { nfc_perror(pnd, "nfc_initiator_init"); exit(EXIT_FAILURE); } printf("NFC reader: %s opened\n", nfc_device_get_name(pnd)); /* param for reader */ const uint8_t uiPollNr = 10; //określa liczbę odpytywania (0x01 – 0xFE: 1 up to 254 polling, 0xFF: Endless polling) const uint8_t uiPeriod = 1; //wskazuje okres odpytywania w jednostkach of 150 ms (0x01 – 0x0F: 150ms – 2.25s) const size_t szModulations = 1; int res = 0; const nfc_modulation nmModulations[1] = { { .nmt = NMT_ISO14443A, .nbr = NBR_106 } }; /* loop for read tag */ while (1) { sprintf(uID,""); // czyszcenie zmiennej globalnej if ((res = nfc_initiator_poll_target(pnd, nmModulations, szModulations, uiPollNr, uiPeriod, &nt)) < 0) { // check TAG /* nfc_perror(pnd, "nfc_initiator_poll_target");*/ } if (res > 0) { // if tag exist printf("Target uID : "); print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen); // odczytanie tagu SaveToFile(); // zapisanie do pliku printf("Waiting for card removing "); fflush(stdout); while (0 == nfc_initiator_target_is_present(pnd, NULL)) {} // wait for remove tag nfc_perror(pnd, "..."); sprintf(uID,""); SaveToFile(); } else { printf("No target found.\n"); // if tag not exist SaveToFile(); } } /* this never happened - not good, but working OK */ // Close NFC device nfc_close(pnd); // Release the context nfc_exit(context); exit(EXIT_SUCCESS); } |
aby go skompilować należy :
1 2 |
root@raspberrypi:~# cd /home/pi/libnfc-1.7.1/examples/doc/ root@raspberrypi:/home/pi/libnfc-1.7.1/examples/doc# gcc -o mielmar mielmar.c -lnfc |
powstanie plik wykonywalny /home/pi/libnfc-1.7.1/examples/doc/mielmar
Program domyślnie ma pracować jako “daemon“, czyli domyślnie pracuje w tle i żeby go zabić trzeba zabić proces poleceniem kill -9 prgram z opcją –nodaemon koczy się kombinacją klawiszy Ctr+C.
Program czeka na przyłożenie TAG-a – jeśli nie ma to wyświetla komunikat i czeka ponownie. Jeśli TAG jest to odczytuje, wyświetla i zapisuje do pliku w formacie JSON { “uID”: “d9659523” }, a następnie czeka na jego zabranie. Jeśli tag zostanie usunięty to wyświetla i zapisuje do pliku informacje w formacie JSON { “uID”: “” }
Program jest zapętlony i wykonuje się bez końca. / wszystko ma koniec :) /
Ważne – w programie jest ustawiona ścieżka do pliku, w którym zapisane będą dane – dlatego ważne jest aby katalog “/var/www/html/ramdisk/” z uprawnieniem do zapisu był utworzony przed uruchomieniem programu. Jak to zrobić o tym za chwilę.
Konfiguracja (połączenie programu z serwerem WWW)
Jeśli program działa – to zgodnie z założeniem trzeba by go “spiąć” z serwerem WWW. W tym celu przede-wszystkim trzeba mieć serwer WWW :) . Jego instalacja wygląda następująco :
1 |
root@raspberrypi:~# apt-get install apache2 |
Po zainstalowaniu pod adresem http://127.0.0.1/ w Raspberry lub http://adres IP RasPi/ na każdym komputerze w sieci lokalnej powinna wyświetlić się startowa strona Apacheche. Zanim przejdziemy do tworzenia swojej strony – jeszcze chwilę o zapisie danych.
Ponieważ cały system Raspberry zapisany jest na karcie SD – która jak wiadomo nie lubi dużej ilości zapisów – warto zapisywać dane na ram-disku (czyli katalogu który nie jest zapisany na karcie – ale w pamięci RAM która udaje system dyskowy). Ma to dodatkową zaletę, ponieważ zapis/odczyt działa dużo szybciej niż na karcie, ale i wady :
- po pierwsze zabiera RAM (na szczęście dużo go nie potrzeba)
- po drugie wszystkie dane znikają w przypadku braku zasilania (lub restartu)
Stworzenie takiego dysku i podmontowanie (podłączenie jako katalog) nie jest w Linuxie specjalnie trudne i sprowadza się do 3 kroków
1) Tworzymy w systemie plików katalog, w którym ma być ram-dysk. W moim projekcie “ramdisk” w katalogu “/var/www/html/”
1 2 3 |
root@raspberrypi:~# cd /var/www/html/ root@raspberrypi:~# mkdir ramdisk root@raspberrypi:~# chmod 777 ramdisk |
2) Dodajemy wpis w pliku z “partycjami” /etc/fstab. W nowej linii dodajemy wpis :
“tmpfs /var/www/html/ramdisk tmpfs rw,nodev,nosuid,size=512 0 0“
U mnie cały plik wygląda tak :
1 2 3 4 5 6 7 |
proc /proc proc defaults 0 0 /dev/mmcblk0p1 /boot vfat defaults 0 2 /dev/mmcblk0p2 / ext4 defaults,noatime 0 1 tmpfs /var/www/html/ramdisk tmpfs rw,nodev,nosuid,size=512 0 0 # a swapfile is not a swap partition, no line here # use dphys-swapfile swap[on|off] for that |
WAŻNE – żeby nie “namieszać” w innych linijkach – bo system może nie wstać
3) Zrestartować Raspberry i sprawdzić czy dysk się podmontował (np. df -h)
1 2 3 4 5 6 7 8 9 10 11 12 |
root@raspberrypi:/var/www/html# df -h System plików rozm. użyte dost. %uż. zamont. na /dev/root 7,1G 5,1G 1,7G 76% / devtmpfs 333M 0 333M 0% /dev tmpfs 462M 9,2M 453M 2% /dev/shm tmpfs 462M 6,4M 456M 2% /run tmpfs 5,0M 4,0K 5,0M 1% /run/lock tmpfs 462M 0 462M 0% /sys/fs/cgroup tmpfs 4,0K 4,0K 0 100% /var/www/html/ramdisk /dev/mmcblk0p1 60M 22M 39M 37% /boot tmpfs 93M 0 93M 0% /run/user/1000 tmpfs 93M 0 93M 0% /run/user/0 |
Teraz już można uruchomić program, który tworzy i zapisuje dane do pliku w odpowiednim miejscu. Jeśli program ma działać w tle dobrze żeby uruchamiał się podczas startu systemu. W tym celu można dodać do pliku “/etc/rc.local” następującą linikę “/home/pi/libnfc-1.7.1/examples/doc/mielmar >> /dev/null 2>&1” mój plik wygląda tak :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. # Print the IP address _IP=$(hostname -I) || true if [ "$_IP" ]; then printf "My IP address is %s\n" "$_IP" fi #/home/pi/libnfc-1.7.1/examples/doc/mielmar >> /home/pi/libnfc-1.7.1/examples/doc/mielmar.log 2>&1 /home/pi/libnfc-1.7.1/examples/doc/mielmar >> /dev/null 2>&1 exit 0 |
Po restarcie można sprawdzić czy program działa w tle
1 2 3 |
root@raspberrypi:/var/www/html# ps -ax |grep mielmar 833 ? Ss 0:00 /home/pi/libnfc-1.7.1/examples/doc/mielmar 1563 pts/0 S+ 0:00 grep mielmar |
a w pliku wyjściowym “/var/www/html/ramdisk/rfid.html” zmieniają się dane po przyłożeniu/zabraniu TAG-a.
Przykładowa strona.
Ja w swoim projekcie do odczytu danych wykorzystam javascript, ale nic nie szkodzi na przeszkodzie aby zrobić to np. poprzez PHP “inkludując” plik i odczytując jego zawartość. W moim przykładzie podmieniam zawartość pliku “index.html” w katalogu “/var/www/html/”, jego nowa zawartość wygląda następująco :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>RfId sample</title> <style> body { background-color: linen;} #TagId { width: 320px; padding: 10px; border: 5px solid gray; margin: auto; } </style> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script> // funkcja timera - wywołuje sparwdzanie pliku co 1s. function timedRead() { CheckTagId(); setTimeout("timedRead()",1000); } // wywołanie funkcja timera - przy starcie strony timedRead(); // sprawdzenie zawartosi pliku function CheckTagId(){ $.get( "/ramdisk/rfid.html", // probranie zawartości strony /ramdisk/rfid.html function( data ) { var jQobj = jQuery.parseJSON(data); //konwersja danych do obiektu JSON $('#TagId').html("uID : "+ jQobj.uID ); // wpisanie TAG na strone switch (jQobj.uID) { // w zależności od TAG- u rób coś case 'd9659523': $('#TagId').css('background-color' , 'red'); break; case '41dccfc4': $('#TagId').css('background-color' , 'lightyellow'); break; default: $('#TagId').css('background-color' , 'linen'); } }); } </script> </head> <body> <!-- <button onclick="CheckTagId();" >Check</button> --> <div id="TagId">uID</div> </body> </html> |
A efekt końcowy na stronie :
To już koniec. Pytania i uwagi proszę pisać w komentarzach.
Cześć, chcę zrobić podobny projekt i szukam taniego czytnika, który będzie w stanie odczytywać rfid i nfc. Jestem zielony w hardware wiec nie wiem czy ten który Ty użyłeś będzie wystarczał. Zamiast karty chcę używać telefonu i ewentualnie naklejek z tagiem nfc albo takich breloczków jak na stronie ali z linku tego czytnika. Mogę je zaprogramować właśnie z telefonu. Nie wiem jak ma się rfid do nfc i czy ten czytnik wystarczy, podpowiesz coś?
Można to samo zrobić na RasPI ZeroW. Dużo taniej. Można też połączyć bibliotekę libnfc z Pythonem, jak to proponują na: [http://denethor.wlu.ca/raspberry_pi/rpi_PN532_nfc.shtml] http://denethor.wlu.ca/raspberry_pi/rpi_PN532_nfc.shtmlopis. A mając tego typu rozwiązanie, można dane z readera porównywać lub zapisywać w sql-u, co pozwoli nam identyfikować czy rejestrować zdarzenie z kartą/smartfonem zNFC.
polecam blog Jarzebskiego, dla tych którzy chcą zorientować się o co chodzi z tym rfid infc.
Dobry opis. wszystko działa. Jedynie w pliku mielcar.c należy dopisać bibliotekę #include string.h , inaczej zgłosi problem podczas kompilacji ze zrozumieniem funkcji strcmp(). Całość sprawdziłem na RaspberryPi Zero W. Działa na porcie UART dla ttyAMA0 a nie na ttyS0 jak to ma miejsce z RasPi3. Dla prawidłowego działania należy w katalogu boot dokonać korekty w dwóch pliczkach: cmdline.txt i config.txt. Pojawia się też problem z załadowaniem na czas funkcji libnfc podczas wywoływania skompilowanej wersji mielmar w pliku rc.local. Polecenie: ps -ax |grep mielcar tego faktu przecież nie podaje. Służę pomocą dla tych użytkowników, którzy chcą spróbować sił na raspberry Pi Zero W.