LoginSignup
0
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2020-08-10

久し振りにシリアル通信を

久し振りにJavaでシリアル通信をしようと思っていろいろ調べてみた。Javaでプログラム書くのも久し振りだし、昔はJavaシリアル通信ならRXTXしかなかったんだが、今はその後継というか代替というか、そういう記事が検索で挙がっていた。以下は「Java シリアル通信」でGoogle検索した時の上位3つの記事だ。
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はもしかしたら何とかなるかもしれない。受信内容の精査が必要。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0