| Index | Next | Prev. |


4.5 Integrazione e prove sperimentali
Terminata la fase di progettazione e di scrittura del codice è utile per testare l’architettura realizzata fare una prova come dire sul "campo".

Gli ingegneri del laboratorio di misure hanno proprio un’esigenza del tipo: si vogliono controllare degli strumenti di misura da remoto in modo tale che dovendo inviare comandi agli strumenti (per scopo didattico o per una misura reale) non si è costretti ad essere fisicamente legati ad essi.

Gli strumenti che si vogliono controllare sono basati sullo standard VXI e IEEE 488.

In breve, il "VXI" è uno standard per strumentazione su scheda elettronica, ogni strumento è ridotto ad una scheda e dialoga mediante un bus, il VXI appunto, con un controllore dal quale è possibile inviare comandi ad esse e leggere le relative risposte. Il controllore è inserito in un "cestello" ed è lo stesso che contiene le schede.

Il controllore è un personal computer che oltre a interfacciarsi con il bus VXI contiene in questo caso: una scheda di rete, e una scheda che consente l’interfacciamento con strumenti di misura che si basano sul bus IEEE 488 in modo da poter controllare dalla stessa "postazione" anche questo tipo di strumenti.

Il controllore sarà il server su cui vanno installate le classi dell’applicazione server, il client sarà un generico computer connesso in rete sul quale vanno installate le classi dell’applicazione client. Non viene scelta la soluzione di usare l’applet come client - che si scaricava automaticamente sul browser Web - in quanto attualmente non vi è questa esigenza (ma nulla vieta di usare questa soluzione in futuro).

Va notato che la scelta di sfruttare il meccanismo degli applet o meno va fatta anche in base alle risorse hardware disponibili, in quanto questo richiede che il computer che ospita il server del dispositivo deve anche funzionare da server Web e la cosa può essere alquanto onerosa.

L’architettura di base è pronta, per renderla operativa vanno specializzate le classi: DeviceGUI (Figura 4.13) dal lato client e ControllableDevice (Figura 4.10) dal lato server.

Vanno implementati inoltre i due oggetti che incapsulano i dati che vanno dal client al server e viceversa, in base alle specifiche fornite sul tipo di dati che elaborano gli strumenti di misura.

Altro problema da risolvere è l’interfacciamento software del server con gli strumenti di misura che in termini implementativi vuol dire interfacciamento del linguaggio Java con il linguaggio C considerando che i driver di questi strumenti sono scritti in linguaggio C. La soluzione a questo problema la si trova nei metodi nativi.

