ESP8266によるAmazon Dash ButtonとMilkcocoaとの連携

# 概要
プロミスキャスモードのESP8266を用いることで、Amazon Dash Button (ADB)のイベント取得が可能になったものの、プロミスキャスモード動作時にはWiFi接続できない問題点がありました。
今回はイベント取得するたびにWiFi接続, 通信を行うことでMilkcocoaとの外部サービスとの連携が可能となりました。
(こちらの記事は、[プロミスキャスモードを用いたESP8266でのAmazon Dash Buttonのイベント取得](http://qiita.com/kat-kai/items/3b1d5c74138d77a27c4d)の続きです。)

## 前準備
- 「ボタンが押されると届く商品の設定」をキャンセルしたAmazon Dash ButtonのMACアドレス
- WiFiのチャンネル(IEEE802.11gの場合 1~13; もしかしたら不要かもしれません)


## コード
`ESP_Promiscuous.h`に関しては、前の記事に記載しております。


```c:ESP_Promiscuous_Milkcocoa.ino
#include <ESP8266WiFi.h>
#include <Milkcocoa.h>
#include "ESP_Promiscuous.h"

extern "C" {
  #include <user_interface.h>
}

#define WLAN_SSID       "yourSSID"
#define WLAN_PASS       "Password"

#define MILKCOCOA_DATASTORE   "DATASTORE"
#define MILKCOCOA_APP_ID      "APP_ID"
#define MILKCOCOA_SERVERPORT  1883

#define MILKCOCOA_API_KEY     "MILKCOCOA API KEY"
#define MILKCOCOA_API_SECRET  "MILKCOCOA SECRET"

const char MQTT_SERVER[] PROGMEM    = MILKCOCOA_APP_ID ".mlkcca.com";
const char MQTT_CLIENTID[] PROGMEM  = __TIME__ MILKCOCOA_APP_ID;

// 'client' is Ethernet/WiFi Client you defined
WiFiClient client;
Milkcocoa *milkcocoa = Milkcocoa::createWithApiKey(&client, MQTT_SERVER,
        MILKCOCOA_SERVERPORT, MILKCOCOA_APP_ID, MQTT_CLIENTID, MILKCOCOA_API_KEY, MILKCOCOA_API_SECRET);

byte channel = 5;  //WiFi channel
uint8_t targetMAC1[6] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc};  //Amazon Dash Button MAC address

unsigned long lastMillis = 0;
boolean willSend = false;

static void ICACHE_FLASH_ATTR promisc_cb(uint8_t *buf, uint16_t len)
{
  if (len != 128) return;  //Management Packet以外は return
  
  //Amazon Dash Buttonの通信は以下の順序だったはず: 1. Management Packet (128 byte), 2. Data Packet (60 byte)
  //そのためManagement Packetだけの監視で十分だと考えている。
  
  struct sniffer_buf2 *sniffer = (struct sniffer_buf2*) buf;
  struct MAC_header *mac = (struct MAC_header*) sniffer->buf;

  int i;
  boolean MAC_matchingFlag = true;
  for (i=0; i<6; i++) if (mac->addr2[i] != targetMAC1[i]) MAC_matchingFlag = false;

  if (!MAC_matchingFlag) return; //Amazon Dash ButtonのMACアドレスでなければ return
  
  if (millis() - lastMillis < 7000) return; //重複検出を避けるため、7,000ミリ秒以内の検出は無視する。
  //ここは返答がないせいで、Amazon Dash Buttonが再度リクエストをしている？
  //正常通信のときのようなパケットを模倣して送ってやることで、問題解決可能？

  //Amazon Dash Buttonの命令を拾った場合の処理
  lastMillis = millis();
  
  wifi_promiscuous_enable(0);  //イベントを取得したら、WiFi接続のためにプロミスキャスモードを中断。
  willSend = true;  //このcallback内でのWiFi接続処理はできないため、ここではフラグを立てるだけ。loop()内でWiFi接続処理を行う。
 
  return;
}

void WiFiconnect()
{
  // Wifi
  WiFi.begin(WLAN_SSID, WLAN_PASS);

  while (WiFi.status() != WL_CONNECTED) {
    delay(450);
    Serial.print(".");
  }
  Serial.println();

  Serial.println("WiFi connected");
}

void setup() {
  Serial.begin(115200);
  delay(10);
  
  wifi_set_opmode(STATION_MODE);
  wifi_set_channel(channel); //不要かも
  wifi_set_promiscuous_rx_cb(promisc_cb);

  wifi_promiscuous_enable(1);  //プロミスキャスモードを開始
}

void loop() {
  delay(100);

  if (!willSend) return;    //Amazon Dash Buttonのイベント未取得の場合 return
  //callback関数 promisc_cb内で、イベント取得すればtrueにする。

  //ここ以降がイベントを拾った時に実行される
  willSend = false;
  WiFiconnect();

  //WiFi通信処理
  milkcocoa->loop();
  DataElement elem = DataElement();
  elem.setValue("time", "123");
  milkcocoa->push(MILKCOCOA_DATASTORE, &elem);
  Serial.println("Done.");
  
  WiFi.disconnect();

  //プロミスキャスモード再開
  wifi_promiscuous_enable(1);

}
```

## はまったところ
callback関数内で、`wifi_promiscuous_enable(0)`によりプロミスキャスモードを中断した後に同一関数内でWiFi接続をしようとしたら、自身のWiFi通信も拾おうとしてしまうのか無限ループに陥りExceptionとなりました。そのため、一旦抜ける必要がありました。


## 結論・課題
これでESP8266を用いてAmazon Dash Buttonと外部サービスとの連携ができるようになりました。ただ課題としては、イベント検出するたびにWiFi接続するためレイテンシが長くなってしまう点が挙げられます。私の環境だとMilkcocoaに`push()`されるまでに6秒程度かかっております。

`wifi_send_pkt_freedom()`関数であれば、プロミスキャスモードでもパケット送信ができそうなので、Amazon Dash Button監視用のESP8266から、別のESP8266宛にパケットを送信してから、そのESP8266で通常のWiFi通信を行えばレイテンシはさらに短く抑えられるのかもしれません。