MQTTとC++でJSON形式を扱ってみる

筆者はRaspberry Piでオートロックシステムというものを開発しており、その制御のためにAPIを作成したかったのですが、MQTTとJSONを組み合わせれば楽なのではないかということを思いついたため、その方法を記します。

https://qiita.com/takuteh/items/67b4089ee78e66b868dc
## MQTTライブラリの導入
まず、使用するMQTTのライブラリですが`mosquitto`を使用します。導入方法は以下です。
mosquittoはブローカーを立ち上げるためのパッケージ、clientsはMQTTを送受信するためのパッケージです。
```bash
sudo apt install mosquitto mosquitto-clients
```

mosquittoはデフォルトで外部からのアクセスを許可していないので、/etc/mosqitto/mosquitto.confの最終行にanonymous trueと追記します。
```bash
sudo sh -c "echo 'allow_anonymous true' >> /etc/mosquitto/mosquitto.conf"
```
また、ポートはデフォルトで1883を使用しますが、そのほかのポートを使用したい場合は
`listener ポート番号`という感じで任意のポート番号を指定して追記してください。
## mosquittoで送受信の練習
mqttには送信者(Publisher)、仲介者(Broker)、受信者(Subscriber)の３者が必要になります。
mosquitto.serviceを起動すればブローカーが機能します。
### 送信
送信はmosquitto_pubコマンドを使用します。以下のコマンドは送信の例です。
`-t`オプションでトピック名、`-m`でメッセージ、`-h`でブローカーのipアドレスを指定します。
ちなみに`-h`を指定しなければ、デフォルトでlocalhostがブローカーとして指定されます。
```bash
mosquitto_pub -t '/test/topic' -m 'test message' -h 192.168.0.1
```
### 受信
受信側も送信の際と同様に、受信したいトピック名とブローカーのipアドレスを指定することによってメッセージを受け取ります。こちらも`-h`を指定しなければlocalhostから受け取ろうとします。
```bash
mosquitto_sub -t '/test/topic' -h 192.168.0.1
```
詳しくはこちらのページがわかりやすいと思います。

https://developer.mamezou-tech.com/iot/internet-of-things-06/

## mosquittoでjsonメッセージを扱ってみる
JSON形式とは、JavaScript Object Notationの略で、人間にも機械にも読みやすい軽量なデータ交換用フォーマットです。主にWebアプリケーションやAPI間のデータ交換で使われています。

今回オートロックの操作をするにあたりAPIはなんでもよかったのですが、JSONが扱いやすそうということでJSON形式を採用しました。
以下にJSONの基本フォーマットを示します。
```json
{
  "キー1": "文字列の値",
  "キー2": 123,             // 数値
  "キー3": true,            // 真偽値（true/false）
  "キー4": null,            // null値
  "キー5": [1, 2, 3],       // 配列
  "キー6": {
    "サブキー": "入れ子オブジェクト"
  }
}
```
### JSON形式のルール
- キーと文字列は必ずダブルクォーテーション`" "`で囲む
- 配列は [ ]、オブジェクトは { }
- true, false, null は小文字で書く（大文字不可）
- 最後の要素に カンマ , を付けない

以下の例を見ればどのように使うものかわかりやすいと思います。
```json
{
  "name": "Taro",
  "age": 25,
  "isStudent": false,
  "skills": ["Python", "JavaScript", "C++"],
  "address": {
    "city": "Tokyo",
    "zip": "100-0001"
  }
}
```
要素に複数の値を格納したい場合は`[]`を使用し、要素の中に小要素を入れたい場合は`{}`を使用するといった感じです。

### MQTTでJSONの送信
では、いよいよこちらをmosquittoで送信してみたいと思います。
すべて書くと長くなるので、一部省略しています。
```bash
mosquitto_pub -t '/test/topic' -m '{"name": "Taro","skills":["Python","C++"],"address":{"city":"Tokyo"}}'
```
subscriber側では以下のようなメッセージが受け取れます。
```
{"name": "Taro","skills":["Python","C++"],"address":{"city":"Tokyo"}}
```
## メッセージをC++で受信する
mqttで送信されたメッセージをC++で受け取ってみましょう。
新たに`libmosquitto`というライブラリの導入をします。
```bash
sudo apt install libmosquitto-dev
```
```mqtt_sub.cpp
#include <mosquitto.h>
#include <iostream>
#include <cstdlib>
#include <cstring>

void on_connect(struct mosquitto *mosq, void *obj, int rc) {
    if (rc == 0) {
        std::cout << "Connected to broker." << std::endl;
        //接続後に一度subscribeを呼び出す必要がある
        mosquitto_subscribe(mosq, NULL, "/test/topic", 0);
    } else {
        std::cerr << "Failed to connect, return code " << rc << std::endl;
    }
}

void on_message(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) {
    std::cout << "Received message: " << (char*)msg->payload << " on topic: " << msg->topic << std::endl;
}

int main(int argc, char *argv[]) {
    mosquitto_lib_init();

    struct mosquitto *mosq = mosquitto_new(NULL, true, NULL);
    if (!mosq) {
        std::cerr << "Failed to create mosquitto instance." << std::endl;
        return 1;
    }

    //コールバック関数の設定
    mosquitto_connect_callback_set(mosq, on_connect);
    mosquitto_message_callback_set(mosq, on_message);

    //mosquittoに接続、引数->(インスタンス,ブローカー,接続ポート,接続を確認する間隔)
    int rc = mosquitto_connect(mosq, "localhost", 1883, 60);
    if (rc != MOSQ_ERR_SUCCESS) {
        std::cerr << "Unable to connect: " << mosquitto_strerror(rc) << std::endl;
        return 1;
    }

    mosquitto_loop_start(mosq);  // 非同期ループ
    std::cout << "Press Enter to exit..." << std::endl;
    std::cin.get();

    //終了処理
    mosquitto_loop_stop(mosq, true);
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();

    return 0;
}
```
※on_connect関数内で、subscribeを呼び出していますが、接続後に<b>一度subscribeを呼び出さないと、メッセージを受け取れません。</b>複数のトピックを受け取りたい場合は、そのトピック分subscribeを呼び出す必要があります。