I metodi nativi

    Esistono due ragioni per le quali è necessario dichiarare alcuni metodi native, che significa "implementati tramite un linguaggio diverso da Java". La prima regione è rappresentata dalla necessità di utilizzare alcune funzionalità del computer o del sistema operativo, che la libreria di classi di Java non fornisce. Queste funzionalità comprendono l’interfaccia con nuove periferiche, l’accesso a differenti reti di computer, o ancora l’utilizzo di una peculiare caratteristica del sistema operativo.

    La seconda ragione è l’ottimizzazione o meglio la velocità, anche se in realtà raramente sono necessarie velocità cosi spinte da ricorrere ai metodi nativi. La libreria di classi Java utilizza questo approccio per alcune classi di sistema critiche, allo scopo di aumentare il livello di efficienza complessivo del sistema.

    La scelta di utilizzare dei metodi nativi nel proprio programma ha un costo ed è quello della perdita della portabilità del proprio codice Java.

    Con i metodi nativi si crea una libreria di codice nativo da linkare al proprio programma per farlo funzionare in modo appropriato.

    Nel caso degli strumenti di misura lo scopo è di interfacciarli al server del dispostivo. Il discorso della portabilità viene meno in quanto il codice deve essere strettamente dipendente da quel particolare strumento, ovvero la programmazione è fatta a basso livello, perché le schede di misura vanno programmate a basso livello, quindi non ha senso parlare di portabilità.

    La scrittura di metodi in codice nativo comporta alcuni cambiamenti alla scrittura del codice in Java, i metodi nativi in Java vanno solo dichiarati (senza corpo) ed inoltre avranno il modificatore native. Occorre anche aggiungere un inizializzatore static a ciascuna classe che contiene i metodi native per caricare la libreria di codice nativo, libreria che deve essere una DLL.

    L’inizializzatore static contiene nel suo corpo un link alla libreria nativa, esso viene eseguito una sola volta, quando la classe viene caricata nel sistema, il caricamento della classe e il caricamento del codice nativo sono tra di loro legati in modo che se uno dei due fallisce anche l’altro non viene eseguito, garantendo così che non venga creata nessuna versione "impostata a metà" di quella classe.

    Per poter mettere mano agli oggetti e ai tipi di dati di Java, in modo da essere in grado di manipolarli nel codice C, è necessario includere alcuni file speciali di estensione .h forniti assieme al JDK ed altri file speciali devono essere generati a partire dalla propria classe che dichiara metodi native. Sempre nel JDK e fornito lo strumento javah che genera, a partire da una classe con metodi nativi, un file .h di intestazioni il quale è indispensabile per scrivere il codice C in quanto fornisce l’esatta signatura (firma) dei metodi. Sempre con lo strumento javah usandolo con l’opzione javah -stubs si genera un file di stub (.c).

    Gli stub sono porzione di codice che fungono da collante traducendo gli argomenti e i valori restituiti tra il mondo Java e quello del C. Non c’è molto da sapere in riguardo ai file di stub se non che esso deve essere compilato e linkato con il proprio codice C, per fare in modo che possa interfacciarsi con Java in modo opportuno.

    Avendo disponibile il file di intestazione (.h) e il file di stub (.c) ottenuti dalla classe Java che dichiara metodi native si passa alla scrittura del proprio codice in linguaggio C rispettando le signature dei metodi fornite dal file .h. Terminata la scrittura del proprio codice C occorre compilarlo assieme al file di stub utilizzando un compilatore C e specificando i flag di compilazione per indicare di rendere il codice rilocabile e linkabile dinamicamente (.dll).

    A questo punto per far caricare la libreria dll dalla classe Java, l’inizializzatore static deve contenere una chiamata al metodo di classe System.loadLibrary() che si occupa di caricare correttamente la DLL.
     

La sottoclasse di ControllableDevice
    Il codice in linguaggio C per il controllo delle schede di misura esiste già, bisogna solo "adattarlo" per renderlo compilabile come DLL e per avere corrispondenza tra la dichiarazione dei metodi native di Java e le corrispondenti funzioni in C: Inoltre bisogna fare attenzione alla corrispondenza tra i tipi di dati in Java e gli equivalenti tipi di dati in C, alcune di essi non vengono trattati allo stesso modo o ancora peggio non vi è un corrispondente tipo tra un linguaggio e l’altro; sempre nel JDK viene fornita la documentazione con l’equivalenza dei tipi di dati, e viene fornita una libreria di file .h (con le relative dll) contenenti delle funzioni che possono essere richiamate dal codice C per ottenere l’equivalente tipo di dato di Java in tipo di dato C, e viceversa.

    Si deve specializzare la classe ControllableDevice per creare una classe che "dialoga" con la DLL che a sua volta "dialoga" con le scheda di misura (hardware).

    La sottoclasse di ControllableDevice (d’ora in poi IeeeVxi) esplicita il corpo dei metodi astratti definiti in ControllableDevice ed aggiunge dei metodi nativi, oltre ad altri eventuali metodi utili.

    Quindi IeeeVxi contiene gli attributi booleani che indicano lo stato del dispositivo: boolean:inUse, e boolean:isShareble rispettivamente indicano se il dispositivo è in uso o meno e se è condivisibile tra più utenti; i metodi concreti che erano stati dichiarati astratti nella classe ContollableDevice, e dei metodi native dichiarati in base alle specifiche ricavate dalle funzioni implementate in C per il controllo delle schede di misura. I metodi dichiarati native sono: leggiInput(bus:String, strumento:String,camando:String, nomeFileString, query:String) che passa i parametri inviati dall’utente al driver delle schede di misura, leggiStatus():int che legge lo stato delle schede dopo che leggiInput() e stato eseguito, e leggiRisposta():Object che acquisisce il risultato della misura.

    La rappresentazione grafica della classe e riportata nella figura che segue.

    Figura 4.16 Rappresentazione grafica della classe IeeeVxi.

     

