RustでPaPeRo i のアプリをつくる

 Golangを使ってPaPeRo i 本体で動作するバイナリを作成することができましたRustも同じようにPaPeRo i 本体で動作するバイナリを生成できそうだったので、Rustに入門してみることにしました。

開発環境

 Rustのクロス開発はGolangほど簡単ではなく、Windowsでは苦労しそうだったのでVirtualBox上のDebian10で開発することにしました。Rustは最新版をインストールしました。

$ curl https://sh.rustup.rs -sSf | sh
$ . ~/.profile
$ rustc --version
rustc 1.50.0 (cb75ad5db 2021-02-10)

ファイル一式は~/.cargo以下にインストールされ.profileにPATH設定が追加されます。PaPeRo i 本体で動作するバイナリ生成のためのパッケージとCのクロスコンパイラをインストールする必要があります。

$ rustup target add armv7-unknown-linux-musleabihf
$ sudo apt install crossbuild-essential-armhf

統合開発環境はVisual Studio Codeが良いようです。Rust拡張とCodeLLDB拡張をインストールすればソースコードデバッグができるようになります。

PaPeRo i のアプリをつくる準備

 まずバイナリプロジェクトを作成してください。

$ cargo new プロジェクト名

PaPeRo i 用のクロスコンパイルのためプロジェクトディレクトリ/.cargo/configファイルを以下の内容で作成してください。

[target.armv7-unknown-linux-musleabihf]
linker = "arm-linux-gnueabihf-gcc"

ホストで動作する通常のバイナリをビルドするには

$ cargo build

PaPeRo i 用のバイナリをビルドするには

$ cargo build --release --target armv7-unknown-linux-musleabihf

とします。バイナリファイルは
プロジェクトディレクトリ/target/armv7-unknown-linux-musleabihf/release/プロジェクト名
に生成されます。

PaPeRo i アプリ例

 Rust用のPaPeRo i 制御用ライブラリは未完成でまだ発話・LED制御・頭を動かす・イベント検知しか動作確認していませんが、簡単なアプリの例を紹介します。
 
PaPeRo i 制御用ライブラリは1ファイルで提供予定です。srcディレクトリにyapapero.rsを置きmain.rsの冒頭(useの下)に

mod yapapero;

を追加して下さい。依存パッケージをCargo.tomlに追加する必要があります。PaPeRo i本体でSSLを使える様にするにはひと手間かかるため現状は外してあります(シミュレータでは動作しません)。

[dependencies]
serde_json = "1.0"
chrono = "0.4"
#websocket = "0.26.2"
websocket = { version = "0.26.2", default-features = false, features = ["sync", "async",] }

座布団ボタンで発話とLED点灯、キーボード入力で頭を動かす例です。

main.rs:

use std::thread;
use std::io::stdin;
mod yapapero;

// パペロアプリクラス
pub struct PaperoApp {
    papero: yapapero::Yapapero
}

pub fn new_papero_app(papero_url: yapapero::PaperoUrl) -> PaperoApp {
    let papero = yapapero::new_yapapero(papero_url);
    let app = PaperoApp {
        papero: papero
    };
    app
}

impl PaperoApp {
    // アプリ開始
    pub fn start(&mut self) {
        // パペロとの通信開始
        &mut self.papero.start();

        // キーボード入力をイベントにする
        let tx_kbd = self.papero.app_tx.clone();
        thread::spawn(move || {
            loop {
                let mut input = String::new();
                stdin().read_line(&mut input).unwrap();
                let trimmed = input.trim();
                let js = serde_json::json!({
                    "Name": "_keyboard",
                    "Text": trimmed
                });
                match tx_kbd.send(js) {
                    Ok(()) => (),
                    Err(e) => {
                        println!("tx_kbd.send(): {:?}", e);
                    }        
                }
            }
        });

        // イベントループ
        loop {
            match self.papero.app_rx.recv() {
                Ok(m) => self.papero_event(m),
                Err(e) => {
                    println!("papero.rx.recv: {:?}", e);
                    return;
                }
            };
        }
    }

    // 口を光らせる
    fn lit_mouth(&mut self) {
        let ptn = ["NNR1R1R1R1R1NN", "5", "NR2R2R2R2R2R2R2N", "5", "R3R3R3R3R3R3R3R3R3", "5",
        "NG1G1G1G1G1G1G1N", "5", "NNG2G2G2G2G2NN", "5", "NG3G3G3G3G3G3G3N", "5",
        "Y1Y1Y1Y1Y1Y1Y1Y1Y1", "5", "NY2Y2Y2Y2Y2Y2Y2N", "5", "NNY3Y3Y3Y3Y3NN", "5"];
        self.papero.send_turn_led_on(yapapero::PP_MOUTH, &ptn, false);
    }

    // 頭を動かす
    fn move_head(&mut self) {
        let v = ["A5T300L", "A-5T300L", "A5T300L", "A-5T300L",
        "A5T300L", "A-5T300L", "A5T300L", "A-5T300L", "A5T300L", "A0T300L"];
        let h = ["A-9T300L", "A-12T300L", "A-3T300L", "A6T300L",
        "A15T300L", "A6T300L", "A-3T300L", "A-12T300L", "A-9T300L", "A0T300L"];
        self.papero.send_move_head(&v, &h, true);
    }

    // 頭を止める
    fn stop_head(&mut self) {
        self.papero.send_stop_head();
    }

    // しゃべる
    fn speech(&mut self, txt: &str) {
        self.papero.send_start_speech_p(txt.to_string(), None);
    }

    // パペロイベント処理
    fn papero_event(&mut self, js: serde_json::Value) {
        let (name, msg) = self.papero.get_event_name(js);
        match name.as_str() {
            yapapero::PP_READY => {
                println!("recv ready message.");
                self.lit_mouth();
            }
            yapapero::PP_DETECT_BUTTON => {
                println!("known: name={0} msg:{1}", name, msg);
                let btn = self.papero.get_button(msg);
                let txt: &str;
                if &btn == "R" {
                    txt = "こんにちは";
                } else if &btn == "L" {
                    txt = "さようなら";
                } else {
                    txt = "元気ですか";
                }
                self.lit_mouth();
                self.speech(txt);
            }
            "_keyboard" => {
                println!("known: name={0} msg:{1}", name, msg);
                match &msg["Text"].as_str() {
                    Some(s) => {
                        if s == &"h" {
                            self.move_head()
                        } else if s == &"s" {
                            self.stop_head()
                        } 
                    }
                    None => {
                    }
                }
            }
            _ => {
                println!("OTHER EVENT: name={0} msg:{1}", name, msg);
            }
        }
    }
}

fn main() {
    let papero_url = yapapero::PaperoUrl{
        //url: "wss://smilerobo.com:8000/papero".to_string(),
        //sim_id: "abcdefgh".to_string(),
        url: "ws://192.168.5.1:8088/papero".to_string(),
        sim_id: "".to_string(),
        sim_name: "".to_string()
    };
    let mut app = new_papero_app(papero_url);
    app.start()
}

yapapero.rsの使い方は以下の通りです。

(1) PaPeRo i のURLを指定してオブジェクトを生成(yapapero::new_yapapero())
(2) 送受信スレッド開始 (papero.start())
(3) self.papero.app_rxにイベントが送られてくるので必要に応じて発話など制御メソッド(send_start_speech()など)を呼ぶ

また他の機器との通信などを行うアプリではパペロ以外のイベントも扱える必要がありますが、これはpapero.app_tx.clone()したchannelにsend()することで実現できます。上記例ではキーボード入力でイベントを発生させています。

ソースはこちらからダウンロードできます。


0