Java/Comunicazione seriale: differenze tra le versioni

Wikibooks, manuali e libri di testo liberi.
Contenuto cancellato Contenuto aggiunto
Corretto: "necessario"
Nessun oggetto della modifica
Etichetta: Editor wikitesto 2017
Riga 55: Riga 55:
Il contenuto del file di proprietà è in genere una sola riga, il nome della classe Java con il driver nativo, ad esempio:
Il contenuto del file di proprietà è in genere una sola riga, il nome della classe Java con il driver nativo, ad esempio:


<source lang=java> driver = com.sun.comm.Win32Driver </ source>
<syntaxhighlight lang=java> driver = com.sun.comm.Win32Driver </ source>


Il seguente è un hack che permette di distribuire JavaComm via Web Start ignorando che il cervello di proprietà morti file. Si è gravi inconvenienti, e potrebbe non riuscire con le nuove uscite JavaComm - Sun dovrebbe mai venire intorno e fare una nuova versione.
Il seguente è un hack che permette di distribuire JavaComm via Web Start ignorando che il cervello di proprietà morti file. Si è gravi inconvenienti, e potrebbe non riuscire con le nuove uscite JavaComm - Sun dovrebbe mai venire intorno e fare una nuova versione.
Riga 61: Riga 61:
In primo luogo, disattivare il gestore della sicurezza. Alcuni programmatori Doofus a Sun ha deciso che sarebbe bello nuovo e di nuovo per verificare l'esistenza del temuto''javax.comm.properties''file, anche dopo che è stato caricato inizialmente, per nessun altro motivo apparente, rispetto al controllo per la file.
In primo luogo, disattivare il gestore della sicurezza. Alcuni programmatori Doofus a Sun ha deciso che sarebbe bello nuovo e di nuovo per verificare l'esistenza del temuto''javax.comm.properties''file, anche dopo che è stato caricato inizialmente, per nessun altro motivo apparente, rispetto al controllo per la file.


<source lang=java> System.setSecurityManager (null); </ source>
<syntaxhighlight lang=java> System.setSecurityManager (null); </ source>


Poi, quando si inizializza l'API JavaComm, inizializzare il driver manualmente:
Poi, quando si inizializza l'API JavaComm, inizializzare il driver manualmente:


<source lang=java> driverName String = "com.sun.comm.Win32Driver"; / / o ottenere come una proprietà JNLP
<syntaxhighlight lang=java> driverName String = "com.sun.comm.Win32Driver"; / / o ottenere come una proprietà JNLP
 CommDriver CommDriver = (CommDriver) Class.forName (driverName). NewInstance ();
 CommDriver CommDriver = (CommDriver) Class.forName (driverName). NewInstance ();
 commDriver.initialize ();</ source>
 commDriver.initialize ();</ source>
Riga 115: Riga 115:


Enumerating and selecting the desired port identifier is typically done in one loop:
Enumerating and selecting the desired port identifier is typically done in one loop:
<source lang=java>
<syntaxhighlight lang=java>
import javax.comm.*;
import javax.comm.*;
import java.util.*;
import java.util.*;
Riga 156: Riga 156:
//
//
// Use port identifier for acquiring the port
// Use port identifier for acquiring the port
//</source>
//</syntaxhighlight>
...
...


Riga 162: Riga 162:


Once a port identifier has been found, it can be used to acquire the desired port:
Once a port identifier has been found, it can be used to acquire the desired port:
<source lang=java>
<syntaxhighlight lang=java>
//
//
// Use port identifier for acquiring the port
// Use port identifier for acquiring the port
Riga 179: Riga 179:
// Now we are granted exclusive access to the particular serial
// Now we are granted exclusive access to the particular serial
// port. We can configure it and obtain input and output streams.
// port. We can configure it and obtain input and output streams.
//</source>
//</syntaxhighlight>
...
...


Riga 187: Riga 187:


As part of the initialization process the Input and Output streams for communication will be configured in the example.
As part of the initialization process the Input and Output streams for communication will be configured in the example.
<source lang=java>
<syntaxhighlight lang=java>
import java.io.*;
import java.io.*;
...
...
Riga 235: Riga 235:
if (is != null) is.close();
if (is != null) is.close();
if (os != null) os.close();
if (os != null) os.close();
if (port != null) port.close();</source>
if (port != null) port.close();</syntaxhighlight>


=== Simple Data Transfer ===
=== Simple Data Transfer ===
Riga 250: Riga 250:
* Explain how to mix binary and character I/O over the same stream
* Explain how to mix binary and character I/O over the same stream
* Fix the example to use streams}}
* Fix the example to use streams}}
<source lang=java>
<syntaxhighlight lang=java>
// Write to the output
// Write to the output
os.print("AT");
os.print("AT");
Riga 257: Riga 257:
is.readLine(); // First read will contain the echoed command you sent to it. In this case: "AT"
is.readLine(); // First read will contain the echoed command you sent to it. In this case: "AT"
is.readLine(); // Second read will remove the extra line feed that AT generates as output
is.readLine(); // Second read will remove the extra line feed that AT generates as output
</syntaxhighlight>
</source>


==== Simple Reading of Data (Polling) ====
==== Simple Reading of Data (Polling) ====
Riga 263: Riga 263:
If you correctly carried out the write operation (see above) then the read operation is as simple as one command:
If you correctly carried out the write operation (see above) then the read operation is as simple as one command:


<source lang=java> // Read the response
<syntaxhighlight lang=java> // Read the response
String response = is.readLine(); // if you sent "AT" then response == "OK"</source>
String response = is.readLine(); // if you sent "AT" then response == "OK"</syntaxhighlight>


