WebクライアントからのメッセージをOmniverse拡張機能で受け取る



# 1. はじめに
今回もOmniverseネタです。
[configurator-viewer-sample](https://github.com/NVIDIA-Omniverse/configurator-viewer-sample)を見ると、Omniverseの画面をブラウザでストリーミング表示しつつ、Webアプリに配置したボタンの押下イベント(メッセージ)をOmniverse側に送信し、Omniverse側でそのメッセージを受取り、任意の処理を実行できるようです。
[configurator-viewer-sample](https://github.com/NVIDIA-Omniverse/configurator-viewer-sample)のReadmeを読んだだけではどうやっているのか全くわからなかったため、ソースを読み込んで確認してみました。

# 2. Webクライアント側からOmniverseへのメッセージを送信する方法

configurator-viewer-sampleの[AppStream.tsx](https://github.com/NVIDIA-Omniverse/configurator-viewer-sample/blob/main/src/AppStream.tsx)にWebクライアント側からOmniverseへメッセージを送信する部分のコードがありました。

nvidiaからAppStreamerというReactのライブラリが配布されており、そのAPIを実行するだけで簡単にOmniverseにメッセージを送信することができます。Webクライアント側のボタンの押下や画面コンポーネントの操作時にこれを実行してやればよさそうです。

以下は、AppStream.tsxのメッセージ送信処理の抜粋です。

```python
    static sendMessage(message: string) {
        AppStreamer.sendMessage(message);
    }
```


# 3. Omniverse側でメッセージを受信する方法
[configurator-viewer-sample](https://github.com/NVIDIA-Omniverse/configurator-viewer-sample)のReadmeを見ると、[kit-app-template](https://github.com/NVIDIA-Omniverse/kit-app-template)で使用するように設計していると記載があるため、kit-app-templateのソースコードを調べてみました。

>This application is designed to be used with the USD Viewer Sample in https://github.com/NVIDIA-Omniverse/kit-app-template. However, with some editing, it can also be used to stream any other Kit application as well.
>
>このアプリケーションは、 https://github.com/NVIDIA-Omniverse/kit-app-templateUSD Viewer Sampleで使用するように設計されています。ただし、少し編集すれば、他のKitアプリケーションのストリーミングにも使用できます。

ストリーミング関係のキーワードで引っかかるだろうとあたりをつけて、streamでgrepしてみると、以下のようなコードがいくつか見つかりました。

```python 
omni.kit.app.get_app().get_message_bus_event_stream()
```

さらに上記でGoogle検索してみると、[Omniverse Developer Guide](https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/events.html)で使い方を見つけることができました。次のように実装するとOmniverse側でメッセージを受取り、任意の処理を実行できそうです。

```python
# App/Subscribe to Update Events
import carb.events
import omni.kit.app

update_stream = omni.kit.app.get_app().get_update_event_stream()

def on_update(e: carb.events.IEvent):
    print(f"Update: {e.payload['dt']}")

sub = update_stream.create_subscription_to_pop(on_update, name="My Subscription Name")
```

# 4. 拡張機能を作成してメッセージ受信の動作を検証してみた
Webクライアントからのメッセージを受信して、標準出力に出力するだけの拡張機能を作ってみました。

```python
import omni.ext

class MyExtension(omni.ext.IExt):
    def on_receive(self, e):
        print(f"on_receive_, Received event: {e.sender} {e.type} {e.payload}")

    def on_startup(self, _ext_id):
        print("[my_company.messageevent] Extension startup")

        ## メッセージ受信設定  #####
        bus = omni.kit.app.get_app().get_message_bus_event_stream()
        self.sub = bus.create_subscription_to_pop(self.on_receive)

    def on_shutdown(self):
        print("[my_company.messageevent] Extension shutdown")

```

[configurator-viewer-sample](https://github.com/NVIDIA-Omniverse/configurator-viewer-sample)を動作させます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4072158/a9164c4d-1c90-4cc0-b237-775f25be6417.png)

一番上のPackageのプルダウンを変更してみます。(Oliveに変更)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4072158/235b9b6a-f585-4dcb-be0e-026dfecb828f.png)

Omniverse側の標準出力には以下が出力されました。Packagesの変更メッセージを受信できていそうです。

>on_receive_, Received event: 0 10878282169950594789 {'message': '{"event_type":"setPackage","payload":{"carPaint":"Olive","wheelColors":"Gold","lightStripColor":"blue","intLeather":"Tan","intLeatherDash":"Tan","intTrimColor":"Black","stitchColor":"Black","screenColor":"Blue"}}'}


configurator-viewer-sampleのソースを確認してみると[Window.tsc](https://github.com/NVIDIA-Omniverse/configurator-viewer-sample/blob/main/src/Window.tsx)にかきのメッセージ送信処理を見つけることができました。

```python
    /**
     * @function _onSelectPackage
     *
     * Updates the car colors
     *
     * @param {PackageOption} option 
     */
    _onSelectPackage(option: PackageOption) {
        const message: AppStreamMessageType = {
            event_type: "setPackage",
            payload: {
                "carPaint": option.carPaint,
                "wheelColors": option.wheelColors,
                "lightStripColor": option.lightStripColor,
                "intLeather":  option.intLeather,
                "intLeatherDash": option.intLeatherDash,
                "intTrimColor": option.intTrimColor,
                "stitchColor": option.stitchColor,
                "screenColor": option.screenColor
            }
        };
        AppStream.sendMessage(JSON.stringify(message));
        
    }
```


# 5. うまくいかなかったこと
[Omniverse Developer Guide](https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/events.html)では、特定のイベントに絞り込んでsubscribeできるような例が載っているのですが、Webクライアントからのメッセージの受信時はできないようです。
どうやらWebクライアントで発生させたイベントには使えず、Omniverseの拡張機能内で発生させたイベントの受信にのみ使えるようです。

```
# これはうまく動作しなかった
sub2 = bus.create_subscription_to_pop_by_type(MY_CUSTOM_EVENT, on_event)
```

また、Omniverseの拡張機能側からWebクライアント側に任意のイベントを送信するのもできそうに見えたのですが、実機で動かすことはできませんでした。

[AppStream.tsx](https://github.com/NVIDIA-Omniverse/configurator-viewer-sample/blob/main/src/AppStream.tsx)に以下のようにイベントを受け取るための設定あり、また[Omniverse Developer Guide](https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/events.html)にも拡張機能側でイベントをpushするAPIもあるためできそうではあったんですが。。。。。

```python
 AppStreamer.setup({
                    streamConfig: streamConfig,
                    onUpdate: (message: StreamEvent) => this._onUpdate(message),
                    onStart: (message: StreamEvent) => this._onStart(message),
                    # Omniverse側で発生させたイベントを取得できそう
                    onCustomEvent: (message: any) => this._onCustomEvent(message),
                    authenticate: false,
                    nativeTouchEvents: true,
                    doReconnect: true,
                    onStop: function (message: StreamEvent): void {
                        console.log(message);
                    },
                    onTerminate: function (message: StreamEvent): void {
                        console.log(message);
                    },
                    onISSOUpdate: function (message: StreamEvent): void {
                        console.log(message);
                    }

                })
```


# 6. まとめ
今回は、Webクライントで発生したボタン押下などのイベントをOmniverseの拡張機能で受け取れるか検証を行いました。