Web of Things(WoT)のFirestore Bindingの紹介

# はじめに

Web of Things(WoT)は、Web技術を使ってInternet of Things(IoT)を実現するための、W3Cが策定する[標準技術](https://www.w3.org/WoT/)です。
WoTでは、センサーデータなどを生み出すThingがサーバとなり、Thingが生み出すデータを収集・分析するプログラムがクライアントとなります。WoTでは、クライアントとサーバ間の通信はBindingと呼ぶインターフェースが標準化されたプログラムが担います。Bindingには、HTTP、WebSocket、MQTTなどのプロトコルを実装したものがあります。インターフェースが標準化されているため、IoTシステムの要求に合わせてBindingは入れ替え可能です。

今回、Firestoreを利用したBindingを作成し、npmjsに[公開](https://www.npmjs.com/package/@hidetak/binding-wotfirestore)しましたので紹介します。

またここでご紹介するサンプルは以下で公開していますのでご参照ください。

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

# Web of Thingsとは

WoTは以下の利点があります。

* 広く利用されているWeb技術を利用してIoTシステムを開発できるため開発者を確保しやすいです
* Thingのインターフェース(以下の３種類)はThing Descriptionと呼ぶ標準化された表現方法で定義できます(Thing Descriptionで定義できる振る舞いを処理するプログラムを作れば、汎用的なThingのクライアントを実現することも可能です)
  * **Property**: Thingが持つ情報を公開します。センサデータを提供する場合にはプロパティとして公開します。プロパティには読み取り専用/読み書き可能や、変化のあった際に通知するかどうかの属性があります。
  * **Action**: Thingが持つ機能を公開します。
  * **Event**: Thingで発生した事象を通知します。
* WoTのThingとクライアントとの通信は、インターフェースが標準化されたBindingと呼ぶプログラムで実現されるため、IoTシステムの要求に合わせて適切なBindingを利用することができます
* WoT Scripting APIが定義されており、Thingの実装やクライアントの実装はこのAPIにしたがって行えば良いです。Scripting APIによる実装は、通信の仕方を規定するBindingとは切り離されるため、サーバ/クライアントのプログラムとBindingは自由に組み合わせられるようになります。

WoTの実装はいくつかありますが、[Eclipse Thingweb node-wot](https://github.com/eclipse/thingweb.node-wot)を利用しました。

# Firestore Bindingの特徴

WoTではWeb技術をベースにするため、普通、BindingにもHTTPやWebSocket、MQTTなどのプロトコルを利用します。これら３つのプロトコルの場合、以下の課題があります。

* HTTPやWebSocketのBindingは、ThingがFirewall内に存在するときはクライアントからアクセスすることができません（HTTPのトンネリングなどの仕組みを別途用意する必要があります）
* MQTTの場合は、ThingがFirewall内でも通信することができますが別途MQTTブローカーを用意する必要があります（無料で運用に利用できるMQTTブローカーがあれば良いと思ったのですが良さそうなサービスを見つけることができませんでした）

一方、FirestoreをBindingに用いると、ThingがFirewall内にあっても利用でき、またFirestoreは無料プランを提供しているため無料で試すことも可能です（予期せぬ課金が行われないように、念の為注意してください）。

Firestore Bindingは、FirestoreをThingとクライアントとの間でデータのやり取りをするために利用しており、Firestoreへのデータの蓄積は行いません。

# Firestoreの準備

Firestore Bindingを利用できるように、[Firestore console](https://console.firebase.google.com/)でFirebaseのセットアップを行います。

1. Firebase consoleにサインインします
１. プロジェクトを追加します 
1. Authenticationの利用を開始します
1. `Sign-in Method`タブを開き、メール/パスワードを有効にします
1. `Users`タブを開き、ユーザーを追加します(メールとパスワードを入力します) (A)
1. 左側のメニューからCloud Firestoreを開き、`データベースの作成`を行います
1. `ルール`タブを開き、Firestoreを読み書きするにはユーザ認証を必須とします(以下に例を示します)

    ```
    rules_version = '2';
    service cloud.firestore {
        match /databases/{database}/documents {
            match /{document=**} {
                allow read, write: if request.auth.uid != null;
            }
        }
    }
    ```

1. 左上のギアアイコンをクリックし、`プロジェクトを設定`をクリックします
1. 表示されたプロジェクトID(B)とウェブAPIキー(C)を確認します

(A), (B)、(C)は、後ほど説明する設定ファイルに記載する情報です。

# Firestore Bindingを使ったプログラム開発

今回、作成するのは、`count`のプロパティを持つThingと、そのプロパティを取得するクライアントの二つのプログラムです。

## 準備

通常のnodejsプログラムの作り方と同様に`npm init`によりアプリの開発を開始します。

```
$ npm init
```
上記を実行すると、nameやversionなどの入力を求められますので適宜適切な値を入力します。

以下のコマンド実行により、必要なモジュールをインストールします。

```
$ npm install -S @node-wot/core
$ npm install -S @hidetak/binding-wotfirestore
```

## 設定ファイルの作成

まず、Firestoreの設定を記載したJSONファイルを作成します。設定内容を以下に記載します。
(A), (B)、(C)は、`Firestoreの準備`で示した値です。

```json
{
    "hostName": "<任意のホスト名を入力>",
    "firebaseConfig": {
        "apiKey": "<(C)を入力>",
        "projectId": "<(B)を入力>",
        "authDomain": "<(B).firebaseapp.comを入力>"
    },
    "user": {
        "email": "<(A)のメールアドレスを入力>",
        "password": "<(A)のパスワードを入力>"
    }
}
```

この設定ファイルは後述するプログラムから参照します。

## プログラム例

WoTで定義された標準技術の一つである[Scripting API](https://www.w3.org/TR/wot-scripting-api/)を利用してThingやクライアントを実装します。

ここでは単純な例として、countプロパティを持つThingプログラムを作成し、クライアントプログラムからThingのcountプロパティを取得します。

### Thingプログラム

**example-thing.js**

```js
const Servient = require("@node-wot/core").Servient
const WoTFirestoreServer = require("@hidetak/binding-wotfirestore").WoTFirestoreServer
const WoTFirestoreCodec = require("@hidetak/binding-wotfirestore").WoTFirestoreCodec

// 設定ファイルを読み込みます
const firestoreConfig = require("./firestore-config.json")

// create server
const server = new WoTFirestoreServer(firestoreConfig)

// create Servient and add Firebase binding
let servient = new Servient()
servient.addServer(server)

const codec = new WoTFirestoreCodec()
servient.addMediaType(codec)

servient.start().then((WoT) => {
    // countのプロパティを持つThing Descriptionを定義します
    WoT.produce({
        "@context": "https://www.w3.org/2019/wot/td/v1",
        title: "MyCounter",
        properties: {
	    count: {
                type: "integer",
                observable: true,
                readOnly: false        
            }
        }
    }).then((thing) => {
        console.log("Produced " + thing.getThingDescription().title)
        // countに0を入力します
        thing.writeProperty("count", 0)
        // Thingを公開状態にします
        thing.expose().then(() => {
            console.info(thing.getThingDescription().title + " ready")
            console.info("TD : " + JSON.stringify(thing.getThingDescription()))
            thing.readProperty("count").then((c) => {
                console.log("count is " + c)
            })
        })
    })
})
```

### クライアントプログラム

**example-client.js**

```js
const Servient = require("@node-wot/core").Servient
const WoTFirestoreClientFactory = require("@hidetak/binding-wotfirestore").WoTFirestoreClientFactory
const Helpers = require("@node-wot/core").Helpers
const WoTFirestoreCodec = require("@hidetak/binding-wotfirestore").WoTFirestoreCodec

const firestoreConfig = require("./firestore-config.json")

let servient = new Servient()
const clientFactory = new WoTFirestoreClientFactory(firestoreConfig)
servient.addClientFactory(clientFactory)

const codec = new WoTFirestoreCodec()
servient.addMediaType(codec)
  
let wotHelper = new Helpers(servient)
// Thing Descriptionを読み込みます
wotHelper.fetch("wotfirestore://sample-host/MyCounter").then(async (td) => {
    try {
        servient.start().then((WoT) => {
            // Thing Descriptionを解釈し、thingオブジェクトを生成します
            // 以降、thingオブジェクトを操作することでThingプログラムにアクセスできます
            WoT.consume(td).then((thing) => {
                // Thingプログラムで定義したcountプロパティを読み込みます
                thing.readProperty("count").then((s) => {
                    console.log("count=", s)
                })
            })
        })
    } catch (err) {
        console.error("Script error:", err)
    }
}).catch((err) => { console.error("Fetch error:", err) })
```

## プログラムの実行

Thingプログラムをまず実行します。

```
$ node example-thing.js
```

次に、クライアントプログラムを実行します。

```
$ node example-client.js
```

読み込みに成功するとクライアントプログラムが読み込んだThingのcountプロパティの値をTerminalに表示します。この値が、ThingプログラムからクライアントプログラムにFirestoreを介して取得された値です。

```
count= 0
```

Thingプログラムのcountの値を変更することで、クライアントプログラムで取得される値も変更されることが分かると思います。

# おわりに

WoTの簡単な紹介と、Firestoreを使ったWoTのBindingを紹介しました。

WoTの実装には、[Eclipse Thingweb node-wot](https://github.com/eclipse/thingweb.node-wot)を利用しましたが、WoTの考え方がわかればWoTの実装に利用し易いと思いました。

今回紹介したプログラムはPropertyを取得する単純なものでしたが、Propertyを更新したり、ActionやEventも利用できますので試して頂ければ幸いです。