4.2 Applicazione mobile
4.2.2 Controllo del nodo
Nella sezione 4.1 è stato descritto come sia possibile programmare un nodo in modo che comunichi con la Companion App. Verrà ora descritto brevemente come vengono inviati i comandi dall’app verso il nodo e come quest’ultimo pos- sa rispondere. La Companion App è stata definita in modo tale da permettere l’invio di comandi standard o comandi personalizzati dall’utente. Per quanto l’applicazione che li riceve sia libera di interpretarli come vuole, descriviamo ora la sintassi e la semantica di questi comandi:
• INIT_NETWORK
È il comando tramite quale si chiede una inizializzazione esplicita della rete. In particolare, il nodo che riceve questo comando dovrebbe cercare di contattare direttamente o indirettamente i nodi da lui raggiungibili e restituire all’app la dimensione attuale della rete.
• GET_TEMP, GET_LIGHT, GET_PRESSURE
Rispettivamente il comando per chiedere la temperatura, il livello di luminosità e la pressione atmosferica, correntemente rilevati dal nodo contattato.
• GET_AVG_TEMP, GET_AVG_LIGHT, GET_AVG_PRESSURE
Il significato è simile a quello dei comandi precedentemente descritti, la sola differenza è che in questo caso si richiede che il valore restituito sia quello medio della rete connessa al nodo contattato. Ad esempio, il nodo che riceve un comando GET_AVG_TEMP dovrà farsi carico di contattare gli altri nodi da lui raggiungibili ed interrogarli a sua volta riguardo la
4.3 Nodo sensore 43
temperatura rilevata da loro e dai loro vicini. Una volta ottenuti tutti i valori, il nodo principale dovrà calcolare il valore medio e restituirlo alla Companion App.
• <CUSTOM>
Nell’ambiente di sviluppo viene data la possibilità di scrivere un program- ma che resti in attesa di un comando definito dall’utente. La Companion App dà coerentemente la possibilità di inviare questo stesso comando personalizzato.
Figura 4.30: La schermata di controllo della Companion App.
Si noti che in ognuno dei casi sopra descritti, è il sistema che fornisce le primitive per l’invio, la ricezione e la definizione del formato dei comandi e dei valori utilizzati. L’utente ha il solo compito di stabilire la semantica di cosa viene trasmesso.
4.3
Nodo sensore
Per abilitare la funzionalità di programmazione dei nodi VirtualSense utiliz- zando la Companion App, è stato necessario modificare il codice sorgente del sistema. L’architettura software dei nodi, già descritta in 3.3, è stratificata perciò si è dovuto scegliere a quale livello intervenire per implementare le fun- zionalità necessarie. Per i motivi che andiamo ora ad illustrare, la scelta è ricaduta su quello più vicino all’hardware.
44 4.3 Nodo sensore
Gli infusi delle applicazioni VirtualSense, per essere eseguiti, devono essere scritti direttamente nella memoria flash del processore MSP430. Un compito che richiede di lavorare con primitive di programmazione messe a disposizione direttamente dal produttore del componente hardware. Per poter programma- re un task, scritto in Java, che dal livello delle applicazioni utente, riuscisse ad interagire con le primitive di scrittura della memoria interna, si sarebbero dovute rendere disponibili queste funzionalità alle applicazioni VirtualSense, aggiungendo alle API della piattaforma svariati metodi Java con implemen- tazione nativa. Inoltre l’implementazione di un modulo software che renda disponibile le funzionalità necessarie per la programmazione dei moduli trami- te la Companion App, non rientra sicuramente nell’insieme delle applicazioni pensate per essere implementate da un utente esterno. In virtù di queste va- lutazioni è stata presa la decisione di scrivere un nuovo processo Contiki che si occupasse dell’installazione di nuove applicazioni.
Il processo in questione non solo consente di programmare un mote Vir- tualSense passando per il collegamento Bluetooth del nodo con un’applicazione esterna, ma permette di farlo mentre i task, precedentemente avviati, continua- no la loro normale esecuzione. Potendo sfruttare le primitive di basso livello fornite dalla piattaforma, una volta che la nuova applicazione è stata installata, può essere avviata senza che il sistema venga riavviato. Il comportamento di questo processo Contiki verrà descritto nei dettagli.
Il processo, chiamato SerialProgrammer, viene avviato dalla funzione virtualsense-main()
la funzione principale del sistema. Da questo momento in poi rimane in ascolto sull’interfaccia seriale, tramite la quale passano anche i dati inviati via Bluetoo- th, in attesa di ricevere il comando che dà il via al protocollo di programmazione over the air.
Una volta che il comando STARTNEWAPP è stato ricevuto, il processo Serial- Programmer alloca un buffer la cui dimensione, che va dai 128 ai 512 byte, viene comunicata all’applicazione in ascolto dall’altro capo del collegamento seriale (tipicamente la Companion App). Quest’ultima, ricevuta la dimensione mas- sima consentita, divide lo stream di byte in tante parti quante sono necessarie e ne inizierà la trasmissione verso il nodo, intervallando ogni invio da qual- che millisecondo di attesa, per permetter al nodo di elaborare le informazioni. Ogni chunk di byte viaggia codificato secondo lo schema base64 ed è seguito da un hash di 8 byte, utile al nodo per verificare che la trasmissione sia stata completata senza errori. Per evitare di allocare un buffer troppo grande, data la scarsità di memoria RAM della piattaforma, i byte appena ricevuti vengono immediatamente scritti nella flash e il buffer liberato per riceverne altri. Prima di essere scritti in memoria i dati vengono decodificati e si verifica che il loro hash corrisponda con quello ricevuto. Quando viene ricevuto il comando di fine trasmissione ENDNEWAPP, il processo SerialProgrammer si occupa di finalizzare la scrittura in memoria.
4.3 Nodo sensore 45
La memoria flash del processore MSP430 è una memoria paginata su cui le azioni di lettura e scrittura devono essere gestite in modo esplicito dal pro- cesso. In particolare la scrittura di un frammento di memoria ne implica la preventiva cancellazione, perciò è necessario mettere in atto delle strategie di ottimizzazione di tale processo. Quello che accade durante il procedimento di trasmissione dei byte è che il processo SerialProgrammer cerca il primo frammento di memoria flash completamente libera e, poiché quella zona del- la memoria non viene utilizzata per nessun altro scopo, scrive i dati ricevuti nei frammenti contigui ad esso, fino al termine dello stream. Il processo di installazione della piattaforma VirtualSense prevede che venga sempre instal- lato almeno un infuso di base, i cui dati vengono scritti nella memoria flash a partire da un’indirizzo noto. Grazie a questa regola, l’area di memoria li- bera necessaria alla scrittura delle nuove applicazioni viene cercata a partire dall’indirizzo al quale si trova il primo infuso, proseguendo per aree contigue, fino a trovare il primo segmento completamente libero. Lo stesso avviene per le installazioni successive di altre applicazioni.
Una volta che il nuovo infuso è stato scritto in memoria, il processo Serial- Programmer si occupa di aggiornare la tabella delle applicazioni, anch’essa me- morizzata nella flash, in un frammento il cui indirizzo è predefinito. La tabella altro non è che una lista di applicazioni in cui ogni elemento della lista, oltre al nome e all’indirizzo di memoria nel quale cercare l’infuso dell’applicazione, contiene anche un ContextId un chiave numerica univoca che identifica ogni applicazione installata nel nodo.
Completato anche l’aggiornamento della tabella delle applicazioni, l’esito del procedimento di installazione viene comunicato alla Companion App tra- mite un comando RESULT:<result> in cui la variabile <result> è 1, in caso di successo oppure 0.
Successivamente la comunicazione con l’applicazione mobile termina, men- tre il processo SerialProgrammer utilizza le primitive della piattaforma per caricare la nuova applicazione nella macchina virtuale Darjeeling ed avviar- ne l’esecuzione. Compiuta anche questa ultima azione, lo stato del processo Contiki viene reinizializzato e l’attesa sulla porta seriale ricomincia.
Durante tutto il processo le applicazioni già avviate sul nodo continuano la loro esecuzione.
Capitolo 5
Caso d’uso
Introduciamo ora un caso d’uso che coinvolge l’intero paradigma di sviluppo e che permette di dimostrarne le funzionalità descritte precedentemente.
5.1
Obiettivo dell’applicazione
5.2 Implementazione 47
Si vuole creare un’applicazione per una WSN VirtualSense che consen- ta di ottenere il valore di luminosità medio fra quelli rilevati dai nodi che compongono la rete.
I nodi della rete dovranno agire diversamente in base al loro ruolo: il nodo principale, detto sink, avrà il compito di ricevere i comandi dalla Companion App e di raccogliere ed elaborare i dati dei nodi della rete; gli altri nodi, chiamati node, dovranno rimanere in attesa di comandi dal sink, rilevare il dato richiesto e inviare il valore indietro. Per semplicità, gestiremo solo collegamenti di tipo single hop fra i nodi e il sink.
5.2
Implementazione
Il primo passo da compiere è l’implementazione delle due applicazioni per il sink e per i nodi.
Sarebbe possibile creare un’unica applicazione installabile su entrambi i tipi di nodo, in cui il comportamento verrebbe discriminato in base al node_id, disponibile tramite le API della piattaforma VirtualSense. Nonostante ciò, dividere l’applicazione in due eseguibili distinti rende il codice più leggibile e più adatto agli scopi di questo capitolo. Inoltre, il supporto al multitasking dei nodi VirtualSense è stato pensato anche per permettere la scrittura di applicazioni distinte per scopi diversi. Ogni applicazione può essere installata insieme alle altre nello stesso nodo, seguendo una logica modulare.
5.2.1 Sink
Le mansioni principali del Sink sono due: rimanere in attesa di comandi dalla Companion App e dar seguito a questi comandi una volta arrivati. Dal mo- mento che l’attesa di comandi è un’azione bloccante, dovendo scrivere lo stesso programma in Java dichiareremmo un thread dedicato allo scopo, di conse- guenza i problemi di concorrenza andrebbero gestiti esplicitamente. Grazie all’astrazione fornita dall’App Maker possiamo non preoccuparci dei thread, e specificare direttamente cosa debba avvenire all’arrivo di un comando dalla Companion App. Nel corpo della funzione principale gestiamo i dati in arrivo e l’invio della risposta con il valore calcolato.
All’avvio il programma inizializza il protocollo di comunicazione usato per interagire con gli altri nodi della rete. La scelta ricade sul protocollo NULL che implementa solamente le primitive di invio e ricezione senza fare operazioni aggiuntive sui messaggi. L’applicazione continua accendendo il terzo led per segnalare attività sul nodo, mentre spegne gli altri due: il primo verrà usato per segnalare l’inizio e la fine della fase di richiesta delle informazioni agli altri nodi. Inoltre il sink dichiara una variabile condivisa, che può essere utilizzata anche negli altri blocchi istanziati sullo stage. Questa variabile servirà a sommare i diversi valori di luminosità ricevuti dai nodi. A seguito di queste prime fasi di inizializzazione, il programma si mette in attesa di messaggi contenenti
48 5.2 Implementazione
dati sulla luminosità dagli altri nodi. Come già specificato si vuole che il sink calcoli il valore medio fra quelli ricevuti dagli altri nodi. Per calcolare la media semplicemente è necessario conoscere il totale dei valori comunicati dai messaggi e il numero di nodi che hanno contribuito. Perciò quando il sink riceve un messaggio di dati dai nodi, quello che fa è controllare che lo stesso nodo non abbia già spedito un altro messaggio per quel turno, se è il primo messaggio ricevuto da quello specifico nodo allora il suo id viene aggiunto alla NodesList – una struttura condivisa fra i vari blocchi, già disponibile nell’App Maker – e il suo valore di luminosità aggiunto al totale.
Figura 5.2: L’applicazione da installare nel nodo Sink.
Vedremo in seguito che i nodi non inviano autonomamente il proprio valo- re di luminosità ma rispondono a richieste esplicite da parte del sink. Perciò dobbiamo definire un meccanismo di interrogazione nell’applicazione che stia- mo strutturando. Lo facciamo all’interno del blocco when receive command GET_AVG_LIGHT do in cui specifichiamo il comportamento dell’app all’arrivo di un comando GET_AVG_LIGHT da parte della Companion App. In questo ca- so il sink reinizializza la variabile condivisa, che contiene la somma dei valori precedentemente ricevuti dai nodi e conseguentemente svuota la lista dei nodi che hanno già inviato la luminosità rilevata. A questo punto il sink dà inizio all’interrogazione vera e propria dei suoi vicini, inviando un messaggio di ri- chiesta della luminosità. Ogni nodo ha a disposizione 8 secondi per rispondere alla richiesta, scaduti i quali il valore inviato non verrà preso in considera- zione. Durante questo lasso di tempo l’esecuzione del sink rimane sospesa, o meglio, rimane sospesa l’esecuzione del thread associato al blocco di ricezione dei comandi dall’app, mentre i messaggi inviati dai nodi vengono normalmente elaborati. Al termine dell’attesa, durante la quale il led 0 rimane acceso, i dati raccolti vengono utilizzati per calcolare la luminosità media, che viene poi inviata indietro alla Companion App.
5.2 Implementazione 49
5.2.2 Node
Il programma eseguito dagli altri nodi VirtualSense ha una struttura più lineare.
Come accade per il sink, il primo blocco è quello che inizializza il protocollo di rete. Questo blocco non deve necessariamente essere il primo fra quelli utilizzati sebbene debba essere comunque istanziato sullo stage nel caso in cui l’applicazione utilizzi le funzionalità di comunicazione fra nodo e nodo. La fase di inizializzazione si conclude dopo aver spento i primi due led ed aver acceso il terzo.
Figura 5.3: L’applicazione da installare negli altri nodi.
La parte centrale dell’applicazione è implementata nel ciclo while, all’inter- no del quale ci sono le istruzioni per la ricezione dei messaggi di interest inviati dal sink. Una volta che un messaggio di richiesta della luminosità è stato ri- cevuto, il dato in questione viene ottenuto per mezzo del sensore a bordo del nodo e inviato al sink con un messaggio. Il primo e il secondo led sono accesi e spenti rispettivamente agli estremi del ciclo while e agli estremi del blocco di istruzioni che gestiscono i messaggi in arrivo.
I codici sorgenti delle due applicazioni, generati tramite i processi descritti, sono riportati integralmente nell’allegato A.
50 5.3 Installazione
5.3
Installazione
Terminato di assemblare i programmi descritti, si procede alla loro compila- zione cliccando il bottone “play” nell’App Maker. Se il processo termina senza errori, viene generata l’immagine di un QR Code contenente l’URI dal quale recuperare il file infuso creato a seguito della compilazione.
Figura 5.4: Il QR Code generato dall’App Maker
A questo punto l’utente che si occupa della installazione dell’applicazione sui vari nodi, avvia la Companion App e scansiona il QR Code. Una volta acquisita l’immagine, questa viene decodificata ottenendo l’URI del file remoto, il quale viene immediatamente scaricato dal server. Terminato il download del file, la Companion App dà la possibilità di installare l’applicazione nei nodi connessi tramite Bluetooth. L’infuso scaricato è ormai memorizzato nell’app e non è necessario ripercorrere tutto il percorso di generazione e scaricamento per ogni nodo. L’utente avvia il processo di installazione cliccando sul bottone dedicato e attende che questo termini.
Una volta installata, l’applicazione inizia subito la sua esecuzione.