La sottoclasse di DeviceGUI
Dal lato client invece va specializzata la classe DeviceGUI creando una sottoclasse. Essa deve contenere un pannello GUI con cui l’utente può interagire per impostare dei parametri e i metodi necessari per acquisire i parametri impostati dall’utente e inviarli al server e quindi alle schede di misura.

Si realizza ciò in due classi: IeeeVxiPanel che eredita dalla classe java.awt.Panel e si occupa solo dell’interfaccia grafica utente e IeeeVxiGUI che eredita dalla classe DeviceGUI e contiene le operazioni che consentono di acquisire i parametri impostati dall’utente in IeeeVxiPanel inviarli al server e attendere una risposta.

In base alle specifiche fornite dagli Ingegneri del laboratorio di misure, la classe IeeeVxiPanel deve contenere per i dati in input alle schede di misura i seguenti elementi:

  1. Un menu per la scelta del bus VXI o IEEE 488: choiceBUS:Choice;
  2. Una lista per la scelta dello strumento IEEE: listStrumentoIEEE:list;
  3. Un campo di testo in cui digitare il comando da impartire alle schede di misura: textFieldComnado:TextField;
  4. Un campo di testo per indicare quanti byte si vogliono leggere dalla scheda di misura: textFieldByte:TextField;
  5. Un campo di testo per immettere un nome di file se si vuole salvare su file il risultato della misura: textFieldFile:TextField;
  6. Un campo di testo in cui è possibile indicare l’indirizzo della scheda VXI, in quanto mentre gli strumenti IEEE 488 hanno nomi simboli per quelli VXI va specificato l’indirizzo che occupa la scheda sul bus VXI: textFieldStrumentoVXI:TextField.

  7.  

    Per i dati in output invece occorrono due campi di testo: uno per visualizzare lo stato degli strumenti textFiledStatus:TextField, l’altro che visualizza i risultati della misura: textFieldRisposta:TextField.

    Nel pannello ci sono anche due checkbox, uno per settare se i risultati della misurra devono essere salvato su file e l’altro per indicare se dall’invio dei comandi alle schede di misura si deve attendere una risposta, questo in quanto alcuni comandi non forniscono risultati ma cambiano solo lo stato della scheda.

    Infine, nel pannello sono presenti due pulsanti, uno per inviare i dati impostati nei campi di testo al server (e quindi alle schede di misura), l’altro per resettare tutti i campi di testo.

    L’unica operazioni presente in IeeeVxiPanel e una action():boolean che in base alle interazione dell’utente abilita o disabilita alcuni campi di testo.

    La classe IeeeVxiGUI contiene le operazioni che leggono i dati dal pannello e li inviano al server, leggono i dati provenienti dal server e li visualizzano nel pannello. Queste operazioni sono implementate con il metodo sendAction() che viene eseguito nel momento in cui sono stati settati i campi di testo del pannello e si preme un pulsante "send", questo metodo legge i dati contenuti nei campi di testi, li incapsula in un oggetto e invia l’oggetto al server che a sua volta passa l’oggetto a DeviceVXI. Il metodo opposto a sendAction() e readResult() che attende un oggetto proveniente dal server, estrai i dati contenuti in esso e li visualizza nel pannello. Un generico metodo cancelAction() invece provvede a resettare tutti i campi del pannello mentre un metodo action():boolean acquisisce le interazione dell’utente con il pannello.

    La rappresentazione grafica della classe DeviceGUIVXI insieme alla classe PanelVXI e riportata in Figura 4.17, appare chiaro a questo punto che la classe DeviceGUIVXI deve contenere la classe PanelVXI.

 

Figura 4.17 Rappresentazioni grafica delle classi che costituiscono il client per il controllo degli strumenti di misura basati su bus VXI e IEEE 488.

Le classi DeviceDataInput e DeviceDataOutput

     Rimane un’ultima cosa da specificare affinché tutta l’applicazione funzione correttamente, quest’ultima cosa è rappresentata dal tipo di dati di input e di output delle schede di misura, i quali condizionano le informazione che il client e il server devono scambiare tra loro.

