IoTのAPIサービスから取得した温度計情報をVue.jsとCanvas Gaugesでレンダリングしてみた

# 概要

少し前に
　[『Gobotの招きにあひて、徒然なるままにArduinoとRaspberry PiでIoTっぽいことをやってみるなり』](http://qiita.com/KemoKemo/items/10fb644f9d359c35646a)
という記事で温度計情報をAPIとして公開できるIoTの仕組みを作りました。

やっぱり、情報とれるようになったら綺麗に描画したい！
今回はそんな感じの話をします。よろしくお願い致します！٩( 'ω' )و

なお、今回もGitHubに連動したリポジトリを作っています。あわせてご覧ください。

> [KemoKemo/IoT-Web-Sample](https://github.com/KemoKemo/IoT-Web-Sample)

# どうやって描画する？

[Vue.js](https://jp.vuejs.org/)使ってクライアントサイドでレンダリングします！
小さくはじめられる簡便性、プロジェクトが大きくなっても対応可能なスケーラビリティ性、仮想DOMによって実現される軽快動作のリアクティブ性などなどが気に入って1週間ほど前にVue.jsに入門しました。

## vue-material

Vue.js用のマテリアルデザイン実装である[vue-material](https://github.com/vuematerial/vue-material)も使って簡単に綺麗なUIを手に入れます。

**[vue-material](http://vuematerial.io/#/)**
![29198697-523e-4fad-a242-4c89beaa6957.png](https://qiita-image-store.s3.amazonaws.com/0/166225/f91be46b-cb57-e1ac-d934-297ea8b56170.png)

## Canvas Gauges

温度計や各種ゲージを模したUIパーツ[Canvas Gauges](https://github.com/Mikhus/canvas-gauges)も使います。

**[Canvas Gauges](https://canvas-gauges.com/)**
![f84ad7cb-8210-4e99-9842-6a7e5a694795.png](https://qiita-image-store.s3.amazonaws.com/0/166225/e117664c-4b68-7be3-746f-46e329cc8926.png)

## axios

APIサービスにアクセスするhttpクライアントには[axios](https://github.com/mzabriskie/axios)を使います。
ネット上では[vue-resource](https://github.com/vuejs/vue-resource)を使った記事が多いのですが、公式の以下の記事を呼んでaxiosに変更しました。

> [vue-resource の引退について](https://jp.vuejs.org/2016/11/03/retiring-vue-resource/)

# やってみた

```html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Temperature Monitor</title>
    <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic">
    <link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons">
</head>

<body>

    <div id="tempmon">
        <md-toolbar>
            <h1 class="md-title">Temperature Monitor</h1>
        </md-toolbar>

        <md-button class="md-raised md-primary" v-on:click="getData">Update</md-button>

        <md-layout md-gutter>
            <md-layout md-flex-xsmall="100" md-flex-small="50" md-flex-medium="33" class="main-content">
                <md-card>
                    <md-card-header>
                        <div class="md-title">{{ name }}</div>
                    </md-card-header>

                    <md-card-content>
                        <canvas id="gauge-id"></canvas>
                        <br>
                        <p>{{ message }}</p>
                    </md-card-content>
                </md-card>
            </md-layout>
        </md-layout>

    </div>

</body>

<script src="js/vue.js"></script>
<link rel="stylesheet" href="css/vue-material.css">
<script src="js/vue-material.js"></script>
<script src="js/axios.js"></script>
<script src="js/gauge.min.js"></script>
<script src="js/app.js"></script>

</html>
```

```js
Vue.use(VueMaterial);

// TODO: サービス実行時に外部の設定ファイルから値を受け取りたい
const baseURI = 'http://localhost:5000/api/v1';

let tempmon = new Vue({
    el: '#tempmon',
    data: {
        name: 'Sensor',
        gauge: null,
        message: '',
    },
    methods: {
        init: function() {
            this.gauge = new LinearGauge({
                renderTo: 'gauge-id',
                colorNumbers: 'red',
                minValue: -10,
                maxValue: 50,
                majorTicks: [-10, 0, 10, 20, 30, 40, 50],
                highlights: [{
                    "from": 30,
                    "to": 50,
                    "color": "rgba(200, 50, 50, .75)"
                }],
                width: 200,
                height: 400,
            });
        },
        getData: function() {
            axios.get(baseURI + '/sensors')
                .then((response) => {
                    let sensors = response.data.sensor_list;
                    this.name = 'Sensor: ' + sensors[0].name;
                    this.gauge.value = sensors[0].temp_c;
                    this.message = '';
                })
                .catch((error) => {
                    this.message = error;
                });
        }
    }
});

window.onload = function() {
    tempmon.init();
}
```

`LinearGauge`のインスタンスを生成してVueのdataにバインドする処理を`init`関数に実装し、`window.onload`のタイミングで呼ぶようにしました。これならhtml側に記述した`<canvas id="gauge-id"></canvas>`が存在するので`LinearGauge`の生成がうまくいきます。

APIサービスのベースURIは、Webサービス実行時の引数から反映したかったのですがうまく実装できず`baseURI`定数として`app.js`に直書きしてしまっております(´・ω・`) 賢者様、私にアドバイスください。。。

# 動かしてみた

![web-sample.gif](https://qiita-image-store.s3.amazonaws.com/0/166225/b97003a6-0078-1ce6-29ab-0de7ccb48090.gif)

こんな感じになりました。
見た目が本当に温度計そのもので良い感じですし、ゲージのぐりゅんと動くアニメーションも好きです(*´ω｀*)

`Update`ボタンを押すと、以下のように`v-on:click`によって紐付けられたVueインスタンスの`getData`メソッドが実行されます。

```html
<md-button class="md-raised md-primary" v-on:click="getData">Update</md-button>
```

`getData`関数ではhttpクライアントである`axios`を使って、以下のようにHTTPのGETメソッドでJSONを取得しています。

```js
axios.get(baseURI + '/sensors')
    .then((response) => {
        let sensors = response.data.sensor_list;
        this.name = 'Sensor: ' + sensors[0].name;
        this.gauge.value = sensors[0].temp_c;
        this.message = '';
    })
    .catch((error) => {
        this.message = error;
    });
```

取得できるJSONは以下のような内容です。

```json
{
  "sensor_list": [
    {
      "number": 1,
      "name": "Kitchen",
      "temp_c": 25.86
    }
  ]
}
```

最初はルートのオブジェクト名を`sensor-list`というチェーンケースにしていたために、`response.data.sensor-list`ではなく`response.data['sensor-list']`という記法で取り出さなくてはならず面倒でした。そこで`sensor-list`ではなく`sensor_list`に変更しました。JSONのオブジェクト名は、チェーンケースではなくスネークケースを使った方が何かと捗りそうです(´・ω・`)ｂ

# まとめ

* Vue.jsの門扉を叩いて入門し、IoTのAPIをレンダリングする簡単なWebサービスが作れました
    * Vue material本当に綺麗で好き(*´ω｀*)
* Canvas Gaugeで温度計っぽい表現も簡単に実現できました
    * アニメーション、個人的には好きですが好き嫌いがでそうですね・・・
* JSONのオブジェクト名はスネークケース（アンダーバー区切り）のほうが良さそう

こんな感じでやってみましたが、相変わらずフロントエンド技術はひよっこですのでコメント欄でアドバイスやご指導いただけますと幸いですm(_ _)m