筆者はRaspberry Piでオートロックシステムというものを開発しており、その制御のためにAPIを作成したかったのですが、MQTTとJSONを組み合わせれば楽なのではないかということを思いついたため、その方法を記します。
MQTTライブラリの導入
まず、使用するMQTTのライブラリですがmosquitto
を使用します。導入方法は以下です。
mosquittoはブローカーを立ち上げるためのパッケージ、clientsはMQTTを送受信するためのパッケージです。
sudo apt install mosquitto mosquitto-clients
mosquittoはデフォルトで外部からのアクセスを許可していないので、/etc/mosqitto/mosquitto.confの最終行にanonymous trueと追記します。
sudo sh -c "echo 'allow_anonymous true' >> /etc/mosquitto/mosquitto.conf"
また、ポートはデフォルトで1883を使用しますが、そのほかのポートを使用したい場合は
listener ポート番号
という感じで任意のポート番号を指定して追記してください。
mosquittoで送受信の練習
mqttには送信者(Publisher)、仲介者(Broker)、受信者(Subscriber)の3者が必要になります。
mosquitto.serviceを起動すればブローカーが機能します。
送信
送信はmosquitto_pubコマンドを使用します。以下のコマンドは送信の例です。
-t
オプションでトピック名、-m
でメッセージ、-h
でブローカーのipアドレスを指定します。
ちなみに-h
を指定しなければ、デフォルトでlocalhostがブローカーとして指定されます。
mosquitto_pub -t '/test/topic' -m 'test message' -h 192.168.0.1
受信
受信側も送信の際と同様に、受信したいトピック名とブローカーのipアドレスを指定することによってメッセージを受け取ります。こちらも-h
を指定しなければlocalhostから受け取ろうとします。
mosquitto_sub -t '/test/topic' -h 192.168.0.1
詳しくはこちらのページがわかりやすいと思います。
mosquittoでjsonメッセージを扱ってみる
JSON形式とは、JavaScript Object Notationの略で、人間にも機械にも読みやすい軽量なデータ交換用フォーマットです。主にWebアプリケーションやAPI間のデータ交換で使われています。
今回オートロックの操作をするにあたりAPIはなんでもよかったのですが、JSONが扱いやすそうということでJSON形式を採用しました。
以下にJSONの基本フォーマットを示します。
{
"キー1": "文字列の値",
"キー2": 123, // 数値
"キー3": true, // 真偽値(true/false)
"キー4": null, // null値
"キー5": [1, 2, 3], // 配列
"キー6": {
"サブキー": "入れ子オブジェクト"
}
}
JSON形式のルール
- キーと文字列は必ずダブルクォーテーション
" "
で囲む - 配列は [ ]、オブジェクトは { }
- true, false, null は小文字で書く(大文字不可)
- 最後の要素に カンマ , を付けない
以下の例を見ればどのように使うものかわかりやすいと思います。
{
"name": "Taro",
"age": 25,
"isStudent": false,
"skills": ["Python", "JavaScript", "C++"],
"address": {
"city": "Tokyo",
"zip": "100-0001"
}
}
要素に複数の値を格納したい場合は[]
を使用し、要素の中に小要素を入れたい場合は{}
を使用するといった感じです。
MQTTでJSONの送信
では、いよいよこちらをmosquittoで送信してみたいと思います。
すべて書くと長くなるので、一部省略しています。
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
というライブラリの導入をします。
sudo apt install libmosquitto-dev
#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を呼び出していますが、接続後に一度subscribeを呼び出さないと、メッセージを受け取れません。複数のトピックを受け取りたい場合は、そのトピック分subscribeを呼び出す必要があります。
libmosquitto
をリンクして、上記のコードをコンパイルします。
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というライブラリが良さそうであるため、こちらを使用します。
導入は下記コマンドで行うか、gitから直接落としてビルドしても良いです。
sudo apt install nlohmann-json3-dev
先ほど示したコードにJSONの処理を追加しました。
ライブラリを読み込んで、受け取った文字列をnlohmann::jsonで扱えるJSON形式に変換すると、配列のように要素を指定して、値にアクセスできるようになります。
#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
以下は不要
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で送信することによって、メインのプログラムは何も変更することなく、機能を拡張できるということです。