Help us understand the problem. What is going on with this article?

ThingWorx マッシュアップの再利用性を考える

はじめに

対象読者
この記事は、ThingWorx を使ったマッシュアップ作成に関するヒントを提供します。このため、マッシュアップビルダーの使いかたを一通り理解している人を対象としています。

対象バージョン
New Composer を使用するため、ThingWorx 8.3 以降に対応します。

ウィジェットと Thing のバインド

まずは次のマッシュアップを見てください。

image.png

ゲージウィジェットと LED ディスプレイウィジェットがふたつづつありますね。左側のゲージと LED には qiita.device.01 というデバイスのセンサー情報が、右側のゲージと LED には qiita.device.02 というデバイスのセンサー情報がそれぞれ表示されています。

もっとも簡単にこのマッシュアップを作るには、ウィジェットと Thing のバインディングは次の画像のようになります。

image.png
image.png

また、このときマッシュアップビルダー右側のデータパネルにはこのように、Thing が二つ登録されています。

image.png

このマッシュアップは完全に動作しますし、データとウィジェットのバインド関係もシンプルでわかりやすく、メンテナンス性に優れていると言えそうです。ただし、ポーティングを考えなければ

開発環境と本番環境のように、複数の ThingWorx サーバーを運用している場合に何が起こるでしょうか? このマッシュアップを開発環境で作成したとします。本番環境にポーティングするには、開発環境でマッシュアップをエクスポートして、本番環境にインポートすればよいのですが、このマッシュアップが「なんの変更もなく」本番環境で動くには条件がいくつかあります。もっとも気にしなければならないのは...

  • 開発環境でバインドした Thing と同名の Thing が本番環境にもある

ということですね。マッシュアップのエクスポートとインポートでは、ウィジェットとデータのバインディング状態も合わせて複製されます。このため、開発環境で qiita.device.01 という名前の temperature というデータをゲージウィジェットにバインドさせていれば、ThingWorx サーバーはインポート先の本番環境でも qiita.device.01 という Thing にバインドさせようとします。そして、qiita.device.01 という Thing が本番環境に存在しなければ、このマッシュアップは動きません。

開発環境と本番環境のような関係ではなく、マザー工場と子工場のような関係ではどうでしょうか? マザー工場で見える化の仕組みをつくり、それを子工場へ配布することを考えます。このときも、「全部の子工場でマザー工場と同じデバイス名やプロパティ名で Thing が構成されている」場合に限り、マッシュアップを修正せずにそのまま子工場で動かせます。

各環境でデバイスが同じ名前で Thing として登録されているというのは、どの程度「ありそうなこと」でしょうか?

それでは、「各環境ではデバイスが違う名前の Thing として登録されている」ときに、マッシュアップの可搬性を上げるためにどういったアプローチがとれるか考えてみましょう。

方法その1: ちから技

ThingWorx でマッシュアップを他の環境へ持ち込む一般的な方法は、マッシュアップのエクスポートとインポートです。実は ThingWorx 8.5 からは素敵な ThingWorx Solution Portal という複数インスタンス環境でのアプリケーション配布の仕組みが備わっていますが、この記事では取りあげません。

マッシュアップをエクスポートするときには、出力形式をバイナリファイルか XML か選べます。(下図参照)、

image.png

XML を選んで Export ボタンを押すと、マッシュアップの定義が含まれた XML ファイルがローカルフォルダに保存されます。

XML ファイルには JSON の記述が大量に含まれており、データパネルで呼び出している Thing やそのサービス、ウィジェットにバインドしているプロパティの情報などがすべて記述されています。この XML ファイルを開いて、デバイス名をすべてインポート先の名前に置き換えると、インポート先でそのまま動くマッシュアップになります。

下記はデータパネルの設定

