久し振りにシリアル通信を
久し振りにJavaでシリアル通信をしようと思っていろいろ調べてみた。Javaでプログラム書くのも久し振りだし、昔はJavaシリアル通信ならRXTXしかなかったんだが、今はその後継というか代替というか、そういう記事が検索で挙がっていた。以下は「Java シリアル通信」でGoogle検索した時の上位3つの記事だ。
- Javaでシリアル通信を使ってみよう http://web.sfc.wide.ad.jp/~tinaba/tutorials/serial-j/index.html
- Javaのシリアル通信ライブラリRXTXの課題と代替調査 https://qiita.com/kyosho/items/fc0c40458385c7d98232
- ソフトウェアエンジニアリング/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だけだった。がどれも完全ではない。なお、ポーリング受信は試してもいない。
- RXTX [http://fizzed.com/oss/rxtx-for-java]
- jSerialComm [http://fazecast.github.io/jSerialComm/]
- jpurejavacomm [http://www.sparetimelabs.com/purejavacomm/purejavacomm.php]
- 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
結論
- RXTXはFATAL ERRORを吐く。お手上げ。もうメンテされていないしお蔵入り!
- jSerialCommは不安定。無反応だったり例外を吐く。もうちょっと頑張るか?
- jpurejavacommはエベント受信が無反応。使い方が悪いのか?
- jsscはもしかしたら何とかなるかもしれない。受信内容の精査が必要。