ESP8266でIDCF Cloudにつなぐ

# イントロダクション

既に多くの方が記事を書いているように、ESP8266はArduino IDEを使って開発することもできます。そこで、IDCF Cloudのチュートリアルである「[myThingsをはじめよう](http://www.idcf.jp/cloud/iot/detail01.html)」を元に、Raspberry PiではなくESP8266を使ってIDCF Cloudまで接続できるようにしてみました。

参考にした記事はMasato Shimizuさんの「[ArduinoからDHT11の温度と湿度データをMQTTを使ってMeshbluに送信する](http://masato.github.io/2015/04/20/arduino-dht11-mqtt-wr702n-meshblu/)」です。この記事が書かれたのは今年の4月20日なのですが、その後に[Arduino Client for MQTT](http://knolleary.github.io/pubsubclient/)は更新され、若干APIが変わりました。また、この記事はArduino Ethernet Shieldを対象に書かれていますが、Arduino Client for MQTTはESP8266でも動作するため、かなりコンパクトかつ安価に実現できます。

# プログラミング：ESP8266からBME280環境センサのデータ送信

これは、Arduino Client for MQTTに含まれるサンプルmqtt_publish_in_callbackとMasato Shimizuさんの記事中のサンプルを元にしたものです。BME280を使用するライブラリやサンプルには様々なものがありますが、ここではの[Embedded AdventuresのIan Harris氏によるライブラリ](https://github.com/embeddedadventures/BME280)を使用しています。

ファイルは2つに分かれています。Wi-FiアクセスポイントやIDCF CloudでのUUIDやトークンなど、各自で変更する必要のあるものはconfig.hという名前で別のタブ（実体はファイル）にして分離しています。

```cpp:config.h
// MQTTのクライアントID（ユニークにするためにスケッチをコンパイルした日付と時刻を使用）
const char *mqtt_client_id = "kotobuki" __DATE__ __TIME__;

// Wi-FiアクセスポイントのSSIDとパスワード
const char *ssid = "********";
const char *pass = "********";

// IDCF Cloudに関する設定
const char *server = "210-***-***-***.jp-east.compute.idcfcloud.com";
const char* trigger_1_uuid = "********-****-****-****-************";
const char* trigger_1_token = "********";
const char* action_1_uuid = "********-****-****-****-************";
const char* action_2_uuid = "********-****-****-****-************";
```
```cpp:IDCF_MQTT_publisher.ino
#include <Wire.h>

#include <ESP8266WiFi.h>

// MQTTを扱うためのライブラリ
// https://github.com/Imroy/pubsubclient
#include <PubSubClient.h>

// BME280を扱うためのライブラリ
// https://github.com/embeddedadventures/BME280
#include <BME280_MOD-1022.h>

#include "config.h"

WiFiClient wclient;

// MQTTクライアント
PubSubClient client(wclient, server);

// 温度と湿度、気圧を格納する変数
float temperature = 0.0;
float humidity = 0.0;
float pressure = 0.0;

// コールバック関数
void callback(const MQTT::Publish& pub) {
  // コールバックで必要な処理があればここで
}

// JSONフォーマットでペイロードを用意
String getPayloadInJson() {
  String json = "{";
  json +=  "\"devices\":";
  json += "[\"";
  json += action_1_uuid;
  json += "\"";
  json += ",";
  json += "\"";
  json += action_2_uuid;
  json += "\"";
  json += "],";
  json += "\"payload\":";
  json += "{";
  json += "\"pressure\":\"";
  json += pressure;
  json += "\",";
  json += "\"humidity\":\"";
  json += humidity;
  json += "\",";
  json += "\"temperature\":\"";
  json += temperature;
  json += "\"}";
  json += "}";

  return json;
}

// 浮動小数点数を小数点以下2桁でシリアルにプリント
void printFormattedFloat(float val) {
  char buffer[10];

  dtostrf(val, 4, 2, buffer);
  Serial.print(buffer);
}

void setup() {
  // IO4: SDA
  // IO14: SCL
  Wire.begin(4, 14);

  Serial.begin(115200);
  delay(10);
  Serial.println();
  Serial.println();

  // BME280の補償値を読み取る
  BME280.readCompensationParams();

  // オーバーサンプリングの回数を設定
  BME280.writeOversamplingTemperature(os1x);
  BME280.writeOversamplingHumidity(os1x);
  BME280.writeOversamplingPressure(os1x);
}

void loop() {
  // Wi-Fiアクセスポイントに接続していなければ接続
  if (WiFi.status() != WL_CONNECTED) {
    Serial.print("Connecting to ");
    Serial.print(ssid);
    Serial.println("...");
    WiFi.begin(ssid, pass);

    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
      Serial.println("Failed");
      return;
    } else {
      Serial.println("WiFi connected");
    }
  }

  // クライアントがICDF Cloudのサーバに接続されていなければ接続
  if (!client.connected()) {
    // トリガー1のUUIDとトークンで認証
    MQTT::Connect mqttConnect(mqtt_client_id);
    mqttConnect.set_auth(trigger_1_uuid, trigger_1_token);

    if (client.connect(mqttConnect)) {
      client.set_callback(callback);
      Serial.println("Client connected");
    } else {
      Serial.println("Client failed to connect");
    }
  }

  // クライアントがICDF Cloudのサーバに接続されていたら以下の処理を実行
  if (client.connected()) {
    // BME280を1度だけ測定を行うモードに設定し計測が終わるまで待機
    BME280.writeMode(smForced);
    while (BME280.isMeasuring()) {
      delay(1);
    }

    // BME280から測定値を読み取る
    BME280.readMeasurements();

    // 温度と湿度、気圧を取得
    temperature = BME280.getTemperature();
    humidity = BME280.getHumidity();
    pressure = BME280.getPressure();

    // 温度と湿度、気圧をシリアルにプリント
    Serial.print("Temperature: ");
    printFormattedFloat(temperature);
    Serial.println();
    Serial.print("Humidity   : ");
    printFormattedFloat(humidity);
    Serial.println();
    Serial.print("Pressure   : ");
    printFormattedFloat(pressure);
    Serial.println();

    // IDCF CloudにMQTTでデータを送信
    bool sent = client.publish("message", getPayloadInJson());
    if (sent) {
      Serial.println("Sent");
    } else {
      Serial.println("Failed to send");
    }

    Serial.println();
  }

  delay(10000);
}
```

# myThingsアプリと接続：myThingsから「IDCF」チャンネルを使う

これは、Arduino Client for MQTTに含まれるサンプルmqtt_subscriberを元にしたものです。オリジナルの記事では、天気・災害のトリガーをfreeboard上で確認するようになっています。このサンプルは受け取ったペイロードをそのままシリアルにプリントしているだけですが、内容に応じてLEDの色を変えて表示する、ディスプレイに文字で表示する、サーボの動きで表現するなど、様々な展開ができると思います。

前のサンプルと同様にファイルは2つに分かれています。Wi-FiアクセスポイントやIDCF CloudでのUUIDやトークンなど、各自で変更する必要のあるものはconfig.hという名前で別のタブ（実体はファイル）にして分離しています。

```cpp:config.h
// MQTTのクライアントID（ユニークにするためにスケッチをコンパイルした日付と時刻を使用）
const char *mqtt_client_id = "kotobuki" __DATE__ __TIME__;

// Wi-FiアクセスポイントのSSIDとパスワード
const char *ssid =  "********";
const char *pass =  "********";

// IDCF Cloudに関する設定
const char* action_1_uuid = "********-****-****-****-************";
const char* action_1_token = "********";
IPAddress server(210, ***, ***, ***);
```

```cpp:IDCF_MQTT_subscriber.ino
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

#include "config.h"

#define BUFFER_SIZE 100

// メッセージを受け取ったらシリアルにプリント
void callback(const MQTT::Publish& pub) {
  Serial.print(pub.topic());
  Serial.print(" => ");
  if (pub.has_stream()) {
    // ペイロードのサイズが大きい場合にはローカルに用意したバッファに分割して読み取り
    // 読み取った単位ごとにシリアルにプリント
    uint8_t buf[BUFFER_SIZE];
    int read;
    while (read = pub.payload_stream()->read(buf, BUFFER_SIZE)) {
      Serial.write(buf, read);
    }
    pub.payload_stream()->stop();
    Serial.println();
  } else {
    // ペイロードのサイズが小さい場合にはそのままシリアルにプリント
    Serial.println(pub.payload_string());
  }
}

WiFiClient wclient;
PubSubClient client(wclient, server);

void setup() {
  Serial.begin(115200);
  delay(10);
  Serial.println();
  Serial.println();
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.print("Connecting to ");
    Serial.print(ssid);
    Serial.println("...");
    WiFi.begin(ssid, pass);

    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
      return;
    }
    Serial.println("WiFi connected");
  }

  if (!client.connected()) {
    // アクション1のUUIDとトークンをユーザ名およびパスワードとしてサーバに接続
    MQTT::Connect mqttConnect(mqtt_client_id);
    mqttConnect.set_auth(action_1_uuid, action_1_token);

    if (client.connect(mqttConnect)) {
      client.set_callback(callback);
      client.subscribe(action_1_uuid);
    }
  }

  if (client.connected()) {
    client.loop();
  }
}
```

# 参照
 * [myThingsをはじめよう](http://www.idcf.jp/cloud/iot/detail01.html)
 * [ArduinoからDHT11の温度と湿度データをMQTTを使ってMeshbluに送信する](http://masato.github.io/2015/04/20/arduino-dht11-mqtt-wr702n-meshblu/)
 * [Arduino Client for MQTT](http://pubsubclient.knolleary.net/)
 * [BME280](https://github.com/embeddedadventures/BME280)