export.json
 "Data" : {
    "Things_qiita.device.01" : {
      "DataName" : "Things_qiita.device.01",
      "EntityName" : "qiita.device.01",
      "EntityType" : "Things",
      "Id" : "cb56883b-79e6-41fd-9d9f-444a2287454e",
      "Services" : [ {
        "APIMethod" : "get",
        "Characteristic" : "Services",
        "Id" : "96ab37b8-948c-4478-bcd1-2fc9281b3e8c",
        "Name" : "GetProperties",
        "Parameters" : { },
        "RefreshInterval" : 0,
        "Target" : "GetProperties"
      } ]
    },
    "Things_qiita.device.02" : {
      "DataName" : "Things_qiita.device.02",
      "EntityName" : "qiita.device.02",
      "EntityType" : "Things",
      "Id" : "222f9106-4826-42a5-9113-26a7c23cd660",
      "Services" : [ {
        "APIMethod" : "get",
        "Characteristic" : "Services",
        "Id" : "fccae8ed-4de3-441a-9947-b9bba6acbdcb",
        "Name" : "GetProperties",
        "Parameters" : { },
        "RefreshInterval" : 0,
        "Target" : "GetProperties"
      } ]
    }

こちらはバインディング情報の設定

export.json
  "DataBindings" : [ {
    "Id" : "f3796daf-570d-4cf4-b21d-ec2e60977943",
    "PropertyMaps" : [ {
      "SourceProperty" : "Humidity",
      "SourcePropertyBaseType" : "NUMBER",
      "SourcePropertyType" : "Property",
      "TargetProperty" : "Data",
      "TargetPropertyBaseType" : "NUMBER",
      "TargetPropertyType" : "property"
    } ],
    "SourceArea" : "Data",
    "SourceDetails" : "AllData",
    "SourceId" : "GetProperties",
    "SourceSection" : "Things_qiita.device.01",
    "TargetArea" : "UI",
    "TargetId" : "leddisplay-5",
    "TargetSection" : ""
  }

注意事項

この方法、容易に予想できると思いますが、大変にミスをしやすいです。また、当然何かあった場合にも PTC によるサポートサービスは受けられません。開発環境同士でのマッシュアップのポーティングや、緊急避難的な方法です。ただ、こうした方法もあるということを頭の片隅に置いておくと役に立つこともありそうです。... あるかな...。

方法その2: バインディングを実行時に決定する

それでは別の方法を見ていきましょう。最初に作ったシンプルな例では、ウィジェットにバインディングされるデータはどれか(qiita.device.01 の Tempreture を Gauge-01 へ、など)を、マッシュアップのデザイン時に決めていました。デザイン時に固定されたバインディングは、実行時に変更することはできません。

そこで、デザイン時にバインディングのソースとなる Thing を固定せず、実行時に動的に決定する方法を紹介します。

データは Thing ではなく、ThingTemplate を使う

ThingTemplate には、自分自身から派生している Thing を返す GetImplementingThings() サービスがあります。このサービスを使うと、実行時に「どの Thing のデータをマッシュアップで使うのか」を動的に決められます。

さっそく見ていきましょう。

まず、新しいマッシュアップをつくります。リストウィジェットをひとつ、ゲージウィジェットをひとつ、そして LED ウィジェットを一つ配置します。

image.png

つぎに、このマッシュアップで使うデータを読み込みます。このとき、"Select Entity" のベースタイプを "Thing" ではなく、"Thing Template" に変更し、読み込むデバイスの Thing Template を選択します。ここでは "qiita.tt" という名前を選択しています。

image.png

その後、"qiita.tt" のもつ "GetImeplmentingThings" サービスを選択し、"Mashup Loaded?" にチェックを入れて "Done" ボタンをクリックします。

image.png

データパネルには次のように表示されます。

image.png

さて、ここですこし補足を。上記の操作で、"Select Entity" に "qiita.tt" という Thing Template を指定しました。つまり、本記事の例題では、qiita.device.01 も qiita.device.02 も qiita.tt という Thing Template から派生しています。qiita.tt 自体は Generic Thing Template から派生していて、Generic Thing Template のもつ GetImplementingThing を継承しています。

実行時に利用するデバイスを動的に決める場合には、どの Thing Template に対して GetImplementingThings() を呼び出すかが重要になります。すべてのデバイスが Generic Thing Template や Remote Thing などの基本的な Thing Template から派生していると、Thing の絞り込みが十分に行われず、マッシュアップの開発時に GetImplementingThings() などのサービスを上手に使いこなせなくなる場合があります。

Thing をグループ化し、それぞれのグループに対応する Thing Template を作ることが、効率的にマッシュアップを開発する上ではとても大切です。こうした Thing Template の設計や Thing のグループ化のことを ThingWorx の「モデル設計」といいます。ThingWorx は画面開発がとても簡単な仕組みですが、その裏には「モデル設計がしっかりとできていること」が前提として存在することを書き記しておきます。

動的に Thing を決定する

まず、読み込んだ GetImplementingThings() の All Data をリストウィジェットにバインドします。リストウィジェットの Display Field と Value Field にはそれぞれ "Name" を指定しておいてください。

この状態でマッシュアップをセーブして実行すると、次の画面のようになります。

image.png

リストウィジェットに "qiita.tt" Thing Template から派生している五つの Thing が表示されていますね。リストウィジェットは、各アイテムを選択できるようになっており、どのアイテムを選択したかを他のウィジェットやサービスに通知することができます。(選択状態を共有できます)。

選択された Thing のプロパティを読み込む

つぎに、データパネルでさらにサービスを追加します。(すでに読み込んでいる Thing Template の名前の右隣ではない)データパネルのトップレベルに表示されているプラスボタンをクリックして、Add Data 画面を表示します。もう一度 "Select Entity" に Thing Template を指定し、先ほどと同じ Thing Template を選択します。

image.png

今度は、Thing Template の名前の右側にある "Dynamic?" ボタンにチェックを入れます。この Dynamic の指定こそが、バインドする Thing の決定を実行時にまで遅延させる仕掛けになります。

Service には GetProperties() を選択し、"Mashup Loaded?" はチェックせずに "Done" ボタンをクリックします。なぜ Mashup Loaded をチェックしないのかというと、この GetProperties() サービスはマッシュアップの開始時には呼び出したくないからですね。この GetProperties() は、リスト上で Thing を選択したあとで実行するようにします。

操作完了後には、データパネルは次のようになります。

image.png

"Dynamic_" で始まるサービスが追加されていますので、その中から適当なプロパティを選んでウィジェットにバインドさせておきます。
今回は Temperature をゲージウィジェットへ、Humidity を LED ディスプレイウィジェットへバインドさせました。

image.png

さて、ここで実施した処理を少し解説しておきましょう。実際にウィジェットにバインドしたデータはなんでしょうか?メモリ上に実態を持つ Thing のプロパティではなく、Thing の雛形となる Thing Template のプロパティをバインドしています。Thing Template 自体はあくまでも雛形ですから、それ自身はどんなデータも持っていません。データを持っているのはあくまでも、雛形から生成されてインスタンス化される Thing です。したがって、このままではゲージウィジェットも LED ディスプレイウィジェットも、表示すべき数値データを得られません。

そこで、いよいよこの章の目的である、各ウィジェットがバインドしているデータを実行時に決定するための仕込みをします。

GetImplementingThings と GetProperties を結びつける

しかけの一つ目を説明します。GetImplementingThings() サービスでは、Thing Template を実装している Thing の一覧が得られます。この Thing の一覧は、現在はリストウィジェットにバインドされています。そしてリストウィジェットでは、アイテムを選択できます。この一連の流れで、リストウィジェット上で Thing を選択すると、Thing Template から派生している Thing のうちの一つを特定できることになります。これはユーザー操作です。(つまり、動的操作です)

しかけの二つ目はこうです。リストの選択状態は GetImplementingThings() サービスの "Selectd Row(s)" に反映され、さらに "Selected Row(s)" はバインディングソースとして利用でき、かつ GetProperties() サービスには入力パラメータとして Thing 名を受け付ける、というものです。これはデザイン時の設計です。(つまり、静的定義です)

言葉で説明するとわかりづらいので、画面操作を見ていきましょう。簡単です。

まず、GetImplemtingThings サービスの Selectd Row(s) から、Name を選んで GetProperties サービスの "Entity Name" 入力パラメタへバインドします。

image.png

この操作だけで、GetProperties サービスは次の状態で呼び出されるようになります。

  • GetImeplentingThings サービスが戻す Thing の一覧の中から、ユーザーが選択した Thing に対して GetProperties を呼び出す。

つまり、実行時にユーザーが選択した Thing に対して動的に GetProperties() サービスを呼び出しているんですね。デザイン時に GetPropertires() を呼び出す Thing を固定していないため、ウィジェットと Thing が直接結びついていません。Thing Template を介してウィジェットとデータソースの結合状態が緩やかになる(疎結合になる)ため、柔軟性が高い設計と言えます。

サービスを呼び出すタイミング

あと一つ、手順が必要です。GetProperties() サービスをデータパネルに登録する際、"Mashup Loaded" にチェックしませんでした。このままでは、GetPropertis() サービスを誰も呼び出さず、ウィジェットにデータが渡りません。

そこで、GetImplementingThings() サービスの "Selected Row Changed" イベントを、GetPropertis() サービスに結びつけます。

image.png

こうすることで、このマッシュアップは下記の動作を行うようになります。

  • マッシュアップが開始したときに GetImplementingThings() サービスが呼ばれ、Thing Template から派生している Thing の一覧がリストウィジェットに展開される。
  • ユーザーがリストで Thing を選択すると、選択した Thing の名前が GetProperties() サービスの入力パラメタとしてセットされ、SelectedRowChanged イベントの発火に伴って GetProperties() サービスが呼び出される。
  • GetProperties() サービスから戻された数値データがウィジェットに渡される。

image.png

さて、本稿の目的は、環境に依存せずにマッシュアップを利用可能にするための方法、マッシュップの可搬性を上げるための方法を紹介するものでした。それでは、今説明している「バインディングを実行時に決定する」方法は、どのようにしてマッシュアップの可搬性を上げるのでしょうか?

たとえば開発環境と本番環境で、あるいはマザー工場と子工場でデバイスの名前が違っていても、そうしたデバイスが同じ Thing Template を親に持つならば、この方法で作成されたマッシュアップは変更を必要とせずどこでも動きます。繰り返しますが、この方法ではウィジェットとデータソースのバインディングは Thing を特定せずに、Thing Template を介して行われます。実際の Thing とウィジェットが密に結合していないため、可搬性が優れたものとなります。

方法3: ローカルバインドを使う

上記の「バインディングを実行時に決定する」方法はマッシュアップの可搬性を担保するには良い方法ですが、限界もあります。それは、マッシュアップに呼び出されてくる Thing が同じ Thing Template から作られていなければならない、ということです。ウィジェットとデータのバインディングはプロパティの名前を元に行われますから、そもそも開発環境と本番環境で、あるいはマザー工場と子工場でデバイスのプロパティ名が異なるという状況では方法2は使えません。また、数種類の本質的に異なるデバイス(その結果としてプロパティ名は非常に異なる)をマッシュアップで同様に扱いたいといった場合にも使えません。

そうした場合には、さらに一段「バインドするデータの抽象化」を押し進めることができます。この章では、「ローカルバインドによるデータソースの匿名化」に関して説明します。

ローカルバインドとは

ThingWorx には、Thing のプロパティを他の Thing のプロパティと結びつけ、データを同期させる機能があります。マッシュアップでデータソースとウィジェットを結びつける操作と同様に、この機能も「バインド」あるいは「バインディング」と呼ばれます。

Thing プロパティのバインドには二種類あり、ひとつは Kepware など遠隔のエージェントが送ってくるデータを Thing のプロパティに紐付ける「リモートバインド」、そしてもう一つが ThingWorx 内で、ある Thing を別の Thing と紐付ける「ローカルバインド」です。

参考までにリモートバインドを ThingWorx で設定している画面が下図になります。

image.png

上図の表で左端が Thing として保持しているプロパティの名前、一つ挟んで左から3番目にあるのが、リモートのデバイスが送ってきている実際のデータの名前です。三行目を見ると、リモートのデバイスでは "TankPressure" として表現されているデータが、ThingWorx 上では "Pressure" という名前のプロパティで管理されていることがわかりますね。

プロパティのバインディングは、Kepware や Edge Microserver、あるいは Ege SKD Client など AlwaysOn で ThingWorx と繋がるデバイスを処理するためには必須の操作ですが、こうした Remote Thing でなくとも、Thing 間でプロパティをバインドできます。

Thing 同士のバインドであるローカルバインドを指定する方法を、コンポーザーの操作を通して見ていきましょう。

コンポーザー画面にて、先ほど、方法1: や方法2: で使った、"qiita.device.01" の "Properties and Alerts" タブを確認します。そうすると、"Manage Bindings" という黒いボタンがあるのがわかります。

image.png

この "Manage Bindings" というボタンをクリックします。そうすると、"Manage Bidings" という画面が表示されますので、左上にある検索ボックスに、コピーしたいプロパティを持っている(ソースとなる)Thing の名前を入力して確定させます。すると、"Property" リストに、指定した Thing のプロパティが一覧表示されます。

下記の画像はバインディングソースとして "ArchiFuture2" という Thing を指定しています。

image.png

あとは左側のリストから右側のリストへ、バインドさせたいプロパティをドラッグ・アンド・ドロップするだけです。左側のリストがバインディングソース、右側のリストがバインディングターゲットです。ドラッグ操作をおこなうときは、左側のリストの右端にある十字矢印を摘んでください。

image.png

無事に操作が完了すると、右側のリストに "Property" および "Source" が表示されますので、バインディングの状態を確認できます。上図では、ArchiFuture2 の MixedPower という名前のプロパティが qiita.device.01 の Humidity へ、ArchiFuture2 の Pressure プロパティが qiita.device.01 の Temperature へ、それぞれバインドされていることがわかります。

操作が完了したら "Done" ボタンをクリックしてウィンドウを閉じ、編集中の qiita.device.01 を保存します。

ローカルバインドに必要な操作はこれだけです。簡単ですね。

ローカルバインドが必要な理由

さて、ローカルバインドを使うと、どうしてマッシュアップの可搬性がよくなるのでしょうか? それは、「マッシュアップが必要とする Thing の構造と、物理的な Thing の構造を分離できる」ことにあります。別の表現を使うなら、「画面要件から導き出されるモデルと物理モデルを分離できる」とも、「システム標準のモデルと物理モデルを分離できる」とも言えます。

言葉だとわかりづらいので、下の図を用いて説明しましょう。

image.png

この図では、ThingWorx のシステムが二つあります。左側にあるのが「自動車用システム」、そして右側にあるのが「電車用システム」です。
それぞれのシステムの下部に物理的なデバイスからのデータを受け取るための Thing があります。car.01 と tarin.99 ですね。これらの Thing を便宜上「物理 Thing」と呼びます。

図の上部にはマッシュアップがあります。このマッシュアップは自動車用システムも電車用システムも同一のもので、それぞれゲージウィジェットと LED ディスプレイウィジェットを一つ持ち、それらのウィジェットが qiita.device.01 という名前の Thing のプロパティにバインドされています。ここでは、便宜上 qiita.device.01 を「論理 Thing」と呼ぶことにします。

図を見るとすぐにわかることですが、マッシュアップが直接つながりを持っているのは論理 Thing である qiita.device.01 だけです。マッシュアップが論理 Thing と共にエクスポートされる限り、マッシュアップ側から見えるバインディング関係はどんな環境にインポートされようとも変化はありません。つまり、とてもポータビリティに優れた仕組みとなります。

一方で、論理 Thing のプロパティは、物理 Thing のプロパティに紐付けられてます。自動車システムでは Temperature が data_02 に、Humidity が data_01 に「ローカルバインド」されています。電車システムでは Temperature が 4212322 に、Humidity が3010100 にローカルバインドされています。マッシュアップからは物理 Thing のプロパティ名が隠されているので、Kepware や Edge Micro Server などから移入されてくるデータの差異にマッシュアップが影響を受けないことがわかりますね。

利点と注意点

ローカルバインドを利用して実際のデータを隠蔽する方法は強力です。この機能を駆使すると、画面構成から導き出される Thing のあるべき姿と、物理デバイスの都合によって実装しなければならない Thing の現実とを、上手に切り離すことができます。言い換えると、物理モデルと標準モデルを規定し、マッシュアップからは標準モデルだけを使って画面構築が可能になります。

マッシュアップと物理モデルの間に標準モデル(論理モデル)が挟まってデータを仲立ちすることで、マッシュアップと物理モデルを疎結合にしているんですね。

ただし、この方法にも注意すべき点があります。それは「ローカルバインドを誰がするか」を考えなければならない点ですね。

本稿ではコンポーザーからグラフィカル・ユーザー・インタフェースを用いてローカルバインドを行いました。これ以外の方法としては JavaScript でローカルバインドをするためのサービスを作り込むことが考えられます。

必要に応じて手動で都度行うか、一定の条件をトリガーにサービスで自動で行うか、運用を見据えた設計が必要ではあります。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした