Arduinoのネットワーク通信

MQTT通信


Socket通信はサーバー/クライアント間の1対1の通信ですが、MQTTはPublish(送り側)/Subscribe(受け側)ともに Socketクライアントです。
MQTTでは1対多の通信を行うことができるようになります。
MQTTのパケットフォーマットは公開されているのでライブラリを使わずに実現することもできますが、便利なライブラリが公開されています。
以下のページよりArduinoのMQTTクライアントライブラリ(pubsubclient-master.zip)をダウンロードしてライブ ラリに追加します。
https://github.com/knolleary/pubsubclient

但し、メモリの小さいUNOではぎりぎりのサイズで、最低限の動作確認しかできません。
色々処理を加えると、メモリに余裕のあるATMEGA2560が必要になります。

UNO+ENC28J60+UIPEthernetライブラリ+MQTTクライアントライブラリ
最大32256バイトのフラッシュメモリのうち、スケッチが24796バイト(76%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が1736バイト(84%)を使っていて、ローカル変数で312バイト使うことができます。
スケッチが使用できるメモリが少なくなっています。動作が不安定になる可能性があります。

UNO+W5100+Ethernetライブラリ+MQTTクライアントライブラリ
最大32256バイトのフラッシュメモリのうち、スケッチが16554バイト(51%)を使っています。
最大2048バイトのRAMのうち、グローバル変数が1061バイト(51%)を使っていて、ローカル変数で987バイト使うことができます。



Publish側のスケッチは以下の様になります。
10秒ごとにTOPICをBrokerに送ります。
以下はENC28J60を使う場合ですが、W5100を使う場合もライブラリだけを変更すれば動きます。

Brokerには以下の公開Brokerを使うこともできます。
//#define MQTT_SERVER     "broker.hivemq.com"
//#define MQTT_SERVER     "iot.eclipse.org"

MQTT通信を行う時の注意点として、Brokerに接続する際のClientIDにはユニークなIDを指定する必要があります。
同じClientIDのクライアントを2つ起動すると、最初のクライアントはBrokerから強制的に切断されてしまいます。
そこで、以下のスケッチではDHCPから払い出されたIPアドレスをClientIDとして使っています。
/*
 * W5100/ENC28J60 Ethernet Module MQTT Publish.
 */

#include <UIPEthernet.h> // https://github.com/UIPEthernet/UIPEthernet
//#include <SPI.h>
//#include <Ethernet.h>
#include <PubSubClient.h> // https://github.com/knolleary/pubsubclient

#define INTERVAL        10 // 10秒毎にPublish
#define MQTT_SERVER     "192.168.10.40"  // You need change
//#define MQTT_SERVER     "broker.hivemq.com"
//#define MQTT_SERVER     "iot.eclipse.org"
#define MQTT_PORT       1883
#define MQTT_TOPIC      "Arduino/UNO"
#define MQTT_WILL_MSG   "I am leaving..." // You can change
#define RUNNING_LED     13 // 0: Disable RUNNING_LED

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

EthernetClient ethClient;
PubSubClient pubsubClient(ethClient);

unsigned long nextMillis;

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

void errorDisplay(char* buff) {
  int stat = 0;
  Serial.print("Error:");
  Serial.println(buff);
  while(1) {
    digitalWrite(RUNNING_LED,stat);
    stat = !stat;
    delay(100);
  }
}

void setup() {
  Serial.begin(9600);
  Serial.print("Ethernet begin....");
  if (Ethernet.begin(mac) == 0) {
    Serial.println("faild");
    while(1) {}
  }
  Serial.println("ok");
  Serial.print("localIP: ");
  Serial.println(Ethernet.localIP());
  Serial.print("subnetMask: ");
  Serial.println(Ethernet.subnetMask());
  Serial.print("gatewayIP: ");
  Serial.println(Ethernet.gatewayIP());
  Serial.print("dnsServerIP: ");
  Serial.println(Ethernet.dnsServerIP());
 
  pubsubClient.setServer(MQTT_SERVER, MQTT_PORT);
  pubsubClient.setCallback(callback);

  char clientid[30];
  IPAddress ip = Ethernet.localIP();
  sprintf(clientid,"%03d-%03d-%03d-%03d",ip[0], ip[1], ip[2], ip[3]);
  Serial.print("clientid=");
  Serial.println(clientid);
  // Loop until we're reconnected
  while (!pubsubClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (pubsubClient.connect(clientid,MQTT_TOPIC,0,0,MQTT_WILL_MSG)) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(pubsubClient.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
  nextMillis = millis();
}

void loop() {
  static int value = 0;
  char msg[50];
  unsigned long now;

  pubsubClient.loop();
  now = millis();
  if ( long(now - nextMillis) > 0) {
    nextMillis = millis() + (INTERVAL * 1000);
    ++value;
    snprintf (msg, 75, "MQTT from Arduino/UNO %06d",value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    if (!pubsubClient.publish(MQTT_TOPIC, msg) ) {
       errorDisplay("Failed to Publsh");
    }
  }
}



Subscribe側のスケッチは以下の様になります。
[Arduino/#]のTOPICをSubscribeしています。
以下はW5100を使う場合ですが、ENC28J60を使う場合もライブラリだけを変更すれば動きます。
/*
 * W5100/ENC28J60 Ethernet Module MQTT Publish.
 */

//#include <UIPEthernet.h> // https://github.com/UIPEthernet/UIPEthernet
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h> // https://github.com/knolleary/pubsubclient

#define MQTT_SERVER     "192.168.10.40"
#define MQTT_PORT       1883
#define MQTT_TOPIC      "Arduino/#"
#define MQTT_WILL_MSG   "I am leaving..." // You can change
#define RUNNING_LED     13 // 0: Disable RUNNING_LED

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

EthernetClient ethClient;
PubSubClient pubsubClient(ethClient);

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] [");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println("]");
}

void errorDisplay(char* buff) {
  int stat = 0;
  Serial.print("Error:");
  Serial.println(buff);
  while(1) {
    digitalWrite(RUNNING_LED,stat);
    stat = !stat;
    delay(100);
  }
}

void setup() {
  Serial.begin(9600);
  Serial.print("Ethernet begin....");
  if (Ethernet.begin(mac) == 0) {
    Serial.println("faild");
    while(1) {}
  }
  Serial.println("ok");
  Serial.print("localIP: ");
  Serial.println(Ethernet.localIP());
  Serial.print("subnetMask: ");
  Serial.println(Ethernet.subnetMask());
  Serial.print("gatewayIP: ");
  Serial.println(Ethernet.gatewayIP());
  Serial.print("dnsServerIP: ");
  Serial.println(Ethernet.dnsServerIP());
 
  pubsubClient.setServer(MQTT_SERVER, MQTT_PORT);
  pubsubClient.setCallback(callback);

  char clientid[30];
  IPAddress ip = Ethernet.localIP();
  sprintf(clientid,"%03d-%03d-%03d-%03d",ip[0], ip[1], ip[2], ip[3]);
  Serial.print("clientid=");
  Serial.println(clientid);
  // Loop until we're reconnected
  while (!pubsubClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (pubsubClient.connect(clientid,MQTT_TOPIC,0,0,MQTT_WILL_MSG)) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(pubsubClient.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }

  if (!pubsubClient.subscribe(MQTT_TOPIC)) {
    errorDisplay("subscribe Fail");
  }

}

void loop() {
  pubsubClient.loop();
}



Publish側のIDEにはこのように表示されます。


Subscribe側のIDEにはこのように表示されます。


次回は、NTPクライアントによる時刻合わせを紹介します。

続く...