Xiaomi Mijia 温湿度計 をIoTデバイスとして使う

以前の投稿で、ローカルネットワークにMosquittoによるIoTブリッジサーバを立ち上げたので、これからいろんなデータをIoTに上げてみたいと思います。

[AWS IoTにMosquittoをブリッジとしてつなぐ](https://qiita.com/poruruba/items/084317e01b9fababc02e)

（可視化はいろいろややこしそうなので、おいおい勉強します）

今回のIoTデバイスは、Xaomi Mijia 温湿度計 です。
以下の方が詳しくご紹介されています。（参考にさせていただきました。ありがとうございます）

Xiaomi Mijia 温湿度計レビュー 表示スクリーン付き+スマホ ログ確認
　https://bey.jp/?p=63555

（値段を調べてみるとわかるのですが、とにかく安いです。２千円しないです。。。）
この製品は、BLE通信で、温度と湿度を取得することができます。もちろん液晶がついているので、見ても確認できるのですが、これを継続的に蓄積しよう、ということです。

これを室内に配置し、Raspberry PiからBLE接続して、MQTTでAWS IoTに温度と湿度をアップします。AWS IoT側では、受信を契機に、DynamoDBにタンキングします。
なぜ、DynamoDBにしたかというと、今後さまざまなデバイスをIoTで上げようと思っていまして、それぞれのデータの内容が異なるため、スキーマレスなデータベースを使いたかったためです。

# Xiaomi Mijia 温湿度計のセットアップ

電池入れて、好きな場所に置くだけです。理由は以降の章で分かります。

# Raspberry Piのセットアップ

Node.jsからBLE通信ができるように準備をします。
私の場合は、Raspberry Pi 3ではないので、使っていなかったBLE4.0対応のUSBドングルをRaspberry Piにぶっさしています。（たいていのBLE4.0対応のUSBドングルで、大丈夫だと思います）

BLE接続に使うnpmモジュールは、毎度の「noble」です。
以下のページに、OSごとのセットアップ方法があるので、インストールしておいてください。

https://github.com/noble/noble

# 温湿度を取得しPublishする手順

温湿度を取得する実装に入る前に、仕組みから。

普通BLEデバイスは、ScanしてペアリングしてCharacteristicをReadして。。。などするかと思いますが、この温湿度計は、単にアドバタイズデータに温湿度が含まれているだけなのです。だから、温湿度計のセットアップもなにもいらないのです。

しかも、有志の方が、npmモジュールを用意してくださっているので、何も苦労はいりません。

node-xiaomi-gap-parser
　https://github.com/LynxyssCZ/node-xiaomi-gap-parser

MQTTのPublishは、これもnpmモジュール「mqtt」を使います。
以下の流れです。

① nobleでアドバタイズデータを取得
② node-xiaomi-gap-parserでアドバタイズデータを解析して、温湿度を取得
③ mqttで、イントラネットにあるMosquittoサーバにPubish
④ Mosquittoサーバは、AWS IoTにPublishデータを転送
⑤ AWS IoTは、受け取ったPublishデータをDynamoDBに格納

※③④の部分は、すでにセットアップが完了している前提です。以下を参考にしてください。
　[AWS IoTにMosquittoをブリッジとしてつなぐ](https://qiita.com/poruruba/items/084317e01b9fababc02e)

# 温湿度を取得しPublishする実装

以下のnpmモジュールを使います。インストールしておきましょう。

> npm init -y
> npm install --save dotenv
> npm install --save mqtt
> npm install --save noble
> npm install --save xiaomi-gap-parser

以下、実装です。

```:index.js
var noble = require('noble');
var xiaomi = require('xiaomi-gap-parser');
var mqtt = require('mqtt');
require('dotenv').config();

const MQTT_HOST = process.env.MQTT_HOST || 【Mosquittoサーバのホスト名】;
const MQTT_TOPIC = process.env.MQTT_TOPIC || 【Publishするトピック名】;
const SCAN_DURATION = process.env.SCAN_DURATION ? parseInt(process.env.SCAN_DURATION) : 【BLEスキャン時間】;

console.log('MQTT_HOST=' + MQTT_HOST);
console.log('MQTT_TOPIC=' + MQTT_TOPIC);

var client = mqtt.connect(MQTT_HOST);

function wait_async(timeout){
	return new Promise((resolve, reject) =>{
		setTimeout(resolve, timeout);
	});
}

noble.on('stateChange', async function(state) {
    console.log('stateChange: ' + state);
    if (state === 'poweredOn') {
            console.log('Start Scanning');
            noble.startScanning(["fe95"]);
            await wait_async(SCAN_DURATION);
            console.log('Stop Scanning');
            noble.stopScanning();
            process.exit();
    } else {
        noble.stopScanning();
        process.exit();
    }
});

noble.on('discover', function(peripheral) {
    var serviceData = peripheral.advertisement.serviceData;
    if (serviceData && serviceData.length) {
        for (var i in serviceData) {
            if( serviceData[i].uuid == 'fe95' ){
                var data = serviceData[i].data;
                var mijia = xiaomi.readServiceData(data);
                console.log(mijia);
                console.log(mijia.event.data);

				var date = new Date();

				var message = {
					productId: mijia.productId,
					counter: mijia.counter,
					mac: mijia.mac,
					tmp: mijia.event.data.tmp,
					hum: mijia.event.data.hum,
					createdat: date.getTime(),
					createdatstr: date.toLocaleString()
				};
				console.log(message);
                client.publish(MQTT_TOPIC, JSON.stringify(message), 0, function(err, granted){
                    if( err ){
                        console.log(err);
                        return;
                    }
                    console.log('publish OK');
                });
            
                noble.stopScanning();
            }
        }
    }
});
```

以下を環境に合わせて変更してください。

【Mosquittoサーバのホスト名】
【Publishするトピック名】例えば、「awsiot/mijia」とします。
【BLEスキャン時間】：温湿度計のアドバタイズ間隔は長めなので、このスキャン時間も眺めがよいです。例えば、30000(30秒)とします。

Publishするデータのフォーマットは以下の部分です。

```
var message = {
    productId: mijia.productId,
    counter: mijia.counter,
    mac: mijia.mac,
    tmp: mijia.event.data.tmp,
    hum: mijia.event.data.hum,
    createdat: date.getTime(),
    createdatstr: date.toLocaleString()
};
```

# DynamoDBのテーブルを作成する。

Publishデータを格納するDynamoDBのテーブルをあらかじめ作成しておきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/b61766a9-d953-aea2-5f6c-d313956f88c3.png)

テーブル名は例えば、「MqttDataset」とします。
パーティションキーは「type」(文字列)とし、ソートキーとして「createdat」(文字列)としました。

# AWS IoT側でDynamoDBに登録されるようにする

AWS IoTで受け取ったPublishデータをDynamoDBに登録するために、AWS IoTのルールを定義します。
左側のナビゲータから、ACTを選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/82eff5c3-859d-e2f0-2134-22983edb83cc.png)
 
