RXTXってもう使えない?代替方法は？

# 久し振りにシリアル通信を
久し振りにJavaでシリアル通信をしようと思っていろいろ調べてみた。Javaでプログラム書くのも久し振りだし、昔はJavaシリアル通信ならRXTXしかなかったんだが、今はその後継というか代替というか、そういう記事が検索で挙がっていた。以下は「Java シリアル通信」でGoogle検索した時の上位３つの記事だ。
1. Javaでシリアル通信を使ってみよう <http://web.sfc.wide.ad.jp/~tinaba/tutorials/serial-j/index.html>
2. Javaのシリアル通信ライブラリRXTXの課題と代替調査 <https://qiita.com/kyosho/items/fc0c40458385c7d98232>
3. ソフトウェアエンジニアリング/Java シリアル通信<https://www.torutk.com/projects/swe/wiki/Java%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E9%80%9A%E4%BF%A1>
最初のやつはRXTXの使い方、次とその次が代替ライブラリの提案になっている。

# 準備作業としてcom0comを
モニタ用のCOMポートとしてNull-modem emulator(<https://sourceforge.net/projects/com0com/>)をインストールしておく。Windows10(64bit)にはインストールできないような場合もあるという記事も多くヒットするが、こちら(Windows10 Home 64bit)では何の問題もなくインストールできた。ちなみに開発環境はPleiades All In Oneの「Eclipse 2020 (Windows 64bit版)」「Platform」の「Full Edition」でJDTだけ追加インストールした。COMモニタ用ターミナルにはお馴染みの「Tera Term」を使っている。

#代替できるかな
ここでは以下の4つのテストをしてみたが、自分の環境(Windows10 Home)で「PrintWriterなどで文字出力できる（送信）」「ターミナルからの文字入力をEventで受け取れる（エベント受信）」が出来なかったのはRXTXだけだった。がどれも完全ではない。なお、ポーリング受信は試してもいない。
1. RXTX [http://fizzed.com/oss/rxtx-for-java]
2. jSerialComm [http://fazecast.github.io/jSerialComm/]
3. jpurejavacomm [http://www.sparetimelabs.com/purejavacomm/purejavacomm.php]
4. jssc [https://github.com/scream3r/java-simple-serial-connector]

# RXTXを久し振りに試す
上に示したサイトから「mfz-rxtx-2.2-20081207-win-x64.zip」を持ってきた。binの下にdllを入れろとかlib\extにjarを入れろと言われるが、openjdk11だとlib/extというフォルダーが無いので、jarはEclipseのビルドパスで「外部アーカイブの追加」でライブラリに追加した。dllはEclipseフォルダの下にjre/binがあるので、そこに突っ込んだ。
import文は省略するが、クラス宣言、インタフェース、main関数（try/catchが面倒なのでthrows文を追加）は以下のようにした。他を使うときもだいたい同じである。

```
public class RXTXtest implements SerialPortEventListener {
	public static void main(String[] args) throws Exception {
		new RXTXtest();
	}
	...(中略)
}
```
コンストラクタと最初の処理は以下の通り。EnumerationでアクセスできるCOMポート一覧を表示させている。特に問題なく表示できる。

```
	String name;
	SerialPort port;
	RXTXtest() throws Exception {
		name=this.getClass().getSimpleName();
		Enumeration<CommPortIdentifier> ids=CommPortIdentifier.getPortIdentifiers();
		while(ids.hasMoreElements()) {
			CommPortIdentifier id=(CommPortIdentifier)ids.nextElement();
			System.out.println(id.getName());
		}
	...中略
	}
```

実際にCOMポートをオープンしてPrintWriterで書き込んでみる（送信機能）と、Fatal Errorが出た。com0comで「COM7&COM8」がペアになっており、Tera Termで「COM8」側に接続して「いない」とエラーは出ないが、接続してモニタされているとFATAL ERRORになる。flush()手前までは大丈夫だったが、flush()するとFATALになる。

```
		CommPortIdentifier id=CommPortIdentifier.getPortIdentifier("COM7");
		SerialPort port=(SerialPort) id.open(name, 2000);
		port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
		port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
		port.addEventListener(this);
		PrintWriter pw=new PrintWriter(port.getOutputStream());
		pw.println(name+": "+"Tested!");
		pw.flush();
```
参考のためFATAL ERROR全文を載せておく。こんなんどうしろと。

```
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000180004465, pid=4480, tid=4852
#
# JRE version: OpenJDK Runtime Environment (11.0.7+10) (build 11.0.7+10)
# Java VM: OpenJDK 64-Bit Server VM (11.0.7+10, mixed mode, tiered, compressed oops, g1 gc, windows-amd64)
# Problematic frame:
# C  [rxtxSerial.dll+0x4465]
#
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# C:\Users\akira\Documents\workspace\SerialTesting1\hs_err_pid4480.log
#
# If you would like to submit a bug report, please visit:
#   https://github.com/AdoptOpenJDK/openjdk-support/issues
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
```

# jSerialCommを試す
<http://fazecast.github.io/jSerialComm/>の上の方の「jar」ボタンから「jSerialComm-2.6.2.jar」を持ってきた。これも「外部アーカイブの追加」でビルドパスに入れておく。
コンストラクタ周辺は以下の通り。インタフェースはサンプルソースに倣って処理中に書いたので、冒頭では宣言しない。

```
public class JSerialCommTest {
	public static void main(String[] args) throws Exception {
		new JSerialCommTest();
	}
	...中略
}
```
使用できるポートの列挙は配列で示される。直感的だがスマートじゃない。また、隠蔽されているはずのcom0comの特殊ペアポート「CNCA0」「CNCB0」も列挙される（RXTXでは出て来なかった）。

```
	SerialPort port;
	String name;
	JSerialCommTest() throws Exception {
		name=this.getClass().getSimpleName();
		SerialPort[] ports=SerialPort.getCommPorts();
		for(int i=0;i<ports.length;i++) System.out.println(ports[i].getSystemPortName());
		...中略
	}
```

オープンして出力するところは特に問題なく実行できた。少なくとも一方的に送信はできる（RXTXではpw.flush()でFATALを吐いた）。

```
		port=SerialPort.getCommPort("COM7");
		port.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
		port.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);
		port.openPort();
		PrintWriter pw=new PrintWriter(port.getOutputStream());
		pw.println(name+": Tested!");
		pw.flush();
```
問題は受信側である。以下の様に無限ループで入力を待ちながら、イベント監視すると例外が発生したり無反応だったりする。例外はTimeOut関係だったが、実力不足でこれもうまく解決できなかった。

```
		port.addDataListener(new SerialPortDataListener() {
			public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; }
			public void serialEvent(SerialPortEvent event) {
				if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) return;
				byte[] newData = new byte[port.bytesAvailable()];
				int numRead = port.readBytes(newData, newData.length);
				pw.println("Read " + numRead + " bytes.");
			}
		});
		while(true) {}
```

# jpurejavacommを試す

オジサンはもう疲れたよ。次はjpurejavacommを使ってみよう。<https://github.com/nyholku/purejavacomm>のbinフォルダから、「purejavacomm-1.0.3.jar」を持ってきた。これだけでは動かないらしく「jna.jar」も必要だった。ここ<https://github.com/java-native-access/jna>のdistフォルダの下にある。
クラス定義は他とだいたい同じ。ここでもインタフェースは匿名で処理中に書くようにしたので、implementsは無い。

```
public class JPureJavaCommTest  {
	public static void main(String[] args) throws Exception {
		new JPureJavaCommTest();
	}
	...中略
}
```

コンストラクタ～使えるポートの列挙は普通にできた。ここまではどのライブラリも問題ないのだが。

```
	SerialPort port;
	String name;
	JPureJavaCommTest() throws Exception {
		name=this.getClass().getSimpleName();
		Enumeration<CommPortIdentifier> e=CommPortIdentifier.getPortIdentifiers();
		while(e.hasMoreElements()) {
			CommPortIdentifier id=(CommPortIdentifier)e.nextElement();
			System.out.println(id.getName());
		}
		...中略
	}
```
ポートのオープンから送信機能の確認までも何の問題もなく終了。RXTXはダメだったが、jSerialCommもここまでは良かった。

```
		CommPortIdentifier id=CommPortIdentifier.getPortIdentifier("COM7");
		port=(SerialPort)id.open(name,2000);
		port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);
		port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
	
		PrintWriter pw=new PrintWriter(port.getOutputStream());
		pw.println(name+": Tested!");
		pw.flush();
```

さてイベント受信はどうかというと反応なし。エラーも例外も吐かない。どうやら使い方がおかしいのか？

```
		port.notifyOnDataAvailable(true);
		port.addEventListener(new SerialPortEventListener() {
			public void serialEvent(SerialPortEvent arg0) {
				pw.println(name+": SerialEvent!");
			}});
		while(true) {}
```

# jsscを試すぜ
もう駄目だ。javaではシリアル送信はできても受信はできないんだ!!でも最後にjsscを試してみるか！ここ<https://github.com/java-native/jssc/releases>から「jssc-2.9.2.jar」を落としたのだが、ちょっと使ってみたらNGっぽかったので、一つ前の「jssc-2.9.1.jar」を使った（外国人が前のバージョンじゃ出なかったエラーだよ、バグじゃね？って書いてたので）。
もうこの時点で半ば諦めムードだ。しかも「native-lib-loader-2.3.4.jar」「slf4j-api-1.7.30.jar」「slf4j-simple-1.7.30.jar」も必要らしい。なんだよMavenって。知らないよ…
クラス定義は以下の通り。インタフェースはなぜか処理中に「new SerialPortEventListener()」が出来ない（コンストラクタが無い？）ので、implements文で追加してある。

```
public class JSSCtest  implements SerialPortEventListener {
	public static void main(String[] args) throws Exception {
		new JSSCtest();
	}
	...中略
}
```
使えるポートの列挙も普通にできる。まぁRXTXだって出来たからな。ここはEnumurationじゃなくString[]配列を返してくる。趣味には合わないがシンプルでいい。だが、ここでも隠蔽されるべき（これらはデバイスマネージャに出て来ない）「CNCA0」「CNCB0」も列挙される。
なお、ここで文字lsにline.separatorを設定しているのには意味がある。

```
	SerialPort port;
	String ls;
	JSSCtest() throws Exception {
		ls=System.getProperty("line.separator");
		String[] ports = SerialPortList.getPortNames();
		for(int i = 0; i < ports.length; i++) {
		   System.out.println(ports[i]);
		}
		...中略
	}
```
ポートを開けたり送信したりするのも特に問題なくできるが、何故かOutputStreamを取得できず、writeStringなどというメソッドを使うしかないらしい。上手く改行できなかったので文字lsを使った（中身はSystem.geyProperty("line.separator"))が、Tera Term側で\nを受け取れるように設定してもよい。

```
		port=new SerialPort("COM7");
		port.openPort();
		port.setParams(SerialPort.BAUDRATE_9600,SerialPort.DATABITS_8, SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);
		port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
		port.writeString("JSSC Test!"+ls);

		port.addEventListener(this);
		while(true) {}
```

さて問題の受信だが、結論としてはイベントは発生できたが、受信される中身がよくわからない。

```
	public void serialEvent(SerialPortEvent arg0) {
		try {
			byte[] bs=port.readBytes();
			System.out.println(bs);
		} catch (SerialPortException e) {
			e.printStackTrace();
		}
	}
```
Tera Termから「aaabb」と手入力したら以下の様に返ってくる。1文字1イベントで分かりやすいのだがこりゃなんだ？（いやポインタだよね…今回はここまで）

```
[B@1df7e046
[B@19c87720
[B@5ccaea7d
[B@4f8bbf28
[B@6d5c934f
```

# 結論
1. RXTXはFATAL ERRORを吐く。お手上げ。もうメンテされていないしお蔵入り！
2. jSerialCommは不安定。無反応だったり例外を吐く。もうちょっと頑張るか？
3. jpurejavacommはエベント受信が無反応。使い方が悪いのか？
4. jsscはもしかしたら何とかなるかもしれない。受信内容の精査が必要。