Web of Things(WoT)を使ってWebブラウザ上で動作するIoTサンプルを作ってみる

# はじめに

Web of Things(WoT)は、Web技術を使ってInternet of Things(IoT)を実現するための、W3Cが策定する標準技術です。

このWoTを利用してセンサーなどを接続したデバイス上で稼働するThingと、Thingと接続してデータの確認や操作を行うClientを各々ブラウザ上で実現してみます。今回実現するThingはcountというプロパティを持つダミーですが、Webブラウザ上でもGPSの値や向きなどを取れるため、そのようなデータを取得できるように実装しても良いかもしれません。
WoTではThingのインターフェースをThingDescriptionで定義します。インターフェースには以下の３種類があります。

* **プロパティ**: Thingが持つセンサーなどの情報にClientからアクセスできるようにします。プロパティには読み取り専用/読み書き可能や、変化のあった際に通知するかどうかの属性があります。
* **アクション**: Thingが持つ機能をClientから呼び出せるようにします。
* **イベント**: Thingで発生した事象をClientに通知します。

また、ThingとClient間の通信はBindingと呼ぶ標準化されたモジュールが担います。
今回はFirestoreを利用した[Bindingのブラウザ版](https://www.npmjs.com/package/@hidetak/binding-wotfirestore-browser-bundle)を利用します。このBindingの詳細については[Web of Things(WoT)のFirestore Bindingの紹介](https://qiita.com/hidekita/items/9599cfd4e411fc0a409a)をご参照ください。

またここで紹介するサンプルは以下で公開していますのでソースコードの全体はこちらで確認してください。

* https://github.com/hidetak/node-wot-sample
* binding-wotfirestore-browser-bundle-basicディレクトリ

# 実装方法の概要

## Thing

WoTを利用した場合、Thingの実装手順は以下となります。

1. Thingが持つインターフェースをThingDescriptionとして定義する
2. ThingDescriptionで定義したインターフェースの実装を行う

## Client

WoTを利用した場合、Thingに接続してThingの出す値を参照したりThingを操作するClientを実装する手順は以下となります。

1. ThingDescriptionをフェッチする（Thingを操作するためのThingオブジェクトが生成される)
2. 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を設定する必要があります。設定の仕方は以下を参照ください。

* [Web of Things(WoT)のFirestore Bindingの紹介](https://qiita.com/hidekita/items/9599cfd4e411fc0a409a)

また、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プロパティが０になり、さらに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による開発の参考になれば幸いです。