UNIVERSITÁ DEGLI STUDI DI MODENA E REGGIO EMILIA Facoltà di Ingegneria – Sede di Modena Corso di Laurea in Ingegneria Informatica
PROGETTO E SVILUPPO DI UN SISTEMA PER LA COMUNICAZIONE WIRELESS TRA ROBOT LEGO
Relatore: Chiar.mo Prof. Franco Zambonelli
Tesi di Laurea di: Francesco Molfese
Correlatore: Ing. Marco Mamei
Anno Accademico 2003-2004
2
SOMMARIO
Introduzione ...........................................................................................................6 1. Strumenti Hardware .........................................................................................9 1.1. Kit Lego Minstorms......................................................................................9 1.2. L’RCX ........................................................................................................10 1.2.1. Architettura dell’RCX ......................................................................12 1.2.2. Programmazione dell’RCX...............................................................14 1.2.3. Firmware alternativi..........................................................................15 1.2.4. Lejos .................................................................................................16 1.2.4.1. Come funziona Lejos ...........................................................16 1.2.4.2. Tiny VM ..............................................................................17 1.2.4.3. leJOS APIs...........................................................................19 1.2.4.3.1. josx.platform.rcx..................................................19 1.2.4.3.2. josx.rcxcomm.......................................................26 1.3. I motori e i sensori ......................................................................................27 1.4. Lego IR-Tower ...........................................................................................29 1.5. iPAQ ...........................................................................................................30 2. Comunicazione Wireless .................................................................................32 2.1. Standard di comunicazione 802.11.............................................................32 2.2. Protocollo di comunicazione Wireless .......................................................35 2.2.1. Principi di funzionamento del protocollo di comunicazione ............36 2.2.2. Classi che compongono l'applicazione .............................................39 2.2.3. Realizzazione dell'interfaccia Wireless ............................................41 2.2.4. Metodo di invio dei comandi dal palmare al PC ..............................46 3. Robot Lego .......................................................................................................48 3.1. Costruzione del Robot ................................................................................48 3.2. Esempio di Robot lego................................................................................51 3.3. Comando a distanza da PC .........................................................................52 4. Conclusioni .......................................................................................................56 4.1. Risultati ottenuti..........................................................................................56 4.2. Possibilità di sviluppi futuri........................................................................57 A - Il codice sorgente ...........................................................................................59
3
INDICE DELLE FIGURE
Figura 1 - Gestione RCX attraverso tecnologia a infrarossi...................................6 Figura 2 - Sistema di comunicazione wireless tra robot lego.................................7 Figura 4 - RCX .....................................................................................................10 Figura 5 - Display e pulsanti.................................................................................11 Figura 6 - Porta a infrarossi dell'RCX ..................................................................11 Figura 7 - Porte di ingresso e uscita .....................................................................11 Figura 8 - Sensore di contatto...............................................................................12 Figura 9 - Sensore di luce .....................................................................................15 Figura 10 - Sensore di temperatura.......................................................................17 Figura 11 - Sensore di rotazione...........................................................................19 Figura 12 - Lego IR Tower...................................................................................27 Figura 13 - Palmare Compaq iPAQ 3600.............................................................27 Figura 14 - Scheda di rete Wireless dell'iPAQ .....................................................28 Figura 15 - Supporto dell'iPAQ con porta seriale.................................................29 Figura 16 - 802.11 Protocol Stack ........................................................................30 Figura 17 - Il nuovo nodo connesso avvisa gli altri nodi della sua presenza .......30 Figura 18 - Il nuovo nodo riceve informazioni sui nodi connessi ........................31 Figura 19 - Un nodo avvisa gli altri componenti della rete che intende disconnettersi .................................................................................................32 Figura 20 - Architettura dell'RCX ........................................................................36 Figura 21 - Schema di esecuzione del codice.......................................................37 Figura 22 - Passaggi per trasferire i file binari sull'RCX .....................................38 Figura 23 - Schema di esecuzione del codice in leJOS ........................................39 Figura 24 - RCX con connessi motori e sensori ...................................................48 Figura 25 - Motore dell'RCX................................................................................49 Figura 26 - Connessione del motore all'RCX.......................................................49 Figura 27 - Barrette mobili e fisse ........................................................................49 Figura 28 - Ingranaggio per sviluppare elevata velocità ......................................50 Figura 29 - Ingranaggio a cinghia.........................................................................50 Figura 30 - Ruote disponibili all'interno del kit Lego Mindstorms ......................50 Figura 31 - Robot lego costruito per testare l'efficienza del sistema di comunicazione wireless .................................................................................51 Figura 32 - Pannello di controllo ..........................................................................52 Figura 33 - Trasferimento di un comando al palmare ..........................................53 Figura 34 - Trasferimento di un comando all’RCX..............................................53 Figura 35 - Telecamera Logitech Quick Cam ......................................................57
4
INDICE DELLE TABELLE
Tabella 1 - Metodi principali della classe Motor ..................................................20 Tabella 2 - Constanti definite dall’interfaccia SensorConstants...........................22 Tabella 3 - Metodi principali della classe Sensor .................................................22 Tabella 4 - Metodi principali della classe Button .................................................23 Tabella 5 - Metodi principali della classe Sound..................................................24 Tabella 6 - Metodi principali della classe Display LCD ......................................25 Tabella 7 - Metodi principali della classe TextLCD.............................................25
5
Introduzione Scopo di questo lavoro di tesi è stato la messa a punto di un sistema di comunicazione di tipo wireless tra robot Lego Mindstorms. L’introduzione da parte della Lego di Mindstorms Robotics Inventions System, un kit di costruzione equipaggiato da un microcontrollore programmabile da PC che può percepire informazioni dall’ambiente in cui si trova (attraverso dei sensori) e reagire attivando degli attuatori (in particolare motori) ha dato la possibilità di ampliare notevolmente le iniziative di istituti e associazioni che hanno la possibilità di realizzare robot “intelligenti” a basso costo. Infatti grazie proprio a questo rapporto qualità/prezzo il kit Lego Mindstorms è stato reso appetibile da parte delle università. Le sue caratteristiche di flessibilità sono notevoli soprattutto se si considerano i numerosi firmware alternativi che consentono la programmazione del robot con vari linguaggi. In particolare, leJOS unisce le caratteristiche del diffusissimo linguaggio Java alla flessibilità del kit Lego Mindstorms. Tutto ciò rende possibile svariate applicazioni, ma ogni singolo robot lego può ricevere ed inviare informazioni via infrarossi solamente con un unico PC alla volta, al quale sarà collegata l’IR-Tower che si occupa della comunicazione infrarossi. Questo è sicuramente un limite non trascurabile. Attraverso gli infrarossi è infatti possibile o scaricare un software sul robot che, una volta messo in esecuzione, piloterà il robot facendo eventualmente uso, oltre ai motori, di tutti i sensori a disposizione oppure inviare comandi istantaneamente al robot.
Figura 1 - Gestione RCX attraverso tecnologia a infrarossi
6
In questo progetto si è voluto quindi ampliare le caratteristiche del Kit Lego Mindstorms con la possibilità di far comunicare tra loro più robot lego e di riuscire ad impartire comandi da postazioni remote a uno o più robot. Per poter rendere possibile questo tipo di attività si è progettato e sviluppato un sistema che si basa sulla comunicazione wireless. Per poter interfacciare un robot lego ad una rete wireless si è deciso di integrarlo con un palmare, il quale, essendo dotato di un scheda di rete wireless fornisce la possibilità di connessione ad una rete. Inoltre il palmare possiede una porta seriale alla quale sarà collegata l’IR-Tower, anch’essa abbinata al kit Lego Mindstorms, che sfruttando la comunicazione infrarossi invierà comandi attraverso il palmare sulla porta infrarossi dell’RCX e viceversa. In questo modo si aggirano i limiti della comunicazione ad infrarossi e si crea una vera e propria rete wireless in cui i vari nodi (palmari associati ai robot, PC, portatili, etc.) possono scambiarsi informazioni. Questo consente quindi di far comunicare tra loro vari robot con la possibilità di scambiarsi dei dati e di interagire tra loro, nonché di comandare e ricevere informazioni attraverso un PC da postazione remota.
Figura 2 - Sistema di comunicazione wireless tra robot lego
In questo elaborato viene affrontato in modo più dettagliato il sistema di comunicazione wireless, il quale è stato progettato e sviluppato sfruttando le potenzialità del linguaggio di programmazione Java. La comunicazione a infrarossi viene affrontata in modo meno esaustivo in quanto oggetto di tesi Francesco Cristian Panessa, con il quale ho collaborato per portare a termine codesto progetto.
7
Argomenti Trattati Nel primo capitolo di questo documento vengono riportati, esaminandoli nei dettagli, i vari strumenti hardware utilizzati per sviluppare questo progetto (contenuto del Kit Lego Mindstorms, iPAQ). Successivamente si esamina l’architettura dell’RCX per poi passare ad affronta in modo esaustivo l’argomento della programmazione vera e propria dell’RCX. In questo capitolo si prende inoltre in considerazione la possibilità di installazione di firmware alternativi e si conclude con lo studio del firmware leJOS, il quale è stato utilizzato per codesto progetto. Nel capitolo 2 ci si concentra sul sistema di comunicazione wireless realizzato. Prima di tutto viene introdotto lo standard di comunicazione 802.11 per poi passare ad esaminare il protocollo di comunicazione wireless. Verranno mostrati i principi di funzionamento del protocollo di comunicazione e successivamente verrà descritta la parte di implementazione del protocollo di comunicazione wireless, esaminando le varie classi Java che compongono l’applicazione. Il capitolo 3 prende in esame come effettuare la costruzione fisica del Robot; viene inoltre mostrato l’esempio di Robot realizzato per testare praticamente l’efficacia di ciò che è stato sviluppato. Infine viene descritta nel dettaglio la possibilità di impartire da PC semplici comandi a distanza al robot sfruttando la rete wireless. Nel quarto e ultimo capitolo vengono descritti i risultati ottenuti in questa attività di progettazione e sviluppo, con un esame approfondito dei possibili sviluppi futuri. Nell’appendice vengono riportati i listati delle classi Java implementate per realizzare questo progetto. Tutto il codice risulta essere opportunamente commentato.
8
Capitolo 1 1. Strumenti Hardware Per poter realizzare il progetto si sono utilizzati alcuni strumenti hardware messi a disposizione dall’Università. In particolare si è fatto uso del Kit Lego Mindstorms, contenente in particolare l’RCX e la Lego IR Tower, grazie al quale si sono realizzati fisicamente i Robot Lego. Inoltre si è utilizzato un palmare iPAQ dotato di una scheda Wireless, per potere essere connesso alla rete LAN. In seguito verranno esaminati nel dettaglio tutti gli strumenti hardware effettivamente utilizzati riportandone le caratteristiche. In questo capitolo verrà esaminata nel dettaglio l’architettura dell’RCX, per poi passare ai suoi metodi di programmazione ponendo particolare attenzione alla possibilità di installazione di vari firmware alternativi. Nell’ultima parte verrà esaminato in modo approfondito il firmware utilizzato per la realizzazione di questo progetto: leJOS.
1.1. Kit Lego Minstorms Il kit commercializzato da Lego Mindstorms contiene tutto il necessario per realizzare robot in grado di muoversi grazie all’uso di motori e di interagire con l’ambiente circostante, attraverso dei sensori, il tutto coordinato da un microcomputer (RCX) programmabile attraverso un qualunque PC. Il kit Robotics Invention System (RIS) contiene una dotazione di pezzi davvero ampia. Si passa, infatti, dai normali mattoncini colorati di numerose forme e misure, ai pezzi tipici della serie Technic (ruote dentate, perni, snodi, pulegge e un differenziale) per un totale di oltre 700 pezzi. A questi, si aggiungono, poi, quei componenti caratteristici della serie Mindstorms: per primo, va sicuramente presentato l’RCX, il microcomputer che rappresenta il componente più prezioso del kit. L’RCX (Robotic Command eXplorer) esteticamente è realizzato in perfetto stile Lego: sia la facciata superiore, che quella inferiore possono, infatti, essere attaccate ad un qualunque altro mattoncino. Inoltre, ancora sulla facciata superiore, sono presenti dei connettori per il collegamento degli ingressi e delle uscite dell’RCX: la prima categoria è rappresentata dai sensori, la seconda dagli attuatori (più comunemente motori) e, per entrambe, sono disponibili tre connettori. Nella confezione del RIS si trovano tre sensori (due Touch Sensor: sensori di contatto, e un Light Sensor: sensore di luce) e due motori con relativi cavetti di collegamento. Nel RIS c’è anche tutto quello che serve per programmare e gestire l’RCX utilizzando un Personal Computer: la IR Tower, cioè un dispositivo che, collegato
9
ad una porta seriale a 9 pin standard o ad una porta USB, è in grado di far comunicare un PC con l’RCX utilizzando raggi infrarossi e un CD contenente il software necessario per la programmazione.
1.2. L’RCX Il componente fondamentale del kit RIS è l’RCX, acronimo di Robotic Command eXplorer. L’RCX dà al robot l’abilità di muoversi in maniera del tutto indipendente, è alimentato da sei batterie da 1.5 Volts, pesa all’incirca 280 grammi e sta sul palmo di una mano.
Figura 3 – RCX
La CPU dell’RCX è rappresentata da un microcontrollore Hitachi H8/3292 a 16 bit, con un clock compreso tra 10 e 16 Mhz. Il microcontrollore contiene 16KB di ROM, e 512 bytes di RAM on-board. L’RCX possiede inoltre 32KB di RAM aggiuntiva. L’RCX è in grado di interpretare ed eseguire degli opcode di un byte che gli vengono forniti da un programma residente in memoria RAM oppure tramite la porta IR. Tutto ciò per mezzo di un firmware che viene scaricato nella memoria RAM alla prima accensione della macchina e che funge da interfaccia tra i programmi utente e il codice di gestione dell’hardware residente nella memoria ROM. Il fatto che il firmware venga posizionato nella memoria RAM rappresenta un notevole punto di forza dell’RCX: infatti, sono disponibili firmware alternativi a quello standard (fornito dalla Lego) che consentono di programmare l’RCX utilizzando linguaggi diversi o diverse modalità di accesso all’hardware (ad esempio, controllandolo direttamente, piuttosto che tramite le funzionalità offerte dal programma residente nella ROM di sistema). Oltre al firmware, la RAM è destinata a contenere programmi e variabili.
10
Sulla facciata superiore, l’RCX è dotato di un piccolo display LCD (che mostra lo stato di funzionamento) e di quattro pulsanti: uno di accensione/spegnimento, uno per la selezione del programma desiderato (può contenere fino a 5 differenti programmi), uno per avviare il programma selezionato e uno che mostra, sul display, il valore letto dai vari sensori. Figura 4 - Display e pulsanti
Sul lato anteriore è posta la porta infrarossi. Va detto che questa non opera in standard IrDA, ma utilizza un protocollo semplificato e, pertanto, è in grado di comunicare soltanto con l’IR Tower in dotazione e non con le porte ad infrarossi standard. Viene utilizzata essenzialmente per il download dei programmi, ma anche per scambiare informazioni tra RCX e PC o addirittura per far comunicare due RCX. Si possono usare varie modalità di comunicazione, una più potente a lungo raggio e una a corto raggio.
Figura 5 - Porta a infrarossi dell'RCX
Porte di Ingresso Nell’RCX sono inoltre presenti tre connettori denominati con i numeri 1, 2, 3 che rappresentano le porte per il collegamento dei sensori. Porte di uscita Le porte di uscita sono invece denominate dalle lettere dell’alfabeto A, B e C. Queste porte sono utilizzate per pilotare attuatori; esse possono essere configurate per fornire una tensione da 0 a 7 Volts.
Figura 6 - Porte di ingresso e uscita
11
1.2.1. Architettura dell’RCX Come già accennato, l'RCX di Lego Mindstorms si basa sul processore Hitachi H8/3292 della famiglia H8/3297. Il chip integra in se una CPU della serie H8/300, memoria e dispositivi di I/O, e un controllore di interruzioni.
Figura 7 - Architettura dell'RCX
CPU H8/300 Si tratta di un microprocessore di tipo RISC con registri accessibili a 16 bit o a 8 bit. I modi di indirizzamento previsti sono: indirizzamento indiretto di registro, indirizzamento assoluto, indirizzamento immediato, indirizzamento relativo al Program Counter e indirizzamento indiretto di memoria. Lo spazio di indirizzamento è a 16 bit (dunque un totale di 64 Kbytes) per dati e programma. Il set di istruzioni è composto da 55 istruzioni, divise in varie categorie: trasferimento dati singoli e a blocchi, operazioni di tipo logico e aritmetico, operazioni di shift, manipolazione di bit, salti, controllo di sistema. On-chip Memory La memoria on-chip si divide in 16 Kbytes di ROM e 512 bytes di RAM. È presente inoltre un register file da 128 bytes, dedicato ai registri di I/O. La MEMORY MAP definisce in quale modo la memoria on-chip ed eventuale memoria esterna aggiuntiva devono essere mappate nello spazio di indirizzamento a 16 bit. Attraverso due bit di un particolare registro (Mode Control Register, MCR, bit 1 e 0 chiamati MD1 e MD0) si possono impostare tre diverse modalità di funzionamento. A seconda della modalità di funzionamento impostata, è possibile utilizzare solo la memoria on-chip o anche eventuali espansioni esterne. In particolare:
12
• • •
in modalità espansa 1 sono disponibili la RAM on-chip e l'espansione esterna di memoria; in modalità espansa 2 sono disponibili la ROM e la RAM on-chip e l'espansione esterna di memoria; in modalità single-chip sono disponibili solo ROM e RAM on-chip.
On-Chip Input/Output Sono presenti, integrati nel chip, vari dispositivi di I/O. Si evidenziano in particolare: • tre tipi di timer (un Free Running Timer a 16 bit, un timer a 8 bit con 2 canali e un watchdog timer utilizzabile anche come timer normale), i quali possono funzionare senza bisogno di circuiteria esterna aggiuntiva; • un'interfaccia per la comunicazione seriale di tipo full-duplex compatibile con lo standard UART, che può operare sia in modalità sincrona sia in modalità asincrona; • un convertitore A/D con risoluzione di 10 bit, con otto canali analogici di ingresso; da notare che il multiplexer dedicato può agire sia in modalità single-shot (un unico campionamento) sia in modalità scan (conversioni continue); • 7 porte di I/O.
Controllore di interruzioni Il controllore di interruzioni integrato gestisce 19 tipi di interruzioni interne e fino a 4 interruzioni esterne (tra cui NMI, Non Maskable Interrupt). Le interruzioni interne sono generate da dispositivi di I/O interni, come la fine di un conteggio di un timer o la fine di una conversione A/D. Le interruzioni esterne sono segnalate su pin del microcontrollore. Tutte le interruzioni, tranne ovviamente NMI, possono essere abilitate e disabilitate individualmente o globalmente.
13
1.2.2. Programmazione dell’RCX Le possibilità di programmazione dell’RCX sono molteplici, grazie ai diversi firmware a disposizione, con ambienti e linguaggi di programmazione tra cui scegliere in base alle proprie esigenze, alle proprie capacità o, semplicemente, per comodità. Il firmware standard offre, seppur con qualche limitazione, interessanti possibilità di programmazione. Primo fra tutti, va descritto il sistema di programmazione “visuale” messo a punto dalla Lego e fornito in dotazione, che unisce ad una indubbia facilità d’uso delle evidenti limitazioni che portano presto un utente mediamente evoluto a passare ad altri sistemi. La programmazione nell’ambiente visuale consiste nel creare, attraverso l’uso di comandi o di procedure definibili, dei programmi composti da task che saranno eseguiti in maniera concorrente dall’RCX. Si possono avere fino a 10 task per ogni programma, che possono essere sia attivi che inattivi. Il metodo di programmazione è davvero semplice: ogni comando impartibile all’RCX è rappresentato da una “tessera” sulla quale compaiono le eventuali opzioni; per creare un programma non c’è altro da fare che giustapporre le varie tessere in catena per indicare il flusso dell’algoritmo. Sono a disposizione un buon numero di comandi predefiniti, ad esempio per l’accensione e lo spegnimento dei motori. Tessere particolari permettono la gestione dei sensori. Sono previsti comandi per il controllo del flusso, come strutture condizionali e di iterazione (sebbene queste ultime non possano essere annidate). Non bisogna però cadere in inganno: l’RCX non resta semplicemente un giocattolo molto sofisticato, ma risulta invece uno strumento di incredibile valore. Permette infatti di progettare robot (prototipi) a basso prezzo con doti di flessibilità incredibili. Inoltre la sua commercializzazione rende ancora più tranquilli sulle sue doti di robustezza. Scuole, università e gruppi di appassionati possono trovare, con questo strumento flessibile, affidabile ed economico, un valido supporto alle loro ricerche. In passato era necessario uno studio ingegneristico approfondito per realizzare robot di questo tipo e quindi molti sforzi e denaro dovevano essere spesi per progetti non direttamente legati al “core” della ricerca. Un altro fattore che ne ha decretato il successo è la sua diffusione internazionale dovuta chiaramente alla notorietà del marchio LEGO. Questo aspetto è molto importante in quanto ha permesso la nascita di numerosi gruppi di appassionati con relativi siti Web che hanno alimentato l’interesse verso il RIS come strumento per la realizzazione e programmazione di robot intelligenti. Si potrebbe desumere che, il kit LEGO Mindstorms sia un valido supporto solamente per realizzare semplici “robottini”, ma analizzando più a fondo le sue caratteristiche e, soprattutto, ciò che è nato attorno a questa piattaforma, ci si può rapidamente convincere delle sue potenzialità di “strumento di sperimentazione”.
14
1.2.3. Firmware alternativi Il firmware standard, come già accennato precedentemente, esegue i programmi forniti dall’utente interpretando una sequenza di bytecode definiti dalla LEGO, in modo analogo a quanto avviene per una virtual machine Java.
Figura 8 - Schema di esecuzione del codice
Nulla vieta, tuttavia, di scaricare sull’RCX un firmware diverso, che ridefinisca completamente il comportamento a basso livello del sistema. Abbandonati i bytecode, la strada è aperta per fare eseguire al controllore Hitachi qualunque funzione si desideri. Nel caso si usi il firmware standard, un apposito traduttore ha il compito di convertire il programma dell’utente (sia esso l’ambiente visuale, Visual Basic, NQC, o PERL) nel bytecode LEGO, che viene poi scaricato ed eseguito sull’RCX. Nel caso in cui si usi un firmware alternativo, invece, l’interazione tra PC e RCX cambia a seconda del nuovo firmware: in generale, sarà possibile trasferire sull’RCX programmi in un formato diverso dall’originale e basati su un bytecode di propria invenzione oppure direttamente codice macchina per la CPU H8/300 (come nel caso del progetto legOS). Il passaggio a un firmware non standard offre diversi vantaggi: innanzitutto, alcuni firmware consentono di assumere il completo controllo dell’hardware (sia nel caso in cui si decida di continuare a utilizzare almeno la ROM di sistema, sia nel caso in cui la si voglia bypassare accedendo direttamente ai registri), ottenendo così un comportamento totalmente personalizzabile dell’RCX per esempio, per effettuare letture particolari dai sensori di ingresso, usare sensori di propria concezione, accedere direttamente alle diverse parti del display, usare timer più precisi, ecc. Un altro vantaggio è poter usare linguaggi comuni, come C e Java, per scrivere programmi per l’RCX senza sottostare alle limitazioni degli ambienti di programmazione tipici di Mindstorms. Inutile dire che questo facilita il porting di eventuali algoritmi di intelligenza artificiale già disponibili e magari testati solo su simulatori. Infine, il passaggio dal bytecode compilato a codice macchina nativo porta innegabili vantaggi in termini di prestazioni, vantaggi dei quali, peraltro, si
15
può spesso fare a meno, considerate le scale temporali piuttosto dilatate entro le quali i robot LEGO hanno l’esigenza di reagire. L’immancabile rovescio della medaglia, a parte ovviamente la maggiore complessità, deriva proprio dalla maggiore libertà di azione permessa dai firmware non standard: la possibilità di accedere direttamente ai registri hardware e a locazioni di memoria critiche pone l’intero sistema in serio pericolo. Il firmware standard, invece, confina l’esecuzione dei programmi utente.
1.2.4. Lejos Il LEGO Java Operating System rappresenta uno dei firmware di ultima concezione per l’RCX Brick. LeJOS permette di eseguire codice Java all’interno dell’RCX e inoltre implementa molte delle caratteristiche del linguaggio, a partire dai thread, array multidimensionali, operazioni in virgola mobile, ricorsione, e modello ad eventi. Oltre a ciò leJOS fornisce delle semplici APIs per la gestione dell’hardware dell’RCX.
1.2.4.1. Come funziona Lejos Per prima cosa è necessario installare una JDK (Java Development Kit) dalla versione 1.1 alla versione 1.4. Successivamente è indispensabile installare correttamente leJOS. L’ultima versione è possibile scaricarla gratuitamente dalla sezione download del sito web: http://www.lejos.org. La parte dove occorre prestare maggiore attenzione è quando si procede a impostare le variabili di ambiente. L’installazione prevede infatti l’aggiunta al path dei comandi usati per compilare i propri programmi Java. Inoltre bisogna impostare, oltre al classpath con le classi che utilizza leJOS, anche una variabile di ambiente RCXTTY ed assegnarli il valore COM1 o USB a seconda che si stia utilizzando la torretta seriale o la torretta con interfaccia USB. Giunti a questo punto, se si è fatto tutto correttamente, è possibile utilizzare leJOS. Per fare eseguire un programma JAVA si procede nel seguente modo: I. Si scrive il programma con estensione *.java; II. Si compila tale file dalla shell di comando, creando il file *.class, utilizzando il comando: lejosc nomefile.java; III. Successivamente è necessario convertire il file *.class nel file binario *.bin, il quale verrà poi trasferito sull’RCX. Per fare questo si utilizza il seguente comando: lejoslink.bat -o nomefile.bin nomefile, il quale procederà a linkare le diverse classi creando un unico file oggetto; IV. A questo punto non rimane che trasferire il file con estensione bin sull’RCX, attraverso la torretta infrarossi, utilizzando il comando: lejosdl.bat nomefile.bin.
16
In realtà i due passaggi di linking e downloading possono essere fati con un unico comando: lejos nomefile. Infatti questo comando si occuperà sia di linkare le diverse classi creando un unico file oggetto sia di trasferirlo sull’RCX dove sarà eseguito alla pressione del tasto RUN.
Figura 9 - Passaggi per trasferire i file binari sull'RCX
1.2.4.2. Tiny VM La JVM usata è una TinyVM, essa è l’analogo dello standard LEGO firmware e come quest’ultimo essa deve essere uplodata sull’RCX per abilitarlo ad eseguire sorgenti in bytecode Java. TinyVM è una versione ‘ristretta’ della standard JVM e alcune limitazioni sono state introdotte per renderla compatibile con l’hardware dell’RCX: in seguito verranno esaminate più in dettaglio. Memoria L’RCX è dotato di un chip di RAM di 32KB, 4KB di questi sono riservati, e quindi non utilizzabili dal programmatore. Rimangono disponibili 28KB di memoria per far girare la JVM e i programmi utente. La dimensione dell’attuale JVM è di circa 16KB, questo vuol dire che lo spazio a disposizione del programmatore è di 12KB. Questo spazio è sufficiente per i programmi di robotica ma diventa limitativo quando, invece, si cerca di realizzare degli algoritmi molto più complessi. Numeri in virgola mobile LeJOS è l’unico firmware per RCX che supporta i numeri in virgola mobile. Questo permette di fare calcoli di funzioni trigonometriche, che sono fondamentali per alcuni algoritmi di robotica.
17
Thread Una delle parti più importanti di leJOS è il supporto della programmazione multiconcorrente. La virtual machine gestisce i thread secondo uno scheduling di tipo preemptive. Il numero massimo di thread che si possono creare in leJOS è di 255, che per applicazioni di robotica risultano davvero tanti. Tuttavia la creazione di un thread usa una quantità di memoria considerevole e di solito il numero massimo di thread istanziabili è cinque. Array leJOS aggiunge un supporto per array multidimensionali. La dimensione massima è di 255 elementi, ma anche in questo caso meglio non esagerare con le dimensioni. Modello ad Eventi leJOS è capace di usare il modello ad eventi di Java, che include listeners e sorgenti di eventi. I possibili sorgenti nel sistema sono essenzialmente tre: timers, pulsanti e sensori. I sensori cambiano di stato quando un nuovo valore viene letto, i pulsanti possono essere solo in due stati (premuto e rilasciato); mentre la gestione del timer avviene come nell’ambiente Java standard. Altre caratteristiche La JVM include anche un supporto per le eccezioni e per la ricorsione, che non può annidarsi per più di 10 livelli. Tuttavia la JVM leJOS a differenza di una JVM standard non ha un Garbage Collector. L’assenza del Garbage Collector si fa molto sentire, poiché non essendoci un costrutto per distruggere oggetti in Java, la memoria dell’RCX rimane occupata da oggetti non referenziati creando problemi di spazio. Questo influisce molto sullo stile di programmazione, poiché impone di limitare al massimo la creazione di oggetti e la definizione di funzioni prediligendo perciò una programmazione procedurale anziché una orientata agli oggetti. I progettisti stanno lavorando ad un mini GC che possa girare su pochi KBs di RAM, ma ancora non sono riusciti ad integrarlo con leJOS. Dall’altra parte il gruppo LEGO sta lavorando ad una nuova versione dell’RCX Brick che monta 64KB di RAM. Con questo upgrade non si dovrebbero più avere problemi di spazio e a qualcuno potrebbe venire in mente di far girare direttamente una Java Micro Edition Virtual Machine standard all’interno dell’RCX Brick riuscendo così ad avere più compatibilità..
18
1.2.4.3. Lejos APIs Nei paragrafi seguenti verranno presentati nel dettaglio il package josx.platform.rcx e il package josx.rcxcomm, che sono stati utilizzati nella realizzazione di questo progetto.
1.2.4.3.1.
josx.platform.rcx
Il sistema leJOS è costruito intorno ad un package di base chiamato josx.platform.rcx che permette un semplice accesso alle porte di ingresso/uscita, timers, pulsanti e sensori dell’RCX. Le classi leJOS non sono inglobate nella JVM, e quindi se vengono usate nel nostro programma devono essere uploadate nell’RCX insieme al nostro codice (vedi figura seguente). Questo diminuisce ancora lo spazio a nostra disposizione costringendoci a lavorare su pochi KBs di RAM.
Figura 10 - Schema di esecuzione del codice in leJOS
Diamo uno sguardo alle API più significative del package josx.platform.rcx.
Motor La classe Motor permette un accesso alle tre porte di uscita dell’RCX per pilotare gli attuatori. Molti dei metodi sono espliciti solo per dei motori, ma la classe permette di pilotare qualsiasi tipo di attuatore. Motor non permette la creazione di istanze; l’accesso ai tre motori, chiamati A B C, avviene quindi tramite variabili di classe (statiche) di Motor. Rispettivamente Motor.A rappresenta l’unica istanza del motore A, Motor.B quella del motore B e infine Motor.C è l’istanza del motore C.
19
Le tre istanze rappresentano quindi dei Singleton; questa soluzione, come vedremo in seguito, è molto usata in tutto l’ambiente leJOS. Andiamo ad analizzare alcuni dei metodi fondamentali di questa classe. Classe Motor Metodo
Descrizione
void forward()
Fa girare il motore in avanti.
void backward()
Fa girare il motore indietro (N.B. la direzione di rotazione dipende da come sono connessi i fili). Ferma il motore quasi istantaneamente e lo tiene in tensione applicando una controforza.
void stop()
Spegne il motore lasciandolo fermare per inerzia.
void flt() Void setPower(int aPower)
char getID()
Imposta il valore della tensione per il motore. I valori di tensione che possono essere applicati vanno da 0V a 7V compresi. Restituisce l’ID del motore che può essere uno tra A, B o C.
Tabella 1 - Metodi principali della classe Motor
Il programma o l’RCX non hanno idea di quale direzione sia “forward” o backward”. Questa discriminazione deriva solamente dalla polarità del voltaggio fornito sull’uscita. Un altro aspetto importante riguarda la potenza con cui questi motori sono attivati; Ci sono metodi che definiscono la potenza in otto diversi power levels (0 = no power | 7 = full power). Ma in realtà le differenze fra questi livelli di potenza sono difficilmente tangibili a meno che i motori non debbano trascinare un grosso carico.
Sensor La classe Sensor definisce tre membri statici, uno per ogni porta di ingresso dell’RCX (connettori grigi): • Sensor.S1 • Sensor.S2 • Sensor.S3 S1, S2, S3 rappresentano i tre sensori che possono essere collegati all’RCX.Sensor.SENSORS è, invece, un vettore di tre elementi che mantiene il riferimento agli stessi oggetti dei membri statici descritti sopra. In questo modo è
20
possibile accedere ai sensori attraverso un indice, ad esempio all’interno di un “ciclo”. Sensor Configuration Per utilizzare un sensore è necessaria una configurazione preliminare: 1. Definire il tipo di sensore (Sensor Type); 2. Definire il modo di lavoro del sensore (Sensor Mode); 3. Attivare il sensore. Il metodo Sensor.setTypeAndMode ragruppa i primi due punti ella configurazione mentre, il metodo Sensor.activate attiva il sensore come descritto nel terzo punto. Per l’RCX è necessario conoscere in anticipo il tipo di sensore collegato, in quanto a seconda del tipo di sensore i segnali ricevuti hanno diverse caratteristiche. A tal proposito è bene conoscere quali tipi di sensori sono predefiniti in lejOS. L’interfaccia SensorConstants definisce le costanti per i differenti tipi di sensori: • SensorConstants.SENSOR_TYPE_TOUCH • SensorConstants.SENSOR_TYPE_LIGHT • SensorConstants.SENSOR_TYPE_ROT • SensorConstants.SENSOR_TYPE_TEMP • SensorConstants.SENSOR_TYPE_RAW Sensor Mode L’RCX riceve i valori provenienti dai sensori e li codifica in 10 bit ottenendo valori discreti in un range fra 0 e 1023 (1023 dec = 1111111111 bin), il cosidetto raw mode restituisce proprio i valori letti in questo modo. Il Sensor Mode descrive all’RCX come interpretare il “raw value”. L’interfaccia SensorConstants definisce le costanti, descritte nella seguente tabella, per i differenti modi di lettura. Sensor mode
Usato per sensori di...
Osservazioni
SENSOR_MODE_BOOL
Contatto
Le letture del sensore vengono codificate in “true” o “false”.
SENSOR_MODE_RAW
Tutti
Non ci sono interpretazioni, è il valore puro letto dal sensore.
SENSOR_MODE_PCT
Tutti
Valore in “raw mode”in percentuale. 0=high raw value; 100=low raw value.
SENSOR_MODE_ANGLE
Rotazione
Somma del numero di segnali ricevuti dal sensore.
SENSOR_MODE_DEGC
Temperatura
Espresso in gradi centigradi.
SENSOR_MODE_DEGF
Temperatura
Espresso in gradi fahrenheit.
21
SENSOR_MODE_EDGE
Contatto
Restituisce la misura del tempo in cui il sensore rimane premuto.
SENSOR_MODE_PULSE
Contatto
Restituisce il numero totale di variazioni da 1 a 0 ricevuti fino ad un determinato momento.
Tabella 2 - Constanti definite dall’interfaccia SensorConstants
Alcuni tipi di sensori (rotazione, luminosità, …), definiti Active Sensor, devono essere alimentati; mentre, i cosiddetti Passive Sensor (temperatura, contatto, …) non necessitano di alimentazione. Per questo motivo in lejOS esistono rispettivamente due metodi adibiti al controllo di questa caratteristica: • void activate(); • void passivate(). La lettura dei sensori può essere invece effettuata attraverso il metodo: Sensor.readValue(). Segue la tabella con i principali metodi della classe Sensor. Classe Sensor Metodo
Descrizione
void activate()
Alcuni tipi di sensori (in gergo chiamati modulanti) per funzionare hanno bisogno di essere alimentati, tra questi troviamo sensori di luce e di rotazione. Il comando activate fornisce dunque l’alimentazione al sensore. Per sensori autoeccitanti (non modulanti) il metodo non ha nessun effetto.
void passivate()
Toglie l’alimentazione al sensore (l’opposto del comando activate).
void setTypeAndMode (int aType, int aMode)
Imposta il tipo e la modalità del sensore. Le costanti sono esplicitate nell’interfaccia SensorConstants.
int readValue()
Legge il valore canonico (determinato dal tipo e modo impostato) del sensore. Ad esempio se il sensore è impostato come sensore di contatto in modalità boolean il valore ritornato sarà uno 0 o un 1.
void setPreviousValue (int aValue)
Imposta un valore da cui partire per effettuare delle misurazioni (utile nei sensori di rotazione).
Tabella 3 - Metodi principali della classe Sensor
22
Button La classe Button permette la gestione dei pulsanti posti vicino al display dell’LCD. Il tasto On/Off non può essere gestito, il comportamento di default quindi non può essere modificato. Per gli altri tasti invece abbiamo dei metodi che ci permettono di cogliere eventuali eventi (pressione o rilascio) del tasto e quindi personalizzare il comportamento del nostro robot subordinandolo allo stato dei tasti. I principali metodi della classe sono riportati qui di seguito. Classe Button Metodo
Descrizione
void addButtonListener (ButtonListener aListener)
Permette a dei Listener di registrarsi per eventi.
void waitForPressAndRelease()
Ferma l’esecuzione del programma in attesa che il tasto specifico venga premuto e rilasciato.
void stop()
Ferma il motore quasi istantaneamente e lo tiene in tensione applicando una controforza.
Tabella 4 - Metodi principali della classe Button
Sound La classe Sound permette l’accesso allo speaker contenuto nell’RCX Brick. E’ possibile eseguire dei suoni specificandone la frequenza e la durata, oppure eseguire dei suoni standard tipo il beep e il doppio beep.
Classe Sound Metodo
Descrizione
void beep()
Emette un beep.
void twoBeeps()
Emette due beep.
void beepSequence()
Emette un arpeggio discendente.
void buzz()
Emette un beep lungo e grave.
void systemSound (boolean aQueued, int aCode)
Riassume i metodi precedenti attraverso l’impostazione degli argomenti.
23
void playTone (int aFrequency, int aDuration)
Emette un suono di durata e frequenza impostate dall’utente.
Tabella 5 - Metodi principali della classe Sound
Il metodo systemSound in particolare emette un suono predefinito da LeJOS. Qui di seguito sono descritto i codici corrispondenti al parametro aCode. Acode 0 1 2 3 4 5
Suono risultante Beep breve Beep doppio Arpeggio discendente Arpeggio ascendente Beep lungo e grave Arpeggio ascendente veloce
Il parametro aQueued è di tipo boolean, se impostato a “true” inserisce il suono richiesto in una coda (che può contenere al massimo 8 elementi); se “false”, invece, il suono verrà emesso solamente se il Beeper interno non è occupato. Gli altri metodi della classe non fanno altro che richiamare il suddetto con un particolare codice. Il metodo playTone , al contrario dei precedenti permette di fare qualcosa di più. Emette un tono, che ha per frequenza aFrequency e per durata aDuration. Le frequenze udibili rientrano nel range 31-2100 Hertz; la durata, invece, è espressa in centesimi di secondo. LejOS accetta una durata massima di 256 e cioè 2,56 secondi.
Display LCD Ci sono due diverse classi per controllare il display LCD: • LCD • TextLCD Classe LCD La classe “LCD” possiede metodi per controllare i segmenti del display o per scrivere numeri. Di seguito l’elenco dei metodi: Classe Display LCD
24
Metodo
Descrizione
Void showNumber(int aValue)
Scrive aValue sul display. Non ha bisogno di rinfresco. aValue = [0..9999]
void showProgramNumber(int aValue)
Scrive un digit nel Program section del display. Non ha bisogno di rinfresco. aValue = [0..9]
void setNumber(int aCode, int aValue, int aPoint)
Imposta un numero da scrivere sul display, che viene emesso solo alla chiamata di refresh().
void refresh()
Esegue il rinfresco del display. Viene utilizzato per fare in modo che alcuni metodi abbiano effetto.
void setSegment(int aCode)
Attiva un segmento del display.
void clearSegment (int aCode)
Cancella un segmento dal display. Richiede il rinfresco.
void clear()
Cancella tutto il display. Richiede il rinfresco.
Tabella 6 - Metodi principali della classe Display LCD
Classe TextLCD La classe TextLCD dispone solo di tre metodi per il trattamento di testo sul display LCD. Classe TextLCD Metodo
Descrizione
void print(String str)
Scrive una stringa sul display. Non è necessario rinfresco.
Void print(char[] text)
Scrive i primi 5 caratteri di un vettore di char. Non è necessario rinfresco.
void printChar(char ch, int pos)
Scrive un carattere ‘ch’ nella posizione ‘pos’ del display partendo da destra. Non è necessario rinfresco.
Tabella 7 - Metodi principali della classe TextLCD
Serial Mette a disposizione delle API per la comunicazione attraverso la porta a raggi infrarossi dell’RCX. Essa permette di mandare informazioni a un PC o verso un altro RCX. L’unico tipo di dato gestito dalla classe Serial è il byte, quindi se volessimo mandare degli interi non possiamo utilizzare questa classe.
25
ROM La classe ROM permette di accedere ad alcune routines immagazzinate nel firmware di basso livello. La classe ha soltanto due metodi, uno restituisce il valore della carica della batteria mentre l’altro è usato per resettare il timer dell’RCX.
1.2.4.3.2.
josx.rcxcomm
Sfruttando il package visto precedentemente se ne sono sviluppati altri che espandono ulteriormente le potenzialità della piattaforma leJOS. Uno di questi è il josx.rcxcomm; questo pacchetto permette la comunicazione tra RCX e qualsiasi altro dispositivo a raggi infrarossi con un modello a strati simile allo standard OSI. Questo package infatti permette la creazione di porte e riesce a gestire comunicazioni point-to-point tra vari dispositivi. All’attuale stato dell’arte esistono essenzialmente tre tipi di porte che possono essere create, tutte e tre definiscono un’interfaccia simile alla java.net.Socket ma al loro interno lavorano in modo diverso: • RCXPort: Crea una socket affidabile (tutti i pacchetti vanno a buon fine) basata su una comunicazione a basso livello (LLC), non supporta la possibilità di pilotare più RCX dalla stessa sorgente IR (addressing), i pacchetti sono mandati in broadcast. • RCXLNPPort: E’ una versione dell’RCXPort che usa il Lego Network Protocol (LNP). Il risultato è una socket che assicura l’integrità dei pacchetti (ma non viene assicurato che ogni pacchetto inviato venga effettivamente ricevuto) e non supporta l’addressing. • RCXLNPAddressingPort: Questa porta è un’estensione della precedente, che aggiungendo uno strato al protocollo riesce a gestire bene anche l’addressing. Tuttavia il protocollo continua a non rilevare eventuali errori di trasmissione. Il protocollo LNP ha il vantaggio di integrarsi bene con l’ambiente Java. Questa soluzione tuttavia risulta poco robusta, poiché il protocollo non assicura la ricezione dei pacchetti inviati. L’idea potrebbe essere di sovraccaricare il protocollo con un altro strato per così riuscire a gestire il problema dell’affidabilità, ma bisogna stare attenti a non rendere il tutto pesante sul lato RCX. D’altronde il protocollo LNP è ancora in via di sviluppo; nello stato attuale, ad esempio, non è possibile creare più porte nello stesso RCX, rendendone l’uso ancora poco pratico. Al package josx.rcxcomm che è inglobato nella piattaforma leJOS è associato un package da includere nella applicazione lato PC chiamato pcrcxcomm. La libreria fornisce una versione delle porte sopra citate che però hanno senso sul lato PC.
26
1.3. I motori e i sensori I due motori forniti sono definiti “ad alta efficienza”: infatti, internamente, possiedono un volano che permette, una volta avviato il motore, di continuare a farlo girare con basso consumo energetico. Attraverso l’RCX si possono pilotare i motori impostandone il verso di rotazione e la modalità di funzionamento che può essere “On”, “Off” o “Floating”. La differenza tra queste due ultime modalità sta nel fatto che se un motore, che si trova nello stato “On”, viene portato nello stato “Off” cessa immediatamente di girare; mentre, se viene portato nello stato “Floating”, ha la possibilità di girare liberamente per inerzia, fino a fermarsi del tutto. I sensori di contatto sono dei normali mattoncini , che hanno una dimensione di 3x2 unità fondamentali delle costruzioni Lego, che presentano da un lato un piccolo pulsante: pertanto, possono fornire all’RCX soltanto due valori corrispondenti agli stati di pulsante “premuto” o “non premuto” (boolean). Questo tipo di sensore può essere utilizzato, ad esempio, per riconoscere quando il robot incontra un ostacolo. Molto interessante è il sensore di luce, che è in grado di riconoscere variazioni di luminosità. Anch’esso è un mattoncino, di dimensione 4x2 unità fondamentali delle costruzioni Lego, dotato su uno dei lati corti di un LED emettitore a luce rossa e di un fotodiodo in grado di fornire all’RCX un valore proporzionale all’intensità della luce ricevuta. Per default questo sensore lavora in percentage mode, vale a dire che fornisce un valore compreso tra 0 e 100, con i valori più alti che stanno a significare un’intensità luminosa maggiore. Come per altri tipi di sensori, il funzionamento può essere controllato in modo che, ad esempio, funzioni in raw mode (fornendo valori compresi tra 0 e 1023), in boolean mode oppure che sia in grado di contare le transizioni da 0 a 1 e/o viceversa. Per quanto riguarda il sensore di luce, si è rilevato che l’uso più efficace che se ne può fare è per l’individuazione di sorgenti luminose, mentre può essere usato con qualche limitazione per il riconoscimento dei colori: il fotodiodo è infatti in grado, grazie al LED presente sul sensore stesso, di individuare la quantità di luce riflessa dall’oggetto che ha di fronte. La limitazione consiste nell’influenza che può avere la luce ambientale sulle rilevazioni del sensore e sulla limitata risoluzione di misura. Con qualche accorgimento, si possono creare facilmente robot in grado di distinguere colori chiari da colori scuri.
Figura 11 – Sensore di contatto
Figura 12 - Sensore di luce
27
In commercio esistono altri due tipi di sensori che non vengono forniti all’interno del kit Lego Mindstorms: un sensore di temperatura e un sensore di rotazione. Il sensore di temperatura è un sensore di tipo passivo (cioè non alimentato, funziona per variazione della termoresistenza) ed è in grado di rilevare temperature comprese tra -20°C e + 70°C. Il sensore di rotazione è un sensore di tipo attivo, ed è in grado di misurare le rotazioni con una precisione di 22.5°. Può risultare molto utile in quanto rende possibile far compiere per due volte consecutive le stesse distanze; ciò non è possibile in assenza di tale sensore poiché i motori molto imprecisi.
Figura 13 - Sensore di temperatura Figura 14 - Sensore di rotazione
28
1.4. Lego IR-Tower La Lego IR-Tower è un dispositivo interamente hardware che si connette al PC attraverso l'interfaccia RS-232 (la comune porta seriale), il cui scopo principale è il caricamento del software sull'RCX. Anche se teoricamente si tratta semplicemente di un convertitore da ambiente wired ad ambiente wireless (in particolare luce infrarossa), alcune sue particolarità hanno portato a difficoltà di implementazione non indifferenti. Si fa riferimento in particolare allo stesso fenomeno presente nella porta infrarossa dell'RCX: ogni bit inviato dalla sezione trasmittente viene anche ricevuto dalla sezione ricevente. Il dispositivo ha anche un'altra fastidiosa caratteristica: mentre la trasmissione è sempre abilitata (o meglio, viene abilitata automaticamente all'atto della scrittura sulla porta seriale), la ricezione è attiva solo per un breve tempo dopo l'ultima trasmissione (si parla di un paio di secondi), per cui è stato necessario un sistema per evitare lo spegnimento della Tower e mantenere la ricezione abilitata. Per il resto, il dispositivo gestisce automaticamente tutti gli aspetti di più basso livello legati alla ricetrasmissione infrarossa. Inoltre per poter ottenere il giusto bit rate è sufficiente impostare la porta seriale RS-232 per avere la velocità richiesta di 2400 bps.
Figura 15 - Lego IR Tower
29
1.5. iPAQ Il palmare utilizzato durante l’attività progettuale è un Compaq iPAQ H3600 Series Pocket PC con le seguenti caratteristiche tecniche: • PROCESSORE 206-MHz Intel Strong ARM SA – 1110 Processore 32-bit RISC; • MEMORIA 64-MB SDRAM, 16-MB di memoria Flash ROM; • DISPLAY 4096 colori (12 bit), touch-sensitive, reflective thin film transistor (TFT), Liquid crystal display (LCD). Sul palmare Compaq iPAQ 3600 è installato il sistema operativo open source Familiar Linux, una distribuzione concepita appositamente per palmari, derivata dalla più famosa Debian Linux.
Figura 16 - Palmare Compaq iPAQ 3600
Il palmare è inoltre dotato di una schede di rete Wireless della 3Com che gli consente di accedere alla wireless LAN.
Figura 17 - Scheda di rete Wireless dell'iPAQ
30
Un’altra importante caratteristica che ci ha spinto ad integrare l’RCX con il palmare è che quest’ultimo è dotato di una porta seriale alla quale è possibile collegare la Lego IR Tower con interfaccia seriale. Per poter collegare correttamente la Lego IR Tower al palmare è però necessario utilizzare un cavo cross seriale.
Figura 18 - Supporto dell'iPAQ con porta seriale
Sul palmare è inoltre installata una Java Virtual Machine compatibile con il sistema operativo Familiar Linux. Grazie alla portabilità di Java è stato quindi possibile compilare il codice Java sul PC e trasferire sull’iPAQ solamente i file *.class i quali vengono eseguiti sull’architettura ARM senza alcun tipo di problema. Per trasferire attraverso la rete wireless i file compilati di Java sul palmare si è utilizzato il software WinSCP.
31
Capitolo 2 2. Comunicazione Wireless Il mercato offre numerosi standard di comunicazione di tipo wireless con approccio a layer, i quali si basano su due tecnologie: • ricetrasmissione ad onde radio; • ricetrasmissione infrarossa. La tecnologia a infrarossi ha caratteristiche tecniche che non le consentono un ampio utilizzo, mentre la tecnologia ad onde radio possiede buone caratteristiche che l’hanno portata ad essere la tecnologia wireless per eccellenza. Il mondo dei computer negli ultimi anni sta cercando di sfruttare sempre più queste connessioni senza fili ed in questo ambito si è progettato e sviluppato un sistema di comunicazione wireless tra robot lego. In seguito verrà introdotto lo standard IEEE-802.11 per poi passare ad esaminare nel dettaglio il protocollo di comunicazione realizzato utilizzando le potenzialità di Java.
2.1
Standard di comunicazione 802.11
Lo standard IEEE 802.11, nato nel 1997, è il primo standard di riferimento per le trasmissioni basate su radiofrequenza. Lo standard, che si occupa dei livelli fisico e MAC dello standard ISO/OSI, permette trasferimenti di dati con velocità di 1 o 2 Mbps, sfruttando le frequenze radio a 2.4 GHz o l'infrarosso. Lo standard originale prevede, per il livello fisico, tre metodi di trasmissione, uno basato su infrarosso e due su radiofrequenza, con tecniche di modulazione diverse. Abbiamo inoltre numerose evoluzioni, tra le quali ricordiamo il supplemento 802.11b, che può arrivare ad un data rate di 11 Mbps (mantenendo comunque la compatibilità con lo standard precedente), e il supplemento 802.11a, la cui velocità di punta è di 54 Mbps. Inoltre il supplemento 802.11e introduce alcune novità sul livello MAC e fornisce servizi simili a quelli richiesti per un sistema Real-Time. Nella figura seguente si può vedere la situazione complessiva.
Figura 19 - 802.11 Protocol Stack
32
L'obiettivo dello standard 802.11 è quello di definire i livelli fisico e MAC di una rete LAN di tipo Wireless (WLAN), i cui vantaggi rispetto ad una LAN wired classica sono evidenti. Nello standard proposto dalla IEEE per le reti wireless, vi sono due modi differenti per configurare una rete: • Ad-hoc; • Infrastructure. Nella rete ad-hoc, i vari punti di accesso si incontrano per formare una rete temporanea e istantanea. Non vi è alcuna struttura nella rete, non ci sono punti fissi e, solitamente, ogni nodo è in grado di comunicare con tutti gli altri nodi. Se non regolato, questo traffico completamente libero può dare origine a un collision rate elevato. Nella modalità Infrastructure si mettono in evidenza due tipi di dispositivi: • access point; • wireless terminal. Lo scopo degli access point è quello di fare da bridge della WLAN verso la normale rete cablata, in modo da connettere i wireless terminals alla rete globale. L'idea centrale dello standard è quella di dividere l'area di interesse in celle, ognuna delle quali servita da almeno un access point dedicato allo smistamento del traffico. Gli access point comunicano la loro presenza attraverso l'invio di messaggi particolari detti beacon frame, inviati a cadenze regolari. I terminali grazie ai beacon frame sono a conoscenza della presenza degli access point e si collegano all'access point, dedicato alla cella in cui si trovano, per ottenerne i servizi. Quando la cella di riferimento cambia (ancora una volta il terminale se ne accorge grazie alla ricezione di un beacon frame proveniente da un access point diverso), il terminale dovrà disconnettersi dall'access point attuale e connettersi con l'access point della nuova cella. A livello fisico, lo standard originale prevede tre possibilità distinte: • trasmissione ad onde radio con Spread Spectrum System (SSS, sistema di dispersione dello spettro) di tipo Frequency Hopping (FH); • trasmissione ad onde radio con tecnica SSS di tipo Direct Sequenze (DS); • trasmissione infrarossa Le tecniche di SSS citate occupano una banda maggiore del necessario ma garantiscono ricezioni migliori, oltre a permettere più trasmissioni contemporanee su frequenze diverse. Inoltre questa tecnologia consente a più utenti di condividere lo stesso insieme di frequenze a patto che i due segnali non si sovrappongano mai sullo stesso canale, ma saltino ogni volta su canali distinti. La tecnica DS consiste nel rappresentare ogni bit di messaggio come una sequenza ridondante di bit a frequenza maggiore. Di fatto la banda del segnale anche in questo caso è più larga, ma se le interferenze non colpiscono tutta la sequenza ridondante, ogni bit potrà essere ricostruito, e questo offre una maggiore resistenza alle interferenze. Il livello fisico basato sull'infrarosso di fatto non ha avuto diffusione, per alcuni problemi che lo accompagnano. Da notare ad esempio che il range di
33
funzionamento è limitato da ostacoli come i muri, che le radiazioni infrarosse non riescono a superare; inoltre questo porta ad interferenze dovute alla riflessione. Il risultato di questo è che è molto difficile trovare in commercio dispositivi ad infrarossi che siano perfettamente uniformati allo standard, il che significa ad esempio che si dovrebbero adottare soluzioni proprietarie, con evidenti difficoltà per future espansioni. È per questo motivo che in pratica lo standard 802.11 è conosciuto come standard per la trasmissione ad onde radio. Le evoluzioni proposte introducono altre versioni del livello fisico. Tra le altre ricordiamo la versione 802.11a, che sfrutta un sistema detto Orthogonal Frequency Division Multiplexing (OFDM) grazie a cui si arriva ad un bit rate di 54 Mbps, e la versione 802.11b, il cui livello fisico è sostanzialmente una evoluzione del DSSS, chiamato High-Rate DSSS (HR/DSSS). Diverso è il discorso per la versione 802.11e, in quanto le differenze con l'originale influenzano il livello MAC e non il livello fisico. Il livello MAC è costituito da un insieme di protocolli atti ad imporre un ordinamento nell’accesso al mezzo condiviso. Lo standard 802.11 specifica che il protocollo da usare sia il CSMA/CA (Carrier Sense Multiple Access / Collision Avoidance). Il protocollo CSMA/CD non è, invece, adatto alle trasmissioni wireless, dal momento che non è possibile per un nodo ascoltare il mezzo mentre si trasmette un messaggio. Quando occorre trasmettere il pacchetto, il nodo trasmittente prima invia un pacchetto molto breve con cui notifica la necessità di inviare (Ready To Send), contenete informazioni sulla lunghezza del pacchetto vero e proprio. Se il nodo ricevente sente l’RTS, risponde con un pacchetto altrettanto breve (Clear To Send) con cui abilita il nodo trasmittente ad effettuare la trasmissione. Dopo questo scambio, il nodo trasmittente invia il pacchetto. Quando il pacchetto viene ricevuto con successo dal nodo ricevente (i controlli di consistenza sono effettuati tramite un Cyclic Redundancy Check), esso trasmette un pacchetto di ACK. Tale scambio è necessario per evitare il problema del nodo nascosto: supponiamo che i nodi A e C non siano posizionati nella stessa zona di copertura (ovvero A e C non possono comunicare); allora nel caso A voglia trasmettere a B un pacchetto, A dovrebbe fare innanzitutto il sensing del canale. Se A si limitasse a ciò, potrebbe non avvertire un’eventuale trasmissione tra B e C che invaliderebbe di fatto la sua trasmissione. Con l’introduzione della fase di set-up, invece, B non risponderebbe ad A con il pacchetto CTS, impedendogli di fatto una trasmissione inutile. Tra le principali caratteristiche dello standard 802.11 originale vale la pena di sottolineare alcuni aspetti: • il protocollo è in grado di variare la velocità di trasmissione per adattarsi al canale; • la velocità è elevata (e le evoluzioni innalzano molto questa velocità); • nell'ottica di ridurre la congestione, il protocollo sceglie automaticamente la banda di trasmissione meno occupata, e l'access point più adatto; per facilitare la mobilità ed il roaming, è possibile creare celle parzialmente sovrapposte; • le potenze in gioco sono basse, nell'ordine di alcune decine di mW; • la copertura va dai 30-50 metri nei casi peggiori fino a 70-100 metri e oltre in buone condizioni.
34
2.2
Protocollo di comunicazione Wireless
Verrà ora spiegato in modo dettagliato come si è progettato il protocollo di comunicazione wireless. Prima di tutto verranno esposti i principi di funzionamento di questo protocollo in generale e successivamente si scenderà nei dettagli implementativi, utilizzati con il linguaggio di programmazione Java, esaminando le varie classi che compongono l’applicazione. Si precisa che codesto è un semplice ma efficace protocollo che potrà in futuro essere modificato per essere reso maggiormente affidabile. Per realizzare questo sistema di comunicazione wireless tra Robot Lego Mindstorms si è deciso di utilizzare come linguaggio di programmazione Java. Questa decisione è dovuta dal fatto che questo linguaggio di programmazione consente di ottenere una notevole portabilità creando, in fase di compilazione, dei Java bytecode indipendenti dalla piattaforma. L’obbiettivo che si è deciso di perseguire è stato infatti quello di realizzare un sistema general purpose che permetta di far comunicare tra loro dispositivi diversi, come PC e palmari, dotati di una Java Virtual Machine che le consenta di interpretare il formato compilato di Java (bytecode). Inoltre si è presa questa decisione in quanto Java mette a disposizione una serie di strumenti utili per effettuare la programmazione di rete (come le socket) e permette di scrivere facilmente codice multithreaded.
35
2.2.1 Principi di funzionamento del protocollo di comunicazione L’idea che sta alla base di questo protocollo di comunicazione è che in ogni istante ogni nodo della rete deve essere a conoscenza di tutti i nodi connessi alla rete in quel determinato momento. Infatti ogni nodo deve avere la possibilità di consultare tutti i nodi connessi in quel istante. Per poter fare questo è necessario uno scambio di messaggi di configurazione tra i vari nodi della rete. Per prima cosa la regola di base è che un nodo appena si connette spedisce ad un indirizzo di multicast un messaggio di configurazione contenente il seguente testo: “Sono nuovo”. Questo messaggio verrà ricevuto da tutti i nodi già connessi in quel determinato istante.
Figura 20 - Il nuovo nodo connesso avvisa gli altri nodi della sua presenza
Tutti coloro che hanno ricevuto codesto messaggio non fanno altro che memorizzare le informazioni del nuovo nodo che si è connesso e di rispondergli con un messaggio contenente il testo: “Ci sono anche io“. In questo modo ogni nodo informa il nodo che si è appena connesso della sua presenza. Da parte sua quindi, il nuovo nodo riceverà una serie di pacchetti contenente il messaggio “Ci
36
sono anche io” e, dopo aver identificato il nodo che glielo ha mandato, procederà a salvare le informazioni relative a quel preciso nodo.
Figura 21 - Il nuovo nodo riceve informazioni sui nodi connessi
Nel caso invece in cui un nodo voglia disconnettersi dovrà spedire a tutti i nodi connessi in quel momento un pacchetto contenente come messaggio “Bye Bye” e poi terminare effettivamente la connessione. Utilizzando questo metodo i nodi che riceveranno il pacchetto con messaggio “Bye Bye” sapranno che il mittente di quel messaggio non è più connesso e quindi provvederanno alla cancellazione di esso dalla lista dei nodi connessi.
37
Figura 22 - Un nodo avvisa gli altri componenti della rete che intende disconnettersi
Ogni nodo deve quindi stare in ascolto, di eventuali messaggi di configurazione della rete, durante tutta la durata della connessione. Questi messaggi vengono facilmente riconosciuti in quanto sono preceduti da uno specifico header. Oltre a questi messaggi di configurazione della rete c’è naturalmente anche la possibilità di inviare pacchetti contenenti delle informazioni di scambio tra i vari nodi della rete. Questi pacchetti hanno un header diverso che li differenzia dai vari messaggi di configurazione della rete. Un nodo può decidere di inviare un pacchetto contenente informazioni di scambio ad un unico nodo oppure a tutti i nodi connessi. Questo protocollo è stato sviluppato utilizzando il linguaggio di programmazione Java; si passa ora ad esaminare nel dettaglio l’effettiva implementazione delle varie classi che compongono l’applicazione.
38
2.2.2 Classi che compongono l’applicazione Per rendere più semplice la comunicazione wireless tra i vari nodi della rete e per poi inoltrare i vari comandi all’RCX tramite la tecnologia a infrarossi, una volta che questi sono stati ricevuti dal palmare, si è deciso di strutturare tutto il software utilizzando varie classi che mettono a disposizione una serie di metodi necessari per il conseguimento di questo obbiettivo. Nella figura seguente verranno riportate le classi che compongono l’applicazione con indicati i rispettivi attributi e metodi.
Figura 23 - Classi che compongono l'applicazione
39
La classe ComWireless implementa l’interfaccia Wireless ed è la classe principale che mette a disposizione i vari metodi per consentire la comunicazione wireless tra i vari nodi della rete. La classe Pacchetto fornisce un utile metodo che riceve come parametri l’header del pacchetto, il messaggio e la parte conclusiva del pacchetto e restituisce la stringa con la concatenazione dei vari parametri separati dal delimitatore. La classe Rete contiene il main del programma che gira sui vari nodi della rete ed in particolare nel main che gira sul PC viene visualizzato il pannello di controllo che contiene i vari pulsanti per far muovere il robot lego. La classe Com_IR_Ipaq implementa l’interfaccia Infrared_Ipaq_Side che consente al palmare di gestire la comunicazione infrarossi. Allo stesso modo la classe Com_IR_RCX implementa l’interfaccia Infrared_RCX_Side mettendo a disposizione i metodi per gestire la comunicazione a infrarossi dell’RCX. Infine la classe RCXmain contiene il main del programma che gira sull’RCX il quale consente di far reagire il robot in base ai numeri di comando ricevuti sulla porta a infrarossi. In seguito verranno esaminate nel dettaglio queste classi che compongono l’applicazione.
40
2.2.3 Realizzazione dell’interfaccia Wireless Per consentire ai vari nodi della rete di comunicare tra loro si è realizzata l’interfaccia Wireless, la quale contiene una serie di metodi che permettono sia di gestire la configurazione della rete, sia di inviare e ricevere comandi. In seguito si riporterà tale interfaccia e si illustreranno i metodi che sono stati implementati, spiegando come essi operano e riportando le parti di codice Java maggiormente significative. public interface Wireless { // dichiarazione dei metodi astratti /* Metodo che consente di effettuare la connessione di un nodo alla rete passando come parametri il nome che intende assumere e la sua tipologia */ public void connect(String nomenodo,String tipo); /* Metodo che consente ad un nodo di disconnettersi dalla rete */ public void disconnect(); /* Metodo che permette di inviare un comando ad un unico nodo della rete identificato con nomenodo */ public void send_one(String comando,String nomenodo); /* Metodo con il quale è possibile inviare un comando a tutti i nodi connessi */ public void send_all(String comando); /* Metodo che consente di ricevere un comando */ public String receive(); /* Metodo che verifica se un nodo è connesso */ public boolean isConnected(String nomenodo); /* Metodo che restituisce tutti i nodi connessi */ public String whoIsConnected(); }
Per quanto riguarda il metodo public void connect(String nomenodo,String tipo), si è stabilito a priori un numero fisso della porta attraverso la quale avverrà la comunicazione. Si è inoltre creato un indirizzo per il multicast nel seguente modo: try { gruppo = InetAddress.getByName("225.0.1.0"); } catch (UnknownHostException e) {
41
System.out.println(e); System.exit(0); } Come spiegato in precedenza ogni nodo che si connette deve inviare in multicast il messaggio che indica che si è connesso un nuovo nodo. Per fare questo si è fatto uso del seguente codice: /* Creo la stringa -> ConfigIP##Sono nuovo: nomeTipo##FineIP utilizzando un apposito metodo della classe Pacchetto */ stringaPacchetto= packet.creaPacchetto("ConfigIP","Sono nuovo:"+nomeTipo,"FineIP"); // Apro la comunicazione in multicast sulla porta indicata nella variabile 'porta' canaleConnessioni = new MulticastSocket(portaConnessioni); canaleConnessioni.joinGroup(gruppo); /* Appena mi connetto invio in multicast il pacchetto che contiene la stringa-> ConfigIP##Sono nuovo: nomeTipo##FineIP */ DatagramPacket pack = new DatagramPacket(stringaPacchetto.getBytes(),stringaPacchetto.length(),gruppo,po rtaConnessioni); canaleConnessioni.send(pack); Successivamente si entra in un ciclo infinito all’interno del quale ci si mette in ascolto di eventuali pacchetti ricevuti. byte[] buf = new byte[1000]; DatagramPacket recv = new DatagramPacket(buf, buf.length); // Mi metto in ascolto sul canale aperto canaleConnessioni.receive(recv); String ricevuto=new String(recv.getData()); // Salvo in una stringa i dati ricevuti Nel momento in cui si riceve un pacchetto, viene salvato il contenuto in una stringa e si controlla il messaggio che si è ricevuto. Possono verificarsi le seguenti situazioni: • Se si riceve un pacchetto con il messaggio “Sono nuovo” e non è quello da me spedito, si procede a recuperare l’indirizzo IP da cui si è ricevuto tale pacchetto, lo si concatena al nome e al tipo del nodo specificato nel messaggio e si va a salvare il tutto in un array di stringhe contenente tutti i nodi connessi. Inoltre si procede ad inviare a tale nodo un messaggio “Ci sono anche io” per informarlo della mia presenza; • Se il pacchetto ricevuto contiene il messaggio “Ci sono anche io” non si fa altro che recuperare l’indirizzo IP da cui si è ricevuto tale pacchetto, lo si concatena al nome e al tipo del nodo specificato nel messaggio e si salva il tutto nell’array dei nodi connessi; • Quando si riceve un pacchetto con il messaggio “Bye Bye” si controlla da chi è stato inviato; se il mittente è il nodo stesso si esce dal ciclo infinito e si termina la connessione chiudendo il canale di comunicazione. In caso
42
contrario si recupera l’indirizzo IP da cui si è ricevuto tale messaggio e dopo averlo concatenato al nome e al tipo del nodo specificato nel messaggio si procede a cercarlo ed eliminarlo dall’array. Per recuperare l’indirizzo IP del nodo da cui si è ricevuto il pacchetto si usa il metodo recv.getAddress(). Il metodo void disconnect() non fa altro che spedire a tutti i nodi connessi il pacchetto contenente il messaggio “Bye Bye”; sarà poi all’interno del metodo connect che si procederà a terminare la connessione chiudendo il canale di comunicazione quando si riceve tale messaggio da se stessi. Il metodo void send_one(String comando,String nomenodo) per prima cosa scorre l’array per verificare se il nodo al quale si intende inviare il messaggio è effettivamente connesso, dopodichè se ciò è vero procede all’invio del pacchetto contenente il comando passato come parametro. Per fare ciò si è scritto il seguente codice: // Creo la stringa da inviare -> Comando##COMANDO##Fine Comando stringaPacchettoSend= pacchettoSend.creaPacchetto("Comando",""+comando,"Fine Comando"); try { DatagramPacket packSendOne = new DatagramPacket(stringaPacchettoSend.getBytes(),stringaPacchetto Send.length(),IPdest,portaComandi); // Invio la stringa all'indirizzo IP di 'nomenodo' canaleComandi.send(packSendOne); } catch (IOException e) { System.out.println(e); System.exit(0); } Il metodo void send_all(String comando) è simile al metodo send_one, ma anzichè inviare il comando ad un unico nodo lo invia a tutti i nodi connessi alla rete. Il metodo String receive() permette di ricevere un comando. Infatti entra in un ciclo infinito in cui viene fatta la receive e ci rimane finché non riceve un pacchetto che abbia come header la stringa “Comando” (che indica che contiene effettivamente un comando). Si riporta il codice maggiormente significativo: while(ascolta){ try { byte[] buf = new byte[1000]; DatagramPacket ricevuto = new DatagramPacket(buf, buf.length); // Mi metto in ascolto sul canale aperto canaleComandi.receive(ricevuto); // Salvo in una stringa i dati ricevuti stringaRicevuto=new String(ricevuto.getData()); messHeader=stringaRicevuto.substring(0,7);
43
/* Se ho ricevuto un pacchetto con header “Comando” non mi torno a mettere in ascolto e restituisco il comando ricevuto */ if(messHeader.compareTo("Comando")==0){ ascolta=false; }// Fine If } catch (IOException e) { System.out.println(e); System.exit(0); } } // Fine While Il metodo boolean isConnected(String nomenodo), non fa altro che controllare all’interno dell’array dei nodi connessi se è presente un nodo con il nome uguale a quello passato come parametro. In caso affermativo ritorna true, altrimenti ritorna false. Il metodo String whoIsConnected() restituisce una stringa contenente tutte le informazioni dei nodi connessi contenute all’interno dell’array. A questo punto un nodo, dopo aver effettuato la connessione, ha una serie di metodi a disposizione che gli consentono di impartire (e ricevere) comandi agli (dagli) altri nodi connessi. Grazie a questo meccanismo, unito alle potenzialità di leJOS, sarà possibile impartire comandi ad uno o più robot connessi alla rete tramite iPAQ. Infatti, essendo Java un linguaggio portabile, codesti metodi potranno essere utilizzati anche sull’iPAQ, il quale connesso alla rete potrà ricevere (ed inviare) comandi da (ad) un altro nodo; a sua volta il palmare si occuperà poi di inviare tramite l’IR Tower tali comandi al robot che reagirà di conseguenza. Per fare in modo che tutto funzioni correttamente è necessario fare uso dei Thread. Infatti la prima cosa da fare è invocare il metodo void connect(String nomenodo, String tipo) che consente ad un nodo di collegarsi alla rete; questo metodo deve però essere in esecuzione per tutta la durata della connessione, in quanto deve essere in grado di recuperare e gestire le informazioni degli altri nodi che si connettono o disconnettono dalla rete. Contemporaneamente un nodo deve avere la possibilità di inviare comandi agli altri nodi, di mettersi in ascolto di eventuali pacchetti ad esso inviati e di verificare la presenza di altri nodi connessi. Il codice con cui si è realizzata la classe contenente il main del programma con la gestione dei Thread è il seguente. public class Rete implements Runnable{ private static ComWireless comunicazione; // Implemento il metodo run() contenente il corpo del thread public void run(){ comunicazione.connect("Portatile","Master"); } public static void main(String args[]){ comunicazione = new ComWireless (); // Creo il Thread principale
44
Thread ct = Thread.currentThread(); // Creao un oggetto della classe che implementa Runnable Rete comunica = new Rete(); /* Creo il Thread che si occupa della connessione; per fare questo creo un oggetto della classe Thread utilizzando il costruttore il quale prevede come unico parametro un oggetto della classe che implementa Runnable */ Thread threadConnessione = new Thread (comunica); // Faccio partire la connessione attivando il relativo Thread threadConnessione.start(); /* Faccio fermare il Thread principale per 4 secondi in modo che si riesca a completare il Thread di connessione */ try { Thread.sleep(4000); } catch (InterruptedException e) { System.out.println("Thread principale interrotto"); } // Invio i comandi attraverso i metodi sopra mostrati comunicazione.send_one("Vai avanti","Robot"); comunicazione.send_all("Stop"); … … … // Mi disconnetto comunicazione.disconnect(); } }
45
2.2.4 Metodo di invio dei comandi dal palmare all’RCX A questo punto i nodi connessi alla rete possono scambiarsi dei comandi, utilizzando gli specifici metodi dell’interfaccia Wireless. Quello che si è poi realizzato è di inoltrare i comandi che vengono ricevuti dal palmare all’RCX, tramite la comunicazione a infrarossi, in modo che esso reagisca di conseguenza attivando o spegnendo i motori del Robot Lego. Per capire come si è realizzato il tutto si esaminerà l’interfaccia Infrared_Ipaq_side e l‘interfaccia Infrared_RCX_side.
Interfaccia Infrared_Ipaq_side public interface Infrared_Ipaq_side { // dichiarazione dei metodi astratti public void sendToRCX(int messaggio); public int receiveFromRCX(); }
// dichiarazione di interfaccia
Come si può notare sono presenti due metodi: • void sendToRCX(int messaggio); questo metodo si aspetta come parametro un numero intero che, sfruttando il metodo writeInt(int) della classe DataOutputStream, verrà poi inviato tramite infrarossi; • int receiveFromRCX(); si mette in ascolto finché sulla Lego IR-Tower non viene ricevuto un intero trasmesso dalla porta infrarossi dell’RCX; per fare questo viene utilizzato il metodo dis.readInt() della classe DataInputStream. Allo stesso modo deve venir data la possibilità all’RCX di poter ricevere ed inviare, tramite infrarossi, dei numeri di comando.
Interfaccia Infrared_RCX_side public interface Infrared_RCX_side { // dichiarazione dei metodi astratti public void sendToIpaq(int messaggio); public int receiveFromIpaq(); }
// dichiarazione di interfaccia
I due metodi che sono stati implementati sono: • void sendToIpaq(int messaggio); si aspetta come parametro un numero intero che utilizzando il metodo writeInt(int) della classe DataOutputStream lo si invierà tramite la porta a infrarossi dell’RCX;
46
•
int receiveFromIpaq(); con questo metodo l’RCX si mette in ascolto finché non riceve sulla sua porta a infrarossi un intero trasmesso dal palmare attraverso la Lego IR-Tower; per fare questo viene utilizzato il metodo dis.readInt() della classe DataInputStream.
Come si può notare dal codice delle interfacce sopra riportato, anziché trasferire le stringhe di comando ricevute dal palmare si sono dovuti trasferire dei numeri interi. Questo lo si è dovuto fare in quanto, compilando il codice che gira sull’RCX con Lejos, non era presente la classe BufferedReader; notando ciò si è provato ad aggiungerla ma sorgevano problemi di incompatibilità tra le recenti versioni della JDK (Java Development Kit) e Lejos. Per questo motivo si è deciso di inviare dei numeri di comando che non necessitano di utilizzare la classe BufferedReader. Infatti nel momento in cui il palmare riceve una stringa di comando, inviatagli tramite la rete wireless, esso provvede ad inviare il numero di comando al quale viene attribuito il significato della stringa ricevuta; si è dovuto quindi stabilire a priori le corrispondenze tra le varie stringhe di comando e i numeri. Di conseguenza l’RCX quando riceve un numero di comando dal palmare sa come deve reagire.
47
Capitolo 3 3.Robot Lego Utilizzando il contenuto del Kit Lego Mindstorms è molto facile costruire robot in quanto la dotazione di pezzi è notevole; quindi per prima cosa è necessario avere ben chiaro ciò che si desidera far fare al robot, dopodichè la costruzione sulla base delle caratteristiche volute è semplice ed ognuno può dar svago alla propria fantasia. In questo capitolo verranno presi in considerazioni i pezzi fondamentali per la costruzione di un robot lego; inoltre verrà mostrato il robot che è stato realizzato nell’ambito di questo progetto e il pannello di controllo che consente di impartire semplici comandi al robot tramite PC da postazione remota.
3.1
Costruzione del Robot
Ogni robot, oltre alla parte fondamentale cioè l’RCX, deve essere realizzato utilizzando una serie di pezzi lego indispensabili per potergli far compiere determinati movimenti o azioni. All’RCX possono infatti essere collegati i motori, che si occupano di far muovere il robot, e i sensori che catturano informazioni dall’ambiente in cui si trova il robot.
Figura 24 - RCX con connessi motori e sensori
48
Il motore funziona in corrente continua (DC) ed ha dimensioni relativamente ridotte; inoltre ha la possibilità di ruotare in entrambe i versi. Al motore vengono solitamente collegate delle ruote, ma tipicamente è necessario un ingranaggio meccanico per cambiarne il rapporto Figura 25 - Motore dell'RCX
Questa è una parte sicuramente indispensabile per il robot ed è molto semplice da utilizzare. Infatti e sufficiente collegarlo, attraverso un apposito connettore, a una delle tre porte di uscita (contrassegnate con le lettere A,B,C) dell’RCX come mostrato nella seguente figura.
Figura 26 - Connessione del motore all'RCX
Anche i sensori vengono collegati all’RCX tramite analoghi connettori, ma a differenza dei motori è necessario fare attenzione e collegarli alle porte di input, contrassegnate con i numeri (1,2,3).
All’interno del kit sono presenti piccoli pezzi di colore grigio che vanno usati per interconnettere barrette forate che vogliano essere mobili. Mentre i piccoli pezzi di colore nero vanno utilizzati in tutti i casi in cui non si presuppone movimento tra le parti interconnesse.
Figura 27 - Barrette mobili e fisse
Dalle prove pratiche effettuate è emerso che l’utilizzo corretto degli ingranaggi è abbastanza complicato. Infatti da essi dipendono tutti gli spostamenti del robot all’interno dell’ambiente. Si possono costruire una serie di ingranaggi differenti,
49
ognuno con caratteristiche e proprietà diverse (sviluppo di velocità, coppia). In generale non esiste quindi l’ingranaggio più efficiente, ma il tutto dipende dalle caratteristiche che deve avere il robot. Nelle figure seguenti vengono mostrati due tipi differenti di ingranaggi.
Figura 28 - Ingranaggio per sviluppare elevata velocità
Figura 29 - Ingranaggio a cinghia
All’interno del kit Lego Mindstorms troviamo anche una serie di ruote con diverse caratteristiche in base alle quali scegliere le più adatte per le proprie esigenze.
Figura 30 - Ruote disponibili all'interno del kit Lego Mindstorms
Le ruote di tipo A hanno un’ottima aderenza sulle superfici lisce. Inoltre sono caratterizzate da basse velocità ed ottima coppia. Quelle di tipo B hanno una buona aderenza su molte superfici, raggiungono alte velocità ma hanno una coppia bassa. Le ruote di tipo C sono caratterizzate dall’avere un’ottima aderenza su superfici lisce, velocità media e coppia media. Le tipologie di ruote D ed E hanno una scarsa aderenza su superfici lisce, ma una buona aderenza su sterrato. La differenza è che le ruote di tipo D hanno una velocità media e una coppia media, mentre quelle di tipo E sono più lente ma con un’ottima coppia.
50
3.2
Esempio di Robot lego
Nell’ambito di questo progetto si è realizzato un robot lego per testare le funzionalità e l’efficienza del protocollo di comunicazione wireless. Questo robot ha solamente le capacità di muoversi avanti e indietro e di girare a destra e a sinistra. Non è dotato di alcun tipo di sensore, ma essi potranno essere facilmente aggiunti in applicazioni future.
Figura 31 - Robot lego costruito per testare l'efficienza del sistema di comunicazione wireless
La struttura del robot è stata realizzata in modo molto robusto in quanto il peso che deve trasportare è considerevole. Infatti il robot deve trasportare il palmare, il supporto del palmare dotato di un porta seriale e l’IR Tower alla quale è collegata. Le difficoltà maggiori si sono riscontrate nel riuscire a inglobare nel robot tutti i cavi. Infatti si ricorda che la lego IR Tower, oltre ad essere dotata di un cavo molto lungo, deve essere collegata alla presa seriale dell’iPAQ tramite un ulteriore cavo cross. La torretta a infrarossi la si è facilmente incastrata al lego grazie alla sua predisposizione, mentre si è dovuto trovare un modo alternativo per fissare il palmare al robot. Considerando tutto ciò che deve essere presente nel robot si è realizzato un lego di dimensioni abbastanza considerevoli, che le impediscono di raggiungere elevate velocità e di svoltare agilmente.
51
Il robot è a trazione anteriore e si muove grazie all’ausilio di due motori. Nelle ruote posteriori è stato costruito un differenziale per agevolare la svolta del robot a destra e a sinistra. Nella parte posteriore sono state messe due grosse ruote per parte in modo da rendere il robot ancora più robusto, mentre nella parte anteriore si è deciso di dotarlo di ruote larghe per garantire una buona aderenza sulle superfici lisce . Per realizzare questo robot, date le sue notevoli dimensioni, è stato necessario utilizzare i pezzi di due kit Lego Mindstorms.
3.3
Comando a distanza da PC
La possibilità di impartire comandi ad uno o più robot da un PC connesso alla rete è sicuramente molto importante. Per agevolare maggiormente questo compito e per rendere il tutto più veloce è stato creato in Java un semplice pannello di controllo. Il codice che consente di visualizzare il pannello lo si inserisce all’interno del main: // Visualizzo il pannello che consente di impartire comandi al robot ControlPanel p = new ControlPanel(comunicazione); p.show(); Il pannello è formato da una matrice 3 x 3 e contiene 5 pulsanti ai quali è assegnata un’azione differente esplicita nel nome.
Figura 32 - Pannello di controllo
Per prima cosa, quando viene mandato in esecuzione il programma, viene automaticamente instaurata la connessione alla rete wireless. Nel momento in cui l’utente preme il pulsante “Avanti” il PC provvede ad inviare tramite la rete wireless un pacchetto di comando contenente il messaggio “Vai avanti” all’iPAQ (che è identificato in rete con il nome Ipaq).
52
Comando (Vai avanti, fermati, gira a destra, …)
Figura 33 - Trasferimento di un comando al palmare
Esso appena riceve tale pacchetto interpreta il comando e spedisce tramite la lego IR Tower il numero 1 sulla porta infrarossi dell’RCX. Quest’ultimo è a conoscenza che nel momento in cui riceve il numero 1 deve avviare entrambe i motori in avanti. In questo modo il robot procede in avanti finché non riceve un ulteriore comando dal PC.
Invio comando via infrarossi
Figura 34 - Trasferimento di un comando all’RCX
53
CODICE PC public class AvantiReact implements ActionListener{ public void actionPerformed(ActionEvent ev){ // Stampo a video la scritta "Avanti !" System.out.println("Avanti !"); // Spedisco al nodo di nome Ipaq il comando "Vai avanti" wireless.send_one("Vai avanti","Ipaq"); } }
CODICE iPAQ Com_IR_Ipaq comIpaq=new Com_IR_Ipaq(); // Mi metto in ascolto di un comando comandoricevuto=comunicazione.receive(); // Stampo a video il comando ricevuto System.out.println("Ho ricevuto il comando:"+comandoricevuto); if (comandoricevuto.equals("Vai avanti")) comIpaq.sendToRCX(1);
CODICE RCX Com_IR_RCX comRCX=new Com_IR_RCX(); comandoRicevuto=comRCX.receiveFromIpaq(); if (comandoRicevuto==1){ // Scrittura sul diplay TextLCD.print("Avanti"); // Attivo in avanti i due motori connessi alla porta A e C Motor.A.forward(); Motor.C.forward(); }
Nel caso in cui venga premuto il pulsante “Indietro” il PC invia all’iPAQ il comando “Vai indietro”. A questo punto il palmare spedisce il numero 2, tramite infrarossi, all’RCX il quale attiva entrambe i motori indietro. Quando viene premuto il bottone “Destra” il procedimento è analogo: il PC invia il pacchetto contenente il comando “Vai a destra”, il palmare dopo aver ricevuto tale comando spedisce all’RCX il numero 3 e di conseguenza l’RCX avvia solamente il motore di sinistra. Nel momento in cui viene premuto il pulsante “Sinistra” il PC invia il pacchetto contenente il comando “Vai a sinistra” e l’iPAQ dopo aver ricevuto tale comando spedisce all’RCX il numero 4. In questo modo l’RCX avvia solamente il motore di destra consentendo la rotazione del robot verso sinistra.
54
Se l’utente preme il bottone “Stop” di colore rosso al centro del pannello viene inviato al palmare il comando “Fermati”. Il palmare alla ricezione di tale comando procede ad inviare tramite infrarossi il numero 0, che viene interpretato dall’RCX il quale provvede a fermare entrambe i motori. Ogni qualvolta l’RCX riceve un numero, oltre ad agire sui motori, scrive anche sul suo display l’azione che sta effettuando.
55
4. Conclusioni L’obiettivo fondamentale che ci si era posti all’inizio di questa attività di progettazione e sviluppo era di creare un sistema di comunicazione wireless tra robot lego. In questo capitolo si esamineranno i risultati ottenuti e si prenderanno in considerazione le numerose possibilità di sviluppo future.
4.1
Risultati ottenuti
In questo lavoro di tesi è stata presentata la progettazione e la realizzazione di un sistema che consente la comunicazione tra più unità robotiche Lego. Nell’ambito di questo progetto si sono raggiunti gli scopi prefissati in modo soddisfacente. Infatti lo “strato” di comunicazione wireless progettato risulta essere completamente trasparente agli RCX che, a differenza dell’utilizzo della sola tecnologia a infrarossi, formano una rete di comunicazione completamente connessa (dal punto di vista della comunicazione). Questo consentirà di creare in futuro numerose applicazioni basate sulla cooperazione di più unità RCX mobili che potranno interagire scambiandosi dati ed eseguendo comandi impartiti da postazioni remote. Il protocollo di comunicazione progettato e realizzato non è sicuramente immune da difetti ma si è giunti ad una struttura semplice e funzionante che lascia spazio a miglioramenti futuri sia in termini di affidabilità che di efficienza; le numerose prove effettuate hanno comunque dimostrato che il tutto funziona correttamente. Molto interessante ed istruttiva è stata la possibilità di realizzare fisicamente questi robot lego utilizzando il kit Lego Mindstorms e di vedere come reagivano ai comandi impartiti. Nonostante il robot costruito in questa attività progettuale sia semplice dal punto di vista delle funzionalità, in futuro sarà possibile prevedere costruzioni di robot più complessi; codesto robot è stato sviluppato solo per verificare l’effettivo e corretto funzionamento del protocollo di comunicazione. Questo lavoro di tesi ha consentito di ampliare notevolmente le conoscenze relative al linguaggio di programmazione Java utilizzando tecniche di programmazione avanzate. Inoltre questo progetto ha consentito di imparare ad utilizzare il palmare dotato del sistema operativo Familiar Linux. Le maggiori difficoltà durante la fase realizzazione si sono sicuramente riscontrate nell’istallazione di leJOS in quanto è necessario porre particolare attenzione alla configurazione delle variabili di ambiente. Inoltre può accadere che alcune versioni di leJOS non siano pienamente compatibili con le ultime versioni della JDK (Java Development Kit). In conclusione il sistema di comunicazione wireless tra robot lego, qui realizzato, deve essere considerato come un utile strumento da cui partire per progettare e realizzare applicazioni, anche complesse, che prevedono lo scambio di dati tra unità robotiche e un’iterazione tra di esse.
56
4.2
Possibilità di sviluppi futuri
Questa attività di progettazione e sviluppo di un sistema di comunicazione wireless tra robot lego ha sicuramente notevoli possibilità di subire ulteriori miglioramenti. Essa infatti deve essere considerata come la base di un sistema su cui poter implementare applicazioni che permettono l’iterazione tra più unità robotiche lego. In futuro si può pensare di sviluppare una metodo che, basandosi sul sistema di comunicazione wireless qui realizzato, consenta a una serie di robot lego lo scambio di informazioni, permettendo ad essi di svolgere una attività coordinata quale può essere lo studio di un ampio ambiente oppure la trasmissione ad un PC remoto di una serie di rilevazioni effettuate. Infatti occorre considerare che i robot lego, come si è esaminato nei capitoli precedenti, possono essere dotati di una serie di sensori di luminosità, contatto, temperatura e rotazione che gli consentono di captare una serie di informazioni dall’ambiente che li circonda e quindi di reagire di conseguenza. Basandosi su questo sistema di comunicazione wi-fi si può pensare inoltre di realizzare una serie di applicazioni che consentono di comandare uno o più robot lego dal Web e di ricevere dalla parte opposta informazioni, che vengono rilevate dai sensori, quali la presenza di un ostacolo o la temperatura; l’utente potrà quindi reagire di conseguenza impartendo diversi comandi al robot. L’utente potrà inoltre decidere a quale robot, tra una serie disponibili, inviare una determinata richiesta. Occorre inoltre considerare che si può utilizzare maggiormente la presenza del palmare installato sul robot. Infatti esso, oltre ad avere il compito di accedere alla rete wi-fi, è dotato anche di un microfono. Una interessante applicazione potrebbe quindi essere la realizzazione di un robot “spia”, che trasmetta ad un PC remoto l’audio dell’ambiente in cui si trova il robot. Tra i vari accessori messi a disposizione dalla Lego per questi robot troviamo anche una normale telecamera Logitech Quick Cam che permette di essere comodamente fissata ai mattoncini Lego.
Figura 35 - Telecamera Logitech Quick Cam
57
In realtà questa telecamera USB è dotata di un lungo cavo in modo da poter essere fissata sul robot e normalmente essa dovrebbe essere installa sul PC. Infatti le elaborazioni riguardanti la telecamera dovrebbero essere eseguite su PC, mentre verrebbero trasmesse le operazioni da effettuare tramite la torretta IR. Tutto ciò è alquanto limitativo. Un importante ed interessante applicazione che si potrebbe sviluppare in futuro è installare questa telecamera sull’iPAQ; in questo modo il robot potrà spostarsi senza i limiti imposti dalla lunghezza del cavo USB. Le immagini catturate dalla telecamera potranno poi essere trasmesse, tramite rete wifi, ad un PC. Questo permetterebbe ad un utente situato in una postazione remota di guidare il robot sulla base delle immagini che riceve. Si conclude dicendo che le potenzialità del kit Lego Mindstorms unite al progetto sviluppato di un sistema di comunicazione wireless tra più unità robotiche lego consente di realizzare una serie di applicazioni interessanti dal punto di vista didattico e sperimentale. Infatti quelli precedentemente esposti sono solo delle ipotesi sui possibili sviluppi futuri, i quali probabilmente troveranno ampio spazio nelle attività di ricerca e sviluppo.
58
Appendice A A - Il codice sorgente In seguito verrĂ riportato il codice sorgente Java commentato, delle varie classi sviluppate per realizzare il sistema di comunicazione wireless e per sfruttarlo impartendo ad un robot dei semplici comandi.
™ Classe Wireless /** Nome File: Wireless.java * Interfaccia Wireless con i relativi metodi che verrano implementati nella classe ComWireless @author Francesco Molfese @version 1.0, 16/09/2004 */ // dichiarazione di interfaccia public interface Wireless { // dichiarazione dei metodi astratti /* Metodo che consente di effettuare la connessione di un nodo alla rete passando come parametri il nome che intende assumere e la sua tipologia */ public void connect(String nomenodo,String tipo); /* Metodo che consente ad un nodo di disconnettersi dalla rete */ public void disconnect(); /* Metodo che permette di inviare un comando ad un unico nodo della rete identificato con nomenodo */ public void send_one(String comando,String nomenodo); /* Metodo con il quale è possibile inviare un comando a tutti i nodi connessi */ public void send_all(String comando); /* Metodo che consente di ricevere un comando */ public String receive(); /* Metodo che verifica se un nodo è connesso */ public boolean isConnected(String nomenodo); /* Metodo che restiruisce tutti i nodi connessi */ public String whoIsConnected(); }
59
™ Classe ComWireless import java.io.*; import java.net.*; import java.util.*; import java.lang.*; /** Nome File: ComWireless.java * Classe ComWireless con i relativi attributi e metodi @author Francesco Molfese @version 1.0, 18/09/2004 */ public class ComWireless implements Wireless { // Variabili private della classe ComWireless private String nomenodo; private String tipo; private String messaggio; private String nodiconnessi[]; private int portaConnessioni; private int portaComandi; private int numnodi; // Variabile che tiene il conto dei nodi connessi private boolean attendi; private boolean connesso; private InetAddress gruppo; private MulticastSocket canaleComandi; private MulticastSocket canaleConnessioni; /** Metodo Costruttore che mi permette di inizializzare le variabili della classe */ public ComWireless(){ nomenodo=""; tipo=""; messaggio=""; // Imposta la variabile portaConnessioni dando un valore prestabilito portaConnessioni = 3000; // Imposta la variabile portaComandi dando un valore prestabilito portaComandi = 3453; numnodi=0; connesso=false; attendi=true; // Creazione indirizzo per multicast delle connessioni try { gruppo = InetAddress.getByName("225.0.1.0"); } catch (UnknownHostException e) { System.out.println(e); System.exit(0); } // Creazione del canale di comunicazione try{ canaleComandi = new MulticastSocket(portaComandi); } catch (IOException e) {
60
System.out.println(e); System.exit(0);} } // Fine costruttore /**Metodo che mi permette di effettuare la connessione di un nodo @param nomenodo indica il nome del nodo che intende connettersi @param tipo indica il ruolo del nodo che intende connettersi */ public void connect(String nomenodo,String tipo){ Pacchetto packet = new Pacchetto(); String stringaPacchetto = new String(""); String stringaPacchettoRisp = new String(""); String mess = new String(""); String messrisp = new String(""); String messBye = new String(""); String messNomeTipo = new String(""); String messNomeTiporisp = new String(""); String ipNomeTipo = new String(""); String nomeTipo= new String(nomenodo + "/" + tipo); // Trovo l'Indirizzo IP locale che è associato al nome della macchina e inserisco // il solo indirizzo IP nella variabile 'soloIP' String soloIP=""; InetAddress mioHost= null; try { mioHost = InetAddress.getLocalHost(); } catch (UnknownHostException e) { System.out.println("Impossibile recuperare il mio IP"); System.exit(0); } // Converto l'indirizzo IP e il nome della macchina in stringa String mioHostString = mioHost.toString(); // Tengo solo l'indirizzo IP e lo metto nella variabile stringa 'soloIP' StringTokenizer st = new StringTokenizer(mioHostString,"/"); while (st.hasMoreTokens()) { soloIP=st.nextToken(); } // Creo la stringa -> ConfigIP##Sono nuovo: nomeTipo##FineIP stringaPacchetto=packet.creaPacchetto("ConfigIP","Sono nuovo: "+nomeTipo,"FineIP"); try { // Apro la comunicazione in multicast sulla porta indicata nella variabile 'porta' canaleConnessioni = new MulticastSocket(portaConnessioni); canaleConnessioni.joinGroup(gruppo); connesso=true; // Appena mi connetto invio in multicast il pacchetto che contiene la stringa-> ConfigIP##Sono nuovo: nomeTipo##FineIP DatagramPacket pack = new DatagramPacket(stringaPacchetto.getBytes(),stringaPacchetto.length(),gruppo,portaConnessioni); canaleConnessioni.send(pack); // Array di stringhe che contiene le informazioni su tutti i nodi connessi nodiconnessi=new String[100]; // Nella prima posizione dell'array salvo le informazioni relative al mio nodo nodiconnessi[0]="/"+soloIP+"/"+nomeTipo;
61
numnodi=1; while(attendi) { try { byte[] buf = new byte[1000]; DatagramPacket recv = new DatagramPacket(buf, buf.length); // Mi metto in ascolto sul canale aperto canaleConnessioni.receive(recv); // Salvo in una stringa i dati ricevuti String ricevuto=new String(recv.getData()); // Dalla stringa ricevuta isolo solo la parte del messaggio: 'Sono nuovo','Ci sono anche io', 'Bye Bye' mess=ricevuto.substring(10, 20); messrisp=ricevuto.substring(10, 26); messBye=ricevuto.substring(10, 17); // Converto l'indirizzo IP e il nome della macchina da cui ho ricevuto il pacchetto in stringa InetAddress hostRicevuto=recv.getAddress(); String hostRicevutoString = hostRicevuto.toString(); // Controllo se il pacchetto ricevuto contiene la stringa 'Sono nuovo' e che // non sia quello inviato da me if ((mess.compareTo("Sono nuovo")==0)&& (soloIP.compareTo(hostRicevutoString.substring(1))!=0)) { // in tal caso salvo l'indirizzo IP del nuovo nodo connesso in un array, // concatenandolo con il nome di quel nodo e con il tipo System.out.println("Nuovo nodo connesso: "+hostRicevutoString); // Recupero il nome e il tipo del nodo che si è connesso messNomeTipo=ricevuto.substring(22); StringTokenizer stmess = new StringTokenizer(messNomeTipo,"##"); messNomeTipo=stmess.nextToken(); // Concateno l'indirizzo IP con il corrispondente nome e tipo del nodo ipNomeTipo=recv.getAddress()+"/"+messNomeTipo; // Salvo le informazioni del nodo nell'array nodiconnessi[numnodi]=ipNomeTipo; // Rispondo inviando un messaggio con scritto 'Ci sono anche io' a colui // che mi ha inviato il messaggio 'Sono nuovo' stringaPacchettoRisp=packet.creaPacchetto ("ConfigIP","Ci sono anche io: "+nomeTipo,"FineIP"); DatagramPacket packrisposta = new DatagramPacket(stringaPacchettoRisp.getBytes(), stringaPacchettoRisp.length(),recv.getAddress(), portaConnessioni); canaleConnessioni.send(packrisposta); System.out.println("Informo "+hostRicevutoString+" della mia presenza"); numnodi++; } // Fine If // Controllo se il pacchetto ricevuto contiene la stringa 'Ci sono anche io' if (messrisp.compareTo("Ci sono anche io")==0) { // in tal caso salvo l'indirizzo IP del nuovo nodo connesso in un array, // concatenandolo con il nome di quel nodo e con il tipo System.out.println("Rilevata la presenza di: " +hostRicevutoString); // Recupero il nome e il tipo del nodo che si è connesso messNomeTiporisp=ricevuto.substring(28); StringTokenizer stmessrisp = new StringTokenizer(messNomeTiporisp,"##");
62
messNomeTiporisp=stmessrisp.nextToken(); // Concateno l'indirizzo IP con il corrispondente nome e tipo del nodo ipNomeTipo=recv.getAddress()+"/"+messNomeTiporisp; // Salvo le informazioni del nodo nell'array nodiconnessi[numnodi]=ipNomeTipo; numnodi++; } // Fine If // Controllo se il pacchetto ricevuto contiene la stringa 'Bye Bye' if ((messBye.compareTo("Bye Bye")==0) &&(soloIP.compareTo(hostRicevutoString.substring(1))!=0)) { // in tal caso cancello il nodo da cui ho ricevuto il messaggio 'Bye Bye' // dall'array che contiene le informazioni dei nodi connessi // Recupero il nome e il tipo del nodo che si è disconnesso messNomeTipo=ricevuto.substring(19); StringTokenizer stmessbye = new StringTokenizer(messNomeTipo,"##"); messNomeTipo=stmessbye.nextToken(); // Concateno l'indirizzo IP con il corrispondente nome e tipo del nodo ipNomeTipo=recv.getAddress()+"/"+messNomeTipo; numnodi--; // Elimino le informazioni del nodo dall'array for(int k=0;k<numnodi+1;k++){ if (nodiconnessi[k].compareTo(ipNomeTipo)==0){ // Ho trovato il nodo nell'array // Sovrascrivo le informazioni di quel nodo con quelle dei nodi // salvati nelle posizioni successive dell'array for(int j=0;j<numnodi-k;j++){ nodiconnessi[k+j]=nodiconnessi[k+j+1]; } } //Fine If } // Fine For System.out.println("Si è disconnesso il nodo: "+hostRicevutoString); } // Fine If // Se il messaggio di Bye Bye è stato inviato da me pongo la variabile 'attendi=false' in // modo da uscire dal ciclo e terminare la connessione if ((messBye.compareTo("Bye Bye")==0) &&(soloIP.compareTo(hostRicevutoString.substring(1))==0)) { attendi = false; } // Fine If } catch (InterruptedIOException e) { attendi = false; } }// Fine while // Termino la connessione canaleConnessioni.leaveGroup(gruppo); canaleConnessioni.close(); // Chiudo il canale di comunicazione } catch (IOException e) {System.out.println(e);} }//Fine metodo connect(String nomenodo,String tipo)
/**Metodo che permettere di disconnettere un nodo dalla rete spedendo in multicast il messaggio: ConfigIP##Bye Bye: nomeTipo##FineIP */ public void disconnect(){ Pacchetto pac = new Pacchetto();
63
String stringaPacchettoBye = new String(""); String nomeTipo= new String(""); int lung; if (connesso == true){ // Trovo l'Indirizzo IP locale che è associato al nome della macchina e inserisco il solo //indirizzo IP nella variabile 'soloIP' String soloIP=""; InetAddress mioHost= null; try { mioHost = InetAddress.getLocalHost(); } catch (UnknownHostException e) { System.out.println("Impossibile recuperare il mio IP"); System.exit(0); } // Converto l'indirizzo IP e il nome della macchina in stringa String mioHostString = mioHost.toString(); // Tengo solo l'indirizzo IP e lo metto nella variabile stringa 'soloIP' StringTokenizer st = new StringTokenizer(mioHostString,"/"); while (st.hasMoreTokens()) { soloIP=st.nextToken(); } lung=soloIP.length(); // Variabile che contiene la lunghezza del mio indirizzo IP // Salvo nella variabile 'nomeTipo' il nome e il tipo relativo al mio nodo nomeTipo=nodiconnessi[0].substring(lung+2); // Creo la stringa -> ConfigIP##Bye Bye: nomeTipo##FineIP stringaPacchettoBye=pac.creaPacchetto("ConfigIP","Bye Bye: " +nomeTipo,"FineIP"); try { // Invio in multicast il pacchetto che contiene la stringa-> ConfigIP##Bye Bye: nomeTipo##FineIP DatagramPacket pack = new DatagramPacket(stringaPacchettoBye.getBytes(), stringaPacchettoBye.length(),gruppo,portaConnessioni); canaleConnessioni.send(pack); } catch (IOException e) {System.out.println(e);} } //Fine If else{ System.out.println("***Disconnessione impossibile***: <<Non sono connesso>>"); } } // Fine metodo disconnect()
/**Metodo che mi permette di spedire un comando indirizzandolo ad un unico nodo @param comando indica il comando che verrĂ inviato @param nomenodo specifica il nodo a cui deve essere indirizzato il comando */ public void send_one(String comando,String nomenodo){ Pacchetto pacchettoSend = new Pacchetto(); String stringaPacchettoSend = new String(""); String stringaIPdest = new String(""); String infoNodo = new String(""); String soloNome = new String(""); String app = new String(""); InetAddress IPdest= null; boolean nodoConnesso=false; int n=0; // Controllo se la variabile 'nomenodo' contiene un nome presente nell'array dei nodi connessi // Scorro l'array contenente le informazioni dei nodi connessi for(int k=0;k<numnodi;k++){
64
n=0; StringTokenizer st = new StringTokenizer(nodiconnessi[k],"/"); while (st.hasMoreTokens()) { n++; if(n==2) soloNome=st.nextToken(); else app=st.nextToken(); } // Fine While if(soloNome.compareTo(nomenodo)==0){ nodoConnesso=true; infoNodo=nodiconnessi[k];} } // Fine For // Se il nodo di nome 'nomenodo' è effettivamente connesso procedo all'invio del comando if(nodoConnesso){ try { // Recupero l'indirizzo IP del nodo al quale si vuole inviare un comando StringTokenizer stIPdest = new StringTokenizer(infoNodo,"/"); stringaIPdest=stIPdest.nextToken(); IPdest = InetAddress.getByName(stringaIPdest); } catch (UnknownHostException e) { System.out.println(e); System.exit(0); } // Creo la stringa da inviare -> Comando##COMANDO##Fine Comando stringaPacchettoSend=pacchettoSend.creaPacchetto("Comando", ""+comando,"Fine Comando"); try { DatagramPacket packSendOne = new DatagramPacket(stringaPacchettoSend.getBytes(), stringaPacchettoSend.length(),IPdest,portaComandi); // Invio la stringa all'indirizzo IP di 'nomenodo' canaleComandi.send(packSendOne); } catch (IOException e) { System.out.println(e); System.exit(0); } } // Fine If else System.out.println("*** Impossibile inviare il messaggio a " + nomenodo+ " perchè non è connesso ***"); } // Fine metodo send_one(String messaggio,String nomenodo)
/**Metodo che mi permette di inviare un comando in multicast @param comando indica il comando che verrà inviato */ public void send_all(String comando){ Pacchetto pacchettoSendAll = new Pacchetto(); String stringaPacchettoSend = new String(""); String stringaIPdest = new String(""); String infoNodo = new String(""); InetAddress IPdest= null;
65
// Creo la stringa da inviare -> Comando##COMANDO##Fine Comando stringaPacchettoSend=pacchettoSendAll.creaPacchetto("Comando", ""+comando,"Fine Comando"); // Scorro l'array, recupero le informazioni di ogni // singolo nodo connesso e gli invio il comando for(int j=0;j<numnodi;j++){ infoNodo=nodiconnessi[j]; try { // Recupero l'indirizzo IP del nodo in questione StringTokenizer stIPdest = new StringTokenizer(infoNodo,"/"); stringaIPdest=stIPdest.nextToken(); IPdest = InetAddress.getByName(stringaIPdest); } catch (UnknownHostException e) { System.out.println(e); System.exit(0); } try { DatagramPacket packSendAll = new DatagramPacket (stringaPacchettoSend.getBytes(),stringaPacchettoSend.length(), IPdest,portaComandi); // Invio il pacchetto all'indirizzo IP di 'nomenodo' canaleComandi.send(packSendAll); } catch (IOException e) { System.exit(0); } }//Fine For } // Fine metodo send_all(String messaggio)
/**Metodo che si mette in ascolto finchè non riceve un comando e restituisce una stringa contenente il comando ricevuto @return stringaRicevuto una stringa contenente il comando ricevuto */ public String receive(){ boolean ascolta=true; String messHeader = new String(""); String stringaRicevuto= new String(""); String stringComando = new String(""); while(ascolta){ try { byte[] buf = new byte[1000]; DatagramPacket ricevuto = new DatagramPacket(buf, buf.length); // Mi metto in ascolto sul canale aperto canaleComandi.receive(ricevuto); // Salvo in una stringa i dati ricevuti stringaRicevuto=new String(ricevuto.getData()); messHeader=stringaRicevuto.substring(0,7); if(messHeader.compareTo("Comando")==0){ ascolta=false; } } catch (IOException e) { System.exit(0); }
66
} // Fine While stringComando=stringaRicevuto.substring(9); StringTokenizer stcom = new StringTokenizer(stringComando,"##"); stringComando=stcom.nextToken(); // Restituisco una stringa contenente il comando ricevuto return stringComando; }// Fine metodo receive()
/**Metodo che permette di stabilire se il nodo 'nomenodo' è connesso andando a controlare l'array contenente le informazioni dei nodi connessi @param nomenodo specifica il nome del nodo per il quale deve essere controllata l'eventuale connessione @return 'true' se il nodo 'nomenodo' risulta essere connesso, 'false' se il nodo 'nomenodo' non risulta essere connesso */ public boolean isConnected(String nomenodo){ String soloNome = new String(""); String app = new String(""); int n=0; // Scorro l'array contenente le informazioni dei nodi connessi for(int k=0;k<numnodi;k++){ n=0; StringTokenizer st = new StringTokenizer(nodiconnessi[k],"/"); while (st.hasMoreTokens()) { ++; if(n==2) soloNome=st.nextToken(); else app=st.nextToken(); } // Fine While // Se ho trovato il nome del nodo nell'array significa che è connesso e quindi restituisco 'true' if(soloNome.compareTo(nomenodo)==0) return true; } // Fine For // Restituisco 'false' perchè non ho trovato il nodo nell'array return false; } // Fine metodo isConnected(String nomenodo)
/**Metodo che restituisce una stringa contenente le informazioni dei nodi connessi alla rete in quel momento @return una stringa contenente tutti le informazioni dei nodi connessi alla rete */ public String whoIsConnected(){ String whoIsConn = new String (""); for(int k=0;k<numnodi;k++){ whoIsConn=whoIsConn+"\n"+nodiconnessi[k]; } // Resituisco la stringa che contiene tutti i nodi connessi alla rete return whoIsConn; } // Fine metodo whoIsConnected()
} //Fine Classe
67
Â&#x2122; Classe Pacchetto /** Nome File: Paccheto.java * Classe Pacchetto con i relativi attributi e metodi @author Francesco Molfese @version 1.0, 16/09/2004 */ public class Pacchetto{ private String headerStart; private String headerEnd; private String delimiter; private String messaggio; /** Metodo Costruttore che mi permette di inizializzare le variabili della classe */ public Pacchetto(){ headerStart=""; headerEnd=""; delimiter="##"; messaggio=""; } // Fine cosruttore /** Metodo che crea una stringa concatenando, separandoli con il delimitatore, i vari campi del pacchetto @param headerStart specifica l'header del pacchetto @param messaggio specifica il messaggio contenuto nel pacchetto @param headerEnd specifica la stringa che termina il pacchetto @return stringa che contiene i vari campi del pacchetto separati dal delimitatore */ public String creaPacchetto(String headerStart,String messaggio,String headerEnd){ return (headerStart + "" + delimiter + "" + messaggio+ "" + delimiter + "" +headerEnd); } }
Â&#x2122; Classe ControlPanel /** Nome File: ControlPanel.java * Classe che permette di creare il pannello di controllo con il quale impartire i comandi al robot @author Francesco Molfese @version 1.0, 30/09/2004 */ import java.awt.*; import java.awt.event.*; public class ControlPanel extends Frame{ private Button avanti; private Button indietro; private Button destra; private Button sinistra; private Button stop;
68
private ComWireless wireless; public ControlPanel(ComWireless wireless) { this.wireless = wireless; /* impostiamo il layout manager */ GridLayout mainFrameLayout = new GridLayout(); this.setTitle("Control Panel"); mainFrameLayout.setColumns(3); mainFrameLayout.setRows(3); this.setLayout(mainFrameLayout); this.setSize(300,300); this.setBackground(Color.lightGray); /* creazione degli elementi grafici */ avanti = new Button("Avanti"); avanti.setBackground(Color.green); avanti.addActionListener(new AvantiReact()); indietro = new Button("Indietro"); indietro.setBackground(Color.green); indietro.addActionListener(new IndietroReact()); destra = new Button("Destra"); destra.setBackground(Color.blue); destra.addActionListener(new DestraReact()); sinistra = new Button("Sinistra"); sinistra.setBackground(Color.blue); sinistra.addActionListener(new SinistraReact()); stop = new Button("Stop"); stop.setBackground(Color.red); stop.addActionListener(new StopReact()); /* posizionamento degli elementi grafici */ this.add(new Label()); this.add(avanti); this.add(new Label()); this.add(sinistra); this.add(stop); this.add(destra); this.add(new Label()); this.add(indietro); this.add(new Label()); } /*******************************************************************************/ /*ACTION LISTENERS*/ /*******************************************************************************/ public class AvantiReact implements ActionListener{ public void actionPerformed(ActionEvent ev){ // Stampo a video la scritta "Avanti !" System.out.println("Avanti !"); // Spedisco al nodo di nome Ipaq il comando "Vai avanti" wireless.send_one("Vai avanti","Ipaq"); }
69
} public class IndietroReact implements ActionListener { public void actionPerformed(ActionEvent ev) { // Stampo a video la scritta "Indietro !" System.out.println("Indietro!"); // Spedisco al nodo di nome Ipaq il comando "Vai indietro" wireless.send_one("Vai indietro","Ipaq"); } } public class DestraReact implements ActionListener { public void actionPerformed(ActionEvent ev) { // Stampo a video la scritta "Destra !" System.out.println("Destra !"); // Spedisco al nodo di nome Ipaq il comando "Gira a destra" wireless.send_one("Gira a destra","Ipaq"); } } public class SinistraReact implements ActionListener{ public void actionPerformed(ActionEvent ev){ // Stampo a video la scritta "Sinistra !" System.out.println("Sinistra !"); // Spedisco al nodo di nome Ipaq il comando "Gira a sinistra" wireless.send_one("Gira a sinistra","Ipaq"); } } public class StopReact implements ActionListener{ public void actionPerformed(ActionEvent ev){ // Stampo a video la scritta "Stop !" System.out.println("Stop !"); // Spedisco al nodo di nome Ipaq il comando "Fermati" wireless.send_one("Fermati","Ipaq"); } } }// Fine classe
Â&#x2122; Classe Rete import java.io.*; import java.net.*; import java.util.*; import java.lang.*; /** Nome File: Rete.java * Classe che contiene il main del programma con la gestione dei Thread. @author Francesco Molfese @version 1.0, 22/09/2004 */ public class Rete implements Runnable{ public static ComWireless comunicazione = new ComWireless ();
70
public void run(){ System.out.println("Provo a connettermi"); comunicazione.connect("Ipaq","Slave"); } public static void main(String args[]){ String nodiConnessi; // Creo il Thread principale Thread ct = Thread.currentThread(); Rete comunica = new Rete(); // Creo il Thread che si occupa della connessione Thread threadConnessione = new Thread (comunica); // Faccio partire la connessione threadConnessione.start(); // Faccio fermare il Thread principale per 4 secondi // in modo che si riesca a completare il Thread di connessione try { Thread.sleep(4000); } catch (InterruptedException e) { System.out.println("Thread principale interrotto"); } String comandoricevuto; Com_IR_Ipaq comIpaq=new Com_IR_Ipaq(); while(true){ comandoricevuto=comunicazione.receive(); System.out.println("Ho ricevuto il comando:"+comandoricevuto); // Invio il comando ricevuto sull'RCX if (comandoricevuto.equals("Vai avanti")){ comIpaq.sendToRCX(1);} else if(comandoricevuto.equals("Vai indietro")){ comIpaq.sendToRCX(2);} else if(comandoricevuto.equals("Gira a destra")){ comIpaq.sendToRCX(3);} else if(comandoricevuto.equals("Gira a sinistra")){ comIpaq.sendToRCX(4);} else if(comandoricevuto.equals("Fermati")){ comIpaq.sendToRCX(0);} } //Fine while } }
Â&#x2122; Classe Infrared_Ipaq_side /** Nome File: Infrared_Ipaq_side.java * Interfaccia Infrared_Ipaq_side con i relativi metodi che verrano implementati nella classe Com_IR_Ipaq @author Francesco Molfese @version 1.0, 22/09/2004 */ public interface Infrared_Ipaq_side { // dichiarazione di interfaccia // dichiarazione dei metodi astratti public void sendToRCX(int messaggio); public int receiveFromRCX(); }
71
Â&#x2122; Classe Com_IR_Ipaq import java.io.*; import java.lang.*; import josx.rcxcomm.*; /** Nome File: Com_IR_Ipaq.java * Classe Com_IR_Ipaq con i relativi attributi e metodi @author Francesco Molfese @version 1.0, 24/09/2004 */ public class Com_IR_Ipaq implements Infrared_Ipaq_side { private RCXPort port; /** Metodo Costruttore che mi permette di inizializzare le variabili della classe */ public Com_IR_Ipaq(){ try { port = new RCXPort(); } catch (Exception e) { System.out.println(e); } } /**Metodo che mi permette di inviare, tramite infraossi, dei numeri di comando all'RCX @param comando è un numero che indica all'RCX cosa fare */ public void sendToRCX(int messaggio){ try { OutputStream os = port.getOutputStream(); DataOutputStream dos = new DataOutputStream(os); //Invio il numero di comando all'RCX dos.writeInt(messaggio); dos.flush(); } catch (Exception e) { System.out.println(e); } } // Fine metodo sendToRCX(String messaggio)
/**Metodo che riceve un numero inviato dall'RCX via infrarossi @return ricevutoDaRCX rappresenta il numero inviato dall'RCX */ public int receiveFromRCX(){ int ricevutoDaRCX; ricevutoDaRCX=0; try { InputStream is = port.getInputStream(); DataInputStream dis = new DataInputStream(is); // Lettura del numero ricevuto dall'RCX ricevutoDaRCX = dis.readInt(); } catch (Exception e) { System.out.println(e);
72
} // Restituisco il numero ricevuto dall'RCX return(ricevutoDaRCX); } // Fine metodo receiveFromRCX() } //Fine classe
Classe Infrared_RCX_side /** Nome File: Infrared_RCX_side.java * Interfaccia Infrared_RCX_side con i relativi metodi che verrano implementati nella classe Com_IR_RCX @author Francesco Molfese @version 1.0, 22/09/2004 */ public interface Infrared_RCX_side { // dichiarazione di interfaccia // dichiarazione dei metodi astratti public void sendToIpaq(int messaggio); public int receiveFromIpaq(); }
Classe Com_IR_RCX import java.io.*; import java.lang.*; import josx.rcxcomm.*; /** Nome File: Com_IR_RCX.java * Classe Com_IR_RCX con i relativi attributi e metodi @author Francesco Molfese @version 1.0, 23/09/2004 */ public class Com_IR_RCX implements Infrared_RCX_side { private RCXPort port; /** Metodo Costruttore che mi permette di inizializzare le variabili della classe */ public Com_IR_RCX(){ try { port = new RCXPort(); } catch (Exception e) { System.exit(0); } } /**Metodo che mi permette di inviare, tramite infraossi, dei numeri all'Ipaq @param messaggio è un numero che viene inviata all'Ipaq */ public void sendToIpaq(int messaggio){ try {
73
OutputStream os = port.getOutputStream(); DataOutputStream dos = new DataOutputStream(os); //Invio la stringa di comando all'RCX dos.writeInt(messaggio); dos.flush(); } catch (Exception e) { System.exit(0); } } /**Metodo che riceve un numero inviato dall'Ipaq via infrarossi @return ricevutoDaIpaq rappresenta il numero inviato dall'Ipaq */ public int receiveFromIpaq(){ int ricevutoDaIpaq=0; try { InputStream is = port.getInputStream(); DataInputStream dis = new DataInputStream(is); // Lettura del numero ricevuto dall'Ipaq ricevutoDaIpaq =dis.readInt(); } catch (Exception e) { System.exit(0); } // Restituisco il numero ricevuto dall'RCX return(ricevutoDaIpaq); } } //Fine classe
Â&#x2122; Classe RCXmain import java.io.*; import java.util.*; import java.lang.*; import josx.rcxcomm.*; import josx.platform.rcx.*; /** Nome File: RCXmain.java * Classe che contiene il main del programma che gira sull'RCX. @author Francesco Molfese @version 1.0, 26/09/2004 */ public class RCXmain { public static void main(String args[]){ int comandoRicevuto; Com_IR_RCX comRCX=new Com_IR_RCX();
74
while (true) { comandoRicevuto=comRCX.receiveFromIpaq(); if (comandoRicevuto==1){ // Scrittura sul diplay TextLCD.print("Avanti"); // Setto la potenza dei motori al massimo Motor.A.setPower(7); Motor.C.setPower(7); // Attivo in avanti i due motori connessi alla porta A e C Motor.A.forward(); Motor.C.forward();} if (comandoRicevuto==2){ // Scrittura sul diplay TextLCD.print("Indietro"); // Setto la potenza dei motori al massimo Motor.A.setPower(7); Motor.C.setPower(7); // Attivo indietro i due motori connessi alla porta A e C Motor.A.backward(); Motor.C.backward();} if (comandoRicevuto==3){ // Scrittura sul diplay TextLCD.print("Destra"); Motor.C.stop(); // Setto la potenza del motore A al massimo Motor.A.setPower(7); // Faccio partire il motore A Motor.A.forward();} if (comandoRicevuto==4){ // Scrittura sul diplay TextLCD.print("Sinistra"); Motor.A.stop(); // Setto la potenza del motore C al massimo Motor.C.setPower(7); // Faccio partire il motore C Motor.C.forward();} if (comandoRicevuto==0){ // Scrittura sul diplay TextLCD.print("Stop"); // Fermo entrambe i motori Motor.A.stop(); Motor.C.stop();} } //Fine While } //Fine main } //Fine classe
75