In fase di progetto più volte si è detto di non fare nessuna assunzione sul tipo di dati che l’architettura client-server trasferiva, i dati venivano visti come dei generici oggetti, questo è ancora vero ma il programmatore che va specializzare le classi ControllableDevice e DeviceGUI (come si è fatto) deve anche costruire gli oggetti che incaspulano i dati che vengono trasferiti in rete sia in input che in output ed e compito del programmatore gestire correttamente i dati incapsulati nel senso che ci deve essere congruenza tra il tipo di oggetto inviato dall’applicazione client, con cui l’utente interagisce, e quello che si aspetta di ricevere l’applicazione server che controlla il dispositivo elettronico, e viceversa.

Nel caso dell’implementazione realizzata per il controllo degli strumenti che si basano su bus VXI e IEEE 488 il tipo di dati di input e diverso da quello di output quindi si procede definendo due classi diverse: la classe DeviceDataInput che incaspula i dati rappresentanti l’input degli strumenti e la classe DeviceDataOutput che incapsula i dati in output dagli strumenti. Nulla vieta di definire una sola classe se la stessa va bene sia per l’input che per l’output, o più classi.

Una chiara distinzione tra i dati in input e quelli in output è stata già fase in fase di progetto del panello IeeeVxiPanel, ed infatti le classi che si stanno progettando contengono dei dati che riflettono esattamente la distinzione dei campi di testo fatta in IeeeVxiPanel.

La classe DeviceDataInput ha attributi di tipo String e sono: bus, strumento, comando, nomefile e letturabyte, e un attributo checkRisposta:Boolean.

La classe DeviceDataOutput ha gli attributi statusByteIn, statusByteOut:int, un attributo risposta:String e un attributo b[]:byte ovvero un array di byte che viene utilizzato nel caso in cui il risultato della misura è un insieme di valori (per es. campionamento), in tal caso, dal lato client, i valori contenuti nell’array vengono salvati in un file per poi essere elaborato successivamente.

Queste classi non ereditano da nessuna classe quindi esse sono automaticamente sottoclassi di Object come vuole il linguaggio Java; in realtà questo non basta, per come sono state definite fino ad ora, queste due classe ancora non possono essere usate per trasferire oggetti in rete, per l’uso che se ne vuole fare occorre implementare una specifica interfaccia di Java, affinché tutto funzioni correttamente.

Le interfacce del linguaggio Java
Nel linguaggio Java oltre alle classi ci sono le interfacce, esse sono un po’ come le classi astratte: possono contenere dichiarazioni di metodi ma questi sono implicitamente astratti (senza implementazione) possono contenere variabili ma sono implicitamente costanti!

Le interfacce forniscono solo un modello di comportamento, spetta alla classe che implementa l’interfaccia fornire la giusta implementazione ai metodi. Ma le interfacce sono molto più potenti.

Si è detto fin dall’inizio che il linguaggio Java non implementa l’ereditarietà multipla, questo per evitare di aggiungere complessità al linguaggio, ma fornisce comunque una gerarchia che consente di ottenere la stessa espressione dell’ereditarietà multipla.

Questa nuova gerarchia è una gerarchia di interfacce. Le interfacce non sono limitate ad avere una singola interfaccia "madre", perciò consentono di implementare una forma di ereditarietà multipla, tuttavia esse passano solo descrizioni di metodi alle proprie "figlie", non implementazioni né variabili istanza, il che elimina le complessità insite dei sistemi a ereditarietà multipla.

Le interfacce sono come le classi: dichiarate in file sorgenti, una per file, e compilate in file .class. A differenza delle classi le interfacce non sono istanziabili, devono essere implementate da una classe, ma sono comunque un tipo di dato. Inoltre un’interfaccia "figlia" può ereditare da quante interfacce "madri" si vogliono; non solo ma se una classe può ereditare da una sola superclasse può invece implementare un numero qualunque di interfacce.

Una delle più potenti funzionalità che le interfacce aggiungono al linguaggio Java è la capacità di separare l’ereditarietà di progetto da quella di implementazione. Poiché le interfacce si trovano in una gerarchia separata, possono essere "mescolate" alle classi in un albero a ereditarietà singola, il che consente in fase di progetto di spargere interfacce ovunque.

