Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
20
Help us understand the problem. What is going on with this article?
@TomoakiYAMAGUCHI

MQTT-SNを簡単にテストする方法 その1

More than 3 years have passed since last update.

MQTT-SNを試してみよう

最近耳にする事が増えてきたMQTTは軽量でM2Mにとって使いやすいプロトコルと言われています。しかし、MQTTはTCP/IPを使用するため常時接続が基本です。 クライアントデバイスの電池等の消耗を防ぐには通信の都度CONNECTする必要があり、通信のオーバーヘッドが必要です。 一方、MQTT-SNはUDPなどのセッションレスのプロトコルを使用できるため、通信しない時はCPUをスリープさせて電池の消耗を防ぐ事ができます。  

こんな特徴を持つMQTT-SNを簡単にテストできるライブラリーを紹介します。 MQTTのライブラリーとして有名なのはPahoですが、紹介するライブラリー は、Pahoと設計思想が違います。  Pahoはプリミティブで汎用的な機能を提供しますが、このライブラリーはMQTT-SNクライアントの特定用途向けのアプリケーション・フレームワーク汎用的なゲートウェイを提供しています。 そのためPUBLISHだけで、SUBSCRIBE、SERCHGW、GWINFO、CONNECTやREGISTERを知らなくても簡単にアプリが作成できちゃったりします。

Non Block I/Oで非同期通信を実現しています。 PUBLISHでデータ送信しながら、同時にSUBSCRIBEしたトピックがPUBLISHされると指定した処理が実行されます。 Arduino Ethernet (Flash 30KB, RAM 2kB) でも動きます。 

用意するもの

  • LinuxマシンとC++開発環境  
  • Arduino EthernetボードとArduino開発環境(1.6.3)
  • インターネットルーター DHCPサーバー付

作成するもの

  • UDPを使用したクライアントで、Linux用とArduino用の2種類。
  • MQTTプローカーとMQTT-SNクライアントを中継するゲートウェイ。

テスト内容

  • ArduinoクライアントはLEDランプのオンオフ用トピックxxxx/onoff/arduinoをSUBSCRIBEし、トピックがBool値と一緒にPUBLISHされるとLEDのオンオフを行います。また、xxxx/onoff/linuxトピックをFalse/Tureと一緒に交互にPUBLISHします。
  • LinuxクライアントはLEDランプのオンオフ用トピックxxxx/onoff/linuxをSUBSCRIBEし、トピックがBool値と一緒にPUBLISHされると標準出力にON/OFFと出力します。また、xxxx/onoff/arduinoトピックをFalse/Tureと一緒に交互にPUBLISHします。
  • ゲートウェイはこれらのトピックをMQTTとMQTT-SNの変換を行って、クライアントとブローカ間で中継します。     

  1. この連携により、ArduinoのLEDランプが点滅し、LinuxクライアントがON/OFFとプリントすることを確認します。 
  2. ゲートウェイは通信ログをリアルタイムで書き出しているので、このログを表示させます。 ログを見ればMQTT-SNプロトコルを理解しやすくなるでしょう。

Linux用クライアントの作成

step1. ホームディレクトリーで、Githubからライブラリーをダウンロードします。

$ git clone https://github.com/ty4tw/AsyncMQTT-SN.git

AsyncMQTT-SN/AsyncClient/srcにクライアントアプリのソースコードApplication.cppとAsyncMQTT-SN/AsyncClient/src/libに必要なライブラリーが入っています。

step2. Application.cppをコンパイルします。

$ cd AsyncMQTT-SN/AsyncClient
$ make
コンパイルエラーがなければテスト用クライアントをコピーします。
$ make install
実行モジュール TomyAsyncClient がホームディレクトリーにコピーされます。

step3. クライアントを実行します。

$ ./TomyAsyncClient
ゲートウェイが動いていないので何も起きませんが、wiresharkでモニターすればSEARCHGWメッセージがUDPで送信されていることが確認できます。 
また、MqttsnClientApp.h の68,69行の行頭の'//'を削除してから再コンパイルすると、UDPで送信しているメッセージが標準出力に表示されます。
Screenshot from 2015-05-20 06:41:50.png

