Firebase Cloud FunctionsでMQTT Publisherを作ってみた

# Firebase Cloud FunctionsでMQTT Publisherを作ってみた

VR空間とIoTデバイスをMQTTで接続する仕組みを構築するにあたり、Firebase Cloud FunctionsでMQTT Publisherを作ることができないか試してみたところ、うまくいったので忘れない内に記事にします。

ちなみに、以下が作ったものです！普段はVR関係の活動をしていますので、ご興味があればフォローよろしくお願いします！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">VR空間内でお絵描きすると、自宅の8x8LEDに反映される仕組みを作ってみました！<br>個人サーバーを介して、VR(STYLY)内の仮想LEDの状態を、自宅内の物理LEDへ反映させてます。<br>PlaymakerのWWW Objectと、MQTTを組み合わせて実現してます。<br>右下はMQTTのメッセージを可視化させたものです。<a href="https://twitter.com/hashtag/STYLY?src=hash&amp;ref_src=twsrc%5Etfw">#STYLY</a> <a href="https://twitter.com/hashtag/IoT?src=hash&amp;ref_src=twsrc%5Etfw">#IoT</a> <a href="https://t.co/YEL02dHsJN">pic.twitter.com/YEL02dHsJN</a></p>&mdash; segur(せぎゅ) (@segur_vita) <a href="https://twitter.com/segur_vita/status/1301404377387683846?ref_src=twsrc%5Etfw">September 3, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



# やりたいこと

以下が、やりたいことです。

- FirebaseにWebAPIを構築する

- WebAPIにGETメソッドでアクセスすると、クエリパラメーター `message` の内容が、MQTT Brokerへ送信される。



# 事前準備

### Firebase開発環境の構築

事前にFirebaseプロジェクトの作成やツールの導入をお願いします。

こちらがわかりやすいのでオススメです。

[Cloud Functions + ExpressでサーバレスAPIを実装](https://taroosg.io/cloud-functions-express)



### 従量課金制のプランにする

完全無料のSparkプランだと外部ドメインのサーバーにアクセスすることができないので、MQTT Brokerへ接続することができません。

あらかじめ、従量課金制のBlazeプランに変更しておく必要があります。（こればっかりはどうしようもないです。。。）



### mqttのインストール

以下のコマンドでexpressとmqttをインストールしておいてください。

```bash
npm install express
npm install mqtt
```



# サンプルコード

index.jsに以下のように書きます。定数の箇所は適宜、ご自身の環境に合わせて変更してください。

```javascript
const functions = require('firebase-functions');
const express = require("express");
const app = express();
const mqtt = require('mqtt');

// 定数
const broker = 'mqtt://example.com';
const topic = 'example';

// MQTTでメッセージを送信する
const publishMessage = (message) => {
    // Brokerに接続する
    const client = mqtt.connect(mqttBroker);
    client.on('connect', () => {
        // メッセージをBrokerへ送信する
        client.publish(topic, message, null, (err) => {
            if (err) {
                console.error(err);
            }

            // Brokerとの接続を切断する
            client.end();
        });
    });
}

// GETコントローラー
app.get('/publish', (req, res) => {
    const message = req.query.message;    
    if(message){
        // メッセージをPublishする
        publishMessage(message);
        
        return res.send('Success!');
    }else{
        return res.send('Error!');
    }
});

const api = functions.https.onRequest(app);
module.exports = { api };
```

これをデプロイすれば動くようになります！



# 解説

こちらの関数を解説します。

```javascript
// MQTTでメッセージを送信する
const publishMessage = (message) => {
    // Brokerに接続する
    const client = mqtt.connect(mqttBroker);
    client.on('connect', () => {
        // メッセージをBrokerへ送信する
        client.publish(topic, message, null, (err) => {
            if (err) {
                console.error(err);
            }

            // Brokerとの接続を切断する
            client.end();
        });
    });
}
```

こちらの関数では、以下の3つの処理を一気に実施しています。

- Brokerと接続
- Brokerへメッセージの送信
- Brokerとの接続を切断

つまり、関数が呼ばれる度に、Brokerとの接続・切断を繰り返すことになり、非常に効率が悪いです。

しかし、Firebase Cloud Functionsでは、Brokerとの接続を維持することが難しかったため、このようなスタイルになりました。

たとえば、以下のコードのように、関数外でBrokerとの接続処理を書いてみても、うまく動きませんでした。
（厳密には `app.get` の外です）

```javascript
// Brokerに接続する
const client = mqtt.connect(mqttBroker);
client.on('connect', () => {});

// MQTTでメッセージを送信する
const publishMessage = (message) => {
    // メッセージをBrokerへ送信する
    client.publish(topic, message, null, (err) => {
        if (err) {
            console.error(err);
        }
    });
}
```



# さいごに

本記事作成にあたり、以下の記事を参考にさせていただきました。ありがとうございました。

- [Cloud Functions + ExpressでサーバレスAPIを実装](https://taroosg.io/cloud-functions-express)
- [Node.js＋MQTT.js＋Mosca を使ってみた](https://qiita.com/tinymouse/items/7cccdc68597ade264ee1)