GolangとAmazon Dash Buttonで遊ぶ

# はじめに
　普段はPHP7 & Laravel5で色々作ってます。Amazon Dash Button、おもしろそうだなーと思っていろいろ調べてみるとAmazon Dash Buttonはシンプルながらもスゴイ ハードウェアでした。

　残念なことにPHPではAmazon Dash Buttonをコントロールするのが難しいようで、いままで気になっていたGolangにてAmazon Dash Buttonをコントロールして見ることにしました。

# Amazon Dash Buttonのセットアップ

## セットアップに必要なもの

- Amazon Dash Button 本体
- Android端末またはiPhone/iPad端末
    - Amazon Dash ButtonにWiFi情報を書き込むためにBluetooth接続が必要
    - Bluetooth接続ができない場合は、超音波通信でWiFi情報を書き込むらしい
- Amazonショッピングアプリ
- インターネット接続環境（WiFi(2.4GHz帯)）

### セットアップについては、下記の記事を参照してください。

* [Amazon Dash ButtonをただのIoTボタンとして使う](http://qiita.com/jsoizo/items/3b8bba4160f41aef20f4)
* [Amazon Dash Button でおうちハックしようその1 セットアップ編](http://qiita.com/tokio-takeda/items/02bae474f8f20f35a02c)

# Amazon Dash ButtonのMACアドレスを調べる

- DHCPサーバーでIPアドレスの払い出し状況を確認。見慣れないMACアドレスを見つける。
- 無線LANルーター(WiFi＋DHCPサーバー)のログで、見慣れないMACアドレスを見つける。


# GolangでAmazon Dash Buttonが押されたことを検知する

## ソースコード

　Aamazon Dash Buttonから送られるブロードキャストを拾って、押されたことを感知させてます。

```go:catch_arp.go
package main

import (
	"bytes"
	"fmt"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"log"
	"net"
	"time"
)

var (
	snapshot_len int32         = 1600
	promiscuous  bool          = false
	timeout      time.Duration = 500 * time.Millisecond
)

func main() {

	//Packet capture
	handle, err := pcap.OpenLive("eth0", snapshot_len, promiscuous, timeout)
	defer handle.Close()

	err = handle.SetBPFFilter("port 67 or port 68")
	checkErr(err)

	macBroadcast, _ := net.ParseMAC("FF:FF:FF:FF:FF:FF")

	fmt.Println("start.")

	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
	for packet := range packetSource.Packets() {

		ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
		ethernetPacket, _ := ethernetLayer.(*layers.Ethernet)

		if bytes.Equal(ethernetPacket.DstMAC, macBroadcast) {
			//Amazon Dush Button, Push?
			fmt.Printf("%#v\n", ethernetPacket.SrcMAC.String()) //※
		}
	}
}

func checkErr(err error) {
	if err != nil {
		log.Fatalln(err)
		panic(err)
	}
}
```

　※の行：`fmt.Printf("%#v\n", ethernetPacket.SrcMAC.String())`のところに、Amazon Dash Buttonが押されたときに処理したいことを記述しましょう。


## 実行結果

　パケットキャプチャーは特権ユーザーにて動かさないと、パケットを受信してくれないので、`sudo`などで特権ユーザーとして実行します

```shell-session
pi@raspberrypi:~/go $ sudo /usr/local/go/bin/go run catch_arp.go 
start.
"b4:7c:9c:40:48:62"    ……(1)
"b4:7c:9c:40:48:62"    ……(2)
"b4:7c:9c:40:48:62"    ……(3)
```

## 動作説明 【概略】
1. Amazon Dash Buttonが押されると、Amazon Dash Buttonが登録されている無線LANにつなごうとします。
2. その時、BOOTP/DHCPのプロトコルにてAmazon Dash Buttonが「DHCPサーバー、ここにいる？」の問い合わせブロードキャストが飛ばしてきます。その時のAmazon Dash ButtonのMACアドレスが(1)として出力されてます。
3. DHCPサーバーから「DHCPサーバー、ありますよ」と返信が来ます。
4. Amazon Dash Buttonが「IPアドレスください」と問い合わせのブロードキャストが飛ばしてきます。その時のAmazon Dash ButtonのMACアドレスが(2)として出力されてます。
5. Amazon Dash Buttonがサーバーといろいろ通信します。（5秒くらい？）
6. その後(通信完了後)、通信を切るときに、ブロードキャストが飛ばしてきます。その時のAmazon Dash ButtonのMACアドレスが(3)として出力されてます。

### BOOTP/DHCPで感知するか、ARPで感知するか？
　ARP…IPアドレスの衝突を防ぐプロトコル…でAmazon Dash Buttonを押されたことを確認できるようです。ただ、TP-LinkのTL-WR841Nでは、ARPのパケットがキャプチャーできなかったため、BOOTP/DHCPのブロードキャストを拾う形で対応してます。

　BOOTP/DHCPで感知する方式だと3回(最初2回と時間を置いて1回)通信をキャッチしてしまいます。そのためプログラムの方でなんらかの重複を排除する処理が必要になってきます。

# Amazon Dash Buttonで遊ぶ

##「ボタンが押されたら、スイッチオンの通知」

　たくさんのAmazon Dash Buttonをつなげたら、簡単なアンケートシステムができるのでは？と思いつき、とりあえず、Amazon Dash Buttonを30個ほど買い込みました。

[<img src="https://qiita-image-store.s3.amazonaws.com/0/38845/143e0f08-9367-eba1-ffc0-bbe939b5bedf.png" width="100">](https://qiita-image-store.s3.amazonaws.com/0/38845/143e0f08-9367-eba1-ffc0-bbe939b5bedf.png)

その結果、昔「笑っていいとも」というテレビ番組があったのですが、こちらの名物コーナー「100人アンケート」のようなシステムがいい感じに出来あがり。

## どこまで拡張できるんだろう？
　TP-LINKの無線LANルーターがOpenWRT対応してるという話を聞き、OpenWRTが動くなら その場(ルーター内のCPU)でGolangを動かして～とやりたかったのですが、残念ながらOpenWRT化することができず、Golangの処理実行部をRaspberry Pi 3 Model Bがあったのでコチラで代用。無線LANルーター：TL-WR841NのDHCPのアドレス配布数が100個くらいまでは問題なさそうです。

　あと、Amazon Dash Buttonを70個ほど買って実証事件をしてみたいものです。

## ボタンの感知方式は、BOOTP/DHCP方式で
　BOOTP/DHCP方式だと、Amazon Dash Buttonからパケットが飛んできます。このパケットをとりあえずデータベースのテーブルに登録し、ボタンが押された数をカウントするところでレコード集計する形にしています。

```SQL:とりあえず登録するテーブル
-- テーブル構造
CREATE TABLE [push_histories] (
    [keytime] INTEGER,         --押された時間, ミリ秒
    [mac_address] VARCHAR(24), --押されたAmazon Dash ButtonのMACアドレス
    PRIMARY KEY(keytime,mac_address)
);
```

　操作を簡単にするため、リセット専用のAmazon Dash Buttonを設定しています。次のSQL文はリセット専用Button(MAC:`de:ad:be:ef:xx:xx`)が押された以降のレコードを集計するようにしています。

```SQL:押されたボタンの数を集計するSELECT文
SELECT COUNT(DISTINCT mac_address) FROM [push_histories]
 WHERE keytime > (SELECT keytime FROM [push_histories]
                  WHERE mac_address = "de:ad:be:ef:xx:xx"
                  ORDER BY keytime DESC
                  LIMIT 1)
 AND mac_address <> "de:ad:be:ef:xx:xx";
```

　重複した除去した後のレコード数を数えているので、二度押ししてしまっていても特に問題ありません。

## 表示部
　GolangのECHO Frameworkにて、Websocketを使用したリアルタイムでの集計数が表示されるようにしたため、アンケートシステムっぽくなりました。ついでに、集計中はランダムな数字が表示されたほうが、面白いなーってことでランダムな数字が表示されるようにも。
　こちらのランダム表示、Golangでやろうかとおもったけど通信する必要がないので、ブラウザ側でランダムな数字を表示するJavascriptをかいて完了。

## せっかくなのでシステムの名前をつける
　「笑っていいとも」が放映されていた時期のダサさと、現時代のウェイウェイ感を狙って「Dash de YES!」と命名（協力：[Qiitadon](https://qiitadon.com/)の皆さん）。読み方は「ﾀﾞｯｼｭﾃﾞｪｰｽｯ」。発音は「ﾀﾞｧｼｴﾘｲｪｽ」のノリで発音してください。

## せっかくなのでボタンもデザインする
　こちらの記事：[Amazon Dash Buttonを(正しくない方向で)使ってみた](http://qiita.com/takustaqu/items/8539b33780c9675c8657)に掲載されているテンプレートを使って、ボタンをざっくりとデザイン。Amazon Dash Buttonの大きさは、25mm x 60mmくらいです。このサイズのシールを印刷業者さんにお願いしても、あんまり高くないので数量があるときは業者さんに依頼したほうが、キレイなものが出来上がるのでオススメです。大まかにユポ合成紙
 仕上がり50枚で2000円ちょっとです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/38845/90d2fa06-606e-b9a7-e830-5cd584df2085.png)

　印刷されたものはまだ届いていないので、届いたら掲載しようと思います。

# 余談

## この「Dash de YES!」を使ってみませんか？
　社内イベントなどで使ってみたい方がいらっしゃいましたら、こちらの記事のコメントか、[Qiitadonの@yyano](https://qiitadon.com/@yyano)までお知らせ下さい。ゆくゆくは有償レンタルができるようにしたいです。