##はじめに
今回の記事では、Arduino IDEで開発可能で安価なWiFiモジュールESP8266を用いてAmazon Dashu Buttonのボタン押下イベントを取得しています。これまでのプロミスキャスモードによる手法ではなく、WIFI_AP_STAモードで取得することで外部サービスとの通信をサクッとすることができます。
Amazon Dash Buttonの通信は以下の通りになっています (rukihenaさんの記事より)
Amazon Dash Button は AmazonのサーバとHTTPSでしゃべるので、それに先立ち以下のような通信をしていると思われます。
①無線ネゴ→②WPA認証→③DHCP(ARP含む)→④DNS→⑤HTTPS
今回も①無線ネゴの段階で検出しています。①の段階でのAmazon Dash Buttonからのプローブ要求を検知しております。
##実装
(15:45追記 注意点: 1度ボタンを押すと2回程度の重複検知が生じますのでご注意ください。)
(20:28追記 スマホ等の他の機器からのWiFi接続が少し不安定になります。同じSSIDが2つあるため、WiFiの繋ぎ変えが生じていそうです。)
#include <ESP8266WiFi.h>
extern "C" {
#include <user_interface.h>
}
//-------------------------------------------------------------------------
// WiFi Data
//-------------------------------------------------------------------------
#define WLAN_SSID "yourSSID"
#define WLAN_PASS "yourPassword"
//-------------------------------------------------------------------------
//String monitoringMAC1 = "12:34:56:78:9a:bc"; //各アドレスにおいて0F以下であれば、0は省略されます。
//String monitoringMAC2 = "a:b:c:d:e:f"; //例えば0a:0b:0c:0d:0e:0fだと"a:b:c:d:e:f"と表記する必要があります。
uint8_t monitoringMAC1[6] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}; //MACアドレスの比較法変更 (2017/01/11 9:15)
uint8_t monitoringMAC2[6] = { 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
boolean detectingMAC1 = false;
boolean detectingMAC2 = false;
String getStrMAC(uint8_t mac[6]){
String res = String(mac[0], HEX) + ":" + String(mac[1], HEX) + ":" + String(mac[2], HEX) + ":" +
String(mac[3], HEX) + ":" + String(mac[4], HEX) + ":" + String(mac[5], HEX);
return res;
}
void ICACHE_FLASH_ATTR wifi_handle_event_cb(System_Event_t *evt) {
if (evt->event != EVENT_SOFTAPMODE_PROBEREQRECVED) return; //プローブ要求以外は無視
uint8_t* mac = evt->event_info.ap_probereqrecved.mac;
if (memcmp(mac,monitoringMAC1,6) == 0) detectingMAC1 = true; //Thanks to @rukihena san
if (memcmp(mac,monitoringMAC2,6) == 0) detectingMAC2 = true;
}
void setup() {
Serial.begin(115200);
connectWiFi();
//既設アクセスポイントと同一のSSIDでsoftAP作成
WiFi.softAP(WLAN_SSID, "pleasechangethisdummypassword");
wifi_set_event_handler_cb(wifi_handle_event_cb);
}
void loop() {
//milkcocoa->loop(); milkcocoa用
if (detectingMAC1) {
detectingMAC1 = false;
detectMAC1();
}
if (detectingMAC2) {
detectingMAC2 = false;
detectMAC2();
}
}
void connectWiFi() {
WiFi.disconnect();
delay(10);
WiFi.mode(WIFI_AP_STA);
WiFi.begin(WLAN_SSID, WLAN_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(450);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected");
}
void detectMAC1() {
Serial.println("ADB1 detected!");
}
void detectMAC2() {
Serial.println("ADB2 detected!");
}
今回はWIFI_AP_STAモード(自身はアクセスポイントになりつつ、既設のアクセスポイントにクライアントとしても接続するモード)を用いています。また複数台検知も容易になりました。
まずsetup()
で無線ルータに接続し、softAP()
によりアクセスポイント作成後にwifi_set_event_handler_cb();
でWiFiのイベントを取得します。
wifi_handle_event_cb()
ではプローブ要求以外のイベントであれば無視し、プローブ要求してきたクライアントのMACアドレスを取得・比較します。今回でも、このコールバック関数内ではdetectingMAC1, detectingMAC2のフラグを立て、loop()内で処理します。
またWiFiイベントとして以下に示すイベントも取得できるようです。
(user_interface.hより)
EVENT_STAMODE_CONNECTED = 0,
EVENT_STAMODE_DISCONNECTED,
EVENT_STAMODE_AUTHMODE_CHANGE,
EVENT_STAMODE_GOT_IP,
EVENT_STAMODE_DHCP_TIMEOUT,
EVENT_SOFTAPMODE_STACONNECTED,
EVENT_SOFTAPMODE_STADISCONNECTED,
EVENT_SOFTAPMODE_PROBEREQRECVED,
EVENT_MAX
IoTとして他のサービスとの連携
Milkcocoa
void detectMAC1(char *msg) {
DataElement elem = DataElement();
elem.setValue("Event", msg);
milkcocoa->push(MILKCOCOA_DATASTORE, &elem);
Serial.println("Done.");
}
例えばdetectMAC1()を上記のように変更すれば、milkcocoaとの連携も可能になります。
Line Notify (15:42追記)
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
extern "C" {
#include <user_interface.h>
}
const char* host = "notify-api.line.me";
String LINE_TOKEN = "yourtokenishere";
WiFiClientSecure client;
(中略)
void detectMAC1(String msg) {
LinePost("message=" + msg, LINE_TOKEN);
}
String LinePost(String data, String Token) {
Serial.println("httpsPost");
if (client.connect(host, 443)) {
Serial.println("Connect");
client.println("POST /api/notify HTTP/1.1");
client.println("Host: " + (String)host);
client.println("User-Agent: ESP8266/1.0");
client.println("Connection: close");
client.println("Content-Type: application/x-www-form-urlencoded;");
client.print("Authorization: Bearer ");
client.println(Token);
client.print("Content-Length: ");
client.println(data.length());
client.println();
client.println(data);
delay(10);
String response = client.readString();
int bodypos = response.indexOf("\r\n\r\n") + 4;
return response.substring(bodypos);
}
else {
return "ERROR";
}
}
Line Notifyのトークン取得後、上記コードにより連携可能となります。
セキュリティ問題
これまでのプロミスキャスモードと同様に、MACアドレスのみの照合なのでセキュリティ上の問題あり!どうしよう・・・
しかしWIFI_AP_STAモードを使うことでWiFi接続したままにできるので、他のサービスとの連携が一層スピーディーに行えるようになりました。例えばMilkcocoaへのpush時間は1秒もかからないです。まだ少し問題は残っているものの、外部サービスとの連携も低遅延で行えるようになりました。今後は押下イベント検出にARPが使えないかを検討していこうと思います。
##ToDo
- Line Notify, Twitter, IFTTT, Slack等の連携
- SSID, パスワードでの認証, ARP? ちょっと難しそう。
##参考文献
今回、以下の記事の内容・アイディアを参考に実装しました。
- ESP8266 > Amazon Dash Buttonの接続先アクセスポイントになる > v0.1 (失敗編): http://qiita.com/7of9/items/89b24045bf5e44f97be4
- Amazon Dash Button から低遅延で鍵を開ける: http://qiita.com/rukihena/items/aed9999a6a4376d68f0b