どうやるか、相当時間考えたが現状の結論はこうだ。
まず、顔認識が実行されたときに発生させるイベント関数(ALFaceDetectionのEvent APIを参照する)を設定しておいて(このやり方はspeechRecognitionのサンプルを使えばいい)、このCallBack関数に与えられるデータから、認識された顔情報をALMemoryのInsertメソッドを通じて、変数に値を組み込む。
ということなのだが。
QiChatスクリプトLoad時の例外処理
QiChatスクリプトをロボットに組み込むときに、スクリプトにバグがあると正常に組み込めない。C++でこのプロセスをALDialogのloadメソッドでやると、組み込み失敗でプログラムが止まってしまう。例外処理をやらないといけないのだ。
APIマニュアルには例外を取れとだけ書いてあってどのような例外がスローされるか書いてなかった。例外処理をしないと実用上とてもまずいので、また、Aldebaranのdeveloper forumに質問したところ早速解答があった。
catch(const std::exception& e){
std::cout << e.what() << std::endl;
}
と、
catch (const AL::ALError& e)
{
std::cout << e.what() << std::endl;
}
のどちらも、正常に機能した。基本的に同じものであるようだ。どちらかでよい。
スクリプトの暗号化
QiChatはスクリプトであり、ロボットは可読なテキストファイルの状態でそれを入力する。作成したユーザーがそれを一貫して読めることは大事だが、そのスクリプトを他者に使わせるときに読まれることが望ましくない場合がある。
iBotシステムでも、ロボットが使用する瞬間には可読なテキストファイルでなければならないが、ユーザーから他者にわたったときには暗号化されているようにするつもりだ。それができるのは、スクリプトをロボットに読み込ませるのはiBotであって、iBotが暗号化されたスクリプトを複合できれば良いわけだから。
ユーザーは、結局最終的なスクリプトの利用者に複合化キーを渡さなければならないので、完全ではない。
iBotが固有の複合キーを持たせてスクリプトを隠蔽すれば、利用者はほぼ完全にテキストで見れなくなるが、iBotは、C++ で書かれ、コンパイルされ、バイナリファイルになっているので、それをリバースエンジニアリングで解読すればそれも見られてしまう。しかし、それは仕方がないだろう。
QiChatにおける変数の扱い
iBotシステムは、最後の詰めのところで、複雑で手間のかかる作業になっている。
特に変数の取り扱いだ。iBotは、Choregrapheを全く使わなくても、対話、動作、認知の機能を利用できるようにするシステムだ。中心には人との対話があり、それに起動されてさまざまな動作、認識機能が発動される。コンセプトがそうなっている。
その全体を直接コンんとロールするのがQiChatスクリプトである。QiCHatスクリプトにはめ込まれる機能を全て解釈して実行するのは、直接にはロボットなのだが、人とそのロボットの機能を媒介するシステムがiBotである。ほとんどそれはiBotで実現できるようになるのだが、NAOQiには、多様な機能があるので、全ては組み込まない。
その際、変数の扱いが面倒である。というのもNaoqiというロボットOSは、変数のイベント処理が重要な役割を持っている。変数に値が入力されたり変更されたりすると、それだけでイベントが発生し、プログラムの方でそれを獲得できるようになっているのだ。
たとえば、人の顔を学ぶとき、それが誰の顔かを変数で入れ籠まないと行けない。具体的な名前にすると、全く融通が利かないからだ。それをQiChatから、iBotシステム経由で、naoqiに渡す作業が必要になる。あるいは、認識した顔が誰であるかをQiChatのスクリプトの中に戻せなければならない。これら全てが変数によって媒介される。非常によくねられたシステムであることは間違いない。ちゃんと使えるようになっている。
iBotがどのように拡張されても、この変数を合理的に扱えるようなシステムにしたい。
編集機能の充実
iBotシステムのQiChatスクリプト編集機能で、簡単に入力できるように、また、質疑応答の構造かも容易になるような機能を組み込んでいる。当初は、これは後まわしにしてシステムを公開することを考えたが、この編集機能があるかないかで、スクリプトの開発スピードが格段に違うようになりそうなので、その部分も完成した後に公開しようと思う。
ファイル転送ローカルモジュール
ファイル転送ローカルモジュールがほぼできた。全体のシステム概要を示すと以下のようになる。
ファイル転送ローカルモジュールは、ロボットの中に組み込まれるために、ユーザーの管理のもと、サーバーから対話用のトピックファイル(スクリプト)をダウンロードするものだ。殿ファイルをダウンロードするかは、サーバーにあるユーザー専用の設定ファイルに記載されているものに従う。
対話を管理するローカルモジュールも必要なのだが、これはリモートモジュールとしては出来上がっているので、ローカルモジュールとしてロボットに組み込めるものに変更する必要がある。簡単に終わりそうだ。だから、先に初期化インストーラーを作成しよう。
xercesによるC++コンパイル
ユーザー設定ファイルのXMLをDOMで解析するために、にxerces3.1.1をUbuntuにソースからデフォルトの設定でインストールしたが、プログラムをそのままg++でコンパイルしても動かない。次の操作が必要だ。
(1)リンカ段階で、xerces-cライブラリをくっつける。すなわち、こんな感じ。g++ -o xmldom xmldom.cpp -lxerces-c
そうしないときらめくようなエラーの流れが発生する。
(2)/etc/ld.so.confに次の1行を加える。(デフォルトではそこにライブラリがインストールされている)
/usr/local/lib
(3)/sbin/ldconfigを実行しておく。
ただ、XMLのparse exceptionが発生する。と、思ったら、サンプルに使っているxmlファイルがおかしかったようだ。
ファイル転送モジュールのプロトコル
ロボット管理のクラウドサイトの原型はできて、ロボットとクラウドとのファイル転送を仲介するモジュールを作成している。すでに作成していたのは、httpプロトコルで、あくまでもウェッブサーバー上のファイルをロボットに取込むものだった。これでは、クラウド上に作成したスクリプトの取り組みには使えないので、ftpプロトコルでロボットにファイルを取込むモジュールに変更しているところ。
ロボットとサイト間通信のC++プログラムの基本はできているので、あとはFTPのプロトコルをそこに組み込むだけである。基本的に、ロボットはローカルネットワークのファイヤーウォールの内側にあることがメインであると想像されるから、ftpプロトコルはpassiveモードにしなければならない。
telnetを使って、このpassveモードの手続きをシミュレートしたので、記録のためにここに残しておく。あとはプログラムに組み込むだけである。
********** 端末1を開く ************
sample.jp(仮想サイト)からrobo.xxxx.jp(仮想サイト)への接続。$から始まる行は、クライアントが打ち込んだコマンド。
$ telnet sample.jp 21
Trying 133.12.158.102...
Connected to robo.xxxx.jp.
Escape character is '^]'.
220 (vsFTPd ....)
$ USER xxxxx
331 Please specify the password.
$ PASS yyyyy
230 Login successful.
$ TYPE I (I:がバイナリモード、A:がアスキーモード)
200 Switching to Binary mode.
$ PASV
227 Entering Passive Mode (zzz,zzz,zzz,zzz,39,6).
(いま端末を動かしているクライアントが、NATなどのファイヤーウォールの内側にいると想定。普通は、このあとPORTコマンドで指示されたクライアントのポートに、サーバー側からつなぐのであるが、ファイヤーウォールのもとでは、それが正常にできないので、クライアントからつなぐ、サーバーのIPアドレスが、zzz,zzz,zzz,zzzとして、また、ポート番号が39×256+6=9990として指定されてきている。そこで、もう一つ端末を開いて、すなわち独立のコネクションを実行する。これもまた、クライアント側の端末であることに注意。)
******* 端末2のコネクション ********
telnet zzz,zzz,zzz,zzz 9990
Trying 133.12.158.102...
Connected to robo.xxxx.jp.
Escape character is '^]'.
(これは、このままなにもしない。)
******* 端末の1コネクションにもどる ********
$ CWD /home/xxxxx
(たとえば、上記コマンドで、フォルダを変更する:これ自体はPASV指定前でもできる。)
$ LIST
(こうすると、先ほどの端末2にフォルダのリストが出力される。このあたりがPASVモードの面白さと言うか、おどろきというか。)
******* 端末2のコネクション ********
drwxr-xr-x 3 1000 1000 4096 Jul 15 12:30 Desktop
drwxr-xr-x 3 1000 1000 4096 Dec 26 2012 Documents
drwxr-xr-x 2 1000 1000 4096 Jul 15 10:06 Downloads
drwxr-xr-x 2 1000 1000 4096 Dec 24 2012 Music
drwxr-xr-x 4 1000 1000 4096 Mar 02 06:13 Pictures
drwxrwxr-x 13 1000 1000 4096 Jun 21 22:29 Project
drwxr-xr-x 2 1000 1000 4096 Dec 24 2012 Public
(以上のように、実際のデータのやり取りは、端末2上で行われ、コマンドは、端末1でやりとりされる。)
というわけである。
※ ただ、ロボットがグローバルIPを持っていて、20番ポートでデータのやり取りができる場合でも、PASSIVEモードにしても良いかどうかは若干気がかりである。多分問題ないと思うが。問題があれば、おそらく20番ポートを指定してPORTコマンドを打ったときに、
500 Illegal PORT command.
と返した場合にだけ、PASSIVEが必要になるとすれば良いと思う。
Javascriptによるnaoqiモジュールのコントロール
戦略的には、誰でもどこでも自分のロボットを必要なレベルでコントロールできるようにすることを狙っている。そのために、ロボット内に自作のモジュールを組み込み、外のサーバーとQiChatスクリプトを組み込むようにしてきた。
次の段階としては、ブラウザから、ロボットおよび、そのコンテンツを持っているサーバーを制御し、また、ブラウザ上でHtml5の手法でスクリプトの作成などもできるようにすることだ。
今日、特別に作成したサイトから、Javascriptでロボットとブラウザを接続し、モジュールAltextSpeechを起動させ、ブラウザから入力したテキストをしゃべらせてみた。簡単にできた。他のモジュールを動かすこともそう難しくはないだろう。
Postureモーションのぎこちなさ問題が解決か・・・
先に書いた、座る、たつ、動作をC++のAPIをとおしてやると動きがぎこちなくなってしまうという問題を、たぶん、解決できたと思うので、記録のために記載しておく。
Choreguraphでこのような動作をやってもきわめてスムーズに行く。そのログをみると、AutonomousLifeをInteractiveの状態で行なっているのでそれをまねしようとしたがどれも失敗した。プログラマティカルに、Interactiveには移行できないと指摘しながら、C++でそこにいこうとしても、Documentoをどう読んでも書いていない。C++上は、StateはSolitaryになっていて、それをいったんDisabledにしてみたりしたが、ロボットは破壊的な動きをすることがわかって断念。
結局、ALAutonomousMovesを座る前にfalseにして、再び立ち上がってから、trueにするという方法で解決できたような気がする。プログラムはこんな感じだ。
/*****************************
amove = new AL::ALProxy(broker, "ALAutonomousMoves");
if(poseType == "Sit"){
// If sitdown, ALAutonomousMoves makes disabled
amove->callVoid("setExpressiveListeningEnabled",false);
}else{
// if stand up, enabled
amove->callVoid("setExpressiveListeningEnabled",true);
}
**************************/
ALAutonomousMovesとPostureがバッティングして、動きのぎこちなさが発生したと考えることができるのだ。
しかし、この間いろいろなことを試して、ロボットの動きについての知識が増え、また、C++でコントロールする方法が深まったことはとても良かったと思う。