久しぶりのJAVA

この間、でっかいTOPICファイルを二つばかり作り、そこにいろんなネタのデータを放り込んだんだけれど、大きなデータをTOPICファイルに入れるのはやや無理があると思い、それらをもう一度読み取って、MySqlのデータベースに入れようと思った。変換のためのJAVAプログラムを適当に作成しようと思って、簡単にできるはずなのだが、久しぶりにJAVAを使おうとすると、頭が戻りにくい。まあ、また、思い出し思い出しやるしかないな。

HTMLのパーサーを作成している

必要があって、C++で、HTMLのパーサーを作成することになった。ロボットに組み込む、ローカルモジュールの一部となる。
XMLはtinyxml2で対応していることは既に書いている。原則的に、Htmlも、Xmlのルールに従っているはずなのだから、tinyxml2でパース(構文解析)してやれば良いようなものだが、実はそう簡単にいかない。
なぜなら、Htmlは、多少文法通りに書かれていなくても、パースをやめたりしてはいけない。なんらかのかたちで表出しなければならないのだ。そうしたあたりのいい加減さをxmlパーサーはわかっていない。こちらが、Htmlパーサーに求めているのは、Xmlほどの厳密さや、正確性ではない。必要な、タグ、属性、テキストを切り分けられれば良い。
ネットで探しても適当なものはなかった。あまり大掛かりなものは、ロボットに組み込めない。必要最小限の軽やかなものでよいので、結局自分で作ることにした。以外と手間はかからない。

ロボットを倒さない

テレパシーモジュール(ロボットに組み込むローカルライブラリ)に関わって、対話をめぐるシグナルのやりとりは、すでにビデオにアップしているように、問題無く機能する。ただ、対話と動作を組み込んだテレパシーのやりとりに問題が発生した。動作を開始しようとすると突然倒れるのだ。この問題を回避するために、十日以上時間を使ったことになる。
テレパシーモジュールがらみの問題だと思った。この間の経験から、メモリエラーが発生すると、すぐに倒れる。例えば、配列の枠以上のところにアクセスすると、セグメントエラーで、ロボットが麻痺して倒れることはよくあった。
そのために、モジュールの怪しい箇所を徹底的に調べた。結果的に、forkコマンドからpthreadによるスレッド処理にかえたり、自動で体を動かす機能をコントロールしたり、したが結局それらは問題ではなかった。そもそも、テレパシーモジュールの問題ではなかった。
要するに、歩行開始前の状態の問題だった。歩行開始前は、StandInitのポーズにしなければならない。ibotの基本モジュールでは、初期状態をALPostureモジュールで把握して、それがStandInitでなければ、ポーズをStandInitにセットするとしていたのだが、じつは、実際の姿勢がStandInitではなくても、そうであるかのように値を返すことが分かった。
そのあたりの問題があることに気づいたのは、実は、Choregrapheでも、初期状態をちょっと変にしておくと、Poseをとったときに、バーチャルロボットが倒れることがわかったことが大きい。つまり、Choregrapheも状況によって、機能できないと倒れるのだ。ここがヒントになって、上記のチェックを行った結果、どのような状況でも、moveToコマンド前に、StandInit状態にすることで、倒れなくなった。
さらに、moveToの終了後、まず、Standのポーズをとらせてから、体を自動的に動かす機能を復活させるにしたことも大切な対応だった。というのも、ロボットは、こうしたAutolife機能は、Standの状態で行うので、早めにStandを確保しないと、自動の動きを突然やらせると、体が揺れて、そのために倒れることがあるのだ。これも回避することができるようになった。
ロボットにとって、倒れることは深刻な問題だ。Pepperは、プログラマティカルに倒れることはないだろう。しかし、NAOは二足歩行である。倒れやすい。というか、倒れることを前提に、いろいろな対応が取られている。PepperにはPushrecovery機能があるのに、NAOには、なぜかその機能が組み込まれていない。NAOの足の裏には、片足、4個のセンサーが付いていて、圧力をチェックしているので、それを使って、Pushrecovery機能を持たせることができそうだが、なぜか、そのイベントと値を取得できない。
NAOにとって倒れないプログラム作りは決定的に重要なのだ。

理解できないことに直面して

この数日テレパシーに関わる作業を続けてきたが、どうしてもうまくいかないことが続いた。ある方向で実行すると動かない、別の方向では動く、これが多種多様に発生して何が何だかわらかなくなってきた。が、おそらく次の二つが原因になっているのではないかと思う。それを書き留めておこう。
(1)あるローカルモジュール内で登録したALMemoryイベントを、同一モジュール内で、raiseEventメソッドで、イベント発生させハンドラを呼び出すことはできない。
そもそもやろうとしていることが奇妙に思える。同一モジュール内だったら、直接そのイベントハンドラに対応する関数を呼び出せば良いからである。ただ、ここで必要になったのは、子プロセスから親プロセスのハンドラを呼び出すことをしたかったわけだ。Linuxのネイティブなシステムコールなどを使えばできるのかもしない。また、naoqiのリモートモジュールだったらこれもできることは確認した。が、ローカルモジュールになった途端できない。そのハンドラを、他のモジュールや、外側のPythonから呼び出すと正常に呼び出すことができる。
できないなら仕方がない。本質的には、他のモジュールから呼び出せる限り問題ないとは言える。
(2)これも不思議なのだが、子プロセスから、textspeechなどの機能を呼び出せないーようだ。親プロセスと子プロセスを取り替えた途端しゃべるので、たぶん、そうだろうと。
JAVAだったか、グラフィックツールはメインスレッドでしかうごかせないなどという制約があったのと同じかもしれない。つまり、ハードウェアをコントロールするAPIは、メインプロセスでしかうごかせないのかもしれないということである。
上記の二つが本当かどうか、絶対にそうだとは言えない。何か他の要因でできないでいるだけかもしれない。しかし、何日も無駄にしたので、腹立ち紛れに書いておいた。
イソップ物語にある、狐と葡萄の話だ。ブドウのふさに跳びついて取れないとわかった途端、狐は「こんなぶどうは酸っぱいに決まっている」と言い捨てて立ち去ったという話だ。高校生の頃に知ったのだが、いまだにこの狐のたくましさが好きだ。

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が必要になるとすれば良いと思う。