RoBoHoNアプリ開発

先日RoBoHoNアプリの調査を行いました。
(RoBoHoN打ちにくいので以下ロボホン)

ロボホンはAndroid端末なので、開発環境もAndroid用です。
開発面で大きな違いは

  • 発話機能
  • プロジェクター機能

多分これくらいです。多分。

ひとまずAndroid開発も初なので、SHARP様からいただいたマニュアルに従い環境構築を行う。細かな設定・ライブラリ追加以外は特殊な手続きはなし。

マニュアルを読み進めると、どうやらロボホンの発話はHVMLというファイルに定義するようです。
HTMLの音声版か?と思ったらほんとにそんな感じらしい。どちらかというとXMLだろうけど、headタグやbodyタグがあります。

座学もそこそこに開発をスタートする。・・・が
HVMLの使い方がわからずに苦戦。
どこに配置して、どうやって呼び出すのだろうか。
ネットで調べるも情報は殆ど無く、
半ばあきらめつつマニュアルのあった圧縮ファイル群を見直していると、
その中にサンプルソースを発見。
ちゃんと発話のサンプルもありました。

・・・
とりあえずサンプルをそのままロボホン実機にインストール。
ちゃんと動く。イベント駆動で発話したり、音声を解析したりいろいろなことができそうです。

↑のサンプルを元に、手始めにボタンイベントから駆動するシンプルなアプリを作成。

MainActivity.java



package jp.co.sharp.sample.simple;


import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import jp.co.sharp.android.voiceui.VoiceUIManager;
import jp.co.sharp.sample.simple.customize.ScenarioDefinitions;
import jp.co.sharp.sample.simple.util.VoiceUIManagerUtil;
import jp.co.sharp.sample.simple.util.VoiceUIVariableUtil.VoiceUIVariableListHelper;


/**
 * 音声UIを利用した基本的な機能だけ実装したActivity.
 */
public class MainActivity extends Activity  {
    public static final String TAG = MainActivity.class.getSimpleName();

    // VoiceUIManager
    private VoiceUIManager mVoiceUIManager = null;

    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.v(TAG, "onCreate()");

        setContentView(R.layout.activity_main);

        // button1
        Button button1 = (Button)findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mVoiceUIManager != null) {
                    VoiceUIVariableListHelper helper = new VoiceUIVariableListHelper().addAccost(ScenarioDefinitions.ACC_BUTTON1);
                    VoiceUIManagerUtil.updateAppInfo(mVoiceUIManager, helper.getVariableList(), true);
                }
            }
        });
        // button2
        Button button2 = (Button)findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mVoiceUIManager != null) {
                    VoiceUIVariableListHelper helper = new VoiceUIVariableListHelper().addAccost(ScenarioDefinitions.ACC_BUTTON2);
                    VoiceUIManagerUtil.updateAppInfo(mVoiceUIManager, helper.getVariableList(), true);
                }
            }
        });
        // button3
        Button button3 = (Button)findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mVoiceUIManager != null) {
                    VoiceUIVariableListHelper helper = new VoiceUIVariableListHelper().addAccost(ScenarioDefinitions.ACC_BUTTON3);
                    VoiceUIManagerUtil.updateAppInfo(mVoiceUIManager, helper.getVariableList(), true);
                }
            }
        });

    }

    @Override
    public void onResume() {
        super.onResume();
        Log.v(TAG, "onResume()");

        //VoiceUIManagerのインスタンス取得.
        if (mVoiceUIManager == null) {
            mVoiceUIManager = VoiceUIManager.getService(getApplicationContext());
        }

        //Scene有効化.
        VoiceUIManagerUtil.enableScene(mVoiceUIManager, ScenarioDefinitions.SCENE);
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.v(TAG, "onPause()");
        //バックに回ったら発話を中止する.
        VoiceUIManagerUtil.stopSpeech();

        //Scene無効化.
        VoiceUIManagerUtil.disableScene(mVoiceUIManager, ScenarioDefinitions.SCENE);
        //バックに回ったらアプリを終了する.
        finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.v(TAG, "onDestroy()");

        //インスタンスのごみ掃除.
        mVoiceUIManager = null;
    }


}


ScenarioDefinitions .java



public class ScenarioDefinitions {

    //static クラスとして使用する.
    private ScenarioDefinitions(){}

    /** sceneタグを指定する文字列 */
    public static final String TAG_SCENE = "scene";
    /** accostタグを指定する文字列 */
    public static final String TAG_ACCOST = "accost";
    /** target属性を指定する文字列 */
    public static final String ATTR_TARGET = "target";
    /** function属性を指定する文字列 */
    public static final String ATTR_FUNCTION = "function";
    /** memory_pを指定するタグ */
    public static final String TAG_MEMORY_PERMANENT = "memory_p:";

