ESP8266でAmazon Dash Buttonのボタンプッシュを検知する方法

### 本記事について
ESP8266という、WiFi通信ができるArduino互換のマイコンがあります。
それを用いて、Amazon Dash Buttonのボタンプッシュを検知する方法です。
WiFiアクセスポイント不要。ESP8266のみでボタンプッシュを検知できます。

## Amazon Dash Button ハックの仕組み

Amazon Dash Button ハックの仕組みについておさらいしておきます。
仕組みについては [Amazon Dash ButtonをただのIoTボタンとして使う](https://qiita.com/jsoizo/items/3b8bba4160f41aef20f4) に詳しく書いてあります。

改めて説明すると、Amazon Dash Buttonは、ボタンが押されると、電源が入り、WiFiに接続し、インターネットを介してAmazonのサーバーにアクセスし、実際の注文を行います。

しかし、注文に絡むデバイスのため、本当にハックするのは難しく、改造等は行わないで、Amazon Dash ButtonがWiFiに接続したのを検知して、ボタンがプッシュされたとみなす、間接的なハックを行います。

ボタンを押すとAmazon Dash ButtonがWiFiに接続するようにするには、Amazon Dash ButtonにWiFiのアクセスポイント情報が登録されている必要があります。

そして、その登録のために、注文商品選択直前までAmazon Dash Buttonのセットアップを進め、そこでセットアップを中断しておきます。

そこまでセットアップが進んでいると、WiFiアクセスポイントの登録は完了していて、以後は、ボタンを押すとWiFiアクセスポイントに接続するようになります。

## WiFi接続検知の方法
WiFi接続検知の方法は２つあります。

### １つ目
Amazon Dash ButtonはWiFiへ接続の際、IPアドレス取得のため、まず自分のMACアドレスをネットワークにブロードキャストします。その仕組を利用して、ボタン検出する側の機器が同じWiFiネットワークに接続しておき、Amazon Dash ButtonのMACアドレスが配信されるのをウォッチして検出する方法です。

### ２つ目
ボタン検出する側の機器自らがWiFiのアクセスポイントとなり、Amazon Dash Buttonに登録アクセスポイントして登録する方法です。そして、Amazon Dash Buttonから、自らのアクセスポイントへのコネクションを検出します。

手順がシンプルなので、多くのハックは１つ目の方法を用いています。

しかし、１つ目の場合、常時別途WiFiアクセスポイントを用意する必要があり、ESP8266とAmazon Dash Buttonだけで動かすことができません。
WiFi環境が無い状況でも動作できるよう、ここでは２つ目の方法を用いました。

## 手順
### ESP8266をアクセスポイントとして登録
ESP8266はWiFiを受けて、WiFi発信することができるので、仕様上はWiFiからWiFiへの中継ポイント、つまりNATになることができます。

しかし、[ESP8266でNAT(NAPT)でWi-Fiを中継する](http://neocat.hatenablog.com/entry/20161012/1476219628) に記載されているように、ESP8266のライブラリではその機能をOFFにされているため、一旦ライブラリを修正してその機能をONにしてライブラリを再コンパイルし、そのライブラリを使ってNATを実装し可動する必要があります。

そして、ESP8266をNATとして可動させ、Amazon Dash ButtonをそのNATを介してセットアップして、ESP8266をAmazon Dash Buttonに登録する必要があります。

幸い、[A full functional WiFi Repeater](https://github.com/martin-ger/esp_wifi_repeater) にESP8266をNATにするソースとバイナリがありますので、今回はそのバイナリをインストールして使用しました。

### NATインストール
詳細は [A full functional WiFi Repeater](https://github.com/martin-ger/esp_wifi_repeater) の「Building and Flashing」に記載されています。

1. ダウンロード<br>
   [A full functional WiFi Repeater](https://github.com/martin-ger/esp_wifi_repeater) で[Clone or download]-[Download ZIP]して解凍
2. ソフトインストール<br>
   https://www.espressif.com/en/support/download/other-tools からバイナリ書き込みツール「Flash Download Tools」をダウンロードしてインストール
3. バイナリロード<br>
   解凍したファイルの「firmware」にバイナリ「0x00000.bin」「0x10000.bin」があるのでそれを「Flash Download Tools」を使って書き込みます。<br>
   （製品によってESP8266のFLASH SIZEが異なるので、使っているESP8266に合わせます）
   
### NATセットアップ
1. ESP8266セットアップ<br>
   起動するとNATになっているので 詳細は [A full functional WiFi Repeater](https://github.com/martin-ger/esp_wifi_repeater) の「Basic Web Config Interface」に従って、ブラウザでアクセスして設定します。<br>
   （ここではデフォルトの設定を用いました）
2. Amazon Dash Buttonセットアップ<br>
   上記で設定したESP8266のアクセスポイントにAmazon Dash Buttonがアクセスするようにして、Amazon Dash Buttonのハックにあるのと同様の、商品選択直前までのセットアップを行います。

### ボタン検知プログラム
ESP8266のアクセスポイント（SSIDとパスワード）がAmazon Dash Buttonに登録されましたので、後はボタン検知のESP8266のプログラムを書いて転送し実行するだけです。

サンプルプログラムは下記になります。

- Amazon Dash ButtonがESP8266のアクセスポイントに接続すると、``onStationConnected()`` コールバック関数が呼ばれますので、これをもってボタンプッシュ検知としています 
- DNSサーバーは必須ではないのですが、あった方がAmazon Dash Buttonがリトライをあきらめるのが早い（次のボタン検知可能までの間隔が短くなる）ようなので入れてあります

```c
#include <ESP8266WiFi.h>
#include <DNSServer.h>

const char *ssid = "xxxx";
const char *password = "xxxxxxxxxx";
const byte dns_port = 53;
IPAddress apIP(192, 168, 4, 1);
IPAddress subnet(255, 255, 255, 0);
DNSServer dnsServer;
WiFiEventHandler stationConnectedHandler;

byte bConnection = 0;
int push_count= 0;
int send_frame = 0;
const int max_send_frame = 200000;
byte pin_a = 16;
byte pin_b = 14;

void setup() {
  delay(1000);
  Serial.begin(115200);
  Serial.println();
  Serial.println("Configuring access point...");

  WiFi.softAPConfig(apIP, apIP, subnet);
  WiFi.softAP(ssid, password);
  stationConnectedHandler = WiFi.onSoftAPModeStationConnected(&onStationConnected);

  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);

  dnsServer.start(dns_port, "*", apIP);

  pinMode(pin_a, OUTPUT);
  pinMode(pin_b, OUTPUT);

  digitalWrite(pin_a, LOW);
  digitalWrite(pin_b, LOW);

}

void onStationConnected(const WiFiEventSoftAPModeStationConnected& evt) {
  bConnection = 1;
  send_frame = 0;
  ++push_count;

  Serial.print("Station connected: ");
  Serial.print(macToString(evt.mac) + " -> ");
  Serial.println(push_count);
  
  digitalWrite(pin_a, HIGH); 
  digitalWrite(pin_b, HIGH); 
  Serial.println("pin HIGH");
}

void pinOut(){
  if(bConnection){
    if(send_frame<max_send_frame){
      ++send_frame;
    } else {
      bConnection = 0;
      digitalWrite(pin_a, LOW);
      digitalWrite(pin_b, LOW);
      Serial.println("pin LOW");
    }
  }
}

void loop() {
  dnsServer.processNextRequest();
  pinOut();
}

String macToString(const unsigned char* mac) {
  char buf[20];
  snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  return String(buf);
}

```

## 最後に
WiFiアクセスポイント不要で、ESP8266だけでボタン検知できるようになるので、屋外等、任意の場所へと、Amazon Dash Button の活用フィールドが広がります。

## 関連記事
- [ESP8266でAmazon Dash Buttonのボタンプッシュを検知する方法](https://www.kwbtblog.com/entry/2019/02/25/023107)