Create Service


ここではBaku.LibqiDotNetを用いて自作のサービスを定義し、ロボット上に登録する手順を紹介します。この機能を使う機会はそこまで多くありませんが、以下のような状況で必須となります。

  • マシンリソースを要求するタスクをロボットからPCに委譲したい
  • Windowsとの連携に特化したデバイス(Kinectなど)をロボットとシームレスに接続したい
  • 一部の既存サービス(ALAudioDeviceなど)の機能をフルに活用したい


本ページでもコンソールアプリケーションを作成します。アプリケーションの作成方法についてはHello Worldを参照してください。



1. サービスの作成と呼び出し



qi Frameworkに登録可能なサービスを作成するにはQiObjectBuilderというクラスを用います。以下のサンプルはネットワークを必要としません。プロセス内でサービスを定義し、自分で利用する例となっています。

using System;

using Baku.LibqiDotNet;
using Baku.LibqiDotNet.Path;



static void Main(string[] args)
{
    PathModifier.AddEnvironmentPath(
        "dlls", PathModifyMode.RelativeToEntryAssembly);

    var objBuilder = QiObjectBuilder.Create();
    objBuilder.AdvertiseMethod("test::v()", (sig, arg) =>
    {
        Console.WriteLine("test was called");
        return QiValue.Void;
    });
    objBuilder.AdvertiseMethod("twice::i(i)", (sig, arg) =>
    {
        Console.WriteLine("twice was called");
        int i = (int)arg[0];
        return new QiInt32(i * 2).QiValue;
    });

    var obj = objBuilder.BuildObject();
    obj["test"].Call();

    int res = (int)obj["twice"].Call(21);
    Console.WriteLine($"Res = {res}");
}


出力例です。

Complete sig:test::v()
test was called
Complete sig:twice::i(i)
twice was called
Res = 42


この例では"test"および"twice"というメソッドを持つサービスを作成しています。まずは単純な例として"test"メソッドを定義するコードを見てみましょう。

var objBuilder = QiObjectBuilder.Create();
objBuilder.AdvertiseMethod("test::v()", (sig, arg) =>
{
    Console.WriteLine("test was called");
    return QiValue.Void;
});


QiObjectBuilder.Create関数でインスタンスを取得し、AdvertiseMethod関数を呼び出すことで「このサービスにはこのメソッドがあり、実装はこうなっています」という情報を登録します。


関数名に続けて記載された"::v()"というのは関数シグネチャを表しています。"::"は単にメソッド名とシグネチャを区切る文字列です。これに続く"v"という文字列はvoidを意味し、関数の戻り値がないことを示しています。これに続く()は関数の引数が0個であることを示しています。つまりtestは引数を取らず戻り値も無い、単に呼び出すだけの関数であることが宣言されています。


シグネチャについてもう一つの例を見てみましょう。twice関数は"twice::i(i)"という形式で定義されています。"::i(i)"というシグネチャ表記には"i"という文字が2度現れますが、"i"intを表しています。"i(i)"のうち左側(カッコの外側)にある"i"は関数の戻り値が整数であることを示し、"(i)"は関数が単一の整数値を引数に取ることを示しています。


上記で紹介した以外の型を指定したい場合、qi Frameworkドキュメントに対応表があるので参考にしてください。



2. サービスの登録/登録されたサービスの呼び出し


前節の例ではサービスをローカルなプロセスで作成できることを確認しました。これを実際にリモートのロボットに登録し、公開するにはQiSession.RegisterService関数を用います。プログラムの例は次の通りです。

using System;

using Baku.LibqiDotNet;
using Baku.LibqiDotNet.Path;



