実機が手元にない状況でも、シリアル通信の動作を検証したいケースは珍しくありません。本記事では、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 アプリ、もう片方を screen や minicom で開くことで手動応答テストが可能です。
送受信ロジックの実装
共通の設定をまとめた 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 を提供するため、テストコードの保守も容易です。実機が届る前にロジックを固めておくことで、後工程での手戻りを大幅に削減できます。