#1.はじめに
この記事はRust Advent Calendar 2021 (カレンダー2)の11日目の記事です。
FA機器を遠隔制御する通信規格「MODBUS」でイーサネット通信に対応した「MODBUS/TCP」を使って、RustからPLCを遠隔制御するテストをしてみました。
関連記事
使用言語 | |
---|---|
Rust | こちらの記事 |
Elixir | https://qiita.com/myasu/items/6d342ea875ee71a723c7 |
#2.構成
PC側のOSはWindows10で、UbuntuLinuxはVirtualBoxの上で動かしてます。
GXWorksからPLCへの書き込みは、USBを使わずLAN経由でもOKです。
PLC側をMODBUSスレーブ、PC側をMODBUSマスターとします。
FX5Uシーケンサです。
#3.PLC側の準備
設定の詳細は、別記事 を参照してください。
#4.PC側の準備
Rustのインストール参考
$ rustc --version
rustc 1.54.0 (a178d0322 2021-07-26)
##(1)modbusクレート
今回使用するクレートはこちらです。
pub fn new(addr: &str) -> Result<Transport>
//出力リレーYの状態読み込み
fn read_coils(&mut self, address: u16, quantity: u16) -> Result<Vec<Coil>>
//出力リレーYの操作
fn write_multiple_coils(&mut self, address: u16, coils: &[Coil]) -> Result<()>
//入力Xの状態読み込み
fn read_discrete_inputs(&mut self, address: u16, quantity: u16) -> Result<Vec<Coil>>
//データレジスタDの読み込み
fn read_holding_registers(&mut self, address: u16, quantity: u16) -> Result<Vec<u16>>
//データレジスタDの書き込み
fn write_multiple_registers(&mut self, address: u16, values: &[u16]) -> Result<()>
引数 | 意味 | 今回のFX5Uでの例 |
---|---|---|
addr | 対象となるPLCのIPアドレス | 192.168.5.41 |
address | 対象のレジスタの開始位置 | 0x00 |
quantity (read系) | 読み込みビット数 | (後述のコードで) |
Coil (write系) | 出力のON/OFF(リスト型で渡せます) | 例→[Coil::Off, Coil::On, Coil::Off, Coil::On] |
##(2)コードの作成
MODBUS経由でPLCを読み書きする、簡単なコードを作ってみます。
#作業ディレクトリを作成
$ mkdir work
$ cd work
#プロジェクト作成
$ cargo new modbus_prac
$ cd modbus_prac
必要なクレートを追加します。
[package]
name = "modbus_intro"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
// ↓↓追加
modbus = "*"
とりあえず実行。
$ cargo run
Compiling modbus_intro v0.1.0 (modbus_intro)
Building [========================> ] 8/9: modbus_intro(bin)
Finished dev [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/modbus_intro`
Hello World!
#5.実験
##(1)出力YリレーをON・OFF
GXWorksでのリレーYの割当は以下のようになってます。
出力Y | startRegister |
---|---|
Y0 | 0x00 |
Y1 | 0x01 |
Y2 | 0x02 |
Y3 | 0x03 |
・・・ |
Y0はラダーの方で2secのフリック制御をしているので、ここではY1~Y4までを同時にON・OFFしてみます。
use modbus::tcp;
use modbus::{Client, Coil};
use std::thread;
use std::time::Duration;
const IPADDRPLC: &str = "192.168.5.41";
///エントリーポイント
fn main() {
println!(" --- modbus test ---");
for n in 0..5 {
//回数を表示
println!(" > Count {:?}", n);
//ウェイト
thread::sleep(Duration::from_millis(10));
//出力リレーYの操作(OFF→ON)と状態表示
out_relay(Coil::On, IPADDRPLC);
thread::sleep(Duration::from_millis(10));
read_relay(IPADDRPLC);
thread::sleep(Duration::from_millis(1000));
//出力リレーYの操作(ON→OFF)と状態表示
out_relay(Coil::Off, IPADDRPLC);
thread::sleep(Duration::from_millis(10));
read_relay(IPADDRPLC);
}
println!(" > end");
}
/// 出力リレーYの状態取得
/// -
/// Arguments
/// * `ipaddr` - 制御対象のPLCのIPアドレス
fn read_relay(ipaddr: &str) {
println!(" --- > Relay Read");
let mut client = tcp::Transport::new(ipaddr).unwrap();
//出力Yを読み込み
//対象 → Y0~7
match client.read_coils(0x00, 7) {
Ok(data) => {
println!(" --- > Relay Y0 - Y7 / {:?}", data)
}
Err(err) => {
eprintln!("{}", err.to_string())
}
}
}
/// 出力リレーYの操作
/// -
/// Arguments
/// * `val` - コイルのON/OFF
/// * `ipaddr` - 制御対象のPLCのIPアドレス
fn out_relay(val: Coil, ipaddr: &str) {
println!(" --- > Relay write");
//PLCに接続
let mut client = tcp::Transport::new(ipaddr).unwrap();
//出力Yを1つ操作
//操作対象 → Y1をON
match client.write_single_coil(0x01, val) {
Ok(_) => {}
Err(err) => {
eprintln!("{}", err.to_string());
return;
}
}
//出力Yを複数操作
//操作対象 → Y2~3をON
match client.write_multiple_coils(0x02, &[val, val]) {
Ok(_) => {}
Err(err) => {
eprintln!("{}", err.to_string());
return;
}
}
//閉じる
match client.close() {
Ok(_) => {}
Err(err) => {
eprintln!("{}", err.to_string());
return;
}
}
//
println!(" --- > Relay Y / {:?}", val)
}
実行すると、Y1~Y3が4回点滅します。
$ cargo run
Compiling modbus_intro v0.1.0 (/home/myasu/gitwork/rust/modbus_intro)
Finished dev [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/modbus_intro`
--- modbus test ---
> Count 0
--- > Relay write
--- > Relay Y / On
--- > Relay Read
--- > Relay Y0 - Y7 / [Off, On, On, On, Off, Off, Off, Off]
--- > Relay write
--- > Relay Y / Off
--- > Relay Read
--- > Relay Y0 - Y7 / [On, Off, Off, Off, Off, Off, Off, Off]
> Count 1
--- > Relay write
--- > Relay Y / On
--- > Relay Read
--- > Relay Y0 - Y7 / [On, On, On, On, Off, Off, Off, Off]
--- > Relay write
--- > Relay Y / Off
--- > Relay Read
--- > Relay Y0 - Y7 / [Off, Off, Off, Off, Off, Off, Off, Off]
・・・(省略)・・・
> end
##(3)データレジスタDの読み書き
GXWorksでのデータレジスタDの割当は以下のようになってます。
データレジスタ | startRegister |
---|---|
D0 | 0x00 |
D1 | 0x01 |
D2 | 0x02 |
D3 | 0x03 |
・・・ |
D0はラダーの方で1秒ごとにインクリメントしているので、ここではD0~D7までをまとめて読み込んでみます。D0に相当する0ビット目の値が変化しています。
use modbus::tcp;
use modbus::{Client, Coil};
use std::thread;
use std::time::Duration;
const IPADDRPLC: &str = "192.168.5.41";
///エントリーポイント
fn main() {
println!(" --- modbus test ---");
for n in 0..5 {
//回数を表示
println!(" > Count {:?}", n);
//ウェイト
thread::sleep(Duration::from_millis(1000));
//データレジスタDの変更と状態表示
write_register(n * 10, IPADDRPLC);
thread::sleep(Duration::from_millis(10));
read_register(IPADDRPLC);
}
println!(" > end");
}
/// データレジスタDの状態取得
/// -
/// Arguments
/// * `ipaddr` - 制御対象のPLCのIPアドレス
fn read_register(ipaddr: &str) {
println!(" --- > Register Read");
let mut client = tcp::Transport::new(ipaddr).unwrap();
//データレジスタDを読み込み
//対象 → D0~7
match client.read_holding_registers(0x00, 8) {
Ok(data) => {
println!(" --- > Registers D0 - D7 / {:?}", data)
}
Err(err) => {
eprintln!("{}", err.to_string())
}
}
}
/// データレジスタDに値を書き込み
/// -
/// Arguments
/// * `val` - データレジスタにお設定する値
/// * `ipaddr` - 制御対象のPLCのIPアドレス
fn write_register(val: u16, ipaddr: &str) {
println!(" --- > Register Write");
let mut client = tcp::Transport::new(ipaddr).unwrap();
//データレジスタDに書き込み
//対象 → D1~3(さらにレジスタ毎にインクリメント)
match client.write_multiple_registers(0x01, &[val, val + 1, val + 2]) {
Ok(_) => {
println!(" --- > Registers D1 - D3 / {:?}", val)
}
Err(err) => {
eprintln!("{}", err.to_string())
}
}
}
実行すると、D0がラダーでインクリメントされている様子と、D1~D3の値が変化していることが分かります。
$ cargo run
Compiling modbus_intro v0.1.0 (/home/myasu/gitwork/rust/modbus_intro)
Finished dev [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/modbus_intro`
--- modbus test ---
> Count 0
--- > Register Write
--- > Registers D1 - D3 / 0
--- > Register Read
--- > Registers D0 - D7 / [356, 0, 1, 2, 0, 0, 0, 0]
> Count 1
--- > Register Write
--- > Registers D1 - D3 / 10
--- > Register Read
--- > Registers D0 - D7 / [357, 10, 11, 12, 0, 0, 0, 0]
> Count 2
--- > Register Write
--- > Registers D1 - D3 / 20
--- > Register Read
--- > Registers D0 - D7 / [358, 20, 21, 22, 0, 0, 0, 0]
> Count 3
--- > Register Write
--- > Registers D1 - D3 / 30
--- > Register Read
--- > Registers D0 - D7 / [359, 30, 31, 32, 0, 0, 0, 0]
> Count 4
--- > Register Write
--- > Registers D1 - D3 / 40
--- > Register Read
--- > Registers D0 - D7 / [360, 40, 41, 42, 0, 0, 0, 0]
> end
#6.まとめ
先日書いたElixir版に引き続いて、Rustでも書いてみました。
ご参考になれば幸いです。
#参考資料