Giornata Tecnica da Piave Servizi, 11 aprile 2024 | ALBIERO Andrea
Progettazione e sviluppo di un software applicativo su un single board computer
1. UNIVERSITÀ DEGLI STUDI DI
TRIESTE
Dipartimento di Ingegneria e Architettura
Laurea Triennale in Ingegneria dell’Informazione
Progettazione e sviluppo di un software
applicativo su single-board computer
2 dicembre 2016
Laureando Relatore
Alessandro Mascherin Chiar.mo Prof. Sergio Carrato
Correlatore
Ing. Piergiorgio Menia
Anno Accademico 2016/2017
3. Introduzione
La seguente tesi descriverà il lavoro svolto presso la Elimos S.r.l per il pro-
getto e lo sviluppo di un software applicativo implementato su single-board
computer. Il progetto prevede l’installazione di numerose unità distribuite
in rete in grado di leggere un Tag RFID, richiedere ad un server centrale
connesso in rete TCP/IP un set di informazioni legate al Tag in oggetto, e
stampare su stampante termica i dati ricevuti dal centro di controllo. Que-
sto sistema deve essere piccolo, inscatolato per ambiente industriale, ed in
grado di essere acceso/spento senza controllo di un operatore. A fronte del-
l’analisi dei requisiti e dei target di costo richiesti da un’azienda committente
esterna, si è deciso di utilizzare come unità centrale di elaborazione per ogni
modulo un single-board computer (SBC) embedded Raspberry Pi 2 model
B. Il lavoro si è sviluppato in più fasi. Nella prima, ci si è dedicati alla
scelta dell’ambiente di lavoro, andando a definire la totalità dell’hardware
su cui si sarebbe andati a lavorare e gli strumenti software a disposizione.
Nella seconda fase si è andati a sviluppare un software applicativo in gra-
do di soddisfare le richieste del committente. Si è cercato di sviluppare un
programma il più modulare possibile, per far fronte a diversi casistiche e
per garantire longevità al progetto. In fase di sviluppo si è lavorato paral-
lelamente sul codice applicativo e sulla parte piu’ sistemistica propria del
Raspberry, installando driver e pacchetti, e creando le librerie software ne-
cessarie al funzionamento del programma, il tutto per ottenere un sistema
operativo configurato in maniera opportuna. Nella fase finale del lavoro si è
andati a integrare l’applicativo sull’hardware testando completamente tutta
la connettività e ricercando delle soluzioni per rendere il dispositivo in grado
di operare in maniera autonoma senza operatore. In questa fase sono anche
stati sviluppati dei tool per permettere un accesso remoto alla SBC e special-
mente le procedure di aggiornamento e configurazione remota direttamente
sul campo.
Nei capitoli seguenti andremo ad esporre le soluzioni sviluppate, descri-
vendo le problematiche riscontrate in fase di sviluppo e le scelte effettuate
per soddisfare i requisiti di progetto.
ii
4. INTRODUZIONE
Obiettivi della tesi
L’obiettivo della tesi è quello di descrivere le operazioni da svolgere per
utilizzare un single-board computer ARM-based in ambiente non presidiato,
a partire dall’impostazione iniziale dell’ambiente di sviluppo per giungere
all’implementazione di un programma di una certa complessità sul sistema
oggetto di analisi. Uno dei requisiti cardine sui cui si è sviluppato il progetto
è stato quello di ottenere un sistema di semplice utilizzo per l’utente finale,
privo di interfacce per l’operatore. Il sistema deve essere interfacciato ad
un lettore di tessere RFID, ad una stampante termica e deve comunicare
attraverso la rete TCP/IP. Si è quindi lavorato per rendere il dispositivo in
grado di lavorare in maniera autonoma, gestendo eventuali configurazioni e
aggiornamenti in maniera remota.
Organizzazione della tesi
Nel capitolo 1 verrà effettuata un’analisi dei requisiti richiesti dal committen-
te e dei motivi che hanno portato alla scelta del Raspberry Pi come ambiente
di lavoro per la realizzazione del progetto. Verranno inoltre discusse alcune
possibili scelte relative ai sistemi operativi utilizzabili. Le varie configurazio-
ni del sistema operativo scelte e le principali librerie software installate sono
descritte nel capitolo 2. Nel capitolo 3 viene esposto il software applicativo
sviluppato, sia nel suo funzionamento generale che negli specifici moduli svi-
luppati dal laureando. Il capitolo 4 è dedicato alla descrizione degli strumenti
utilizzati per garantire un funzionamento autonomo del Raspberry, fornendo
nel contempo degli strumenti per l’aggiornamento e la configurazione remota
all’utente. Infine il capitolo 5 è dedicato alle conclusioni sul lavoro svolto,
mentre in appendice sono inseriti la bibliografia e parte del codice sorgente
opportunamente commentato.
Strumenti utilizzati
L’hardware su cui si è andati a sviluppare il progetto è stato il Raspberry
Pi 2 model B, un computer single-board rilasciato sul mercato nel febbraio
2015 dalla RaspberryPi Foundation™. Il Raspberry Pi è un computer co-
siddetto single-board, in quanto costruito su un’unica scheda circuitale, con
processore ARM in grado di supportare una vasta gamma di sistemi operativi
GNU/Linux. Il software sviluppato è stato scritto in C/C++ 11 sfruttando
l’ambiente di sviluppo integrato NetBeans. Sono stati inoltre scritti diversi
script bash e per alcune opzioni di configurazione vengono utilizzati degli
script AWK open-source.
iii
5. Capitolo 1
Analisi Introduttiva
In questa sezione verranno descritte le specifiche richieste al progetto e le
scelte iniziali fatte per l’implementazione del dispositivo.
1.1 Descrizione problema
Le specifiche di progetto prevedevano la realizzazione di un sistema senza in-
terfaccia utente in grado di implementare diverse funzionalità che verranno
di seguito esposte. A livello globale il sistema finale si presenta essenzial-
mente come un timbratore che deve fornire a ogni operatore un ruolino di
servizio con le attività prevista per la giornata lavorativa. Il dispositivo fina-
le necessita di una connessione con un server centrale, installato in sede del
committente, che contiene i dati relativi agli operatori e alle attività previste.
Per quanto riguarda la connettività, il dispositivo finale deve quindi potersi
collegare alla rete Internet tramite protocollo TCP/IP, attraverso la tecno-
logia Ehternet. La comunicazione con il server centrale avviene attraverso
un web-service. Altro aspetto fondamentale dei requisiti è la gestione del
sistema di input/output. Per poter stampare il ruolino di servizio, il dispo-
sitivo deve poter comunicare con una stampante termica collegabile tramite
l’interfaccia USB, il cui modello è stato stabilito dal committente. Il sistema
deve gestire un processo di analisi dei pin in input ricevendo dei dati da un
lettore di tessere magnetiche, collegato ai GPIO del Raspberry. Questo letto-
re utilizza l’interfaccia Wiegand per la trasmissione dei dati e deve pertanto
essere opportunamente collegato al dispositivo su cui si andrà a sviluppare il
progetto. Tramite lo stesso lettore è necessario fornire all’utente un feedback
sonoro e visivo delle operazioni che vengono svolte; il feedback viene invia-
to grazie al led e al buzzer presenti sul lettore di tessere. Questo sistema
deve analizzare la sequenza di bit che riceve dal lettore di tessere e, in se-
guito ad un’opportuna elaborazione, inviare una richiesta al server centrale
con i dati elaborati. Per comunicare con il server è stato necessario quindi
implementare un client web-service sul dispositivo, per poter effettuare una
1
6. CAPITOLO 1. ANALISI INTRODUTTIVA
convalida dei dati elaborati dal badge e ricevere delle opportune risposte dal
server. L’utilizzo dei web service è lo standard de facto per la comunica-
zione attraverso il Web di diversi dispositivi e diverse applicazioni software.
Secondo la definizione fornita dal W3C, il World Wide Web Consortium, le
web service sono la tecnologia standard per poter mettere in comunicazione
diversi applicazioni software installate su una grande varietà di piattaforme
e framework. Nel caso in analisi lo sviluppo di una web service permette di
poter ottenere una facile comunicazione con il server centrale, senza doversi
preoccupare della compatibilità fra diverse piattaforme e sistemi operativi.
Sono previste diverse tipologie di risposta da parte del web server, e
il sistema deve adottare un opportuno comportamento per ogni possibile
risposta:
• Codice di errore in caso di tessera sconosciuta
• Codice di errore differente nel caso di tessera conosciuta, ma rispo-
sta di errore da parte del web server (data/ora non valida, mancata
timbratura, o altro)
• In caso di risposta positiva da parte del web server è necessaria un’e-
laborazione dei dati ricevuti e la preparazione di un modulo di stampa
da inviare alla stampante termica collegata al sistema.
Durante l’elaborazione dei dati per la stampa o in presenza di un codice
di errore, deve essere previsto uno specifico feedback per l’utente tramite il
led e il sistema sonoro implementato nel lettore di tessere. In assenza di erro-
ri, i dati ricevuti devono venire stampati in dei ruolini di servizio con diversi
formati; è necessario quindi sviluppare un programma in grado di distinguere
le varie casistiche e di creare dei layout di stampa adeguati, in modo tale da
fornire ad ogni diverso operatore il suo specifico ruolino di servizio. Il tutto,
dovendo operare senza interfaccia utente, deve inoltre disporre di un sistema
per l’aggiornamento e la configurazione in modo remoto, senza la necessità
di accedere fisicamente ai dispositivi finali. Per queste procedure è stato
deciso di utilizzare i protocolli FTP e SSH per accedere al sistema. L’ag-
giornamento e la configurazione devono avvenire stabilendo una connessione
ad un indirizzo IP specifico, che deve essere attivo per un certo intervallo
temporale dopo il quale il sistema si deve connettere a un indirizzo IP di
servizio. Un ulteriore requisito è la robustezza sia del software applicativo,
sia del dispositivo in caso di malfunzionamenti. In particolare, non essen-
doci interfaccia utente, bisogna prevenire errori di memoria in seguito allo
spegnimento della scheda o in caso di problemi legati all’alimentazione.
2
7. CAPITOLO 1. ANALISI INTRODUTTIVA
1.2 Scelta dell’ambiente di lavoro
La prima decisione è stata quella di non utilizzare dei semplici microcontrol-
lori, in quanto non in grado di rispettare le specifiche fornite in modo sem-
plice ed economico. Per poter soddisfare in modo efficace i requisiti imposti
dal committente è necessario avere a disposizione un dispositivo che deve
offrire le funzionalità di un personal computer, mantenendo nel contempo
delle dimensioni contenute. Si è scelto quindi di sfruttare una single-board
computer embedded, dispositivi molto compatti e dal costo contenuto, in
grado di fornire prestazioni comunque elevate. Sono dei computer completi
che vengono installati su una singola scheda elettronica. Questi single-board
computer hanno avuto un’ampia diffusione negli ultimi anni per utilizzi edu-
cazionali o come controllori embedded. Pur non potendo competere con la
potenza di calcolo di un personal computer di fascia alta, hanno come van-
taggio le dimensioni estremamente contenute, un basso consumo e un costo
notevolmente inferiore. L’hardware preso in considerazione consisteva nel
Raspberry Pi 2 model B e nell’Odroid-C1plus. Sono schede molto simili in
caratteristiche e prestazioni, e sono quasi equivalenti quando utilizzati per la
realizzazione di questo tipo di progetti. Entrambi i prodotti sono basati su
processori ARM, hanno dimensioni comparabili e sono dotati delle interfacce
I/O richieste. L’Odroid è superiore al Raspberry in termini di caratteristi-
che hardware, mentre è leggermente inferiore in termini di supporto tecnico
e di reperibilità dei dispositivi. Essendo il costo dei due prodotti essenzial-
mente identico e non essendo necessarie particolari prestazioni hardware per
l’implementazione delle richieste del committente, si è deciso di iniziare lo
sviluppo sul Raspberry Pi, data la facile reperibilità del prodotto e il maggior
supporto tecnico presente in rete.
1.3 Descrizione dell’hardware
Figura 1.1: Foto del chip BCM2836
3
8. CAPITOLO 1. ANALISI INTRODUTTIVA
Il Raspberry Pi 2 model B è basato su un processore quad-core ARM
Cortex-A7 da 900MHz costruito dalla Broadcom™. In particolare il chip uti-
lizzato è il BCM2836, che va a sostituire il precedente BCM2835, installato
sulle versioni precedenti del RaspberryPi.
Questo modello di Raspberry dispone inoltre di 1 GB di memoria RAM, e
di una GPU, prodotta anch’essa dalla Broadcom™, da 250 MHz con OpenGL
ES 2.0 OpenVG 1080p30 H.264. Per quanto riguarda la connettività la
scheda dispone di 4 porte USB 2.0, utilizzando il Microchip™ LAN9514, una
porta Ethernet, una porta HDMI e un jack audio da 3,5 mm che comprende
anche il video composito utilizzabile con un adattatore a 4 poli. Per la
memorizzazione dei dati è necessario l’utilizzo di una scheda SD, collegabile al
Raspberry tramite un apposito slot. Le periferiche di basso livello installate
sulla scheda consistono in un modulo GPIO da 40 pin, di un Serial Peripheral
Interface Bus (SPI), di pins per le comunicazioni I2C e I2S, e un Universal
asynchronous receiver/transmitter (UART). La scheda per il funzionamento
richiede 5V in DC, che possono essere forniti tramite una porta Micro USB
di tipo B.
La suddivisione dei pin dell’interfaccia GPIO è illustrata nell’immagine
1.2:
Figura 1.2: Schema GPIO
I pins 2 e 4 sono pin utilizzati per alimentare dispositivi collegati all’in-
terfaccia a una tensione di 5V e con una corrente che può arrivare a 1,5A. I
pin da 5V possono essere anche utilizzati per alimentare il Raspberry stesso,
prestando attenzioni ad eventuali picchi di tensione che potrebbero danneg-
giare i pins del bus GPIO. In modo analogo i pin 1 e 17 forniscono una
tensione di 3.3V ma con una limitazione alla corrente fornita. Esistono poi
dei pin deputati a specifici metodi di input/output. Ad esempio i pins 3, 5,
27 e 28 vengono utilizzati per il sistema di comunicazione I2C mentre i pin 8
e 10 vengono utilizzati per le comunicazioni UART (Universal Asynchronous
Receiver/Transmitter).
4
9. CAPITOLO 1. ANALISI INTRODUTTIVA
L’immagine 1.3 raffigura il mechanical drawing del modello di Raspberry
utilizzato. Il drawing che viene presentato è quello del Raspberry Pi Model
B+, ma risulta valido anche per il Raspberry Pi 2 Model B. Le misure sono
espresse in mm.
Figura 1.3: Mechanical Drawing
1.3.1 Interfaccia e protocollo Wiegand
Un’altra componente hardware fondamentale del sistema oggetto di analisi
è costituita dal lettore di tessere magnetiche. Questo lettore utilizza un in-
terfaccia Wiegand per la trasmissione dei dati al Raspberry. L’interfaccia
Wiegand è costituita da 3 linee che devono essere collegate al bus GPIO
del Raspberry. Una linea costituisce la massa e le altre due linee vengono
utilizzate per la trasmissione dei dati e di solito vengono chiamate DATA0 e
DATA1. In assenza di dati da trasmettere le due linee vengono mantenute
ad un livello di tensione “alto” che generalmente consiste in 5 V in DC. Nel
caso in cui viene trasmesso un bit di valore 0, la tensione della linea DATA0
viene abbassata per una specifica durata temporale. Viceversa, nel caso in
cui viene trasmesso un bit con valore 1 è la tensione della linea DATA1 ad
essere abbassata, mentre DATA0 rimarrà su un valore di tensione alto. Le
tempistiche di questi segnali sono tipicamente 50-100 µs per gli impulsi in-
tervallati da 2ms, ma non sono assolutamente stringenti e possono variare
anche in percentuali significative. Questi bit vengono inviati secondo il for-
5
10. CAPITOLO 1. ANALISI INTRODUTTIVA
mato stabilito dal protocollo Wiegand standard da 26 bit. Questo formato
consiste in uno stream di 26 bit che sono così suddivisi:
Figura 1.4: Protocollo Wiegand
Il bit di testa e di coda dello stream vengono utilizzati come controllori
di parità. In particolare il primo bit è settato in modo tale che il numero di
“1” nei primi 13 bit dello stream sia pari, mentre l’ultimo viene impostato
per far sì che il numero di “1” negli ultimi 13 bit dello stream sia dispari.
Il facility code contiene le informazioni relative al modello e al produttore
della scheda. L’user code, chiamato anche ID code, come viene suggerito dal
nome, codifica un valore identificativo della singola tessera. Per l’analisi di
questi bit è stato sviluppato un modulo software che si occupa di eseguire
il controllo di parità della sequenza trasmessa e di separare il facility code e
l’ID code.
1.4 Scelta del sistema operativo
Sui single-board computer ARM-based è possibile installare una gran varie-
tà di sistemi operativi, generalmente linux-based. Nel caso del Raspberry i
sistemi operativi supportati ufficialmente sono Rasbian e Noobs, maggior-
mente adatti per un utilizzo di tipo didattico o dimostrativo della scheda.
Esistono tuttavia alcuni sistemi operativi che non sono sviluppati dalla Ra-
spberryPi Foundation™ ma che sono comunque ottimizzati per il Raspberry,
quali Ubuntu Mate, Windows 10 IOT, Snappy Ubuntu e altri. Si è deciso
di installare una versione di Ubuntu Mate, in quanto più adatta alle speci-
fiche di progetto rispetto ai sistemi sopra menzionati. Un’altra alternativa
presa inizialmente in considerazione è stata quella di installare un sistema
Android; l’ipotesi è stata scartata durante la prima fase di analisi in quanto
il sistema non è ufficialmente supportato e risulta spesso instabile sul mo-
dello di hardware scelto. L’implementazione di Android sarebbe stata una
valida scelta utilizzando l’Odroid, che supporta nativamente questo sistema
operativo.
6
11. Capitolo 2
Impostazione iniziale
dispositivo
In questa sezione verrà descritto il lavoro svolto per l’impostazione generale
della scheda a livello software e le soluzioni adottate per rendere il sistema
robusto in seguito a eventi non previsti dalle specifiche, quali l’interruzione
improvvisa dell’alimentazione o la rimozione della scheda di memoria. Ver-
ranno introdotte e analizzate le librerie software installate, le configurazioni
di rete adottate e un’importante modifica al sistema operativo, ovvero la
creazione di differenti partizioni di sistema configurate in modo differente
per risolvere diverse situazioni impreviste che potrebbero insorgere durante
l’utilizzo del dispositivo.
2.1 Installazione librerie
Nel corso del processo di sviluppo del software applicativo è stato necessario
installare diverse librerie software disponibili in Internet. In ambiente linux
sono disponibili un ampio numero di librerie, la maggior parte delle quali
gratis e open-source. L’installazione, l’aggiornamento e la rimozione di li-
brerie e pacchetti software è gestita di default da APT (Advanced Packaging
Tool), software che mette a disposizione vari comandi che facilitano queste
operazioni, come apt-get. Tutte le librerie analizzate in seguito sono state
installate utilizzando il comando apt-get. Le principali librerie che sono sta-
te installate e configurate per lo sviluppo del software applicativo sono le
seguenti:
• le librerie grafiche Pango e Cairographics utilizzate per la creazione del
modulo di stampa
• la libreria CUPS per la gestione della stampante termica
• la libreria libconfig++, utilizzata per il modulo di configurazione
7
12. CAPITOLO 2. IMPOSTAZIONE INIZIALE DISPOSITIVO
• ntpdate per l’aggiornamento di data e ora
• TinyXML
Cairographics è una libreria grafica 2D che viene utilizzata per la rea-
lizzazione di grafica vettoriale in modo consistente su diversi dispositivi e
sistemi operativi. E’ una libreria open-source sviluppata in linguaggio C,
che offre numerosi bindings per altri linguaggi di programmazione. Pango è
una libreria che viene utilizzata per la gestione del layout e del rendering di
contenuto di tipo testuale. E’ integrata nativamente con Cairo ed è infatti
raccomandata dalla documentazione di Cairographics per la gestione di testi.
CUPS è un sistema di stampa open source sviluppato da Apple per siste-
mi UNIX-like e macOS. Generalmente CUPS è installato di default in tutti
i sistemi Ubuntu, ma per operazioni di controllo più avanzate è necessario
installare la versione per sviluppatori della libreria. L’utilizzo di CUPS ci ha
permesso di controllare la stampante termica con il programma sviluppato
in C++ e di poter avere un maggior controllo delle opzioni di stampa, quali
le dimensioni della superficie di stampa o la gestione del taglio dei fogli.
Libconfig è una libreria per la gestione di file di configurazione strutturati.
La libreria è scritta in C++, e prevede l’uso di file di configurazione scritti in
un formato più semplice e leggibile dell’XML. E’ stata scelta questa libreria,
tra le molte disponibili, per la semplicità nell’utilizzo e per la consolidata
compatibilità con i sistemi Unix.
Ntp è un protocollo basato su TCP/IP per l’aggiornamento dell’ora di un
computer, il cui funzionamento è basato sulla comunicazione fra un client,
in esecuzione sulla macchina di cui si vuole aggiornare l’ora, e dei server
che forniscono l’ora corrente. Ubuntu dispone di due diversi strumenti per
utilizzare ntp, chiamati ntpdate e ntpd. Nel sistema oggetto di analisi si è
deciso di installare ntpdate, strumento che contatta i server ntp ad ogni avvio
del sistema operativo per aggiornare l’ora del sistema. Questo strumento
inoltre richiede meno utilizzo di risorse rispetto a ntpd.
Tinyxml è un analizzatore di testo XML sviluppato in C++. Questa
libreria fa dei suoi punti di forza la semplicità di utilizzo e un basso utilizzo
di risorse di sistema per il suo funzionamento. E’ inoltre disponibile gra-
tuitamente ed è open-source, caratteristiche che hanno quindi portato alla
scelta di questa libreria rispetto ad altre disponibili sul mercato.
8
13. CAPITOLO 2. IMPOSTAZIONE INIZIALE DISPOSITIVO
2.2 Configurazioni di rete
Di default il Raspberry adotta il protocollo DHCP per ottenere accesso alla
rete IP al quale è stato connesso. Nel caso in esame è sorta la necessità
di installare diversi dispositivi sulla stessa network, rimanendo in grado di
individuare in modo univoco tramite l’indirizzo IP ogni scheda in quella
data network. Questa identificazione univoca è necessaria solo per poter
effettuare aggiornamenti e controlli mirati al singolo computer. E’ stato
quindi deciso di impostare due diversi tipi di configurazione, i cui parametri
verranno gestiti dal programma in esecuzione sulla macchina. Si è andati a
creare due distinte interfacce di rete, una configurata in maniera statica e una
seconda configurata in maniera dinamica tramite DHCP. Per implementare
queste interfacce è stato necessario modificare il file /etc/network/interfaces
contenente i parametri di accesso e configurazione ethernet. In questo file,
di default è presente l’interfaccia di loopback, ovverosia il localhost. Inoltre,
in presenza di un accesso ethernet il sistema operativo aggiunge in maniera
autonoma un’interfaccia ethernet configurata tramite DHCP. Nel caso in
analisi il file si presentava inizialmente così:
auto lo
auto MAC-Address
iface lo inet loopback
iface MAC-Address inet dhcp
Nei dispositivi in uso il sistema operativo denomina di default l’interfaccia
ethernet con l’indirizzo MAC della stessa (denominato MAC-Address nel file
precedente). E’ stato necessario utilizzare il seguente script, scritto in bash,
per modificare in modo permanente questa impostazione:
#!/bin/bash
mymac="$(ifconfig | grep ’HWaddr’ | cut -d -f11)"
file="/etc/udev/rules.d/70-persistent-net.rules"
echo $mymac
if [ -f $file ]
then
echo "$file Found"
else
echo "$file Not Found"
echo ’SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*",
ATTR{address}=="’$mymac’", ATTR{dev_id}=="0x0",
ATTR{type}=="1", KERNEL=="eth*", NAME="eth0" ’>> $file
fi
Lo script va ad inserire una nuova regola di sistema nella quale si imposta
il nome dell’interfaccia ethernet con eth0. Questa operazione è stata resa
necessaria per avere un software uniforme e semplice su tutti i dispositivi
che si andranno a configurare.
9
14. CAPITOLO 2. IMPOSTAZIONE INIZIALE DISPOSITIVO
Effettuata questa modifica, si è proceduto a modificare il file /etc/networ-
k/interfaces. L’interfaccia di loopback non è stata modificata, mentre sono
state distinte due diverse interfacce ethernet, chiamate eth0-conf e eth0-work,
la prima destinata a essere configurata tramite indirizzo statico, la seconda
tramite DHCP. L’interfaccia eth0-conf è stata configurata con un indirizzo
IP statico, appartenente alla network privata nella quale è installato il Rasp-
berry. L’interfaccia eth0-work è stata impostata in modalità DHCP. Queste
due interfacce sono modificabili dal software applicativo secondo necessità,
basandosi su un opportuno file di configurazione. E’ stato inoltre imposta-
to che all’avvio la scheda non deve connettersi con nessuna interfaccia, in
quanto sarà uno specifico script bash che si occuperà di attivare la connes-
sione di configurazione (eth0-conf), mentre il software applicativo effettuerà
il cambio di interfaccia portandosi sull’interfaccia eth0-work.
Il file /etc/network/interfaces modificato si presenta quindi così:
auto lo
iface lo inet loopback
iface eth0-work inet dhcp
iface eth0-conf inet static
address some-IP
network some-networknumber
gateway some-gatewayIP
I parametri di rete quali l’indirizzo IP, il network number e l’indirizzo di
gateway vengono specificati nel file di configurazione.
2.3 Partizioni del sistema operativo
Uno dei principali requisiti di lavoro consiste nell’ottenere un sistema in gra-
do di operare senza interfaccia utente. Nel caso del sistema operativo preso
in considerazione è stato riscontrato un bug che al momento della realizza-
zione del progetto non era ancora diventato oggetto di analisi per il team di
sviluppo di Ubuntu Mate. In seguito il problema non è stato riconosciuto
come valido in quanto le build del sistema operativo in cui si verifica questo
problema non sono state considerate come ufficiali. Il problema consiste nel
fatto che, in caso di spegnimento del dispositivo in seguito all’interruzione
dell’alimentazione, al reboot il sistema operativo entrerà sempre in una mo-
dalità di emergenza, richiedendo un controllo ai file di sistema. Per uscire da
questa modalità di emergenza è necessario inserire manualmente le creden-
ziali di amministratore e riavviare nuovamente il sistema operativo. Tuttavia
questa modalità blocca il boot del sistema operativo e impedisce l’esecuzione
di qualsiasi altro processo che si vuole eseguire, andando pertanto a creare
un incongruenza con le specifiche di progetto. Questo sistema di emergenza
è stato implementato per verificare l’integrità del sistema in caso di spegni-
10
15. CAPITOLO 2. IMPOSTAZIONE INIZIALE DISPOSITIVO
menti considerati anomali dal sistema operativo, andando però a inficiare il
funzionamento automatizzato del Raspberry.
Per risolvere questo problema si è deciso quindi di configurare il sistema
in modo tale da prevenire errori in caso di spegnimento anomalo, evitando
l’insorgere del problema descritto.
La soluzione adottata è stata quella di impostare una parte del sistema
operativo in modalità read-only, per prevenire errori in fase di scrittura nel
file-system. Non è stato possibile impostare l’intero sistema operativo in
modalità di sola lettura in quanto il software necessario per il funzionamento
della stampante ha necessità di creare dei file temporanei in diverse sezioni
del sistema operativo. Si è optato quindi per una configurazione in cui solo
la directory contenente i file necessari all’avvio del sistema operativo è im-
postata in modalità di sola lettura, in modo tale da prevenire il problema
della modalità di emergenza descritto in precedenza.
La tabella di configurazione dei file system è un file che contiene i dati
relativi alle partizioni di sistema e alle sue proprietà, ed è nota come fstab e
si trova all’interno della directory etc. La tabella è stata così impostata:
file system mount-point type options dump pass
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs nodev, nosuid, 0 0
size=100 M, mode=1777
/dev/mmcblk0p2 / ext4 defaults,noatime 0 2
/dev/mmcblk0p1 /boot vfat defaults,ro 0 2
E’ stata allocata una partizione sulla RAM del dispositivo, in cui vengono
destinati i processi di creazione di file temporanei generati dal software appli-
cativo. In questa partizione verranno creati tutti i file temporanei necessari
per il funzionamento del software applicativo che si è andati a sviluppare.
Grazie a questa partizione, nel caso in cui avvenga una rimozione imprevista
della scheda di memoria si previene la possibilità di andare a corrompere dei
file essenziali per il funzionamento del software applicativo. L’unico impre-
visto che può arrecare danni al sistema è l’interruzione dell’alimentazione o
la rimozione della memoria durante le operazioni di aggiornamento software,
eventualità che non può essere risolta sfruttando particolari partizioni della
memoria. Questa problematica può essere però risolta utilizzando altri ac-
corgimenti software, come ad esempio l’utilizzo di backup esterni e copie del
programma principale in altre sezioni della scheda di memoria. La scheda SD
è stata quindi divisa in due partizioni, la prima in formato ext4 che contiene
la maggior parte del sistema operativo e una partizione in formato vfat in
cui è stata allocata la sezione /boot. Per la partizione /dev/mmcblk0p2 è
stata impostata l’opzione noatime per limitare le operazioni di scrittura sulla
11
16. CAPITOLO 2. IMPOSTAZIONE INIZIALE DISPOSITIVO
scheda di memoria. Linux generalmente associa ad ogni file degli attributi
in cui vengono memorizzati le date di creazione del file, di ultimo accesso
al file e di ultima modifica. Senza l’opzione noatime questi attributi ven-
gono aggiornati per ogni accesso ad uno specifico file. Impostando noatime
in una partizione quando il sistema operativo legge un determinato file di
sistema non aggiorna più gli attributi dello stesso, limitando notevolmente
le operazioni di scrittura, migliorando le performance del sistema operati-
vo e riducendo la possibilità di incorrere in errori in fase di scrittura. La
partizione /dev/mmcblk0p1 è stata impostata in modalità di sola lettura
per ovviare al bug descritto all’inizio di questa sezione e per evitare errori
in scrittura nella partizione. In questo modo tutto ciò che è necessario per
l’avvio del sistema operativo viene protetto dalla corruzione dei dati in caso
di interruzione dell’alimentazione.
12
17. Capitolo 3
Sviluppo Software applicativo
Tutti i moduli software descritti in questa sezione sono stati sviluppati dal
laureando. Il programma è stato scritto in C++.
3.1 Descrizione generale
Lo schema generale del sistema sviluppato è così composto:
Il Raspberry è collegato ad un lettore di schede magnetiche, a un led e a
un piezo monotonale tramite l’interfaccia GPIO. E’ inoltre collegato ad una
stampante termica tramite USB e a internet tramite ethernet. Il software
si occupa di gestire tutte le periferiche collegate al Raspberry, eseguendo le
opportune elaborazioni richieste dal committente. Il software è stato svilup-
pato in maniera modulare e i vari moduli software vengono attivati in caso di
necessità. Il sistema si avvia quando si rileva un input dal lettore di tessere
magnetiche. E’ presente quindi un modulo che esegue un loop in lettura dei
13
18. CAPITOLO 3. SVILUPPO SOFTWARE APPLICATIVO
dati provenienti dal lettore di schede connesso all’interfaccia GPIO. Quando
avviene la ricezione dei dati verrà attivato un modulo per fornire un feedback
audiovisivo all’utente che ha utilizzato il badge magnetico, e un secondo mo-
dulo che si occupa di codificare i dati ricevuti per renderli compatibili con
quelli attesi dal WebServer. Dal WebServer il sistema si attende tre possibili
risposte: scheda valida e dei dati, scheda non valida e scheda non riconosciu-
ta. Per ogni risposta è previsto un differente feedback audiovisivo. L’analisi
dei dati ricevuti viene svolta dai moduli descritti nella sezione 3.3, moduli
dedicati alla codifica e all’interpretazione dei dati. In caso di scheda valida, i
dati ricevuti e codificati vengono ulteriormente elaborati per essere inviati in
stampa alla stampante termica. Il modulo software che si occupa di questa
operazione è descritto nella sezione 3.2. Il modulo di stampa fornisce dei
feedback sullo stato della stampa e sulla presenza di eventuali errori nei dati
al modulo che lo ha attivato; quest’ultimo modulo segnalerà all’utente in
modo opportuno tramite l’interfaccia GPIO i feedback ricevuti.
All’avvio inoltre il software attiva un modulo dedicato alla configurazione
del sistema e all’aggiornamento dello stesso nel caso in cui siano presenti
nuove opzioni di configurazione.
3.2 Modulo di stampa
La stampa viene gestita da un modulo sviluppato in C++ chiamato Prin-
terModule.cpp. Questo modulo fa uso delle librerie Cairographics, Pango
e Cups descritte nella sezione 2.1. Pango e Cairo vengono utilizzate per
generare un file in formato PostScript che viene poi inviato alla stampante
termica tramite la libreria Cups. Le stampe generate hanno un layout simile
a quello presentato nella seguente immagine:
Figura 3.1: Esempio di layout di stampa desiderato
Lo scopo di questo modulo software è quelli di posizionare le varie strin-
ghe desiderate sul foglio, secondo uno specifico layout. Sono state previste le
14
19. CAPITOLO 3. SVILUPPO SOFTWARE APPLICATIVO
seguenti tipologie di stampa per soddisfare i requisiti forniti dal committente:
• Una stampa effettuata all’avvio del sistema, per segnalare all’operatore
che il programma è pronto per l’utilizzo; in questo messaggio si è deciso
di inserire dati relativi alla versione del software e all’indirizzo di rete
di lavoro al quale è stato configurato il sistema
• A seconda dei dati ricevuti dal webserver è prevista una stampa deno-
minata “responsabile” e una seconda stampa chiamata “operatore”
Le stampe operatore e responsabile hanno layout e dati differenti, pertanto
sono gestiti da due metodi differenti.
Per generare un file PostScript con Cairo è necessario creare un oggetto
denominato surface che funge da struttura di base del file finale. Questa
surface è inizializzata specificando la tipologia di file che si vuole andare a
creare, in questo caso PostScript. Le dimensioni del file PostScript sono
espresse in punti tipografici. Questa unità di misura può essere convertita
utilizzando la seguente relazione: 1 punto tipografico = 1/72 inch = 0,35278
mm = 0,75 pixel. Questa equivalenza è importante in quanto Cairo for-
nisce le dimensioni del file generato in pixel o in punti tipografici, mentre
CUPS ha bisogno di un valore espresso in millimetri per un corretto taglio
della stampa. Ottenuta la superficie di lavoro il programma genera un og-
getto chiamato context su cui vengono effettuate le operazioni di disegno e
scrittura. Cairo fornisce un API per la gestione testuale che però presenta
diverse limitazioni e di cui è sconsigliato l’uso se non per prove o demo. Per
il progetto in analisi, se si fosse deciso di utilizzare Cairo anche per l’inse-
rimento del testo il codice sarebbe risultato molto più complesso in quanto
la gestione di diverse tipologie di font e il posizionamento del testo in modo
dinamico non sono ben gestiti dalle funzioni fornite dall’API. L’inserimento
del testo viene eseguito utilizzando la libreria Pango. Con questa libreria
è possibile definire agevolmente diversi font di scrittura, che possono venire
utilizzati per una preparazione più personalizzata della stampa. Per ogni
font desiderato si inizializza una struttura chiamata PangoFontDescription
in cui vengono specificati tramite diverse funzioni il colore, la dimensione, la
famiglia del font e altre opzioni. Il testo viene posizionato sulla superficie di
lavoro tramite coordinate cartesiane, la cui unità di misura è il punto tipo-
grafico e la cui origine è nell’angolo superiore sinistro della pagina. Pango
permette inoltre di definire vari parametri per il layout, quali le coordinate
di inserimento del testo, fornite da Cairo, l’interlinea tra le righe, la larghez-
za massima del testo e il “text wrapping”, ovvero le opzioni di gestione del
testo se questo supera una cella di dimensioni prefissate. Quando si procede
all’inserimento del testo si specifica il layout scelto e il descrittore del font
che si vuole utilizzare.
Uno dei problemi sorti durante lo sviluppo di questo modulo è stato il
calcolo delle dimensioni della stampa finale, in particolare della lunghezza
15
20. CAPITOLO 3. SVILUPPO SOFTWARE APPLICATIVO
del file PostScript, parametro essenziale per il funzionamento corretto della
stampante. Il problema sorge in quanto in alcune tipologie di stampa previste
è variabile sia il numero di parametri che la lunghezza delle stringhe fornite.
Non è quindi possibile calcolare a priori la lunghezza finale del foglio ed è
stato necessario ricorrere a due cicli di impaginazione, il primo per il calcolo
della dimensione finale, e il secondo per la creazione del file PostScript finale.
Un’altra funziona importante implementata in questo modulo è quella
dedicata alla gestione della stampante termica tramite il sistema di stampa
CUPS. Questa funzione non si occupa solamente di inviare alla stampante
il file PostScript creato, ma deve anche rilevare eventuali malfunzionamenti
nella stampante per poterli segnalare all’utente tramite una specifica codifica
audio-visiva. Il programma per prima cosa crea una opzione di stampa per-
sonalizzata inizializzando l’oggetto cups_option_t. In questo oggetto viene
inserita la lunghezza del foglio che verrà stampato, determinata dalla funzio-
ne che crea il file PostScript, e la larghezza, che ha un valore predefinito. In
seguito la funzione invia un comando per svuotare la coda di stampa, questo
per evitare che in caso di errori e mancata stampa di una qualche chiamata
precedente della funzione non si accumulino troppe stampe in coda. Questo
potrebbe risultare problematico ad esempio nel momento in cui la carta o
il toner risultassero esauriti, poiché al ripristino della stampante potrebbero
esserci troppe stampe in attesa ed eventualmente multiple se l’utente ha pas-
sato il badge più volte sul lettore. Gli errori di stampa vengono rilevati da
un loop che verifica che, per ogni stampa che è stata inserita nella coda, lo
stato inviato come feedback dalla stampante sia “printing”. Nel caso in cui le
stampe in coda rimangono in uno stato di “pending” o di altro errore è neces-
sario segnalare all’utente il malfunzionamento della stampante. La funzione
terminerà quindi inviando un valore false (EXIT_FAILURE) al metodo
che l’ha invocata, che si occuperà quindi di segnalare il malfunzionamento
tramite il led e un segnale sonoro.
16
21. CAPITOLO 3. SVILUPPO SOFTWARE APPLICATIVO
3.3 Modulo di analisi e gestione dell’input
In questa sezione verranno descritti i procedimenti eseguiti per l’analisi e la
gestione dei dati ricevuti dal WebServer in seguito alla lettura di un badge
tramite il lettore di tessere magnetiche. Il software applicativo contatta il
WebServer ad un URL specificato nei parametri di configurazione, che ver-
ranno descritti in seguito, inviando il codice della tessera magnetica e la
data. Il WebServer invia una risposta in un formato testuale, la cui sintassi
è molto simile a quella del linguaggio marcatore XML. Infatti, a causa di
un’errata configurazione del WebServer, alcuni caratteri vengono codificati
con una sintassi non corretta; ad esempio i tag ’<’ e ’>’ vengono letti dal
web-service client come ’<’ e ’>’. La prima operazione che viene esegui-
ta dal client è pertanto quella di convertire questa risposta in XML “puro”,
in modo tale da poter esplorare agevolmente i dati ricevuti. Effettuata la
conversione viene chiamata una funzione che ha il compito di eseguire un
parsing del testo XML per salvarlo in un vettore di stringhe. Il parsing
viene effettuando utilizzando la libreria TinyXML introdotta in preceden-
za (sezione 2.1). TinyXML a partire dal documento XML costruisce un
Document Object Model (o DOM), ovverosia struttura il documento come
oggetti C++ che possono essere esplorati e manipolati. Le varie sezioni del
documento XML vengono quindi modificate a seconda del loro significato; ad
esempio un commento, espresso in XML nella forma <!–Comment–>, diven-
ta un oggetto denominato TiXmlComment. Nell’analisi che si vuole andare
ad effettuare gli elementi rilevanti sono i tag, elaborati come TiXmlElement,
e il testo contenuto nelle varie sezioni, che diventa un oggetto TiXmlText.
L’algoritmo utilizza questi oggetti per analizzare il documento, inserendo in
un vettore di stringhe i vari elementi testuali che vengono incontrati duran-
te l’esplorazione dell’albero del DOM. L’algoritmo termina quando tutti gli
TiXmlElement sono stati analizzati e restituisce il vettore di stringhe al me-
todo invocante. Ogni stringa che viene inserita nel vettore contiene i dati
relativi per una singola stampa che si vuole andare a ottenere. I singoli va-
lori testuali che devono essere inseriti nel foglio che si vuole ottenere sono
inseriti con un ordine prestabilito e separati da un carattere con la funzione
di delimitatore. Queste stringhe devono essere quindi ulteriormente analiz-
zate per venire scomposte in vari “tokens” che potranno essere utilizzati dal
modulo di stampa. Questa operazione viene gestita da un modulo software
denominato InputParser. Il modulo inizialmente esplora la stringa fornitagli
in input andando a cercare al suo interno il carattere fornito come delimita-
tore, separando la stringa principale in sottostringhe in corrispondenza dei
delimitatori. La seconda operazione consiste nel riconoscimento della tipo-
logia di stampa, indicata dalla prima sottostringa. Sono previsti due casi
di stampa e per ogni tipologia viene effettuata un’ulteriore elaborazione dei
tokens. In un caso, denominato “operatore”, è previsto un numero di para-
metri stabilito a priori, pertanto l’algoritmo verifica se il numero di stringhe
17
22. CAPITOLO 3. SVILUPPO SOFTWARE APPLICATIVO
è corretto, producendo una segnalazione di errore per il metodo invocante
nel caso in cui il controllo non venga superato. Fra le stringhe oggetto di
analisi è presente una stringa che ha il valore di una data che, da specifi-
che, deve essere convertita dal formato “dd/mm/yyyy” (giorni, mesi e anno
espressi in formato numerico) nel formato “dayname dd/monthname/yyyy”
dove dayname è il nome esteso in italiano del giorno, e monthname il no-
me esteso del mese considerato. La conversione viene effettuata sfruttando
le funzioni strptime e strftime di linux. Strptime converte una stringa che
rappresenta un’indicazione temporale in una struttura di tipo tm. Questa
struttura è definita nell’header time.h presente nella libreria standard di C.
Al suo interno vengono inseriti tutti i dati relativi alle informazioni temporali
contenute nella stringa che viene fornita a strptime. La struttura tm viene
poi riconvertita in stringa sfruttando la funzione strftime secondo i parametri
di formattazione desiderati.
Nel secondo caso, denominato “responsabile” i dati contenuti nei tokens
devono essere ulteriormente suddivisi in quanto prevedono a loro volta dei
delimitatori. Per ogni token viene quindi applicato l’algoritmo utilizzato
inizialmente per suddividere la stringa di input nei vari tokens.
3.4 Modulo di configurazione
La libreria libconfig++ permette di creare file di configurazione molto ver-
satili e in grado di fornire diversi dati al sistema. Il formato del file di confi-
gurazione è di tipo testuale ed è costruito per essere più leggibile e compatto
dell’XML. E’ inoltre un sistema di gestione della configurazione che richiede
poche risorse e, visto il numero non elevato di parametri da configurare nel
progetto in analisi, si è rivelato la scelta più opportuna.
Una configurazione, utilizzando la sintassi di questa libreria, è costituita
da un insieme settings associate ad un nome e a uno o più valori. I valori
possono essere valori scalari, array di valori, gruppi di settings, o liste. La
sintassi per definire una setting è la seguente: name:value; o name=value; i
gruppi vengono identificati dalle parentesi grafe. Ogni settings viene iden-
tificata in maniera univoca da un percorso (path), che è costituito da una
sequenza di nomi, separati da punti, che partono dal nome del gruppo di
livello più alto fino a giungere al nome della setting stessa. Un altro vantag-
gio nell’utilizzare questa libreria consiste nel fatto che il tipo dei valori viene
determinato in automatico dal valore stesso del dato. Ad esempio un valore
racchiuso fra apici viene considerato di tipo stringa; una stringa ’true’ (o ’fal-
se’), senza tenere in considerazione maiuscole o minuscole, viene considerata
come un parametro di tipo booleano.
18
23. CAPITOLO 3. SVILUPPO SOFTWARE APPLICATIVO
Il file di configurazione per il progetto è stato così strutturato:
# --------------------------
# Example Configuration File
# --------------------------
version = "1.0.0.0";
authWebServiceURL = "http://192.168.50.50:8725/ws.asmx";
NetworkParam = {
conf = {
dhcp = FALSE;
address = "192.168.50.150";
gateway = "192.168.50.254";
netmask = "255.255.255.0";
network = "192.168.20.0";
dnsVal = "8.8.8.8 8.8.8.4";
}
work = {
dhcp = FALSE;
address = "192.168.50.151";
gateway = "192.168.50.254";
netmask = "255.255.255.0";
network = "192.168.20.0";
dnsVal = "8.8.8.8 8.8.8.4";
}
}
I parametri principali contenuti nel file di configurazione sono quindi la
versione del software, l’indirizzo URL del WebService con il quale il program-
ma dovrà comunicare e i parametri per la configurazione di rete. Il modulo
C++ di configurazione del software applicativo si occupa di effettuare un’a-
nalisi del documento e di estrarre i dati contenuti nel file, sfruttando le
funzionalità della libreria. L’indirizzo del WebService viene salvato e utiliz-
zato quando si verifica la necessità di contattare il WebService per ottenere
dei dati relativi a una tessere magnetica. I parametri di rete vengono utiliz-
zati come input per uno script AWK che si occupa della configurazione di
rete. Questo script è stato sviluppato da terze parti e consente la lettura e la
modifica del file /etc/network/interfaces, descritto nel paragrafo 2.2, senza
necessità di modificare direttamente il file dei parametri di rete. L’utilizzo
di questo script semplifica quindi il programma C++, in quanto è sufficiente
eseguire lo script senza la necessità di accedere ai file di sistema.
19
24. CAPITOLO 3. SVILUPPO SOFTWARE APPLICATIVO
Lo script, chiamato changeInterfaces.awk viene eseguito nel seguente
modo:
awk -f /path/to/changeInterfaces.awk <interfaces file>
dev=<eth device>
[address=<ip addr>] [gateway=<ip addr>] [netmask=<ip addr>]
[network=<ip addr>] [mode=dhcp|static] [dns=<ip addr>]
Tutti i parametri indicati con le parentesi quadre sono opzionali, se non
inseriti non vengono modificati dallo script. Il modulo prepara quindi una
stringa corrispondente al comando awk che si vuole eseguire e la manda in
esecuzione sfruttando i privilegi di amministratore di cui è dotato il software.
Un’altra importante funzionalità gestita da questo modulo software è il
controllo dell’autenticità del programma. Per evitare che il software instal-
lato sulla scheda SD, che funge da memoria per il Raspberry, possa venir
clonato in modo incontrollato, è stato deciso di inserire una licenza univoca
per ogni dispositivo. La licenza sviluppata è legata ai parametri hardware
del Raspberry che, essendo univoci, permettono di legare in maniera indisso-
lubile licenza e hardware. I parametri hardware non sono inseriti “in chiaro”
all’interno della licenza, ma vengono elaborati e crittografati utilizzando un
sistema a codifica XOR. Per evitare manomissioni del file contenente la li-
cenza, essa è stata inserita nella partizione /boot impostata in modalità
read-only (descritta nella sezione 3.3), che risulta accessibile in lettura a tut-
ti gli utenti, ma modificabile solo da un utente amministratore che decida di
reimpostare la partizione in modalità di scrittura. La licenza viene infatti ge-
nerata dall’amministratore di sistema al momento della prima installazione
del software applicativo o in caso di sostituzione della scheda SD. Il modulo
software qui trattato esegue un algoritmo che gli permette di calcolare a sua
volta il codice di licenza che verrà poi confrontato con quello contenuto nella
partizione protetta. Il risultato e il timestamp di questo controllo viene sal-
vato su un file di log accessibile via FTP. Il risultato sarà rispettivamente un
valore EXIT_SUCCESS nel caso in cui il controllo risulti superato, per-
mettendo la prosecuzione del programma, o un valore EXIT_FAILURE
nel caso in cui il controllo fallisca, caso che verrà trattato opportunamente
dalla classe che ha invocato questa funzione.
20
25. Capitolo 4
Automatizzazione del
dispositivo
In questa sezione verranno descritti gli interventi fatti per rendere auto-
matizzato il funzionamento del dispositivo. In particolare verrà descritto e
analizzato lo script bash utilizzato per eseguire il software applicativo, i tool
e le configurazioni adottate per l’utilizzo di FTP e SSH.
4.1 Script per la gestione del software applicativo
Per poter eseguire all’avvio il software applicativo, è stato sviluppato uno
script bash che viene eseguito all’avvio del sistema. Questo script svolge
diverse funzioni: si occupa di monitorare la cartella dove vengono caricati
tramite il protocollo FTP i nuovi file di configurazione e di spostare questi
file nella sezione apposita del dispositivo, crea un log delle accensioni e man-
da in esecuzione il software applicativo. La prima operazione svolta dallo
script è quella di attivare l’interfaccia di rete di default, ovvero l’interfaccia
di configurazione che, come visto nel paragrafo 2.2 è stata configurata tra-
mite indirizzo IP statico. Il log delle accensioni della scheda viene generato
salvando gli ultimi 50 accessi, con data di accesso, data dell’ultimo login e
data dell’ultima configurazione. Questi dati vengono inseriti in un file che è
visualizzabile tramite FTP o SSH. Quando viene eseguito, lo script controlla
se sono stati inseriti nuovi aggiornamenti in una cartella definita in fase di
configurazione. Se il controllo non viene superato, lo script continua a mo-
nitorare la cartella in questione per 30 secondi tramite il tool “inotifywait”.
Inotifywait è un tool utilizzato per monitorare dei cambiamenti su specifici
files, utilizzando l’API inotify di linux. Questa API mette a disposizione
diverse system call per il monitoraggio di files e cartelle. Nel progetto in
analisi è stata scelta inotifywait in quanto più adatta per essere usata in
shell script. Se nel controllo iniziale, o nei 30 secondi successivi vengono
trovati aggiornamenti, lo script crea un backup del programma precedente
21
26. CAPITOLO 4. AUTOMATIZZAZIONE DEL DISPOSITIVO
e posiziona i nuovi file nel percorso voluto. Per l’esecuzione del programma
viene eseguita una fork dello script in esecuzione che viene messa in stato
di “sleep” per 50 secondi. Al termine di questo periodo il processo generato
tramite la chiamata di sistema fork() manda in esecuzione il programma e
invia un comando kill() tramite il segnale SIGUSR1 a se stesso e al processo
che l’ha generato. Tramite questa procedura il processo figlio dello script,
ovvero il software applicativo, viene “adottato” da root, garantendogli quindi
i permessi di amministratore per tutta la durata del programma. L’utiliz-
zo della system call kill() viene adottato anche per terminare eventuali casi
non previsti o errori durante l’esecuzione dello script, come ad esempio er-
rori duranti il loop di monitoraggio della cartella FTP o nella creazione del
backup.
In ambiente linux esistono diverse vie valide per eseguire un programma
o uno script all’avvio del sistema operativo. Una di queste prevede la crea-
zione di uno script, basato su un preciso standard, nella directory /etc/init.d
per eseguire un dato programma. Lavorando con il Raspberry esiste un’al-
ternativa più semplice per eseguire un comando o un programma al boot
del sistema operativo, senza la necessità di ulteriori script o configurazioni.
Questa alternativa consiste nell’inserire i comandi desiderati all’interno del
file rc.local, il cui percorso è /etc/rc.local. Nel caso in analisi la modifica del
file è stata l’inserimento della seguente stringa all’interno di rc.local:
sudo /path/to/ConfigMonitorRoutine.sh &
Il comando esegue con i permessi di superuser lo script analizzato in prece-
denza, chiamato appunto ConfigMonitorRoutine.sh. Il carattere ‘&’ è stato
inserito per eseguire una chiamata di sistema fork() del processo, consenten-
do allo stesso tempo il proseguimento dell’esecuzione dello script rc.local e
la conclusione del boot del Raspberry.
4.2 Accesso via FTP e SSH e permessi utente
Per consentire l’aggiornamento e un monitoraggio in remoto del sistema si è
deciso di abilitare un server FTP e di configurare opportunamente un SSH
server su ogni sistema operativo installato.
La prima operazione è stata quella di creare due tipologie di utente, la
prima con i permessi di superuser, mentre la seconda come utente standard,
senza privilegi di root. Questo per consentire solo agli amministratori di
sistema di effettuare modifiche significative al progetto. L’idea è quella di
garantire l’accesso completo da remoto al sistema solo agli utenti in possesso
dei privilegi di amministratore, mentre gli utenti standard devono poter avere
accesso solo a un particolare server FTP usato per gli aggiornamenti e per
la lettura di alcuni log di sistema.
22
27. CAPITOLO 4. AUTOMATIZZAZIONE DEL DISPOSITIVO
E’ stato quindi attivato un server FTP su ogni scheda, garantendo l’ac-
cesso a tutti gli utenti autorizzati solo a una specifica cartella. Questa car-
tella verrà utilizzata per l’inserimento di nuovi aggiornamenti del software
applicativo e del file di configurazione. In questa cartella vengono inoltre
generati alcuni file di log, in modo tale da garantire un primo metodo per
diagnosticare eventuali problemi avvenuti durante l’avvio del programma.
La configurazione del server FTP è stata fatta utilizzando vsftpd uno dei
programmi più usati disponibili in ambiente linux per l’installazione di un
server FTP. L’installazione di vsftpd viene fatta sfruttando il comando apt-
get, mentre per la configurazione dello stesso si procede alla modifica del file
vsftpd.conf, che viene collocato al momento dell’installazione nella directory
/etc. Il file di configurazione è stato quindi modificato negando l’accesso tra-
mite FTP ad utenti anonimi e limitando l’accesso alle due tipologie di utenti
prima descritte solo a una particolare cartella che si è deciso di collocare
nella directory /var.
Per consentire agli amministratori un accesso completo in remoto al si-
stema operativo è stato inoltre abilitato un server SSH. Anche in questo caso
per l’installazione si è utilizzato il tool apt-get, e per la configurazione è sta-
to necessario modificare il file di configurazione relativo, collocato anch’esso
nella directory /etc.
23
28. Conclusioni
Il dispositivo che si è andati a sviluppare si è dimostrato in grado di soddisfa-
re le specifiche emerse in fase di analisi introduttiva. Sono state utilizzate in
modo efficace tutte le periferiche imposte dal committente ed è stato svilup-
pato un opportuno sistema di feedback per l’utente. Il funzionamento della
web service e la comunicazione con il web server sono stata testati in modo
estensivo, così come i moduli software per l’elaborazione dei dati. Il sistema
si è inoltre dimostrato molto robusto, garantendo così stabilità al progetto.
Il software sviluppato è stato installato su diversi Raspberry Pi 2, che dopo
esser stati opportunamente configurati, sono adesso operativi nelle sedi sta-
bilite dal committente. Grazie alle soluzioni adottate per l’aggiornamento e
la configurazione remota del sistema, c’è la possibilità di migliorare ulterior-
mente il sistema in futuro e eventualmente di sviluppare nuove funzionalità.
Alcune delle soluzioni che sono state sviluppate sono inoltre utilizzabili anche
su differenti progetti basati sulla stessa tipologia di hardware. In particolare
l’impostazione di parte del file-system in modalità di sola lettura, lo script
per l’esecuzione all’avvio di un software applicativo e l’abilitazione degli ac-
cessi FTP e SSH sono strumenti che possono essere agevolmente utilizzati in
altri progetti basati sul Raspberry Pi o schede simili.
24
29. CONCLUSIONI
Figura 4.1: Foto del progetto ultimato e preparato per l’utilizzo industriale
Figura 4.2: Foto del progetto installato in ambito industriale
25
30. Appendice A
Codice Sorgente
In questa sezione verranno presentate le parti più significative del codice
sviluppato. Alcune sezioni sono state omesse per motivi di riservatezza.
A.1 PrinterModule.cpp
Classe contenente le varie funzioni dedite alla creazione di file PostScript per
la stampa e la gestione della stampante termica. Verrà presentata solo una
delle funzioni di stampa, quella per la creazione della stampa all’avvio. La
creazione delle stampe in seguito all’analisi dei dati ricevuti dal WebServer
è analoga, ma con un layout molto più complesso. I dati per questo tipo di
stampa vengono ottenuti dalla classe InputParser.c .
#include "PrinterModule.h"
#include "Periph/InputParser.h"
using std::string;
using std::vector;
/*
Definizione delle dimensioni del foglio e di alcuni parametri
come il bordo laterale e l’interlinea
Questi valori sono espressi in punti tipografici
*/
#define WIDTH 520
#define HEIGHT 2080
#define INTERLINE 20
#define BORD 0
#define FONT "Calibri"
26
31. APPENDICE A. CODICE SORGENTE
int settext (cairo_t *context, PangoFontDescription *desc,
int x, int y, char string[]){
/* Questo metodo posiziona una stringa su un cairo context alle
coordinate desiderate. */
PangoLayout *layout;
//Utilizzo dei metodi della librerie pango e cairo per la
gestione del testo
cairo_translate(context, x, y);
layout = pango_cairo_create_layout(context);
pango_layout_set_width(layout, WIDTH*PANGO_SCALE);
pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
pango_layout_set_spacing
(layout,(int)INTERLINE*PANGO_SCALE*0.5);
pango_layout_set_text(layout, string, -1);
pango_layout_set_font_description(layout, desc);
pango_cairo_update_layout(context, layout);
pango_cairo_show_layout(context, layout);
int width;
int height;
pango_layout_get_size (layout, &width, &height);
height =(int)(height/PANGO_SCALE);
cairo_translate(context, -x, -y);
g_object_unref(layout);
return height;
}
int setcenteredtext (cairo_t *context, PangoFontDescription *desc,
int x, int y, char string[]){
/*Posiziona il testo al centro della riga utilizzando il metodo
precedente*/
int x_len;
int y_len;
//funzione che fornisce lunghezza del testo in px
pango_layout_get_pixel_size (layout, &x_len, &y_len);
//calcola la lunghezza del testo in punti tipografici
x_len=0.75*x_len;
//calcola la posizione del testo centrato
int x_coord=0.5*(WIDTH-x_len+x);
return settext (context, desc, x_coord, y, string);
}
27
32. APPENDICE A. CODICE SORGENTE
int PrinterModule::PrintRoutine(char tmpfilename[], int len){
/*Questa sezione del programma dedicata all’invio alla stampante
termica dei file PostScript creati*/
int num_jobs;
cups_job_t *joblist; //Oggetto contenente la coda di stampa
//Determinazione della stampante di default
cups_dest_t *dests;
int num_dests = cupsGetDests(&dests);
//Creazione delle opzioni di stampa
int num_options;
cups_option_t *options;
num_options = 0;
options = (cups_option_t *)0;
int mylen=(int)((len+10)/10)*10;
string optiontext = "job-sheets=none,none
media=Custom.72x"+std::to_string(mylen)+"mm sides=one-sided";
//Impostazione delle opzioni di stampa
num_options = cupsParseOptions(optiontext.c_str(), num_options,
&options);
//Svuotamento della coda di stampa
cupsFreeDests(num_dests, dests);
cupsCancelJob(cupsGetDefault(), CUPS_JOBID_ALL);
//Inizio della stampa
int job = cupsPrintFile(cupsGetDefault(), tmpfilename, "cairo
PS", num_options, options);
num_jobs = cupsGetJobs(&joblist, NULL, 0, -1);
/*Controllo di eventuale stampe bloccate ed eventuale
segnalazione dell’errore.
Il controllo viene fatto verificando che le stampe inserite in
coda di stampa siano in uno stato di "printed"*/
for (int i = 0; i < num_jobs; i ++){
printf("%-16.16s %-6d %-12.12s %s (%s)n", joblist[i].dest,
joblist[i].id,
joblist[i].user, joblist[i].title,
joblist[i].state != IPP_JOB_PENDING ? "printed" : "pending");
if(joblist[i].id == job) {
if(joblist[i].state == IPP_JOB_PENDING ){
28
33. APPENDICE A. CODICE SORGENTE
std::cout << "Errore nella stampa"<<’n’;
cupsFreeOptions (num_options, options);
cupsFreeJobs(num_jobs, joblist);
return EXIT_FAILURE;
}
}
}
cupsFreeJobs(num_jobs, joblist);
cupsFreeOptions (num_options, options);
return EXIT_SUCCESS;
}
int PrinterModule::StartingPrint (string Version, string
MACAddress, string IPAddress){
char filename[]="/tmp/StartingPrint.ps";
cairo_surface_t* surface;
cairo_t *context;
PangoFontDescription *font;
//Creazione di una surface con una specifica dimensione (in
punti tipografici)
int len = 500;
double mmlen=len*(0.35)/(2.5);
surface=cairo_ps_surface_create (filename, WIDTH, len);
//Creazione del context
context=cairo_create (surface);
//Creazione di un descrittore di font
font=pango_font_description_new ();
pango_font_description_set_size (font,17*PANGO_SCALE);
pango_font_description_set_family (font, FONT);
pango_font_description_set_weight(font, PANGO_WEIGHT_BOLD);
settext(context, font, BORD, 0, (char*)"QAS SERVICE REPORTER
STARTED");
settext(context, font, BORD, 50,
(char*)"------------------------------------------------------");
std::string Versiontext="Version: "+Version;
settext(context, font, BORD, 100, (char*)Versiontext.c_str());
std::string LicenseText="License OK";
settext(context, font, BORD, 150, (char*)LicenseText.c_str());
std::string MACtext="MACAddress: "+MACAddress;
settext(context, font, BORD, 200, (char*)MACtext.c_str());
std::string IPtext="Work IP Address: "+IPAddress;
settext(context, font, BORD, 250, (char*)IPtext.c_str());
settext(context, font, BORD, 300,
(char*)"------------------------------------------------------");
29
34. APPENDICE A. CODICE SORGENTE
//Salvataggio del file e distruzione degli oggetti creati
cairo_surface_flush(surface);
cairo_surface_destroy(surface);
cairo_destroy(context);
pango_font_description_free(font);
//Inizio stampa
PrintRoutine(filename, (int)mmlen);
return EXIT_SUCCESS;
}
A.2 InputParser.cpp
Classe contente le funzioni che vengono utilizzate da PrinterModule.cpp per
ottenere le stringhe da inserire nel modulo di stampa.
#include "InputParser.h"
using std::string;
using std::vector;
vector<string> InputParser::str_decode (string Input, string delim){
//Questa funzione scompone la stringa in input in vari token
separati dalla sottostringa delim
vector<string> res;
int start = 0;
int end = Input.find(delim);
while (end != std::string::npos)
{
res.push_back (Input.substr(start, end - start));
start = end + delim.length();
end = Input.find(delim, start);
}
return res;
}
int InputParser::ConvertDate (char *input){
/*Converte la data dal formato "dd/mm/yyyy" nel formato
"local_dayname dd/mmmm/yyyy" */
setlocale (LC_ALL, "" );
struct tm tm;
char buf[255];
30
35. APPENDICE A. CODICE SORGENTE
memset(&tm, 0, sizeof(struct tm));
//Utilizzo di strptime
if(strptime(input,"%d/%m/%Y",&tm)==NULL){
printf ("Formato data erraton"); //input non valido
return(EXIT_FAILURE);
};
//Utilizzo di strftime
if(strftime(buf, sizeof(buf), "%^A %d %^B %Y", &tm)==0) {
printf ("Errore conversione datan"); //contents of buf are
undeterminate
return(EXIT_FAILURE);
};
std::string Date ((const char*) buf);
this->Date=Date;
return(EXIT_SUCCESS);
}
int InputParser::CheckTokens (vector<string> tokens){
//Controlla il numero di tokens per l’input di tipo "operatore"
if (tokens.size()<6) {
printf("Numero Parametri Insufficienten");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
int InputParser::GetOperator (){
this->Operator = this->tokens[0];
tokens.erase(tokens.begin());
return EXIT_SUCCESS;
}
int InputParser::DecodeInput (string input, string delim){
//Scompone l’input e prepara i parametri necessari alla classe
PrinterModule.cpp
this->tokens = str_decode (input, delim);
this->GetOperator();
if (Operator == "O") {
if (this->CheckTokens(tokens) == EXIT_FAILURE) {
return EXIT_FAILURE;
}
this->Name = str_decode (tokens[0],";");
if (Name.size()==0){
31
39. APPENDICE A. CODICE SORGENTE
}
string EncryptDecrypt(string source) {
//Cifratura XOR. La chiave contenuta nell’array key[], qui omesso
string output = source;
for (int i = 0; i < source.size(); i++) {
output[i] = source[i] ^ key[i % (sizeof (key) / sizeof
(char))];
}
return output;
}
string GetLocalSerial() {
char line[256];
FILE *mac;
mac = fopen("/sys/class/net/eth0/address", "r");
if (mac == NULL) {
throw "Rete non configurata correttamente";
}
fgets(line, 256, mac);
fclose(mac);
string result = line;
return EncryptDecrypt(result);
}
string GetLicenceSerial() {
//Verifica la presenza di una licenza e ne ottiene il codice.
FILE *licenceFile;
char line[256];
char code[256];
licenceFile = fopen("/boot/EliLicence.txt", "r");
if (licenceFile == NULL) {
throw "File di Licenza mancante";
}
else {
while (fgets(line, 256, licenceFile)) {
if (strncmp(line, "License Code", 12) == 0) {
strcpy(code, strchr(line, ’:’) + 2);
}
}
}
fclose(licenceFile);
string LicenseID = code;
return LicenseID;
}
int Configuration::CheckLicence() {
//Controllo della licenza
time_t rawtime;
35
40. APPENDICE A. CODICE SORGENTE
struct tm * timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
ofstream resfile;
//Crea un log nella partizione FTP contenente lo stato della
licenza
resfile.open("/var/ftp/configuser/LicenceResult.txt",
std::ofstream::app);
try {
this->LicenseID = GetLicenceSerial();
} catch (const char* msg) {
//File di licenza mancante
resfile << asctime(timeinfo) << ": " << msg << std::endl;
resfile.close();
throw msg;
return EXIT_FAILURE;
}
(...) //Calcolo della licenza RpiID tramite i parametri di
sistema
if (RpiID.compare(this->LicenseID) != 0) {
resfile << asctime(timeinfo) << ": Codice di licenza non
corrispondenten";
resfile.close();
std::cerr << "Codice di licenza non corrispondente" <<
std::endl;
throw "Codice di licenza non corrispondente";
return EXIT_FAILURE;
}
resfile << asctime(timeinfo) << ": Controllo licenza superato
correttamenten";
resfile.close();
ofstream licfile;
licfile.open("/var/ftp/configuser/EliLicence.txt");
licfile << this->LicenseID;
licfile.close();
return EXIT_SUCCESS;
}
int Configuration::LoadConfiguration() {
//Tentativo di lettura del file di configurazione.
try {
cfg.readFile("/path/to/configuration.cfg"); //posizione
prevista del file
} catch (const FileIOException &fioex) {
std::cerr << "MISSING CONFIGURATION FILE" << std::endl;
36
41. APPENDICE A. CODICE SORGENTE
return (EXIT_FAILURE);
} catch (const ParseException &pex) {
std::cerr << "Parse error at " << pex.getFile() << ":" <<
pex.getLine()
<< " - " << pex.getError() << std::endl;
return (EXIT_FAILURE);
}
//accesso ai parametri
const Setting& root = cfg.getRoot();
const Setting& param = root["NetworkParam"];
this->version = VERSION;
try {
cfg.lookupValue("authWebServiceURL",
this->authWebServiceURL);
} catch (const SettingNotFoundException &nfex) {
cerr << "No authWebServiceURL setting in configuration
file." << endl;
}
//impostazione configurazioni di rete
try {
SettingNetwork("conf");
SettingNetwork("work");
} catch (SettingNotFoundException &nfex) {
}
return 0;
}
void Configuration::SwitchConnection() {
//Passa dalla connessione di Configurazione a quella Operativa
//In caso di fallimento del controllo di licenza, questo cambio
non avviene e
//il dispositivo rimane connesso alla connessione di
configurazione
std::cout << "Turning OFF Configuration Connection" <<
std::endl;
system("ifdown eth0=eth0-conf");
std::cout << "Turning ON Service Connection" << std::endl;
system("ifup eth0=eth0-work");
}
void Configuration::Dump() {
//Stampa che viene effettuata all’avvio del programma, per
segnalare all’utente che il sistema operativo
std::string localIPAddress = GetLocalIPAddress();
PrinterModule module;
37
42. APPENDICE A. CODICE SORGENTE
module.StartingPrint(this->version, this->MACAddress,
localIPAddress);
}
A.4 Utilizzo Tiny Xml
Il parsing del testo XML ricevuto in input è gestito da un modulo inserito nel
WebClient sviluppato per il dispositivo. Esistono due possibili stringhe che
risultano di interesse. La prima è delimitata dai tag <string>< /string>,
mentre la seconda è una stringa inserita fra i tag <lavoro>< /lavoro>, che
viene inserita come figlio dell’elemento <string>.
(...)
vector<string> eliWebClient::ParseResultXML(std::string resultxml)
{
vector<string> result;
TiXmlDocument doc;
doc.Parse(resultxml.c_str(), 0, TIXML_ENCODING_UTF8);
//Determinazione dell’elemento "padre"
//father -> <string>..</string>
TiXmlElement* father = doc.FirstChildElement("string");
//Determinazioni dei "figli"
//element -> <string><lavoro>...</lavoro></string>
TiXmlNode* firstChildString = doc.FirstChild("string");
TiXmlElement* element;
if (firstChildString) {
element = firstChildString->FirstChildElement();
//<string><lavoro>...</lavoro></string>
if (element)
{
for(element; element;
element=element->NextSiblingElement()){
result.push_back(element->GetText());
}
return result;
}
}
if (father){
result.push_back(father->GetText());
38