Bakulog

獏の夢日記的な何か。

【和訳記事】NAOqiのプログラムをqi Frameworkに移行しよう!

本記事は英語の公式ドキュメンテーションのほぼそのままな和訳です。

   

本記事は本家アルデバラン社のドキュメンテーションの一つであるHow to switch from NAOqi to qi Frameworkを英語があまり読みたくない人のために和訳したものになります。和訳しただけだと分かりにくいところもあるので適宜注釈を入れています。

   

訳文

はじめに

このガイドでは貴方のプログラミングをqiへ移行する手助けをします。どのようにNAOqiからqiへコードを置き換えられるかを示し、qiを使う利点を紹介します。

   

qi Frameworkのおもな特徴

qi Frameworkのおもな特徴は次の通りです。

  • 辞書、リスト、構造体、汎用オブジェクトを表現できます(※1)。
  • シグナル、プロパティを利用できます(※2)。
  • モジュールからイベントを購読する必要がなくなりました。
  • 関数の呼び出し結果を非同期で取得できます。

 

※1(訳注): 「辞書、リスト、構造体、汎用オブジェクト」と言ってますが、辞書、リスト、構造体はそれぞれPythonで言うところのdict, list, tupleに対応しており、汎用オブジェクトについては「メンバー関数を持った何か」、より具体的にはALTextToSpeechなどのサービスモジュールの事だと思っておけば問題ありません。

※2(訳注):特に難しい使い方をしない限り、シグナルというのはALMemoryで使われてるイベント処理の事だと思って構いません。またプロパティ機能は別の手段でほぼ等価なものが実装出来るためか、実際にはほとんど使われていないようです。

   

qi FrameworkでのHello World

※本節は原文に無い節ですが説明の補助として追加しています。

qi Frameworkを用いたHello Worldプログラムは次のように書くことが出来ます。NAOqiの場合とあまり変わらない形で記述できることが分かります。

# -*- coding: utf-8 -*-

import qi

app = qi.Application()
session = app.session
#接続先のIPアドレス
session.connect("192.168.xx.xx")
#アドレスを完全に書く場合はこんな感じ
#session.connect("tcp://192.168.xx.xx:9559")

tts = session.service("ALTextToSpeech")
tts.say("Hello, World!")

   

プロキシからセッションを取得する

ここでは従来のNAOqiを用いたプロキシからqi.sessionを取得する方法を説明します。ここで紹介する方法はNAOqiを用いたスクリプトか、またはChoregrpahe内のスクリプトで利用できます。

import naoqi

#ALMemoryモジュールのプロキシを取得
mem = naoqi.ALProxy("ALMemory", "127.0.0.1", 9559)

#プロキシからqiのsessionを取得
ses = mem.session()

#セッションを用いたコード

   

メモリイベントを監視(購読)する

qiを使う場合、モジュールレベルでメモリのイベントを監視する必要は無く、単にコールバック関数を登録するだけで済みます。

注意: イベントのコールバックが行われるようにするためには、ALMemory.Subscriber関数の戻り値であるSignalSubscriber型の変数をプログラム中で保持する必要があります。
def mycallback(value):
  print("val:{0}".format(value))

mem = session.service("ALMemory")
sub = mem.subscriber("foo/bar") #※3
sub.signal.connect(mycallback) #※4

 

※3(訳注): sub変数が注意にある「保持する必要のある変数」のようです。ただしコードを書いて型情報を見てもSignalSubscriber型だとは明記されてないのでご注意下さい。

※4(訳注): 細かい話題ですが、実は上記のsignalという名前付けはALMemoryの実装に特有のものです。ただしALMemory以外からシグナル機能を使うケースはほぼ無いため、通常は上の書き方をイディオムみたいに覚えておけば十分です。

 

コールバック関数の中でメモリのキー情報が使いたい場合、functools.partial関数を使った部分適用が便利です。

import functools

def mycallback2(key, value):
  print("key:{0}".format(key))
  print("val:{0}".format(value))

mem = session.service("ALMemory")
sub = mem.subscriber("foo/bar")
sub.signal.connect(functools.partial(mycallback2, "foo/bar"))

   

PCallを使うかqi.Asyncを使うか

qiモジュールはNAOqiにおいて非同期処理をサポートするため使われていたpCall関数をサポートしませんが、代わりにより汎用的な非同期機構であるqi.async()関数が用意されています。もし非同期に関数を呼び出したければqi.async()関数を用い、戻り値としてqi.Futureを受け取ることが出来ます。このqi.Futureの変数を通じて非同期処理の結果やエラーメッセージを受け取ることが出来ます。

