Bakulog

獏の夢日記的な何か。

Pepperに登録されたイベント形式のメモリデータを全て監視する

一部のヘンテコなデバッグに使えるTips紹介です。

 

もくじ

  • はじめに
  • コードと使い方とか内容とか
  • 蛇足:Choregrapheで同じことをやろうとしてはいけない

   

はじめに

Softbank(Aldebaran)社のPepperやNaoでALMemory機能を用いて登録できるメモリデータのうち"EVENT"扱いで登録されたもの、つまり値を渡しつつイベントを発生させるタイプのメモリデータについてロボットから飛んでくる全てのイベントを監視する方法を紹介します。先に注意しておきますが、Choregrapheのメモリウォッチャー機能で似た処理をしようとするとPepperが落ちるらしいのでご注意ください(Choregrapheで失敗する話については本記事のラストに少しだけ記載しています)。

 

全てのメモリイベントを監視するメリットは以下の通り。

  • ロボットの健康状態についてなるべく沢山のデータが欲しい
  • ロボットの状態モニタリングソフトが自作したいのでそのデバッグ
  • 公式アプリがバックグラウンドでヘンな動作をしてないか確認したい

 

なお公式アプリの挙動とメモリデータに関連するネタとしては「Pepperの感情生成APIに少しだけ触ってみよう!」「Pepperの感情生成APIにさらに触ってみよう!」等で具体的に紹介しているので、ご興味ある方は合わせてどうぞ。

   

コードと使い方とか内容とか

では本題というか、動くサンプルコードを示します。PCにPython2.7/Pynaoqi SDKが入ってれば動作するハズです。

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

import sys
import argparse
from pprint import pprint
from functools import partial
from threading import Lock

import qi

_eventDumpLock = Lock()

#発生頻度が高すぎて雑音源になるのでsubscribeしないイベントはブラックリストに入れておく
_event_blacklist = [
    "FaceDetection/FaceDetected"
    ]

_logfile = "eventlog.txt"

def onEvent(writer, key, value):
    with _eventDumpLock:
        writer.write("key={0}\n".format(key))
        writer.write("value({0})={1}\n".format(type(value), value))

        print("key={0}".format(key))
        print("value=")
        pprint(value, indent=2)

def watchEvents(session):
    mem = session.service("ALMemory")

    subscribers = []
    with open(_logfile, "w+") as writer:
        for k in mem.getEventList():
            if k in _event_blacklist:
                continue

            signal = mem.subscriber(k).signal
            id = signal.connect(partial(onEvent, writer, k))
            subscribers.append((signal, id))

        print("press ENTER to exit...")
        raw_input()
    
    for signal, id in subscribers:
        signal.disconnect(id)

if __name__ == "__main__":

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--ip", type=str, default="127.0.0.1",
        help="Robot IP address. On robot or Local Naoqi: use '127.0.0.1'"
        )
    parser.add_argument("--port", type=int, default=9559, help="Naoqi port number")
    args = parser.parse_args()

    try:
        connection_url = "tcp://{0}:{1}".format(args.ip, args.port)
        app = qi.Application(["event_watcher", "--qi-url=" + connection_url])
    except RuntimeError:
        print(
        "Can't connect to Naoqi at ip '{0}' on port {1}.\n".format(args.ip, args.port) +
        "Please check your script arguments. Run with -h option for help."
        )
        sys.exit(1)
    
    session = app.session
    session.connect(connection_url)

    watchEvents(session)

 

これをデスクトップ等に適当な名前(例えば"event_watcher.py")で保存し、ipアドレスを指定して実行します。たとえばこんな感じ。

python event_watcher.py --ip pepper.local

うまく動くとロボットから飛んで来たイベントをコンソールに出力しつつ、"eventlog.txt"というテキストファイルにイベントの情報を吐き出します。エンターキーを押すと終了します。

 

コードの中身についても少しだけ解説。上のコードはNAOqiより新しいフレームワークであるqi Frameworkをベースにしたコードになっていますが、特にポイントとなるのは24行目からのfor文です。