==== Problems with the simple Reading / Writing ====
==== Problems with the simple Reading / Writing ====
Riga 308: Riga 308:


==== Setting up a serial Event Handler ====
==== Setting up a serial Event Handler ====
<source lang=java>
<syntaxhighlight lang=java>
import javax.comm.*;
import javax.comm.*;


Riga 398: Riga 398:
}
}
}
}
</syntaxhighlight>
</source>
Once the listener is implemented, it can be used to listen to particular serial port events. To do so, an instance of the listener needs to be added to the serial port. Further, the reception of each event type needs to be requested individually.
Once the listener is implemented, it can be used to listen to particular serial port events. To do so, an instance of the listener needs to be added to the serial port. Further, the reception of each event type needs to be requested individually.
<source lang=java>
<syntaxhighlight lang=java>
SerialPort port = ...;
SerialPort port = ...;
...
...
Riga 433: Riga 433:
port.notifyOnRingIndicator(true);
port.notifyOnRingIndicator(true);
<- other events not used in this example */
<- other events not used in this example */
</source>
</syntaxhighlight>




Riga 453: Riga 453:


A ring buffer as such is nothing special, and has no special properties regarding threading. It is just that this simple data structure is used here to provide data buffering. The implementation is done so that access to this data structure has been made thread safe.
A ring buffer as such is nothing special, and has no special properties regarding threading. It is just that this simple data structure is used here to provide data buffering. The implementation is done so that access to this data structure has been made thread safe.
<source lang=java>
<syntaxhighlight lang=java>
/**
/**
* Synchronized ring buffer.
* Synchronized ring buffer.
Riga 607: Riga 607:
}
}
}
}
</syntaxhighlight>
</source>
With this ring buffer one can now hand over data from one thread to another in a controlled way. Any other thread-safe, non-blocking mechanism would also do. The key point here is that the write does not block when the buffer is full and also does not block when there is nothing to read.
With this ring buffer one can now hand over data from one thread to another in a controlled way. Any other thread-safe, non-blocking mechanism would also do. The key point here is that the write does not block when the buffer is full and also does not block when there is nothing to read.


Riga 617: Riga 617:


The skeleton event listener proposed a method <code>outputBufferEmpty()</code>, which could be implemented as it follows.
The skeleton event listener proposed a method <code>outputBufferEmpty()</code>, which could be implemented as it follows.
<source lang=java>
<syntaxhighlight lang=java>
RingBuffer dataBuffer = ... ;
RingBuffer dataBuffer = ... ;


Riga 630: Riga 630:
{{TODO}}
{{TODO}}
}</source>
}</syntaxhighlight>


==== Reading of Data ====
==== Reading of Data ====


The following example assumes that the data's destination is some file. Whenever data becomes available it is fetched from the serial port and written to the file. This is an extremely simplified view, because in reality one would need to check the data for an end-of-file indication to, for example, return to the modem command mode.
The following example assumes that the data's destination is some file. Whenever data becomes available it is fetched from the serial port and written to the file. This is an extremely simplified view, because in reality one would need to check the data for an end-of-file indication to, for example, return to the modem command mode.
<source lang=java>
<syntaxhighlight lang=java>
import javax.comm.*;
import javax.comm.*;


Riga 678: Riga 678:
if (is != null) is.close();
if (is != null) is.close();
if (port != null) port.close();
if (port != null) port.close();
}</source>
}</syntaxhighlight>


=== Handling multiple Ports in one Application ===
=== Handling multiple Ports in one Application ===

Versione delle 19:15, 28 gen 2021

Java
Linguaggio Java
Linguaggio Java
categoria · sviluppo · modifica

Come posso contribuire?
→ Vai su Wikiversity

Guida alla programmazioneJava/Introduzione - Specifica di linguaggioJava/Tipi di dato - Libreria standard

  1. Comunicazione serialeJava/Comunicazione seriale
  2. Stream
  3. Input e output su consoleJava/Input e output
  4. Java Collections Framework
  5. AWT e SwingJava/AWT e Swing
  6. Reflection
  7. SerializzazioneJava/Serializzazione
  8. SocketJava/Socket

Utilizzare Java per la communicazione seriale

Introduzione

Il linguaggio di programmazione Java si è rivelato molto utilizzato negli ultimi anni e inevitabilmente è arrivata la richiesta di poter programmare le interfacce seriali in Java. In ogni caso, data l'indipendenza di Java dalla piattaforma, è necessaria una API standardizzata con implementazioni che tengono conto della piattaforma su cui si lavora.

Sfortunatamente, la Sun non ha prestato molta attenzione alla comunicazione seriale in Java, esiste una API definita dalla Sun per tale scopo, JavaComm, ma tale API non è prevista all'interno della Java Standard Edition. In particolare alla fine del 2005 la Sun fece sparire il supporto per Windows alla JavaComm. Sono disponibili implementazioni alternative a quella omessa dalla Sun. JavaComm non ha subito molta attività di manutenzione, la Sun ha fatto solo il minimo necessario, fatta eccezione per le pressioni degli aquirenti del client Sun Ray alle quali Sun ha risposto adattando il supporto per Windows per la JavaComm unicamente alla propria piattaforma, dimenticandosi del supporto per Windows.

Questa situazione, e il fatto che la Sun originariamente non ha proveduto ad un'implemntazione di JavaComm per Linux, ha diretto verso lo sviluppo di una libreria gratuita RxTx. RxTx è disponibile, non solo per Linux, ma per diverse piattaforme. Può essere usata con o senza l'appoggio della JavaComm(la RxTx fornisce i suoi driver specifici). Quando vengono utilizzate assieme, il collegamento tra JavaComm e RxTx viene gestito dalla JCL (JavaComm for Linux). La JCL fa parte della RxTx.

Il disinteresse della Sun per JavaComm e il modello di programmazione di quest'ultima contribuirono a raggiungere la reputazione di API inusabile. Fortunatamente in realtà non è così, tale reputazione si è diffusa tra la gente che non ha basi solide nella programmazione delle interfacce seriali e che attribuisce i propri errori all'API.

RxTx - se non è utilizzata come un driver JavaComm - fornisce un'interfaccia completa, ma che purtroppo non segue gli standard. RxTx supporta più piattaforme rispetto a JavaComm. Recentemente, RxTx è stata utilizzata per fornire le stesse intefacce fornite dalla JavaComm, tuttavia i nomi dei packages non corrispondono a quelli dei packages della Sun.

Quale libreria conviene usare? Se si cerca la massima portabilità, allora la JavaComm è una buona scelta. Se non esiste il supporto della JavaComm per una particolare piattaforma allora è bene passare alla RxTx, utilizzandola come driver per quella piattaforma per poi appoggiarsi a JavaComm. Utilizzando la JavaComm si può ottenere supporto per tutte le piattaforme che sono direttamente citate da quelle dichiarate dalla Sun e in aggiunta attraverso RxTx utilizzando JCL. In questo modo l'applicazione non necessita di cambiamenti, e può funzionare utilizzando una sola interfaccia , ovvero la JavaComm.

Questo modulo parla sia della JavaComm e della RxTx. Principalmente ha l'obbiettivo di dimostrare dei concetti chiave. Per coloro che vogliono copiare ad occhi chiusi il codice ci sono riferimenti al codice esempio fornito nei packages. Coloro che vogliono sapere quello che fanno potranno trovare informazioni importanti.

Requisiti

  • Conoscenza di base della comunicazione e programmazione seriale.
  • Avere a portata di mano la documentazione del dispositivo con cui si vuole dialogare (es il modem).
  • Configurare l'hardware coinvolto e testare l'ambiente di lavoro.
  • Scarica l'implementazione delle API necessarie per il sistema operativo utilizzato.
  • Letture
    • guida all'installazione di JavaComm e/o di RxTx (riportate di seguito)
    • documentazione sulle API
    • codice esempio

Installazione

Problemi Generali

Sia JavaComm che RxTX presentano alcune particolarita' durante l'installazione. Si consiglia di seguire con estrema attenzione le istruzioni di installazione. Se viene detto che un file jar o una library condivisa devono andare in una directory particolare, cosí si deve fare! Se l'istruzione dice che un particolare file o una device necessita di avere una specifica proprietà o diritti di accesso, questo significa anche serietà. Molti semplici problemi di installazione derivano da non seguire le istruzioni precisamente.

Si deve esplicitamente notare che qualche versione di JavaComm avviene con due istruzioni di installazione. Una per Java 1.2 e versioni successive, una per Java 1.1. Usando quella sbagliata risulterà un'installazione non funzionante. Da un lato, qualche versione/builds/packages di RxTx avviene con istruzioni incomplete. In tali casi il corrispondente codice sorgente della distribuzione di RxTx necessita di essere verificata, i quali dovrebbero contenere le istruzioni complete.

Dovrebbe ulteriormente essere notato che è inoltre tipico dell'installazione del Windows JDK avvenire con fino a tre VM, e così tre indici di estensione.

  • Uno ha parte nel JDK,
  • Uno ha parte nel JRE privato il quale con il JDK fa eseguire i JDK tools, e
  • uno ha parte nel JRE pubblico il quale con il JDK fa eseguire le applicazioni

Alcuni persino sostengono avere un quarto JRE in qualche luogo nella gerarchia indice di Windows

JavaComm dovrebbe essere installato almeno come estensione nel JDK ed in tutto il JRE pubblico.

Webstart

JavaComm

Un problema generale, sia perJavaCommeRxTxè, che si oppongono all'installazione via Java WebStart:

JavaComm' è noto, perché richiede un file chiamato' 'javax.comm.properties' 'per essere inserito nella directory lib JDK, qualcosa che non si può fare con Java WebStart. Ciò è particolarmente triste, perché il bisogno di quel file è il risultato di una progettazione inutili / decisioneJavaComme avrebbe potuto facilmente essere evitata mediante laJavaCommdesigner. Sun rifiuta costantemente per correggere questo errore, citando il meccanismo è essenziale. Che è, sono sdraiato con i denti quando si tratta diJavaComm, soprattutto, perché Java per lungo tempo ha una architettura service provider esattamente destinati a tali scopi.

Il contenuto del file di proprietà è in genere una sola riga, il nome della classe Java con il driver nativo, ad esempio:

 driver = com.sun.comm.Win32Driver </ source>

Il seguente è un hack che permette di distribuire JavaComm via Web Start ignorando che il cervello di proprietà morti file. Si è gravi inconvenienti, e potrebbe non riuscire con le nuove uscite JavaComm - Sun dovrebbe mai venire intorno e fare una nuova versione.

In primo luogo, disattivare il gestore della sicurezza. Alcuni programmatori Doofus a Sun ha deciso che sarebbe bello nuovo e di nuovo per verificare l'esistenza del temuto''javax.comm.properties''file, anche dopo che è stato caricato inizialmente, per nessun altro motivo apparente, rispetto al controllo per la file.

<syntaxhighlight lang=java> System.setSecurityManager (null); </ source>

Poi, quando si inizializza l'API JavaComm, inizializzare il driver manualmente:

<syntaxhighlight lang=java> driverName String = "com.sun.comm.Win32Driver"; / / o ottenere come una proprietà JNLP
 CommDriver CommDriver = (CommDriver) Class.forName (driverName). NewInstance ();
 commDriver.initialize ();</ source>

===== RxTx =====

''RxTx'' on some platforms requires changing ownership and access rights of serial devices. This is also something which can't be done via WebStart.

At startup of your program you could ask the user to perform the necessary setup as super user.

Further, RxTx has a pattern matching algorithm for identifying "valid" serial device names. This often breaks things when one wants to use non-standard devices, like USB-to-serial converters. This mechanism can be overridden by system properties. See the RxTx installation instruction for details.

== JavaComm API ==

=== Introduction ===

The official API for serial communication in Java is the JavaComm API. This API is not part of the standard Java 2 version. Instead, an implementation of the API has to be downloaded separately. Unfortunately, JavaComm has not much attention from Sun, and hasn't been really maintained for a long time. From time to time Sun does trivial bug-fixes, but doesn't do the long overdue main overhaul.

This section explains the basic operation of the JavaComm API. The provided source code is kept simple to demonstrate important point. It needs to be enhanced when used in a real application.

The source code in this chapter is not the only available example code. The JavaComm download comes with several examples. These examples almost contain more information about using the API than the API documentation. Unfortunately, Sun does not provide any real tutorial or some introductory text. Therefore, it is worth studying the example code to understand the mechanisms of the API. Still, the API documentation should be studied, too. But the best way is to study the examples and play with them. Due to the lack of easy-to-use application and people's difficulty in understanding the API's programming model, the API is often bad-mouthed. The API is better than its reputation, and functional. But no more.

The API uses a callback mechanism to inform the programmer about newly arriving data. It is also a good idea to study this mechanism instead of relying on polling the port. Unlike other callback interfaces in Java (e.g. in the GUI), this one only allows one listener listening to events. If multiple listeners require to listen to serial events, the one primary listener has to be implemented in a way that it dispatches the information to other secondary listeners.

=== Download & Installation ===

==== Download ====
Sun's [http://java.sun.com/products/javacomm/ JavaComm] webpage points to a [http://www.sun.com/download/products.xml?id=43208d3d download location]. Under this location Sun currently (2007) provides JavaComm 3.0 implementations for Solaris/SPARC, Solaris/x86, and Linux x86. Downloading requires to have registered for a Sun Online Account. The download page provides a link to the registration page. The purpose of this registration is unclear. One can download JDKs and JREs without registration, but for the almost trivial JavaComm Sun cites legal and governmental restrictions on the distribution and exportation of software.

The Windows version of JavaComm is no longer officially available, and Sun has - against their own product end-of-live policy - not made it available in the [http://java.sun.com/products/archive/ Java products archive]. However, as of this writing (2007) the 2.0 Windows version is still downloadable from Sun's own "Java Shop" [http://javashoplm.sun.com/ECom/docs/Welcome.jsp?StoreId=22&PartDetailId=7235-javacomm-2.0-spec-oth-JSpec&SiteId=JSC&TransactionId=noreg]. The so-called ''Specification'' also contains the reference implementation.

==== Installation ====

Follow the installation instructions that come with the download. Some versions of JavaComm 2.0 come with two installation instructions. The most obvious of the two instructions is unfortunately the wrong one, intended for ancient Java 1.1 environments. The information referring to the also ancient Java 1.2 (jdk1.2.html) is the right one.

Particularly Windows users are typically not aware that they have copies of the same VM installed in several locations (typically three to four). Some IDEs also like to come with own, private JRE/JDK installations, as do some Java applications. The installation needs to be repeated for every VM installation (JDKs and JREs) which should be used in conjunction with the development and execution of a serial application.

IDEs typically have IDE-specific ways of how a new library (classes and documentation) is made known to the IDE. Often a library like JavaComm not only needs to be made known to the IDE as such, but also to each project that is supposed to use the library. Read the IDE's documentation. It should be noted that the old JavaComm 2.0 version comes with JavaDoc API documentation that is structured in the historic Java 1.0 JavaDoc layout. Some modern IDEs are no longer aware of this structure and can't integrate the JavaComm 2.0 documentation into their help system. In such a case an external browser is needed to read the documentation (a recommended activity ...).

Once the software is installed it is recommended to examine the samples and JavaDoc directories. It makes sense to build and run one of the sample applications to verify that the installation is correct. The sample applications typically need some minor adaptations in order to run on a particular platform (e.g. changes to the hard-coded com port identifiers). It is a good idea to have some serial hardware, like cabling, a null modem, a breakout box, a real modem, PABX and others available when trying out a sample application. [[Serial_Programming:RS-232 Connections]] and [[Serial_Programming:Modems and AT Commands]] provide some information on how to set up the hardware part of a serial application development environment.

=== Finding the desired serial Port ===

The first three things to do when programming serial lines with JavaComm are typically 

#to enumerate all serial ports (port identifiers) available to JavaComm, 
#to select the desired port identifier from the available ones, and
#to acquire the port via the port identifier.

Enumerating and selecting the desired port identifier is typically done in one loop:
<syntaxhighlight lang=java>
 import javax.comm.*;
 import java.util.*;
 ...
 //
 // Platform specific port name, here a Unix name
 //
 // NOTE: On at least one Unix JavaComm implementation JavaComm 
 //       enumerates the ports as "COM1" ... "COMx", too, and not
 //       by their Unix device names "/dev/tty...". 
 //       Yet another good reason to not hard-code the wanted
 //       port, but instead make it user configurable.
 //
 String wantedPortName = "/dev/ttya"; 
 //
 // Get an enumeration of all ports known to JavaComm
 //
 Enumeration portIdentifiers = CommPortIdentifier.getPortIdentifiers();
 //
 // Check each port identifier if 
 //   (a) it indicates a serial (not a parallel) port, and
 //   (b) matches the desired name.
 //
 CommPortIdentifier portId = null;  // will be set if port found
 while (portIdentifiers.hasMoreElements())
 {
     CommPortIdentifier pid = (CommPortIdentifier) portIdentifiers.nextElement();
     if(pid.getPortType() == CommPortIdentifier.PORT_SERIAL &&
        pid.getName().equals(wantedPortName)) 
     {
         portId = pid;
         break;
     }
 }
 if(portId == null)
 {
     System.err.println("Could not find serial port " + wantedPortName);
     System.exit(1);
 }
 //
 // Use port identifier for acquiring the port
 //
...

JavaComm itself obtains the default list of available serial port identifiers from its platform-specific driver. The list is not really configurable via JavaComm. The method CommPortIdentifier.addPortName() is misleading, since driver classes are platform specific and their implementations are not part of the public API. Depending on the driver, the list of ports might be configurable / expendable in the driver. So if a particular port is not found in JavaComm, sometimes some fiddling with the driver can help.

Once a port identifier has been found, it can be used to acquire the desired port:

 //
 // Use port identifier for acquiring the port
 //
 SerialPort port = null;
 try {
     port = (SerialPort) portId.open(
         "name", // Name of the application asking for the port 
         10000   // Wait max. 10 sec. to acquire port
     );
 } catch(PortInUseException e) {
     System.err.println("Port already in use: " + e);
     System.exit(1);
 }
 //
 // Now we are granted exclusive access to the particular serial
 // port. We can configure it and obtain input and output streams.
 //
...

Initialize a Serial Port

The initialization of a serial port is straight forward. Either individually set the communication preferences (baud rate, data bits, stop bits, parity) or set them all at once using the setSerialPortParams(...) convenience method.

As part of the initialization process the Input and Output streams for communication will be configured in the example.

 import java.io.*;
 ...

 //
 // Set all the params.  
 // This may need to go in a try/catch block which throws UnsupportedCommOperationException
 //
 port.setSerialPortParams(
     115200,
     SerialPort.DATABITS_8,
     SerialPort.STOPBITS_1,
     SerialPort.PARITY_NONE);

 //
 // Open the input Reader and output stream. The choice of a
 // Reader and Stream are arbitrary and need to be adapted to
 // the actual application. Typically one would use Streams in
 // both directions, since they allow for binary data transfer,
 // not only character data transfer.
 //
 BufferedReader is = null;
 PrintStream    os = null;

 try {
   is = new BufferedReader(new InputStreamReader(port.getInputStream()));
 } catch (IOException e) {
   System.err.println("Can't open input stream: write-only");
   is = null;
 }
 os = new PrintStream(port.getOutputStream(), true);

 // New Linuxes rely on UNICODE and it is possible you need to specify here the encoding scheme to be used
 // for example : 
 //     os = new PrintStream(port.getOutputStream(), true, "ISO-8859-1");
 // will ensure that you sent 8 bits characters on your port. Don't know about a modem accepting
 // UNICODE data for its commands... 
 //
 // Actual data communication would happen here
 // performReadWriteCode();
 //
 //

 // It is very important to close output/input streams as well as the port.
 // Otherwise Java, driver and OS resources are not released.
 //
 if (is != null) is.close();
 if (os != null) os.close();
 if (port != null) port.close();

Simple Data Transfer

Simple Writing of Data

Writing to a serial port is as simple as basic Java IO. However there are a couple of caveats to look out for if you are using the AT Hayes protocol:

  1. Don't use println (or other methods that automatically append "\n") on the OutputStream. The AT Hayes protocol for modems expects a "\r\n" as the delimiter (regardless of underlying operating system).
  2. After writing to the OutputStream, the InputStream buffer will contain a repeat of the command that was sent to it (with line feed), if the modem is set to echoing the command line, and another line feed (the answer to the "AT" command). So as part of the write operation make sure to clean the InputStream of this information (which can actually be used for error detection).
  3. When using a Reader/Writer (not a really good idea), at least set the character encoding to US-ASCII instead of using the platform's default encoding, which might or might not work.
  4. Since the main operation when using a modem is to transfer data unaltered, the communication with the modem should be handled via InputStream/OutputStream, and not a Reader/Writer.

Template:TODO

 // Write to the output 
 os.print("AT");
 os.print("\r\n"); // Append a carriage return with a line feed
 
 is.readLine(); // First read will contain the echoed command you sent to it. In this case: "AT"
 is.readLine(); // Second read will remove the extra line feed that AT generates as output

Simple Reading of Data (Polling)

If you correctly carried out the write operation (see above) then the read operation is as simple as one command:

 // Read the response
 String response = is.readLine(); // if you sent "AT" then response == "OK"

Problems with the simple Reading / Writing

The simple way of reading and/or writing from/to a serial port as demonstrated in the previous sections has serious drawbacks. Both activities are done with blocking I/O. That means, when there is

  • no data available for reading, or
  • the output buffer for writing is full (the device does not accept (any more) data),

the read or write method (os.print() or is.readLine() in the previous example) do not return, and the application comes to a halt. More precisely, the thread from which the read or write is done gets blocked. If that thread is the main application thread, the application freezes until the blocking condition is resolved (data becomes available for reading or device accepts data again).

Unless the application is a very primitive one, freezing of the application is not acceptable. For example, as a minimum some user interaction to cancel the communication should still be possible. What is needed is non-blocking I/O or asynchronous I/O. However, JavaComm is based on Java's standard blocking I/O system (InputStream, OutputStream), but with a twist, as shown later.

The mentioned "twist" is that JavaComm provides some limited support for asynchronous I/O via an event notification mechanism. But the general solution in Java to archive non-blocking I/O on top of the blocking I/O system is to use threads. Indeed, this is a viable solution for serial writing, and it is strongly recommended to use a separate thread to write to the serial port - even if the event notification mechanism is used, as explained later.

Reading could also be handled in a separate thread. However, this is not strictly necessary if the JavaComm event notification mechanism is used. So summarize:

Activity Architecture
reading use event notification and/or separate thread
writing always use separate thread, optionally use event notification

The following sections provide some details.

Event Driven Serial Communication

Introduction

The JavaComm API provides an event notification mechanism to overcome the problems with blocking I/O. However, in typical Sun half-arsed manner this mechanism is not without problems.

In principle an application can register event listeners with a particular SerialPort to be kept informed about important events happening on that port. The two most interesting event types for reading and writing data are

  • javax.comm.SerialPortEvent.DATA_AVAILABLE and
  • javax.comm.SerialPortEvent.OUTPUT_BUFFER_EMPTY.

But there are also two problems:

  1. Only one single event listener per SerialPort can be registered. This forces the programmer to write "monster" listeners, discriminating according to the event type.
  2. OUTPUT_BUFFER_EMPTY is an optional event type. Well hidden in the documentation Sun states that not all JavaComm implementations support generating events of this type.

Before going into details, the next section will present the principle way of implementing and registering a serial event handler. Remember, there can only be one handler at all, and it will have to handle all possible events.

Setting up a serial Event Handler

 import javax.comm.*;

 /**
  * Listener to handle all serial port events.
  * NOTE: It is typical that the SerialPortEventListener is implemented
  *       for the main class that is supposed to communicate with the
  *       device. That way it is easier to get access to the current
  *       state of communication.
  *
  *       However, for demonstration purposes this example implements a
  *       separate class.
  */ 
 class SerialListener implements SerialPortEventListener {
 
     /**
      * Handle serial events. Dispatches the event to event-specific
      * methods.
      * @param event The serial event
      */
     public void serialEvent(SerialPortEvent event){
 
         //
         // Dispatch event to individual methods, to avoid a
         // cluttered, messy switch/case statement.
         //
         switch(event.getEventType()) {
             case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                 outputBufferEmpty(event);
                 break;
 
             case SerialPortEvent.DATA_AVAILABLE:
                 dataAvailable(event);
                 break;

 /* Other events, not implemented here ->
             case SerialPortEvent.BI:
                 breakInterrupt(event);
                 break;

             case SerialPortEvent.CD:
                 carrierDetect(event);
                 break;

             case SerialPortEvent.CTS:
                 clearToSend(event);
                 break;

             case SerialPortEvent.DSR:
                 dataSetReady(event);
                 break;

             case SerialPortEvent.FE:
                 framingError(event);
                 break;

             case SerialPortEvent.OE:
                 overrunError(event);
                 break;

             case SerialPortEvent.PE:
                 parityError(event);
                 break;
             case SerialPortEvent.RI:
                 ringIndicator(event);
                 break;
 <- other events, not implemented here */

         }
     }

     /**
      * Handle output buffer empty events.
      * NOTE: The reception is of this event is optional and not
      *       guaranteed by the API specification.
      * @param event The output buffer empty event
      */
     protected void outputBufferEmpty(SerialPortEvent event) {
         // Implement writing more data here
     }

     /**
      * Handle data available events.
      *
      * @param event The data available event
      */
     protected void dataAvailable(SerialPortEvent event) {
         // implement reading from the serial port here
     }
 }