`libmosquitto`をリンクして、上記のコードをコンパイルします。
```bash
g++ -o mqtt_sub mqtt_sub.cpp -lmosquitto
```
コンパイルしたコードを実行し、例の通りメッセージを送信すると以下のようになると思います。
```
Press Enter to exit...
Connected to broker.
Received message: {"name": "Taro","skills":["Python","C++"],"address":{"city":"Tokyo"}} on topic: test/topic
```
これで、C++でのメッセージ受信は成功です。

## C++でJSON形式の処理
C++でメッセージを受信できたので、受信したJSONを扱っていきます。
JSONを扱うためのC++ライブラリとして[nlohmann/json](https://github.com/nlohmann/json)というライブラリが良さそうであるため、こちらを使用します。

導入は下記コマンドで行うか、gitから直接落としてビルドしても良いです。
```bash
sudo apt install nlohmann-json3-dev
```
先ほど示したコードにJSONの処理を追加しました。
ライブラリを読み込んで、受け取った文字列をnlohmann::jsonで扱えるJSON形式に変換すると、配列のように要素を指定して、値にアクセスできるようになります。
```diff_cpp
#include <mosquitto.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
+ #include <nlohmann/json.hpp>

void on_connect(struct mosquitto *mosq, void *obj, int rc) {
    if (rc == 0) {
        std::cout << "Connected to broker." << std::endl;
        mosquitto_subscribe(mosq, NULL, "/test/topic", 0);
    } else {
        std::cerr << "Failed to connect, return code " << rc << std::endl;
    }
}

void on_message(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) {
    std::cout << "Received message: " << (char*)msg->payload << " on topic: " << msg->topic << std::endl;
    
+    //文字列をnlohmann::jsonで扱えるように変換（パース）
+    auto jobj = nlohmann::json::parse((char*)msg->payload);

+    std::cout<<jobj["name"]<<std::endl;
+    std::cout<<jobj["skills"][0]<<std::endl;
+    std::cout<<jobj["address"]["city"]<<std::endl;
}

int main(int argc, char *argv[]) {
    mosquitto_lib_init();

    struct mosquitto *mosq = mosquitto_new(NULL, true, NULL);
    if (!mosq) {
        std::cerr << "Failed to create mosquitto instance." << std::endl;
        return 1;
    }

    mosquitto_connect_callback_set(mosq, on_connect);
    mosquitto_message_callback_set(mosq, on_message);

    int rc = mosquitto_connect(mosq, "localhost", 1883, 60);
    if (rc != MOSQ_ERR_SUCCESS) {
        std::cerr << "Unable to connect: " << mosquitto_strerror(rc) << std::endl;
        return 1;
    }

    mosquitto_loop_start(mosq);  // 非同期ループ
    std::cout << "Press Enter to exit..." << std::endl;
    std::cin.get();

    mosquitto_loop_stop(mosq, true);
    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();

    return 0;
}
```
コンパイルをします。gitから落としている場合インクルードディレクトリの指定が必要になると思うので`-I`で指定します。※aptで入れた場合は`-I`以下は不要
```bash
g++ -o mqtt_sub mqtt_sub.cpp -lmosquitto -Ijson/include
```
出力は以下のようになります。しっかり指定した要素の値が表示できていることがわかりました。
```
Press Enter to exit...
Connected to broker.
Received message: {"name": "Taro","skills":["Python","C++"],"address":{"city":"Tokyo"}} on topic: /test/topic
"Taro"
"Python"
"Tokyo"
```
これでMQTTでJSONメッセージを送信し、C++で処理することができました。

筆者のオートロックシステムではこれを利用し、データベースの情報と照合することで操作をできるようにしています。そして、これのいいところは、LINEやSlackでメッセージを受け取るプログラムを作成し、メッセージからJSONに整形し、それをMQTTで送信することによって、メインのプログラムは何も変更することなく、機能を拡張できるということです。