クラウド経由でPaPeRo iを制御する-httpでポーリング編

 クラウド経由でPaPeRo iを制御する方法を紹介します。まずは一番簡単にできそうな、クラウドに作成したWebページをPaPeRo iで定期的にポーリングさせる方法で、単にしゃべらせるだけ、を試してみました。

無料で試せるクラウドサービス

 今回の様にとりあえず試してみるという場合には有料では辛いし、今後のことを考えると期間限定の無料というのもできれば避けたいものです。
2018年1月現在で無期限無料で試せるクラウドサービスはIBM Cloudライト・アカウントしか見つけられませんでした。当然制約はいろいろある様ですが、クレジットカード不要、評価利用だけでなく本番環境でも利用可という太っ腹なサービスです。
 登録は名前、会社名とメールアドレスだけで可能でした。確認のメールが送られてくるのでそのメールにある”Confirm Account”ボタンでサービスが使えるようになります。
(※Herokuもありました。2018/1/23追記)

制御用のWebページを作る

 今回はクラウドにしゃべる文字列を登録するためのWebページと、PaPeRo iが登録された文字列をポーリングするためのWebページを作りたいと思います。
IBM CloudでWebページを作る方法は色々ありそうですが、簡単そうなNode-REDでやってみました。
 カタログのプラットフォームにあるNode-RED Starterをクリックし、アプリ名を入力します。デフォルトではNode-REDのフローエディタやNode-REDで作成したWebページが
https://アプリ名.mybluemix.net/???
となるので、アプリ名は他と被らなさそうな名前に決めます。デプロイする地域/ロケーションの選択はライトアカウントでは「米国南部」とする必要があるそうです。
作成ボタン後にBASIC認証の設定入力があり、起動後にフローエディタが使えるようになります。今回は以下のようなフローを作成しました。

(1) 「speech」ノード
 しゃべる文字列を登録するためのWebページ用のhttp入力ノードです。メソッド=GET、URLと名前をspeechとしました。
(2) 「入力用html」ノード
 しゃべる文字列を登録するためのWebページ(/speech)のHTMLを記述したtemplateノードです。しゃべる文字列だけでは少し寂しいのでポーリングするパペロが自分のIDと一致している場合だけしゃべるようにパペロIDの入力欄もつけました。また、htmlのパラメータで入力初期値を設定できるようにしています。

テンプレート:

<html>
    <body>
            <form action="/postpage" method="POST">
                パペロID:<input type="text" name="paperoid" value="{{payload.paperoid}}">
                <br>
                しゃべる文字列:<input type="text" name="speechtext" value="{{payload.speechtext}}">
                <input type="submit" value="送信">
            </form>
    </body>
</html>

例えば
https://spitest201801091517.mybluemix.net/speech?paperoid=1234&speechtext=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%EF%BC%81
にアクセスすると、パペロID=1234、しゃべる文字列=こんにちは!と設定できます。
(3) 「paperoidとspeechtextをglobalに保持」ノード
 入力をglobalに保持するためのfunctionノードです。

コード:

var id = msg.payload.paperoid;
var txt = msg.payload.speechtext;

global.set('paperoid', id);
global.set('speechtext', txt);

return msg

(4) http出力ノード
 前段のHTMLを出力します。設定項目はありません。(3個とも同じです)
(5) 「postpage」ノード
 送信ボタンでのPOST用のhttp入力ノードです。メソッド=POST、URLと名前をpostpageとしています。
(6) 「speechにリダイレクト」ノード
 POST後にspeechページにリダイレクトするためのHTMLを記述したtemplateノードです。

テンプレート:

<html>
    <head>
        <meta http-equiv="refresh" content="0;URL=/speech?paperoid={{payload.paperoid}}&speechtext={{payload.speechtext}}">
    </head>
    <body>
    </body>
</html>

(7) 「paperoget」ノード
 PaPeRo iにポーリングさせるWebページ用のhttp入力ノードです。メソッド=GET、URLと名前をpaperogetとしています。
(8) 「globalのpaperoidとspeechtextを返す」ノード
 PaPeRo iに返すデータを記述したfunctionノードです。PaPeRo iで簡単に解析できるようにパペロIDとしゃべる文字列を’\n’でつないで返します。
コード:

msg.payload = global.get('paperoid') + '\n' + global.get('speechtext');
return msg;

(9) 「debug」ノード
 必須ではなく、デバッグのためのノードです。デプロイ後、/paperogetにアクセスがあるたびに右側デバッグペインに指定内容が出力されます。

 このフローをデプロイすると、/speechページで設定したパペロIDとしゃべる文字列が、/paperogetページで取得できることが確認できました。改行はブラウザ上ではスペースで表示されています。

PaPeRo i側のプログラム

 PaPeRo i側のポーリングはPythonでも簡単にできると思いますが、今回はgolangで試してみました。golangでPaPeRo iのアプリをつくるのサンプルプログラムを改造します。Webからデータを定期的に取得し、パペロIDとapp.paperoId(任意に設定します)が一致したらchannelに送信するコードは以下のようになります(標準パッケージnet/http, io/ioutil, time, stringsを追加でimportする必要があります。また、簡単のためエラーは考慮していません)。

func httpGet(dataUrl string) string {
    txt := ""
    resp, err := http.Get(dataUrl)
    if err != nil {
        fmt.Println(err)
        return ""
    }
    defer resp.Body.Close()
    ba, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return ""
    }
    txt = string(ba)
    fmt.Println(txt)
    return txt
}

func (app *App) httpReader() bool {
    t := time.NewTicker(app.httpPollSec * time.Second)
    for {
        select {
        case <-t.C:
            txt := httpGet(app.dataUrl)
            if 0 < len(txt) {
                l := strings.Split(txt, "\n")
                if 2 <= len(l) {
                    if l[0] == app.paperoId && 0 < len(l[1]) {
                        app.httpch <- l[1]
                    } else if 0 < len(l[1]) {
                        fmt.Println("id not match.")
                    }
                }
            }
        }
    }
    t.Stop()
    return true
}

これをgo httpReader()でgoroutineとして動かし、メインループでは

    select {
    case msg = <-app.papero.ch:
        isMsg = true
    case txt = <-app.stdinch:
        isStdin = true
    case txt = <-app.httpch:
        isHttp = true
    }

        if isStdin {
            app.papero.speech(txt, &ApiParams{})
            app.state = APP_SPEECH
        } else if isHttp {
            if txt != app.lastTxt {
                app.papero.speech(txt, &ApiParams{})
                app.state = APP_SPEECH
                app.lastTxt = txt
            }
        } else if isMsg {

とすれば、文字列に変化があった時だけしゃべらせることができます。
PCではそのまま動作したのですが、arm用にビルドして本体で動かしたところ、sslのエラーで動作しませんでした。原因はPaPeRo iに証明書がインストールされていないためと思われ、あてずっぽうだったのですがDebian stretchの/etc/ssl以下をPaPeRo iの/etc/sslに丸コピーしたところ、正常に動作するようになりました。