Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
20
Help us understand the problem. What is going on with this article?
@kat-kai

プロミスキャスモードを用いたESP8266でのAmazon Dash Buttonのイベント取得

More than 3 years have passed since last update.

概要

プロミスキャスモードのESP8266を用いることで、Amazon Dash Button (ADB)のイベント取得に成功しました。
これによりRaspberry Pi等を必要とせずにESP8266単独での検出が可能となりました。今回はESP-WROOM-02, Arduino 1.6.13を利用しています。

ESP8266とは

秋月電子等で購入可能なWiFi通信可能なモジュール(500~600円程度)。Arduinoで開発可能。
Wi-Fiモジュール ESP-WROOM-02 DIP化キット http://akizukidenshi.com/catalog/g/gK-09758/

Amazon Dash Buttonとは

ボタンを押すだけで、自動注文により商品を届けてくれるIoTデバイス。今回のように注文せずに、ボタン押下イベント取得だけをしたい場合は「ボタンが押されると届く商品の設定」をキャンセルする必要があります。

参考

WiFiチャンネルとボタンのMACアドレスを取得

プロミスキャスモードでの動作にはWiFiチャンネルが、またAmazon Dash Buttonのイベント取得にはMACアドレスが必要となります。

それらを取得するために、まず以下のスケッチをESP8266に書き込み、実行します。

ESP_Promiscuous_getDashInfo.ino
#include <ESP8266WiFi.h>
#include "ESP_Promiscuous.h"

//プロミスキャスモードのAPIを利用するために必要
extern "C" {
  #include <user_interface.h>
}

//WiFi接続部 参考:http://qiita.com/azusa9/items/7f78069cb09872cf6cbf
char toSSID[] = "SSID";
char ssidPASSWD[] = "Password";

//プロミスキャスモードでパケットを受け取ったときのcallback
static void ICACHE_FLASH_ATTR promisc_cb(uint8_t *buf, uint16_t len)
{
  if (len != 128) return; //In order to discard both unknown packets and data packets

  struct sniffer_buf2 *sniffer = (struct sniffer_buf2*) buf;
  struct MAC_header *mac = (struct MAC_header*) sniffer->buf;

  int i;
  boolean beaconFlag = true;

  for (i=0; i<6; i++) if (mac->addr2[i] != mac->addr3[i]) beaconFlag = false;

  if (beaconFlag) return; //In order to remove beacon Packet

  Serial.print("Possible MAC Address: ");
  for (i=0; i<6; i++){
    Serial.print(mac->addr2[i], HEX);
    Serial.print(":");
  }
  Serial.println("");
}

void setup() {
  byte channel;
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.begin(toSSID, ssidPASSWD);

  Serial.print("WiFi connecting.");

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

  Serial.println("");

  channel = wifi_get_channel();
  Serial.print("Your WiFi Channel: ");
  Serial.println(channel);
  WiFi.disconnect();

  Serial.println("");

  Serial.println("ESP8266 promiscuous mode started.");
  Serial.println("Please push Amazon Dash Button");
  Serial.println("");

  wifi_set_opmode(STATION_MODE);
  wifi_set_channel(channel);
  wifi_set_promiscuous_rx_cb(promisc_cb);

  wifi_promiscuous_enable(1);
}

void loop() { }
ESP_Promiscuous.h
struct RxControl {
    signed rssi:8;
    unsigned rate:4;
    unsigned is_group:1;
    unsigned:1;
    unsigned sig_mode:2;
    unsigned legacy_length:12;
    unsigned damatch0:1;
    unsigned damatch1:1;
    unsigned bssidmatch0:1;
    unsigned bssidmatch1:1;
    unsigned MCS:7;
    unsigned CWB:1;
    unsigned HT_length:16;
    unsigned Smoothing:1;
    unsigned Not_Sounding:1;
    unsigned:1;
    unsigned Aggregation:1;
    unsigned STBC:2;
    unsigned FEC_CODING:1;
    unsigned SGI:1;
    unsigned rxend_state:8;
    unsigned ampdu_cnt:8;
    unsigned channel:4;
    unsigned:12;
};

struct LenSeq {
    uint16_t length;
    uint16_t seq;
    uint8_t  address3[6];
};

struct sniffer_buf {
    struct RxControl rx_ctrl;
    uint8_t buf[36];
    uint16_t cnt;
    struct LenSeq lenseq[1];
};

struct sniffer_buf2{
    struct RxControl rx_ctrl;
    uint8_t buf[112];
    uint16_t cnt;
    uint16_t len;
};