ゲートウェイは MQTT-SNを簡単にテストする方法 その2 で作成します。

その前に、アプリケーションの構造について説明します。 アプリケーションの構造はLinuxもArduinoも共通です。

最初のブロックは宣言部分で、すべてのアプリケーションで共通です。

application.cpp

#ifdef ARDUINO
#include <SoftwareSerial.h>
#include <lib/MqttsnClientApp.h>
#include <lib/MqttsnClient.h>
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#else
#include "lib/MqttsnClientApp.h"
#include "lib/MqttsnClient.h"
#endif

#if defined(ARDUINO) && (defined(DEBUG_NW) || defined(DEBUG_MQTTSN) || defined(DEBUG))
SoftwareSerial debug(8, 9);
#endif

using namespace std;
using namespace tomyAsyncClient;
extern MqttsnClient* theClient;

次のブロックはアプリケーションが使用するUDPとXBeeに関わるパラメータとMQTT-SNの設定をしています。

Linux用のクライアントのIPアドレスはDHCPサーバーから取得しているアドレスが暗黙的に使用されますが、Arduinoの場合はMACアドレスとIPアドレスを明示的に設定する必要があります。 
また、SERCHGW、GWINFO、ADVERTISEメッセージをブロードキャストするため、マルチキャスト用のIPアドレスとポートNoも指定します。 このアドレスとポートNoは通信するクライアントとゲートウェイで同じ値を設定します。

MQTT-SNの設定では、KeepAliveでPINGREQの送信間隔を秒単位で指定します。
この他、Willメッセージを使用するならばWillTopic と WillMessage を文字列で指定します。 使用しない場合は 0 を指定します。""空文字列は使えません。

MqttsnClientApp.h の43行目 '#define NETWORK_UDP'をコメントアウトするとXBee用の設定が選択されます。

application.cpp

/*============================================
 *      MQTT-SN Client Application
 *===========================================*/
 #ifdef NETWORK_XBEE
 XBEE_APP_CONFIG = {
    {
        "client01",     //ClientId
        57600,          //Baudrate
        0,              //Serial PortNo (for Arduino App)
        "/dev/ttyUSB0"  //Device (for linux App)
    },
    {
        300,            //KeepAlive
        true,           //Clean session
        false,           //EndDevice
        "willTopic",    //WillTopic   or 0   DO NOT USE NULL STRING "" !
        "willMessage"   //WillMessage or 0   DO NOT USE NULL STRING "" !
    }
 };
#endif

#ifdef NETWORK_UDP
UDP_APP_CONFIG = {
    {
        "LinuxClient",      //ClientId
        {225,1,1,1},        // Multicast group IP
        1883,               // Multicast group Port
        {0,0,0,0},          // Local IP     (for Arduino App)
        12001,              // Local PortNo
        {0,0,0,0,0,0}       // MAC address  (for Arduino App)
    },
    {
        300,            //KeepAlive
        true,           //Clean session
        false,          //EndDevice
        "willTopic",    //WillTopic   or 0   DO NOT USE NULL STRING "" !
        "willMessage"   //WillMessage or 0   DO NOT USE NULL STRING "" !
    }
};
#endif

  
次のブロックではアプリケーションで使用するトピックを定義します。

XXXの部分は適当に変えてください。 テストで使用するブローカーは test.mosquitto.org です。 トピックは利用者全員の共用となるので、他の人が使用するトピックと重複しないようにしてください。

application.cpp
/*------------------------------------------------------
 *             Create Topic
 *------------------------------------------------------*/
const char* topic1 = "xxxx/onoff/arduino";
const char* topic2 = "xxxx/onoff/linux";

次のブロックはタイマーで定期的に起動されるタスクを関数として定義します。

関数はの様式は void func(void) で、複数のタスクを定義できます。 
タスクの中でPUBLISH(Topic*,Payload*,QoS)できます。
PayloadはPUBLISHメッセージで送信するバイナリーデータを表すクラスで、様式はMessagePackのサブセットを使用してデータを圧縮しています。 使い方は次のようになります。

