Javaで仮想シリアルポートを使った送受信テスト環境の構築

実機が手元にない状況でも、シリアル通信の動作を検証したいケースは珍しくありません。本記事では、Java で仮想シリアルポートを用いて送信・受信を再現する方法を解説します。実際のデバイスがなくても、PC 内で完結するテスト環境を構築できます。

必要なライブラリ

今回は jSerialComm を採用します。Maven プロジェクトであれば pom.xml に以下を追加してください。

<dependency>
    <groupId>com.fazecast</groupId>
    <artifactId>jSerialComm</artifactId>
    <version>2.10.4</version>
</dependency>

仮想ポートの作成

Windows の場合 com0com、macOS/Linux の場合 socat を使ってペアの仮想ポートを用意します。

# Linux/macOS の例
socat -d -d PTY,link=/tmp/ttyV0,raw,echo=0 PTY,link=/tmp/ttyV1,raw,echo=0

これにより /tmp/ttyV0/tmp/ttyV1 がペアで生成されます。片方を Java アプリ、もう片方を screenminicom で開くことで手動応答テストが可能です。

送受信ロジックの実装

共通の設定をまとめた SerialConfig クラスを用意し、送受信それぞれの責務を分離します。

public final class SerialConfig {
    public static final int BAUD = 115200;
    public static final int DATA_BITS = 8;
    public static final int STOP_BITS = 1;
    public static final int PARITY   = SerialPort.NO_PARITY;
}

送信側

public class DataSender implements AutoCloseable {
    private final SerialPort port;

    public DataSender(String portName) {
        port = SerialPort.getCommPort(portName);
        port.setComPortParameters(
            SerialConfig.BAUD,
            SerialConfig.DATA_BITS,
            SerialConfig.STOP_BITS,
            SerialConfig.PARITY
        );
        port.setComPortTimeouts(SerialPort.TIMEOUT_WRITE_BLOCKING, 1000, 0);
        if (!port.openPort()) {
            throw new IllegalStateException("ポートが開けません: " + portName);
        }
    }

    public void transmit(String message) throws IOException {
        byte[] frame = message.getBytes(StandardCharsets.UTF_8);
        int wrote = port.writeBytes(frame, frame.length);
        if (wrote != frame.length) {
            throw new IOException("送信バイト数が一致しません");
        }
    }

    @Override
    public void close() {
        port.closePort();
    }
}

受信側

public class DataReceiver implements Runnable, AutoCloseable {
    private final SerialPort port;
    private final BlockingQueue<String> queue = new LinkedBlockingQueue<>();

    public DataReceiver(String portName) {
        port = SerialPort.getCommPort(portName);
        port.setComPortParameters(
            SerialConfig.BAUD,
            SerialConfig.DATA_BITS,
            SerialConfig.STOP_BITS,
            SerialConfig.PARITY
        );
        port.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 0, 1000);
        if (!port.openPort()) {
            throw new IllegalStateException("ポートが開けません: " + portName);
        }
    }

    @Override
    public void run() {
        byte[] buf = new byte[256];
        while (!Thread.currentThread().isInterrupted()) {
            int len = port.readBytes(buf, buf.length);
            if (len > 0) {
                queue.offer(new String(buf, 0, len, StandardCharsets.UTF_8));
            }
        }
    }

    public String poll(long timeout, TimeUnit unit) throws InterruptedException {
        return queue.poll(timeout, unit);
    }

    @Override
    public void close() {
        port.closePort();
    }
}

動作確認

メインクラスで送信側と受信側を別スレッドで起動し、文字列を送受信してみます。

public class LoopbackDemo {
    public static void main(String[] args) throws Exception {
        String txPort = "/tmp/ttyV0";
        String rxPort = "/tmp/ttyV1";

        try (DataSender sender = new DataSender(txPort);
             DataReceiver receiver = new DataReceiver(rxPort)) {

            Thread rxThread = new Thread(receiver);
            rxThread.setDaemon(true);
            rxThread.start();

            sender.transmit("Ping");
            String echo = receiver.poll(1, TimeUnit.SECONDS);
            System.out.println("受信: " + echo);
        }
    }
}

実行結果

受信: Ping

トラブルシューティング

  • ポートが開動かない:仮想ドライバが正しくインストールされているか確認してください。
  • 文字化け:送受信側の文字エンコーディングが一致しているか再確認してください。
  • 受信が途切れる:受信バッファサイズを増やすか、フロー制御を有効にしてください。

まとめ

仮想シリアルポートを活用すれば、ハードウェアを使わずに通信プロトコルの実装検証が可能です。jSerialComm は OS 非依存でシンプルな API を提供するため、テストコードの保守も容易です。実機が届る前にロジックを固めておくことで、後工程での手戻りを大幅に削減できます。

タグ: jSerialComm VirtualSerialPort Java SerialCommunication com0com

6月9日 16:05 投稿