Virtualisation de ports séries sous GNU/Linux, et écoute en Java

Introduction

Il peut parfois être utile d’accéder à un port série (RS-232) en Java (ou tout autre langage). Par exemple, pour la connexion avec certains automates qui utilisent cette connectique.
Les trames envoyées via un port série sont généralement simples, la complexité n’est pas dans le traitement de ces trames, mais plutôt dans la connexion avec l’automate, et la mise en place d’un environnement de test (utile quand on n’a pas la machine pour tester).
J’utiliserai RXTX pour la communication entre l’application et le port série. RXTX est une librairie Java, qui utilise JNI (Java Native Interface) pour communiquer avec des ports séries ou parallèles. L’utilisation de JNI implique l’installation d’une librairie native propre à l’OS (.so pour GNU/Linux, .dll pour Windows).

Installation de la librairie

Le jar de la librairie est présent dans les dépôts maven. Il suffit donc d’ajouter la dépendance au pom d’un projet maven existant (ou de télécharger la librairie directement depuis le wiki) :

<dependency>
	<groupId>org.rxtx</groupId>
	<artifactId>rxtx</artifactId>
	<version>2.1.7</version>
</dependency>

Il faut ensuite installer le package qui inclut les librairies natives. Sous debian et ses dérivés :

apt-get install librxtx-java

C’est tout pour l’installation des dépendances. On va pouvoir s’attaquer à la communication avec un port série.

Classe de communication avec un port série

Tout d’abord, pour établir la connexion, il faut instancier un objet de classe gnu.io.CommPortIdentifier. Comme son nom l’indique, il permet de déclarer sur quel port l’écoute va se dérouler.
Pour obtenir une instance de cette classe, il faut faire appel à la méthode getPortIdentifier de la classe CommPortIdentifier :

CommPortIdentifier commPortIdentifier = CommPortIdentifier.getPortIdentifier(identifiant);

Où « identifiant » est une chaîne de caractère identifiant le port (COMn sous Windows, et /dev/quelquechose sous GNU/Linux).

A partir de cet identifiant de port, on peut ouvrir une connexion vers le port :

SerialPort serialPort = (SerialPort) commPortIdentifier.open("Test", 2000);

Je n’ai pas bien compris à quoi sert la chaîne de caractère passée dans le constructeur. En regardant le code source de la classe, il semble qu’elle n’influe pas sur le comportement de l’objet.
Le deuxième paramètre est le « timeout » du port : le temps maximum pour lequel on autorise l’application à attendre que le port soit disponible.
On peut également effectuer certains réglages (baudrate, stopbit etc.) en appelant les méthodes setFlowControlMode, setSerialPortParams, setDTR et setRTS.
Une fois l’objet serialPort accessible, on peut récupérer un InputStream et un OutPutStream, qui vont permettre de lire les trames et d’en écrire :

OutputStream portOutputStream = new BufferedOutputStream(serialPort.getOutputStream());
InputStream portInputStream = new BufferedInputStream(serialPort.getInputStream());

Je mets à disposition une classe qui permet d’effectuer toutes les opérations (connexion/déconnexion, écriture/lecture) : SerialConnection.java.
J’ai également fait un mini projet, qui lance juste une interface, qui affiche toutes les trames qui passent par le port.

Virtualisation de ports séries

Il existe un petit logiciel qui permet de créer une paire de ports virtuels : socat. Pour l’installer :

apt-get install socat

Pour créer deux ports séries virtuels, il suffit de lancer la commande suivante, une fois socat installé :

socat -d -d PTY: PTY:

socat indique alors où les deux ports ont été créés (/dev/pts/n). Ces deux ports sont liés. C’est à dire que si l’on envoie une trame par l’un des ports, elle « sort » par l’autre.
On peut maintenant installer un logiciel qui va permettre d’injecter des trames dans les ports. Il s’agit de gtkterm :

apt-get install gtkterm

gtkterm a une interface toute simple. Pour le connecter à un port, il suffit d’aller dans Configuration > Port et d’écrire le chemin vers le port dans le champ correspondant.
Il est alors facile de vérifier que les ports virtuels fonctionnent bien : ouvrir deux fenêtres de gtkterm, connecter chacune des instances de l’application sur un des ports créés par socat. Ensuite, injecter une trame dans l’un des ports : Fichier > Envoi de fichier brut (=fichier texte contenant la trame). Le contenu du fichier texte devrait apparaître dans la deuxième fenêtre.
On peut maintenant tester rxtx sur un des ports virtuels. Sauf que rxtx ne veut pas se connecter sur les ports situés dans /dev/pts : si on ouvre la classe RXTXCommDriver, on peut y voir une ligne qui ressemble à ça :

if(osName.equals("Linux"))
{
	String[] Temp = {
		"ttyS", // linux Serial Ports
		"ttySA", // for the IPAQs
		"ttyUSB", // for USB frobs
		"rfcomm",       // bluetooth serial device
		"ttyircomm", // linux IrCommdevices (IrDA serial emu)
	};
	CandidatePortPrefixes=Temp;
}

Ce qui indique que rxtx n’accepte que les ports situés dans /dev/ttySn ou /dev/ttySAn etc.
Le plus simple pour se connecter au port est donc de faire un lien symbolique que rxtx puisse prendre en compte. Par exemple :

ln -s /dev/pts/1 /dev/ttyUSB01

Si socat a créé un port en /dev/pts/1.
On pourrait également recompiler rxtx en l’adaptant pour qu’ils prennent en compte les ports dans /dev/pts, mais le lien symbolique est quand même plus simple.
On peut désormais écouter le port /dev/ttyUSB01 avec rxtx.

Conclusion

La connexion a un port série dans une application Java est relativement facile, le seul soucis est qu’il faut faire appel à du code natif, ce qui implique que le code est portable, mais nécessite qu’une librairie spécifique soit installée côté client.
Après la réalisation d’applications qui se connectent à diverses machines par un port série, une conclusion (logique) s’impose : il faut absolument tester l’application sur la machine, même si l’on connaît d’avance la forme des trames qui seront reçues (l’utilisation d’un port virtuel n’est pas suffisante). Il semblerait que le comportement ne soit pas le même en fonction du matériel : on rencontre des problèmes avec certaines machines (le problème que j’ai eu est la réception d’une trame en plusieurs morceaux : une seule trame semblait envoyée par la machine, mais côté client, tout se passait comme si rxtx en recevait plusieurs). J’ignore s’il s’agit d’un problème au niveau de rxtx, ou au niveau du matériel.

Vus : 3045
Publié par mael : 17