Rust: シリアル通信やってみた
今までPythonで作成していたアプリをRustへ移行する一環として共有します。
準備
手元にハードウェアがない場合は、仮想シリアル環境を構築して、デバッグするやり方がオススメです。
※私はコロナの影響で在宅リモートでコードを書いて、たまに現場で動作確認をしています
- Tera Term
ハードウェアの模擬としてTera Term
を使用しますので、インストールしてください。
- com0com
仮想シリアルはcom0comがオススメです。以下の記事が分かりやすいので参考にしてください。
インストールが完了したら、Setup
から下図のような設定をしましょう。
接続ポートの指定は任意で構いません。
下図の例では、COM1
とCOM2
、COM11
とCOM12
が接続されています。
試しにTera Term
を2つ開いて、それぞれCOM1
とCOM2
に接続して、通信できるか確認してみましょう!
以下の例では、COM1
の画面を選択した状態で、キーボードを叩いて、COM2
に送信されていることを確認しています。
Dependencies
本記事で使用するCrate
は以下になりますので、Cargo.toml
に記載しておきましょう。
同じようなCrate
にserial
というものもありますが、examplesがあって、使いやすそうなserialport
を採用します。
serial
を使用した感じだと、同じような使い方なので、そちらを使い方も参考にはなると思います。
[dependencies]
serialport = "4.2.0"
データ送受信
コードの全容は以下の通り。
以下のような動きになります。
- Helloという文字列をTera Term側に1秒周期で送り続ける
- 1秒周期でTera Termからのデータを受け取り、標準出力する
※1秒待機しているだけので、厳密には1秒周期ではないです
main.rs
use std::error::Error;
use std::io::prelude::*;
use std::time::Duration;
fn main() -> Result<(), Box<dyn Error>> {
let mut port = serialport::new("COM1", 9600)
.stop_bits(serialport::StopBits::One)
.data_bits(serialport::DataBits::Eight)
.parity(serialport::Parity::None)
.timeout(Duration::from_millis(100))
.open()?;
let mut buf: Vec<u8> = vec![0; 1000];
loop {
println!("Write...");
match port.write("Hello\r\n".as_bytes()) {
Ok(_) => std::io::stdout().flush()?,
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => (),
Err(e) => eprintln!("{:?}", e),
}
println!("Read...");
match port.read(buf.as_mut_slice()) {
Ok(t) => {
let bytes = &buf[..t];
let string = String::from_utf8(bytes.to_vec())?;
println!("bytes: {:?}", bytes);
println!("string: {:?}", string);
}
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => (),
Err(e) => eprintln!("{:?}", e),
}
std::thread::sleep(Duration::from_millis(1000));
}
}
- 接続設定
今回、COM1
をハードウェアの模擬的な接続先として、COM2
にTera Term
を接続します。
※stop bits
やdaa bits
、parity
はデフォルトをそのまま設定しているだけなので、省略可能
let mut port = serialport::new("COM1", 9600)
.stop_bits(serialport::StopBits::One)
.data_bits(serialport::DataBits::Eight)
.parity(serialport::Parity::None)
.timeout(Duration::from_millis(100))
.open()?;
- 送信
実際に送信しているコードは、port.write()
の部分だけです。これだけでも送信はできますが、バッファをクリアしたり、エラー処理する場合は、以下のようにします。引数はString
型ではなく、Vec<u8>
なので、as_bytes()
で変換するか、b"Hello"
とする必要があります。
match port.write("Hello\r\n".as_bytes()) {
Ok(_) => std::io::stdout().flush()?,
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => (),
Err(e) => eprintln!("{:?}", e),
}
- 受信
受信する場合は、以下のようなコードになります。
バイト列で扱うか、文字列で扱うかは用途により異なるので、今回は標準出力として両方を出力することにしました。
match port.read(buf.as_mut_slice()) {
Ok(t) => {
let bytes = &buf[..t];
let string = String::from_utf8(bytes.to_vec())?;
println!("bytes: {:?}", bytes);
println!("string: {:?}", string);
}
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => (),
Err(e) => eprintln!("{:?}", e),
}
- 動作確認
Rustアプリ側の標準出力は以下の通り。
Tera Term側の標準出力は以下の通り。
まとめ
今回作成したデモアプリは以下のGithubにありますので、参考にしてください。
また、以下のexamplesにその他の使い方が紹介されていますので、合わせて参考にしてください。
Discussion