Nel linguaggio Java si può quindi considerare la gerarchia di implementazione contenuta nell’albero a ereditarietà singola, mentre la gerarchia di progetto è contenuta nell’albero a ereditarietà multipla.

Implementare un’interfaccia significa promettere di implementare tutti i metodi specificati in essa, solo le classi astratte possono ignorare questa promessa, ma tutte le sottoclassi concrete devono rispettarla.

Le interfacce forniscono elevata dinamicità al linguaggio Java.

Per esempio, nel caso di un oggetto che vuole essere trasferito attraverso la rete, ma più in generale attraverso uno stream di I/O esso deve implemantare l’interfaccia java.io.Serializable, in quanto le classi ObjectInputStream e ObjectOutputStream che consentono di leggere e scrivere oggetti attraverso uno stream di I/O, contengono dei metodi che ricevono parametri di tipo Serializable, cioè si aspettano che un generico oggetto che voglia essere letto o scritto da uno stream di I/O sia di tipo Serializable.

Anche DeviceDataInput e DeviceDataOutput devono allora implementare l’interfaccia java.io.Serializabile per fare in modo che l’applicazione funzioni correttamente. In questo caso il compito del programmatore è facilitato in quanto l’interfaccia java.io.Serializzable non contiene nessun metodo, quindi la promessa di implementare tutti i metodi contenuti nell’interfaccia e implicitamente soddisfatta.

In Figura 4.19 è riportata la rappresentazione grafica del progetto delle classi DeviceDataOutput e DeviceDataInput tenendo presente quanto detto sulle interfacce.
 

 

Figura 4.18 Rappresentazione grafica delle classi DeviceDataInput e DeviceDataOutput.

 

Nelle Figure 4.19 e 4.20 si riporta il modulo completo delle classi utilizzate per l’applicazione server e per l’applicazione client nel caso specifico di controllo di strumenti di misura elettronici basati su bus VXI e IEEE 488.

Figura 4.19 L'applicazione Server nel caso specifico di controllo di strumenti di misura su bus VXI e IEEE 488.

 

Figura 4.20 L'applicazione client per il controllo di strumenti di misura su bus VXI e IEEE 488.

 

Le Figure 4.21 e 4.22 invece, mostrano l’applicazione server e quella client in esecuzione in ambiente Windows 95, sostanzialmente l’applicazione server è un programma in esecuzione in una shell di Windows con un frame che ne illustra lo stato, mentre dell’applicazione client è visibile l’interfaccia grafica utente implementata in IeeeVxiPanel. In Figura 4.23 è mostrato il client eseguito come applet.

 Figura 4.21 Il programma server in esecuzione .

 

Figura 4.22 Il programma client in esecuzione.

 

Figura 4.23 Il programma client in esecuzione come Applet.

  Prove sperimentali
Una prima prova viene fatta senza gli strumenti di misura, viene utilizzata tutta l’architettura fin qui realizzata, due PC Pentium con sistema operativo Windows 95 connessi in rete e un programma scritto in linguaggio C, e poi compilato come DLL, che simula la presenza di un dispositivo controllabile mediante delle funzioni che effettuano query su se stesse; lo scopo in questa fase e quello di individuare eventuali bug presenti nell’implementazione dell’architettura progettata e risolvere eventuali problematiche dovute all’interfacciamento Java-C. Ed è proprio l’interfacciamento Java-C che richiede attenzione: i tipi di dati non sono rappresentati allo stesso modo inoltre per alcuni tipi di dati non vi è corrispondenza, a questo si aggiunge una documentazione sull’argomento superficiale. Importanti informazioni si ricavano direttamente dai file sorgenti e di intestazione forniti con il JDK.

In questa fase, inoltre, si scopre che attualmente il client non può essere usato come applet ma necessariamente come applicazione. Il problema è che l’implementazione è stata fatta usando il JDK1.1beta, questo perché sono state usate delle classi proprie di questa versione ed assenti nelle precedenti, ma gli attuali browser Web che supportano Java si basano sulla versione precedente del JDK, in particolare incorporano un set di classi standard ridotto rispetto a quello del JDK1.1beta che ha una libreria di classi molto più vasta; ciò comporta problemi di esecuzione agli applet scritti usando il JDK1.1beta. Appena la Sun Microsystems rilascerà la versione definitiva del JDK1.1 dovrebbero essere disponibili le versioni aggiornate dei browser (si spera) ed il problema non sussisterà più.