右上の作成を押下します。
適当な名前を付けます。例えば、「IoTAnalytics_mijia」

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/4a050cbf-40df-f3d0-7011-b333624cb929.png)

ルールクエリステートメントには、以下のように指定します。FROMの部分はトピック名です。

　SELECT * FROM 'awsiot/mijia'

「アクションの追加」ボタンを押下します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/f32f552b-16d7-bad4-c8a4-80a1e530b2b6.png)

DynamoDBテーブルにメッセージを挿入する を選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/22077082-bf19-f2ca-312e-f8964992b40e.png)

テーブル名には、さきほど作成したDynamoDBのテーブル名を指定します。
そうすると、ハッシュキーやレンジキーおよびそのタイプが自動的に入力されます。
ハッシュキー値には、今後追加されるであろう他のデータと区別するために、「mijia」としました。
レンジキーの値には、「${capturedat}」としました。
このcapturedatは、さきほどの実装の中で説明しましたが、Publishデータの中の１項目で、温湿度を取得した日時です。

それから、ロールにDynamoDBに書き出す権限を与えます。ページの下の方に、アクセス権を与えるためのボタンがあります。
新規ロール作成の場合には「新しいロールの作成」ボタンを押下すればよいですが、既存のロールを再利用する場合は、「ロールの更新」ボタンを忘れずに押下しておきましょう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/41ec7515-fcb5-e652-0a88-ac70c17b04ba.png)

# 実行

まずは、AWS IoT側で、Subscribe状態にしておきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/5bd9e548-05b9-d471-c26a-68734bbcda13.png)

nobleでBLEを使うので、Admin権限が必要です。
以下の感じで結果が出力されれば成功です。

```
> node index.js
MQTT_HOST=mqtt://【Mosquittoサーバのホスト名】
MQTT_TOPIC=awsiot/mijia
stateChange: poweredOn
Start Scanning
{ productId: 426,
  counter: 252,
  frameControl: [ 'MAC_INCLUDE', 'EVENT_INCLUDE' ],
  mac: 'XXXXXXXXXXXX',
  event:
   { eventID: 4109,
     length: 4,
     raw: 'ce00cd01',
     data: { tmp: 20.6, hum: 46.1 } } }
{ tmp: 20.6, hum: 46.1 }
{ productId: 426,
  counter: 252,
  mac: 'XXXXXXXXXXXX',
  tmp: 20.6,
  hum: 46.1,
  capturedat: '2018-12-27 18:26:20' }
publish OK
Stop Scanning
```

温度が20.6度、湿度が46.1% であることがわかります。
AWS IoT側にもPublishされました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/5e862072-db1e-7bc2-62af-87ffe35a00da.png)

DynamoDBにも登録されました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/f487d2b2-649f-9ff8-b113-b870470f4b0b.png)
 
※ときどき、温度と湿度のいずれかが欠ける時があるようです。

今回は一例として、Mijiaを使った温湿度を取得しました。
今後も取得するデータを増やしていこうと思います。


# (補足) 10分毎に温湿度を取得する

さて、node index.js とすることで、MQTT経由で温湿度がアップされるようになりました。
ただ、1実行1アップですので、定期的に実行させたいです。

そこで、以前に作った「PM2 Process Management」を使いたいと思います。

[PM2をもっともっと快適に！](https://qiita.com/poruruba/items/90c0fa66d0e94a8fc07f)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/1cb7f9d1-3847-4886-9acc-ad65d622cdf9.png)

「App追加」ボタンを押下して、アプリ定義を追加します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/261826/c3b20ba6-63e9-41c8-ae51-8c6ca9c6fb5e.png)

* name：適当な名前を付けます。例えば、「MqttMijia」とします。
* memo：適当に
* script：index.js
* cwd：index.jsを置いたフォルダを記載します。
* url：指定不要です。Webページがないため
* cron：10分間隔で起動させたいため「*/10 * * * *」としました。
* autorestart：false（cronを使うため）

そして、AppListのMqttMijiaの「開始」ボタンを押下すると、起動して、稼働監視対象になります。
statusがonlineになりますが、1分ぐらいたってから「ProcList更新」ボタンを押下すると、stoppedになります。10分間隔で、起動・停止を繰り返すためです。

以上です。