本記事は英語の公式ドキュメンテーションのほぼそのままな和訳です。
本記事は本家アルデバラン社のドキュメンテーションの一つである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.say
やmotion.moveTo
といった関数でしょう。これらのケースではALModule
で提供されているpCall
関数を使うことで従来のコードと同じように非同期呼び出しのID数値であるpcallId
を受け取り、これを用いてALModule
のstop
やwait
といった関数を利用できます。
注意: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を使った時のちょっといい話
基本的なことは上記の訳でだいたい説明されていますが、Python
でqi
を使う細かいメリットがもう一つあるので紹介しておきます。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の公開ソースもなかなか有用に使えるので、気になることはどんどん突っ込んで調べていくと良いと思います。