#1.はじめに
横河電機の「リアルタイムOSコントローラ(e-RT3)」における入出力モジュールの制御を、Rust言語から制御する方法をまとめてみました。
目標としては、前回記事のように、Rustから出力リレーの操作、入力の検知ができることとしています。
リアルタイムOSコントローラ e-RT3 関連記事
第1回 | セットアップ編 |
第2回 | 入出力ユニット編 (PythonとC言語から制御) |
第3回 | Elixirから制御編 |
第4回 | ROS2から制御編 |
第5回(今回) | Rustから制御編 |
第6回 | Goから制御編 |
#2.考え方
libloadingというクレートを使って、共有ライブラリにアクセスして、e-RT3のIOを操作します。
大ざっぱな操作の流れのイメージは下記の通りです。
[Rust]
↓
[libloadingクレート] -> [libm3.so] -> e-RT3のIOユニット
名称 | 役割 |
---|---|
libm3.so | e-RT3のIOユニット制御のライブラリ(メーカから提供) |
libm3.soの配置状況
$ ls -l /usr/local/lib
total 692
lrwxrwxrwx 1 root root 19 Mar 21 2020 libert3dgc.so -> libert3dgc.so.1.1.1
lrwxrwxrwx 1 root root 19 Mar 21 2020 libert3dgc.so.1 -> libert3dgc.so.1.1.1
-rw-r--r-- 1 root root 662644 Mar 17 2020 libert3dgc.so.1.1.1
lrwxrwxrwx 1 root root 14 Mar 5 2019 libm3.so -> libm3.so.1.0.1
lrwxrwxrwx 1 root root 14 Mar 5 2019 libm3.so.1 -> libm3.so.1.0.1
-rw-r--r-- 1 root root 36348 Mar 5 2019 libm3.so.1.0.1
#3.Rustのインストール
e-RT3のOSはubuntu18.04
ですので、公式マニュアルに従ってインストールします。
$ cd Download
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
(ここでインストールが始まります)
$ source $HOME/.cargo/env
$ rustc --version
rustc 1.51.0 (2fd73fabe 2021-03-23)
ログイン時に、自動的にrust関連のパスを通すようにします。
$ nano ~/.bashrc
・・・(省略)・・・
# for Rust
. $HOME/.cargo/env
#4.Rustで共有ライブラリの関数を呼び出す仕組み
libloadingというクレートを使います。
##ソース例
たとえば、「libmine.so」という共有ライブラリに下記add
関数が定義されているとします。
//引数2つの値の和を返す
int add(int a, int b)
{
return(a + b);
}
上記を共有ライブラリとしてビルドします。
$ gcc -fPIC -shared -o libmine.so mine.c
次に、rustから下記のコードで呼び出します。
const LIBPASS: &str = "<共有ライブラリのパス>/libmine.so";
fn main() {
sharedadd(1,2);
}
//足し算の表示
fn sharedadd(x: i32, y: i32) -> i32{
let ret: i32;
unsafe {
//共有ライブラリを検索
match libloading::Library::new(LIBPASS) {
Ok(lib) => {
//共有ライブラリの中の関数を検索
match lib.get::<libloading::Symbol<unsafe extern fn(x: i32, y: i32) -> i32>>(b"add") {
Ok(func) => {
//共有ライブラリの中の関数を実行
ret = func(x, y);
//計算結果を表示
println!("add: {:?}", ret);
return ret;
},
Err(_) => {
//関数が見つからない
eprintln!(" ERROR: No function");
}
}
},
Err(_) => {
//ライブラリが見つからない
eprintln!(" ERROR: No library");
}
}
}
return -1;
}
##実行例
共有ライブラリのadd関数を使って、足し算の結果を取得しています。
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.11s
Running `target/debug/libhelloso`
add: 3
#5.ソースコード
長くなるので、主要な部分だけ抜粋してます。
※※まだGithubにソースを上げていません(21/5/24時点)※※
新規にプロジェクトを作成します。
$ cd gitwork/rust
$ cargo new --lib ert3io
$ cd ert3io
Cargo.toml
を編集します。
[package]
name = "ert3io"
version = "0.1.0"
authors = ["ert3"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libloading = "0.7.0" # ←追記
lib.rs
を編集します。
pub mod libm3; // ←これから作るモジュール名をpubを付けて追記
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
e-RT3の共有ライブラリlibm3.so
を操作するモジュールを作ります。
$ cd src
$ touch libm3.rs
pub mod libm3 {
//! ---------------------------------------------
//! Rust API Drivers for e-RT3 Relay
//! ---------------------------------------------
//! Copyright (c) 2021 myasu
//! MIT Licence
//! ---------------------------------------------
//libm3のパス
const PATHLIBM3: &str = "/usr/local/lib/libm3.so";
・・・(省略)・・・
///リレー出力:ブロック
///Description.
///呼び出し先:writeM3OutRelay
/// * `unit` - ユニット番号を指定(0~7)
/// * `slot` - スロット番号を指定(1~16)
/// * `pos` - 出力リレー番号を指定(1, 17, 33, 49)
/// * `num` - 書き込みブロック数(1~4)
/// * `data` - 書き込みデータ
/// * `mask` - 書き込みデータマスク
pub fn write_out_relay(
unit: i32,
slot: i32,
pos: i32,
num: i32,
data: u32,
mask: u32,
) -> Result<u32, String> {
//アンセーフで呼び出し
unsafe {
//libm3.soから関数を呼び出す
match libloading::Library::new(PATHLIBM3) {
//共有ライブラリが見つかった
Ok(lib) => {
match lib.get::<libloading::Symbol<
unsafe extern "C" fn(
unit: i32,
slot: i32,
pos: i32,
num: i32,
data: &[u16; 4],
mask: &[u16; 4],
) -> i32,
>>(b"writeM3OutRelay")
{
//関数が見つかった
Ok(func) => {
//書き込みデータを配列に代入
let data_d = data as u16;
let data_u = (data >> 16) as u16;
let writedata: [u16; 4] = [0xffff & data_u, 0xffff & data_d, 0, 0];
let mask_d = mask as u16;
let mask_u = (mask >> 16) as u16;
let writemask: [u16; 4] = [0xffff & mask_u, 0xffff & mask_d, 0, 0];
//libm3.soの関数を実行
match func(unit, slot, pos, num, &writedata, &writemask) {
//正常完了
0 => {
//型変換
let data2 = data as u32;
let mask2 = mask as u32;
//返り値に実際に書き込んだ値を付加
Ok(data2 & mask2)
}
//関数の引数の値範囲間違い
_ => Err(errmes("Function args")),
}
}
//関数が見つからない
Err(_) => Err(errmes("Function load")),
}
}
//共有ライブラリが見つからない
Err(_) => Err(errmes("Library load")),
}
}
}
・・・(省略)・・・
///リレー入力:ブロック
///Description.
///呼び出し先:writeM3OutRelay
/// * `unit` - ユニット番号を指定(0~7)
/// * `slot` - スロット番号を指定(1~16)
pub fn read_in_relay(unit: i32, slot: i32) -> Result<u32, String> {
//アンセーフで呼び出し
unsafe {
//libm3.soから関数を呼び出す
match libloading::Library::new(PATHLIBM3) {
//共有ライブラリが見つかった
Ok(lib) => {
match lib.get::<libloading::Symbol<
unsafe extern "C" fn(
unit: i32,
slot: i32,
pos: i32,
num: i32,
data: &[u16; 4],
) -> u16,
>>(b"readM3InRelay")
{
//関数が見つかった
Ok(func) => {
//書き込みデータを配列に代入
let readdata: [u16; 4] = [0, 0, 0, 0];
//libm3.soの関数を実行
match func(unit, slot, 1, 2, &readdata) {
//正常完了
0 => {
//型変換
let data_d = readdata[0] as u32;
let data_u = readdata[1] as u32;
//読み込んだ値を直接出力
Ok((data_u << 16) | data_d)
}
//関数の引数の値範囲間違い
_ => Err(errmes("Function args")),
}
}
//関数が見つからない
Err(_) => Err(errmes("Function load")),
}
}
//共有ライブラリが見つからない
Err(_) => Err(errmes("Library load")),
}
}
}
///エラーメッセージの生成
///Description.
/// * `message` - エラーメッセージの文字列
fn errmes(message: &str) -> String {
// 文字列の変換 https://qiita.com/smicle/items/29a4d5d1d14ad7f77f60
//先頭にエラー共通文字を付加してエラーメッセージを生成
let a = "! <ERROR> : ".to_string();
let b = message.to_string();
return a + &b;
}
}
上記を操作します。
use std::thread;
use std::time::Duration;
//モジュールの読み込み
use ert3io::libm3::libm3;
//実行
fn main() {
println!(" --- libm3 test ---");
out_relay();
in_relay();
println!(" done.");
}
/// ---------------------------------------------
/// 各動作チェック用関数
/// ---------------------------------------------
///リレー出力・ブロックまとめて点灯/消灯
fn out_relay() {
println!(" --- > Output Block");
// スレッドを起動する
let th = thread::spawn(|| {
for n in 0..4 {
println!(" > Output: ON {:?}", n);
// 点灯データ マスク:1で有効
match libm3::write_out_relay(0, 2, 1, 2, 0xf0f0a0a0, 0xffffffff) {
Ok(_) => {}
Err(err) => {
//エラーメッセージ
eprintln!("{}", err.to_string())
}
}
//100msのウェイト
thread::sleep(Duration::from_millis(300));
// 点灯データ マスク:1で有効
match libm3::write_out_relay(0, 2, 1, 2, 0x0f0f0a0a, 0xffffffff) {
Ok(_) => {}
Err(err) => {
//エラーメッセージ
eprintln!("{}", err.to_string())
}
}
//ウェイト
thread::sleep(Duration::from_millis(300));
}
});
th.join().unwrap();
}
///リレー入力・ロックまとめて確認
fn in_relay() {
println!(" --- > Input Block");
// スレッドを起動する
let th = thread::spawn(|| {
//一定間隔で読み込み
for _ in 0..10 {
match libm3::read_in_relay(0, 3) {
Ok(v) => {
//入力状態の表示
println!(" > Input : {:?}", v);
}
Err(err) => {
//エラーメッセージ
eprintln!("{}", err.to_string())
}
}
//ウェイト
thread::sleep(Duration::from_millis(500));
}
});
th.join().unwrap();
}
##実行例
YDの出力が数回点滅した後、XDの入力状態を500ms毎に10回読み込んで入力状態を数値で表示します。
$ cargo run
Compiling ert3io v0.1.0 (/home/ert3/gitwork/rust/ert3io)
Finished dev [unoptimized + debuginfo] target(s) in 6.81s
Running `target/debug/ert3io`
--- libm3 test ---
--- > Output Block
> Output: ON 0
> Output: ON 1
> Output: ON 2
> Output: ON 3
--- > Input Block
> Input : 15
> Input : 15
> Input : 15
> Input : 1
> Input : 1
> Input : 1
> Input : 1
> Input : 0
> Input : 0
> Input : 0
#6.おわりに
Python→Elixirと試してきたので、今度はRustだろうと(笑)取り組んでみましたが、libloadingクレートにたどり着くまでに結構手間取りました・・・
Golangも、同じような考え方でできるようですので、次回取り組んでみます。
#参考資料
- 本記事のソースコード
- (準備中)
- ElixirのNIFs版の参考
- libloadingクレートの使用例
- コードの書き方