Utilizzo dei driver
Interagire con
le porte seriali o parallele di un sistema è una operazione solitamente
molto legata al sistema operativo stesso. Accedere alla seriale, ad esempio,
in Windows è diverso dal farlo in un ambiente Solaris. Sicuramente,
però, le operazioni che si possono e vogliono eseguire sono le stesse
per cui, nelle JCA, è stata definita l'interfaccia javax.comm.CommDriver
che
dovrà essere implementata da ciascun Driver JavaComm
il cui compito è proprio quello di tradurre le varie operazioni
sulle porte, in codice nativo legato alla particolare implementazione della
JVM.
I compiti del
driver sono quelli dei metodi che descrive:
-
void initialize()
:
Questo metodo deve assicurarsi della presenza dell'hardware e caricare
le librerie native che ne permettono l'utilizzo. E' compito di questo metodo
anche la determinazione delle porte disponibili e la memorizzazione dell'elenco
in un oggetto di tipo CommPortIdentifier.
-
CommPort getCommPort(String
portName,int portType) : Questo metodo permette di ottenere un riferimento,
come vedremo meglio in seguito, ad un oggetto CommPort, rappresentativo
di una porta. I parametri sono relativi al nome della porta (es:COM1 in
Windows) ed al tipo della stessa (ad esempio: Serial).
Ogni applicazione
che utilizza le JCA potrà essere portata in ogni sistema operativo
per il quale esiste un CommDriver, ovvero una implementazione della
precedente interfaccia. La cosa importante è che il codice Java
non deve essere minimamente modificato ma sarà sufficiente fornire
una diversa libreria nativa utilizzata dal Driver. Nel caso di Windows
si utilizza la libreria Win32Com.dll.
Per capire come
viene associata una libreria ad una particolare implementazione della JVM,
vediamo come si installano le JCA. Ottenuto, dal sito JDC (Java Developer
Connection), il file javacomm20-win32.zip e scompattato, si hanno,
oltre ad alcuni esempi, i seguenti file:
-
comm.jar :
Rappresenta il file
.jar delle classi relative alle JCA. Nella versione
relativa a Windows, si nota, al suo interno, una classe di nome com.sun.comm.Win32Driver
che rappresenta proprio l'implementazione dell'interfaccia javax.comm.CommDriver
relativa al sistema operativo utilizzato. In fase di installazione, questo
file deve essere posizionato nella subdirectory in cui sono contenute le
classi del JDK o comunque in una directory contenuta nel CLASSPATH.
-
Win32Com.dll
:
Nella implementazione del metodo initialize() della classe
Win32Driver,
ci dovrà essere il caricamento di tutte le librerie native utilizzate.
Nel caso di Windows, tali librerie sono rappresentate dal singolo file
Win32Com.dll.
Esso va posizionato nella directory
<jdk1.1>\bin dove <jdk1.1>
è la directory base di installazione del JDK (ad esempio: <jdk1.1>=C:\JDK1.1.5).
-
javax.comm.properties
:
Questo file di proprietà contiene proprio il nome del driver da
utilizzare e va posizionato nella directory <jdk1.1.>\lib. Esso
contiene, oltre ad un commento esplicativo, la riga Driver=com.sun.comm.Win32Driver.
E' molto importante
eseguire l'installazione in modo corretto in quanto la mancanza del file
javax.comm.properties
provoca un errore di mancato reperimento del Driver, mentre l'errata posizione
della .dll fa sì che non vengano trovate le varie porte.
Astrazione delle porte di
comunicazione
Nel precedente
paragrafo abbiamo visto come si possa interagire con parti native di un
sistema, mantenendo un elevato grado di portabilità di una applicazione.
A questo punto, le altre classi delle JCA, vedono un oggetto di tipo CommDriver
e non si interessano minimamente della particolare implementazione. Se
si utilizzano le JCA, non dobbiamo, comunque, mai interagire direttamente
con il Driver ma utilizziamo la classe javax.comm.CommPortIdentifier
che fornisce i metodi per:
-
Determinare le porte
di comunicazione disponibili:
-
Determinare la proprietà
(OwnerShip) di una particolare porta
-
Aprire una determinata
porta di comunicazione
-
Gestire la contesa
di una porta tra applicazioni diverse
Vediamo nel dettaglio
come si raggiungono questi obiettivi.
Determinare le porte di
comunicazione disponibili
L'oggetto CommPortIdentifier
è di fondamentale importanza nelle JCA in quanto è quello
che interagisce con il Driver. Quando tale classe viene caricata in memoria,
accede al file javax.comm.properties che indica il nome del Driver
da caricare. Essa, in un inizializzatore statico (vedi Riquadro), carica
il Driver e chiede la lista delle porte disponibili. Il Driver utilizzerà
la libreria nativa per ottenere tale lista e registrerà ciascuna
porta nell'oggetto CommPortIdentifier attraverso il suo metodo addPortName()
specificando, per ognuna, un nome ed un valore intero caratteristico del
tipo di porta (SERIAL o PARALLEL). Dopo che tale classe è stata
caricata, e l'inizializzatore statico eseguito, essa conterrà la
lista delle porte disponibili. Tale lista si ottiene come Enumeration
di oggetti CommPortIdentifier, attraverso il metodo staticgetPortIdentifiers().
Derminare la proprietà
(OwnerShip) di una porta
Una volta ottenuta
la lista delle porte disponibili in un sistema, è possibile sapere
se questa porta è attualmente in uso da una particolare applicazione
oppure è libera. Il metodo isCurrentlyOwned() applicato ad
un oggetto CommPortIdentifier
descrittivo di una porta, permette
di ottenere un valore booleano che indica, se true, che la
porta è occupata. Il metodo getCurrentlyOwer() permette,
inoltre, di conoscere il nome della applicazione che, in quel momento,
occupa la porta.
Aprire una determinata porta
di comunicazione
Per aprire ed
impossessarsi del controllo di una determinata porta, si possono seguire
due metodi:
-
Testare, con il
metodo isCurrentlyOwed(), se la porta è libera e successivamente
aprirla con il metodo open().
-
Tentare l'apertura
della porta con il metodo open(). Nel caso in cui tale porta fosse
occupata, verrà sollevata una eccezione di tipo javax.comm.PortInUseException.
Il primo metodo
non è ottimale in quanto potrebbe succedere che al momento di esecuzione
del metodo isCurrentlyOwed(), la porta sia libera ma diventi occupata
al momento dell'esecuzione della
open(). Tanto vale provare ad eseguire
immediatamente la open(), e gestire, eventualmente, l'eccezione
che ne consegue nel caso in cui la porta sia occupata. L'oggetto relativo
all'eccezione, contiene un campo (currentOwner) che indica anche
il nome dell'applicazione che attualmente è in possesso della porta.
Il metodo open()
permette di impossessarsi della porta relativa all'oggetto CommPortIdentifier
su cui tale metodo è invocato. Esso permette di specificare il nome
dell'applicazione che richiede la porta, ed un timeout passato il
quale, se la porta nel frattempo non si è resa disponibile, il metodo
esce e solleva l'eccezione. Il metodo open() ritorna un oggetto
javax.comm.CommPort
rappresentativo di una porta di cui, attraverso il metodo getPortType(),
è possibile di conoscerne il tipo (SERIAL o PARALLEL). Dell'oggetto
CommPort
ottenuto dal metodo open() si farà, in base al tipo, un cast
con la classe javax.comm.SerialPort o javax.comm.ParallelPort.
Gestire la contesa
di una porta tra applicazioni diverse
Le JCA permettono
di gestire le eventuali contese relative all'acquisizione del controllo
sulle porte. Questo è ottenuto utilizzando l'ormai collaudata gestione
degli eventi per delega. L'oggetto CommPortIdentifier dispone dei
metodi che l'identificano come sorgente di eventi di tipo PortOwnership
che contengono informazioni relativamente alle richieste di ownership
o chiusure delle porte. Se un oggetto vuole essere notificato delle richieste
o dei rilasci relativi alle porte, implementerà l'interfaccia javax.comm.CommPortOwnershipListener
e si registrerà come ascoltatore all'oggetto CommPortIdentifier
attraverso
il metodo addCommPortOwnershipListener(). L'interfaccia prevede
la definizione del metodo ownershipChange(int tipo) il cui parametro
è un valore indicativo del tipo di evento. I tipi di eventi possibili
sono:
-
PORT_OWNERSHIP_REQUESTED
: Un evento di questo tipo indica che è stata inoltrata una richiesta
di possesso della porta. In questa fase è importante il timeout.
Supponiamo che l'applicazione A sia in possesso della porta COM3. Di seguito
l'applicazione B vuole la stessa porta ed eseguirà una open()
sull'oggetto
CommPortIdentifier descrittore della stessa. L'applicazione
B starà in attesa della porta COM3 un tempo specificato nel secondo
parametro (il
timeout appunto) del metodo open(). A questo
punto, un oggetto in ascolto delle varie richieste può gestire l'assegnazione
della porta COM3. Non appena l'applicazione B inoltra la richiesta della
porta, tale oggetto ne è notificato attraverso un evento di tipo
PORT_OWNERSHIP_REQUESTED.
L'oggetto CommPortIdentifier che notifica l'evento sarà rappresentativo
della porta. Se si vuole che l'applicazione B assuma il controllo della
porta COM3, basterà togliere il controllo all'applicazione A con
una close(). Scaduto il timeout, l'applicazione B verrà
in possesso della porta. Nel caso in cui non si volesse concedere la porta
all'applicazione B, basterà ignorare l'evento.
-
PORT_OWNED : Questo
evento viene notificato dall'oggetto CommPortIdentifier descrittore
di una porta, quando la stessa è stata ottenuta da un'applicazione.
-
PORT_UNOWNED : Questo
evento viene generato nel caso in cui la porta sia stata liberata.
Attraverso una gestione
ad eventi per delega, è quindi possibile gestire l'assegnazione
delle varie porte alle varie applicazioni.
Gestione delle
porte: oggetto CommPort
Come visto,
attraverso il metodo open() dell'oggetto CommPortIdentifier descrittore
di una porta, è possibile ottenerne la ownership. Il metodo
open()
ritorna, infatti, un oggetto di tipo javax.comm.CommPort.
Questa
classe astratta raggruppa l'insieme delle operazioni relative sia ad una
porta seriale che parallela. I metodi più significativi sono quelli
che permettono di:
-
Chiudere e liberare
una porta. Questa operazione, ottenuta con il metodo close(), genera
un evento PortOwnership di tipo PORT_UNOWNED.
-
Impostare le dimensioni
dei buffer di lettura e di scrittura.
-
Ottenere un riferimento
all'InputStream ed all'OutputStream di una porta per poter
comunicare con essa.
Dall'ultimo punto
si intuisce il meccanismo attraverso il quale si interagisce con ciascuna
porta. Per inviare dei dati su una porta, basterà ottenerne l'OutputStream
su
cui scrivere. Analogamente per la lettura, basterà ottenere un riferimento
all'oggetto InputStream, e leggere da esso.
Dall'oggetto
CommPort,
dopo aver testato il tipo di porta ottenuta attraverso il metodo getPortType(),
basterà fare una operazione di cast con la classe SerialPort
o ParallelPort per la gestione di una porta Seriale o Parallela
rispettivamente.
Le porte seriali
Nel caso in
cui il tipo della porta ottenuta sia SERIAL, basterà fare una operazione
di cast dell'oggetto
CommPort ottenuto dalla open() con la
classe SerialPort. Vediamo l'utilizzo di tale classe attraverso
la descrizione di una operazione di lettura e di scrittura sulla porta.
-
Scrittura su
porta seriale
Effettuare una
operazione di scrittura sulla porta seriale è molto semplice. Le
operazioni da effettuare sono quelle del Listato1.
Listato 1 - Scrittura
su Seriale
package
dev.javacomm;
import
javax.comm.*;
import
java.io.*;
// Questa classe permette
di scrivere sulla porta
// seriale il cui nome
è specificato nel costruttore
// della stessa.
//
public class ScriviSuSeriale
{
//
Crea un oggetto che scrive il valore della stringa
//
message sulla porta seriale port.
//
public
ScriviSuSeriale(String port, String message){
try{
// Prendiamo il descrittore della porta scelta.
// Nel caso in cui la porta non esista verrà
// sollevata una eccezione di tipo
// NoSuchPortException.
CommPortIdentifier id_porta
d_porta= CommPortIdentifier.getPortIdentifier(port);
// Proviamo a prendere l'ownership della porta data.
// Nel caso in cui la porta sia già occupata verrà
// sollevata una eccezione
// di tipo PortInUseException
CommPort porta=id_porta.open("ScriviSuSeriale",200);
// Otteniamo un riferimento all'OutputStream
// della porta
OutputStream out=porta.getOutputStream();
// Impostiamo le proprietà di comunicazione nel
// seguente modo:
// baudrate 9600
// dataBits DATABITS_8
// stopBits DATABITS_1
// parity PARITY_NONE
// Nel caso in cui i valori impostati non siano
// supportati verrà sollevata una eccezione di
// tipo UnsupportedCommOperationException
SerialPort
porta_seriale=(SerialPort)porta;
porta_seriale.setSerialPortParams(9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
//
Procediamo, quindi, alla scrittura del
//
messaggio sulla porta
PrintStream
ps= new PrintStream(out);
ps.print(message);
Chiudiamo
la porta
ps.close();
porta.close();
}
catch(NoSuchPortException
ne){
System.out.println("La porta "+port+" non e' presente");
}catch(PortInUseException
pe){
System.out.println("La porta "+port+" e' occupata da
"+pe.currentOwner);
}catch(UnsupportedCommOperationException
ue){
System.out.println("La porta non supporta le proprietà
impostate");
}catch(IOException
ioe){
System.out.println("Errore di IO");
}//
fine try/catch
}// fine
public
static void main(String[] args){
if(args.length<2)
System.out.println("Usage java ScriviSuSeriale <port name> <message>");
else{
ScriviSuSeriale scrivi= new ScriviSuSeriale(args[0],args[1]);
}//
fine else
}// fine
}//
fine
Il primo passo
consiste nell'ottenere un riferimento all'identificatore della porta. Questo
si ottiene attraverso il metodo getPortIdentifier(). Se la porta
esiste, e quindi non sono state sollevate eccezioni, si esegue una open()
per diventarne l'owner. Se essa è disponibile si ottiene
l'oggetto JavaComm dal quale si ottiene il riferimento allo stream
di scrittura. Non resta che impostare i tradizionali parametri della porta
seriale (baudrate, bit dati, bit stop e parità) attraverso il metodo
setSerialPortParameter()
e scrivere sullo stream attraverso i comuni metodi. Per l'impostazione
delle proprietà della porta seriale è bene utilizzare le
costanti static definite nella stessa classe
SerialPort.
-
Lettura da seriale
La lettura da
porta seriale avviene attraverso la gestione di eventi di tipo javax.comm.SerialPortEvent.
Se osserviamo il Listato2,
Listato
2 - Lettura dalla Seriale
package
dev.javacomm;
import
javax.comm.*;
import
java.io.*;
/*
Questa classe permette di leggere dalla porta seriale
il
cui nome è specificato nel costruttore della stessa.
*/
public
class LeggiDaSeriale extends Thread implements SerialPortEventListener
{
//
Riferimento all'InputStream da cui leggere
private
InputStream in;
//
Crea un oggetto che legge dalla porta seriale
//
specificata nel costruttore
public
LeggiDaSeriale(String port){
try{
//
Prendiamo il descrittore della porta scelta.
//
Nel caso in cui la porta non esista verrà sollevata una
//
eccezione di tipo NoSuchPortException.
CommPortIdentifier
id_porta;
id_porta=CommPortIdentifier.getPortIdentifier(port);
//
Proviamo a prendere l'ownership della porta data.
//
Nel caso in cui la porta sia già occupata verrà sollevata
//
una eccezione di tipo PortInUseException
CommPort
porta=id_porta.open("LeggiDaSeriale",200);
//
Otteniamo un riferimento all'InputStream della porta
in=porta.getInputStream();
//
Impostiamo le proprietà di comunicazione nel
//
seguente modo:
//
baudrate 9600
//
dataBits DATABITS_8
//
stopBits DATABITS_1
//
parity PARITY_NONE
//
Nel caso in cui i valori impostati non
//
siano supportati verrà sollevata una eccezione di
//
tipo UnsupportedCommOperationException
SerialPort
porta_seriale=(SerialPort)porta;
porta_seriale.setSerialPortParams(9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
//
Ci poniamo in ascolto di eventi sulla porta.
//
Solamente un oggetto si può registrare come
//
ascoltatore per cui bisogna gestire l'eccezione
//
TooManyListenersException
porta_seriale.addEventListener(this);
//
Avviamo il processo di lettura
start();
}
catch(NoSuchPortException
ne){
System.out.println("La porta "+port+" non e' presente");
}
catch(PortInUseException
pe){
System.out.println("La porta "+port+" e' occupata da
"+pe.currentOwner);
}
catch(UnsupportedCommOperationException
ue){
System.out.println("La porta non supporta le proprietà
impostate");
}
catch(IOException
ioe){
System.out.println("Errore di IO");
}
catch(java.util.TooManyListenersException
tme){
System.out.println("Si può registrare solamente un
ascoltatore");
}
//
fine try/catch
}
//
Rappresenta il corpo del Thread di lettura
public
void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {}
}//
fine run
public
void serialEvent(SerialPortEvent event) { switch(event.getEventType())
{
case
SerialPortEvent.BI:
case
SerialPortEvent.OE:
case
SerialPortEvent.FE:
case
SerialPortEvent.PE:
case
SerialPortEvent.CD:
case
SerialPortEvent.CTS:
case
SerialPortEvent.DSR:
case
SerialPortEvent.RI:
case
SerialPortEvent.OUTPUT_BUFFER_EMPTY:
break;
case
SerialPortEvent.DATA_AVAILABLE:
byte[] readBuffer = new byte[20];
try {
while (in.available() > 0){
int numBytes = in.read(readBuffer);
}
System.out.print(new String(readBuffer));
}
catch (IOException e) {}
break;
}//
fine switch
}//
fine serialEvent
public
static void main(String[] args){
if(args.length<2)
System.out.println("Usage java LeggiDaSeriale
<port name>");
else{
LeggiDaSeriale leggi= new LeggiDaSeriale(args[0]);
}// fine else
}//
fine main
}//
fine
notiamo che
la classe LeggiDaSeriale implementa l'interfaccia SerialPortEventListener
e si registra come ascoltatore della porta. Non appena sulla porta seriale
in esame si rendono disponibili delle informazioni, di dati o di controllo,
viene generato un evento di tipo SerialPortEvent. L'insieme della
costati static della classe SerialPortEvent ci permettono
di stabilire il tipo di informazione e di gestirla di conseguenza. Ad esempio,
se l'evento è di tipo DATA_AVAILABLE significa che ci sono
nuovi dati da leggere dallo stream. Se il tipo di evento è PE significa
che si è verificato un errore di parità, ecc.
Gestione porte parallele
Per inviare
dei dati attraverso una porta parallela si segue un procedimento analogo
(Listato 3) a quanto fatto per le porte seriali ovvero:
-
Si ottiene un riferimento
all'oggetto
CommPortIdentifier descrittivo della porta.
-
Se il tipo della
porta è PARALLEL si fa una operazione di cast dell'oggetto CommPort
ottenuto attraverso il metodo open(), con la classe ParallelPort.
-
Si ottiene un riferimento
agli stream per la lettura o scrittura attraverso porta
-
Si impostano le
proprietà della porta attraverso il metodo setMode()
-
Si inviano o leggono
dati dalla porte come si fa comunemente con gli stream.
Listato
3 - Scrittura su Parallela
package
dev.javacomm;
import
javax.comm.*;
import
java.io.*;
//
Questa classe permette di scrivere sulla porta parallela
//
il cui nome è specificato nel costruttore della stessa.
public
class ScriviSuParallela {
//
Crea un oggetto che scrive il valore della stringa
//
message sulla porta parallela port
public
ScriviSuParallela(String port, String message){
try{
//Prendiamo il descrittore della porta scelta.
// Nel caso in cui la porta non esista verrà sollevata
// una eccezione
// di tipo NoSuchPortException
CommPortIdentifier
id_porta;
id_porta=
CommPortIdentifier.getPortIdentifier(port);
//
Proviamo a prendere l'ownership della porta data.
//
Nel caso in cui la porta sia già occupata verrà
//
sollevata una eccezione di tipo PortInUseException
CommPort
porta=id_porta.open("ScriviSuParallela",200);
//
Otteniamo un riferimento all'OutputStream della porta
OutputStream
out=porta.getOutputStream();
//
Impostiamo il modo di comunicazione in LPT_MODE_ECP
//
ovvero
//
Enhanced capabilities port
//
Nel caso in cui i valori impostati non siano supportati
//
verrà sollevata una eccezione di tipo
//
UnsupportedCommOperationException
ParallelPort porta_parallela=(ParallelPort)porta;
porta_parallela.setMode(ParallelPort.LPT_MODE_SPP);
// Procediamo, quindi, alla scrittura del messaggio
// sulla porta
PrintStream ps= new PrintStream(out);
ps.print(message);
// Chiudiamo la porta
ps.close();
porta.close();
System.out.println("Porta chiusa");
}
catch(NoSuchPortException
ne){
System.out.println("La porta "+port+" non e' presente");
}
catch(PortInUseException
pe){
System.out.println("La porta "+port+" e' occupata da "+
pe.currentOwner);
}
catch(UnsupportedCommOperationException
ue){
System.out.println("La porta non supporta le proprietà
impostate");
}
catch(IOException
ioe){
System.out.println("Errore di IO");
}//
fine try/catch
}//
fine
public
static void main(String[] args){
if(args.length<2)
System.out.println("Usage java ScriviSuParallela <port name>
<message>");
else{
ScriviSuParallela scrivi= new ScriviSuParallela(args[0],args[1]);
}// fine else
}//
fine
}//
fine classe
Se esaminiamo
nel dettaglio i metodi della classe ParallelPort, notiamo come essa
sia predisposta all'utilizzo della parallela per la comunicazione con la
stampante. Esistono, infatti, metodi come isPrinterBusy() o isPrinterSelected()
che permettono di sapere, rispettivamente, se la stampante collegata alla
porta specificata è occupata o se è selezionata.
Anche la lettura
da parallela avviene in modo simile al caso della porta seriale solamente
che, in questo caso, gli eventi da gestire saranno di tipo ParallelPortEvent
che conterranno informazioni sulle variazioni dei dati.
Conclusioni
In questo articolo
abbiamo esaminato nel dettaglio il funzionamento delle JCA che si rivelano
un ottimo strumento per la scrittura di applicazioni Java portabili che
utilizzino le porte seriali e parallele. Per ora queste classi supportano
le porte seriali RS232 e le porte parallele IEEE1284 ma la JavaSoft
ha promesso, entro breve tempo, la disponibilità di classi per la
gestione di protocolli di comunicazione di livello più alto quali
i classici X/Y/ZModem.
Bibliografia
[1] Documentazione
JavaComm
presso http://java.sun.com
|