for k in mem.getEventList():
    if k in _event_blacklist:
        continue

    signal = mem.subscriber(k).signal
    id = signal.connect(partial(onEvent, writer, k))
    subscribers.append((signal, id))

 

まずALMemoryのgetEventList関数でイベント名を全て取得します。ここで取得できるイベント名はChoregrapheのメモリウォッチャー機能で調べることが出来るのと同じ、メモリのキーとなる名前です。取得したイベント名に対し、一部の監視しないイベントだけをブラックリスト指定で弾き、残っているイベントに対し定型文として以下の処理を行います。

  1. subscriber関数を呼び出してコールバックを登録するためのオブジェクトを取得
  2. signal.connect関数を使って実際にコールバックとなる関数を登録(このときに登録番号的なidが戻り値になるので必ず拾う)
  3. ガベージコレクションされないように、1と2の結果を変数で保持
  4. イベントの監視を終了するときにdisconnect関数を呼び出す  

この手順は公式ドキュメンテーションここあるいは同文書の和訳であるコレでも紹介しています。

今回は特に「どのキーのイベントが」「どんな値を渡されて発生したのか」という二点だけに注目するため、コンソール出力とファイル出力をシンプルに行う関数をコールバックに登録しています。signal.connect関数の引数部分で使っているpartial(functools.partial)関数は関数への部分適用を行った関数を作るファクトリメソッドですが、詳細はリファレンスを見てください。

 

本題の最後にヒント的なことをいくつか。まずシミュレータロボットと実機ロボットではイベントの発生数がぜんぜん違う(実機の方がイベント数も発生回数も多い)ので、シミュレータロボットでも監視可能なイベントはシミュレータで調べるのがオススメです。またイベントのキーでフィルタリングをすれば見たい範囲のイベントだけを選んで監視できる(例:キーが"Dialog/"で始まってるイベントだけ監視すると会話に関連しそうなイベントを抽出できる、など)ので、その辺はうまく工夫してください。

   

蛇足:Choregrapheで同じことをやろうとしてはいけない

本題は以上ですが蛇足を一つ。

 

このスクリプトアトリエ秋葉原で動作チェックしていた所、スタッフの方から本スクリプトに関連する次のような主旨のアドバイスを頂きました。

実は以前にいらっしゃった方が操作ミスでやってたんだけど、Choregrapheのメモリウォッチャーから全部のメモリを監視する操作をしてしまってた時があって、その時はPepperがメチャクチャ重くなっちゃってたから気をつけて試してね~

 

本記事で紹介したPythonスクリプトにそのような副作用はありませんでした。Choregrapheでメモリ監視する場合と何が違ったのでしょうか?

 

細かくは調べてませんが、Choregrapheからメモリ監視を行った際の挙動を想像するのはそこまで難しい事ではありません。大方こんな感じでしょう。

  1. Pepperの全てのメモリを監視しようとすると、本記事では触れなかった"DATA"系の種類のデータ、つまりイベントを発生させず単に値を保持するだけのデータも監視する事になる
  2. "DATA"系のメモリデータはALMemory.getData関数などで明示的に取得しないと値が得られない
  3. "DATA"系のデータは性質上、監視しようとすると定期的なチェックが必要になり、例えば1秒おきに値を読みだす処理をずっと続けなければならない
  4. 3の処理は監視対象にしているメモリデータの数が多いと追いつかず、最悪の場合処理落ちやクラッシュに繋がる

 

要するにポーリング処理が増えすぎて破綻したのだろう、というのが私の予測です。これに対し本記事のスクリプトではあくまで"EVENT"なデータからイベントが来た時だけ反応するので、監視対象が多くても処理自体はそこまで厳しくなりません(イベントさえ来なければ何もしない)。

 

何にせよ、結論としてはChoregrapheの機能で本記事のマネをするのはオススメできません、という蛇足でした。