In generale, comunque, le applicazioni client e server si sono comportate come previsto e l’interfacciamento Java-C non ha dato grossi problemi.

Delle difficoltà sull’interfacciamento dei due linguaggi si sono presentate nel caso reale in cui il codice in linguaggio C va a pilotare gli strumenti di misura che si basano su bus VXI e IEEE 488.

Il codice C disponibile per questi strumenti era stato scritto per ambiente DOS/Windows 3.x (16 bit), le stesse librerie fornite con gli strumenti erano per lo sviluppo di applicazioni in questo ambiente.

Il codice Java per poter essere eseguito richiede un ambiente completamente a 32 bit quindi librerie per ambienti a 32 bit.

Un primo tentativo è stato quello di adattare il codice C esistente al nuovo ambiente (Windows 95) utilizzando le librerie di aggiornamento fornite, nel frattempo, dalla National Instruments Italia.

Dopo un susseguirsi di problemi (alcuni dei quali causati proprio dalle nuove librerie) si è deciso di riscrivere buona parte del codice e di scaricare direttamente dal sito dalla National Instruments (dopo varie consultazione con la National Instruments Italia) le librerie per la programmazione degli strumenti in ambiente Windows 95.

Risolti in questo modo gran parte dei problemi il codice C è stato compilato usando un compilatore Microsoft Visual C++ e linkato dal codice Java.

Le prove sperimentali sono state effettuate installando l’applicazione server sul controllore del cestello VXI, un PC-VXI 486DX66 con sistema operativo Windows 95, a cui erano connessi uno strumento che si basa su bus VXI e uno strumento interfacciabile mediante bus IEEE 488, mentre l’applicazione client è stata installata su un PC Pentium con Windows 95, connesso al PC-VXI mediante rete locale (Figura 4.24).

Figura 4.24 Prova sperimentale con strumenti di misura su bus VXI e IEEE 488.

 

Nella Figura 4.25 è illustrato lo stato del server durante le prove sperimentali, sostanzialmente visualizza le connessioni aperte e quali strumenti si stanno controllando.

 

 

Figura 4.25 Stato del server durante le prove sperimentali.

 

Nelle Figure 4.26 e 4.27, invece, è illustrato lo stato del client, rispettivamente prima dell’operazione di misura e immediatamente dopo.

 

 

Figura 4.26 Il client prima dell'operazione di misura.

 

Figura 4.27 Il client con il risultato della misura.

 

    4.6 Conclusioni
L’idea di realizzare un’architettura client-server per applicazioni di didattica distribuita ha dato origine a questo lavoro di tesi. Fin da principio ci si è posti nelle ipotesi di generalizzare il più possibile il problema, non un’architettura per questa o quella applicazione di didattica remota bensì qualcosa di molto generale, qualcosa da rendere disponibile per usi futuri.

La progettazione dell’architettura è stata fatta seguendo questa filosofia, inoltre in fase di implementazione il linguaggio Java ha dimostrato una flessibilità (e dinamicità) tale da consentire il pieno rispetto delle ipotesi iniziali e di progetto.

La fase di estensione dell’architettura per realizzare il controllo remoto di strumenti di misura su bus VXI e IEEE 488 è stata, oltre che un esempio lampante dell’uso dell’architettura, anche un test per valutare quanto le ipotesi di generalizzazione fossero ben poste. L’architettura generale si è dimostrata "robusta" nel senso che non ci sono stati ripensamenti su quanto progettato e poi implementato.

E’ vero che il solo test con strumenti di misura VXI e IEEE 488 non può considerarsi esaustivo, ma è comunque significativo al punto da poter affermare che l’obbiettivo generale di: una architettura client-server per applicazioni di didattica distribuita, è stato raggiunto.

Chi avrà l’esigenza di controllare dispositivi elettronici da remoto e vuole usare questa architettura potrà completamente disinteressarsi dei dettagli implementativi seguiti e far uso solo delle specifiche progettuali di alcune classi, le quali andranno specializzate in modo opportuno in base alle proprie esigenze.


 | Index | Next | Prev. |