qi Frameworkのc++環境構築と導入

# 初めに

naoqi-sdk(2.3)で推奨となった[qi Framework](http://doc.aldebaran.com/libqi/index.html)のc++環境構築と同フレームワークの簡単な使い方をまとめます。

2.3のバージョンでNAOqi Frameworkは非推奨になり、qi Frameworkを推奨するように変わったようです。ただし、ドキュメントのほとんどはNAOqi Frameworkベースでかかれています。qi Frameworkは使い方の点でわからない部分が多いため、サンプルを書くため四苦八苦した記録となってしまいました。

# qi Frameworkとは

pepperのプログラムでは[qi Framework](http://doc.aldebaran.com/libqi/index.html)をつかいます。これはqimessagingというオブジェクト指向なRPC（リモートプロシージャーコール）でネットワーク透過なプロトコルを使います。このプロトコルでpepperの中であっても外であってもpepperのシステムにアクセスでき,必要な機能の呼び出しやコールバックを受けとることができます。新しくサービスをつくり、qi Frameworkに登録して、外部から呼びだすことができます。pepperの実機がない場合にシミュレーションをする場合などに自分でサービスを追加することがあります。例えば、[タブレットのサービス](https://github.com/tkawata1025/PepperTabletEmulator)。

本家の方にはベースとなるc++以外にc,python,java,javascript,golang,juliaのバインディングがあります。それぞれの完成度はよくわかりません。

個人的な意見ですが、NAOqi Frameworkは通信だけでなく、実際の機能が含まれている感じがしますが、qi Frameworkのほうは純粋に通信に特化しているようです。


# qi Frameworkのインストールと環境構築

qi Frameworkのインストールはnaoqi-sdkをつかってもいいのですが、ubuntu（trusty）のboostとバージョンが違って使いにくかったので本家(githubにあるもの)の[libqi](https://github.com/aldebaran/libqi)をビルドして使うことにしました。

以下のようなコマンドで/usr/localにインストールします。パスは適宜変更してください。


* 実行環境
    * ubuntu trusty
* gccのバージョン
    * 4.8

```bash:実行コマンド
git clone git@github.com:aldebaran/libqi.git
cd libqi
#以下のような感じでCMakeLists.txtからtestのコードを外す。
#google testをインストールしておけば外さなくていいはず。
cmake -G "Unix Makefiles" .
make
sudo make install DESTDIR=/usr/local
```

```diff:CmakeLists.txtの変更箇所
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 013d644..410a6c4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -49,10 +49,10 @@ qi_add_optional_package(BOOST_LOCALE "Enable usage of boost::locale")
 if(WITH_BOOST_LOCALE)
   add_definitions(" -DWITH_BOOST_LOCALE ")
 endif()
-option(WITH_EXAMPLES "Examples"          ON)
-option(WITH_PERF     "Performances test" ON)
+#option(WITH_EXAMPLES "Examples"          ON)
+#option(WITH_PERF     "Performances test" ON)
 
-enable_testing()
+#enable_testing()
 include(CMakeDependentOption)
 
 if (WITH_PROBES)
@@ -406,5 +406,5 @@ option(BUILD_EXAMPLES "build examples")
 if (BUILD_EXAMPLES)
   add_subdirectory("examples")
 endif()
-add_subdirectory("tests")
+#add_subdirectory("tests")
 add_subdirectory("bin")
```

そのあとでもまえでもchoregrapheをインストールしておけば、
準備ができました。

# Pepperを喋らせてみましよう。（サービスの関数の呼び出しの例)

下記のサンプルをビルドして、Pepperを喋らせてみましよう。
ALTextToSpeechサービスのsayメソッドを呼び出し、喋らせます。

```cpp:test.cpp
  
#include <qi/applicationsession.hpp>

int main(int argc, char** argv)
{
  qi::ApplicationSession app(argc, argv);
  app.start();
  qi::SessionPtr session = app.session();
  qi::AnyObject tts = session->service("ALTextToSpeech");
  tts.call<void>("say", "Hello world");
}
```

ライブラリのあるところにパスを通して、コンパイル。

```bash:コンパイルコマンド
export LD_LIBRIRY_PATH=/usr/local/lib
g++ test.cpp -lqi -lboost_{chrono,filesystem,locale,program_options,regex,system,thread}
```

できたプログラムa.outにpepperのipを渡して実行します。
choregragheのバーチャルロボットの場合はnetstatかchoregrapheでUIでnaoqi-binがLISTENしているポートを調べてtcp://localhost:portを代わりに設定してください。
これでHello worldと喋ってくれます。

```bash:プログラムの実行例
./a.out --qi-url=tcp://<pepperのip>
or
./a.out --qi-url=tcp://localhost:<naoqi-binのport>
```

余談ですが、下記のコマンドで使えるサービス一覧がとれます。
使用するサービスがあるかチェックするのに使えます。

```
qicli --qi-url=tcp://<pepperのip> info
```

下記のコマンドでサービスのメソッド一覧がとれます。

```
qicli --qi-url=tcp://<pepperのip> info <サービス名>
```



# Pepperに触ってみましょう。（コールバックの受けとり方）

コールバックを受けとるためにはALMemoryサービスにコールバック関数を登録します。
下記の例ではPepperの頭に反応するFrontTactilTouchedイベントにcallback関数を登録してみます。

```cpp:tactile.cpp
#include <iostream>
#include <qi/applicationsession.hpp>
#include <qi/anyvalue.hpp>

using namespace qi;
using namespace std;


void callback(AnyValue value){
  cout << "touched" << endl;
}

int main(int argc, char** argv){

  ApplicationSession app(argc, argv);
  app.start();
  SessionPtr session = app.session();
  
  //{
     //イベントループまでに下記のAnyObjectが解放されるとコールパックがとれないようです。
     //そのため{}のブロックで囲ったりしないでください。
  AnyObject almemory = session->service("ALMemory");
  AnyObject subscriber = almemory.call<AnyObject>("subscriber", "FrontTactilTouched");

  subscriber.connect("signal",boost::function<void(AnyValue)>(&callback)); 
  // callback関数で受け取れるように登録。"signal"とある文字列は変更してはいけない!!

  //}
  app.run();
  //イベントループ？
  return 0  ;
}

```

下記のコマンドでビルドして実行するとpepperのあたまをさわるたびにconsoleにtouchedの文字がでます。

```
g++ tactile.cpp -lqi -lboost_{chrono,filesystem,locale,program_options,regex,system,thread}
```

# Pepperに聞いてもらいましょう（サービスの起動とコールバックを両方使うケース）

注意！！

Pepperに聞いてもらうにはALSpeechRecognitionサービスに聞いてほしい単語を設定して、subscribe関数で同サービスを動かしつつ、ALMemoryからWordRecognizedのイベントのコールバックの受けとります。

下記のlisten.cppの例ではyesとnoの単語を聞きます。

ここのサンプルは未完成です。

コールバック関数は認識した文字列と認識率の数字を交互にならべた配列が返ってきます。下記のサンプルではtoStringで文字列をとってますが、なぜかうまくyes,noがとれません。今後の課題です。

また、実行後ALSpeechRecognitionサービスが起動しっぱなしになるので、ALSpeechRecognitionのunsubscribeを実行後呼び出してサービスをとめてください。下記のstop.cppのプログラムでとまるはずです。


```cpp:listen.cpp
#include <iostream>
#include <qi/applicationsession.hpp>
#include <qi/anyvalue.hpp>

using namespace qi;
using namespace std;


void call2(AnyValue value){
  cout << "speechreco" << endl;
  vector<AnyValue> v = value.toList<AnyValue>();
  for(int i=0;i<v.size();i+=2){
    cout << v[i].toString() << endl;
    cout << v[i+1].toFloat() <<endl;
  }
  // http://doc.aldebaran.com/libqi/api/cpp/type/anyvalue.html#qi::AnyValue
  // によると
  // value[i].as<string>()
  // value[i+1].as<float>()
  //　が正しいアクセスほうほうなのかもしれません。（未実施です。）
}

int main(int argc, char** argv)
{

  ApplicationSession app(argc, argv);
  app.start();
  SessionPtr session = app.session();

  AnyObject rec = session->service("ALSpeechRecognition");
  vector<string> v;
  v.push_back("yes");
  v.push_back("no");
  rec.call<void>("setVocabulary",v,true);
  rec.call<void>("subscribe", "test-asr");


  qi::AnyObject almemory = session->service("ALMemory");
  AnyObject subscriber = almemory.call<AnyObject>("subscriber", "WordRecognized");
  subscriber.connect("signal",boost::function<void(AnyValue)>(&call2));

  app.run();
  rec.call<void>("unsubscribe", "test-asr");
  return 0;
}
```

```cpp:stop.cpp
#include <iostream>
#include <qi/applicationsession.hpp>
#include <qi/anyvalue.hpp>

using namespace qi;
using namespace std;

int main(int argc, char** argv)
{

  ApplicationSession app(argc, argv);
  app.start();
  SessionPtr session = app.session();

  AnyObject rec = session->service("ALSpeechRecognition");
  rec.call<void>("unsubscribe", "test-asr");
  return 0;
}
```

# AnyValueの使い方補足

コールバック関数の引数に使われるAnyValueの使い方です。
公式のドキュメントは、[これ](http://doc.aldebaran.com/libqi/api/cpp/type/anyvalue.html)ですね。


* 通常の型をAnyValueへ変換する方法
    * 変換する前の変数をsとして
    * ```qi::AnyValue value = qi::AnyValue::from(s);```
* AnyValueを 通常の型へ変換する方法
    * AnyValueの変数をvalueとして変換する前の型をT、
        * ```value.as<T>()```
    * toを使うとintやfloatとかの変換ならやってくれるはず。
        * ```value.to<int>()```
* AnyValueの型を調べる方法
    * AnyValueの変数をvalueとしてkind関数で型を調べる。
        * ```value.kind()```
    * `でてきた値をqi/type/fwd.hppのTypeKindと比較する。

```  
enum TypeKind
  {
    TypeKind_Unknown  = 0,
    TypeKind_Void     = 1,
    TypeKind_Int      = 2,
    TypeKind_Float    = 3,
    TypeKind_String   = 4,
    TypeKind_List     = 5,
    TypeKind_Map      = 6,
    TypeKind_Object   = 7,
    TypeKind_Pointer  = 8,
    TypeKind_Tuple    = 9,
    TypeKind_Dynamic  = 10,
    TypeKind_Raw      = 11,
    TypeKind_Iterator = 13,
    TypeKind_Function = 14,
    TypeKind_Signal   = 15,
    TypeKind_Property = 16,
    TypeKind_VarArgs  = 17,
  };
```


# 終わりに

本稿を書くにあたり、すばらしいサポートをしていただいた@tkawata1025さん、アルデバラン・アトリエ秋葉原のみなさんに感謝いたします。