Introduzione
Controllo accessi con badge RFID: un sistema avanzato per la gestione degli accessi e dei permessi supportato dalla potenza della Raspberry Pi. Questa soluzione integrata è progettata per offrire un controllo degli accessi efficiente e flessibile, utilizzando la tecnologia RFID per garantire un’identificazione sicura e rapida degli utenti.
Al centro di questo ecosistema si trova un database SQLite3, un’opzione ideale per la memorizzazione e la gestione dei dati degli utenti e dei permessi. SQLite3 offre una combinazione unica di leggerezza, affidabilità e flessibilità, consentendo un rapido accesso ai dati e una gestione ottimizzata delle informazioni senza la necessità di un server separato.
Oltre alla gestione dei dati, il sistema include anche un server API REST realizzato tramite Flask che espone funzionalità CRUD per la gestione della tabella degli utenti. CRUD sta per Create, Read, Update e Delete (Crea, Leggi, Aggiorna e Cancella) cioè le operazioni principali che in genere si fanno su un record di un database. Questo permette agli amministratori di modificare dinamicamente le autorizzazioni degli utenti. Oltre a questo, le API REST consentono di visualizzare il registro degli accessi per un dato giorno. L’interfaccia API REST offre un’esperienza intuitiva e scalabile per l’interazione con il sistema, consentendo una facile integrazione con altre applicazioni e sistemi.
Inoltre, il sistema è arricchito da un bot Telegram dedicato, che consente agli utenti di ricevere notifiche istantanee sugli accessi tramite messaggi diretti e di eseguire comandi CRUD sulla tabella degli utenti (come quelle disponibili con le API REST) direttamente dalla piattaforma di messaggistica. Questo aggiunge un livello di flessibilità e accessibilità al sistema, consentendo agli utenti di gestire gli accessi ovunque si trovino.
Da un punto di vista tecnico, l’intero sistema è virtualizzato tramite Virtualenv (di cui parleremo più avanti), garantendo un ambiente isolato e gestibile per l’esecuzione delle applicazioni Python sulla Raspberry Pi. Questo approccio consente una maggiore sicurezza e stabilità del sistema, assicurando un’esperienza affidabile e coerente per gli utenti.
In sintesi, il controllo accessi RFID rappresenta una soluzione completa e all’avanguardia per la gestione degli accessi e dei permessi, integrando tecnologie avanzate come RFID, SQLite3, API REST e bot Telegram per offrire un’esperienza completa e altamente personalizzabile agli utenti finali.
Descrizione del funzionamento del controllo accessi con badge
Il nostro progetto di sicurezza domestica si basa su un sistema automatizzato che integra tecnologie avanzate per garantire l’accesso sicuro e controllato. Ecco come funziona:
- Inizializzazione del Sistema: Il sistema contiene sul database una tabella di nome users che contiene, per ogni riga, l’UUID di un utente, il suo nome e i suoi privilegi di accesso (“g” per granted e “d” per denied). Ogni volta che un tag RFID viene rilevato dal lettore, il sistema legge questa tabella e la carica su un dizionario (di cui parleremo in uno dei successivi paragrafi). Quindi il dizionario viene sempre aggiornato perché la tabella potrebbe essere stata modificata nel frattempo (per esempio ad un utente potrebbero essere stati cambiati i privilegi di accesso). Inoltre il sistema dispone di un piccolo buzzer (che verrà brevemente descritto più avanti) che emetterà un segnale acustico ogni volta che il lettore RFID ha rilevato un tag RFID (cioè un badge o un portachiavi dotato di tag RFID).
- Identificazione dell’Utente: Quando un badge viene avvicinato al modulo RFID, il sistema legge l’UUID (Universally Unique Identifier) del badge. Questo UUID è un identificatore univoco per ogni badge, utilizzato per distinguere gli utenti.
- Controllo delle Autorizzazioni: Dopo aver letto l’UUID, il sistema controlla il dizionario (che è stato appena aggiornato con la lettura della tabella users del database) per determinare se l’utente ha il permesso di accedere.
- Decisione di Accesso: In base al permesso associato all’UUID, il sistema decide se far scattare il relè, consentendo o negando l’accesso. Se l’UUID è associato a un permesso concesso (“g”), il relè si attiva, consentendo l’accesso. Se il permesso è negato (“d”), l’accesso viene negato. Inoltre, nel primo caso si accende un LED verde mentre nel secondo caso si accende un LED rosso. Il LED rosso rimane acceso anche quando il sistema è nello stato “inattivo” cioè sta aspettando che venga avvicinato un tag RFID al lettore.
- Registrazione degli Accessi: Ogni tentativo di accesso viene registrato sul database nella tabella accessusers . Questo fornisce un registro temporale degli accessi, utile per il monitoraggio e la sicurezza. Per ogni riga viene registrato l’UUID, la data, l’ora e se l’ingresso è stato concesso (“g”) o negato (“d”). Nel caso lo UUID non fosse presente nella tabella users, il tentativo di accesso verrebbe ugualmente registrato nel database ma con privilegio “n” (che sta per not recognized) e l’ingresso verrebbe negato (in quanto UUID sconosciuto al sistema).
- Notifiche Telegram: Contemporaneamente al tentativo di accesso (che può andare a buon fine o no), il sistema invia un messaggio di avviso al bot Telegram. Questo permette agli utenti di ricevere notifiche in tempo reale sugli accessi, aumentando il controllo e la sicurezza.
- Gestione delle Autorizzazioni tramite API REST e tramite bot Telegram: Il sistema è ulteriormente integrato con un server che espone API REST per la gestione delle autorizzazioni. Queste API permettono di leggere la tabella delle autorizzazioni, aggiungere un nuovo utente con i suoi privilegi, modificare i privilegi di un utente esistente o eliminare un utente ma anche leggere gli accessi avvenuti in un dato giorno. Tramite il bot Telegram si potrà aggiungere un nuovo utente (UUID) con i suoi privilegi o modificare i privilegi di un utente già esistente, eliminare un utente, leggere i privilegi di accesso di un dato utente. In entrambi i casi (API REST o bot Telegram) le modifiche alle autorizzazioni saranno permanenti in quanto verranno salvate sul database.
Cosa è la tecnologia RFID
Il Radio-Frequency Identification (RFID) è un sistema di identificazione automatica che utilizza campi elettromagnetici per trasferire dati tra un lettore e una tag RFID. Questa tecnologia è basata sulla comunicazione senza contatto e consente la memorizzazione e il recupero di informazioni da oggetti, animali o persone dotati di tag RFID. Ecco alcuni punti chiave relativi al funzionamento e all’utilizzo dell’RFID:
- Principio di funzionamento:
- Un sistema RFID è composto da un lettore (o interrogatore) e una o più tag RFID.
- La tag contiene un chip che memorizza un codice univoco o altre informazioni.
- Quando la tag si trova nel campo elettromagnetico del lettore, riceve energia e trasmette i dati memorizzati al lettore.
- Frequenze di funzionamento:
- Gli RFID operano a diverse frequenze, suddivise principalmente in bassa frequenza (LF), alta frequenza (HF), ultra alta frequenza (UHF), e frequenza molto alta (VHF).
- Le diverse frequenze influenzano la distanza di lettura e la capacità di penetrare attraverso materiali.
- Tipi di tag RFID:
- Le tag possono essere attive (con batteria) o passive (senza batteria).
- Le tag passive sono alimentate dal campo del lettore e hanno una distanza di lettura più breve rispetto alle tag attive.
- Applicazioni comuni:
- Controllo degli accessi: badge RFID per aperture automatiche o autenticazioni di sicurezza.
- Logistica e tracciabilità: monitoraggio di merci in transito attraverso magazzini e catene di distribuzione.
- Pagamenti contactless: sistemi di pagamento senza contatto come le carte contactless.
- Sicurezza e privacy:
- L’RFID può presentare sfide legate alla sicurezza e alla privacy, con la possibilità di intercettare e clonare i dati.
- Sono state sviluppate varie tecniche di crittografia per proteggere le informazioni scambiate tra i lettori e le tag.
- Implementazione con la Raspberry PI:
- La Raspberry PI può essere utilizzata come lettore RFID, interfacciandosi con un modulo RFID compatibile.
- Le informazioni lette dalla tag RFID possono essere utilizzate per attivare specifiche azioni o accedere a risorse.
L’utilizzo di RFID offre un’efficace e conveniente soluzione per molteplici applicazioni, consentendo l’automazione di processi e migliorando l’efficienza operativa.
Il kit RFID impiegato in questo progetto
Il kit, distribuito dall’azienda AZ-DELIVERY, si compone di un lettore RFID, un badge, un portachiavi (sempre RFID), due tipi di connettori da saldare sul lettore (uno con i contatti dritti e uno con i contatti ad angolo retto). Se non hai dimestichezza col mondo dei saldatori e vuoi provare a saldare il connettore in autonomia, ti consiglio di dare uno sguardo all’articolo Un altro tutorial su come saldare.
Sia il badge che il portachiavi contengono un codice che può essere letto dal lettore RFID. Nel mio caso il badge ha codice esadecimale 73051e99f1 mentre il portachiavi ha codice esadecimale a305cd0b60.
Il modulo di lettura RFID si interfaccia alla Raspberry PI tramite comunicazione SPI di cui parleremo nel paragrafo successivo.
Nella foto successiva potrai vedere il kit completo:
Interfaccia SPI
Il modulo lettore RFID usa l’interfaccia SPI, che è un protocollo seriale di comunicazione a 4 fili comunemente utilizzato in progetti embedded. Questi quattro fili sono:
- MISO (Master In Slave Out): questo è il pin attraverso il quale il modulo riceve dati dal dispositivo master, che di solito è l’Arduino o un altro microcontrollore.
- MOSI (Master Out Slave In): questo è il pin attraverso il quale il modulo invia dati al dispositivo master.
- SCK (Serial Clock): questo è il pin del clock che sincronizza la trasmissione dei dati tra il modulo e il dispositivo master.
- CS (Chip Select): questo pin viene utilizzato per selezionare il modulo RFID e inizializzare le operazioni di invio/ricezione dei dati.
Il buzzer
Il buzzer è un componente elettronico ampiamente utilizzato per generare segnali acustici. Esistono due tipi principali di buzzer, attivi e passivi:
- Buzzer passivi: funzionano in modo simile agli altoparlanti: richiedono un segnale di ingresso, generalmente sotto forma di un’onda quadra PWM, per produrre suoni. Questo segnale può essere variato in ampiezza e frequenza per ottenere toni diversi, consentendo di suonare melodie e generare segnali sonori più complessi rispetto ai buzzer attivi.
- Buzzer attivi: sono dotati di un integrato interno che genera una frequenza predefinita quando alimentati, senza la necessità di fornire un segnale di ingresso esterno. In pratica, basta applicare l’alimentazione per farli suonare alla frequenza prestabilita.
Il buzzer utilizzato in questo progetto è di tipo attivo.
Cosa è Flask
Flask è un framework leggero per la creazione di applicazioni web in Python. È flessibile, semplice da usare e offre un’ampia gamma di funzionalità per lo sviluppo di server di API RESTful.
Essenzialmente, Flask permette di definire delle “routes” o percorsi URL che corrispondono alle richieste dei client. Ogni route è associata ad una funzione Python che elabora la richiesta e restituisce una risposta al client. Questo approccio rende facile creare endpoint per le API REST, in cui le richieste HTTP (come GET, POST, PUT, DELETE) sono gestite in modo appropriato per accedere e manipolare le risorse del server.
Flask è basato su Werkzeug, un toolkit WSGI, e Jinja2, un motore di templating per Python. Utilizza un approccio “micro”, il che significa che fornisce solo il minimo indispensabile per creare applicazioni web, lasciando al programmatore la libertà di estendere le funzionalità secondo le proprie esigenze.
Grazie alla sua semplicità e flessibilità, Flask è ampiamente utilizzato per la creazione di server di API REST, fornendo un modo efficiente e scalabile per gestire le comunicazioni tra client e server in applicazioni web e servizi basati su microservizi. Più in particolare:
- Framework leggero: Flask è progettato per essere leggero e non intrusivo. Non impone una struttura rigida per le tue applicazioni, ma fornisce solo gli strumenti essenziali per creare server web.
- Routing flessibile: Con Flask, è possibile definire facilmente dei percorsi URL, noti come “routes”, che corrispondono alle richieste dei client. Ogni route è associata ad una funzione Python, chiamata “view function”, che elabora la richiesta e restituisce una risposta.
- Gestione delle richieste HTTP: Flask offre un’interfaccia intuitiva per gestire le varie operazioni HTTP come GET, POST, PUT, DELETE e altre. Questo consente di creare facilmente endpoint per le API REST, in cui le richieste HTTP sono elaborate in modo appropriato per accedere e manipolare le risorse del server.
- Estensioni modulari: Flask supporta un sistema di estensioni che consente di aggiungere funzionalità aggiuntive all’applicazione in modo modulare. Ci sono estensioni disponibili per quasi ogni esigenza, dall’autenticazione all’accesso al database e altro ancora.
- Integrazione con Werkzeug e Jinja2: Flask si basa su Werkzeug, un toolkit WSGI per Python, per gestire le richieste HTTP e le risposte. Utilizza anche Jinja2 come motore di templating per generare dinamicamente i contenuti HTML delle pagine web.
- Approccio “micro”: Flask adotta un approccio “micro”, il che significa che fornisce solo il minimo indispensabile per creare applicazioni web. Questo permette ai programmatori di mantenere il controllo completo sulle funzionalità dell’applicazione e di estenderle secondo le proprie esigenze.
- Semplicità e flessibilità: Grazie alla sua semplicità e flessibilità, Flask è ampiamente utilizzato per la creazione di server di API REST. È particolarmente adatto per progetti di dimensioni ridotte o medio-piccole, dove è importante mantenere il codice pulito, leggibile e facilmente gestibile.
In sintesi, Flask offre un modo efficiente e scalabile per creare server web e gestire le comunicazioni tra client e server in applicazioni web e servizi basati su microservizi. La sua flessibilità e facilità d’uso lo rendono una scelta popolare tra i programmatori Python per lo sviluppo di applicazioni web.
Cosa è Sqlite3
SQLite è un database leggero, veloce e autonomo progettato per gestire database locali in modo semplice ed efficiente. È una libreria software che fornisce un sistema di gestione di database relazionali (RDBMS) incorporato direttamente nelle applicazioni. A differenza dei tradizionali sistemi client-server, SQLite funziona senza la necessità di un server separato, rendendolo ideale per applicazioni embedded, mobile e web.
Con SQLite, è possibile creare, gestire e interrogare database direttamente dal proprio codice, senza dover installare o configurare un server di database separato. Grazie alla sua portabilità e leggerezza, SQLite è ampiamente utilizzato in una vasta gamma di applicazioni, tra cui applicazioni mobili, browser web, sistemi embedded, applicazioni desktop e altro ancora.
Una delle caratteristiche distintive di SQLite è la sua capacità di gestire database di piccole e medie dimensioni con estrema efficienza e velocità. Anche se è leggero, SQLite supporta molte delle caratteristiche avanzate dei database relazionali, tra cui transazioni ACID, vincoli di integrità referenziale, trigger, viste e molto altro ancora.
- Database embedded: SQLite3 è un database relazionale incorporato che opera direttamente su file di database senza la necessità di un server client separato. Questo lo rende ideale per l’integrazione in applicazioni software che richiedono la gestione dei dati senza l’installazione e la configurazione di un sistema di database separato.
- Zero configurazione: SQLite3 non richiede alcuna configurazione o amministrazione del server. Basta creare un file di database e SQLite3 è pronto per l’uso. Questo lo rende estremamente facile da utilizzare e adatto per progetti in cui la complessità e l’overhead di un database server tradizionale non sono necessari.
- Supporto completo SQL: SQLite3 supporta la maggior parte del linguaggio SQL standard, consentendo di creare, modificare e interrogare facilmente il database utilizzando le istruzioni SQL familiari come SELECT, INSERT, UPDATE e DELETE. Offre anche funzionalità avanzate come JOIN, GROUP BY, e ORDER BY per interrogare e analizzare i dati in modo efficace.
- Transazioni ACID: SQLite3 supporta transazioni ACID (Atomicity, Consistency, Isolation, Durability), garantendo che le operazioni di scrittura sul database siano sicure e affidabili. Le transazioni possono essere avviate, eseguite e commesse o rollbackate secondo necessità, garantendo l’integrità e la coerenza dei dati.
- Portabilità: SQLite3 è altamente portabile e può essere eseguito su una vasta gamma di piattaforme, compresi sistemi operativi desktop (Windows, macOS, Linux) e dispositivi embedded come smartphone, tablet e dispositivi IoT. Questa portabilità lo rende una scelta popolare per lo sviluppo di applicazioni multi-piattaforma.
- Dimensioni ridotte: SQLite3 è progettato per essere estremamente leggero e ha una piccola dimensione del file binario, occupando poche risorse di sistema e richiedendo una quantità minima di memoria per funzionare. Questo lo rende adatto per l’uso su dispositivi con risorse limitate, come smartphone e dispositivi IoT.
- Facilità d’uso: SQLite3 è progettato per essere semplice da usare e richiede solo poche righe di codice per iniziare a utilizzare il database in un’applicazione. Offre anche un’ampia documentazione e una vasta comunità di sviluppatori che forniscono supporto e risorse per l’apprendimento.
In sintesi, SQLite3 è una scelta eccellente per le applicazioni che richiedono un database leggero, incorporato e facile da usare. Offre un’ampia gamma di funzionalità SQL, una sicurezza e affidabilità delle transazioni, e una portabilità che lo rende adatto per una vasta gamma di progetti e piattaforme.
Cosa è un dizionario
Definizione di dizionario:
- In programmazione, un dizionario è una struttura dati che consente di memorizzare coppie chiave-valore.
- Ogni elemento del dizionario è costituito da una chiave unica associata a un valore.
- Struttura e accesso:
- I dizionari sono strutturati in modo da consentire un accesso rapido ai valori tramite le chiavi.
- L’accesso ai valori avviene specificando la chiave associata, consentendo un recupero efficiente delle informazioni.
- Chiavi e Valori:
- Le chiavi in un dizionario sono di solito stringhe, numeri o altri tipi di dati immutabili.
- I valori possono essere di qualsiasi tipo, inclusi numeri, stringhe, liste o addirittura altri dizionari.
- Utilità e applicazioni:
- I dizionari sono ampiamente utilizzati per gestire dati strutturati e associare informazioni in modo flessibile.
- Sono particolarmente utili quando si deve accedere ai dati tramite identificatori univoci anziché posizioni fisse.
- Operazioni comuni:
- Aggiunta di nuove coppie chiave-valore:
mioDizionario[chiave] = valore
. - Rimozione di una coppia:
delete mioDizionario[chiave]
. - Recupero del valore associato a una chiave:
valore = mioDizionario[chiave]
.
- Aggiunta di nuove coppie chiave-valore:
- Iterazione:
- È possibile iterare su tutte le chiavi, i valori o le coppie chiave-valore del dizionario.
- Ciò consente di eseguire operazioni su tutti gli elementi del dizionario in modo efficiente.
- È possibile estrarre la lista di tutte le chiavi o di tutti i valori presenti nel dizionario.
- Implementazione nella Raspberry PI:
- In contesti di programmazione per la Raspberry PI, i dizionari sono spesso utilizzati per gestire configurazioni, associazioni chiave-valore dinamiche o mappare informazioni rilevanti.
- Efficienza e complessità:
- L’efficienza nell’accesso ai dati tramite chiavi rende i dizionari una scelta ideale per situazioni in cui è necessario un recupero veloce delle informazioni.
- La complessità temporale per le operazioni di ricerca, inserimento e cancellazione è spesso molto bassa rispetto ad altre strutture dati.
L’utilizzo di dizionari aggiunge un livello di flessibilità e organizzazione ai programmi, consentendo una gestione efficiente e dinamica delle informazioni.
Cosa è Virtualenv
Virtualenv è uno strumento che consente agli sviluppatori di creare e gestire ambienti Python isolati e indipendenti all’interno dei propri progetti. Con virtualenv, è possibile creare un ambiente Python isolato che contiene solo le dipendenze specifiche del progetto, senza interferire con altre installazioni Python sul sistema.
L’utilizzo di virtualenv offre numerosi vantaggi, tra cui la possibilità di mantenere ordine e coerenza tra le dipendenze dei vari progetti, la facilità di condivisione e distribuzione del codice e la capacità di testare applicazioni in un ambiente controllato e isolato. Inoltre, virtualenv consente agli sviluppatori di sperimentare con diverse versioni di Python e librerie senza influire sulle altre applicazioni o sul sistema operativo.
Una volta creato un ambiente virtuale con virtualenv, è possibile attivarlo per utilizzare le versioni specifiche di Python e le dipendenze del progetto. Questo consente agli sviluppatori di lavorare in modo efficiente e sicuro su progetti complessi senza preoccuparsi di conflitti di dipendenze o problemi di compatibilità.
Ora, esploreremo le caratteristiche e l’utilizzo di virtualenv in modo più dettagliato attraverso una serie di punti:
- Creazione di un ambiente virtuale: Con virtualenv, è possibile creare un ambiente virtuale in pochi passaggi, specificando il percorso della directory di destinazione e, eventualmente, la versione di Python da utilizzare.
- Attivazione e disattivazione dell’ambiente virtuale: Dopo aver creato un ambiente virtuale, è possibile attivarlo utilizzando uno script specifico generato da virtualenv. Una volta attivato, l’ambiente virtuale modifica le variabili di ambiente per puntare alla sua directory, isolando il progetto dagli altri ambienti Python installati sul sistema. Al termine del lavoro sul progetto, è possibile disattivare l’ambiente virtuale per tornare all’ambiente Python predefinito.
- Gestione delle dipendenze del progetto: Utilizzando un ambiente virtuale, è possibile installare le dipendenze del progetto in modo isolato, senza influire sulle altre installazioni Python sul sistema. Ciò consente di mantenere l’ordine e la coerenza tra le dipendenze dei vari progetti e semplifica la gestione delle versioni delle librerie.
- Condivisione e distribuzione del codice: Gli ambienti virtuali possono essere facilmente condivisi e distribuiti insieme al codice sorgente del progetto. Questo consente agli altri membri del team di riprodurre facilmente l’ambiente di sviluppo e garantire la coerenza tra le diverse installazioni.
- Testing e sviluppo sicuro: L’utilizzo di ambienti virtuali consente agli sviluppatori di testare le proprie applicazioni in un ambiente controllato e isolato, riducendo al minimo il rischio di conflitti di dipendenze o problemi di compatibilità. Inoltre, consente di sperimentare con diverse configurazioni e versioni di Python senza influire sul sistema operativo o sulle altre applicazioni.
Il set di API REST a disposizione
Il dispositivo comunica con l’esterno tramite un set di 5 API REST, 1 di tipo GET e 4 di tipo POST:
- readusers (GET) restituisce il contenuto della tabella users contenente le autorizzazioni per ciascun utente;
- insertuser (POST) aggiunge alla tabella users un utente con il suo UUID, nome e permessi di accesso tramite un Json del tipo:
{
"uuidUser": "73051e99f1",
"name" : "Mike",
"access" : "d"
}
- moduser (POST) modifica nella tabella users un utente esistente con il suo UUID, nome e permessi di accesso tramite un Json del tipo:
{
"uuidUser": "73051e99f1",
"name" : "Mike",
"access" : "g"
}
- deluser (POST) elimina un utente tramite il suo UUID con un Json del tipo:
{
"uuidUser": "1105cd0b60"
}
- getdata (POST) restituisce l’elenco degli accessi registrati nel database corrispondenti ad un dato giorno identificato tramite un Json del tipo:
{
"year": "2024",
"month" : "3",
"day" : "17"
}
Il set di comandi Telegram a disposizione
Il bot Telegram riceve una notifica ogni volta che viene tentato un accesso (cioè ogni volta che un tag RFID viene rilevato dal sensore) che indica l’UUID e il privilegio di accesso ad esso associato. Ma è anche in grado di modificare i permessi contenuti nel database tramite i seguenti comandi:
- /adduser uuid name permission aggiunge un utente con i suoi privilegi (g o d) (esempio: /adduser a305cd0b60 Paul d)
- /moduser uuid name permission modifica i dati di un utente già esistente (esempio: /moduser a305cd0b60 Ringo g)
- /del uuid rimuove un utente identificato dal proprio uuid (esempio: /del a305cd0b60)
- /readuser uuid restituisce i dati dell’utente specificato (esempio /readuser a305cd0b60)
Di che componenti abbiamo bisogno per il controllo accessi RFID?
La lista dei componenti non è particolarmente lunga:
- una breadboard per connettere la Raspberry PI agli altri componenti
- alcuni fili DuPont (maschio – maschio, maschio – femmina, femmina – femmina)
- un buzzer attivo di tipo KY-012
- un resistore da 100Ω
- un resistore da 82Ω
- un LED verde
- un LED rosso
- un kit RFID come quello mostrato al paragrafo precedente
- un modulo con doppio/singolo relè optoisolato
- una (micro) SD card da non più di 32GB formattata in FAT32
- un eventuale dongle WiFi USB per la Raspberry
- e, ovviamente, una Raspberry !
Le SD card che ho usato per gli esperimenti sono una da 8GB e l’altra da 16GB.
Il progetto è stato testato con successo su una Raspberry Pi 1 Model B e su una Raspberry PI 3 Model B ma non è escluso che funzioni anche su altri modelli di Raspberry.
Realizzazione del progetto
Lo schema elettrico
Prima di realizzare il circuito vero e proprio diamo un’occhiata ai pinout delle due Raspberry utilizzate:
Come puoi osservare i due pinout coincidono per le prime 13 file.
Di seguito vediamo il pinout del modulo RFID:
Il terminale IRQ non verrà utilizzato.
Ed ecco il pinout del buzzer:
Useremo i terminali Ground – GND e Output signal -S.
Il modulo doppio relè
- Alimentazione:
- Accetta un’ampia gamma di tensioni di alimentazione, di solito compresa tra 5V e 12V.
- Il connettore di alimentazione è progettato per essere facilmente collegato a una sorgente di alimentazione esterna, come una batteria o un alimentatore.
- Relè:
- Due relè a bordo, ciascuno con i propri contatti elettrici: comune (COM), normale aperto (NO) e normale chiuso (NC).
- I contatti del relè sono progettati per gestire carichi di potenza. Tuttavia, le specifiche esatte dipendono dal modello specifico del modulo relè.
- Ingressi di Controllo:
- Due ingressi di controllo (IN1 e IN2) che possono essere collegati a pin digitali di una scheda di sviluppo tipo Arduino.
- Attivare uno di questi ingressi con un segnale logico alto (o basso, a seconda dei casi) attiverà il relè corrispondente.
- Indicatori LED:
- Indicatori LED incorporati per ogni relè che indicano lo stato di attivazione (spesso con colori come rosso per attivato e spento per disattivato).
- Compatibilità Arduino:
- Progettato per essere facilmente integrato con piattaforme di sviluppo come Arduino, rendendo il controllo dei relè un’operazione semplice e accessibile.
- Carichi Pilotabili:
- In grado di pilotare una varietà di carichi elettrici come lampadine, motori, elettrovalvole, e altri dispositivi che richiedono controllo on/off.
- Le specifiche esatte del carico dipendono dal modello del relè, ma spesso possono gestire carichi con tensioni alternate fino a 250V e correnti fino a 10A.
Questi moduli relè sono ampiamente utilizzati in progetti di domotica, automazione elettronica e controlli remoti, fornendo un’interfaccia sicura e controllata per dispositivi di potenza.
Vediamo ora lo schema elettrico del progetto, realizzato come al solito con Fritzing, in due versioni. Uno per la Raspberry Pi 1 Model B e l’altro per la Raspberry Pi 3 Model B:
È possibile che qualche modulo abbia bisogno di qualche connettore e quindi si renda necessario fare qualche saldatura. Se sei nuovo a questo argomento ti consiglio di dare una lettura all’articolo Un altro tutorial su come saldare.
Il modulo RFID è collegato alla porta SPI e all’alimentazione a 3.3V della Raspberry secondo lo schema :
RFID | GPIO RASPBERRY |
3.3V | 3.3V |
RST | 25 |
GND | GND |
MISO | 9 |
MOSI | 10 |
CK | 11 |
SDA (che sarebbe il Chip Select) | 8 |
L’ingresso di controllo del modulo relè IN1 è collegato al GPIO 17 della Raspberry (anche se il modulo ha due relè, noi ne useremo solo uno) mentre i due pin 5V e GND della Raspberry vengono utilizzati per alimentare il modulo a doppio relè, collegando il pin 5V (lato Raspberry) al pin VCC (lato modulo a doppio relè) e i due pin GND (massa).
Noterai che sul modulo relè è presente un ponticello (disegnato in azzurro sul connettore sinistro) che collega i morsetti JD-VCC e VCC. Questo ponticello viene utilizzato per alimentare il modulo relè attraverso i terminali VCC e GND sul connettore destro. Senza questo ponticello, saremmo costretti ad alimentare il modulo con un alimentatore esterno.
Il buzzer è collegato al GPIO 22.
I LED sono collegati alla Raspberry al GPIO 18 tramite dei resistori per limitare la corrente che li attraversa ed evitare di bruciarli (e di bruciare le uscite digitali a cui sono collegati). Quello rosso sarà collegato al resistore da 100Ω, quello verde al resistore da 82Ω.
Il LED ha due terminali (chiamati anodo e catodo) e, come tutti i diodi, è un componente che ha una sua polarità: fa passare la corrente quando è polarizzato direttamente (cioè la tensione all’anodo è maggiore di quella al catodo) e blocca la corrente quando è polarizzato inversamente (cioè la tensione all’anodo è minore di quella al catodo). La tensione tra anodo e catodo, che indicheremo con Vd, varia a seconda del colore della luce emessa. In particolare abbiamo che:
- Vd = 1.8 V per il LED rosso
- Vd = 1.9 V per il LED giallo
- Vd = 2 V per il LED verde
- Vd = 2 V per il LED arancio
- Vd = 3 V per il LED blu
- Vd = 3 V per il LED bianco
Come facciamo ad identificare l’anodo e il catodo del LED? Lo facciamo osservando i suoi terminali. Il più lungo corrisponde all’anodo. Inoltre il corpo del LED presenta un appiattimento in un punto del bordo che indica che il terminale vicino è il catodo.
Quindi, se un LED non si accende è possibile che sia stato collegato al contrario. In questo caso, per farlo funzionare, è sufficiente invertirne i collegamenti.
Come si calcola la resistenza da collegare al LED?
Nota Bene: questo paragrafo tratta il calcolo della resistenza di limitazione in maniera teorica e richiede un minimo di conoscenza delle basi dell’Elettrotecnica. Pertanto non è fondamentale per la comprensione del resto del progetto e può essere saltato dal lettore non interessato a tali aspetti teorici.
Come abbiamo già detto, il resistore tra il generico GPIO e il LED serve a limitare la corrente che attraversa il LED. Ma come possiamo calcolare il suo valore di resistenza? Ci viene in soccorso la Legge di Ohm la quale dice che la differenza di potenziale ai capi di un resistore (cioè la tensione misurata agli estremi del resistore) è proporzionale alla corrente I che lo attraversa e la costante di proporzionalità è proprio il valore di resistenza del resistore R:
V2 - V1 = RI
Nota Bene: per amor di precisione bisogna puntualizzare che mentre il resistore è il componente fisico (l’oggetto vero e proprio), la resistenza è il suo valore. Quindi è improprio (anche se accade di frequente) chiamare il resistore col termine resistenza.
Possiamo vedere la Legge di Ohm su un semplice circuito costituito da un generatore di tensione (il cerchio a sinistra) e un resistore:
La tensione (o differenza di potenziale) V2 – V1 impressa dal generatore di tensione sul resistore è uguale al prodotto di R per I.
Vediamo ora uno schema leggermente più complesso dove sono presenti il solito generatore di tensione, il resistore e un LED rosso:
Nel nostro caso la Vg rappresenta la tensione presente all’uscita digitale della Raspberry quando è HIGH ed è pari quindi a 3.3V.
La Vd è la tensione ai capi del diodo (tra anodo e catodo) quando questo è polarizzato direttamente (cioè quando fa scorrere la corrente). Avendo scelto un LED rosso, sappiamo, dalla tabella precedente, che Vd = 1.8V.
Dobbiamo determinare il valore R del resistore. Abbiamo ancora una incognita: il valore della corrente I che deve scorrere nel circuito quando il pin è in stato HIGH.
Nota Bene: quando il pin digitale è nello stato LOW la sua tensione (cioè la Vg) è nulla, ne consegue che anche la corrente I nel circuito è nulla.
I LED in genere non sopportano correnti maggiori di 20mA, quindi imponiamo una corrente massima di 15mA per stare sul sicuro.
Per la Legge di Kirchhoff alle maglie (detta anche Legge di Kirchhoff delle tensioni) , abbiamo che:
Vg - Vr - Vd = 0
Da cui ricaviamo che:
Vr = Vg - Vd
Passando ai valori reali, abbiamo che:
Vr = 3.3V - 1.8V
Ne risulta che:
Vr = 1.5V
Ma, per la Legge di Ohm, abbiamo che:
Vr = RI
da cui:
R = Vr / I
Sostituendo i valori reali:
R = 1.5V / 0.015A
Ne deriva un valore di R pari a 100Ω.
Seguendo un ragionamento analogo per il LED verde, avremo che
R = ((3.3V - 2V) / 0.015A) = 1.3V / 0.015A = 86.67Ω
Il valore commerciale più vicino è 82Ω. Ricalcolando la corrente avremo che questa sarà pari a circa 15.8 mA. Siamo ben dentro i limiti di sicurezza.
Come creare un bot Telegram
Telegram è un’applicazione di messaggistica istantanea e VoIP che può essere installata sul tuo smartphone (Android e iPhone) o computer (PC, Mac e Linux). Telegram ti consente di creare bot con cui il nostro dispositivo può interagire.
Creiamo ora il nostro bot!
Se non hai già Telegram, installalo e poi cerca il bot botFather. Fai clic sull’elemento visualizzato. Apparirà la seguente schermata:
Digita il comando /start per leggere le istruzioni:
Ora digita il comando /newbot per creare il tuo bot. Dagli un nome e uno username:
Se il tuo bot è stato creato con successo, riceverai un messaggio con un link per accedere al bot e al token del bot.
Salva il token del bot perché ti servirà in seguito affinché la board possa interagire con il bot.
Ecco come appare la schermata in cui è scritto il token del bot:
Chiunque conosca il nome utente del tuo bot può interagire con esso. Per filtrare i messaggi in modo da ignorare quelli che non provengono dal tuo account Telegram, devi utilizzare il tuo ID utente Telegram. Pertanto, quando il tuo bot Telegram riceve un messaggio, la nostra Raspberry PI saprà se proviene da noi (e quindi lo elaborerà) o da altri (e quindi lo ignorerà). Ma…..come troviamo questo ID?
Nel tuo account Telegram, cerca IDBot e avvia una conversazione con quel bot:
Quindi digita il comando /getid e lui ti risponderà col tuo ID:
A questo punto abbiamo creato il nostro bot e abbiamo tutti gli elementi per interfacciarlo con il nostro dispositivo: lo username, il token e lo userid.
Preparazione della Raspberry
Per poter installare l’applicazione sulla Raspberry è necessario fare alcuni passi preliminari ed installare alcuni software.
Iniziamo subito con l’installazione del sistema operativo.
Il sistema operativo scelto è una distribuzione fatta apposta per funzionare su tutti i tipi di Raspberry, anche le più datate. I test sono stati fatti su una Raspberry Pi 1 Model B e su una Raspberry PI 3 Model B.
Se la Raspberry non ha connessione Wireless nativa puoi usare un dongle WiFi da inserire in una delle sue prese USB.
Scarichiamo e installiamo il sistema operativo su SD card
Per scaricare l’ultima versione, vai all’indirizzo https://www.raspberrypi.com/software/operating-systems/
e portati alla sezione Raspberry Pi OS (Legacy). Scaricherai una versione che non ha ambiente grafico in modo che sia la più leggera possibile:
Il file scaricato sarà compresso in formato xz. Per decomprimerlo su Linux dovrai prima installare il tool:
sudo dnf install xz su CentOS/RHEL/Fedora Linux.
sudo apt install xz-utils su Ubuntu/Debian
e poi dare la riga di comando:
xz -d -v filename.xz
dove filename.xz è il nome del file che hai appena scaricato contenente il sistema operativo.
Su Windows sarà sufficiente utilizzare uno fra questi tool: 7-Zip, winRAR, WinZip.
Il risultato sarà un file con estensione img che è l’immagine da flashare sulla SD card della Raspberry.
Per flashare l’immagine sulla SD card userai il tool Balena Etcher che funziona sia su Linux che su Windows che su MACOS.
Il suo utilizzo è molto semplice: è sufficiente selezionare l’immagine da flashare, la SD card di destinazione e premere il pulsante Flash.
Ecco come appare la sua interfaccia:
A sinistra viene impostata l’immagine da flashare, al centro la SD card da flashare, a destra il pulsante per iniziare l’operazione di flashing.
Alla fine della operazione la SD card conterrà due partizioni: boot e rootfs. Nel gestore dei dispositivi su Linux appare un menu del genere:
Anche Windows mostrerà un menu del genere: dal tuo file explorer, alla voce Questo computer vedrai le 2 partizioni.
Ora, con un editor di testo, crea sul tuo computer un file che chiamerai wpa_supplicant.conf e che editerai in questo modo:
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=«your_ISO-3166-1_two-letter_country_code»
network={
ssid="«your_SSID»"
psk="«your_PSK»"
key_mgmt=WPA-PSK
}
Dovrai sostituire le seguenti voci:
- «your_ISO-3166-1_two-letter_country_code» con l’identificativo del tuo paese (per esempio per l’Italia è IT)
- «your_SSID» con il nome SSID della tua rete WiFi
- «your_PSK» con la password della rete WiFi
Poi crea un file vuoto che chiamerai ssh (senza nessuna estensione).
Le nuove distribuzioni non hanno il classico utente pi con password raspberry quindi, per poter entrare in SSH, dobbiamo provvedere in altro modo.
Con una Raspberry funzionante dobbiamo creare un file di nome userconf che conterrà l’utente che vogliamo creare con una versione criptata della password che gli vogliamo attribuire. Il formato sara quindi username:password-hash.
Supponiamo di voler tenere l’utente pi, dobbiamo creare la password-hash. Supponiamo di voler creare l’hash della password raspberry, sempre nella Raspberry dove abbiamo creato il file userconf. Dobbiamo dare il comando, da shell, seguente:
echo "raspberry" | openssl passwd -6 -stdin
Questo comando restituirà l’hash della password raspberry. Per esempio potrebbe essere una stringa così:
$6$ROOQWZkD7gkLyZRg$GsKVikua2e1Eiz3UNlxy1jsUFec4j9wF.CQt12mta/6ODxYJEB6xuAZzVerM3FU2XQ27.1tp9qJsqqXtXalLY.
Questo è l’hash della password raspberry che ho calcolato sulla mia Raspberry.
Il nostro file userconf quindi conterrà la seguente stringa:
pi:$6$ROOQWZkD7gkLyZRg$GsKVikua2e1Eiz3UNlxy1jsUFec4j9wF.CQt12mta/6ODxYJEB6xuAZzVerM3FU2XQ27.1tp9qJsqqXtXalLY.
NOTA BENE: è necessario calcolare l’hash con una Raspberry perché l’hash calcolato col computer utilizza un altro algoritmo che non consentirebbe alla Raspberry che stiamo preparando di riconoscere la password.
Alternativamente puoi scaricare dal link qua sotto il file userconf che ho creato io per avere utente pi con password raspberry.
User configuration for Raspberry
Ora apri la partizione boot sulla SD card e copia dentro i tre files wpa_supplicant.conf, ssh e userconf. Rimuovi in sicurezza la SD card dal computer e inseriscila nella Raspberry.
Accendi la Raspberry, aspetta qualche minuto. Per poterti loggare in ssh alla Raspberry, dovrai scoprire quale sia il suo IP (quello che il router le ha assegnato tramite DHCP).
Per fare questo è sufficiente dare il comando da una shell del pc:
ping raspberrypi.local
valido sia su Linux che su Windows (previa installazione di Putty su Windows).
Nel mio PC la Raspberry risponde così:
Ciò mi fa capire che l’IP assegnato è 192.168.43.27. Ovviamente l’IP assegnato alla tua Raspberry sarà diverso.
Alternativamente puoi usare il tool Angry IP Scanner oppure puoi accedere alle impostazioni del tuo router per vedere i dispositivi collegati via WiFi e scoprire che IP ha la Raspberry.
Per poter loggarti sulla Raspberry in ssh dai il comando da shell:
con password raspberry. Su Windows è necessario Putty. Ovviamente dovrai usare l’IP che ti è stato assegnato.
Una volta dentro la Raspberry dai i seguenti comandi per fare l’update del software:
sudo apt update
sudo apt upgrade
La password è sempre raspberry.
Configuriamo la timezone
Per configurare la timezone dai il comando:
sudo raspi-config
alla shell della Raspberry. Supponiamo di voler impostare il fuso orario di Roma.
Apparirà una schermata così:
Seleziona l’opzione sulla localizzazione e dai Ok:
Seleziona poi l’opzione sulla timezone e dai Ok:
Seleziona ora l’area geografica e dai Ok:
Infine seleziona la città e dai Ok:
Ecco fatto!
Riavvia la Raspberry dando il comando:
sudo reboot
e, dopo pochi minuti, rientra in ssh come hai fatto prima.
Dai il comando
date
La Raspberry dovrebbe ora mostrare data e ora corrette.
Impostiamo l’IP statico
Per fare in modo che la Raspberry abbia sempre lo stesso indirizzo IP, dobbiamo impostarlo in modo che sia statico. Nei miei test l’ho impostato a 192.168.1.190. Se non facessimo così, il router le assegnerebbe un IP diverso ad ogni riavvio il che ci costringerebbe ogni volta a cambiare l’indirizzo IP dell’API REST.
Procederemo in due passi:
- imposteremo l’IP fisso nella Raspberry
- imposteremo il router in modo che riservi quell’indirizzo alla nostra Raspberry
Per il primo punto, dai il comando:
nano /etc/dhcpcd.conf
per aprire il file dhcpcd.conf ed editarlo.
Alla fine del file dovrai aggiungere un blocco del genere:
interface [INTERFACE]
static_routers=[ROUTER IP]
static domain_name_servers=[DNS IP]
static ip_address=[STATIC IP ADDRESS YOU WANT]/24
dove:
- [INTERFACE] è il nome dell’interfaccia WiFi (nel nostro caso sarà wlan0)
- [ROUTER IP] è l’indirizzo del nostro router (in genere è qualcosa del tipo 192.168.0.1 oppure 192.168.1.1). Lo puoi trovare entrando nell’interfaccia di amministrazione del tuo modem/router
- [DNS IP] è l’indirizzo del server DNS, che in genere coincide con il parametro [ROUTER IP] del modem/router
- [STATIC IP ADDRESS YOU WANT] è l’indirizzo IP che vogliamo assegnare come IP fisso alla Raspberry
Quindi, supposto che [ROUTER IP] = [DNS IP] = 192.168.1.1 e che [STATIC IP ADDRESS YOU WANT] = 192.168.1.190, il blocco avrà un aspetto del genere:
interface wlan0
static_routers=192.168.1.1
static domain_name_servers=192.168.1.1
static ip_address=192.168.1.190/24
Riavvia la Raspberry sempre col comando
sudo reboot
e poi accedi nuovamente in ssh, questa volta con IP 192.168.1.190.
Come secondo passo imposteremo il router in modo che riservi l’indirizzo 192.168.1.190 alla nostra Raspberry. Ogni modem/router è diverso dagli altri ma più o meno si somigliano. Mostrerò qui come appare il mio.
Per entrare digito l’indirizzo 192.168.1.1 (perche il mio modem ha questo IP) sul browser e, dopo aver dato la password di amministratore, arrivo alla schermata principale. Da qui devo cercare la schermata relativa al controllo degli accessi.
Ci sarà un pulsante di aggiunta di un IP statico: aggiungi l’IP scelto abbinato al MAC address della scheda WiFi della Raspberry. Ti consiglio comunque di consultare il manuale di istruzioni del tuo modem/router per questa operazione.
Verifica ora che la Raspberry si connetta alla rete dando il comando:
ping www.google.com
Se ottieni la risposta al ping la rete è connessa. Se ottieni un messaggio del tipo “Network is unreachable” dai il comando
sudo route add default gw [ROUTER IP]
dove [ROUTER IP] è il gateway che nel nostro caso è l’IP del router, cioè 192.168.1.1
Installiamo alcuni indispensabili tool e l’ambiente virtuale
Procediamo ora installando alcuni tool indispensabili.
Innanzitutto installerai il programma di gestione del database sqlite digitando il comando:
sudo apt install sqlite3
Poi dobbiamo installare il comando pip3 che ci consente di installare ulteriori pacchetti e librerie per Python.
Sulla shell di Raspberry dai il comando:
sudo apt install python3-pip
che installerà pip3.
A questo punto devi installare il comando che crea l’ambiente virtuale dando il seguente comando:
sudo pip3 install virtualenv
Virtualenv , di cui abbiamo discusso in un apposito paragrafo, è un ambiente virtuale che rimane isolato dal resto del sistema operativo. Una volta creato, ci consente di installare al suo interno pacchetti e librerie della versione che ci serve senza che queste entrino in conflitto con quelle di sistema. In questo modo possiamo creare un ambiente in cui ogni progetto può avere le sue librerie e i suoi requisiti (con versioni differenti) senza rischiare che vadano in conflitto fra loro e con quelle di sistema.
Ora, nella home dell’utente pi, crea la cartella testRFID col comando
mkdir testRFID
ed entra nella cartella appena creata col comando
cd testRFID
Ora crea l’ambiente virtuale col comando
virtualenv env
e attivalo col comando
source env/bin/activate
NOTA: quando vuoi disattivare un ambiente virtuale ti è sufficiente dare il comando
deactivate
A questo punto devi installare alcune librerie dando i seguenti comandi:
pip install pyTelegramBotAPI
pip install pytz
pip install RPi.GPIO
python3 -m pip install spidev
python3 -m pip install mfrc522
pip install flask
Ora devi abilitare il bus SPI perché possa interagire col lettore RFID.
Dai il comando
sudo raspi-config
Apparirà il menu principale:
Seleziona il punto 3:
Vai al punto 4 per attivare la SPI:
Dai la conferma premendo su Yes:
Ci apparirà la schermata di conferma:
Usciamo quindi dal menu di configurazione:
Scarica ora, dal link qui sotto, gli script python e il file di database che dovrai poi caricare sulla Raspberry.
RFID Raspberry PI gate access control system
Una volta che hai scompattato la cartella, trasferisci i files appena scompattati verso la cartella /home/pi/testRFID della Raspberry.
Su Linux puoi aprire una shell sulla cartella appena scompattata e dare il comando:
rsync -avzP * [email protected]:/home/pi/testRFID
Su Windows puoi utilizzare FileZilla come indicato in questo link: https://howtoraspberrypi.com/transfer-files-raspberry-ssh/
Una volta fatto il trasferimento dovresti trovare nella cartella testRFID i files app.py, config.py , db_test_rfid_1.db, teleg.py, test.py oltre alla cartella env che fa parte di Virtualenv.
In breve ecco cosa fanno questi script:
- db_test_rfid_1.db è il file che contiene il database. Il database contiene due tabelle: users contiene gli utenti col loro uuid, il nome e i privilegi di accesso, accessusers contiene registrati tutti i tentativi di accesso con l’uuid dell’utente seguita da data, ora e privilegi di accesso
- test.py è il file che controlla gli accessi, aziona il relè, i LED e il buzzer e manda la notifica al bot Telegram sul tentativo di accesso. Inoltre registra il tentativo di accesso sulla tabella accessusers del database
- app.py è il file che contiene il server che utilizza Flask per la gestione delle API REST
- teleg.py è il file che gestisce il set di comandi provenienti dal bot Telegram
- config.py è il file che contiene i parametri di configurazione di Telegram e la timezone. Il file che troverai nel pacchetto contiene un TOKEN e un CHAT_ID fittizi. Nel tuo file dovrai inserire i valori determinati nel paragrafo “Come creare un bot Telegram”. Inoltre dovrai definire i parametri TIMEZONE e TIMEZONE_COMMON_NAME relativi alla tua regione/città.
NOTA: il file db_test_rfid_1.db contiene, a titolo di esempio iniziale, nella tabella users due utenti fittizi e nella tabella accessusers alcuni accessi fittizi. Potrai manipolare questi dati con le funzionalità del bot Telegram e/o con le API REST.
Utilizzo
A questo punto apri altre 3 shell (oltre quella già in uso) dove ti loggherai in ssh alla Raspberry con il comando di prima:
e password raspberry.
Per ciascuna nuova shell aperta portati nella cartella di lavoro col comando
cd testRFID
e per ciascuna attiva l’ambiente virtuale:
source env/bin/activate
Nella prima shell, che avevi già aperta quando hai installato le librerie e che ha l’ambiente virtuale già attivo, lancia lo script test.py col comando:
python test.py
nella seconda shell lancia lo script teleg.py:
python teleg.py
e nella terza shell lancia lo script app.py:
python app.py
Se tutto va bene il sistema è completamente funzionante nel controllo degli accessi col primo script, nel controllo del bot Telegram col secondo script e nel controllo del server delle API REST col terzo script.
La quarta shell ci serve per controllare i dati nel database.
Dai il comando:
sqlite3
e si aprirà la shell dei comandi di sqlite. Dando il comando:
.open db_test_rfid_1.db
ti collegherai al database (che è proprio il file db_test_rfid_1.db).
Se per esempio dai il comando
.tables
verranno elencate le tabelle presenti, cioè users e accessusers.
Se dai il comando:
select * from users;
potrai vedere gli utenti col loro codice, il nome e i privilegi di accesso.
Se dai il comando:
select * from accessusers;
potrai vedere gli accessi degli utenti col loro codice, la data, l’ora e i privilegi di accesso.
Per ulteriori approfondimenti sui comandi di Sqlite puoi leggere la guida ufficiale al link https://sqlite.org/cli.html
Test del funzionamento
Prima di tutto è doverosa una precisazione. Nei paragrafi che seguono possiamo osservare il funzionamento del sistema col bot Telegram nel primo e il funzionamento con le API REST nel secondo ma bisogna sottolineare che questa “divisione” delle funzionalità è stata fatta per semplicità. In realtà le funzionalità coesistono e funzionano contemporaneamente (a patto che tutti gli script siano in esecuzione).
Testiamo gli accessi e la gestione utenti con il bot Telegram
Nel video seguente possiamo osservare il funzionamento del sistema col controllo degli accessi, la loro registrazione sul database, le notifiche sul bot Telegram e la gestione degli utenti tramite il bot. Tale gestione è abbastanza semplice e ricalca gli esempi del precedente paragrafo “Il set di comandi Telegram a disposizione”.
Testiamo gli accessi e la gestione utenti con le API REST
Nel video seguente possiamo osservare il funzionamento del sistema col controllo degli accessi, la loro registrazione sul database, la gestione degli utenti tramite l’uso delle API REST con il programma Postman. Fai riferimento al paragrafo precedente “Il set di API REST a disposizione” per vederne l’elenco. È da sottolineare come la struttura tipica del comando da dare su Postman è del tipo:
(GET/POST) IP_RASPBERRY:5000/commandname
Abbiamo inizialmente il tipo di API (GET o POST) selezionata tramite il menù a tendina alla sinistra della barra dell’URL. Segue poi l’IP_RASPBERRY che nel nostro caso abbiamo fissato in 192.168.1.190. L’IP è seguito dalla porta 5000 su cui rimane in ascolto il server Flask. Conclude il vero e proprio comando commandname (per esempio readusers oppure deluser e così via).
In particolare, per quanto riguarda le API di tipo POST, esse prevedono di fornire come dato di ingresso dell’API un documento Json. Per fornire questo Json, dovrai selezionare la voce Body che sta sotto la barra dell’URL. Poi seleziona la voce raw (sotto Body) e poi, sul menù a tendina sulla destra, seleziona la voce JSON al posto di Text. Puoi vedere un esempio nella immagine sottostante:
Diamo un’occhiata agli script
Esaminiamo ora i tre script che costituiscono il nostro sistema.
Lo script test.py
Come già anticipato, è lo script che controlla gli accessi, aziona il relè, i LED e il buzzer e manda la notifica al bot Telegram sul tentativo di accesso. Inoltre registra il tentativo di accesso sulla tabella accessusers del database.
Inizialmente vengono importate le librerie necessarie:
import telebot
import config
import datetime
import pytz
import json
import traceback
import sqlite3
import RPi.GPIO as GPIO
from mfrc522 import SimpleMFRC522
import time
import datetime
Poi viene ricavata la timezone dal file config.py:
P_TIMEZONE = pytz.timezone(config.TIMEZONE)
TIMEZONE_COMMON_NAME = config.TIMEZONE_COMMON_NAME
Viene istanziato il bot Telegram e notificato all’utente (su Telegram) che lo script è in funzione:
bot = telebot.TeleBot(config.TOKEN)
bot.send_message(config.CHAT_ID, 'Hi! I\'m online and ready!\nUse the command /help to see the available commands.')
Vengono poi definiti i GPIO che comandano il relè, i LED e il buzzer come uscite. Viene inoltre istanziato il lettore (reader) RFID:
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(17,GPIO.OUT) # RELE
GPIO.setup(18,GPIO.OUT) # LED
GPIO.setup(22,GPIO.OUT) # BUZZER
reader = SimpleMFRC522()
Segue la definizione della funzione sanitize che fa in modo che la stringa in ingresso venga trasformata in una stringa a due caratteri, nel caso fosse inizialmente composta da un solo carattere. Questo ci serve per una questione puramente estetica per trasformare ore, minuti e secondi da oggetti del tipo “1”, “8”, “3” a oggetti del tipo “01”, “08”, “03”:
def sanitize(value):
if len(value) == 1:
return "0" + value
else:
return value
Viene poi inizializzato il dizionario vuoto:
diz = {}
Esso sarà aggiornato costantemente con i dati presenti nella tabella users del database e verrà consultato dal programma ogni volta che deve decidere se un certo utente può accedere o no.
Inizia ora un loop infinito che controlla se un badge è stato avvicinato al lettore RFID, fa emettere il suono in caso affermativo, aggiorna il dizionario per decidere se l’utente è riconosciuto o meno e , nel caso lo fosse, se può entrare (attivando o meno il relè) e scrive nella tabella accessusers il tentativo di accesso con UUID, data e ora e un flag (“g” per granted, “d” per denied e “n” per not recognized):
while True:
# GPIO.cleanup()
time.sleep(1)
try:
status,TagType = reader.read_no_block()
# print(status)
if status == None:
#print ("No Card Found")
pass
elif status != None:
GPIO.output(22,GPIO.HIGH) # BUZZER ON
time.sleep(0.1)
GPIO.output(22,GPIO.LOW) # BUZZER OFF
dbconnect = sqlite3.connect("db_test_rfid_1.db");
dbconnect.row_factory = sqlite3.Row;
cursor = dbconnect.cursor();
cursor.execute('SELECT * FROM users');
for row in cursor:
diz[row['uuidUser']] = row['access']
print (diz)
print ("\nCard Found!")
id,text = reader.read()
print(id)
idHex = hex(id)[2:]
print(idHex)
now = datetime.datetime.now()
year_s = str(now.year)
month_s = str(now.month)
day_s = str(now.day)
hour_s = str(now.hour)
minute_s = str(now.minute)
second_s = str(now.second)
print("Current year:", year_s)
print("Current month:", month_s)
print("Current day:", day_s)
print("Current hour:", hour_s)
print("Current minutes:",minute_s)
print("Current seconds:", second_s)
qry = "select * from accessusers"
cursor.execute(qry)
lista = []
for row in cursor:
boh = int(row['id'])
lista.append(boh)
if lista == []:
num = 1
else:
num = max(lista)
num = num + 1
if(idHex not in diz):
print("code not recognized: access denied!")
bot.send_message(config.CHAT_ID, 'User ' + idHex + " not recognized: access denied at " + sanitize(day_s) + "/"+ sanitize(month_s) + "/" + year_s + " " + sanitize(hour_s) + ":" + sanitize(minute_s) + ":" + sanitize(second_s))
cursor.execute('''insert into accessusers values (?, ?, ?, ?, ?, ?, ?, ?, ?)''', (num, idHex, day_s, month_s, year_s, hour_s, minute_s, second_s, "n"));
dbconnect.commit();
GPIO.output(18,GPIO.LOW) # LED OFF
elif(diz[idHex] == "g"):
print("access granted")
bot.send_message(config.CHAT_ID, 'User ' + idHex + " recognized: access granted at " + sanitize(day_s) + "/"+ sanitize(month_s) + "/" + year_s + " " + sanitize(hour_s) + ":" + sanitize(minute_s) + ":" + sanitize(second_s))
cursor.execute('''insert into accessusers values (?, ?, ?, ?, ?, ?, ?, ?, ?)''', (num, idHex, day_s, month_s, year_s, hour_s, minute_s, second_s, "g"));
dbconnect.commit();
GPIO.output(18,GPIO.HIGH) # LED ON
GPIO.output(17,GPIO.HIGH) # RELE ON
time.sleep(5)
GPIO.output(18,GPIO.LOW) # LED OFF
GPIO.output(17,GPIO.LOW) # RELE OFF
elif(diz[idHex] == "d"):
print("access denied")
bot.send_message(config.CHAT_ID, 'User ' + idHex + " recognized: access denied at " + sanitize(day_s) + "/"+ sanitize(month_s) + "/" + year_s + " " + sanitize(hour_s) + ":" + sanitize(minute_s) + ":" + sanitize(second_s))
cursor.execute('''insert into accessusers values (?, ?, ?, ?, ?, ?, ?, ?, ?)''', (num, idHex, day_s, month_s, year_s, hour_s, minute_s, second_s, "d"));
dbconnect.commit();
GPIO.output(18,GPIO.LOW) # LED OFF
else:
print("code not recognized: access denied")
diz = {}
except:
print("errore")
Lo script teleg.py
Come già anticipato, è lo script che gestisce il set di comandi provenienti dal bot Telegram.
All’inizio vengono importate le librerie necessarie, caricata la timezone dal file config.py e istanziato il bot Telegram:
import telebot
import config
import datetime
import pytz
import json
import traceback
import sqlite3
P_TIMEZONE = pytz.timezone(config.TIMEZONE)
TIMEZONE_COMMON_NAME = config.TIMEZONE_COMMON_NAME
bot = telebot.TeleBot(config.TOKEN)
Seguono poi gli handler dei comandi che arrivano dal bot.
Il primo è help:
@bot.message_handler(commands=['help'])
def help_command(message):
bot.send_message(
message.chat.id,
'Greetings, here the list of all available commands:\n\n' +
'*/adduser uuid name permission* adds a user with its privileges (g or d) (example: /adduser a305cd0b60 Paul d)\n\n' +
'*/moduser uuid name permission* update a user with its privileges (g or d) if already in database (example: /moduser a305cd0b60 Mike d)\n\n' +
'*/del uuid* removes a user (example: /del a305cd0b60)\n\n' +
'*/readuser uuid* returns the privileges of the specified user (example /readuser a305cd0b60)\n', parse_mode= 'Markdown'
)
Esso dà una lista dei comandi disponibili e, per ciascuno, un esempio di utilizzo.
Poi troviamo l’handler del che si occupa di eliminare un utente dal database (cioè dalla tabella users) dando in ingresso il suo UUID:
@bot.message_handler(commands=['del'])
def del_command(message):
arguments = message.text.split(" ")
if len(arguments) != 2:
bot.send_message(
message.chat.id,
'Wrong number of arguments'
)
return
uuid = arguments[1]
dbconnect = sqlite3.connect("db_test_rfid_1.db")
dbconnect.row_factory = sqlite3.Row
cursor = dbconnect.cursor()
qry = "delete from users where uuidUser = " + "'" + uuid + "'"
print(qry)
cursor.execute(qry)
dbconnect.commit()
dbconnect.close()
bot.send_message(
message.chat.id,
'Deleted ' + uuid + ' user.'
)
Segue l’handler adduser che aggiunge un nuovo utente alla tabella users del database ma solo se non è già presente (i parametri in ingresso sono UUID, nome utente e permessi):
@bot.message_handler(commands=['adduser'])
def adduser_command(message):
arguments = message.text.split(" ")
if len(arguments) != 4:
bot.send_message(
message.chat.id,
'Wrong number of arguments'
)
return
uuid = arguments[1]
nameuser = arguments[2]
permission = arguments[3]
dbconnect = sqlite3.connect("db_test_rfid_1.db")
dbconnect.row_factory = sqlite3.Row
cursor = dbconnect.cursor()
qry = "select * from users where uuidUser = " + "'" + uuid + "'"
row = cursor.execute(qry)
res = row.fetchone()
if res is not None:
print("user in db")
bot.send_message(
message.chat.id,
'User ' + uuid + ' already in database. Nothing to do.'
)
return
print("user not in db")
qry = "select * from users"
cursor.execute(qry)
lista = []
for row in cursor:
boh = int(row['id'])
lista.append(boh)
if lista == []:
num = 1
else:
num = max(lista)
num = num + 1
cursor.execute('''insert into users values (?, ?, ?, ?)''', (num, uuid, nameuser, permission));
dbconnect.commit()
dbconnect.close()
bot.send_message(
message.chat.id,
'Added ' + uuid + ' user to database.'
)
Abbiamo poi l’handler moduser che consente di modificare un utente nella tabella users del database (ma solo se già presente). I parametri in ingresso sono UUID, nome utente e permessi:
@bot.message_handler(commands=['moduser'])
def moduser_command(message):
arguments = message.text.split(" ")
if len(arguments) != 4:
bot.send_message(
message.chat.id,
'Wrong number of arguments'
)
return
uuid = message.text.split(" ")[1]
nameuser = message.text.split(" ")[2]
permission = message.text.split(" ")[3]
dbconnect = sqlite3.connect("db_test_rfid_1.db")
dbconnect.row_factory = sqlite3.Row
cursor = dbconnect.cursor()
num = 0
qry = "select * from users where uuidUser = " + "'" + uuid + "'"
row = cursor.execute(qry)
res = row.fetchone()
if res is None:
print("user not in db")
return
else:
num = res[0]
print("user in db")
print(num)
qry = "delete from users where id = " + "'" + str(num) + "'"
cursor.execute(qry)
dbconnect.commit()
cursor.execute('''insert into users values (?, ?, ?, ?)''', (num, uuid, nameuser, permission));
dbconnect.commit()
dbconnect.close()
bot.send_message(
message.chat.id,
'Modified ' + uuid + ' user in database.'
)
Infine troviamo l’handler readuser che restituisce le informazioni sull’utente selezionato (sempre se presente nel database). Il parametro in ingresso è l’UUID:
@bot.message_handler(commands=['readuser'])
def readuser_command(message):
arguments = message.text.split(" ")
if len(arguments) != 2:
bot.send_message(
message.chat.id,
'Wrong number of arguments'
)
return
uuid = arguments[1]
msg = ""
dbconnect = sqlite3.connect("db_test_rfid_1.db")
dbconnect.row_factory = sqlite3.Row
cursor = dbconnect.cursor()
qry = "select * from users where uuidUser = " + "'" + uuid + "'"
row = cursor.execute(qry)
res = row.fetchone()
if res is None:
print("user not in db")
bot.send_message(
message.chat.id,
'User ' + uuid + ' not in database.'
)
return
else:
qry = "select * from users where uuidUser = " + "'" + uuid + "'"
row = cursor.execute(qry)
for row in cursor:
msg = row['uuidUser'] + " " + row['name'] + " " + row['access']
dbconnect.close()
bot.send_message(
message.chat.id,
msg
)
Lo script termina con la funzione:
bot.polling(none_stop=True)
che tiene il bot in un perenne stato di polling, cioè di interrogazione continua dei messaggi in arrivo per poi poterli gestire con i vari handler.
Lo script app.py
Come già anticipato, è lo script che contiene il server che utilizza Flask per la gestione delle API REST
Come sempre inizia con l’importazione delle librerie necessarie:
from flask import Flask
from flask import request
import json
import sqlite3
Segue poi la funzione sanitize che abbiamo già incontrato in precedenza.
Viene istanziata l’app Flask che gestirà le richieste provenienti dalle API REST:
app = Flask(__name__)
Seguono poi i vari handler delle API REST.
Il primo è “/” e restituisce il classico “Hello world”.
Segue poi l’handler getdata mappato sull’API /getdata di tipo POST che restituisce gli accessi avvenuti in un dato giorno (dato come Json contenente anno, mese e giorno in input):
@app.route('/getdata', methods=['POST'])
def getdata():
request_data = request.get_json()
year_j = request_data['year']
month_j = request_data['month']
day_j = request_data['day']
print(year_j + "/" + month_j + "/" + day_j)
dbconnect = sqlite3.connect("db_test_rfid_1.db")
dbconnect.row_factory = sqlite3.Row
cursor = dbconnect.cursor()
qry = "SELECT * from accessusers where year = " + str(year_j) + " and month = " + str(month_j) + " and day = " + str(day_j)
cursor.execute(qry)
innerdata = {}
outerdata = {}
for row in cursor:
hms = sanitize(str(row['hour'])) + ":" + sanitize(str(row['minutes'])) + ":" + sanitize(str(row['seconds']))
uuid = str(row['uuidUser'])
acc = str(row['access'])
innerdata[uuid] = acc
outerdata[hms] = innerdata
innerdata = {}
dbconnect.close()
json_data = json.dumps(outerdata)
return json_data
Abbiamo poi l’handler readusers mappato sull’API /readusers di tipo GET che restituisce la lista degli utenti presenti nella tabella users del database:
@app.route('/readusers', methods=['GET'])
def readusers():
dbconnect = sqlite3.connect("db_test_rfid_1.db")
dbconnect.row_factory = sqlite3.Row
cursor = dbconnect.cursor()
qry = "SELECT * from users"
cursor.execute(qry)
innerdata = {}
outerdata = {}
for row in cursor:
idrow = row['id']
lista = [row['uuidUser'], row['name'], row['access']]
innerdata = lista
lista = []
outerdata[idrow] = innerdata
innerdata = {}
dbconnect.close()
json_data = json.dumps(outerdata)
return json_data
Incontriamo poi l’handler insertuser mappato sull’API /insertuser di tipo POST che inserisce un nuovo utente nella tabella users del database (se non già presente) con UUID, nome e privilegi di accesso:
@app.route('/insertuser', methods=['POST'])
def insertuser():
request_data = request.get_json()
uuidUser_j = request_data['uuidUser']
name_j = request_data['name']
access_j = request_data['access']
dbconnect = sqlite3.connect("db_test_rfid_1.db")
dbconnect.row_factory = sqlite3.Row
cursor = dbconnect.cursor()
qry = "select * from users where uuidUser = " + "'" + str(uuidUser_j) + "'"
row = cursor.execute(qry)
res = row.fetchone()
if res is not None:
print("user in db")
return 'user in db'
print("user not in db")
qry = "select * from users"
cursor.execute(qry)
lista = []
for row in cursor:
boh = int(row['id'])
lista.append(boh)
if lista == []:
num = 1
else:
num = max(lista)
num = num + 1
cursor.execute('''insert into users values (?, ?, ?, ?)''', (num, uuidUser_j, name_j, access_j));
dbconnect.commit()
dbconnect.close()
return 'Done!'
Arriviamo all’handler deluser mappato sull’API /deluser di tipo POST che elimina un utente (identificato da un certo UUID) dalla tabella users del database (se presente nel database stesso):
@app.route('/deluser', methods=['POST'])
def deluser():
request_data = request.get_json()
uuidUser_j = request_data['uuidUser']
dbconnect = sqlite3.connect("db_test_rfid_1.db")
dbconnect.row_factory = sqlite3.Row
cursor = dbconnect.cursor()
qry = "delete from users where uuidUser = " + "'" + str(uuidUser_j) + "'"
print(qry)
cursor.execute(qry)
dbconnect.commit()
dbconnect.close()
return 'Done!'
Terminiamo con l’handler moduser mappato sull’API /moduser di tipo POST che modifica un utente già presente nella tabella users del database (con i dati UUID, nome e privilegi di accesso forniti tramite Json):
@app.route('/moduser', methods=['POST'])
def moduser():
request_data = request.get_json()
uuidUser_j = request_data['uuidUser']
name_j = request_data['name']
access_j = request_data['access']
dbconnect = sqlite3.connect("db_test_rfid_1.db")
dbconnect.row_factory = sqlite3.Row
cursor = dbconnect.cursor()
num = 0
qry = "select * from users where uuidUser = " + "'" + str(uuidUser_j) + "'"
row = cursor.execute(qry)
res = row.fetchone()
if res is None:
print("user not in db")
return 'user not in db'
else:
num = res[0]
print("user in db")
print(num)
qry = "delete from users where id = " + "'" + str(num) + "'"
cursor.execute(qry)
dbconnect.commit()
cursor.execute('''insert into users values (?, ?, ?, ?)''', (num, uuidUser_j, name_j, access_j));
dbconnect.commit()
dbconnect.close()
return "Done!"
Lo script termina con la parte che lancia l’applicazione Flask e rimane in attesa di richieste da parte del client sull’IP della Raspberry (nel nostro caso 192.168.1.190) sulla porta 5000:
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
Osservazioni finali
Gli script sopra menzionati però vengono interrotti nella loro esecuzione se chiudiamo la shell di appartenenza. Per esempio, se chiudo la shell dove ho lanciato lo script test.py, questo verrà killato (ucciso, la sua esecuzione verrà terminata). Per fare in modo che l’esecuzione dei nostri script non venga interrotta alla chiusura della shell di appartenenza possiamo lanciarli in un modo leggermente modificato. In questo caso è sufficiente una shell sola (ovviamente posizionata dentro la cartella di lavoro e con l’ambiente virtuale attivo) e dare, in successione, i seguenti comandi:
nohup python test.py &
nohup python teleg.py &
nohup python app.py &
In questo modo continueranno a girare anche se chiudiamo le shell. Potremmo fermarli solo col comando kill applicato al PID di ciascuno script in esecuzione oppure spegnendo (o riavviando) la Raspberry.
Newsletter
Se vuoi essere aggiornato sui nuovi articoli, iscriviti alla newsletter. Prima dell’iscrizione alla newsletter leggi la pagina Privacy Policy (UE)
Se ti vuoi disiscrivere dalla newsletter clicca sul link che troverai nella mail della newsletter.