リアルタイムOSコントローラ e-RT3 （Rustから制御編）

#1.はじめに

横河電機の「[リアルタイムOSコントローラ（e-RT3）](https://www.yokogawa.co.jp/solutions/products-platforms/control-system/real-time-os-controller/)」における入出力モジュールの制御を、Rust言語から制御する方法をまとめてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145581/2f2932c0-83b7-f87f-73c9-e1a9696e09c9.png)

目標としては、[前回記事](https://qiita.com/myasu/items/97ac50e968430487d5b5)のように、Rustから出力リレーの操作、入力の検知ができることとしています。

**リアルタイムOSコントローラ e-RT3 関連記事**

|||
|:--|:--|
|[第1回](https://qiita.com/myasu/items/057ce9ec2fa39148a5a3)|セットアップ編|
|[第2回](https://qiita.com/myasu/items/97ac50e968430487d5b5)|入出力ユニット編　(PythonとC言語から制御)|
|[第3回](https://qiita.com/myasu/items/0dbf308947b878423112)|Elixirから制御編|
|[第4回](https://qiita.com/myasu/items/605587bd44576699c2f4)|ROS2から制御編|
|第5回（今回）|Rustから制御編|
|[第6回](https://qiita.com/myasu/items/8b32dc8e66ec4685973c)|Goから制御編|

#2.考え方

libloadingというクレートを使って、共有ライブラリにアクセスして、e-RT3のIOを操作します。

大ざっぱな操作の流れのイメージは下記の通りです。

[Rust]
　↓
[libloadingクレート] -> [libm3.so] -> e-RT3のIOユニット

|名称|役割|
|:--|:--|
|libm3.so|e-RT3のIOユニット制御のライブラリ（メーカから提供）|

**libm3.so**の配置状況

```bash:コマンドライン
$ 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`ですので、公式マニュアルに従ってインストールします。

```sh:コマンドライン
$ 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関連のパスを通すようにします。

```sh:コマンドライン
$ nano ~/.bashrc
```

```sh:.bashrc
･･･（省略）･･･
# for Rust
. $HOME/.cargo/env
```

#4.Rustで共有ライブラリの関数を呼び出す仕組み

[libloading](https://crates.io/crates/libloading)というクレートを使います。

##ソース例

たとえば、「libmine.so」という共有ライブラリに下記`add`関数が定義されているとします。

```c:mine.c
//引数２つの値の和を返す
int add(int a, int b)
{
    return(a + b);
}
```

上記を共有ライブラリとしてビルドします。

```sh:コマンドライン
$ gcc -fPIC -shared -o libmine.so mine.c
```

次に、rustから下記のコードで呼び出します。

```rust:main.rs
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関数を使って、足し算の結果を取得しています。

```sh:コマンドプロンプト
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.11s
     Running `target/debug/libhelloso`
add: 3
```

#5.ソースコード

長くなるので、主要な部分だけ抜粋してます。

`※※まだGithubにソースを上げていません(21/5/24時点)※※`

新規にプロジェクトを作成します。

```sh:コマンドライン
$ cd gitwork/rust
$ cargo new --lib ert3io
$ cd ert3io
```

`Cargo.toml `を編集します。

```toml: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`を編集します。

```rust:src/lib.rs
pub mod libm3;          // ←これから作るモジュール名をpubを付けて追記

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}
```

e-RT3の共有ライブラリ`libm3.so`を操作するモジュールを作ります。

```sh:コマンドライン
$ cd src
$ touch libm3.rs
```

```rust:src/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;
    }
}
```

上記を操作します。

```rust:src/main.rs
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回読み込んで入力状態を数値で表示します。

```sh:コマンドライン
$ 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版の参考
   - https://github.com/trihome/eRT3_nifm3
   - https://qiita.com/myasu/items/0dbf308947b878423112
 - libloadingクレートの使用例
   - https://cordx56.hatenablog.com/entry/2021/04/29/213216
   - https://github.com/aircloud/rust-c-demo
 - コードの書き方
    - https://www.tohoho-web.com/ex/rust.html