IFTTTのトリガーおよびアクションをESP8266で実行する

# イントロダクション

IFTTTのMaker Channelは、トリガーまたはアクションに自家製のデバイスを簡単に接続できるチャンネルです。ここでは、Arduino core for ESP8266 WiFi chipを使い、ESP8266でMaker Channelのトリガーおよびアクションを実装する方法を紹介します。

# 準備

## ESP8266

Arduino core for ESP8266 WiFi chipをArduino IDEで使用する方法については「[ESP-WROOM-02開発ボードでmyThingsのIDCFチャンネルを試す](http://qiita.com/mayfair/items/9aab50397a7bde84a7c1)」を参照してください。なお、この記事を書いた時点での最新版は2.0.0でしたが、その後更新されて[2.1.0](http://esp8266.github.io/Arduino/versions/2.1.0/)となり、さらに機能強化とバグ修正が進んでいます。

## IFTTT

IFTTT側での準備としては、[Maker Channelのウェブページ](https://ifttt.com/maker)にアクセスして、このチャンネルを有効に設定します。設定が終わると「Your key is:」の後にキーが表示されますので、この文字列をコピーしてどこかに保存しておきましょう。

# 実装

## トリガーの場合

### IFTTT側

IFTTT側では、トリガーをMaker Channel、アクションを通知にしたレシピを作成します。この時、トリガーのイベント名として指定したイベント名（この例では「ping」）をESP8266側でも使用します。

<img width="375" alt="IMG_0579.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/92577/d9ca706d-6757-0d99-80a5-1d9434ebc588.png">


### ESP8266側

IFTTTのMaker Channelに対してトリガーをかけるには、次のURLにアクセスします。

```
https://maker.ifttt.com/trigger/{event}/with/key/{key}
```

ボディがある場合には以下のようにJSON形式でデータを渡します（今回はボディは用いずイベント名のみで判断します）。

```json
{ "value1" : "***", "value2" : "***", "value3" : "***" }
```

以下に示すのは、約1分ごとにイベント名「ping」でトリガーをかけるサンプルです。config.hで「*」で示している部分は実際にアクセスするWi-Fiアクセスポイント、およびIFTTTで生成されたキーに置き換えてください。ESP8266からHTTPS（HTTP over TLS）で接続していますが、証明書の確認は行っていません。

```cpp:config.h
// Wi-FiアクセスポイントのSSIDとパスワード
const char* ssid = "********";
const char* password = "********";

// IFTTTのシークレットキー
const char* key = "**********************";

```

```cpp:IFTTT_Trigger
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>

// 設定ファイル
#include "config.h"

const char* host = "maker.ifttt.com";
const char* event   = "ping";
const int httpsPort = 443;

WiFiClientSecure client;

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.print("connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println();
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  Serial.print("connecting to ");
  Serial.println(host);

  if (!client.connect(host, httpsPort)) {
    Serial.println("connection failed");
    return;
  }

  // URLを作成
  // maker.ifttt.com/trigger/{event}/with/key/{key}
  String url = "/trigger/";
  url += event;
  url += "/with/key/";
  url += key;

  Serial.print("requesting URL: ");
  Serial.println(url);

  // ウェブリクエストを送信
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Connection: close\r\n\r\n");

  while (client.connected()) {
    String line = client.readStringUntil('\n');
    if (line == "\r") {
      // ヘッダ部分の受取終了
      break;
    }
  }

  String line = client.readStringUntil('\n');
  Serial.print("reply: ");
  Serial.println(line);
  Serial.println("closing connection");
  Serial.println();

  // 1分後に再度実行
  delay(60000);
}
```

## アクションの場合

IFTTTのMaker Channelでは、アクションは外部のウェブサーバに対してウェブリクエストを送信するようになっています。これに対応するため、ESP8266をウェブサーバにしてグローバルにアクセスできるようにする、という方法も考えられますが、自由にインターネットからアクセスできるサーバを設置できない環境の方が多いでしょう。そこで、ここではより現実的な方法としてESP8266からMQTTで接続させる方法を紹介します。

MQTTにはメッセージブローカが必要ですが、今回の場合にはRESTでアクセスできるものが必要になります。myThingsのIDCFチャンネルで使用しているMeshbluもRESTとMQTTに対応したメッセージブローカですが、MeshbluをRESTで使用する際にはカスタムヘッダで認証情報を与える必要があります。これに対して、IFTTT側で指定できるヘッダが限定されているため、残念ながらIFTTTでウェブリクエストを送信する先としてはMeshbluは使用することができません。

そこで、今回はBeebotteを使用します。BeebotteはIoTに特化したPaaS（Platform as a Service）で、[様々な料金プラン](https://beebotte.com/plans)が用意され、1日あたりのメッセージ数が50,000個までで保存するデータが5,000個まで、データの保存期間3ヶ月間という条件であれば無料でも運用できます。また、PythonやPHP、JavaScriptなどの[ライブラリ](https://beebotte.com/libs)も用意されており、RESTとMQTTの両方から利用できます。

他の手段としては、同様の機能を持つ[Ponte](http://www.eclipse.org/ponte/)などを自分でサーバにインストールして運用する方法があります。既存のサービスを利用するのと自分でサーバを用意するのとではそれぞれメリット／デメリットがありますので、用途に応じて選択すると良いでしょう。

### Beebotte側の設定

Beebotteへの登録が終わったら、コントロールパネルで新規のチャンネルおよびリソースを作成します。ここではチャンネルを「ifttt」、リソースを「action」としています[^1]。いったん作成した後、コントロールパネルで表示されているチャンネル一覧の中から作成したチャンネル名をクリックすると、「Channel Token」の横にそのチャンネル用のトークンが表示されます。

[^1]: ここで、オプションとしてSend on Subscribe（SoS）があります。このオプションを有効にしておくと、クライアントが接続した時に、最近書き込まれた値（the most recent persistent value）が配信されます。スリープ機能を用いて接続時に最新の状態に更新したい場合など、このオプションを有効に設定すると良いでしょう。

![スクリーンショット 2016-02-23 12.23.31.png](https://qiita-image-store.s3.amazonaws.com/0/92577/ce44168c-b01e-3fc5-52d1-1f5d4999e6a6.png)

![スクリーンショット 2016-02-23 12.24.07 のコピー.png](https://qiita-image-store.s3.amazonaws.com/0/92577/cce0f179-6d9f-6676-6d88-be334e783e96.png)

動作確認はcurlコマンドを用いて行います（***で示している文字列は上記で確認したチャンネル用のトークンに置き換えてください）。

```console
$ curl --verbose --header "Content-type: application/json" --request POST --data '{"data": "hello ESP8266"}' https://api.beebotte.com/v1/data/write/ifttt/action?token=******************************
```



### IFTTT側の設定

IFTTTでは新規にレシピを作成します。トリガーはGmailやTwitterなど、自分で任意のタイミングでイベントを発生させられるものでもいいですし、Every hour atのように毎時決めた時間にトリガーをかけるものでもいいでしょう（ただしこの場合には条件に一致しない限りトリガーがかからないので開発中には不便かもしれません）。

アクションにはMaker Channelの「Make a web request」を指定します。最初のパラメータであるURLにはcurlコマンドで動作を確認したURLをコピー&ペーストします。次のMethodにはPOSTを指定し、Content Typeにはapplication/jsonを指定します。最後のBodyにはJSON形式でメッセージを指定します。

```json
{
	"data": "hello ESP8266"
}
```

![FireShot Capture 41 - Recipe 34403279 - IFTTT - https___ifttt.com_myrecipes_personal_34403279.png](https://qiita-image-store.s3.amazonaws.com/0/92577/812edeac-0df5-578f-5a15-09a9ac04acfe.png)


### ESP8266側の設定

ESP8266からはMQTTで接続します。接続の際に必要となる情報は以下の通りです。

* MQTTサーバ：mqtt.beebotte.com
* ポート番号：1883（SSLなしの場合）または8883（SSLありの場合）
* クライアントID：任意
* ユーザ名："token:"＋チャンネルトークン
* パスワード：なし

```cpp:config.h
// Wi-FiアクセスポイントのSSIDとパスワード
const char *ssid = "********";
const char *password = "********";

// クライアントID（任意）
const char *clientID = "ESP8266";

// Beebotteのチャンネルトークン
const char* channelToken = "******************************";

// トピック名（"channel/resource"の形式）
const char* topic = "ifttt/action";
```

```cpp:IFTTT_Action_Beebotte
#include <ESP8266WiFi.h>

// PubSubClientライブラリでのパケットサイズは128バイトなのを拡張
#define MQTT_MAX_PACKET_SIZE 1024
#include <PubSubClient.h>

#include <ArduinoJson.h>

#include "config.h"

const char* host = "mqtt.beebotte.com";

// メッセージを受け取ったらシリアルにプリント
void callback(char* topic, byte* payload, unsigned int length) {
  // PubSubClient.hで定義されているMQTTの最大パケットサイズ
  char buffer[MQTT_MAX_PACKET_SIZE];

  snprintf(buffer, sizeof(buffer), "%s", payload);
  Serial.println("received:");
  Serial.print("topic: ");
  Serial.println(topic);
  Serial.println(buffer);

  // 受け取ったJSON形式のペイロードをデコードする
  StaticJsonBuffer<MQTT_MAX_PACKET_SIZE> jsonBuffer;
  JsonObject& root = jsonBuffer.parseObject(buffer);

  if (!root.success()) {
    Serial.println("parseObject() failed");
    return;
  }

  const char* parsedPayload = root["data"];
  if (parsedPayload != NULL) {
    Serial.print("payload: ");
    Serial.println(parsedPayload);
  }
}

WiFiClient wifiClient;
PubSubClient client(host, 1883, wifiClient);

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

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

    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
      // Wi-Fiアクスポイントへの接続に失敗したら5秒間待ってリトライ
      Serial.println("failed to connect");
      delay(5000);
      return;
    } else {
      Serial.print("WiFi connected: ");
      Serial.println(WiFi.localIP());
    }
  }

  // クライアントがサーバに接続されていなければ
  if (!client.connected()) {
    // ユーザ名を指定して接続
    String username = "token:";
    username += channelToken;
    client.connect(clientID, username.c_str(), NULL);

    if (client.connected()) {
      Serial.println("MQTT connected");
      client.setCallback(callback);

      // トピック名を指定してsubscribe
      client.subscribe(topic);
    } else {
      Serial.print("MQTT connection failed: ");
      Serial.println(client.state());
      delay(5000);
    }
  } else {
    // 既にサーバに接続されていれば通常処理を行う
    client.loop();
  }
}
```

MQTTクライアントの動作確認はBeebotteのコンソールを用いて簡単に行うことができます。スケッチをESP8266モジュールにアップロードしたら、シリアルモニタを開いた状態でBeebotteのコントロールパネルにアクセスします。Account Settingsをクリックした後にCredentialsタブを開くとアクセスキーが表示されていますので、Secret Keyをコピーします（このキーがあれば全てのデータにアクセスできてしまう非常に重要な情報ですのでメールで送信したりブログ記事に掲載したりすることの内容に注意してください）。

次に、コントロールパネルのメニューからConsoleを選択し、画面上部のSecret Keyという欄に先ほどコピーしたSecret Keyをペーストします。この状態で、Publishパネルからチャンネル名とリソース名、データを指定して「Publish Data」ボタンをクリックすると、指定したチャンネルのリソースに対してデータをpublishします。すると、このチャンネルのリソースをトピック名としてsubscribeしているMQTTクライアントであるESP8266にも通知され、シリアルモニタに受け取ったメッセージが表示されるはずです。ここまで動作すれば、BeebotteとESP8266の動作確認は無事に終了です。

![FireShot Capture 40 - Beebotte - kotobuki - https___beebotte.com_dev_console のコピー.png](https://qiita-image-store.s3.amazonaws.com/0/92577/a3b4f46b-3bca-1460-9c89-c9e3bc7870bc.png)

![スクリーンショット 2016-02-23 14.14.04.png](https://qiita-image-store.s3.amazonaws.com/0/92577/d6700ac5-028f-2dc8-adb2-65708a18756e.png)

最後に、IFTTTとBeebotte、ESP8266で動作確認を行います。IFTTT側で、あらかじめ設定したトリガー（GmailやTwitterなど）を発生させてみましょう。トリガーの種類によっては最大で15分程度の遅延が発生しますので、IFTTTのアプリもしくはウェブサイトで作成したレシピを表示させ、Check Nowを実行すると良いでしょう。レシピが実行された時、Arduino IDEのシリアルモニタにBeebotteのConsoleを使用して確認した時のようにメッセージが表示されればOKです。


# 参考資料
* [Arduino Client for MQTT | API Documentation](http://pubsubclient.knolleary.net/api.html)
* [Beebotte MQTT Support](https://beebotte.com/docs/mqtt)