はじめに
Web of Things(WoT)は、Web技術を使ってInternet of Things(IoT)を実現するための、W3Cが策定する標準技術です。
このWoTを利用してセンサーなどを接続したデバイス上で稼働するThingと、Thingと接続してデータの確認や操作を行うClientを各々ブラウザ上で実現してみます。今回実現するThingはcountというプロパティを持つダミーですが、Webブラウザ上でもGPSの値や向きなどを取れるため、そのようなデータを取得できるように実装しても良いかもしれません。
WoTではThingのインターフェースをThingDescriptionで定義します。インターフェースには以下の3種類があります。
- プロパティ: Thingが持つセンサーなどの情報にClientからアクセスできるようにします。プロパティには読み取り専用/読み書き可能や、変化のあった際に通知するかどうかの属性があります。
- アクション: Thingが持つ機能をClientから呼び出せるようにします。
- イベント: Thingで発生した事象をClientに通知します。
また、ThingとClient間の通信はBindingと呼ぶ標準化されたモジュールが担います。
今回はFirestoreを利用したBindingのブラウザ版を利用します。このBindingの詳細についてはWeb of Things(WoT)のFirestore Bindingの紹介をご参照ください。
またここで紹介するサンプルは以下で公開していますのでソースコードの全体はこちらで確認してください。
- https://github.com/hidetak/node-wot-sample
- binding-wotfirestore-browser-bundle-basicディレクトリ
実装方法の概要
Thing
WoTを利用した場合、Thingの実装手順は以下となります。
- Thingが持つインターフェースをThingDescriptionとして定義する
- ThingDescriptionで定義したインターフェースの実装を行う
Client
WoTを利用した場合、Thingに接続してThingの出す値を参照したりThingを操作するClientを実装する手順は以下となります。
- ThingDescriptionをフェッチする(Thingを操作するためのThingオブジェクトが生成される)
- Thingオブジェクトを通してThingの操作を行う
実装方法
ThingDescriptionのインターフェースの種類毎に実装の例を示します。
ライブラリの読み込み
Thing、Client共に以下のスクリプトを読み込みます。
<script src="https://code.jquery.com/jquery-3.5.1.js" integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@node-wot/browser-bundle@latest/dist/wot-bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@hidetak/binding-wotfirestore-browser-bundle@latest/dist/binding-wotfirestore-bundle.min.js"></script>
画面の操作にjqueryを利用していますので最初の行で読み込んでいます。
Firestoreとの接続設定の読み込み
Firestoreを利用したBindingを利用するために、Firebaseを設定する必要があります。設定の仕方は以下を参照ください。
また、Firestoreと接続するための設定を読み込む必要があります。今回、設定はJavaScriptのファイルで定義するようにしました。内容を以下に示します。
const firestoreConfig = {
"hostName": "sample-browser-host",
"firebaseConfig": {
"apiKey": "<API Key of Firebase>",
"projectId": "<Project Id of Firebase>",
"authDomain": "<Auth Domain of Firebase(Usually it will be <projectId>.firebaseapp.com>"
},
"user": {
"email": "<email address registered in Firebase>",
"password": "<password corresponding to the email address above>"
}
}
Firebaseに設定した内容に応じて適宜設定を記載してください。設定したファイルは通常のJavaScriptの読み込みと同様に以下のように読み込みます。
<script src="firestore-config.js"></script>
注意: 今回はサンプルであるためJavaScriptファイルに設定を記載して読み込ませていますが、この方法ではWebブラウザを利用するユーザーが簡単にFirestoreに接続するための認証情報(具体的にはuserのemailとpassword)を見ることができます。本来、この認証情報が見えてはいけないため、FirebaseのAuthentication機能を利用するなどしてユーザーにFirebaseにログインしてもらった上で利用できるようにすべきです。
プロパティ
Thingのプロパティ実装
countというプロパティを定義し、ユーザーがその値を自由に変更できるようにします。
また、そのcountの値が変更されると、Clientに値が通知されるようにします。
1. Thingが持つインターフェースをThingDescriptionとして定義する
countプロパティのThingDescriptionの定義を以下に示します。
properties: {
count: {
type: "integer",
observable: true, // 変更されたときにClientに通知されるようにします
readOnly: false // countを変更できるようにします
}
},
2. ThingDescriptionで定義したインターフェースの実装を行う
countプロパティの実装を以下に示します。
<div>count: <span id="countArea"><input id="countInput" type="text" value=""/><button id="countUpdate">update</button></span></div>
・・・
thing.writeProperty("count", 100) // countプロパティに100を書き込みます
$("#countUpdate").click(()=>{
let v = Number($("#countInput").val())
thing.writeProperty("count", v) // updateボタンを押すと入力フォームに入力された値をcountプロパティに書き込みます
thing.emitEvent('update', v) // イベントを発行します(あとで説明します)
})
Clientのプロパティ実装
1. ThingDescriptionをフェッチする
FirestoreのBindingでは、ThingのThingDescriptionにはwotfirestore://
ではじまるURLでアクセスできます。以下のコードでThingDescriptionのURLをフェッチし、ClientからThingにアクセスするためのthing
オブジェクトを作成します。
wotHelper.fetch("wotfirestore://sample-browser-host/MyCounter").then(async (td) => {
servient.start().then((WoT) => {
WoT.consume(td).then((thing) => {
・・・
}
}
}
2. Thingオブジェクトを通してThingの操作を行う
Thingのcountの値が変化したときにClientで検知できるようにします。また、初期値を画面に表示するためにThingの値を取得します。
<div>count: <span id="count">no data</span></div>
・・・
thing.observeProperty("count", (v) => {
$("#count").html(v) // Thing側でcountの値が更新されたときにこのコールバック関数が呼ばれます
})
thing.readProperty("count").then((v) => {
$("#count").html(v) // Thingからcountの値を読み込み、画面に表示します
})
アクション
Thingのアクション実装
resetアクションを定義します。
resetアクションを実行すると、countプロパティを0に変更します。
1. Thingが持つインターフェースをThingDescriptionとして定義する
resetアクションのThingDescriptionの定義を以下に示します。
actions: {
reset: {
description: 'Resetting counter value',
}
},
2. ThingDescriptionで定義したインターフェースの実装を行う
resetアクションの実装を以下に示します。
thing.setActionHandler('reset', async () => { // resetアクションが呼び出されるとこのコールバックが実行される
await thing.writeProperty('count', 0) // countプロパティの値を0にする
await thing.emitEvent('update', 0) // イベントを発行します(あとで説明します)
})
Clientのアクション実装
1. ThingDescriptionをフェッチする
プロパティでの説明と同様です。
2. Thingオブジェクトを通してThingの操作を行う
Clientからresetアクションを呼び出す例を以下に示します。
<div><button id="resetButton">reset</button></div>
・・・
$("#resetButton").click(()=>{
thing.invokeAction("reset")
})
resetボタンを画面に配置し、ボタンをクリックするとresetアクションを呼び出します。
イベント
Thingのイベント実装
今回は、countプロパティが変化したことをイベントとして通知する。
1. Thingが持つインターフェースをThingDescriptionとして定義する
イベントの通知と共にcountの値を通知することにします。
events: {
update: {
description: 'Update count event',
type: 'integer' // countの値を通知するためintegerとします
}
}
2. ThingDescriptionで定義したインターフェースの実装を行う
イベントの通知は以下のように実装します。
thing.emitEvent('update', v) // vはcountプロパティの値です
プロパティとアクションの実装で示したようにcountプロパティに
Clientのイベント実装
1. ThingDescriptionをフェッチする
プロパティでの説明と同様です。
2. Thingオブジェクトを通してThingの操作を行う
Thingで発生したイベントの通知を受け取るように実装します。
<div>update: <span id="update"></span></div>
・・・
thing.subscribeEvent("update", (v) => {
$("#update").html(v) // イベントとして通知されるcountプロパティの値を画面に表示します
setTimeout(() => { // 3秒後に上記の値を消去します
$("#update").html("")
}, 3000)
})
実行
上記で説明した実装の全体は以下で公開しています。
- https://github.com/hidetak/node-wot-sample
- binding-wotfirestore-browser-bundle-basicディレクトリ
Thingはthing.html、Clientはclient.htmlに実装しています。実行するには、各HTMLをWebブラウザに読み込むだけです。
thing.html画面でcountプロパティの値を変更すると、client.html画面に表示したcountプロパティの値が自動的に変更した値に更新されます。また、イベントも発行されるため、client.html画面のupdate欄にもcountプロパティの値が表示され、3秒後に消えます。
また、client.html画面にあるresetボタンを押すと、Thingのcountプロパティが0にリセットされます。thing.html、client.htmlの両画面のcountプロパティが0になり、さらにclient.htmlにはイベントも通知されるため、update欄に0が表示され3秒後に消えます。
おわりに
この記事では、WoTのFirestore Bindingを利用したWebブラウザ上で動作するThingとClientの実装方法を説明しました。
具体的には、WoTのThingの構成要素であるプロパティ、アクション、イベントの実装の仕方を示しました。
プロパティ、アクション、イベントのインターフェースはThingDescriptionとしてThing側で定義しておきます。これをClient側でフェッチすることにより、ClientからThingを操作するためのオブジェクトが自動的に作られます。このオブジェクトを操作することでClientからリモートにあるThingを簡単に操作できますので、IoTシステムの開発のときはThingでセンサーデータなどを扱う部分やClientで収集したデータを処理する部分など、作りたいIoTシステム独自の部分の開発に集中できるようになりますし、同じインターフェースを持つThingが複数種類ある場合には、それらを入れ替えたり、またClientを入れ替えることも実装を変更することなく柔軟に実現できます。
また、ここで説明したプロパティ、アクション、イベントの実装の仕方は基本的にBindingの種類に依存しないため、Firestore Binding以外を利用する場合にも使えるはずです。つまり、WoTを利用することで、ThingやClientの実装とBindingの実装は切り離せるため、通信の仕方が変わった場合にもBindingを入れ替えることで対応できます。
WoTによる開発の参考になれば幸いです。