application.cpp
  Payload pl = Payload(40);  // 40バイトのデータ領域を持つPayloadを生成
  pl.set_uint32( 200);       // uint32型のデータを書き込む、
  pl.set_uint32( 10);
  pl.set_uint32( 50000);
  pl.set_uint32( 70000);
  pl.set_int32( -300);       // int32型
  pl.set_int32(-70000);
  pl.set_float((float)1000.01);  //浮動小数点型
  pl.set_str("abcdef");          //文字列

 //PayloadをQoS1で送信
  PUBLISH(tp_request_01,&pl,1);  

 // PULISHで送られたPayloadはポインターで関数に渡されます。 
 // Payloadから取り出すデータをインデックスで指定します。
 // 詳しくはライブラリーのPayload.hを見てください。
  uint32_t val0 = pl->get_uint32(0);
  uint32_t val1 = pl->get_uint32(1);
  uint32_t val2 = pl->get_uint32(2);
  uint32_t val3 = pl->get_uint32(3);
  int32_t  val4 = pl->get_int32(4);
  int32_t  val5 = pl->get_int32(5);
  int32_t  val6 = pl->get_float(6);
  uint16_t len;
  const char* str = pl->get_str(7,&len);

```cpp:application.cpp
/*------------------------------------------------------
 *             Tasks invoked by Timer
 *------------------------------------------------------*/
static bool onoffFlg = true;

// 関数実行の都度、ON/OFFと交互になるようにブール値を設定してPUBLISH

void task1(void){
  printf("TASK1 invoked\n");
  Payload* pl = new Payload(10);
  onoffFlg = !onoffFlg;
  pl->set_bool(onoffFlg);
  PUBLISH(topic1,pl,1);
}

// 他にもTask関数を記述すれば、実行されることを確認

void task2(void){
  printf("TASK2 invoked\n");
}

上のブロックで定義したタスクを実行する時間間隔を秒単位で設定します。

task1 は 2秒間隔で、task2 は 20秒間隔で実行するように設定しています。
TASK_LIST, TASK, END_OF_TASK はマクロです。

application.cpp
/*---------------  List of task invoked by Timer ------------*/

TASK_LIST = {  //TASK( const char* topic, executing duration in second),
             TASK(task1,2),
             TASK(task2,20),
             END_OF_TASK_LIST
            };

SUBSCRIBEしたトピックがPUBLISHされたときに実行される関数を定義し、SUBSCRIBE_LISTでトピックと関数をつなぎあわせて、PUBLISHのQoSを指定します。

複数の関数が定義できます。 
INDICATOR_ON(bool)SUBSCRIBE_LISTSUBEND_OF_SUBSCRIBE_LIST もマクロです。
Payloadについている tomyAsyncClient:: の表記はArduinoでは必須となります。

application.cpp
/*------------------------------------------------------
 *       Tasks invoked by PUBLISH command Packet
 *------------------------------------------------------*/

int on_publish(tomyAsyncClient::Payload* payload){
    printf("ON_PUBLISH invoked.  ");
    INDICATOR_ON(payload->get_bool(0)); // Payloadからブール値を取り出すして使用
    return 0;
}

/*------------ Link Callback to Topic -------------*/

SUBSCRIBE_LIST = {  //SUB(topic, on_publish, QoS),
                  SUB(topic2, on_publish, 1),
                  END_OF_SUBSCRIBE_LIST
                 };

次のブロックでは割り込み処理関数と初期設定関数を定義しますが、この例では何も必要ありません。

application.cpp
/*------------------------------------------------------
 *            Tasks invoked by INT0 interruption
 *------------------------------------------------------*/
void interruptCallback(void){

}

/*------------------------------------------------------
 *            setup() function
 *------------------------------------------------------*/
 void setup(void){

 }

クライアント・アプリケーションはこれだけです。 あとは全てライブラリーが実行してくれます。 
TASK関数 void func(void) と On_Publish関数 int func(tomyAsyncGateway::Payload* payload) を記述し、
TASK_LIST と SUBSCRIBE_LIST を宣言するだけで完成です。

その2へ続く

20
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
TomoakiYAMAGUCHI
得意言語 C/C++ / eclipse paho コミッター

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
20
Help us understand the problem. What is going on with this article?