しかしいくつかのNAOqiモジュールは完全にpCall関数に依存してしまっています。特筆すべき例はtts.saymotion.moveToといった関数でしょう。これらのケースではALModuleで提供されているpCall関数を使うことで従来のコードと同じように非同期呼び出しのID数値であるpcallIdを受け取り、これを用いてALModulestopwaitといった関数を利用できます。

注意: pCall関数はqiの標準機能としては利用できずALModuleでのみ利用できます。つまり、各サービスモジュールの製作者(例えばALTextToSpeechの場合なら製作元はAldebaran社)がALModuleの関数を使えるようにサービスを作っているかどうかによってpCall関数が使えるかどうかが決まります。

実際にpCallを使った例を見てみましょう。

tts = session.service("ALTextToSpeech")
pCallId = tts.pCall("say", "I love rock'n'pCall")
tts.stop(pcallId) #おっと、読ませる文字列を間違えちゃった!
#もう一回、今度は正しい文字列で。
pCallId = tts.pCall("say", "I love rock'n'roll")

これに対し、qi.async()を使った実装では次のように書くことが出来ます。

tts = session.service("ALTextToSpeech")
fut = qi.async(tts.say, "I Love rock'n'sync")

#何かしらの処理

#そろそろsay関数の呼び出し結果が知りたい、と思ったら結果を取得する
print("value:{0}".format(fut.value()))

#あるいは完了時に呼びだされるコールバック関数を登録することも出来る
def mycb(fut):
  print("value:{0}".format(fut.value()))

fut.addCallback(mycb)
#sayの完了時処理はコールバックに任せ、別の処理をする

   

補足

個人的にはC#用のqi Frameworkラッパーを作った際にqi Frameworkの仕組みを調べて得た知見があるので色々書きたいのですが、再利用性のありそうな小ネタを一つだけ。

 

Pythonの対話モードでqiを使った時のちょっといい話

基本的なことは上記の訳でだいたい説明されていますが、Pythonqiを使う細かいメリットがもう一つあるので紹介しておきます。Pythonの対話型モードでqi.sessionを利用するとdir関数を使った時の情報がグッと豊富になるのです。以下に、シミュレータのPepperに接続した際の具体例を示します。

from naoqi import ALProxy
import qi
tts_naoqi = ALProxy("ALTextToSpeech", "192.168.xx.xx", 9559) #接続先のIPに応じて適宜書き換え
app = qi.Application()
session = app.session
session.connect("192.168.xx.xx") #接続先のIPに応じて適宜書き換え
tts_qi = session.service("ALTextToSpeech")
for d in dir(tts_naoqi):
    print(d)

#_______________
#__ここから出力__
#_______________

__class__
#"__hoge__"系の関数がいっぱい出力されるが省略
__weakref__
call
initProxies
initProxy
isRunning
lazyLoad
method_missing
pCall
post
pythonCall
pythonPCall
session
stop
this
wait

#_______________
#__ここまで出力__
#_______________

for d in dir(tts_qi):
    print(d)

#_______________
#__ここから出力__
#_______________

__class__
#"__hoge__"系の関数がいっぱい出力されるが省略
_unloadDictionary
addToDictionary
async
call
deleteFromDictionary
exit
getAvailableLanguages
getAvailableVoices
getBrokerName
getLanguage
getMethodHelp
getMethodList
getModuleHelp
getParameter
getSupportedLanguages
getUsage
getVoice
getVolume
isRunning
languageTTS
loadVoicePreference
locale
metaObject
pCall
ping
reset
resetSpeed
say
sayToFile
setLanguage
setLanguageDefaultVoice
setParameter
setVoice
setVolume
showDictionary
stop
stopAll
synchroTTS
version
wait

#_______________
#__ここまで出力__
#_______________

非常に分かりやすい違いが出ました。NAOqiの場合「ALTextToSpeechにはどんな関数があったっけなー?」と思ってdir関数を使っても結果が貧弱なため、何の関数が呼び出せるのかが判然としません。これに対してqiの場合、dirでサービス特有の関数(sayなど)が全部見られるので、対話型モードで機能を確認するときはどう見てもqiに軍配が上がります。

   

まとめ

今回は以上になります。Aldebaran社のドキュメンテーションは新旧が混ざって公開されている事にだけ注意すれば、純粋なドキュメンテーションgithubの公開ソースもなかなか有用に使えるので、気になることはどんどん突っ込んで調べていくと良いと思います。