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


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

AWS IoTにMosquittoをブリッジとしてつなぐ

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

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

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

(値段を調べてみるとわかるのですが、とにかく安いです。2千円しないです。。。)
この製品は、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ごとのセットアップ方法があるので、インストールしておいてください。

温湿度を取得し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をブリッジとしてつなぐ

温湿度を取得し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のテーブルをあらかじめ作成しておきます。

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

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

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

右上の作成を押下します。
適当な名前を付けます。例えば、「IoTAnalytics_mijia」

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

 SELECT * FROM 'awsiot/mijia'

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

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

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

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

実行

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

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されました。

DynamoDBにも登録されました。

※ときどき、温度と湿度のいずれかが欠ける時があるようです。

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

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

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

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

PM2をもっともっと快適に!

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

  • 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分間隔で、起動・停止を繰り返すためです。

以上です。