struct MAC_header {
    uint16_t frameControl;
    uint16_t duration;
    uint8_t addr1[6];
    uint8_t addr2[6];
    uint8_t addr3[6];
    uint16_t sequenceControl;
    uint8_t addr4[6]; 
};

そして実行し、ボタンを押すと検出してくれます。
2行目の5がWiFiチャネル、12:34:56:78:9a:bcがMACアドレスとなります。
Amazon Dash Buttonが関連する通信が10~15個程、表示されます。
ARPプローブ等が送受信されてると思いますが、詳細は把握しておりません。

WiFi connecting......
Your WiFi Channel: 5

ESP8266 promiscuous mode started.
Please push Amazon Dash Button.

Possible MAC Address: 12:34:56:78:9a:bc:
Possible MAC Address: 12:34:56:78:9a:bc:
(以下、続く)

イベント取得

続いて、得られたWiFiチャンネルとMACアドレスを用いて、Amazon Dash Buttonの通信を検知します。

2017/01/02 21:55修正: ご指摘頂きました方々にお礼申し上げます。また本ソースではESP8266WiFi.hの関数を用いていないので、include無しでもコンパイルが通ると思いますが如何でしょうか。不備等ございましたら、ご指摘いただけると幸いです。

ESP_Promiscuous_DashCapt(ver.0.2).ino
#include "ESP_Promiscuous.h"

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

byte channel = 5;  //WiFi channel (1-13)
uint8_t targetMAC1[6] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc};  //Amazon Dash Button MAC address
int detectInterval = 8000; //msec

unsigned long lastDetectedMillis = 0;
boolean ADBdetectingNow = false;  //Amazon Dash Buttonイベント検出中フラグ

static void ICACHE_FLASH_ATTR promisc_cb(uint8_t *buf, uint16_t len)
{
    if (ADBdetectingNow) return;  //Amazon Dash Buttonイベント検知中(detectInterval指定ミリ秒間)であれば無視

    if (len == 12){
      //No accurate information about MAC address and length of the head of packet.
      struct RxControl *sniffer = (struct RxControl*) buf;
      return;
    } else if (len == 128) {
      //Management Packet
      struct sniffer_buf2 *sniffer = (struct sniffer_buf2*) buf;
      struct MAC_header *mac = (struct MAC_header*) sniffer->buf;

      int i;
      boolean MAC_Matching_Flag = true;

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

      if (!MAC_Matching_Flag) return; //No hit

      //Handle it.
      Serial.println("ADB push detected.");
      ADBdetectingNow = true;
      lastDetectedMillis = millis();

      return;

    } else {
      //Data Packet
      struct sniffer_buf *sniffer = (struct sniffer_buf*) buf;
      struct MAC_header *mac = (struct MAC_header*) sniffer->buf;

      return;
    }
}

void setup() {  
  Serial.begin(115200);
  wifi_set_opmode(STATION_MODE);
  wifi_set_channel(channel);
  wifi_set_promiscuous_rx_cb(promisc_cb);

  // Start!
  wifi_promiscuous_enable(1);
}

void loop() {
  unsigned long interval;
  if (lastDetectedMillis > millis()) //稼働時間が49.7日を超えmillis()がオーバーフローした場合
    interval = (0xffffffff - lastDetectedMillis) + millis();
  else
    interval = millis() - lastDetectedMillis;

  if (interval > detectInterval)
    ADBdetectingNow = false;
}

上記スケッチ起動後にADBのボタンを押すとADB push detected.と表示されるはずです。

結論

ADBのイベントを取得し、Lチカ等への展開ができそうです。
欠点としてESP8266はプロミスキャスモードでの動作時、WiFiの接続ができなくなってしまう点が挙げられます。
しかしプロミスキャスモードを解除すれば、WiFiの接続ができるようになるので、Milkcocoa等の他のサービスと連携も可能です。

後日、その詳細を書きたいと思います。
12/30 23:38追記 ESP8266によるAmazon Dash ButtonとMilkcocoaとの連携 書きました

またAPIリファレンスを眺めていると、WPSを利用してWiFi接続できそうだったり、WiFi経由でSleepから起こせそうだったりと色々と面白そうなAPIがありましたので、今後そういったことも記事にしていきたいと思います。

参考文献

20
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
kat-kai
ESP8266/ESP32, Python (3.x), C#が好きです。ESP8266/ESP32で遊んでます(https://kat-kai.github.io)

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
20
Help us understand the problem. What is going on with this article?