ロボホンとラズベリーパイをFlaskで連携して温度を喋らせてみた

# 目的

ラズベリーパイと連携し、ロボホンに温度を喋らせる

![Flaskフロー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502127/04fc0709-816b-abf9-ec6e-22ba39d455b2.png)

# 完成動画

[![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/rfHMgSrAVpI/0.jpg)](https://www.youtube.com/watch?v=rfHMgSrAVpI)

# 前提

ラズベリーパイにFlaskをインストールし、温度センサーのデータが取得できるようになっている。
([前回の記事](https://qiita.com/Hiroki1928/items/9d1fe9df9d9b24fb06c8)通りに設定されている前提とします)
AndroidStudioの設定やロボホンテンプレートのインストール手順等は割愛する。

# Pythonスクリプト修正

前回の手順通りに設定を行った場合、返り値に余計な文字が入ってしまう。
数値のみを返した方が処理しやすいので、ラズベリーパイ(Flask)のPythonスクリプトを修正する。
今回は温度のデータを取り扱うので、compensate_T関数を修正する。

```diff:common.py
def compensate_T(adc_T):
    global t_fine
    v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1]
    v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0] / 8192.0) * digT[2]
    t_fine = v1 + v2
    temperature = t_fine / 5120.0
-    return "temp : %-6.2f " % (temperature)
+    return "%-6.2f " % (temperature)
```

# ロボホンアプリの実装

テンプレートから変更する箇所を列挙していく。
values/string.xml等、記載がないものも適宜変更している。

## MainActivity.java

初回起動の判定の為、hascall変数を定義する。

```java:MainActivity.java
private boolean hascall;
```

実装中のデバッグ用に発話ボタンの設定を行う。
今回のアプリの場合、アプリが自動起動し、発話後に自動終了する為、必須ではない。

```java:MainActivity.java
//発話ボタンの実装.
        Button Button = (Button) findViewById(R.id.accost);
        Button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    access_to_flask();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (mVoiceUIManager != null) {
                    VoiceUIVariableListHelper helper = new VoiceUIVariableListHelper().addAccost(ScenarioDefinitions.ACC_HELLO);
                    VoiceUIManagerUtil.updateAppInfo(mVoiceUIManager, helper.getVariableList(), true);
                }
            }
        });
```

Flaskにアクセスし、hvmlで使用できるように数値を取得する。

```java:MainActivity.java
protected void access_to_flask() throws ExecutionException, InterruptedException {
        String temp = null;
        ExecutorService exec = Executors.newSingleThreadExecutor();
        Future<String> stringFuture = exec.submit(new onBtnHttpClicked());

        temp = stringFuture.get();

        Log.d("変数", temp);
        int ret = VoiceUIVariableUtil.setVariableData(mVoiceUIManager, ScenarioDefinitions.MEM_TEMP, temp);
        if(ret == VoiceUIManager.VOICEUI_ERROR){
            Log.d(TAG, "setVariableData:VARIABLE_REGISTER_FAILED");
        }
    }
```

新規作成するシーンを有効化する。
新規作成シーンについては後述。

```java:MainActivity.java
// onResumeメソッド
//Scene有効化.
        VoiceUIManagerUtil.enableScene(mVoiceUIManager, ScenarioDefinitions.SCENE_COMMON);
        VoiceUIManagerUtil.enableScene(mVoiceUIManager, ScenarioDefinitions.SCENE01);
        VoiceUIManagerUtil.enableScene(mVoiceUIManager, ScenarioDefinitions.SCN_TEMP_CALL);

        if (mVoiceUIManager != null && !hascall) {
            try {
                access_to_flask();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            VoiceUIVariableListHelper helper = new VoiceUIVariableListHelper().addAccost(ScenarioDefinitions.ACC_TEMP_CALL);
            VoiceUIManagerUtil.updateAppInfo(mVoiceUIManager, helper.getVariableList(), true);
        }
```

アプリ終了時にシーンを無効化する。

```java:MainActivity.java
// onPauseメソッド
//Scene無効化.
        VoiceUIManagerUtil.disableScene(mVoiceUIManager, ScenarioDefinitions.SCENE_COMMON);
        VoiceUIManagerUtil.disableScene(mVoiceUIManager, ScenarioDefinitions.SCENE01);
        VoiceUIManagerUtil.disableScene(mVoiceUIManager, ScenarioDefinitions.SCN_TEMP_CALL);
```

VoiceUIListenerクラスからのコールバックを実装する。
この時点でhascall変数のフラグをtrueにする。
finish()メソッドを呼び出すとアプリが自動的に終了する。

```java:MainActivity.java
@Override
    public void onExecCommand(String command, List<VoiceUIVariable> variables) {
        Log.v(TAG, "onExecCommand() : " + command);
        switch (command) {
            case ScenarioDefinitions.FUNC_TEMP_CALL:
                hascall =true;
                Log.d("TEST", "温度トリガー");
                finish();
                break;
            case ScenarioDefinitions.FUNC_END_APP:
                finish();
                break;
            case ScenarioDefinitions.FUNC_START_PROJECTOR:
                //TODO プロジェクタマネージャの開始(プロジェクター利用時のみ).
                //if(!isProjected) {
                //    startService(getIntentForProjector());
                //}
                break;
            default:
                break;
        }
    }
```

## ScenarioDefinitions.java

シナリオで使用する変数を定義する。

```java:ScenarioDefinitions.java
//温度測定シナリオの定数
    public static final String SCN_TEMP_CALL = PACKAGE + ".temp.response";
    public static final String ACC_TEMP_CALL = ScenarioDefinitions.PACKAGE + ".temp.response";
    public static final String FUNC_TEMP_CALL = "temp_response";
    public static final String MEM_TEMP = ScenarioDefinitions.TAG_MEMORY_PERMANENT + ScenarioDefinitions.PACKAGE + ".temp";
    public static final String ACC_GET_MEMORYP =  ScenarioDefinitions.PACKAGE + ".get_memoryp.t1";
```

## onBtnHttpClicked.java

新規作成する。
HttpURLConnectionメソッドでFlaskのURLへアクセスする。

```java:onBtnHttpClicked.java
public class onBtnHttpClicked implements Callable<String> {
    @Override
    public String call(){
        try {
            URL url = new URL("http://IPアドレス:80/temp");
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            String str = InputStreamToString(con.getInputStream());
            Log.d("Flask_caller", str);
            return str;
        } catch (Exception ex) {
            Log.d("Exception Error", String.valueOf(ex));
            return "";
        }
    }

    static String InputStreamToString(InputStream is) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
        sb.append(line);
        }
        br.close();
        return sb.toString();
    }
}
```

## XXX_home.hvml

アプリ起動のワードを変更する。
今回は「おんどなんど(温度、何度？)」とする。

```html:home.hvml
<situation priority="78" topic_id="start" trigger="user-word">
            ${Local:WORD_APPLICATION_FREEWORD} eq おんどなんど
</situation>
```

シナリオ内のセリフも適当に変更する。

```html:home.hvml
<action index="2">
    <speech>温度を測定するね</speech>
        <behavior id="assign" type="normal" />
        <control function="start_activity" target="home">
            <data key="package_name" value="jp.co.anyplus.temp_check" />
            <data key="class_name" value="jp.co.anyplus.temp_check.MainActivity" />
        </control>
</action>
```

## XXX_response.hvml

温度を読み上げるhvmlを新規作成する。
ちなみに「25℃」のように記載すると、「25たび」と読まれてしまった。
平仮名で「25ど」に修正すると、想定通りにしゃべってくれた。
(5歳児なので、多めに見ましょう...)

```html:response.hvml
<?xml version="1.0" ?>
<hvml version="2.0">
    <head>
        <producer>jp.co.anyplus.temp_check</producer>
        <description>温度測定のシナリオ</description>
        <scene value="jp.co.anyplus.temp_check.temp.response" />
        <version value="1.0" />
        <accost priority="74" topic_id="response" word="jp.co.anyplus.temp_check.temp.response" />
    </head>
    <body>
        <topic id="response" listen="false">
            <action index="1">
                <speech>温度は${memory_p:jp.co.anyplus.temp_check.temp}ど、みたいだよ</speech>
                <behavior id="assign" type="normal" />
                <control function="temp_response" target="jp.co.anyplus.temp_check" />
            </action>
        </topic>
    </body>
</hvml>
```

# 参考
今回はこちらの記事を参考にさせて頂きました。
[「私、いくつに見える？」への返答機能をRoBoHoNに実装する（AWS Rekognition連携）](https://qiita.com/n_ueh/items/a765827b432a5399d5c6)