    /** Package名. */
    protected static final String PACKAGE = "jp.co.sharp.sample.simple";
    /** シナリオ共通:プロデューサー名. */
    protected static final String PRODUCER = PACKAGE;
    /** シナリオ共通:controlタグで指定するターゲット名. */
    public static final String TARGET = PACKAGE;

    /** scene名. */
    public static final String SCENE = "jp.co.sharp.sample.simple";

    /** function:アプリ終了. */
    public static final String FUNC_SAMPLE_END = "sample_end";
    /** function:発話内容を通知する. */
    public static final String FUNC_RECOG_TALK = "recog_talk";
    /** accost:アプリ終了発話. */
    public static final String ACC_END =  ScenarioDefinitions.PACKAGE + ".end_accost";
    /** resolve variable:アプリで変数解決する値. */
    public static final String RESOLVE_JAVA_VALUE = ScenarioDefinitions.PACKAGE + ":java_side_value";

    public static final String ACC_BUTTON1 =  ScenarioDefinitions.PACKAGE + ".button1";
    public static final String ACC_BUTTON2 =  ScenarioDefinitions.PACKAGE + ".button2";
    public static final String ACC_BUTTON3 =  ScenarioDefinitions.PACKAGE + ".button3";

}

jp_co_sharp_sample_simple_accost.hvml



<?xml version="1.0" ?>
<hvml version="2.0">
  <head>
    <producer>jp.co.sharp.sample.simple</producer>
    <description>Java側からのトリガーで発話する</description>
    <scene value="jp.co.sharp.sample.simple"/>
    <version value="1.0"/>
    <tool_version>1.00</tool_version>
    <accost priority="75" topic_id="t1" word="jp.co.sharp.sample.simple.test_accost"/>
    <accost priority="75" topic_id="toppic_button1" word="jp.co.sharp.sample.simple.button1"/>
    <accost priority="75" topic_id="toppic_button2" word="jp.co.sharp.sample.simple.button2"/>
    <accost priority="75" topic_id="toppic_button3" word="jp.co.sharp.sample.simple.button3"/>
  </head>

  <body>
    <topic id="t1" listen="false">
      <action index="1">
        <speech>テストだよ</speech>
        <behavior id="assign" type="normal"/>
      </action>
    </topic>
    <topic id="toppic_button1" listen="false">
      <action index="1">
        <speech>ボタン1だよ</speech>
        <behavior id="assign" type="normal"/>
      </action>
    </topic>
    <topic id="toppic_button2" listen="false">
      <action index="1">
        <speech>ボタン2のアクションその1</speech>
        <behavior id="assign" type="normal"/>
      </action>
      <action index="2">
        <speech>ボタン2のアクションその2</speech>
        <behavior id="assign" type="normal"/>
      </action>
    </topic>
    <topic id="toppic_button3" listen="false">
      <action index="1">
        <speech>ボタン3のアクションその1</speech>
        <behavior id="assign" type="normal"/>
      </action>
      <action index="3">
        <speech>ボタン3のアクションその3</speech>
        <behavior id="assign" type="normal"/>
      </action>
      <action index="2">
        <speech>ボタン3のアクションその2</speech>
        <behavior id="assign" type="normal"/>
      </action>
      <action index="99">
        <speech>ボタン3のアクションその99</speech>
        <behavior id="assign" type="normal"/>
      </action>
    </topic>
  </body>
</hvml>

ファイル構成はこんな感じ
ファイル構成

画面はこんな感じ

メニュー画面

ざっくり解説
HVMLには音声を定義。音声はheadのaccostタグのtopic_idで管理されていて一つのファイルに複数定義できます。
topic_idはbodyのtopicタグのidに紐付けられていて、
topicタグにはactionタグが含まれていて、ロボホンの動作を定義しています。
actionタグの中にはspeechタグとbehaviorタグがあり、
speechタグが発話の内容。behaviorタグがモーションを定義しています。
acthonタグは複数定義でき、index属性に入っている数値順にアクションが実行されます。

ボタンのイベントはMainActivity.javaで定義。VoiceUIManagerにHVMLのキーをわたし
発話させています。

ユーティリティ等のファイルはサンプルからそのまま拝借しました。

実際に喋らせてみたところ、普通の文章の定義でしたがイメージどおりのイントネーションで発話できているようです。ほかのロボットだとスムーズには行かないものですが、
どうやってるんでしょうね。

アプリ作成できたので今回はここまで。
次はもっと高度なやつに挑戦しよう。

  • このエントリーをはてなブックマークに追加

コメントをどうぞ

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください