Il progetto “sistema accesso Fingerprint Scanner TTL GT-511C3” è la terza puntata del progetto “Fingerprint Scanner TTL GT-511C3” farai un salto in avanti dal solo riconoscimento dell’impronta e gestione degli errori.
La domanda base che mi sono posto è: qual è il limite delle impronte digitali dal punto di vista della sicurezza?
La risposta che mi sono dato: l’impronta non è modificabile, a differenza di un pin o di una password, quindi, una volta che qualcuno dovesse entrare in possesso della mia impronta digitale, è difficile ma non impossibile, potrebbe avere libero accesso senza che io possa fare nulla.
D’altronde anche pin e password sono “scopribili”, pensa alle microcamere installate sui bancomat, e l’impronta digitale è unica e più difficile da clonare.
La soluzione che puoi utilizzare é unire gli aspetti migliori di entrambe le tecniche.
Fare in modo che il sistema di controllo dia il via libera quando riconosce una sequenza di impronte di 4 dita distinte della tua mano, ad esempio una delle seguenti sequenze:
- indice, indice, indice, indice
- indice, medio, indice, anulare
- tuo indice, indice di un’altra persona, tuo medio, medio di un’altra persona
L’ultimo metodo dovrebbe renderti chiaro come il sistema possa essere usato anche per garantire che l’accesso sia possibile solo se sono presenti due o più persone, una cassetta di sicurezza di banca, ad esempio.
Il principio della sequenza di impronte
La sequenza di impronte è modificabile e aggiunge sicurezza.
Per avere via libera devi possedere le impronte delle dita coinvolte e conoscere la sequenza stessa di sblocco.
Ho definito 2 tipi di sequenza:
- sequenza root: che permette di aggiungere dita e sequenze oltre a permettere l’accesso, ovviamente
- sequenza standard che permette il solo accesso, utile per far accedere persone che non devono modificare le impostazioni del sistema di controllo, quasi una funzione di sola lettura, in questo caso solo accesso.
Le funzioni da aggiungere allo sketch
- inserimento di altre impronte
- inserimento di sequenze
Il tutto pilotato da menu sul display LCD.
I prossimi passi
- permettere di associare un nome ad una sequenza
- permettere di cancellare una sequenza, per questo serve un nome associato alla sequenza
Nella quarta puntata aggiungerai le funzioni complete, interattivamente in questo articolo descrivo cosa fa il mio progetto fino a questo punto:
- inizia chiedendo di registrare almeno 2 impronte
- propone un menu in cui si possono aggiungere altre impronte e/o sequenze
- se non è stata aggiunta almeno una sequenza non ti permette di uscire
- la prima sequenza registrata è considerata di tipo root mentre le altre sono considerate di tipo standard
- una volta registrate almeno 2 impronte ed una sequenza permette di uscire dal menu e si mette in stand by
- alla pressione di un qualsiasi tasto sul display si mette in attesa di leggere la sequenza di dita
- se la sequenza non è riconosciuta restituisce errore
- se la sequenza è riconosciuta come standard cambia lo stato della serratura (simulata dal LED) e si mette nuovamente in attesa
- se la sequenza è riconosciuta come root cambia lo stato della serratura e mostra il menu in cui si possono aggiungere dita e/o sequenze.
- il pulsante ha una funzionalità in più: se si preme 2 volte consecutivamente a distanza di tempo ravvicinata, esegue il reset del sensore e della scheda per permettere di ricominciare da zero
Il numero di impronte registrate e le sequenze sono memorizzate sulla EEPROM di arduino.
Qualche informazione sulla posizione dei dati:
- nella prima posizione [ #0 ] c’è il numero di impronte registrate
- nella seconda [ #1 ] il numero di sequenze
- a partire dalla terza [ #2 ] gruppi di 4 byte indicano le sequenze di dita.
Se ricordi che la EEPROM di un arduino uno è di 1024 byte posso registrare, in questo modo, fino a 255 sequenze.
Lo sketch del sistema accesso fingerprint scanner TTL GT-511C3
Ecco lo sketch, invero un po’ lunghino, con i commenti, come al solito, alla fine:
#include <LiquidCrystal.h> #include <EEPROM.h> #include "FPS_GT511C3.h" #include "SoftwareSerial.h" #include <avr/io.h> #include <avr/wdt.h> #define Reset_AVR() wdt_enable(WDTO_30MS); while(1) {} LiquidCrystal lcd(8, 9, 4, 5, 6, 7); FPS_GT511C3 fps(12, 13); int dita_registrate; int sequenze; int dati_sequenze[10][4]; bool serratura_aperta=true; int led = 11; double intervallo,intervallo2; void setup() { lcd.begin(16, 2); pinMode (10,OUTPUT); dita_registrate=EEPROM.read(0); sequenze=EEPROM.read(1); Serial.begin(9600); fps.Open(); if (sequenze>0) { for (int i = 0; i<sequenze; i++) { dati_sequenze[i][0]=EEPROM.read(i*4+2); dati_sequenze[i][1]=EEPROM.read(i*4+3); dati_sequenze[i][2]=EEPROM.read(i*4+4); dati_sequenze[i][3]=EEPROM.read(i*4+5); } } digitalWrite(10,LOW); if (serratura_aperta) digitalWrite(led,HIGH); else digitalWrite(led,LOW); attachInterrupt(0, apre_chiude, FALLING); } void loop() { int pulsante; int apertura; int timeout; int i; if (dita_registrate<2) inizializza(); digitalWrite(led,serratura_aperta); pulsante=analogRead(0); if (pulsante<1000) { int sequenza[4]; digitalWrite(10,HIGH); fps.SetLED(true); lcd.clear(); lcd.print("Press finger #1"); if (put_finger()==1) return; fps.CaptureFinger(true); int id1 = fps.Identify1_N(); if(id1 < dita_registrate) { lcd.clear(); lcd.print("Remove finger"); if (remove_finger()==1) return; sequenza[0]=id1; lcd.clear(); lcd.print("Press finger #2"); if (put_finger()==1) return; fps.CaptureFinger(true); int id2 = fps.Identify1_N(); if(id2 < dita_registrate) { lcd.clear(); lcd.print("Remove finger"); if (remove_finger()==1) return; sequenza[1]=id2; lcd.clear(); lcd.print("Press finger #3"); if (put_finger()==1) return; fps.CaptureFinger(true); int id3 = fps.Identify1_N(); if(id3 < dita_registrate) { lcd.clear(); lcd.print("Remove finger"); if (remove_finger()==1) return; sequenza[2]=id3; lcd.clear(); lcd.print("Press finger #4"); if (put_finger()==1) return; fps.CaptureFinger(true); int id4 = fps.Identify1_N(); if(id4 < dita_registrate) { sequenza[3]=id4; if(sequenza[0]==dati_sequenze[0][0] && sequenza[1]==dati_sequenze[0][1] && sequenza[2]==dati_sequenze[0][2] && sequenza[3]==dati_sequenze[0][3]) { lcd.clear(); lcd.print("ROOT SEQUENCE"); delay(2000); lcd.clear(); fps.SetLED(false); apre_chiude(); mostra_menu(); } else { for(i = 1;i<sequenze;i++) { if(sequenza[0]==dati_sequenze[i][0] && sequenza[1]==dati_sequenze[i][1] && sequenza[2]==dati_sequenze[i][2] && sequenza[3]==dati_sequenze[i][3]) { lcd.clear(); lcd.print("STD SEQUENCE"); delay(2000); lcd.clear(); fps.SetLED(false); digitalWrite(10,LOW); apre_chiude(); return; } } if (i==sequenze) { lcd.clear(); lcd.print("BAD SEQUENCE"); delay(2000); lcd.clear(); fps.SetLED(false); digitalWrite(10,LOW); return; } } } else { lcd.clear(); lcd.print("BAD FINGER #4"); delay(2000); lcd.clear(); fps.SetLED(false); digitalWrite(10,LOW); return; } } else { lcd.clear(); lcd.print("BAD FINGER #3"); delay(2000); lcd.clear(); fps.SetLED(false); digitalWrite(10,LOW); return; } } else { lcd.clear(); lcd.print("BAD FINGER #2"); delay(2000); lcd.clear(); fps.SetLED(false); digitalWrite(10,LOW); return; } } else { lcd.clear(); lcd.print("BAD FINGER #1"); delay(2000); lcd.clear(); fps.SetLED(false); digitalWrite(10,LOW); return; } } } int inizializza() { int ritorno; int exit=0; int stato_menu=0; int ristampa = 1; digitalWrite(10,HIGH); lcd.clear(); lcd.print("INIZIALIZZAZIONE..."); fps.DeleteAll(); delay(2000); dita_registrate=0; sequenze=0; EEPROM.write(0,0); EEPROM.write(1,0); fps.SetLED(true); ritorno=Enroll(0); while (ritorno != 0) ritorno=Enroll(0); ritorno = Enroll(1); while (ritorno != 0) ritorno=Enroll(1); EEPROM.write(0,2); dita_registrate = 2; fps.SetLED(false); while (exit==0) { int pulsante; if (ristampa==1) { switch(stato_menu) { case 0: lcd.clear(); lcd.print("Add finger <--"); lcd.setCursor(0,1); lcd.print("Add sequence"); break; case 1: lcd.clear(); lcd.print("Add finger"); lcd.setCursor(0,1); lcd.print("Add sequence <--"); break; case 2: lcd.clear(); lcd.print("Add sequence"); lcd.setCursor(0,1); lcd.print("Exit <--"); break; } } ristampa=0; pulsante=analogRead(0); if ((pulsante>190) && (pulsante<220)) { ristampa=1; stato_menu-=1; if (stato_menu<0) stato_menu = 2; delay(300); } if ((pulsante>390) && (pulsante<430)) { ristampa=1; stato_menu+=1; if (stato_menu>2) stato_menu = 0; delay(300); } if ((pulsante>800) && (pulsante<840)) { if (stato_menu==0) { fps.SetLED(true); lcd.clear(); lcd.print("Record new finger"); delay(2000); ritorno = Enroll(dita_registrate+1); if (ritorno==0) { ristampa=1; lcd.clear(); lcd.print("Finger added!!"); dita_registrate+=1; EEPROM.write(0,dita_registrate); delay(2000); } fps.SetLED(false); } if (stato_menu==1) { fps.SetLED(true); ristampa=1; add_sequence(); fps.SetLED(false); } if (stato_menu==2) { if (sequenze > 0) exit=1; else { ristampa=1; lcd.clear(); lcd.print("You have to add"); lcd.setCursor(0,1); lcd.print("one sequence"); delay(2000); } } } } digitalWrite(10,LOW); } int Enroll(int enrollid){ // Enroll test // find open enroll id bool result; int enr_result; lcd.clear(); digitalWrite(10,HIGH); lcd.print("Record finger "); lcd.print(enrollid); enr_result=fps.EnrollStart(enrollid); if (enr_result==0) { while(fps.IsPressFinger() == false) delay(100); result = fps.CaptureFinger(true); if (result) { enr_result=fps.Enroll1(); if (enr_result ==0) { lcd.clear(); lcd.print("Remove finger"); while(fps.IsPressFinger() == true) delay(100); lcd.clear(); lcd.print("Press again"); while(fps.IsPressFinger() == false) delay(100); result = fps.CaptureFinger(true); if (result) { enr_result=fps.Enroll2(); if (enr_result ==0) { lcd.clear(); lcd.print("Remove finger"); while(fps.IsPressFinger() == true) delay(100); lcd.clear(); lcd.print("Press again"); while(fps.IsPressFinger() == false) delay(100); result = fps.CaptureFinger(true); if (result) { lcd.clear(); lcd.print("Remove finger"); enr_result = fps.Enroll3(); if (enr_result == 0) { lcd.clear(); lcd.print("Enrolling OK"); delay(2000); return 0; } else { lcd.clear(); lcd.print("Enrolling KO:"); lcd.setCursor(0,1); switch(enr_result) { case 1: lcd.print("Generic error"); break; case 2: lcd.print("Too bad FPrint"); break; case 3: lcd.print("FPrint exists"); break; } delay(2000); return 1; } } else { lcd.clear(); lcd.print("Failed 3rd read"); delay(2000); return 1; } } else { lcd.clear(); lcd.print("Enroll failed:"); lcd.setCursor(0,1); switch(enr_result) { case 1: lcd.print("Generic error"); break; case 2: lcd.print("Too bad FPrint"); break; case 3: lcd.print("FPrint exists"); break; } delay(2000); return 1; } } else { lcd.clear(); lcd.print("Failed 2nd read"); delay(2000); return 1; } } else { lcd.clear(); lcd.print("Enroll failed:"); lcd.setCursor(0,1); switch(enr_result) { case 1: lcd.print("Generic error"); break; case 2: lcd.print("Too bad FPrint"); break; case 3: lcd.print("FPrint exists"); break; } delay(2000); return 1; } } else { lcd.clear(); lcd.print("Failed 1st read"); delay(2000); return 1; } } else { lcd.clear(); lcd.print("Enroll failed:"); lcd.setCursor(0,1); switch(enr_result) { case 1: lcd.print("Reader DB full"); break; case 2: lcd.print("ID "); lcd.print(enrollid); lcd.print(" invalid"); break; case 3: lcd.print("ID "); lcd.print(enrollid); lcd.print(" busy"); break; } delay(2000); return 1; } } void add_sequence () { int sequenza[4]; int id; lcd.clear(); lcd.print("Press finger #1"); while(fps.IsPressFinger() == false) delay(100); fps.CaptureFinger(true); id = fps.Identify1_N(); if(id < dita_registrate) { sequenza[0]=id; lcd.clear(); lcd.print("Remove finger"); while(fps.IsPressFinger() == true) delay(100); lcd.clear(); lcd.print("Press finger #2"); while(fps.IsPressFinger() == false) delay(100); fps.CaptureFinger(true); id = fps.Identify1_N(); if(id < dita_registrate) { sequenza[1]=id; lcd.clear(); lcd.print("Remove finger"); while(fps.IsPressFinger() == true) delay(100); lcd.clear(); lcd.print("Press finger #3"); while(fps.IsPressFinger() == false) delay(100); fps.CaptureFinger(true); id = fps.Identify1_N(); if(id < dita_registrate) { sequenza[2]=id; lcd.clear(); lcd.print("Remove finger"); while(fps.IsPressFinger() == true) delay(100); lcd.clear(); lcd.print("Press finger #4"); while(fps.IsPressFinger() == false) delay(100); fps.CaptureFinger(true); int id = fps.Identify1_N(); if(id < dita_registrate) { sequenza[3]=id; lcd.clear(); lcd.print("Sequence added!!"); dati_sequenze[sequenze][0]=sequenza[0]; dati_sequenze[sequenze][1]=sequenza[1]; dati_sequenze[sequenze][2]=sequenza[2]; dati_sequenze[sequenze][3]=sequenza[3]; EEPROM.write(sequenze*4+2,sequenza[0]); EEPROM.write(sequenze*4+3,sequenza[1]); EEPROM.write(sequenze*4+4,sequenza[2]); EEPROM.write(sequenze*4+5,sequenza[3]); sequenze+=1; } else { lcd.clear(); lcd.print("Error finger 4"); delay(2000); } } else { lcd.clear(); lcd.print("Error finger 3"); delay(2000); } } else { lcd.clear(); lcd.print("Error finger 2"); delay(1000); } } else { lcd.clear(); lcd.print("Error finger 1"); delay(2000); } } void open_lock() { if (serratura_aperta == false) { serratura_aperta=true; digitalWrite(led,HIGH); } } void close_lock() { if(serratura_aperta) { serratura_aperta = false; digitalWrite(led,LOW); } } void mostra_menu() { int timeout=0; int stato_menu=0; int ristampa = 1; while (timeout < 5000) { int pulsante; if (ristampa==1) { switch(stato_menu) { case 0: lcd.clear(); lcd.print("Add finger <--"); lcd.setCursor(0,1); lcd.print("Add sequence"); break; case 1: lcd.clear(); lcd.print("Add finger"); lcd.setCursor(0,1); lcd.print("Add sequence <--"); break; case 2: lcd.clear(); lcd.print("Add sequence"); lcd.setCursor(0,1); lcd.print("Lock <--"); break; } } ristampa=0; pulsante=analogRead(0); if ((pulsante>190) && (pulsante<220)) { ristampa=1; stato_menu-=1; if (stato_menu<0) stato_menu = 2; timeout=0; delay(300); } else if ((pulsante>390) && (pulsante<430)) { ristampa=1; stato_menu+=1; if (stato_menu>2) stato_menu = 0; timeout=0; delay(300); } else if ((pulsante>800) && (pulsante<840)) { if (stato_menu==0) { fps.SetLED(true); lcd.clear(); lcd.print("Record new finger"); delay(2000); int ritorno = Enroll(dita_registrate+1); if (ritorno==0) { ristampa=1; lcd.clear(); lcd.print("Finger added!!"); dita_registrate+=1; EEPROM.write(0,dita_registrate); delay(2000); } fps.SetLED(false); } if (stato_menu==1) { fps.SetLED(true); ristampa=1; add_sequence(); fps.SetLED(false); } if (stato_menu==2) { close_lock(); } } else if (pulsante>1000) { timeout +=200; delay(200); } } digitalWrite(10,LOW); } void apre_chiude() { double delta; intervallo2=millis(); delta = intervallo2 - intervallo; if (delta>500 && delta<1000) { EEPROM.write(0,0); EEPROM.write(1,0); Reset_AVR(); } else intervallo=intervallo2; if (serratura_aperta) { close_lock(); serratura_aperta = false; } else { open_lock(); serratura_aperta=true; } } int put_finger() { int timeout = 0; while(fps.IsPressFinger() == false) { delay(100); timeout+=100; if (timeout > 3000) { fps.SetLED(false); digitalWrite(10,LOW); return 1; } } return 0; } int remove_finger() { int timeout = 0; while(fps.IsPressFinger() == true) { delay(100); timeout+=100; if (timeout > 3000) { fps.SetLED(false); digitalWrite(10,LOW); return 1; } } return 0; }
Vediamo le differenze rispetto ai precedenti sketch:
Righe 5-8: istruzioni che mi serviranno in seguito per il reset.
Righe 12-14: variabili che memorizzano dati su numero di dita, numero di sequenze e sequenze registrate.
Riga 17: variabili per calcolare se devo o non devo effettuare il reset
Righe 23-24: leggo dalla posizione 0 quante dita e quante sequenze registrate ci sono.
Righe 27-36: se c’è almeno una sequenza registrata leggo i loro valori dalla EEPROM
Riga 49: se ci sono meno di 2 dita registrate eseguo la funzione inizializza.
Riga 52: controlla se viene premuto un pulsante del display. Se non viene premuto non fa nulla
Riga 59: aspetta che venga premuto il primo dito della sequenza sul sensore
Riga 62: controlla se il dito è registrato; se non lo è passa alle righe 175-181 che mostrano l’errore sul display attendono 2 secondi spengono la retroilluminazione ed il sensore e fanno mettere nuovamente in attesa il loop
Riga 67: valorizza in sequenza[0] il valore dell’id del dito
Riga 71: aspetta il secondo dito
Riga 73: se lo identifica procede altrimenti gestisce l’errore alle righe 164-170
Riga 78: valorizza il secondo id
Righe 79-97: analoghe ma per le dita 3 e 4 della sequenza
Righe 98: confronta la sequenza con la sequenza 0(root). Se coincidono cambia stato alla porta ( Riga 107) e richiama la funzione mostra_menu ( Riga 108)
Righe 112-126: altrimenti confronta la sequenza con tutte quelle memorizzate, se la trova cambia stato alla porta
Righe 128-137: dopo aver eseguito il ciclo la variabile i==sequenze solo se non ha trovato la sequenza fra quelle memorizzate scrive l’errore sul display senza mostrare il menu né cambiare lo stato alla porta
Riga 195: ripulisce il sensore dalle eventuali impronte memorizzate
Righe 199-200: azzerano le caselle della EEPROM dove sono valorizzati il numero di dita e sequenze memorizzate
Righe 202-206 richiedono la memorizzazione di 2 dita e poi scrivono nella posizione 0 della EEPROM il valore 2 (2 dita memorizzate). Nota: finché l’enroll di entrambe le dita non è andato a buon fine il programma resta su queste 4 righe
Righe 209: inizia il ciclo while per rimanere nel menu finché non si è memorizzata una sequenza di root.
Riga 212: Controlla la variabile ristampa, il cui significato è: “il contenuto del display è da aggiornare”, se vale 1 allora scrive il menu sul display.
Righe 214-234: Il menu è composto di 3 voci e cosa stampare dipende dalla variabile stato_menu, essa inizialmente vale 0 per cui vengono mostrate le stringhe alle righe 216-221
Riga 236: se non accade nulla il display non viene aggiornato (ristampa =0)
Righe 238-244: se viene premuto il pulsante su del display allora lo stato decrementa (se diventa minore di 0 viene reimpostato a 2, il valore massimo) e la variabile ristampa valorizzata ad 1 (il display è da aggiornare).
Righe 245-251. analoghe alle righe 238-244 ma per il pulsante giù del display. Se stato_menu diventa > 2 viene reimpostata a 0
Righe 252-291: è stato premuto il pulsante select. Se stato_menu == 0 allora l’utente vuole aggiungere un dito quindi esegue il codice delle righe da 254 a 271. Se stato_menu==1 allora l’utenet vuole aggiungere una sequenza (righe da 272 a 278). Se stato_menu == 2 vuole uscire dall’inizializzazione cosa permessa solo se è stata registrata almeno una sequenza (quella root, controllo alla riga 281).
Righe 297-458: Funzione di Enroll, già descritta nel precedente articolo
Righe 460: inizia la funzione per aggiungere una sequenza di dita
Righe 503-515, dopo aver letto le 4 dita ed averle riconosciute, aggiunge i dati relativi alla sequenza sia in memoria (variabile dati_sequenze) sia nelle EEPROM (la sequenza N inizia in posizione N*4 + 2 perché ogni sequenza occupa 4 byte ed i primi 2 sono utilizzati per memorizzare il numero di dita ed il numero di sequenze.
Righe 546-553: funzione open_lock cambia lo stato al led da spento ad acceso, la trovi dettagliatamente descritta nel precedente articolo
Righe 554-561: funzione close_lock cambia lo stato al led da acceso a spento, la trovi dettagliatamente descritta nel precedente articolo
Righe 563-652: funzione che mostra il menu con logica simile a quella descritta precedentemente.
Righe 658-659: calcola da quanti millisecondi è stata richiamata l’ultima volta
Righe 660-666: se è stata richiamata da un intervallo di tempo variabile tra 0,5 e 1 secondo effettua il reset (doppia pressione ravvicinata del pulsante) alla riga 664. Il limite inferiore c’è perché a volte la pressione del pulsante genera 2 falling molto ravvicinati che vanno filtrati altrimenti c’è il rischio che la pressione del pulsante porti a reset indesiderati.
Righe 679-711: già descritte.
Sei arrivato in fondo a questo lungo articolo e ti confido che, nel codice, c’è un errore di logica, leggi in fondo dopo il video per gli indizi.
Il video
Se riesci a trovarlo per favore scrivi un commento, ti do qualche indizio: il sensore mantiene le impronte registrate anche se viene tolta l’alimentazione. Lo stesso fanno le eeprom di Arduino (per questo le ho utilizzate). L’errore di logica emerge solo se tolgo l’alimentazione e solo se c’è più di una sequenza registrata.
Ho detto troppo?
Qual è l’errore?