http://qiita.com/kat-kai/items/c898a439bafe5e605dae
の記事に触発された記事です。
この記事内のコードはほとんど他の人のコードを切り貼りしています。全ての寄与はそれぞれの人によるものです。
また、コーディングルールを統一はしていません。来年気が向いたらします。
SSL認証に失敗しています。
動作環境
以下を使っています。
- ESP8266ユニバーサル基板実装
- 気圧計に使っていた基板を使用
- http://qiita.com/7of9/items/00b4e13d54f53774df69
- 自宅のプロバイダでのWiFi接続
- Amazon Dash Button (JOY)
実装内容
- プロミスキャスモードによりAmazon Dash Buttonの検知を行う
- http://qiita.com/kat-kai/items/3b1d5c74138d77a27c4d
- http://qiita.com/kat-kai/items/c898a439bafe5e605dae
- アクセスポイントへWiFi接続する
- Slackへ通知する
- http://qiita.com/7of9/items/59c6d12cadad5b8c500a
- にある参考リンクがコードのオリジナルです
code v0.1
code > WiFi設定
WifiConfig.h :
自分のアクセスポイントを記載したファイル。
注意: GitHubなどにチェックインしないこと
例
static const char *kSsid = "pi-31415-g";
static const char *kPass = "47voyager";
code > Slack設定
slackConfig.h :
Slackのチャネル、Incoming WebHooksなどの設定ファイル。
注意: GitHubなどにチェックインしないこと
例
static const char *kSlackUrl = "/services/PIPIPI/NAPIER/3141592653589793228";
static const String kSlackChannel = "#amazon_dash";
static const String kSlackUsername = "7of9";
kSlackUrl にはIncoming WebHooksのURLの/services/
から始まる文字列を設定する。
code > Dash設定
dashConfig.h :
Amazon Dash ButtonのMACアドレス
MACアドレスの調べ方は以下の1つ目のコードを実行します。
http://qiita.com/kat-kai/items/3b1d5c74138d77a27c4d
例
注意: GitHubなどにチェックインしないこと
uint8_t targetMAC1[6] = { 0x12, 0x34, 0x56, 078, 0x9A, 0xBC}; //Amazon Dash Button MAC address
code > プロミスキャス関連
ESP_Promiscuous.h
以下を使わせていただきました。感謝です。
http://qiita.com/kat-kai/items/3b1d5c74138d77a27c4d
code
#include <ESP8266WiFi.h>
#include "ESP_Promiscuous.h"
#include "dashConfig.h"
#include "WifiConfig.h"
#include "slackConfig.h"
/*
* v0.1 Dec. 31, 2016
* - add [slack submit] feature
* - add [Wifi connection] feature
* - add [amazon dash detection] feature
*/
extern "C" {
#include <user_interface.h>
}
byte channel = 5; //WiFi channel (1-13)
unsigned long lastMillis = 0;
static const char *kSlackHost = "hooks.slack.com";
static const int kHttpsPort = 443;
boolean willSend = false;
static void ICACHE_FLASH_ATTR promisc_cb(uint8_t *buf, uint16_t len)
{
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
if (millis() - lastMillis < 7000) return; //In order to avoid a duplicate detection; 1 cycle needs around 6,500 msec.
lastMillis = millis();
//Handle it.
wifi_promiscuous_enable(0); //イベントを取得したら、WiFi接続のためにプロミスキャスモードを中断。
willSend = true;
} else {
//Data Packet
struct sniffer_buf *sniffer = (struct sniffer_buf*) buf;
struct MAC_header *mac = (struct MAC_header*) sniffer->buf;
return;
}
}
void slack_submit()
{
WiFiClientSecure client;
// connect
if (!client.connect(kSlackHost, kHttpsPort)) {
Serial.println("slack connection failed");
} else {
Serial.println("slack connection: OK");
}
// SSL Certificate finngerprint for the host
const char* fingerprint = "ab f0 5b a9 1a e0 ae 5f ce 32 2e 7c 66 67 49 ec dd 6d 6a 38";
// verify the signature of the ssl certificate
if (client.verify(fingerprint, kSlackHost)) {
Serial.println("ssl cert matches");
} else {
Serial.println("ssl cert mismatch");
}
// submit
String message = "Amazon Dash Pushed (ESP8266)";
String payload="payload={\"channel\": \"" + kSlackChannel + "\", \"username\": \"" + kSlackUsername
+ "\", \"text\": \"" + message + "\", \"icon_emoji\": \":ghost:\"}";
Serial.println(payload.c_str());
client.print("POST ");
client.print(kSlackUrl);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(kSlackHost);
client.println("User-Agent: ArduinoIoT/1.0");
client.println("Connection: close");
client.println("Content-Type: application/x-www-form-urlencoded;");
client.print("Content-Length: ");
client.println(payload.length());
client.println();
client.println(payload);
// リクエストを受け取る前に5秒以上は待った方がいいらしい
delay(7000);
while(client.available()) {
String line = client.readStringUntil('\r');
Serial.println(line);
}
client.stop();
}
void WiFi_setup()
{
WiFi.begin(kSsid, kPass);
while( WiFi.status() != WL_CONNECTED) {
delay(500); // msec
}
Serial.println(WiFi.localIP());
}
void setup() {
WiFi.disconnect(); // to avoid WDT reset
Serial.begin(115200);
wifi_set_opmode(STATION_MODE);
wifi_set_channel(channel);
wifi_set_promiscuous_rx_cb(promisc_cb);
Serial.println("Ready");
// Start!
wifi_promiscuous_enable(1);
}
void loop() {
delay(100); // msec
if (!willSend) {
return;
}
willSend = false;
Serial.println("ADB push detected.");
// submit to slack
WiFi_setup();
slack_submit();
Serial.println("submitted to Slack.");
WiFi.disconnect();
wifi_promiscuous_enable(1); //プロミスキャスモード再開
}
実行
実行すると以下となります。
Ready
Amazon Dash Buttonを押しましょう。
Ready
ADB push detected.
192.168.10.2
slack connection: OK
ssl cert mismatch
payload={"channel": "#amazon_dash", "username": "7of9", "text": "Amazon Dash Pushed (ESP8266)", "icon_emoji": ":ghost:"}
submitted to Slack.
すごいですね!(それぞれの仕組みを見つけて実装した人が)
情報感謝です。
備考
ボタンを押す間隔は10秒程度おいた方がよさそうです。
短い間隔で押すとボタン押下検知あとのWiFi接続から進まなくなりました。
プロミスキャスモード移行やその中のdelay()処理などが関係するかもです。
TODO1
SSLの認証はあいかわらず、mismatchです。
TODO2
これをもって、バッテリーテストをしようと考えています。
- CR-123A (1400mAh?)をESP8266の電源とする
- ESP8266を放置
- 時々Amazon Dash Buttonを押す
- 3の繰り返し
Slackの投稿を見て、およその動作時間が分かります。
数日かかるかもしれません。
休み明けにテスト開始する予定です。
v0.2 > 起動時Slack通知
起動時のメッセージをSlack通知するように変更しました。
code
#include <ESP8266WiFi.h>
#include "ESP_Promiscuous.h"
#include "dashConfig.h"
#include "WifiConfig.h"
#include "slackConfig.h"
/*
* v0.2 Dec. 31, 2016
* - slack_submit() takes [message] arg
* v0.1 Dec. 31, 2016
* - add [slack submit] feature
* - add [Wifi connection] feature
* - add [amazon dash detection] feature
*/
extern "C" {
#include <user_interface.h>
}
byte channel = 5; //WiFi channel (1-13)
unsigned long lastMillis = 0;
static const char *kSlackHost = "hooks.slack.com";
static const int kHttpsPort = 443;
boolean willSend = false;
static void ICACHE_FLASH_ATTR promisc_cb(uint8_t *buf, uint16_t len)
{
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
if (millis() - lastMillis < 7000) return; //In order to avoid a duplicate detection; 1 cycle needs around 6,500 msec.
lastMillis = millis();
//Handle it.
wifi_promiscuous_enable(0); //イベントを取得したら、WiFi接続のためにプロミスキャスモードを中断。
willSend = true;
} else {
//Data Packet
struct sniffer_buf *sniffer = (struct sniffer_buf*) buf;
struct MAC_header *mac = (struct MAC_header*) sniffer->buf;
return;
}
}
void slack_submit(String message)
{
WiFiClientSecure client;
// connect
if (!client.connect(kSlackHost, kHttpsPort)) {
Serial.println("slack connection failed");
} else {
Serial.println("slack connection: OK");
}
// SSL Certificate finngerprint for the host
const char* fingerprint = "ab f0 5b a9 1a e0 ae 5f ce 32 2e 7c 66 67 49 ec dd 6d 6a 38";
// verify the signature of the ssl certificate
if (client.verify(fingerprint, kSlackHost)) {
Serial.println("ssl cert matches");
} else {
Serial.println("ssl cert mismatch");
}
// submit
String payload="payload={\"channel\": \"" + kSlackChannel + "\", \"username\": \"" + kSlackUsername
+ "\", \"text\": \"" + message + "\", \"icon_emoji\": \":ghost:\"}";
Serial.println(payload.c_str());
client.print("POST ");
client.print(kSlackUrl);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(kSlackHost);
client.println("User-Agent: ArduinoIoT/1.0");
client.println("Connection: close");
client.println("Content-Type: application/x-www-form-urlencoded;");
client.print("Content-Length: ");
client.println(payload.length());
client.println();
client.println(payload);
// リクエストを受け取る前に5秒以上は待った方がいいらしい
delay(7000);
while(client.available()) {
String line = client.readStringUntil('\r');
Serial.println(line);
}
client.stop();
}
void WiFi_setup()
{
WiFi.begin(kSsid, kPass);
while( WiFi.status() != WL_CONNECTED) {
delay(500); // msec
}
Serial.println(WiFi.localIP());
}
void setup() {
WiFi.disconnect(); // to avoid WDT reset
Serial.begin(115200);
wifi_set_opmode(STATION_MODE);
wifi_set_channel(channel);
wifi_set_promiscuous_rx_cb(promisc_cb);
Serial.println("Ready");
WiFi_setup();
slack_submit("Amazon Dash Detect Start");
Serial.println("submitted to Slack.");
WiFi.disconnect();
// Start!
wifi_promiscuous_enable(1);
}
void loop() {
delay(100); // msec
if (!willSend) {
return;
}
willSend = false;
Serial.println("ADB push detected.");
// submit to slack
WiFi_setup();
slack_submit("Amazon Dash Pushed (ESP8266)");
Serial.println("submitted to Slack.");
WiFi.disconnect();
wifi_promiscuous_enable(1); //プロミスキャスモード再開
}
v0.3 > promiscuousモード 20% duty cycle
promiscuousモードでの消費電力を抑えることにした。
ずっとpromiscuousモードにするのは無駄なので、20% duty cycleでpromiscuousモードをONにすることにした。
code
主な変更は loop()内 (以下のcnt関連の処理追加)。
if (cnt == 5) {
cnt = 0;
wifi_promiscuous_enable(1); //プロミスキャスモード再開
// Serial.println("on");
} else {
wifi_promiscuous_enable(0); //プロミスキャスモード停止
// Serial.println("off");
}
以下がv0.3のコード。
#include <ESP8266WiFi.h>
#include "ESP_Promiscuous.h"
#include "dashConfig.h"
#include "WifiConfig.h"
#include "slackConfig.h"
/*
* v0.3 Jan. 04, 2017
* - use promiscuous mode with 20% duty cycle
* v0.2 Dec. 31, 2016
* - slack_submit() takes [message] arg
* v0.1 Dec. 31, 2016
* - add [slack submit] feature
* - add [Wifi connection] feature
* - add [amazon dash detection] feature
*/
extern "C" {
#include <user_interface.h>
}
byte channel = 5; //WiFi channel (1-13)
unsigned long lastMillis = 0;
static const char *kSlackHost = "hooks.slack.com";
static const int kHttpsPort = 443;
boolean willSend = false;
static void ICACHE_FLASH_ATTR promisc_cb(uint8_t *buf, uint16_t len)
{
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
if (millis() - lastMillis < 7000) return; //In order to avoid a duplicate detection; 1 cycle needs around 6,500 msec.
lastMillis = millis();
//Handle it.
wifi_promiscuous_enable(0); //イベントを取得したら、WiFi接続のためにプロミスキャスモードを中断。
willSend = true;
} else {
//Data Packet
struct sniffer_buf *sniffer = (struct sniffer_buf*) buf;
struct MAC_header *mac = (struct MAC_header*) sniffer->buf;
return;
}
}
void slack_submit(String message)
{
WiFiClientSecure client;
// connect
if (!client.connect(kSlackHost, kHttpsPort)) {
Serial.println("slack connection failed");
} else {
Serial.println("slack connection: OK");
}
// SSL Certificate finngerprint for the host
const char* fingerprint = "ab f0 5b a9 1a e0 ae 5f ce 32 2e 7c 66 67 49 ec dd 6d 6a 38";
// verify the signature of the ssl certificate
if (client.verify(fingerprint, kSlackHost)) {
Serial.println("ssl cert matches");
} else {
Serial.println("ssl cert mismatch");
}
// submit
String payload="payload={\"channel\": \"" + kSlackChannel + "\", \"username\": \"" + kSlackUsername
+ "\", \"text\": \"" + message + "\", \"icon_emoji\": \":ghost:\"}";
Serial.println(payload.c_str());
client.print("POST ");
client.print(kSlackUrl);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(kSlackHost);
client.println("User-Agent: ArduinoIoT/1.0");
client.println("Connection: close");
client.println("Content-Type: application/x-www-form-urlencoded;");
client.print("Content-Length: ");
client.println(payload.length());
client.println();
client.println(payload);
// リクエストを受け取る前に5秒以上は待った方がいいらしい
delay(7000);
while(client.available()) {
String line = client.readStringUntil('\r');
Serial.println(line);
}
client.stop();
}
void WiFi_setup()
{
WiFi.begin(kSsid, kPass);
while( WiFi.status() != WL_CONNECTED) {
delay(500); // msec
}
Serial.println(WiFi.localIP());
}
void setup() {
WiFi.disconnect(); // to avoid WDT reset
Serial.begin(115200);
wifi_set_opmode(STATION_MODE);
wifi_set_channel(channel);
wifi_set_promiscuous_rx_cb(promisc_cb);
Serial.println("Ready");
WiFi_setup();
slack_submit("Amazon Dash Detect Start");
Serial.println("submitted to Slack.");
WiFi.disconnect();
// Start!
wifi_promiscuous_enable(1);
}
void loop() {
static int cnt = 0;
delay(100); // msec
cnt++;
if (cnt == 5) {
cnt = 0;
wifi_promiscuous_enable(1); //プロミスキャスモード再開
// Serial.println("on");
} else {
wifi_promiscuous_enable(0); //プロミスキャスモード停止
// Serial.println("off");
}
if (!willSend) {
return;
}
willSend = false;
Serial.println("ADB push detected.");
// submit to slack
WiFi_setup();
slack_submit("Amazon Dash Pushed (ESP8266)");
Serial.println("submitted to Slack.");
WiFi.disconnect();
wifi_promiscuous_enable(1); //プロミスキャスモード再開
}
検討
ADBボタン押下の取りこぼしがないか。
20秒ごとにADBを押下することを7回繰り返してみた。
7回とも検知できた。
20% duty cycleくらいだと失敗はないのかもしれない。
v0.4 > Modem-SleepとLight-Sleepの使用
@exabugs さんの記事をもとに調べ始めた、省電力モード。
http://qiita.com/exabugs/items/9edf9e2ba8f69800e4c5
http://qiita.com/7of9/items/171a85605c08bd3b4a72
に書いたように「Modem-sleep」と「Light-sleep」のどちらがADB検知に使えるだろうか。
こういうのは実施してみるのが早い。
code
v0.4としての変更内容はsetup()に以下を追加したこと。
// wifi_set_sleep_type(NONE_SLEEP_T);
// wifi_set_sleep_type(MODEM_SLEEP_T);
wifi_set_sleep_type(LIGHT_SLEEP_T);
以下がv0.4のメインファイルのコード。
#include <ESP8266WiFi.h>
#include "ESP_Promiscuous.h"
#include "dashConfig.h"
#include "WifiConfig.h"
#include "slackConfig.h"
/*
* v0.4 Jan. 05, 2017
* - use [LIGHT_SLEEP_T] mode
* v0.3 Jan. 04, 2017
* - use promiscuous mode with 20% duty cycle
* v0.2 Dec. 31, 2016
* - slack_submit() takes [message] arg
* v0.1 Dec. 31, 2016
* - add [slack submit] feature
* - add [Wifi connection] feature
* - add [amazon dash detection] feature
*/
extern "C" {
#include <user_interface.h>
}
byte channel = 5; //WiFi channel (1-13)
unsigned long lastMillis = 0;
static const char *kSlackHost = "hooks.slack.com";
static const int kHttpsPort = 443;
boolean willSend = false;
static void ICACHE_FLASH_ATTR promisc_cb(uint8_t *buf, uint16_t len)
{
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
if (millis() - lastMillis < 7000) return; //In order to avoid a duplicate detection; 1 cycle needs around 6,500 msec.
lastMillis = millis();
//Handle it.
wifi_promiscuous_enable(0); //イベントを取得したら、WiFi接続のためにプロミスキャスモードを中断。
willSend = true;
} else {
//Data Packet
struct sniffer_buf *sniffer = (struct sniffer_buf*) buf;
struct MAC_header *mac = (struct MAC_header*) sniffer->buf;
return;
}
}
void slack_submit(String message)
{
WiFiClientSecure client;
// connect
if (!client.connect(kSlackHost, kHttpsPort)) {
Serial.println("slack connection failed");
} else {
Serial.println("slack connection: OK");
}
// SSL Certificate finngerprint for the host
const char* fingerprint = "ab f0 5b a9 1a e0 ae 5f ce 32 2e 7c 66 67 49 ec dd 6d 6a 38";
// verify the signature of the ssl certificate
if (client.verify(fingerprint, kSlackHost)) {
Serial.println("ssl cert matches");
} else {
Serial.println("ssl cert mismatch");
}
// submit
String payload="payload={\"channel\": \"" + kSlackChannel + "\", \"username\": \"" + kSlackUsername
+ "\", \"text\": \"" + message + "\", \"icon_emoji\": \":ghost:\"}";
Serial.println(payload.c_str());
client.print("POST ");
client.print(kSlackUrl);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(kSlackHost);
client.println("User-Agent: ArduinoIoT/1.0");
client.println("Connection: close");
client.println("Content-Type: application/x-www-form-urlencoded;");
client.print("Content-Length: ");
client.println(payload.length());
client.println();
client.println(payload);
// リクエストを受け取る前に5秒以上は待った方がいいらしい
delay(7000);
while(client.available()) {
String line = client.readStringUntil('\r');
Serial.println(line);
}
client.stop();
}
void WiFi_setup()
{
WiFi.begin(kSsid, kPass);
while( WiFi.status() != WL_CONNECTED) {
delay(500); // msec
}
Serial.println(WiFi.localIP());
}
void setup() {
WiFi.disconnect(); // to avoid WDT reset
// wifi_set_sleep_type(NONE_SLEEP_T);
// wifi_set_sleep_type(MODEM_SLEEP_T);
wifi_set_sleep_type(LIGHT_SLEEP_T);
Serial.begin(115200);
wifi_set_opmode(STATION_MODE);
wifi_set_channel(channel);
wifi_set_promiscuous_rx_cb(promisc_cb);
Serial.println("Ready");
WiFi_setup();
slack_submit("Amazon Dash Detect Start");
Serial.println("submitted to Slack.");
WiFi.disconnect();
// Start!
wifi_promiscuous_enable(1);
}
void loop() {
static int cnt = 0;
delay(100); // msec
cnt++;
if (cnt == 5) {
cnt = 0;
wifi_promiscuous_enable(1); //プロミスキャスモード再開
// Serial.println("on");
} else {
wifi_promiscuous_enable(0); //プロミスキャスモード停止
// Serial.println("off");
}
if (!willSend) {
return;
}
willSend = false;
Serial.println("ADB push detected.");
// submit to slack
WiFi_setup();
slack_submit("Amazon Dash Pushed (ESP8266)");
Serial.println("submitted to Slack.");
WiFi.disconnect();
wifi_promiscuous_enable(1); //プロミスキャスモード再開
}
結果
Light-SleepとModem-Sleepともに、以下の試験を実施した。
- 20秒ごとにADBを押下する
- 手順1を7回繰り返す
Modem-Sleep、Light-Sleepともに7回のADB押下を検知できた。
Light-Sleepを採用することにした。
(追記 2017/01/07)
http://qiita.com/7of9/items/8712a71c8af267170da4
に試験結果を記載した。
Light-Sleepを使用しない場合と同じ14時間弱までの動作となった。
Light-Sleepのまま動作していない可能性がある。
本当にLight-Sleepが機能しているのか確認するには、消費電流測定が必要だ。
Link to the ESP8266 WiFi Repeater (NAT Router)
@yesnoj gave me the information of the ESP8266 WiFi Repeater on his comment.
I appreciate it.