Once the listener is implemented, it can be used to listen to particular serial port events. To do so, an instance of the listener needs to be added to the serial port. Further, the reception of each event type needs to be requested individually.

SerialPort port = ...;
 ...
 //
 // Configure port parameters here. Only after the port is configured it
 // makes sense to enable events. The event handler might be called immediately
 // after an event is enabled.
 ...

 //
 // Typically, if the current class implements the SerialEventListener interface
 // one would call
 //        port.addEventListener(this);
 // but for our example a new instance of SerialListener is created:
 //
 port.addEventListener(new SerialListener());

 //
 // Enable the events we are interested in
 //
 port.notifyOnDataAvailable(true);
 port.notifyOnOutputEmpty(true);

 /* other events not used in this example ->
 port.notifyOnBreakInterrupt(true);
 port.notifyOnCarrierDetect(true);
 port.notifyOnCTS(true);
 port.notifyOnDSR(true);
 port.notifyOnFramingError(true);
 port.notifyOnOverrunError(true);
 port.notifyOnParityError(true);
 port.notifyOnRingIndicator(true);
 <- other events not used in this example */


Writing of Data

  • Demonstrate usage of a separate thread to fill port's write buffer, but avoid blocking.
  • Demonstrate usage of OUTPUT_BUFFER_EMPTY (and since this is event is optional, only as an alternative.


Setting up a separate Thread for Writing

Using a separate thread for writing has one purpose: Avoiding that the whole application blocks in case the serial port is not ready for writing.

A simple, thread-safe Ring Buffer Implementation

Using a separate thread for writing, separate from some main application thread, implies that there is some way to hand off the data which needs to be written from the application thread to the writing thread. A shared, synchronized data buffer, for example a byte[] should do. Further, there needs to be a way for the main application to determine if it can write to the data buffer, or if the data buffer is currently full. In case the data buffer is full it could indicate that the serial port is not ready, and output data has queued up. The main application will have to poll the availability of new space in the shared data buffer. However, between the polling the main application can do other things, for example updating a GUI, providing a command prompt with the ability to abort the sending, etc.

At first glance a PipedInputStream/PipedOutputStream pair seems like a good idea for this kind of communication. But Sun wouldn't be Sun if the a piped stream would actually be useful. PipedInputStream blocks if the corresponding PipedOutputStream is not cleared fast enough. So the application thread would block. Exactly what one wants to avoid by using the separate thread. A java.nio.Pipe suffers from the same problem. Its blocking behavior is platform dependent. And adapting the classic I/O used by JavaComm to NIO is anyhow not a nice task.

In this article a very simple synchronized ring buffer is used to hand over the data from one thread to another. In a real world application it is likely that the implementation should be more sophisticated. E.g. in a real world implementation it would make sense to implement OutputStream and InputStream views on the buffer.

A ring buffer as such is nothing special, and has no special properties regarding threading. It is just that this simple data structure is used here to provide data buffering. The implementation is done so that access to this data structure has been made thread safe.

 /**
  * Synchronized ring buffer. 
  * Suitable to hand over data from one thread to another.
  **/
 public '''synchronized''' class RingBuffer {
 
     /** internal buffer to hold the data **/
     protected byte buffer[];

     /** size of the buffer **/
     protected int size;

     /** current start of data area **/
     protected int start;

     /** current end of data area **/
     protected int end;

     /**
      * Construct a RingBuffer with a default buffer size of 1k.
      */
     public RingBuffer() {
          this(1024);
     }

     /**
      * Construct a RingBuffer with a certain buffer size.
      * @param size   Buffer size in bytes
      */
     public RingBuffer(int size) {
          this.size = size;
          buffer = new byte[size];
          clear();
     }

     /**
      * Clear the buffer contents. All data still in the buffer is lost.
      */
     public void clear() {
         // Just reset the pointers. The remaining data fragments, if any,
         // will be overwritten during normal operation.
         start = end = 0;
     }

     /**
      * Return used space in buffer. This is the size of the
      * data currently in the buffer.
      * <nowiki><p></nowiki>
      * Note: While the value is correct upon returning, it
      * is not necessarily valid when data is read from the 
      * buffer or written to the buffer. Another thread might
      * have filled the buffer or emptied it in the mean time.
      *
      * @return currently amount of data available in buffer
      */
     public int data() {
          return start <= end
                      ? end - start
                      : end - start + size;
     }

     /**
      * Return unused space in buffer. Note: While the value is
      * correct upon returning, it is not necessarily valid when
      * data is written to the buffer or read from the buffer.
      * Another thread might have filled the buffer or emptied
      * it in the mean time.
      *
      * @return currently available free space
      */
     public int free() {
          return start <= end
                      ? size + start - end
                      : start - end;
     }

     /**
      * Write as much data as possible to the buffer.
      * @param data   Data to be written
      * @return       Amount of data actually written
      */
     int write(byte data[]) {
         return write(data, 0, data.length);  
     }

     /**
      * Write as much data as possible to the buffer.
      * @param data   Array holding data to be written
      * @param off    Offset of data in array
      * @param n      Amount of data to write, starting from <code>off</code>.
      * @return       Amount of data actually written
      */
     int write(byte data[], int off, int n) {
         if(n <= 0) return 0;
         int remain = n;
         // @todo check if off is valid: 0= <= off < data.length; throw exception if not

         int i = Math.min(remain, (end < start ? start : buffer.length) - end);
         if(i > 0) {
              System.arraycopy(data, off, buffer, end, i);
              off    += i;
              remain -= i;
              end    += i;
         }

         i = Math.min(remain, end >= start ? start : 0);
         if(i > 0 ) {
              System.arraycopy(data, off, buffer, 0, i);
              remain -= i;
              end = i;
         }
         return n - remain;
     }

     /**
      * Read as much data as possible from the buffer.
      * @param data   Where to store the data
      * @return       Amount of data read
      */
     int read(byte data[]) {
         return read(data, 0, data.length);  
     }

     /**
      * Read as much data as possible from the buffer.
      * @param data   Where to store the read data
      * @param off    Offset of data in array
      * @param n      Amount of data to read
      * @return       Amount of data actually read
      */
     int read(byte data[], int off, int n) {
         if(n <= 0) return 0;
         int remain = n;
         // @todo check if off is valid: 0= <= off < data.length; throw exception if not

         int i = Math.min(remain, (end < start ? buffer.length : end) - start);
         if(i > 0) {
              System.arraycopy(buffer, start, data, off, i);
              off    += i;
              remain -= i;
              start  += i;
              if(start >= buffer.length) start = 0;
         }

         i = Math.min(remain, end >= start ? 0 : end);
         if(i > 0 ) {
              System.arraycopy(buffer, 0, data, off, i);
              remain -= i;
              start = i;
         }
         return n - remain;
     }
 }

With this ring buffer one can now hand over data from one thread to another in a controlled way. Any other thread-safe, non-blocking mechanism would also do. The key point here is that the write does not block when the buffer is full and also does not block when there is nothing to read.

Using the Buffer together with Serial Events
Usage of OUTPUT_BUFFER_EMPTY Event in Writing

Referring to the skeleton event handler presented in the section Setting up a serial Event Handler, one can now use a shared ring buffer from section A simple, thread-safe Ring Buffer Implementation to support the OUTPUT_BUFFER_EMPTY event. The event is not supported by all JavaComm implementations, therefore the code might never be called. However, in case the event is available is is one building block for ensuring best data throughput, because the serial interface is not left idle for too long.

The skeleton event listener proposed a method outputBufferEmpty(), which could be implemented as it follows.

     RingBuffer dataBuffer = ... ;

    /**
     * Handle output buffer empty events.
     * NOTE: The reception is of this event is optional and not
     *       guaranteed by the API specification.
     * @param event The output buffer empty event
     */
    protected void outputBufferEmpty(SerialPortEvent event) {

{{TODO}}
        
    }

Reading of Data

The following example assumes that the data's destination is some file. Whenever data becomes available it is fetched from the serial port and written to the file. This is an extremely simplified view, because in reality one would need to check the data for an end-of-file indication to, for example, return to the modem command mode.

 import javax.comm.*;

 ...
 InputStream is = port.getInputStream();
 BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("out.dat"));

 /**
  * Listen to port events
  */ 
 class FileListener implements SerialPortEventListener {

     /**
      * Handle serial event.
      */
     void serialEvent(SerialPortEvent e) {
         SerialPort port = (SerialPort) e.getSource();

         //
         // Discriminate handling according to event type
         //
         switch(e.getEventType()) {
         case SerialPortEvent.DATA_AVAILABLE:

             //
             // Move all currently available data to the file
             //
             try {
                  int c;
                  while((c = is.read()) != -1) {
                        out.write(c);
                  }
             } catch(IOException ex) {
                  ...
             }
             break;
         case ...:
             ...
             break;
         ...
         }
        if (is != null) is.close();
        if (port != null) port.close();
     }

Handling multiple Ports in one Application

Modem Control

JavaComm is strictly concerned with the handling of a serial interface and the transmission of data over that interface. It does not know, or provide, any support for higher-layer protocols, e.g. for Hayes modem commands typically used to control consumer-grade modems. This is simply not the job of JavaComm, and not a bug.

Like with any other particular serial device, if the control of a modem is desired via JavaComm the necessary code has to be written on top of JavaComm. The module "Hayes-compatible Modems and AT Commands" provides the necessary basic generic information to deal with Hayes modems.

Some operating systems, e.g. Windows or certain Linux distributions provide a more or less standardized way how modem control commands for a particular modem type or brand are configured for the operating system. Windows modem "drivers", for example, are typically just registry entries, describing a particular modem (the actual driver is a generic serial modem driver). JavaComm as such has no provisions to access such operating-system specific data. Therefor, one either has to provide a separate Java-only facility to allow a user to configure an application for the usage of a particular modem, or some platform-specific (native) code needs to be added.

The simple Java way to provide modem-specific configuration information would be a properties file, and the usage of java.util.Properties to read such a modem configuration file.

RxTx

Overview and Versions

Due to the fact that Sun didn't provide a reference implementation of the JavaComm API for Linux, people developed RxTx for Java and Linux [2]. RxTx was then further ported to other platforms. The latest version of RxTx is known to work on 100+ platform, including Linux, Windows, Mac OS, Solaris and other operating systems.

RxTx can be used independent of the JavaComm API, or can be used as a so called provider for the JavaComm API. In order to do the latter, a wrapper called JCL is also needed [3]. JCL and RxTx are usually packaged together with Linux/Java distributions, or JCL is completely integrated into the code. So, before trying to get them separately, it is worth having a look at the Linux distribution CD.

There seems to be a trend to abandon the JavaComm API, and using RxTx directly instead of via the JCL wrapper, due to Sun's limited support and improper documentation for the JavaComm API. However, RxTx's documentation is extremely sparse. Particularly, the RxTX people like to make a mess of their versions and package contents (e.g. with or without integrated JCL). Starting with RxTx version 1.5 RxTx contains replacement classes for the public JavaComm classes. For legal reasons they are not in the java.comm package, but in the gnu.io package. However, the two currently available RxTx versions are packaged differently:

RxTx 2.0
RxTx version supposed to be used as a JavaComm provider. This one is supposed to have its roots in RxRx 1.4, which is the RxTx version before the gnu.io package was added.
RxTx 2.1
RxTx version with a full gnu.io package replacement for java.comm. This version is supposed to have its roots in RxTx 1.5, where gnu.io support started.

So, if one wants to program against the original JavaComm API one needs

  1. Sun's generic JavaComm version. As of this writing this is in fact the Unix package (which contains support for various Unix versions like Linux or Solaris). Even when used on Windows, the Unix package is needed to provide the generic java.comm implementations. Only the part implemented in Java is used, while the Unix native libraries are just ignored.
  2. RxTx 2.0 in order to have a different provider below the generic generic JavaComm version than the ones comming with the JavaComm package

However, if one just wants to program against the gnu.io replacement package, then

  • only RxTx 2.1 is needed.

Converting a JavaComm Application to RxTx

So, if you belong to the large group of people who have been let down by Sun when they dropped Windows support for JavaComm, you are in need to convert a JavaComm application to RxTx. As you can see from the above, there are two ways to do it. Both assume that you manage to install a version of RxTx first. Then the options are either

  1. Using RxTx 2.0 as a JavaComm provider
  2. Porting the application to RxTx 2.1

The first option has already been explained. The second option is surprisingly simple. All one has to do to port some application from using JavaComm to using RxTx 2.1 is to replace all references to java.comm in the application source code with references to gnu.io. If the original JavaComm application was properly written there is nothing more to do.

RxTx 2.1 even provides the tool contrib/ChangePackage.sh to perform the global replacement on a source tree under Unix. On other platforms such a global replacement is easy to do with IDEs supporting a decent set of refactoring features.

See also