static void Main(string[] args)
{
    PathModifier.AddEnvironmentPath(
        "dlls", PathModifyMode.RelativeToEntryAssembly);

    string address = "tcp://xxx.xxx.xxx.xxx:9559";
    var session = QiSession.Create(address);

    session.Listen("tcp://0.0.0.0:0").Wait();
    Console.WriteLine("Listen preparation completed");

    var objBuilder = QiObjectBuilder.Create();
    objBuilder.AdvertiseMethod("test::v()", (sig, arg) =>
    {
        Console.WriteLine("test was called");
        return QiValue.Void;
    });
    objBuilder.AdvertiseMethod("twice::i(i)", (sig, arg) =>
    {
        Console.WriteLine("twice was called");
        int i = (int)arg[0];
        return new QiInt32(i * 2).QiValue;
    });

    var obj = objBuilder.BuildObject();

    //ローカルでの呼び出しはしない
    //obj["test"].Call();
    //int res = (int)obj["twice"].Call(21);
    //Console.WriteLine($"Res = {res}");

    uint id = (uint)session.RegisterService(
        "MyService", 
        objBuilder.BuildObject()
        )
        .GetUInt64(0L);

    //リモートに登録したものをロードして使う
    var myService = session.GetService("MyService");
    myService["test"].Call();
    int res = (int)myService["twice"].Call(21);
    Console.WriteLine($"Res = {res}");

    session.UnregisterService(id);
}


実行例です。

[I] 1458052222.196998 11724 qimessaging.session: Session listener created on tcp://0.0.0.0:0
[I] 1458052222.202500 11724 qimessaging.transportserver: TransportServer will listen on: tcp://192.168.1.3:63893
[W] 1458052222.202500 3664 qi.path.sdklayout: No Application was created, trying to deduce paths
[I] 1458052222.204500 11724 qimessaging.transportserver: TransportServer will listen on: tcp://192.168.56.1:63893
[I] 1458052222.208564 11724 qimessaging.transportserver: TransportServer will listen on: tcp://127.0.0.1:63893
Listen preparation completed
Complete sig:test::v()
test was called
Complete sig:twice::i(i)
twice was called
Res = 42


[I][W]で始まっているのはそれぞれlibqiライブラリから送出されているインフォメーションログおよび警告ログです。特に気にする必要はありませんが、しっかり確認するとライブラリの動作が細かく見られます。


その下の出力を見ていくと、登録したサービスを取得し、定義した関数を呼び出せていることが分かります。自作サービスを登録するプログラムの注意点として、RegisterService関数を呼び出す前にQiSession.Listen関数を使い、外部からのサービス呼び出しを許可する必要があります。

session.Listen("tcp://0.0.0.0:0").Wait();
Console.WriteLine("Listen preparation completed");


明示的にサービスを登録解除する場合はUnregister関数を呼び出します。

session.UnregisterService(id);


なお、上記のプログラムではサービスの作成プロセスと呼び出しプロセスが同じであるため、リモート動作の確認という意味では釈然としないように感じる所があったかもしれません。よりしっかりとしたデバッグを行うには次のような構成を取るとよいでしょう。

  1. 上記のプログラムとほぼ同じ処理を行い、UnregisterServiceを呼び出す前にConsole.ReadLineなどでプログラムを待機状態に入らせる
  2. 別のプログラム(C#, PythonあるいはChoregrapheによる方法でも構いません)から登録したサービスを呼び出す

例えばPythonのNaoqi SDKが入っているPCであれば、次のようなコードで上記のサービスを利用できるはずです。

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

import qi

s = qi.Session()
#アドレス指定
s.connect("xxx.xxx.xxx.xxx")

myservice = s.service("MyService")
#呼び出しにより、C#コンソール側に出力が出る
myservice.test()

x = myservice.twice(21)
print(x)
#>>> 42


最後に蛇足ですが、.NETならではの実用的な機能を提供したい場合、ALMemoryとの連動やKinect/HololensなどWindows系デバイスとの組み合わせ、Unityでのデバイス連携によるサービス登録などは有力な候補となり得ます。選択肢はけっこう広いので、アイデアを膨らませていろいろ試してみてください。